Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement drop-in CLI #37

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ lazy val jarjar_assembly = project
Compile / packageBin := (jarjar / assembly).value
})

lazy val jarjar_abrams_assembly = project
.settings(nocomma {
crossScalaVersions := Vector(scala212, scala213, scala211, scala210)
name := "jarjar-abrams-assembly"
Compile / packageBin := (core / assembly).value
})

lazy val core = project
.enablePlugins(ContrabandPlugin)
.dependsOn(jarjar)
Expand All @@ -118,9 +125,10 @@ lazy val core = project

testFrameworks += new TestFramework("verify.runner.Framework")

Compile / scalacOptions += "-deprecation"
Compile / scalacOptions ++= {
if (scalaVersion.value.startsWith("2.13.")) Vector("-Xlint")
else Vector("-Xlint", "-Xfatal-warnings")
if (scalaVersion.value.startsWith("2.12.")) Vector("-Xlint", "-Xfatal-warnings")
else Vector("-Xlint")
}
})

Expand Down Expand Up @@ -163,3 +171,9 @@ ThisBuild / publishTo := {
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
ThisBuild / publishMavenStyle := true
ThisBuild / assemblyMergeStrategy := {
case "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
}
82 changes: 82 additions & 0 deletions core/src/main/scala/com/eed3si9n/jarjarabrams/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.eed3si9n.jarjarabrams

import com.eed3si9n.jarjar.{ MainUtil, RulesFileParser }
import java.nio.file.Path;
import scala.collection.JavaConverters._

class Main {
def help(): Unit = Main.printHelp()

def process(rulesFile: Path, inJar: Path, outJar: Path): Unit = {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new entry point, which is used via reflection implemented in MainUtil.runMain.

if (rulesFile == null || inJar == null || outJar == null) {
throw new IllegalArgumentException("rulesFile, inJar, and outJar are required");
}
val rules = RulesFileParser
.parse(rulesFile.toFile)
.asScala
.toList
.map(Shader.toShadeRule)
val verbose = java.lang.Boolean.getBoolean("verbose")
val skipManifest = java.lang.Boolean.getBoolean("skipManifest");
Shader.shadeFile(rules, inJar, outJar, verbose, skipManifest)
}
}

object Main {
def main(args: Array[String]): Unit = {
MainUtil.runMain(new Main(), args, "help")
}

def printHelp(): Unit = {
val jarName = "jarjar-abrams-assembly.jar"
val str = s"""Jar Jar Abrams - A utility to repackage and embed Java and Scala libraries

Command-line usage:

java -jar $jarName [help]

Prints this help message.

java -jar $jarName process <rulesFile> <inJar> <outJar>

Transform the <inJar> jar file, writing a new jar file to <outJar>.
Any existing file named by <outJar> will be deleted.

The transformation is defined by a set of rules in the file specified
by the rules argument (see below).

Rules file format:

The rules file is a text file, one rule per line. Leading and trailing
whitespace is ignored. There are three types of rules:

rule <pattern> <result>
zap <pattern>
keep <pattern>

The standard rule ("rule") is used to rename classes. All references
to the renamed classes will also be updated. If a class name is
matched by more than one rule, only the first one will apply.

<pattern> is a class name with optional wildcards. "**" will
match against any valid class name substring. To match a single
package component (by excluding "." from the match), a single "*" may
be used instead.

<result> is a class name which can optionally reference the
substrings matched by the wildcards. A numbered reference is available
for every "*" or "**" in the <pattern>, starting from left to
right: "@1", "@2", etc. A special "@0" reference contains the entire
matched class name.

The "zap" rule causes any matched class to be removed from the resulting
jar file. All zap rules are processed before renaming rules.

The "keep" rule marks all matched classes as "roots". If any keep
rules are defined all classes which are not reachable from the roots
via dependency analysis are discarded when writing the output
jar. This is the last step in the process, after renaming and zapping.
"""
str.linesIterator.foreach(Console.err.println)
}
}
73 changes: 68 additions & 5 deletions core/src/main/scala/com/eed3si9n/jarjarabrams/Shader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,81 @@ package com.eed3si9n.jarjarabrams
import java.nio.file.{ Files, Path, StandardOpenOption }
import com.eed3si9n.jarjar.{ JJProcessor, _ }
import com.eed3si9n.jarjar.util.EntryStruct
import Zip.{ createDirectories, resetModifiedTime }
import scala.collection.JavaConverters._

object Shader {
def shadeFile(
rules: Seq[ShadeRule],
inputJar: Path,
outputJar: Path,
verbose: Boolean,
skipManifest: Boolean
): Unit = {
val tempDir = Files.createTempDirectory("jarjar-in")
val outDir = Files.createTempDirectory("jarjar-out")
val tempJar = Files.createTempFile("jarjar", ".jar")
Zip.unzip(inputJar, tempDir)
shadeDirectory(
rules,
outDir,
makeMappings(tempDir),
verbose = verbose,
skipManifest = skipManifest
)
Zip.zip(makeMappings(outDir), tempJar)
resetModifiedTime(tempJar)
if (Files.exists(outputJar)) {
Files.delete(outputJar)
}
createDirectories(outputJar.getParent)
Files.copy(tempJar, outputJar)
resetModifiedTime(outputJar)
}

def makeMappings(dir: Path): List[(Path, String)] =
Files.walk(dir).iterator().asScala.toList.flatMap { x =>
if (x == dir) None
else Some(x -> dir.relativize(x).toString)
}

def shadeDirectory(
rules: Seq[ShadeRule],
dir: Path,
mappings: Seq[(Path, String)],
verbose: Boolean
): Unit = shadeDirectory(rules, dir, mappings, verbose, skipManifest = true)

def shadeDirectory(
rules: Seq[ShadeRule],
dir: Path,
mappings: Seq[(Path, String)],
verbose: Boolean,
skipManifest: Boolean
): Unit =
if (rules.isEmpty) {} else {
val shader = bytecodeShader(rules, verbose)
if (rules.isEmpty) ()
else {
val shader = bytecodeShader(rules, verbose, skipManifest)
for {
(path, name) <- mappings
if !Files.isDirectory(path)
bytes = Files.readAllBytes(path)
_ = Files.delete(path)
(shadedBytes, shadedName) <- shader(bytes, name)
out = dir.resolve(shadedName)
_ = Files.createDirectories(out.getParent)
_ = createDirectories(out.getParent)
_ = Files.write(out, shadedBytes, StandardOpenOption.CREATE)
} yield ()
Files.walk(dir).iterator().asScala.toList.foreach { x =>
if (x == dir) ()
else Zip.resetModifiedTime(x)
}
}

def bytecodeShader(
rules: Seq[ShadeRule],
verbose: Boolean
verbose: Boolean,
skipManifest: Boolean
): (Array[Byte], String) => Option[(Array[Byte], String)] =
if (rules.isEmpty)(bytes, mapping) => Some(bytes -> mapping)
else {
Expand Down Expand Up @@ -57,7 +107,12 @@ object Shader {
}
}

val proc = new JJProcessor(jjrules, verbose, true, null)
val proc = new JJProcessor(
patterns = jjrules,
verbose = verbose,
skipManifest = skipManifest,
misplacedClassStrategy = null
)
val excludes = proc.getExcludes

(bytes, mapping) =>
Expand All @@ -77,6 +132,14 @@ object Shader {
else
None
}

def toShadeRule(rule: PatternElement): ShadeRule =
rule match {
case r: Rule =>
ShadeRule(ShadeRule.rename((r.getPattern, r.getResult)), Vector(ShadeTarget.inAll))
case r: Keep => ShadeRule(ShadeRule.keep((r.getPattern)), Vector(ShadeTarget.inAll))
case r: Zap => ShadeRule(ShadeRule.zap((r.getPattern)), Vector(ShadeTarget.inAll))
}
}

sealed trait ShadePattern {
Expand Down
86 changes: 86 additions & 0 deletions core/src/main/scala/com/eed3si9n/jarjarabrams/Using.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.eed3si9n.jarjarabrams

import java.io.{
BufferedInputStream,
BufferedOutputStream,
FileInputStream,
FileOutputStream,
InputStream,
IOException,
OutputStream
}
import java.nio.file.{ Files, Path }
import java.util.zip.ZipInputStream
import scala.util.control.NonFatal
import scala.reflect.ClassTag

abstract class Using[Source, A] {
protected def open(src: Source): A
def apply[R](src: Source)(f: A => R): R = {
val resource = open(src)
try {
f(resource)
} finally {
close(resource)
}
}
protected def close(out: A): Unit
}

object Using {
def fileOutputStream(append: Boolean): Using[Path, OutputStream] =
file(f => new BufferedOutputStream(new FileOutputStream(f.toFile, append)))
val fileInputStream: Using[Path, InputStream] =
file(f => new BufferedInputStream(new FileInputStream(f.toFile)))

val zipInputStream = wrap((in: InputStream) => new ZipInputStream(in))

def file[A1 <: AutoCloseable](action: Path => A1): Using[Path, A1] =
file(action, closeCloseable)

def file[A1](action: Path => A1, closeF: A1 => Unit): Using[Path, A1] =
new OpenFile[A1] {
def openImpl(file: Path) = action(file)
def close(a: A1) = closeF(a)
}

def wrap[Source: ClassTag, A1 <: AutoCloseable: ClassTag](
action: Source => A1
): Using[Source, A1] =
wrap(action, closeCloseable)

def wrap[Source: ClassTag, A1: ClassTag](
action: Source => A1,
closeF: A1 => Unit
): Using[Source, A1] =
new WrapUsing[Source, A1] {
def openImpl(source: Source) = action(source)
def close(a: A1) = closeF(a)
}

private def closeCloseable[A1 <: AutoCloseable]: A1 => Unit = _.close()

private abstract class WrapUsing[Source: ClassTag, A1: ClassTag] extends Using[Source, A1] {
protected def label[A: ClassTag] = implicitly[ClassTag[A]].runtimeClass.getSimpleName
protected def openImpl(source: Source): A1
protected final def open(source: Source): A1 =
try {
openImpl(source)
} catch {
case NonFatal(e) =>
throw new RuntimeException(s"error wrapping ${label[Source]} in ${label[A1]}", e)
}
}

private trait OpenFile[A] extends Using[Path, A] {
protected def openImpl(file: Path): A
protected final def open(file: Path): A = {
val parent = file.getParent
if (parent != null) {
try Files.createDirectories(parent)
catch { case _: IOException => }
}
openImpl(file)
}
}
}
Loading