diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-04 03:02:04 +0000 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-04 20:43:25 -0500 |
commit | c6fd3f43a9d2b2f38442602769e920803a7d43ba (patch) | |
tree | e1561087d27c603075b89877213818603c3787d3 | |
parent | 4bd0255f0a39d87652c032533ce45eb65e8f3b1e (diff) | |
download | cbt-c6fd3f43a9d2b2f38442602769e920803a7d43ba.tar.gz cbt-c6fd3f43a9d2b2f38442602769e920803a7d43ba.tar.bz2 cbt-c6fd3f43a9d2b2f38442602769e920803a7d43ba.zip |
separate type-safe proguard wrapper into self-contained library
also make logic to maintain auto-generated sections re-usable
-rw-r--r-- | examples/proguard-example/build/build.scala | 4 | ||||
-rw-r--r-- | libraries/proguard/Proguard.scala | 249 | ||||
-rw-r--r-- | libraries/proguard/Readme.md | 4 | ||||
-rw-r--r-- | libraries/proguard/build/build.scala | 151 | ||||
-rw-r--r-- | libraries/proguard/build/build/build.scala (renamed from plugins/proguard/build/build/build.scala) | 0 | ||||
-rw-r--r-- | libraries/proguard/spec/refcard.html (renamed from plugins/proguard/spec/refcard.html) | 0 | ||||
-rw-r--r-- | plugins/proguard/Proguard.scala | 32 | ||||
-rw-r--r-- | plugins/proguard/build/build.scala | 125 | ||||
-rw-r--r-- | plugins/proguard/src/generated/Proguard.scala | 226 | ||||
-rw-r--r-- | plugins/proguard/templates/Proguard.scala | 85 | ||||
-rw-r--r-- | stage1/cbt.scala | 3 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 13 | ||||
-rw-r--r-- | stage2/plugins/GeneratedSections.scala | 40 |
13 files changed, 493 insertions, 439 deletions
diff --git a/examples/proguard-example/build/build.scala b/examples/proguard-example/build/build.scala index 0b12981..2b3709e 100644 --- a/examples/proguard-example/build/build.scala +++ b/examples/proguard-example/build/build.scala @@ -1,7 +1,7 @@ import cbt._ -class Build(val context: Context) extends Proguard{ - def proguard = proguardKeep( (Nil, """ +class Build(val context: Context) extends ProGuard{ + def proguard = ProGuard( (Nil, """ public class proguard_example.Main{ public void main(java.lang.String[]); } diff --git a/libraries/proguard/Proguard.scala b/libraries/proguard/Proguard.scala new file mode 100644 index 0000000..c4a74bd --- /dev/null +++ b/libraries/proguard/Proguard.scala @@ -0,0 +1,249 @@ +package cbt +package proguard +import java.io.File +import java.nio.file.Files.deleteIfExists + +sealed class KeepOptionModifier(val string: String) +object KeepOptionModifier { + /* AUTO GENERATED SECTION BEGIN: keepModifiers */ + /** 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") + /* AUTO GENERATED SECTION END: keepModifiers */ +} + +object ProGuard { + val artifactId = "proguard-base" + val groupId = "net.sf.proguard" + val version = "5.3.2" + val mainClass = "proguard.ProGuard" + val `rt.jar` = Seq(new File(System.getProperty("java.home"), "lib/rt.jar")) +} +case class ProGuard[T]( + main: Seq[String] => Int, + T: Seq[File] => T, + log: String => Unit = _ => () +) { + + /** + 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 + + /* AUTO GENERATED SECTION BEGIN: docs */ + @param include Read configuration options from the given file. + @param basedirectory Specifies the base directory for subsequent relative file names. + @param injars Specifies the program jars (or wars, ears, zips, or directories). + @param outjars Specifies the names of the output jars (or wars, ears, zips, or directories). + @param libraryjars Specifies the library jars (or wars, ears, zips, or directories). + @param skipnonpubliclibraryclasses Ignore non-public library classes. + @param dontskipnonpubliclibraryclasses Don't ignore non-public library classes (the default). + @param dontskipnonpubliclibraryclassmembers Don't ignore package visible library class members. + @param keepdirectories Keep the specified directories in the output jars (or wars, ears, zips, or directories). + @param target Set the given version number in the processed classes. + @param forceprocessing Process the input, even if the output seems up to date. + @param keep Preserve the specified classes and class members. + @param keepclassmembers Preserve the specified class members, if their classes are preserved as well. + @param keepclasseswithmembers Preserve the specified classes and class members, if all of the specified class members are present. + @param keepnames Preserve the names of the specified classes and class members (if they aren't removed in the shrinking step). + @param keepclassmembernames Preserve the names of the specified class members (if they aren't removed in the shrinking step). + @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 printseeds List classes and class members matched by the various -keep options, to the standard output or to the given file. + @param dontshrink Don't shrink the input class files. + @param printusage List dead code of the input class files, to the standard output or to the given file. + @param whyareyoukeeping Print details on why the given classes and class members are being kept in the shrinking step. + @param dontoptimize Don't optimize the input class files. + @param optimizations The optimizations to be enabled and disabled. + @param optimizationpasses The number of optimization passes to be performed. + @param assumenosideeffects Assume that the specified methods don't have any side effects, while optimizing. + @param allowaccessmodification Allow the access modifiers of classes and class members to be modified, while optimizing. + @param mergeinterfacesaggressively Allow any interfaces to be merged, while optimizing. + @param dontobfuscate Don't obfuscate the input class files. + @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 applymapping Reuse the given mapping, for incremental obfuscation. + @param obfuscationdictionary Use the words in the given text file as obfuscated field names and method names. + @param classobfuscationdictionary Use the words in the given text file as obfuscated class names. + @param packageobfuscationdictionary Use the words in the given text file as obfuscated package names. + @param overloadaggressively Apply aggressive overloading while obfuscating. + @param useuniqueclassmembernames Ensure uniform obfuscated class member names for subsequent incremental obfuscation. + @param dontusemixedcaseclassnames Don't generate mixed-case class names while obfuscating. + @param keeppackagenames Keep the specified package names from being obfuscated. + @param flattenpackagehierarchy Repackage all packages that are renamed into the single given parent package. + @param repackageclasses Repackage all class files that are renamed into the single given package. + @param keepattributes Preserve the given optional attributes; typically Exceptions, InnerClasses, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, and *Annotation*. + @param keepparameternames Keep the parameter names and types of methods that are kept. + @param renamesourcefileattribute Put the given constant string in the SourceFile attributes. + @param adaptclassstrings Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. + @param adaptresourcefilenames Rename the specified resource files, based on the obfuscated names of the corresponding class files. + @param adaptresourcefilecontents Update the contents of the specified resource files, based on the obfuscated names of the processed classes. + @param dontpreverify Don't preverify the processed class files. + @param microedition Target the processed class files at Java Micro Edition. + @param verbose Write out some more information during processing. + @param dontnote Don't print notes about potential mistakes or omissions in the configuration. + @param dontwarn Don't warn about unresolved references at all. + @param ignorewarnings Print warnings about unresolved references, but continue processing anyhow. + @param printconfiguration Write out the entire configuration in traditional ProGuard style, to the standard output or to the given file. + @param dump Write out the internal structure of the processed class files, to the standard output or to the given file. + /* AUTO GENERATED SECTION END: docs */ + */ + case class proguard( + /* AUTO GENERATED SECTION BEGIN: params */ + include: Option[File] = None, + basedirectory: Option[File] = None, + injars: Option[Seq[File]] = None, + outjars: Option[Seq[File]] = None, + libraryjars: Option[Seq[File]] = None, + skipnonpubliclibraryclasses: Boolean = false, + dontskipnonpubliclibraryclasses: Boolean = false, + dontskipnonpubliclibraryclassmembers: Boolean = false, + keepdirectories: Option[Option[String]] = None, + target: Option[String] = None, + forceprocessing: Boolean = false, + keep: Option[(Seq[KeepOptionModifier], String)] = None, + keepclassmembers: Option[(Seq[KeepOptionModifier], String)] = None, + keepclasseswithmembers: Option[(Seq[KeepOptionModifier], String)] = None, + keepnames: Option[String] = None, + keepclassmembernames: Option[String] = None, + keepclasseswithmembernames: Option[String] = None, + printseeds: Option[Option[File]] = None, + dontshrink: Boolean = false, + printusage: Option[Option[File]] = None, + whyareyoukeeping: Option[String] = None, + dontoptimize: Boolean = false, + optimizations: Option[String] = None, + optimizationpasses: Option[Int] = None, + assumenosideeffects: Option[String] = None, + allowaccessmodification: Boolean = false, + mergeinterfacesaggressively: Boolean = false, + dontobfuscate: Boolean = false, + printmapping: Option[Option[File]] = None, + applymapping: Option[File] = None, + obfuscationdictionary: Option[File] = None, + classobfuscationdictionary: Option[File] = None, + packageobfuscationdictionary: Option[File] = None, + overloadaggressively: Boolean = false, + useuniqueclassmembernames: Boolean = false, + dontusemixedcaseclassnames: Boolean = false, + keeppackagenames: Option[Option[String]] = None, + flattenpackagehierarchy: Option[Option[String]] = None, + repackageclasses: Option[Option[String]] = None, + keepattributes: Option[Option[String]] = None, + keepparameternames: Boolean = false, + renamesourcefileattribute: Option[Option[String]] = None, + adaptclassstrings: Option[Option[String]] = None, + adaptresourcefilenames: Option[Option[String]] = None, + adaptresourcefilecontents: Option[Option[String]] = None, + dontpreverify: Boolean = false, + microedition: Boolean = false, + verbose: Boolean = false, + dontnote: Option[Option[String]] = None, + dontwarn: Option[Option[String]] = None, + ignorewarnings: Boolean = false, + printconfiguration: Option[Option[File]] = None, + dump: Option[Option[File]] = None + /* AUTO GENERATED SECTION END: params */ + ) extends (() => T) { + // type class rendering scala values into string arguments + private class argsFor[T](val apply: T => Option[Seq[String]]) + private object argsFor { + def apply[T: argsFor](value: T) = implicitly[argsFor[T]].apply(value) + implicit object SeqFile extends argsFor[Seq[File]](v => Some(Seq(v.map(_.getPath).mkString(":")))) + implicit object File extends argsFor[File](v => Some(Seq(v.getPath))) + implicit object String extends argsFor[String](v => Some(Seq(v))) + implicit object Int extends argsFor[Int](i => Some(Seq(i.toString))) + implicit object Boolean + extends argsFor[Boolean]({ + case false => None + case true => Some(Nil) + }) + implicit def Option2[T: argsFor]: argsFor[Option[T]] = new argsFor( + _.map(implicitly[argsFor[T]].apply(_).toSeq.flatten) + ) + implicit def Option3[T: argsFor]: argsFor[Option[Option[String]]] = + new argsFor(_.map(_.toSeq)) + implicit def SpecWithModifiers: argsFor[(Seq[KeepOptionModifier], String)] = + new argsFor({ + case (modifiers, spec) => + Some(Seq(modifiers.map(_.string).map("," ++ _).mkString).filterNot(_ == "") :+ spec) + }) + } + + // capture string argument values and names + val args = ( + /* AUTO GENERATED SECTION BEGIN: args */ + argsFor(include).map("-include" +: _) + ++ argsFor(basedirectory).map("-basedirectory" +: _) + ++ argsFor(injars).map("-injars" +: _) + ++ argsFor(outjars).map("-outjars" +: _) + ++ argsFor(libraryjars).map("-libraryjars" +: _) + ++ argsFor(skipnonpubliclibraryclasses).map("-skipnonpubliclibraryclasses" +: _) + ++ argsFor(dontskipnonpubliclibraryclasses).map("-dontskipnonpubliclibraryclasses" +: _) + ++ argsFor(dontskipnonpubliclibraryclassmembers).map("-dontskipnonpubliclibraryclassmembers" +: _) + ++ argsFor(keepdirectories).map("-keepdirectories" +: _) + ++ argsFor(target).map("-target" +: _) + ++ argsFor(forceprocessing).map("-forceprocessing" +: _) + ++ argsFor(keep).map("-keep" +: _) + ++ argsFor(keepclassmembers).map("-keepclassmembers" +: _) + ++ argsFor(keepclasseswithmembers).map("-keepclasseswithmembers" +: _) + ++ argsFor(keepnames).map("-keepnames" +: _) + ++ argsFor(keepclassmembernames).map("-keepclassmembernames" +: _) + ++ argsFor(keepclasseswithmembernames).map("-keepclasseswithmembernames" +: _) + ++ argsFor(printseeds).map("-printseeds" +: _) + ++ argsFor(dontshrink).map("-dontshrink" +: _) + ++ argsFor(printusage).map("-printusage" +: _) + ++ argsFor(whyareyoukeeping).map("-whyareyoukeeping" +: _) + ++ argsFor(dontoptimize).map("-dontoptimize" +: _) + ++ argsFor(optimizations).map("-optimizations" +: _) + ++ argsFor(optimizationpasses).map("-optimizationpasses" +: _) + ++ argsFor(assumenosideeffects).map("-assumenosideeffects" +: _) + ++ argsFor(allowaccessmodification).map("-allowaccessmodification" +: _) + ++ argsFor(mergeinterfacesaggressively).map("-mergeinterfacesaggressively" +: _) + ++ argsFor(dontobfuscate).map("-dontobfuscate" +: _) + ++ argsFor(printmapping).map("-printmapping" +: _) + ++ argsFor(applymapping).map("-applymapping" +: _) + ++ argsFor(obfuscationdictionary).map("-obfuscationdictionary" +: _) + ++ argsFor(classobfuscationdictionary).map("-classobfuscationdictionary" +: _) + ++ argsFor(packageobfuscationdictionary).map("-packageobfuscationdictionary" +: _) + ++ argsFor(overloadaggressively).map("-overloadaggressively" +: _) + ++ argsFor(useuniqueclassmembernames).map("-useuniqueclassmembernames" +: _) + ++ argsFor(dontusemixedcaseclassnames).map("-dontusemixedcaseclassnames" +: _) + ++ argsFor(keeppackagenames).map("-keeppackagenames" +: _) + ++ argsFor(flattenpackagehierarchy).map("-flattenpackagehierarchy" +: _) + ++ argsFor(repackageclasses).map("-repackageclasses" +: _) + ++ argsFor(keepattributes).map("-keepattributes" +: _) + ++ argsFor(keepparameternames).map("-keepparameternames" +: _) + ++ argsFor(renamesourcefileattribute).map("-renamesourcefileattribute" +: _) + ++ argsFor(adaptclassstrings).map("-adaptclassstrings" +: _) + ++ argsFor(adaptresourcefilenames).map("-adaptresourcefilenames" +: _) + ++ argsFor(adaptresourcefilecontents).map("-adaptresourcefilecontents" +: _) + ++ argsFor(dontpreverify).map("-dontpreverify" +: _) + ++ argsFor(microedition).map("-microedition" +: _) + ++ argsFor(verbose).map("-verbose" +: _) + ++ argsFor(dontnote).map("-dontnote" +: _) + ++ argsFor(dontwarn).map("-dontwarn" +: _) + ++ argsFor(ignorewarnings).map("-ignorewarnings" +: _) + ++ argsFor(printconfiguration).map("-printconfiguration" +: _) + ++ argsFor(dump).map("-dump" +: _) + /* AUTO GENERATED SECTION END: args */ + ).flatten.toSeq + + def apply: T = { + outjars.foreach(_.map(_.toPath).map(deleteIfExists)) + val c = main(args) + if (c != 0) throw new Exception + T(outjars.toSeq.flatten) + } + } +} diff --git a/libraries/proguard/Readme.md b/libraries/proguard/Readme.md new file mode 100644 index 0000000..36d61d4 --- /dev/null +++ b/libraries/proguard/Readme.md @@ -0,0 +1,4 @@ +Type-safe Scala api on top of proguards .main method. + +TODO: +- capture stdout and make it available to the caller via stream diff --git a/libraries/proguard/build/build.scala b/libraries/proguard/build/build.scala new file mode 100644 index 0000000..399714e --- /dev/null +++ b/libraries/proguard/build/build.scala @@ -0,0 +1,151 @@ +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 Scalafmt with GeneratedSections{ + 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( refcard.readAsString ) \ "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 ) + } + + override def scalafmtConfig = { + import org.scalafmt.config._ + ScalafmtConfig.defaultWithAlign.copy( + maxColumn = 120, + continuationIndent = super.scalafmtConfig.continuationIndent.copy( + defnSite = 2 + ), + align = super.scalafmtConfig.align.copy( + tokens = AlignToken.default, + arrowEnumeratorGenerator = true, + mixedOwners = true + ), + binPack = super.scalafmtConfig.binPack.copy( + parentConstructors = true + ), + spaces = super.scalafmtConfig.spaces.copy( + inImportCurlyBraces = true + ), + lineEndings = LineEndings.unix, + newlines = super.scalafmtConfig.newlines.copy( + penalizeSingleSelectMultiArgList = false + ), + runner = super.scalafmtConfig.runner.copy( + optimizer = super.scalafmtConfig.runner.optimizer.copy( + forceConfigStyleOnOffset = -1 + ) + ) + ) + } + + /** generates Scala code from parameter specification html */ + def replacements = { + 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" => "Seq[File]" + 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 params = options.map{ + case v@(_, "Boolean", _) => v -> Some("false") + 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 args = options.map{ + case (name, _, description) => s"""argsFor($name).map("-$name" +: _)""" + }.mkString("\n++ ") + + Seq( + "keepModifiers" -> keepModifiers, + "docs" -> docs, + "args" -> args, + "params" -> params + ) + } + override def generate{ + super.generate + compile + } + override def compile = { + scalafmt + super.compile + } +} diff --git a/plugins/proguard/build/build/build.scala b/libraries/proguard/build/build/build.scala index 7928cfa..7928cfa 100644 --- a/plugins/proguard/build/build/build.scala +++ b/libraries/proguard/build/build/build.scala diff --git a/plugins/proguard/spec/refcard.html b/libraries/proguard/spec/refcard.html index 9cb0c72..9cb0c72 100644 --- a/plugins/proguard/spec/refcard.html +++ b/libraries/proguard/spec/refcard.html diff --git a/plugins/proguard/Proguard.scala b/plugins/proguard/Proguard.scala new file mode 100644 index 0000000..486d969 --- /dev/null +++ b/plugins/proguard/Proguard.scala @@ -0,0 +1,32 @@ +package cbt + +trait ProGuard extends BaseBuild { + def proguard: () => ClassPath + def ProGuard(keep: (Seq[cbt.proguard.KeepOptionModifier], String)) = { + cbt.ProGuard(context).proguard( + outjars = Some( Seq(scalaTarget / "proguarded.jar") ), + injars = Some( classpath.files ), + libraryjars = Some( ClassPath( cbt.proguard.ProGuard.`rt.jar` ).files ), + keep = Some( keep ) + ) + } +} + +object ProGuard { + def apply( implicit context: Context ) = { + import context._ + val lib = new Lib(context.logger) + import cbt.proguard.ProGuard._ + cbt.proguard.ProGuard( + (args: Seq[String]) => MavenResolver( + cbtLastModified, context.paths.mavenCache, mavenCentral + )( + context.logger, transientCache, context.classLoaderCache + ).bindOne( + MavenDependency(groupId, artifactId, version) + ).runMain(cbt.proguard.ProGuard.mainClass, args: _*).integer, + ClassPath(_), + context.logger.log("proguard",_) + ) + } +} diff --git a/plugins/proguard/build/build.scala b/plugins/proguard/build/build.scala index 5edd7a8..4237e94 100644 --- a/plugins/proguard/build/build.scala +++ b/plugins/proguard/build/build.scala @@ -1,129 +1,8 @@ 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{ +class Build(val context: Context) extends Plugin{ 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 + Seq( libraries.proguard ) ) - - 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/src/generated/Proguard.scala b/plugins/proguard/src/generated/Proguard.scala deleted file mode 100644 index 553df07..0000000 --- a/plugins/proguard/src/generated/Proguard.scala +++ /dev/null @@ -1,226 +0,0 @@ -/* 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 deleted file mode 100644 index 23a3117..0000000 --- a/plugins/proguard/templates/Proguard.scala +++ /dev/null @@ -1,85 +0,0 @@ -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/cbt.scala b/stage1/cbt.scala index ea3237d..d28789c 100644 --- a/stage1/cbt.scala +++ b/stage1/cbt.scala @@ -1,6 +1,7 @@ package cbt import java.io._ import java.nio.file._ +import java.nio.file.Files._ import java.net._ object `package`{ @@ -59,6 +60,8 @@ object `package`{ if( file.isDirectory ) file.listFiles.flatMap(_.listRecursive).toVector else Seq[File]() ) } + + def readAsString = new String( readAllBytes( file.toPath ) ) } implicit class URLExtensionMethods( url: URL ){ def ++( s: String ): URL = new URL( url.toString ++ s ) diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index b6a2870..910cd5e 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -13,8 +13,10 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge implicit def transientCache: java.util.Map[AnyRef,AnyRef] = context.transientCache object libraries{ - def eval = DirectoryDependency( context.cbtHome ++ "/libraries/eval" ) - def captureArgs = DirectoryDependency( context.cbtHome ++ "/libraries/capture_args" ) + private def dep(name: String) = DirectoryDependency( context.cbtHome / "libraries" / name ) + def captureArgs = dep( "capture_args" ) + def eval = dep( "eval" ) + def proguard = dep( "proguard" ) } // library available to builds @@ -86,8 +88,13 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge */ def compileStatusFile: File = compileTarget ++ ".last-success" + def generatedSources: Seq[File] = Seq( projectDirectory / "src_generated" ) /** Source directories and files. Defaults to .scala and .java files in src/ and top-level. */ - def sources: Seq[File] = Seq(defaultSourceDirectory) ++ projectDirectory.listFiles.toVector.filter(sourceFileFilter) + def sources: Seq[File] = ( + Seq(defaultSourceDirectory) + ++ generatedSources + ++ projectDirectory.listFiles.toVector.filter(sourceFileFilter) + ) /** Which file endings to consider being source files. */ def sourceFileFilter(file: File) = lib.sourceFileFilter(file) diff --git a/stage2/plugins/GeneratedSections.scala b/stage2/plugins/GeneratedSections.scala new file mode 100644 index 0000000..417278c --- /dev/null +++ b/stage2/plugins/GeneratedSections.scala @@ -0,0 +1,40 @@ +package cbt +import java.nio.file.Files._ +trait GeneratedSections extends BaseBuild{ + def generatedSectionStartMarker( name: String ) = s"AUTO GENERATED SECTION BEGIN: $name " + def generatedSectionEndMarker( name: String ) = s"AUTO GENERATED SECTION END: $name " + assert( + generatedSectionStartMarker("foo").endsWith(" "), + "generatedSectionStartMarker needs to end with a space character" + ) + assert( + generatedSectionEndMarker("foo").endsWith(" "), + "generatedSectionEndMarker needs to end with a space character" + ) + + def replacements: Seq[(String, String)] + + def generate = { + def replaceSections(subject: String, sections: Seq[(String, String)]): String = { + sections.headOption.map{ + case (name, replacement) => + replaceSections( + s"(?s)(\n[^\n]*AUTO GENERATED SECTION BEGIN: $name [^\n]*\n).*(\n[^\n]*AUTO GENERATED SECTION END: $name [^\n]*\n)" + .r.replaceAllIn( subject, m => m.group(1) ++ replacement ++ m.group(2) ), + sections.tail + ) + }.getOrElse(subject) + } + + val updated = sourceFiles.flatMap{ file => + val template = file.readAsString + val replaced = replaceSections( template, replacements ) + if( template != replaced ) { + write( file.toPath, replaced.getBytes ) + Some(file) + } else None + } + + logger.log("generated-sections","Updated:" + updated.map(_ ++ "\n").mkString) + } +} |