From 84cc83d8b10ed2f0771c1046095fcf5b02ecfc42 Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Fri, 11 Dec 2015 23:02:29 +0400 Subject: [PATCH] Add support for predexing. --- .../kobalt/plugin/android/AndroidFiles.kt | 9 +- .../kobalt/plugin/android/AndroidPlugin.kt | 105 +++++++++++++----- .../plugin/android/KobaltResourceMerger.kt | 2 + 3 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt index 19cda417..924ecf43 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt @@ -31,8 +31,10 @@ class AndroidFiles { fun explodedManifest(project: Project, mavenId: MavenId) = KFiles.joinDir(exploded(project, mavenId), "AndroidManifest.xml") - fun explodedClassesJar(project: Project, mavenId: MavenId) = - KFiles.joinDir(exploded(project, mavenId), "classes.jar") + fun aarClassesJar(dir: String) = KFiles.joinDir(dir, "classes.jar") + + fun explodedClassesJar(project: Project, mavenId: MavenId) = aarClassesJar( + KFiles.joinDir(exploded(project, mavenId))) fun classesDir(project: Project, variant: Variant): String = KFiles.joinDir(project.directory, project.buildDirectory, variant.toIntermediateDir(), "classes") @@ -55,5 +57,8 @@ class AndroidFiles { fun androidHome(project: Project?, config: AndroidConfig) = androidHomeNoThrows(project, config) ?: throw IllegalArgumentException("Neither androidHome nor \$ANDROID_HOME were defined") + fun preDexed(project: Project, variant: Variant) = + KFiles.joinAndMakeDir(intermediates(project), "pre-dexed", variant.toIntermediateDir()) + } } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt index d975706a..ac653701 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt @@ -19,6 +19,23 @@ import java.io.FileInputStream import java.nio.file.Path import java.nio.file.Paths +/** + * The Android plug-in which executes: + * library dependencies (android.library.reference.N) + * ndk + * aidl + * renderscript + * BuildConfig.java + * aapt + * compile + * obfuscate + * dex + * png crunch + * package resources + * package apk + * sign + * zipalign + */ @Singleton public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, val executors: KobaltExecutors, val dependencyManager: DependencyManager) @@ -85,12 +102,15 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, private fun apk(project: Project, flavor: String) = KFiles.joinFileAndMakeDir(project.buildDirectory, "outputs", "apk", "${project.name}$flavor.apk") + private val preDexFiles = arrayListOf() + @Task(name = "generateR", description = "Generate the R.java file", runBefore = arrayOf("compile"), runAfter = arrayOf("clean")) fun taskGenerateRFile(project: Project): TaskResult { val resDir = "temporaryBogusResDir" val aarDependencies = explodeAarFiles(project, File(resDir)) + preDexFiles.addAll(preDex(project, context.variant, aarDependencies)) val rDirectory = KFiles.joinAndMakeDir(KFiles.generatedSourceDir(project, context.variant, "r")) extraSourceDirectories.add(File(rDirectory)) KobaltResourceMerger().run(project, context.variant, configurationFor(project)!!, aarDependencies, rDirectory) @@ -98,6 +118,33 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, return TaskResult(true) } + /** + * Predex all the libraries that need to be predexed then return a list of them. + */ + private fun preDex(project: Project, variant: Variant, aarDependencies: List) : List { + log(2, "Predexing") + val result = arrayListOf() + val aarFiles = aarDependencies.map { File(AndroidFiles.aarClassesJar(it.path))} + val jarFiles = dependencies(project).map { File(it) } + val allDependencies = (aarFiles + jarFiles).toHashSet().filter { it.exists() } + + allDependencies.forEach { dep -> + val versionFile = File(dep.path).parentFile + val artifactFile = versionFile.parentFile + val name = artifactFile.name + "-" + versionFile.name + val outputDir = AndroidFiles.preDexed(project, variant) + val outputFile = File(outputDir, name + ".jar") + if (! outputFile.exists()) { + log(2, " Predexing $dep") + runDex(project, outputFile.path, dep.path) + } else { + log(2, " $dep already predexed") + } + result.add(outputFile.path) + } + return result + } + /** * aapt returns 0 even if it fails, so in order to detect whether it failed, we are checking * if its error stream contains anything. @@ -117,6 +164,7 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, * which will be added to the classpath at compile time via the classpath interceptor. */ private fun explodeAarFiles(project: Project, resDir: File) : List { + log(2, "Exploding aars") val result = arrayListOf() project.compileDependencies.filter { it.jarFile.get().name.endsWith(".aar") @@ -124,13 +172,13 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, val mavenId = MavenId.create(it.id) val destDir = File(AndroidFiles.exploded(project, mavenId)) if (!File(AndroidFiles.explodedManifest(project, mavenId)).exists()) { - log(2, "Exploding ${it.jarFile.get()} to $destDir") + log(2, " Exploding ${it.jarFile.get()} to $destDir") JarUtils.extractJarFile(it.jarFile.get(), destDir) // Copy all the resources from this aar into the same intermediate directory KFiles.copyRecursively(destDir.resolve("res"), resDir) } else { - log(2, "$destDir already exists, not extracting again") + log(2, " $destDir already exists, not extracting again") } val classesJar = AndroidFiles.explodedClassesJar(project, mavenId) @@ -197,6 +245,32 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, return TaskResult() } + private fun dependencies(project: Project) = dependencyManager.calculateDependencies(project, context, projects, + project.compileDependencies).map { + it.jarFile.get().path + }.filterNot { + it.contains("android.jar") || it.endsWith(".aar") || it.contains("retrolambda") + }.toHashSet().toTypedArray() + + class DexCommand : RunCommand("java") { + override fun isSuccess(callSucceeded: Boolean, input: List, error: List) = + error.size == 0 + } + + private fun runDex(project: Project, outputJarFile: String, target: String) { +// DexProcessBuilder(File(jarFile)). + DexCommand().run(listOf( + "-cp", KFiles.joinDir(androidHome(project), "build-tools", buildToolsVersion(project), "lib", "dx.jar"), + "com.android.dx.command.Main", + "--dex", + if (KobaltLogger.LOG_LEVEL == 3) "--verbose" else "", + "--num-threads=4", + "--output", outputJarFile, + *(preDexFiles.toTypedArray()), + target + ).filter { it != "" }) + } + @Task(name = TASK_GENERATE_DEX, description = "Generate the dex file", runBefore = arrayOf("assemble"), runAfter = arrayOf("compile")) fun taskGenerateDex(project: Project): TaskResult { @@ -212,30 +286,7 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, val classesDex = "classes.dex" val outClassesDex = KFiles.joinDir(classesDexDir, classesDex) - val javaExecutable = JavaInfo.create(File(SystemProperties.javaBase)).javaExecutable!! - - val dependencies = dependencyManager.calculateDependencies(project, context, projects, - project.compileDependencies).map { - it.jarFile.get().path - }.filterNot { - it.contains("android.jar") || it.endsWith(".aar") - || it.contains("retrolambda") - }.toHashSet().toTypedArray() - - class DexCommand : RunCommand(javaExecutable.absolutePath) { - override fun isSuccess(callSucceeded: Boolean, input: List, error: List) = - error.size == 0 - } - - DexCommand().run(listOf( - "-cp", KFiles.joinDir(androidHome(project), "build-tools", buildToolsVersion(project), "lib", "dx.jar"), - "com.android.dx.command.Main", - "--dex", "--verbose", "--num-threads=4", - "--output", outClassesDex, - *dependencies, -// KFiles.joinDir(AndroidFiles.intermediates(project), "dex", context.variant.toIntermediateDir()), - project.classesDir(context) - )) + runDex(project, outClassesDex, project.classesDir(context)) // // Add classes.dex to existing .ap_ @@ -389,7 +440,7 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, val result = arrayListOf() dependencies.forEach { if (it is MavenDependency && (isAar(it.mavenId) || it.mavenId.packaging == "aar")) { - val newDep = FileDependency(AndroidFiles.classesJar(project, it.mavenId)) + val newDep = FileDependency(AndroidFiles.explodedClassesJar(project, it.mavenId)) result.add(newDep) val id = MavenId.create(it.groupId, it.artifactId, "aar", it.version) result.add(MavenDependency.create(id)) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt index fc365924..a73cfc85 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt @@ -82,6 +82,8 @@ class KobaltResourceMerger { val logger = StdLogger(level) val androidBuilder = createAndroidBuilder(project, config, logger) + log(2, "Merging resources") + // // Assets //