diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-01 01:19:04 +0000 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-01 08:48:17 -0500 |
commit | 15afa8b71c4a42c1969d58c0d35e1fc3a70b16ad (patch) | |
tree | 533646cee8047a796d9f7e3b0c5eedd580c90ba1 | |
parent | 1a2da7da5b003b8b473f67706a04256f49c86c6f (diff) | |
download | cbt-15afa8b71c4a42c1969d58c0d35e1fc3a70b16ad.tar.gz cbt-15afa8b71c4a42c1969d58c0d35e1fc3a70b16ad.tar.bz2 cbt-15afa8b71c4a42c1969d58c0d35e1fc3a70b16ad.zip |
add comprehensive, type-safe proguard plugin
this also demonstrates how to programmatically generate an extensive,
type-safe api for a stringly-typed interface
-rw-r--r-- | examples/proguard-example/Main.scala | 6 | ||||
-rw-r--r-- | examples/proguard-example/build/build.scala | 9 | ||||
-rw-r--r-- | examples/proguard-example/build/build/build.scala | 5 | ||||
-rw-r--r-- | plugins/proguard/build/build.scala | 129 | ||||
-rw-r--r-- | plugins/proguard/build/build/build.scala | 13 | ||||
-rw-r--r-- | plugins/proguard/spec/refcard.html | 259 | ||||
-rw-r--r-- | plugins/proguard/src/generated/Proguard.scala | 226 | ||||
-rw-r--r-- | plugins/proguard/templates/Proguard.scala | 85 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 4 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 1 |
10 files changed, 735 insertions, 2 deletions
diff --git a/examples/proguard-example/Main.scala b/examples/proguard-example/Main.scala new file mode 100644 index 0000000..932f5b5 --- /dev/null +++ b/examples/proguard-example/Main.scala @@ -0,0 +1,6 @@ +package proguard_example +object Main{ + def main( args: Array[String] ): Unit = { + println( Console.GREEN ++ "Hello World" ++ Console.RESET ) + } +} diff --git a/examples/proguard-example/build/build.scala b/examples/proguard-example/build/build.scala new file mode 100644 index 0000000..0b12981 --- /dev/null +++ b/examples/proguard-example/build/build.scala @@ -0,0 +1,9 @@ +import cbt._ + +class Build(val context: Context) extends Proguard{ + def proguard = proguardKeep( (Nil, """ + public class proguard_example.Main{ + public void main(java.lang.String[]); + } + """ ) ) +} diff --git a/examples/proguard-example/build/build/build.scala b/examples/proguard-example/build/build/build.scala new file mode 100644 index 0000000..2a6e11d --- /dev/null +++ b/examples/proguard-example/build/build/build.scala @@ -0,0 +1,5 @@ +import cbt._ + +class Build(val context: Context) extends BuildBuild { + override def dependencies = super.dependencies :+ plugins.proguard +} diff --git a/plugins/proguard/build/build.scala b/plugins/proguard/build/build.scala new file mode 100644 index 0000000..5edd7a8 --- /dev/null +++ b/plugins/proguard/build/build.scala @@ -0,0 +1,129 @@ +package cbt_build.proguard +import cbt._ +import java.nio.file.Files._ +import java.net._ +import java.io._ +import scala.xml._ +class Build(val context: Context) extends Plugin with Scalafmt{ + override def dependencies = ( + super.dependencies ++ // don't forget super.dependencies here for scala-library, etc. + Resolver( mavenCentral ).bind( + MavenDependency("net.sf.proguard","proguard-base","5.3.2") + ) :+ libraries.captureArgs + ) + + def refcard = projectDirectory / "spec/refcard.html" + + /** downloads html proguard parameter specification */ + def updateSpec = { + System.err.println(lib.blue("downloading ")+refcard) + lib.download( + new URL("https://www.guardsquare.com/en/proguard/manual/refcard"), + refcard, + None, + replace = true + ) + System.err.println("simplifying html") + val tables = ( + loadSloppyHtml( new String( readAllBytes( refcard.toPath ) ) ) \ "body" \\ "table" + ) + val s = ( + "<html><body>\n" ++ tables.map( table => + " <table>\n" ++ (table \\ "tr").map( tr => + " <tr>\n" ++ (tr \\ "td").map( td => + " <td>" ++ td.text ++ "</td>\n" + ).mkString ++ " </tr>\n" + ).mkString ++ " </table>\n" + ).mkString ++ "</body></html>\n" + ) + System.err.println("writing file") + write( refcard.toPath, s.getBytes) + } + + private def loadSloppyHtml(html: String): scala.xml.Elem = { + object XmlNotDownloadingDTD extends scala.xml.factory.XMLLoader[scala.xml.Elem] { + override def parser: javax.xml.parsers.SAXParser = { + val f = javax.xml.parsers.SAXParserFactory.newInstance() + f.setNamespaceAware(false) + f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + f.newSAXParser() + } + } + + val p = new org.ccil.cowan.tagsoup.Parser + val w = new StringWriter + p.setContentHandler(new org.ccil.cowan.tagsoup.XMLWriter(w)) + p.parse( + new org.xml.sax.InputSource( + new ByteArrayInputStream( "<!DOCTYPE[^<]*>".r.replaceFirstIn( html, "" ).getBytes ) + ) + ) + XmlNotDownloadingDTD.loadString( w.toString ) + } + + /** generates Scala code from parameter specification html */ + def generate = { + val tables = XML.loadFile(refcard) \\ "table" + def cellsToSeq( node: Node ) = (node \\ "tr").map( + tr => (tr \\ "td").map( td => td.text ) match { + case Seq( k, v ) => k -> v + } + ) + val options = cellsToSeq( tables(0) ).collect{ + case (k, v) if k.startsWith("-") => k.drop(1).split(" ").toList -> v + }.map{ + case (k,description) => + val name = k(0) + val tpe = k.drop(1).mkString(" ") match { + case "" => "Boolean" + case "n" => "Int" + case "class_specification" | "version" | "optimization_filter" => "String" + case "filename" | "directoryname" => "File" + case "class_path" if name === "outjars" => "Seq[File]" + case "class_path" => "ClassPath" + case "[filename]" => "Option[File]" + case "[directory_filter]" | "[package_filter]" | "[package_name]" + | "[attribute_filter]" | "[string]" | "[class_filter]" | "[file_filter]" + => "Option[String]" + case "[,modifier,...] class_specification" => "(Seq[KeepOptionModifier], String)" + } + (name, tpe, description.split("\n").mkString(" ")) + }.sortBy(_._1) + + val docs = options.map{ + case (name, tpe, description) => s" @param $name $description" + }.mkString("\n") + + val args = options.map{ + case v@(_, "Boolean", _) => v -> Some("false") + case v@("injars" | "libraryjars" | "keep" | "outjars", _, _) => v -> None + case (n, t, d) => (n, s"Option[$t]", d) -> Some("None") + }.map{ + case ((name, tpe, description), default) => s" $name: $tpe" ++ default.map(" = "++_).getOrElse("") + }.mkString(",\n") + + val keepModifiers = cellsToSeq( tables(2) ).map{ + case (k, v) => s""" /** $v */\n object $k extends KeepOptionModifier("$k")""" + }.mkString("\n") + + val template = new String( + readAllBytes( + (projectDirectory / "templates/Proguard.scala").toPath + ) + ) + val code = ( + "/* automatically generated by build/build.scala from templates/Proguard.scala */\n" ++ + template + .replace ("/* ${generated-top-level} */", keepModifiers ) + .replace( "${generated-docs}", docs ) + .replace( "/* ${generated-args} */", args ) + ) + + val targetFile = projectDirectory / "src/generated/Proguard.scala" + targetFile.getParentFile.mkdirs + write( targetFile.toPath, code.getBytes ) + + scalafmt + compile + } +} diff --git a/plugins/proguard/build/build/build.scala b/plugins/proguard/build/build/build.scala new file mode 100644 index 0000000..7928cfa --- /dev/null +++ b/plugins/proguard/build/build/build.scala @@ -0,0 +1,13 @@ +package cbt_build.proguard.build +import cbt._ +class Build(val context: Context) extends BuildBuild{ + override def dependencies = ( + super.dependencies ++ // don't forget super.dependencies here for scala-library, etc. + Resolver( mavenCentral, sonatypeReleases ).bind( + ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5"), + "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2.1" + ) ++ Seq( + plugins.scalafmt + ) + ) +} diff --git a/plugins/proguard/spec/refcard.html b/plugins/proguard/spec/refcard.html new file mode 100644 index 0000000..9cb0c72 --- /dev/null +++ b/plugins/proguard/spec/refcard.html @@ -0,0 +1,259 @@ +<html><body> + <table> + <tr> + <td>@filename</td> + <td>Short for '-includefilename'. </td> + </tr> + <tr> + <td>-include filename</td> + <td>Read configuration options from the given file.</td> + </tr> + <tr> + <td>-basedirectory directoryname</td> + <td>Specifies the base directory for subsequent relative file names.</td> + </tr> + <tr> + <td>-injars class_path</td> + <td>Specifies the program jars (or wars, ears, zips, or directories).</td> + </tr> + <tr> + <td>-outjars class_path</td> + <td>Specifies the names of the output jars (or wars, ears, zips, or directories).</td> + </tr> + <tr> + <td>-libraryjars class_path</td> + <td>Specifies the library jars (or wars, ears, zips, or directories).</td> + </tr> + <tr> + <td>-skipnonpubliclibraryclasses</td> + <td>Ignore non-public library classes.</td> + </tr> + <tr> + <td>-dontskipnonpubliclibraryclasses</td> + <td>Don't ignore non-public library classes (the default).</td> + </tr> + <tr> + <td>-dontskipnonpubliclibraryclassmembers</td> + <td>Don't ignore package visible library class members.</td> + </tr> + <tr> + <td>-keepdirectories [directory_filter]</td> + <td>Keep the specified directories in the output jars (or wars, ears, zips, or directories).</td> + </tr> + <tr> + <td>-target version</td> + <td>Set the given version number in the processed classes.</td> + </tr> + <tr> + <td>-forceprocessing</td> + <td>Process the input, even if the output seems up to date.</td> + </tr> + <tr> + <td>-keep [,modifier,...] class_specification</td> + <td>Preserve the specified classes and class members.</td> + </tr> + <tr> + <td>-keepclassmembers [,modifier,...] class_specification</td> + <td>Preserve the specified class members, if their classes are preserved as well.</td> + </tr> + <tr> + <td>-keepclasseswithmembers [,modifier,...] class_specification</td> + <td>Preserve the specified classes and class members, if all of the specified class members are present.</td> + </tr> + <tr> + <td>-keepnames class_specification</td> + <td>Preserve the names of the specified classes and class members (if they aren't removed in the shrinking step).</td> + </tr> + <tr> + <td>-keepclassmembernames class_specification</td> + <td>Preserve the names of the specified class members (if they aren't removed in the shrinking step).</td> + </tr> + <tr> + <td>-keepclasseswithmembernames class_specification</td> + <td>Preserve the names of the specified classes and class members, if all of the specified class members are present (after the shrinking step).</td> + </tr> + <tr> + <td>-printseeds [filename]</td> + <td>List classes and class members matched by the various -keep options, to the standard output or to the given file.</td> + </tr> + <tr> + <td>-dontshrink</td> + <td>Don't shrink the input class files.</td> + </tr> + <tr> + <td>-printusage [filename]</td> + <td>List dead code of the input class files, to the standard output or to the given file.</td> + </tr> + <tr> + <td>-whyareyoukeeping class_specification</td> + <td>Print details on why the given classes and class members are being kept in the shrinking step.</td> + </tr> + <tr> + <td>-dontoptimize</td> + <td>Don't optimize the input class files.</td> + </tr> + <tr> + <td>-optimizations optimization_filter</td> + <td>The optimizations to be enabled and disabled.</td> + </tr> + <tr> + <td>-optimizationpasses n</td> + <td>The number of optimization passes to be performed.</td> + </tr> + <tr> + <td>-assumenosideeffects class_specification</td> + <td>Assume that the specified methods don't have any side effects, while optimizing.</td> + </tr> + <tr> + <td>-allowaccessmodification</td> + <td>Allow the access modifiers of classes and class members to be modified, while optimizing.</td> + </tr> + <tr> + <td>-mergeinterfacesaggressively</td> + <td>Allow any interfaces to be merged, while optimizing.</td> + </tr> + <tr> + <td>-dontobfuscate</td> + <td>Don't obfuscate the input class files.</td> + </tr> + <tr> + <td>-printmapping [filename]</td> + <td>Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file.</td> + </tr> + <tr> + <td>-applymapping filename</td> + <td>Reuse the given mapping, for incremental obfuscation.</td> + </tr> + <tr> + <td>-obfuscationdictionary filename</td> + <td>Use the words in the given text file as obfuscated field names and method names.</td> + </tr> + <tr> + <td>-classobfuscationdictionary filename</td> + <td>Use the words in the given text file as obfuscated class names.</td> + </tr> + <tr> + <td>-packageobfuscationdictionary filename</td> + <td>Use the words in the given text file as obfuscated package names.</td> + </tr> + <tr> + <td>-overloadaggressively</td> + <td>Apply aggressive overloading while obfuscating.</td> + </tr> + <tr> + <td>-useuniqueclassmembernames</td> + <td>Ensure uniform obfuscated class member names for subsequent incremental obfuscation.</td> + </tr> + <tr> + <td>-dontusemixedcaseclassnames</td> + <td>Don't generate mixed-case class names while obfuscating.</td> + </tr> + <tr> + <td>-keeppackagenames [package_filter]</td> + <td>Keep the specified package names from being obfuscated.</td> + </tr> + <tr> + <td>-flattenpackagehierarchy [package_name]</td> + <td>Repackage all packages that are renamed into the single given parent package.</td> + </tr> + <tr> + <td>-repackageclasses [package_name]</td> + <td>Repackage all class files that are renamed into the single given package.</td> + </tr> + <tr> + <td>-keepattributes [attribute_filter]</td> + <td>Preserve the given optional attributes; typically Exceptions, InnerClasses, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, and *Annotation*.</td> + </tr> + <tr> + <td>-keepparameternames</td> + <td>Keep the parameter names and types of methods that are kept.</td> + </tr> + <tr> + <td>-renamesourcefileattribute [string]</td> + <td>Put the given constant string in the SourceFile attributes.</td> + </tr> + <tr> + <td>-adaptclassstrings [class_filter]</td> + <td>Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes.</td> + </tr> + <tr> + <td>-adaptresourcefilenames [file_filter]</td> + <td>Rename the specified resource files, based on the obfuscated names of the corresponding class files.</td> + </tr> + <tr> + <td>-adaptresourcefilecontents [file_filter]</td> + <td>Update the contents of the specified resource files, based on the obfuscated names of the processed classes.</td> + </tr> + <tr> + <td>-dontpreverify</td> + <td>Don't preverify the processed class files.</td> + </tr> + <tr> + <td>-microedition</td> + <td>Target the processed class files at Java Micro Edition.</td> + </tr> + <tr> + <td>-verbose</td> + <td>Write out some more information during processing.</td> + </tr> + <tr> + <td>-dontnote [class_filter]</td> + <td>Don't print notes about potential mistakes or omissions in the configuration.</td> + </tr> + <tr> + <td>-dontwarn [class_filter]</td> + <td>Don't warn about unresolved references at all.</td> + </tr> + <tr> + <td>-ignorewarnings</td> + <td>Print warnings about unresolved references, but continue processing anyhow.</td> + </tr> + <tr> + <td>-printconfiguration [filename]</td> + <td>Write out the entire configuration in traditional ProGuard style, to the standard output or to the given file.</td> + </tr> + <tr> + <td>-dump [filename]</td> + <td>Write out the internal structure of the processed class files, to the standard output or to the given file.</td> + </tr> + </table> + <table> + <tr> + <td>From being removed or renamed</td> + <td>From being renamed</td> + </tr> + <tr> + <td>Classes and class members</td> + <td>-keep</td> + <td>-keepnames</td> + </tr> + <tr> + <td>Class members only</td> + <td>-keepclassmembers</td> + <td>-keepclassmembernames</td> + </tr> + <tr> + <td>Classes and class members, if class members present</td> + <td>-keepclasseswithmembers</td> + <td>-keepclasseswithmembernames</td> + </tr> + </table> + <table> + <tr> + <td>includedescriptorclasses</td> + <td>Also keep any classes in the descriptors of specified fields and methods.</td> + </tr> + <tr> + <td>allowshrinking</td> + <td>Allow the specified entry points to be removed in the shrinking step.</td> + </tr> + <tr> + <td>allowoptimization</td> + <td>Allow the specified entry points to be modified in the optimization step.</td> + </tr> + <tr> + <td>allowobfuscation</td> + <td>Allow the specified entry points to be renamed in the obfuscation step.</td> + </tr> + </table> +</body></html> diff --git a/plugins/proguard/src/generated/Proguard.scala b/plugins/proguard/src/generated/Proguard.scala new file mode 100644 index 0000000..553df07 --- /dev/null +++ b/plugins/proguard/src/generated/Proguard.scala @@ -0,0 +1,226 @@ +/* automatically generated by build/build.scala from templates/Proguard.scala */ +package cbt +import java.io.File +import java.nio.file.Files.deleteIfExists + +sealed class KeepOptionModifier(val string: String) +object KeepOptionModifier { + + /** Also keep any classes in the descriptors of specified fields and methods. */ + object includedescriptorclasses + extends KeepOptionModifier("includedescriptorclasses") + + /** Allow the specified entry points to be removed in the shrinking step. */ + object allowshrinking extends KeepOptionModifier("allowshrinking") + + /** Allow the specified entry points to be modified in the optimization step. */ + object allowoptimization extends KeepOptionModifier("allowoptimization") + + /** Allow the specified entry points to be renamed in the obfuscation step. */ + object allowobfuscation extends KeepOptionModifier("allowobfuscation") +} + +trait Proguard extends BaseBuild { + def proguardKeep(keep: (Seq[KeepOptionModifier], String)) = { + ProguardLib(context.cbtLastModified, context.paths.mavenCache).proguard( + outjars = Seq(scalaTarget / "proguarded.jar"), + injars = classpath, + libraryjars = Proguard.`rt.jar`, + keep = keep + ) + } +} + +object Proguard { + val version = "5.3.2" + val `rt.jar` = ClassPath( + Seq(new File(System.getProperty("java.home"), "lib/rt.jar"))) +} + +case class ProguardLib( + cbtLastModified: Long, + mavenCache: File, + dependency: Option[DependencyImplementation] = None +)( + implicit logger: Logger, + transientCache: java.util.Map[AnyRef, AnyRef], + classLoaderCache: ClassLoaderCache +) { + + /** + Typed interface on top of the proguard command line tool. + Check the official ProGuard docs for usage. + Use `Some(None)` to call an option without arguments. + Use `true` to set a flag. + + @see https://www.guardsquare.com/en/proguard/manual/refcard + @see https://www.guardsquare.com/en/proguard/manual/usage + + @param adaptclassstrings Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. + @param adaptresourcefilecontents Update the contents of the specified resource files, based on the obfuscated names of the processed classes. + @param adaptresourcefilenames Rename the specified resource files, based on the obfuscated names of the corresponding class files. + @param allowaccessmodification Allow the access modifiers of classes and class members to be modified, while optimizing. + @param applymapping Reuse the given mapping, for incremental obfuscation. + @param assumenosideeffects Assume that the specified methods don't have any side effects, while optimizing. + @param basedirectory Specifies the base directory for subsequent relative file names. + @param classobfuscationdictionary Use the words in the given text file as obfuscated class names. + @param dontnote Don't print notes about potential mistakes or omissions in the configuration. + @param dontobfuscate Don't obfuscate the input class files. + @param dontoptimize Don't optimize the input class files. + @param dontpreverify Don't preverify the processed class files. + @param dontshrink Don't shrink the input class files. + @param dontskipnonpubliclibraryclasses Don't ignore non-public library classes (the default). + @param dontskipnonpubliclibraryclassmembers Don't ignore package visible library class members. + @param dontusemixedcaseclassnames Don't generate mixed-case class names while obfuscating. + @param dontwarn Don't warn about unresolved references at all. + @param dump Write out the internal structure of the processed class files, to the standard output or to the given file. + @param flattenpackagehierarchy Repackage all packages that are renamed into the single given parent package. + @param forceprocessing Process the input, even if the output seems up to date. + @param ignorewarnings Print warnings about unresolved references, but continue processing anyhow. + @param include Read configuration options from the given file. + @param injars Specifies the program jars (or wars, ears, zips, or directories). + @param keep Preserve the specified classes and class members. + @param keepattributes Preserve the given optional attributes; typically Exceptions, InnerClasses, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, and *Annotation*. + @param keepclasseswithmembernames Preserve the names of the specified classes and class members, if all of the specified class members are present (after the shrinking step). + @param keepclasseswithmembers Preserve the specified classes and class members, if all of the specified class members are present. + @param keepclassmembernames Preserve the names of the specified class members (if they aren't removed in the shrinking step). + @param keepclassmembers Preserve the specified class members, if their classes are preserved as well. + @param keepdirectories Keep the specified directories in the output jars (or wars, ears, zips, or directories). + @param keepnames Preserve the names of the specified classes and class members (if they aren't removed in the shrinking step). + @param keeppackagenames Keep the specified package names from being obfuscated. + @param keepparameternames Keep the parameter names and types of methods that are kept. + @param libraryjars Specifies the library jars (or wars, ears, zips, or directories). + @param mergeinterfacesaggressively Allow any interfaces to be merged, while optimizing. + @param microedition Target the processed class files at Java Micro Edition. + @param obfuscationdictionary Use the words in the given text file as obfuscated field names and method names. + @param optimizationpasses The number of optimization passes to be performed. + @param optimizations The optimizations to be enabled and disabled. + @param outjars Specifies the names of the output jars (or wars, ears, zips, or directories). + @param overloadaggressively Apply aggressive overloading while obfuscating. + @param packageobfuscationdictionary Use the words in the given text file as obfuscated package names. + @param printconfiguration Write out the entire configuration in traditional ProGuard style, to the standard output or to the given file. + @param printmapping Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file. + @param printseeds List classes and class members matched by the various -keep options, to the standard output or to the given file. + @param printusage List dead code of the input class files, to the standard output or to the given file. + @param renamesourcefileattribute Put the given constant string in the SourceFile attributes. + @param repackageclasses Repackage all class files that are renamed into the single given package. + @param skipnonpubliclibraryclasses Ignore non-public library classes. + @param target Set the given version number in the processed classes. + @param useuniqueclassmembernames Ensure uniform obfuscated class member names for subsequent incremental obfuscation. + @param verbose Write out some more information during processing. + @param whyareyoukeeping Print details on why the given classes and class members are being kept in the shrinking step. + */ + case class proguard( + adaptclassstrings: Option[Option[String]] = None, + adaptresourcefilecontents: Option[Option[String]] = None, + adaptresourcefilenames: Option[Option[String]] = None, + allowaccessmodification: Boolean = false, + applymapping: Option[File] = None, + assumenosideeffects: Option[String] = None, + basedirectory: Option[File] = None, + classobfuscationdictionary: Option[File] = None, + dontnote: Option[Option[String]] = None, + dontobfuscate: Boolean = false, + dontoptimize: Boolean = false, + dontpreverify: Boolean = false, + dontshrink: Boolean = false, + dontskipnonpubliclibraryclasses: Boolean = false, + dontskipnonpubliclibraryclassmembers: Boolean = false, + dontusemixedcaseclassnames: Boolean = false, + dontwarn: Option[Option[String]] = None, + dump: Option[Option[File]] = None, + flattenpackagehierarchy: Option[Option[String]] = None, + forceprocessing: Boolean = false, + ignorewarnings: Boolean = false, + include: Option[File] = None, + injars: ClassPath, + keep: (Seq[KeepOptionModifier], String), + keepattributes: Option[Option[String]] = None, + keepclasseswithmembernames: Option[String] = None, + keepclasseswithmembers: Option[(Seq[KeepOptionModifier], String)] = None, + keepclassmembernames: Option[String] = None, + keepclassmembers: Option[(Seq[KeepOptionModifier], String)] = None, + keepdirectories: Option[Option[String]] = None, + keepnames: Option[String] = None, + keeppackagenames: Option[Option[String]] = None, + keepparameternames: Boolean = false, + libraryjars: ClassPath, + mergeinterfacesaggressively: Boolean = false, + microedition: Boolean = false, + obfuscationdictionary: Option[File] = None, + optimizationpasses: Option[Int] = None, + optimizations: Option[String] = None, + outjars: Seq[File], + overloadaggressively: Boolean = false, + packageobfuscationdictionary: Option[File] = None, + printconfiguration: Option[Option[File]] = None, + printmapping: Option[Option[File]] = None, + printseeds: Option[Option[File]] = None, + printusage: Option[Option[File]] = None, + renamesourcefileattribute: Option[Option[String]] = None, + repackageclasses: Option[Option[String]] = None, + skipnonpubliclibraryclasses: Boolean = false, + target: Option[String] = None, + useuniqueclassmembernames: Boolean = false, + verbose: Boolean = false, + whyareyoukeeping: Option[String] = None + ) extends (() => ClassPath) { + + // type class rendering scala values into string arguments + private class valueToStrings[T](val apply: T => Option[Seq[String]]) + private object valueToStrings { + def apply[T: valueToStrings](value: T) = + implicitly[valueToStrings[T]].apply(value) + implicit object SeqFile + extends valueToStrings[Seq[File]](v => Some(v.map(_.string))) + implicit object ClassPath + extends valueToStrings[ClassPath](v => Some(Seq(v.string))) + implicit object File + extends valueToStrings[File](v => Some(Seq(v.string))) + implicit object String extends valueToStrings[String](v => Some(Seq(v))) + implicit object Int + extends valueToStrings[Int](i => Some(Seq(i.toString))) + implicit object Boolean + extends valueToStrings[Boolean]({ + case false => None + case true => Some(Nil) + }) + implicit def Option2[T: valueToStrings]: valueToStrings[Option[T]] = + new valueToStrings( + _.map(implicitly[valueToStrings[T]].apply(_).toSeq.flatten) + ) + implicit def Option3[T: valueToStrings] + : valueToStrings[Option[Option[String]]] = + new valueToStrings(_.map(_.toSeq)) + implicit def SpecWithModifiers: valueToStrings[(Seq[KeepOptionModifier], + String)] = + new valueToStrings({ + case (modifiers, spec) => + Some( + Seq(modifiers.map(_.string).map("," ++ _).mkString) + .filterNot(_ == "") :+ spec) + }) + } + + // capture string argument values and names + val capturedArgs = capture_args.captureArgs + + def apply: ClassPath = { + val args = capturedArgs.args + .map(arg => arg.copy(name = "-" ++ arg.name)) + .flatMap(_.toSeqOption) + .flatten + outjars.map(_.toPath).map(deleteIfExists) + val c = dependency getOrElse MavenResolver(cbtLastModified, + mavenCache, + mavenCentral).bindOne( + MavenDependency("net.sf.proguard", "proguard-base", Proguard.version) + ) runMain ( + "proguard.ProGuard", + args: _* + ) + if (c != ExitCode.Success) throw new Exception + ClassPath(outjars) + } + } +} diff --git a/plugins/proguard/templates/Proguard.scala b/plugins/proguard/templates/Proguard.scala new file mode 100644 index 0000000..23a3117 --- /dev/null +++ b/plugins/proguard/templates/Proguard.scala @@ -0,0 +1,85 @@ +package cbt +import java.io.File +import java.nio.file.Files.deleteIfExists + +sealed class KeepOptionModifier(val string: String) +object KeepOptionModifier{ +/* ${generated-top-level} */ +} + +trait Proguard extends BaseBuild { + def proguard( keep: (Seq[KeepOptionModifier], String) ) = { + ProguardLib(context.cbtLastModified, context.paths.mavenCache).proguard( + outjars = Seq( scalaTarget / "proguarded.jar" ), + injars = classpath, + libraryjars = Proguard.`rt.jar`, + keep = keep + ) + } +} + +object Proguard{ + val version = "5.3.2" + val `rt.jar` = ClassPath( Seq( new File(System.getProperty("java.home"),"lib/rt.jar") ) ) +} + +case class ProguardLib( + cbtLastModified: Long, mavenCache: File, + dependency: Option[DependencyImplementation] = None +)( + implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef], classLoaderCache: ClassLoaderCache +){ + /** + Typed interface on top of the proguard command line tool. + Check the official ProGuard docs for usage. + Use `Some(None)` to call an option without arguments. + Use `true` to set a flag. + + @see https://www.guardsquare.com/en/proguard/manual/refcard + @see https://www.guardsquare.com/en/proguard/manual/usage + +${generated-docs} + */ + case class proguard( +/* ${generated-args} */ + ) extends ( () => ClassPath ){ + + // type class rendering scala values into string arguments + private class valueToStrings[T]( val apply: T => Option[Seq[String]] ) + private object valueToStrings{ + def apply[T:valueToStrings](value: T) = implicitly[valueToStrings[T]].apply(value) + implicit object SeqFile extends valueToStrings[Seq[File]](v => Some(v.map(_.string))) + implicit object ClassPath extends valueToStrings[ClassPath](v => Some(Seq(v.string))) + implicit object File extends valueToStrings[File](v => Some(Seq(v.string))) + implicit object String extends valueToStrings[String](v => Some(Seq(v))) + implicit object Int extends valueToStrings[Int](i => Some(Seq(i.toString))) + implicit object Boolean extends valueToStrings[Boolean]({ + case false => None + case true => Some(Nil) + }) + implicit def Option2[T:valueToStrings]: valueToStrings[Option[T]] = new valueToStrings( + _.map(implicitly[valueToStrings[T]].apply(_).toSeq.flatten) + ) + implicit def Option3[T:valueToStrings]: valueToStrings[Option[Option[String]]] = new valueToStrings(_.map(_.toSeq)) + implicit def SpecWithModifiers: valueToStrings[(Seq[KeepOptionModifier], String)] = new valueToStrings({ + case (modifiers, spec) => Some( Seq( modifiers.map(_.string).map(","++_).mkString ).filterNot(_ == "") :+ spec ) + }) + } + + // capture string argument values and names + val capturedArgs = capture_args.captureArgs + + def apply: ClassPath = { + val args = capturedArgs.args.map(arg => arg.copy(name="-"++arg.name)).flatMap(_.toSeqOption).flatten + outjars.map(_.toPath).map(deleteIfExists) + val c = dependency getOrElse MavenResolver( cbtLastModified, mavenCache, mavenCentral ).bindOne( + MavenDependency("net.sf.proguard","proguard-base",Proguard.version) + ) runMain ( + "proguard.ProGuard", + args : _* + ) + if(c != ExitCode.Success) throw new Exception + ClassPath(outjars) + } + } +} diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index 71e6ee5..f2f468c 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -56,8 +56,8 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ def write(file: File, content: String, options: OpenOption*): File = Stage0Lib.write(file, content, options:_*) - def download(url: URL, target: File, sha1: Option[String]): Boolean = { - if( target.exists ){ + def download(url: URL, target: File, sha1: Option[String], replace: Boolean = false): Boolean = { + if( target.exists && !replace ){ logger.resolver(green("found ") ++ url.string) true } else { diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index d97e186..ec39890 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -21,6 +21,7 @@ trait BuildBuildWithoutEssentials extends BaseBuild{ object plugins{ // TODO: move this out of the OO + final lazy val proguard = DirectoryDependency( context.cbtHome ++ "/plugins/proguard" ) final lazy val scalaTest = DirectoryDependency( context.cbtHome ++ "/plugins/scalatest" ) final lazy val sbtLayout = DirectoryDependency( context.cbtHome ++ "/plugins/sbt_layout" ) final lazy val scalaJs = DirectoryDependency( context.cbtHome ++ "/plugins/scalajs" ) |