package sbt import java.io.File object ProguardProject { val ProguardDescription = "Produces the final compacted jar that contains only the minimum classes needed using proguard." val WriteProguardDescription = "Creates the configuration file to use with proguard." } import ProguardProject._ trait ProguardProject extends BasicScalaProject { def rawJarPath: Path def rawPackage: Task def proguardConfigurationPath: Path = outputPath / "proguard.pro" def outputJar: Path def rootProjectDirectory = rootProject.info.projectPath val toolsConfig = config("tools") hide val proguardJar = "net.sf.proguard" % "proguard" % "4.4" % "tools" lazy val proguard = proguardAction def proguardAction = proguardTask dependsOn(writeProguardConfiguration) describedAs(ProguardDescription) lazy val writeProguardConfiguration = writeProguardConfigurationAction def writeProguardConfigurationAction = writeProguardConfigurationTask dependsOn rawPackage describedAs WriteProguardDescription def basicOptions: Seq[String] = Seq( "-keep,allowoptimization,allowshrinking class * { *; }", "-keepattributes SourceFile,LineNumberTable", "-dontnote", "-dontwarn", "-ignorewarnings") ++ optimizeOptions def keepFullClasses: Seq[String] = Nil def keepClasses: Seq[String] = Nil def optimize: Int = 0 def optimizeOptions = if(optimize <= 0) Seq("-dontoptimize") else Seq( "-optimizationpasses 2", "-optimizations !code/allocation/variable") def mapInJars(inJars: Seq[File]): Seq[String] = inJars.map(f => "-injars " + mkpath(f)) def mapLibraryJars(libraryJars: Seq[File]): Seq[String] = libraryJars.map(f => "-libraryjars " + mkpath(f)) def mapOutJar(outJar: File) = "-outjars " + mkpath(outJar) def template(inJars: Seq[File], libraryJars: Seq[File], outJar: File, options: Seq[String], mainClass: Option[String], keepClasses: Seq[String]) = { val keepMain = """-keep public class %s { | public static void main(java.lang.String[]); |}""" val lines = options ++ keepFullClasses.map("-keep public class " + _ + " {\n public protected * ;\n}") ++ keepClasses.map("-keep class " + _ + " {}") ++ mapInJars(inJars) ++ Seq("-injars " + mkpath(rawJarPath.asFile), mapOutJar(outJar)) ++ mapLibraryJars(libraryJars) ++ mainClass.map(main => keepMain.stripMargin.format(main)).toList lines.mkString("\n") } def mkpath(f: File) = '\"' + f.getAbsolutePath + '\"' private def proguardTask = task { FileUtilities.clean(outputJar :: Nil, log) val proguardClasspathString = Path.makeString(managedClasspath(toolsConfig).get) val configFile = proguardConfigurationPath.asFile.getAbsolutePath val exitValue = Process("java", List("-Xmx256M", "-cp", proguardClasspathString, "proguard.ProGuard", "@" + configFile)) ! log if(exitValue == 0) None else Some("Proguard failed with nonzero exit code (" + exitValue + ")") } private def writeProguardConfigurationTask = task { val dependencies = mainDependencies.snapshot log.debug("proguard configuration, all dependencies:\n\t" + dependencies.all.mkString("\n\t")) val externalJars = dependencies.external log.debug("proguard configuration external dependencies: \n\t" + externalJars.mkString("\n\t")) // partition jars from the external jar dependencies of this project by whether they are located in the project directory // if they are, they are specified with -injars, otherwise they are specified with -libraryjars val libraryJars = dependencies.libraries ++ dependencies.scalaJars log.debug("proguard configuration library jars locations:\n\t" + libraryJars.mkString("\n\t")) val proguardConfiguration = template(libraryJars, externalJars, outputJar.asFile, basicOptions, getMainClass(false), keepClasses) log.debug("Proguard configuration written to " + proguardConfigurationPath) FileUtilities.write(proguardConfigurationPath.asFile, proguardConfiguration, log) } }