diff --git a/project/Proguard.scala b/project/Proguard.scala index 75c1fa842..2826a82c3 100644 --- a/project/Proguard.scala +++ b/project/Proguard.scala @@ -6,80 +6,118 @@ object LaunchProguard { lazy val Proguard = config("proguard") hide ; - lazy val configurationFile = SettingKey[File]("configuration-file") - lazy val proguard = TaskKey[File]("proguard", "Produces the final compacted jar that contains only the classes needed using proguard.") - lazy val proguardConfiguration = TaskKey[File]("proguard-configuration", "Creates the configuration file to use with proguard.") - lazy val options = TaskKey[Seq[String]]("options") - lazy val optimizePasses = SettingKey[Int]("optimize-passes") - lazy val keepFullClasses = SettingKey[Seq[String]]("keep-full-classes") - lazy val keepClasses = SettingKey[Seq[String]]("keep-classes") - lazy val inputJars = TaskKey[Seq[File]]("input-jars") + lazy val configurationFile = settingKey[File]("Location of the generated proguard configuration file.") + lazy val proguard = taskKey[File]("Produces the final compacted jar that contains only the classes needed using proguard.") + lazy val proguardConfiguration = taskKey[File]("Creates the configuration file to use with proguard.") + lazy val options = taskKey[Seq[String]]("Proguard options.") + lazy val optimizePasses = settingKey[Int]("Number of optimization passes proguard performs.") + lazy val keepFullClasses = settingKey[Seq[String]]("Fully qualified names of classes that proguard should preserve the non-private API of.") lazy val settings: Seq[Setting[_]] = inScope(GlobalScope)(inConfig(Proguard)(globalSettings)) ++ inConfig(Proguard)( baseSettings ) :+ (libraryDependencies += "net.sf.proguard" % "proguard-base" % "4.8" % Proguard.name) + /** Defaults */ def globalSettings = Seq( - optimizePasses := 2, + optimizePasses := 2, // more don't seem to help much and it increases proguard runtime keepFullClasses := Nil, - keepClasses := Nil, options := basicOptions ) def baseSettings = Seq( - options <++= optimizePasses map { passes => if(passes <= 0) Seq("-dontoptimize") else Seq( "-optimizationpasses " + passes.toString, "-optimizations !code/allocation/variable") }, - options <++= keepFullClasses map { _ map ("-keep public class " + _ + " {\n\tpublic protected * ;\n}") }, - options <++= keepClasses map { _ map ("-keep class " + _ + " {}") }, - configurationFile <<= target / "proguard.pro", - proguardConfiguration <<= writeProguardConfiguration, - proguard <<= proguardTask + optimizeSetting, + options ++= keepFullClasses.value map ("-keep public class " + _ + " {\n\tpublic protected * ;\n}"), + configurationFile := target.value / "proguard.pro", + proguardConfiguration := writeProguardConfiguration.value, + proguard := proguardTask.value ) - + + /** Options to set the number of optimization passes or to disable optimization altogether. */ + def optimizeSetting = options ++= { + val passes = optimizePasses.value + if(passes <= 0) + Seq("-dontoptimize") + else + Seq( + "-optimizationpasses " + passes.toString, + // optimization is problematic without this option, possibly proguard can't handle certain scalac-generated bytecode + "-optimizations !code/allocation/variable" + ) + } + def specific(launchSub: Reference): Seq[Setting[_]] = inConfig(Proguard)(Seq( keepFullClasses ++= "xsbti.**" :: Nil, - keepClasses ++= "org.apache.ivy.plugins.resolver.URLResolver" :: "org.apache.ivy.plugins.resolver.IBiblioResolver" :: Nil, - artifactPath <<= (target, version) { (out,v) => out / ("sbt-launch-" + v + ".jar") }, - options <++= (dependencyClasspath in (launchSub, Compile), compile in (launchSub,Compile), streams) map { (cp, analysis, s) => mapJars(cp.files, analysis.relations.allBinaryDeps.toSeq, s.log) }, - options <+= packageBin map { f => "-injars " + mkpath(f) }, + artifactPath := target.value / ("sbt-launch-" + version.value + ".jar"), + options ++= dependencyOptions(launchSub).value, + options += "-injars " + mkpath(packageBin.value), packageBin := (packageBin in (launchSub, Compile)).value, - options <++= mainClass in (launchSub, Compile) map { _.toList map(s => keepMain.format(s)) }, - options <+= artifactPath map { p => "-outjars " + mkpath(p) }, - fullClasspath <<= (configuration, classpathTypes, update) map Classpaths.managedJars + options ++= mainClass.in(launchSub, Compile).value.toList map keepMain, + options += "-outjars " + mkpath(artifactPath.value), + fullClasspath := Classpaths.managedJars(configuration.value, classpathTypes.value, update.value) )) + def basicOptions = + Seq( + "-keep,allowoptimization,allowshrinking class * { *; }", // no obfuscation + "-keepattributes SourceFile,LineNumberTable", // preserve debugging information + "-dontnote", + "-dontwarn", // too many warnings generated for scalac-generated bytecode last time this was enabled + "-ignorewarnings") + + /** Option to preserve the main entry point. */ + private def keepMain(className: String) = + s"""-keep public class $className { + | public static void main(java.lang.String[]); + |}""".stripMargin + + + private def excludeIvyResources = + "META-INF/**" :: + "fr/**" :: + "**/antlib.xml" :: + "**/*.png" :: + "org/apache/ivy/core/settings/ivyconf*.xml" :: + "org/apache/ivy/core/settings/ivysettings-*.xml" :: + "org/apache/ivy/plugins/resolver/packager/*" :: + "**/ivy_vfs.xml" :: + "org/apache/ivy/plugins/report/ivy-report-*" :: + Nil + + // libraryFilter and the Scala library-specific filtering in mapInJars can be removed for 2.11, since it is properly modularized + private def libraryFilter = "(!META-INF/**,!*.properties,!scala/util/parsing/*.class,**.class)" + private def generalFilter = "(!META-INF/**,!*.properties)" + + + def dependencyOptions(launchSub: Reference) = Def.task { + val cp = (dependencyClasspath in (launchSub, Compile)).value + val analysis = (compile in (launchSub,Compile)).value + mapJars(cp.files, analysis.relations.allBinaryDeps.toSeq, streams.value.log) + } + def mapJars(in: Seq[File], all: Seq[File], log: Logger): Seq[String] = mapInJars(in, log) ++ mapLibraryJars(all filterNot in.toSet) - def writeProguardConfiguration = (options, configurationFile, streams) map { (opts, conf, s) => - val content = opts.mkString("\n") + def writeProguardConfiguration = Def.task { + val content = options.value.mkString("\n") + val conf = configurationFile.value if(!conf.exists || IO.read(conf) != content) { - s.log.info("Proguard configuration written to " + conf) + streams.value.log.info("Proguard configuration written to " + conf) IO.write(conf, content) } conf } - def basicOptions = - Seq( - "-keep,allowoptimization,allowshrinking class * { *; }", - "-keepattributes SourceFile,LineNumberTable", - "-dontnote", - "-dontwarn", - "-ignorewarnings") - - private val keepMain = - """-keep public class %s { - | public static void main(java.lang.String[]); - |}""".stripMargin - def mapLibraryJars(libraryJars: Seq[File]): Seq[String] = libraryJars.map(f => "-libraryjars " + mkpath(f)) def mapOutJar(outJar: File) = "-outjars " + mkpath(outJar) def mkpath(f: File) : String = mkpath(f.getAbsolutePath, '\"') def mkpath(path: String, delimiter : Char) : String = delimiter + path + delimiter - def proguardTask = (cacheDirectory, artifactPath, proguardConfiguration, fullClasspath, packageBin, streams) map { (cache, outputJar, configFile, cp, inJar, s) => - val f = FileFunction.cached(cache / "proguard", FilesInfo.hash) { _ => - runProguard(outputJar, configFile, cp.files, s.log) + def proguardTask = Def.task { + val inJar = packageBin.value + val outputJar = artifactPath.value + val configFile = proguardConfiguration.value + val f = FileFunction.cached(cacheDirectory.value / "proguard", FilesInfo.hash) { _ => + runProguard(outputJar, configFile, fullClasspath.value.files, streams.value.log) Set(outputJar) } f(Set(inJar, configFile)) // make the assumption that if the classpath changed, the outputJar would change @@ -109,20 +147,6 @@ object LaunchProguard } private def excludeString(s: List[String]) = s.map("!" + _).mkString("(",",",")") - private def excludeIvyResources = - "META-INF/**" :: - "fr/**" :: - "**/antlib.xml" :: - "**/*.png" :: - "org/apache/ivy/core/settings/ivyconf*.xml" :: - "org/apache/ivy/core/settings/ivysettings-*.xml" :: - "org/apache/ivy/plugins/resolver/packager/*" :: - "**/ivy_vfs.xml" :: - "org/apache/ivy/plugins/report/ivy-report-*" :: - Nil - - private def libraryFilter = "(!META-INF/**,!*.properties,!scala/actors/**,!scala/util/parsing/*.class,**.class)" - private def generalFilter = "(!META-INF/**,!*.properties)" private def withJar[T](files: Seq[File], name: String) = mkpath(files.headOption getOrElse error(name + " not present") ) private def isJarX(x: String)(file: File) =