Skip to content

Commit

Permalink
Proguard contrib module (#972)
Browse files Browse the repository at this point in the history
This module will run `proguard` on the normal `assembly` output jar to shrink it. By default all steps - shrink, optimize, obfuscate, and preverify - are run though this can be configured. The default entrypoint is `finalMainClass`. Any additional options can be specified freely with `additionalOptions` (proguard has many).

Commits:

* WIP: Initial proguard implementation

* More progress: get proguard actually running

* Finish proguard plugin

* Reset file

* PR feedback

* Add documentation; use Java home binary

* Add ScalaDoc

* PR feedback

* Add some basic tests

Co-authored-by: Stephen Bly <[email protected]>

Pull request: #972
  • Loading branch information
sbly authored Nov 2, 2020
1 parent aa3240a commit f555644
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 0 deletions.
4 changes: 4 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@ object contrib extends MillModule {
}
}

object proguard extends MillModule {
override def compileModuleDeps = Seq(scalalib)
}

object tut extends MillModule {
override def compileModuleDeps = Seq(scalalib)
def testArgs = T{
Expand Down
129 changes: 129 additions & 0 deletions contrib/proguard/src/Proguard.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package mill.contrib.proguard

import coursier.Repositories
import mill.T
import mill.Agg
import mill.api.{Logger, Loose, PathRef, Result}
import mill.define.{Sources, Target}
import mill.scalalib.Lib.resolveDependencies
import mill.scalalib.{Dep, DepSyntax, Lib, ScalaModule}
import os.proc
import os.Path
import os.PathChunk

/** Adds proguard capabilities when mixed-in to a module
*
* The target name is `proguard`. This runs proguard on the output jar of `asssembly`
* and outputs a shrunk/obfuscated/optimized jar under `out.jar` in the `dest/` folder.
*
* Sensible defaults are provided, so no members require overriding..
*
*/
trait Proguard extends ScalaModule {

/** The version of proguard to download from Maven.
*
* Note that currently this is the only available version. */
def proguardVersion: T[String] = T { "7.0.0" }

/** Run the "shrink" step in the proguard pipeline. Defaults to true. */
def shrink: T[Boolean] = T { true }

/** Run the "optimize" step in the proguard pipeline. Defaults to true. */
def optimize: T[Boolean] = T { true }

/** Run the "obfuscate" step in the proguard pipeline. Defaults to true. */
def obfuscate: T[Boolean] = T { true }

/** Run the "optimize" step in the proguard pipeline. Defaults to true.
*
* Note that this is required for Java 7 and above.
*/
def preverify: T[Boolean] = T { true }

/** The path to JAVA_HOME.
*
* This is used for both the `java` command binary,
* as well as the standard library jars.
* Defaults to the `java.home` system property. */
def javaHome: T[PathRef] = T.input {
PathRef(Path(System.getProperty("java.home")))
}

/** Specifies the input jar to proguard. Defaults to the output of the `assembly` task. */
def inJar: T[PathRef] = T { assembly() }

/** The library jars proguard requires
* Defaults the jars under `javaHome`. */
def libraryJars: T[Seq[PathRef]] = T {
val javaJars = os.list(javaHome().path / "lib", sort = false).filter(_.ext == "jar")
javaJars.toSeq.map(PathRef(_))
}

/** Run the proguard task.
*
* The full command will be printed when run.
* The stdout and stderr of the command are written to the `dest/` folder.
* The output jar is written to `dest/our.jar`. */
def proguard: T[PathRef] = T {
val outJar = T.dest / "out.jar"
val java = javaHome().path / "bin" / "java"

val cmd = os.proc(
java,
"-cp",
proguardClasspath().map(_.path).mkString(":"),
"proguard.ProGuard",
steps(),
"-injars",
inJar().path,
"-outjars",
outJar,
"-libraryjars",
libraryJars().map(_.path).mkString(":"),
entryPoint(),
additionalOptions()
)
System.out.println(cmd.command.flatMap(_.value).mkString(" "))
cmd.call(stdout = T.dest / "stdout.txt", stderr = T.dest / "stderr.txt")

// the call above already throws an exception on a non-zero exit code,
// so if we reached this point we've succeeded!
PathRef(outJar)
}

/** The location of the proguard jar files.
* These are downloaded from JCenter and fed to `java -cp`
*/
def proguardClasspath: T[Loose.Agg[PathRef]] = T {
resolveDependencies(
Seq(Repositories.jcenter),
Lib.depToDependencyJava(_),
Seq(ivy"com.guardsquare:proguard-base:${proguardVersion()}"))
}

private def steps: T[Seq[String]] = T {
(if (optimize()) Seq() else Seq("-dontoptimize")) ++
(if (obfuscate()) Seq() else Seq("-dontobfuscate")) ++
(if (shrink()) Seq() else Seq("-dontshrink")) ++
(if (preverify()) Seq() else Seq("-dontpreverify"))
}

/** The default `entrypoint` to proguard.
*
* Defaults to the `main` method of `finalMainClass`.
* Can be overriden to specify a different entrypoint,
* or additional entrypoints can be specified with `additionalOptions`. */
def entryPoint: T[String] = T {
s"""|-keep public class ${finalMainClass()} {
| public static void main(java.lang.String[]);
|}
|""".stripMargin
}

/** Specify any additional options to proguard.
*
* These are fed as-is to the proguard command.
* */
def additionalOptions: T[Seq[String]] = T { Seq[String]() }
}
5 changes: 5 additions & 0 deletions contrib/proguard/test/resources/proguard/src/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import java.nio.file.{Files, Paths}

object Main extends App {

}
50 changes: 50 additions & 0 deletions contrib/proguard/test/src/ProguardTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package mill.contrib.proguard

import mill._
import mill.define.Sources
import mill.define.Target
import mill.scalalib.ScalaModule
import mill.util.TestEvaluator
import mill.util.TestUtil
import os.Path
import utest._
import utest.framework.TestPath

object ProguardTests extends TestSuite {

object Proguard
extends TestUtil.BaseModule
with scalalib.ScalaModule
with Proguard {
// override build root to test custom builds/modules
override def millSourcePath: Path = TestUtil.getSrcPathStatic()
override def scalaVersion = "2.12.0"
}

val testModuleSourcesPath: Path = os.pwd / 'contrib / 'proguard / 'test / 'resources / "proguard"

def workspaceTest[T](m: TestUtil.BaseModule)(t: TestEvaluator => T)(
implicit tp: TestPath): T = {
val eval = new TestEvaluator(m)
os.remove.all(m.millSourcePath)
os.remove.all(eval.outPath)
os.makeDir.all(m.millSourcePath / os.up)
os.copy(testModuleSourcesPath, m.millSourcePath)
t(eval)
}

def tests: Tests = Tests {
'proguard - {
"should download proguard jars" - workspaceTest(Proguard) { eval =>
val Right((agg, _)) = eval.apply(Proguard.proguardClasspath)
assert(!agg.isEmpty)
}

"create proguarded jar" - workspaceTest(Proguard) { eval =>
val Right((path, _)) = eval.apply(Proguard.proguard)
assert(os.exists(path.path))
}

}
}
}
32 changes: 32 additions & 0 deletions docs/pages/9 - Contrib Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,38 @@ object app extends ScalaModule with RouterModule {
}
```

## Proguard

This module allows [Proguard](https://www.guardsquare.com/en/products/proguard/manual/introduction) to be used in Mill builds.
ProGuard is a Java class file shrinker, optimizer, obfuscator, and preverifier.

By default, all four steps - shrink, optimize, obfuscate, verify - are run, but this can be configured through task options.
Any additional options can be specified as a list of strings with `additionalOptions`. The full list of proguard options
can be found [here](https://www.guardsquare.com/en/products/proguard/manual/usage).

The output of `assembly` is used as the input jar and the output is written to `out.jar` in the `dest` folder.

The `stdout` and `stderr` from the proguard command can be found under the `dest` folder.

The only default entrypoint is the main class (i.e. `finalMainClass` task). Additional entrypoints can be configured using `additionalOptions` as well.

Here is a simple example:

```
import $ivy.`com.lihaoyi::mill-contrib-proguard:$MILL_VERSION`
import contrib.proguard._
object foo extends ScalaModule with Proguard {
def scalaVersion = "2.12.0"
override def shrink: T[Boolean] = T { true }
override def optimize: T[Boolean] = T { false }
override def obfuscate: T[Boolean] = T { false }
}
```

Also, please note that Proguard doesn't seem to work with scala 2.13 yet.

## ScalaPB

This module allows [ScalaPB](https://scalapb.github.io) to be used in Mill builds. ScalaPB is a [Protocol Buffers](https://developers.google.com/protocol-buffers/) compiler plugin that generates Scala case classes, encoders and decoders for protobuf messages.
Expand Down

0 comments on commit f555644

Please sign in to comment.