diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt index bc7f53f2..1c8ddfbc 100644 --- a/kobalt/src/Build.kt +++ b/kobalt/src/Build.kt @@ -115,8 +115,7 @@ val kobaltApp = kotlinProject(kobaltPluginApi, wrapper) { dependencies { // Used by the plugins - compile("com.android.tools.build:builder:2.0.0-alpha3", - "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.0-beta-3595", + compile("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.0-beta-3595", "org.jetbrains.dokka:dokka-fatjar:0.9.3") // Used by the main app diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt deleted file mode 100644 index d9218675..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.beust.kobalt.api.Project -import com.beust.kobalt.misc.RunCommand -import com.beust.kobalt.misc.log -import java.io.File - -open class AndroidCommand(project: Project, androidHome: String, command: String, cwd: File = File(project.directory)) -: RunCommand(command) { - init { - env.put("ANDROID_HOME", androidHome) - directory = cwd - } - - open fun call(args: List) = run(args, - successCallback = { output -> - log(1, "$command succeeded:") - output.forEach { - log(1, " $it") - } - }, - errorCallback = { output -> - with(StringBuilder()) { - append("Error running $command:") - output.forEach { - append(" $it") - } - error(this.toString()) - } - }) -} - - diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt deleted file mode 100644 index 924ecf43..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidFiles.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.beust.kobalt.Variant -import com.beust.kobalt.api.KobaltContext -import com.beust.kobalt.api.Project -import com.beust.kobalt.maven.MavenId -import com.beust.kobalt.misc.KFiles - -class AndroidFiles { - companion object { - fun intermediates(project: Project) = KFiles.joinDir(project.directory, project.buildDirectory, - "intermediates") - - fun manifest(project: Project, context: KobaltContext) = - KFiles.joinDir(project.directory, "src", "main", "AndroidManifest.xml") - - fun mergedManifest(project: Project, variant: Variant) : String { - val dir = KFiles.joinAndMakeDir(intermediates(project), "manifests", "full", variant.toIntermediateDir()) - return KFiles.joinDir(dir, "AndroidManifest.xml") - } - - fun mergedResourcesNoVariant(project: Project) = - KFiles.joinAndMakeDir(AndroidFiles.intermediates(project), "res", "merged") - - fun mergedResources(project: Project, variant: Variant) = - KFiles.joinAndMakeDir(mergedResourcesNoVariant(project), variant.toIntermediateDir()) - - fun exploded(project: Project, mavenId: MavenId) = KFiles.joinAndMakeDir( - intermediates(project), "exploded-aar", mavenId.groupId, mavenId.artifactId, mavenId.version!!) - - fun explodedManifest(project: Project, mavenId: MavenId) = - KFiles.joinDir(exploded(project, mavenId), "AndroidManifest.xml") - - 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") - - fun temporaryApk(project: Project, flavor: String) - = KFiles.joinFileAndMakeDir(AndroidFiles.intermediates(project), "res", "resources$flavor.ap_") - - /** - * Use the android home define on the project if any, otherwise use the environment variable. - */ - fun androidHomeNoThrows(project: Project?, config: AndroidConfig?): String? { - var result = System.getenv("ANDROID_HOME") - if (project != null && config?.androidHome != null) { - result = config?.androidHome - } - - return result - } - - 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/AndroidManifestXml.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidManifestXml.kt deleted file mode 100644 index 4f88e729..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidManifestXml.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.beust.kobalt.plugin.android - -import java.io.InputStream -import javax.xml.bind.JAXBContext -import javax.xml.bind.annotation.XmlAttribute -import javax.xml.bind.annotation.XmlElement -import javax.xml.bind.annotation.XmlRootElement - -/** - * Parse AndroidManifest.xml and expose its content. - */ -class AndroidManifest(val ins: InputStream) { - companion object { - const val NAMESPACE = "http://schemas.android.com/apk/res/android" - } - - val manifest: AndroidManifestXml by lazy { - val jaxbContext = JAXBContext.newInstance(AndroidManifestXml::class.java) - jaxbContext.createUnmarshaller().unmarshal(ins) as AndroidManifestXml - } - - val pkg by lazy { - manifest.pkg - } - - val mainActivity: String? by lazy { - fun isLaunch(act: ActivityXml) : Boolean { - val r = act.intentFilters.filter { inf: IntentFilter -> - inf.action?.name == "android.intent.action.MAIN" && - inf.category?.name == "android.intent.category.LAUNCHER" - } - return r.size > 0 - } - val act = manifest.application?.activities?.filter { isLaunch(it) } - if (act != null && act.size > 0) { - act.get(0).name?.let { n -> - if (n.startsWith(".")) pkg + "." + n.substring(1) else n - } - } else { - null - } - } -} - -@XmlRootElement(name = "manifest") -class AndroidManifestXml { - @XmlAttribute(name = "package") @JvmField - val pkg: String? = null - var application: ApplicationXml? = null -} - -class ApplicationXml { - @XmlElement(name = "activity") @JvmField - var activities: List = arrayListOf() -} - -class ActivityXml { - @XmlAttribute(namespace = AndroidManifest.NAMESPACE, name = "name") @JvmField - var name: String? = null - - @XmlElement(name = "intent-filter") @JvmField - var intentFilters: List = arrayListOf() -} - -class IntentFilter { - var action: ActionXml? = null - var category: CategoryXml? = null -} - -class ActionXml { - @XmlAttribute(namespace = AndroidManifest.NAMESPACE, name = "name") @JvmField - var name: String? = null -} - -class CategoryXml { - @XmlAttribute(namespace = AndroidManifest.NAMESPACE, name = "name") @JvmField - var name: String? = null -} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt deleted file mode 100644 index 2c18708d..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt +++ /dev/null @@ -1,535 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.beust.kobalt.* -import com.beust.kobalt.api.* -import com.beust.kobalt.api.annotation.Directive -import com.beust.kobalt.api.annotation.IncrementalTask -import com.beust.kobalt.api.annotation.Task -import com.beust.kobalt.maven.DependencyManager -import com.beust.kobalt.maven.MavenId -import com.beust.kobalt.maven.Md5 -import com.beust.kobalt.maven.dependency.FileDependency -import com.beust.kobalt.maven.dependency.MavenDependency -import com.beust.kobalt.misc.* -import com.beust.kobalt.plugin.java.JavaCompiler -import com.google.common.collect.HashMultimap -import com.google.inject.Inject -import com.google.inject.Singleton -import java.io.File -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, val taskContributor : TaskContributor) - : ConfigPlugin(), IClasspathContributor, IRepoContributor, ICompilerFlagContributor, - ICompilerInterceptor, IBuildDirectoryIncerceptor, IRunnerContributor, IClasspathInterceptor, - ISourceDirectoryContributor, IBuildConfigFieldContributor, ITaskContributor, IMavenIdInterceptor { - companion object { - const val PLUGIN_NAME = "Android" - const val TASK_GENERATE_DEX = "generateDex" - const val TASK_SIGN_APK = "signApk" - const val TASK_INSTALL= "install" - } - - override val name = PLUGIN_NAME - - fun isAndroid(project: Project) = configurationFor(project) != null - - override fun apply(project: Project, context: KobaltContext) { - super.apply(project, context) - if (accept(project)) { - project.compileDependencies.add(FileDependency(androidJar(project).toString())) - - taskContributor.addVariantTasks(this, project, context, "generateR", runBefore = listOf("compile"), - runTask = { taskGenerateRFile(project) }) - taskContributor.addIncrementalVariantTasks(this, project, context, "generateDex", - runAfter = listOf ("compile"), - runBefore = listOf("assemble"), - runTask = { taskGenerateDex(project) }) - taskContributor.addVariantTasks(this, project, context, "signApk", runAfter = listOf("generateDex"), - runBefore = listOf("assemble"), - runTask = { taskSignApk(project) }) - taskContributor.addVariantTasks(this, project, context, "install", runAfter = listOf("signApk"), - runTask = { taskInstall(project) }) - taskContributor.addVariantTasks(this, project, context, "proguard", runBefore = listOf("install"), - runAfter = listOf("compile"), - runTask = { taskProguard(project) }) - } - context.pluginInfo.classpathContributors.add(this) - } - - - override fun accept(project: Project) = isAndroid(project) - - fun compileSdkVersion(project: Project) = configurationFor(project)?.compileSdkVersion - - fun buildToolsVersion(project: Project): String { - val version = configurationFor(project)?.buildToolsVersion - if (OperatingSystem.current().isWindows() && version == "21.1.2") - return "build-tools-$version" - else - return version as String - } - - fun androidHome(project: Project?) = AndroidFiles.androidHome(project, configurationFor(project)!!) - - fun androidJar(project: Project): Path = - Paths.get(androidHome(project), "platforms", "android-${compileSdkVersion(project)}", "android.jar") - - private fun aapt(project: Project) = "${androidHome(project)}/build-tools/${buildToolsVersion(project)}/aapt" - - private fun adb(project: Project) = "${androidHome(project)}/platform-tools/adb" - - 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 aarDependencies = explodeAarFiles(project) - 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) - - 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. - */ - inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String, - cwd: File = File(".")) : AndroidCommand(project, androidHome(project), aapt) { - init { - directory = cwd - useErrorStreamAsErrorIndicator = true - } - - override fun call(args: List) = super.run(arrayListOf(aaptCommand) + args) - } - - /** - * Extract all the .aar files found in the dependencies and add their android.jar to classpathEntries, - * which will be added to the classpath at compile time via the classpath interceptor. - */ - private fun explodeAarFiles(project: Project) : List { - log(2, "Exploding aars") - val result = arrayListOf() - project.compileDependencies.filter { - it.jarFile.get().name.endsWith(".aar") - }.forEach { - 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") - JarUtils.extractJarFile(it.jarFile.get(), destDir) - } else { - log(2, " $destDir already exists, not extracting again") - } - val classesJar = AndroidFiles.explodedClassesJar(project, mavenId) - - // Add the classses.jar of this .aar to the classpath entries (which are returned via IClasspathContributor) - classpathEntries.put(project.name, FileDependency(classesJar)) - // Also add all the jar files found in the libs/ directory - File(destDir, "libs").let { libsDir -> - if (libsDir.exists()) { - libsDir.listFiles().filter { it.name.endsWith(".jar") }.forEach { - classpathEntries.put(project.name, FileDependency(it.absolutePath)) - } - } - } - result.add(destDir) - } - return result - } - - /** - * Implements ICompilerFlagContributor - * Make sure we compile and generate 1.6 sources unless the build file defined those (which can - * happen if the developer is using RetroLambda for example). - */ - override fun flagsFor(project: Project, context: KobaltContext, currentFlags: List) : List { - if (isAndroid(project)) { - var found = currentFlags.any { it == "-source" || it == "-target" } - val result = arrayListOf().apply { addAll(currentFlags) } - if (! found) { - result.add("-source") - result.add("1.6") - result.add("-target") - result.add("1.6") - result.add("-nowarn") - } - return result - } else { - return emptyList() - } - } - - @Task(name = "proguard", description = "Run Proguard, if enabled", runBefore = arrayOf(TASK_GENERATE_DEX), - runAfter = arrayOf("compile")) - fun taskProguard(project: Project): TaskResult { - val config = configurationFor(project) - if (config != null) { - val buildType = context.variant.buildType - if (buildType.minifyEnabled) { - log(1, "minifyEnabled is true, running Proguard") - val classesDir = project.classesDir(context) - val proguardHome = KFiles.joinDir(androidHome(project), "tools", "proguard") - val proguardCommand = KFiles.joinDir(proguardHome, "bin", "proguard.sh") - } - } - 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 inputChecksum(classDirectory: String) = Md5.toMd5Directories(listOf(File(classDirectory))) - - 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 != "" }) - } - - @IncrementalTask(name = TASK_GENERATE_DEX, description = "Generate the dex file", runBefore = arrayOf("assemble"), - runAfter = arrayOf("compile")) - fun taskGenerateDex(project: Project): IncrementalTaskInfo { - File(project.classesDir(context)).mkdirs() - return IncrementalTaskInfo( - inputChecksum = inputChecksum(project.classesDir(context)), - outputChecksum = "1", - task = { project -> doTaskGenerateDex(project) } - ) - } - - fun doTaskGenerateDex(project: Project): TaskResult { - // - // Call dx to generate classes.dex - // - val buildToolsDir = buildToolsVersion(project) - val dx = "${androidHome(project)}/build-tools/$buildToolsDir/dx" + - if (OperatingSystem.current().isWindows()) ".bat" else "" - val classesDexDir = KFiles.joinDir(AndroidFiles.intermediates(project), "dex", - context.variant.toIntermediateDir()) - File(classesDexDir).mkdirs() - val classesDex = "classes.dex" - val outClassesDex = KFiles.joinDir(classesDexDir, classesDex) - - runDex(project, outClassesDex, project.classesDir(context)) - - // - // Add classes.dex to existing .ap_ - // Because aapt doesn't handle directory moving, we need to cd to classes.dex's directory so - // that classes.dex ends up in the root directory of the .ap_. - // - AaptCommand(project, aapt(project), "add").apply { - directory = File(outClassesDex).parentFile - }.call(listOf("-v", KFiles.joinDir( - File(AndroidFiles.temporaryApk(project, context.variant.shortArchiveName)).absolutePath), classesDex)) - - return TaskResult() - } - - private val DEFAULT_DEBUG_SIGNING_CONFIG = SigningConfig( - SigningConfig.DEFAULT_STORE_FILE, - SigningConfig.DEFAULT_STORE_PASSWORD, - SigningConfig.DEFAULT_KEY_ALIAS, - SigningConfig.DEFAULT_KEY_PASSWORD) - - /** - * Sign the apk - * Mac: - * jarsigner -keystore ~/.android/debug.keystore -storepass android -keypass android -signedjar a.apk a.ap_ - * androiddebugkey - */ - @Task(name = TASK_SIGN_APK, description = "Sign the apk file", runAfter = arrayOf(TASK_GENERATE_DEX), - runBefore = arrayOf("assemble")) - fun taskSignApk(project: Project): TaskResult { - val apk = apk(project, context.variant.shortArchiveName) - val temporaryApk = AndroidFiles.temporaryApk(project, context.variant.shortArchiveName) - val buildType = context.variant.buildType.name - - val config = configurationFor(project) - var signingConfig = config!!.signingConfigs[buildType] - - if (signingConfig == null && buildType != "debug") { - warn("No signingConfig found for product type \"$buildType\", using the \"debug\" signConfig") - } - - signingConfig = DEFAULT_DEBUG_SIGNING_CONFIG - - val success = RunCommand("jarsigner").apply { -// useInputStreamAsErrorIndicator = true - }.run(listOf( - "-keystore", signingConfig.storeFile, - "-storepass", signingConfig.storePassword, - "-keypass", signingConfig.keyPassword, - "-signedjar", apk, - temporaryApk, - signingConfig.keyAlias - )) - log(1, "Created $apk") - - return TaskResult(success == 0) - } - - @Task(name = TASK_INSTALL, description = "Install the apk file", runAfter = arrayOf(TASK_GENERATE_DEX, "assemble")) - fun taskInstall(project: Project): TaskResult { - - /** - * adb has weird ways of signaling errors, that's the best I've found so far. - */ - class AdbInstall : RunCommand(adb(project)) { - override fun isSuccess(callSucceeded: Boolean, input: List, error: List) - = input.filter { it.contains("Success")}.size > 0 - } - - val apk = apk(project, context.variant.shortArchiveName) - val result = AdbInstall().useErrorStreamAsErrorIndicator(true).run( - args = listOf("install", "-r", apk)) - log(1, "Installed $apk") - return TaskResult(result == 0) - } - - private val classpathEntries = HashMultimap.create() - - // IClasspathContributor - override fun entriesFor(project: Project?): Collection { - return if (project != null) { - classpathEntries.get(project.name) ?: emptyList() - } else { - emptyList() - } - } - - // IRepoContributor - override fun reposFor(project: Project?): List { - val config = configurationFor(project) - var home = AndroidFiles.androidHomeNoThrows(project, config) - - return if (home != null) { - val path = Paths.get(KFiles.joinDir(home, "extras", "android", "m2repository")) - listOf(HostConfig(path.toUri().toString())) - } else { - emptyList() - } - } - - // IBuildDirectoryInterceptor - override fun intercept(project: Project, context: KobaltContext, buildDirectory: String): String { - if (isAndroid(project)) { - val result = KFiles.joinDir(AndroidFiles.intermediates(project), "classes", - context.variant.toIntermediateDir()) - return result - } else { - return buildDirectory - } - } - - // ICompilerInterceptor - override fun intercept(project: Project, context: KobaltContext, actionInfo: CompilerActionInfo) - : CompilerActionInfo { - val result: CompilerActionInfo = - if (isAndroid(project)) { - val newOutputDir = KFiles.joinDir("kobaltBuild", "intermediates", "classes", - context.variant.toIntermediateDir()) - actionInfo.copy(outputDir = File(newOutputDir)) - } else { - actionInfo - } - return result - } - - // IRunContributor - override fun affinity(project: Project, context: KobaltContext): Int { - val manifest = AndroidFiles.manifest(project, context) - return if (File(manifest).exists()) IAffinity.DEFAULT_POSITIVE_AFFINITY else 0 - } - - override fun run(project: Project, context: KobaltContext, classpath: List): TaskResult { - AndroidFiles.mergedManifest(project, context.variant).let { manifestPath -> - FileInputStream(File(manifestPath)).use { ins -> - // adb shell am start -n com.package.name/com.package.name.ActivityName - val manifest = AndroidManifest(ins) - RunCommand(adb(project)).useErrorStreamAsErrorIndicator(false).run(args = listOf( - "shell", "am", "start", "-n", manifest.pkg + "/" + manifest.mainActivity)) - return TaskResult() - } - } - } - - private fun isAar(id: MavenId) = id.groupId == "com.android.support" && id.artifactId != "support-annotations" - - /** - * For each com.android.support dependency or aar packaging, add a classpath dependency that points to the - * classes.jar inside that (exploded) aar. - */ - // IClasspathInterceptor - override fun intercept(project: Project, dependencies: List): List { - val result = arrayListOf() - dependencies.forEach { - if (it is MavenDependency && (isAar(it.mavenId) || it.mavenId.packaging == "aar")) { - 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)) - } else { - result.add(it) - } - } - return result - } - - // IMavenIdInterceptor - override fun intercept(mavenId: MavenId) : MavenId = - if (isAar(mavenId)) { - val version = mavenId.version ?: "" - MavenId.createNoInterceptors("${mavenId.groupId}:${mavenId.artifactId}:$version@aar") - } else { - mavenId - } - - private val extraSourceDirectories = arrayListOf() - - // ISourceDirectoryContributor - override fun sourceDirectoriesFor(project: Project, context: KobaltContext): List = extraSourceDirectories - - // IBuildConfigFieldContributor - override fun fieldsFor(project: Project, context: KobaltContext): List { - val result = arrayListOf() - configurationFor(project)?.let { config -> - result.add(BuildConfigField("String", "VERSION_NAME", "\"${config.defaultConfig.versionName}\"")) - result.add(BuildConfigField("int", "VERSION_CODE", "${config.defaultConfig.versionCode}")) - } - return result - } - - //ITaskContributor - override fun tasksFor(context: KobaltContext): List = taskContributor.dynamicTasks -} - -class DefaultConfig(var minSdkVersion: Int? = null, - val maxSdkVersion: String? = null, - var targetSdkVersion: String? = null, - var versionCode: Int? = null, - var versionName: String? = null) { - var buildConfig : BuildConfig? = BuildConfig() -} - -class AndroidConfig(val project: Project, - var compileSdkVersion : String? = null, - var buildToolsVersion: String? = null, - var applicationId: String? = null, - val androidHome: String? = null) { - - val signingConfigs = hashMapOf() - - fun addSigningConfig(name: String, project: Project, signingConfig: SigningConfig) { - signingConfigs.put(name, signingConfig) - } - - var defaultConfig: DefaultConfig = DefaultConfig() - - fun defaultConfig(init: DefaultConfig.() -> Unit) { - defaultConfig = DefaultConfig().apply { init() } - } -} - -@Directive -fun Project.android(init: AndroidConfig.() -> Unit) : AndroidConfig = let { project -> - return AndroidConfig(project).apply { - init() - (Kobalt.findPlugin(AndroidPlugin.PLUGIN_NAME) as AndroidPlugin).addConfiguration(project, this) - } -} - -class SigningConfig(var storeFile: String = SigningConfig.DEFAULT_STORE_FILE, - var storePassword: String = SigningConfig.DEFAULT_STORE_PASSWORD, - var keyAlias: String = SigningConfig.DEFAULT_KEY_ALIAS, - var keyPassword: String = SigningConfig.DEFAULT_KEY_ALIAS) { - - companion object { - val DEFAULT_STORE_FILE = homeDir(".android", "debug.keystore") - val DEFAULT_STORE_PASSWORD = "android" - val DEFAULT_KEY_ALIAS = "androiddebugkey" - val DEFAULT_KEY_PASSWORD = "android" - } -} - -@Directive -fun AndroidConfig.signingConfig(name: String, init: SigningConfig.() -> Unit) : SigningConfig = let { androidConfig -> - SigningConfig().apply { - init() - androidConfig.addSigningConfig(name, project, this) - } -} - - diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AppInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AppInfo.kt deleted file mode 100644 index 342f512b..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AppInfo.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.android.io.FileWrapper -import com.android.xml.AndroidManifest -import java.io.File - -/** - * Manage the main application id for the app: values from androidConfig{} have precedence over values - * found in the manifest. - */ -class AppInfo(val androidManifest: File, val config: AndroidConfig) { - val abstractManifest = FileWrapper(androidManifest) - - val versionCode : Int - get() = config.defaultConfig.versionCode ?: AndroidManifest.getVersionCode(abstractManifest) - - val versionName : String - get() = config.defaultConfig.versionName ?: versionCode.toString() - - val minSdkVersion: Int - get() = config.defaultConfig.minSdkVersion ?: (AndroidManifest.getMinSdkVersion(abstractManifest) as Int) - - val maxSdkVersion: Int? - get() = config.defaultConfig.maxSdkVersion?.toInt() - - val targetSdkVersion: String? - get() = config.defaultConfig.targetSdkVersion - ?: AndroidManifest.getTargetSdkVersion(abstractManifest)?.toString() -} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt deleted file mode 100644 index 0fd1b15b..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/KobaltResourceMerger.kt +++ /dev/null @@ -1,283 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.android.builder.core.AaptPackageProcessBuilder -import com.android.builder.core.AndroidBuilder -import com.android.builder.core.ErrorReporter -import com.android.builder.core.LibraryRequest -import com.android.builder.dependency.ManifestDependency -import com.android.builder.dependency.SymbolFileProvider -import com.android.builder.model.AaptOptions -import com.android.builder.model.SyncIssue -import com.android.builder.sdk.DefaultSdkLoader -import com.android.builder.sdk.SdkLoader -import com.android.ide.common.blame.Message -import com.android.ide.common.internal.ExecutorSingleton -import com.android.ide.common.process.* -import com.android.ide.common.res2.* -import com.android.manifmerger.ManifestMerger2 -import com.android.sdklib.AndroidTargetHash -import com.android.sdklib.SdkManager -import com.android.utils.ILogger -import com.android.utils.StdLogger -import com.beust.kobalt.Variant -import com.beust.kobalt.api.IClasspathDependency -import com.beust.kobalt.api.Project -import com.beust.kobalt.maven.dependency.MavenDependency -import com.beust.kobalt.misc.KFiles -import com.beust.kobalt.misc.KobaltLogger -import com.beust.kobalt.misc.log -import com.beust.kobalt.misc.logWrap -import java.io.File - -class KobaltProcessResult : ProcessResult { - override fun getExitValue(): Int { - return 0 - } - - override fun assertNormalExitValue(): ProcessResult? { - throw UnsupportedOperationException() - } - - override fun rethrowFailure(): ProcessResult? { - throw UnsupportedOperationException() - } -} - -class KobaltJavaProcessExecutor : JavaProcessExecutor { - override fun execute(javaProcessInfo: JavaProcessInfo?, processOutputHandler: ProcessOutputHandler?) - : ProcessResult? { - log(1, "Executing " + javaProcessInfo!!) - return KobaltProcessResult() - } -} - -class KobaltProcessOutputHandler : BaseProcessOutputHandler() { - override fun handleOutput(processOutput: ProcessOutput) = - log(3, "AndroidBuild output" + processOutput.standardOutput) -} - -class KobaltErrorReporter : ErrorReporter(ErrorReporter.EvaluationMode.STANDARD) { - override fun handleSyncError(data: String?, type: Int, msg: String?): SyncIssue? { - throw UnsupportedOperationException() - } - - override fun receiveMessage(message: Message?) { - throw UnsupportedOperationException() - } -} - -class ProjectLayout { - val mergeBlame: File? = null - val publicText: File? = null - -} - -class KobaltResourceMerger { - fun run(project: Project, variant: Variant, config: AndroidConfig, aarDependencies: List, - rDirectory: String) { - val level = when(KobaltLogger.LOG_LEVEL) { - 3 -> StdLogger.Level.VERBOSE - 2 -> StdLogger.Level.WARNING - else -> StdLogger.Level.ERROR - } - val logger = StdLogger(level) - val androidBuilder = createAndroidBuilder(project, config, logger) - - log(2, "Merging resources") - - // - // Assets - // - processAssets(project, variant, androidBuilder, aarDependencies) - - // - // Manifests - // - val appInfo = processManifests(project, variant, androidBuilder, config) - - // - // Resources - // - KobaltProcessOutputHandler().let { - processResources(project, variant, androidBuilder, aarDependencies, logger, it, appInfo.minSdkVersion) - mergeResources(project, variant, androidBuilder, aarDependencies, rDirectory, it) - } - } - - private fun createAndroidBuilder(project: Project, config: AndroidConfig, logger: ILogger): AndroidBuilder { - val processExecutor = DefaultProcessExecutor(logger) - val javaProcessExecutor = KobaltJavaProcessExecutor() - val androidHome = File(AndroidFiles.androidHome(project, config)) - val sdkLoader : SdkLoader = DefaultSdkLoader.getLoader(androidHome) - val result = AndroidBuilder(project.name, "kobalt-android-plugin", - processExecutor, - javaProcessExecutor, - KobaltErrorReporter(), - logger, - false /* verbose */) - - val libraryRequests = arrayListOf() - val sdk = sdkLoader.getSdkInfo(logger) - val sdkManager = SdkManager.createManager(androidHome.absolutePath, logger) - val maxPlatformTarget = sdkManager.targets.filter { it.isPlatform }.last() - val maxPlatformTargetHash = AndroidTargetHash.getPlatformHashString(maxPlatformTarget.version) - - result.setTargetInfo(sdk, - sdkLoader.getTargetInfo(maxPlatformTargetHash, maxPlatformTarget.buildToolInfo.revision, logger), - libraryRequests) - return result - } - - private fun createLibraryDependencies(project: Project, dependencies: List) - : List { - val result = arrayListOf() - dependencies.filter { - it is MavenDependency && it.jarFile.get().path.endsWith(".aar") - }.forEach { - val dep = it as MavenDependency - result.add(object: ManifestDependency { - override fun getManifest(): File? { - return File(AndroidFiles.explodedManifest(project, dep.mavenId)) - } - - override fun getName() = it.jarFile.get().path - - override fun getManifestDependencies(): List { - return createLibraryDependencies(project, it.directDependencies()) - } - - }) - it.directDependencies() - } - return result - } - - private fun processAssets(project: Project, variant: Variant, androidBuilder: AndroidBuilder, - aarDependencies: List) { - logWrap(2, " Processing assets...", "done") { - val intermediates = File( - KFiles.joinDir(AndroidFiles.intermediates(project), "assets", variant.toIntermediateDir())) - aarDependencies.forEach { - val assetDir = File(it, "assets") - if (assetDir.exists()) { - KFiles.copyRecursively(assetDir, intermediates) - } - } - } - } - - private fun processManifests(project: Project, variant: Variant, androidBuilder: AndroidBuilder, - config: AndroidConfig): AppInfo { - val mainManifest = File("src/main/AndroidManifest.xml") - val appInfo = AppInfo(mainManifest, config) - logWrap(2, " Processing manifests...", "done") { - val manifestOverlays = variant.allDirectories(project).map { - File("src/$it/AndroidManifest.xml") - }.filter { - it.exists() - } - val libraries = createLibraryDependencies(project, project.compileDependencies) - val outManifest = AndroidFiles.mergedManifest(project, variant) - val outAaptSafeManifestLocation = KFiles.joinDir(project.directory, project.buildDirectory, "generatedSafeAapt") - val reportFile = File(KFiles.joinDir(project.directory, project.buildDirectory, "manifest-merger-report.txt")) - androidBuilder.mergeManifests(mainManifest, manifestOverlays, libraries, - null /* package override */, - appInfo.versionCode, - appInfo.versionName, - appInfo.minSdkVersion.toString(), - appInfo.targetSdkVersion, - appInfo.maxSdkVersion, - outManifest, - outAaptSafeManifestLocation, - // TODO: support aar too - ManifestMerger2.MergeType.APPLICATION, - emptyMap() /* placeHolders */, - reportFile) - } - return appInfo - } - - private fun processResources(project: Project, variant: Variant, androidBuilder: AndroidBuilder, - aarDependencies: List, logger: ILogger, processOutputHandler: KobaltProcessOutputHandler, - minSdk: Int) { - logWrap(2, " Processing resources...", "done") { - val layout = ProjectLayout() - val preprocessor = NoOpResourcePreprocessor() - val outputDir = AndroidFiles.mergedResources(project, variant) - val resourceMerger = ResourceMerger(minSdk) - val fullVariantDir = File(variant.toCamelcaseDir()) - val srcList = listOf("main", variant.productFlavor.name, variant.buildType.name, fullVariantDir.path) - .map { "src" + File.separator + it } - - // TODO: figure out why the badSrcList is bad. All this information should be coming from the Variant - val badSrcList = variant.resDirectories(project).map { it.path } - val goodAarList = aarDependencies.map { it.path + File.separator } - (goodAarList + srcList).map { it + File.separator + "res" }.forEach { path -> - with(ResourceSet(path)) { - addSource(File(path)) - loadFromFiles(logger) - setGeneratedSet(GeneratedResourceSet(this)) - resourceMerger.addDataSet(this) - } - } - - val writer = MergedResourceWriter(File(outputDir), - androidBuilder.getAaptCruncher(processOutputHandler), - false /* don't crunch */, - false /* don't process 9patch */, - layout.publicText, - layout.mergeBlame, - preprocessor) - resourceMerger.mergeData(writer, true) - ExecutorSingleton.getExecutor().shutdown() - } - } - - private fun mergeResources(project: Project, variant: Variant, androidBuilder: AndroidBuilder, - aarDependencies: List, rDirectory: String, - processOutputHandler: KobaltProcessOutputHandler) { - logWrap(2, " Merging resources...", "done") { - - val aaptOptions = object : AaptOptions { - override fun getAdditionalParameters() = emptyList() - override fun getFailOnMissingConfigEntry() = false - override fun getIgnoreAssets() = null - override fun getNoCompress() = null - } - - val aaptCommand = AaptPackageProcessBuilder(File(AndroidFiles.mergedManifest(project, variant)), - aaptOptions) - - fun toSymbolFileProvider(aarDirectory: File) = object : SymbolFileProvider { - override fun getManifest() = File(aarDirectory, "AndroidManifest.xml") - override fun isOptional() = false - override fun getSymbolFile() = File(aarDirectory, "R.txt") - } - - val variantDir = variant.toIntermediateDir() - val generated = KFiles.joinAndMakeDir(project.directory, project.buildDirectory, "symbols") - with(aaptCommand) { - setSourceOutputDir(rDirectory) - val libraries = aarDependencies.map { toSymbolFileProvider(it) } - setLibraries(libraries) - val r = libraries[0].symbolFile - setResFolder(File(AndroidFiles.mergedResources(project, variant))) - setAssetsFolder(File(KFiles.joinAndMakeDir(AndroidFiles.intermediates(project), "assets", variantDir))) - aaptCommand.setResPackageOutput(AndroidFiles.temporaryApk(project, variant.shortArchiveName)) - aaptCommand.setSymbolOutputDir(generated) - -// aaptCommand.setSourceOutputDir(generated) -// aaptCommand.setPackageForR(pkg) -// aaptCommand.setProguardOutput(proguardTxt) -// aaptCommand.setType(if (lib) VariantType.LIBRARY else VariantType.DEFAULT) -// aaptCommand.setDebuggable(debug) - } - - androidBuilder.processResources(aaptCommand, true, processOutputHandler) - } - } - - fun dex(project: Project) { -// androidBuilder.createMainDexList() - } -} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/Proguard.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/Proguard.kt deleted file mode 100644 index 96cd064f..00000000 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/Proguard.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.beust.kobalt.plugin.android - -import com.beust.kobalt.misc.KFiles - -class Proguard(val androidHome: String) { - val proguardHome = KFiles.joinDir(androidHome, "tools", "proguard") - val proguardCommand = KFiles.joinDir(proguardHome, "bin", "proguard.sh") - - fun getDefaultProguardFile(name: String) = KFiles.joinDir(proguardHome, name) -} diff --git a/src/main/resources/META-INF/kobalt-core-plugin.xml b/src/main/resources/META-INF/kobalt-core-plugin.xml index cadfc4a4..17812e37 100644 --- a/src/main/resources/META-INF/kobalt-core-plugin.xml +++ b/src/main/resources/META-INF/kobalt-core-plugin.xml @@ -5,7 +5,6 @@ com.beust.kobalt.plugin.java.JavaPlugin - com.beust.kobalt.plugin.android.AndroidPlugin com.beust.kobalt.plugin.application.ApplicationPlugin com.beust.kobalt.plugin.KobaltPlugin com.beust.kobalt.plugin.kotlin.KotlinPlugin