diff --git a/src/main/kotlin/com/beust/kobalt/Variant.kt b/src/main/kotlin/com/beust/kobalt/Variant.kt index ec696d8c..6aff8efb 100644 --- a/src/main/kotlin/com/beust/kobalt/Variant.kt +++ b/src/main/kotlin/com/beust/kobalt/Variant.kt @@ -12,7 +12,16 @@ class Variant(val productFlavor: ProductFlavorConfig? = null, val buildType: Bui val isDefault : Boolean get() = productFlavor == null && buildType == null - fun toTask(taskName: String) = taskName + productFlavor?.name?.capitalize() + buildType?.name?.capitalize() + fun toTask(taskName: String) = taskName + + (productFlavor?.name?.capitalize() ?: "") + + (buildType?.name?.capitalize() ?: "") + + fun variantSourceDirectories(context: KobaltContext) : List { + val result = + if (isDefault) listOf("src/main") + else (listOf(buildType?.name) + listOf(productFlavor?.name)).filterNotNull() + return result.map { File(it) } + } fun sourceDirectories(project: Project) : List { val sourceDirectories = project.sourceDirectories.map { File(it) } @@ -88,4 +97,36 @@ class Variant(val productFlavor: ProductFlavorConfig? = null, val buildType: Bui } } + override fun toString() = toTask("") + + companion object { + fun allVariants(project: Project): List { + val result = arrayListOf() + + if (project.buildTypes.size > 0) { + project.buildTypes.keys.forEach { + val bt = project.buildTypes[it] + if (project.productFlavors.size > 0) { + project.productFlavors.keys.forEach { + result.add(Variant(project.productFlavors[it], bt)) + } + } else { + result.add(Variant(null, bt)) + } + } + } else if (project.productFlavors.size > 0) { + project.productFlavors.keys.forEach { + val pf = project.productFlavors[it] + if (project.buildTypes.size > 0) { + project.buildTypes.keys.forEach { + result.add(Variant(pf, project.buildTypes[it])) + } + } else { + result.add(Variant(pf, null)) + } + } + } + return result + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt b/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt index 53d1da78..a2c1cf47 100644 --- a/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt @@ -32,20 +32,15 @@ abstract public class BasePlugin : IPlugin { */ protected fun addVariantTasks(project: Project, taskName: String, runAfter : List, runTask: (Project) -> TaskResult) { - project.productFlavors.keys.forEach { - val pf = project.productFlavors.get(it) - project.buildTypes.keys.forEach { btName -> - val bt = project.buildTypes[btName] - val variant = Variant(pf, bt) - val taskName = variant.toTask(taskName) - addTask(project, taskName, taskName, - runAfter = runAfter.map { variant.toTask(it) }, - task = { p: Project -> - context.variant = Variant(pf, bt) - runTask(project) - TaskResult() - }) - } + Variant.allVariants(project).forEach { variant -> + val taskName = variant.toTask(taskName) + addTask(project, taskName, taskName, + runAfter = runAfter.map { variant.toTask(it) }, + task = { p: Project -> + context.variant = variant + runTask(project) + TaskResult() + }) } } diff --git a/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt b/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt index 9093b53e..33a3ba9d 100644 --- a/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt @@ -34,6 +34,9 @@ abstract class JvmCompilerPlugin @Inject constructor( @ExportedProjectProperty(doc = "Projects this project depends on", type = "List") const val DEPENDENT_PROJECTS = "dependentProjects" + @ExportedProjectProperty(doc = "Compiler args", type = "List") + const val COMPILER_ARGS = "compilerArgs" + const val TASK_CLEAN = "clean" const val TASK_TEST = "test" @@ -133,10 +136,19 @@ abstract class JvmCompilerPlugin @Inject constructor( } } - protected val compilerArgs = arrayListOf() + private val compilerArgs = hashMapOf>() - fun addCompilerArgs(vararg args: String) { - compilerArgs.addAll(args) + protected fun compilerArgsFor(project: Project) : List { + val result = project.projectProperties.get(COMPILER_ARGS) + if (result != null) { + return result as List + } else { + return emptyList() + } + } + + fun addCompilerArgs(project: Project, vararg args: String) { + project.projectProperties.put(COMPILER_ARGS, arrayListOf(*args)) } fun findSourceFiles(dir: String, sourceDirectories: Collection): List { 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 f9ae0bc5..eb9e5a43 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt @@ -2,6 +2,7 @@ package com.beust.kobalt.plugin.android import com.beust.kobalt.OperatingSystem import com.beust.kobalt.TaskResult +import com.beust.kobalt.Variant import com.beust.kobalt.api.* import com.beust.kobalt.api.annotation.Directive import com.beust.kobalt.api.annotation.Task @@ -23,6 +24,7 @@ import java.io.File import java.net.URI import java.nio.file.Path import java.nio.file.Paths +import java.util.* class AndroidConfig(var compileSdkVersion : String = "23", var buildToolsVersion : String = "23.0.1", @@ -42,21 +44,20 @@ val Project.isAndroid : Boolean @Singleton public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler) - : ConfigPlugin(), IClasspathContributor, IRepoContributor { + : ConfigPlugin(), IClasspathContributor, IRepoContributor, ICompilerFlagContributor { override val name = "android" fun isAndroid(project: Project) = configurationFor(project) != null override fun apply(project: Project, context: KobaltContext) { super.apply(project, context) - log(1, "Applying plug-in Android on project $project") if (accept(project)) { project.compileDependencies.add(FileDependency(androidJar(project).toString())) } context.pluginInfo.classpathContributors.add(this) // TODO: Find a more flexible way of enabling this, e.g. creating a contributor for it - (Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs("-target", "1.6", "-source", "1.6") +// (Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs("-target", "1.6", "-source", "1.6") } @@ -114,50 +115,68 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler) return TaskResult() } - open class AndroidCommand(androidHome: String, command: String) : RunCommand(command) { - init { - env.put("ANDROID_HOME", androidHome) - } - } - - inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String, - cwd: File = File(project.directory)) : AndroidCommand(androidHome(project), aapt) { + inner open class AndroidCommand(project: Project, command: String, cwd: File = File(project.directory)) + : RunCommand(command) { init { + env.put("ANDROID_HOME", androidHome(project)) directory = cwd } - fun call(args: List) = run(arrayListOf(aaptCommand) + args, + fun call(args: List) = run(args, + successCallback = { output -> + log(1, "$command succeeded:") + output.forEach { + log(1, " $it") + } + }, errorCallback = { output -> - error("Error running $aaptCommand:") + error("Error running $command:") output.forEach { error(" $it") } }) } +// inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String, +// cwd: File = File(project.directory)) : AndroidCommand(project, aapt) { +// init { +// directory = cwd +// } +// +// override val commandName = "$aapt $aaptCommand" +// } + + private fun findResDirs(project: Project) = project.sourceDirectories.filter { it.contains("res") } + + private fun findManifests(variant: Variant) = variant.variantSourceDirectories(context).map { + File(it, "AndroidManifest.xml") + }.filter { + it.exists() + } + private fun generateR(project: Project, generated: Path, aapt: String, resDir: String) { val compileSdkVersion = compileSdkVersion(project) val androidJar = Paths.get(androidHome(project), "platforms", "android-$compileSdkVersion", "android.jar") val applicationId = configurationFor(project)?.applicationId!! - val manifestDir = Paths.get(project.directory, "app", "src", "main").toString() - val manifest = Paths.get(manifestDir, "AndroidManifest.xml") + val manifests = findManifests(context.variant) val crunchedPngDir = KFiles.joinAndMakeDir(intermediates(project).toString(), "res", flavor) - AaptCommand(project, aapt, "crunch").call(listOf( + val resDirArgs = arrayListOf("-S", resDir) + findResDirs(project).filter { + File(it).exists() + }.map { + "-S $it" + }.joinToString(" ").split(" ") + AndroidCommand(project, aapt).call(listOf("crunch") + resDirArgs + listOf( "-v", - "-S", "app/src/main/res",//resourceDir, - "-S", resDir, "-C", crunchedPngDir )) - AaptCommand(project, aapt, "package").call(listOf( + val manifestArgs = manifests.map { "-M ${it.path}" }.joinToString(" ").split(" ") + AndroidCommand(project, aapt).call(listOf("package") + resDirArgs + manifestArgs + listOf( "-f", "--no-crunch", "-I", androidJar.toString(), - "-M", manifest.toString(), - "-S", "app/src/main/res", - "-S", resDir, // where to find more assets "-A", KFiles.joinAndMakeDir(intermediates(project).toString(), "assets", flavor), "-m", // create directory @@ -206,19 +225,37 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler) // Copy all the resources from this aar into the same intermediate directory log(2, "Copying the resources to $resDir") - KFiles.copyRecursively(destDir.resolve("res"), resDir, deleteFirst = false) + KFiles.copyRecursively(destDir.resolve("res"), resDir, deleteFirst = true) } } private fun compile(project: Project, rDirectory: String): File { val sourceFiles = arrayListOf(Paths.get(rDirectory, "R.java").toFile().path) + val c = sourceFiles.javaClass val buildDir = Paths.get(project.buildDirectory, "generated", "classes").toFile() - val cai = CompilerActionInfo(project.directory, listOf(), sourceFiles, buildDir, listOf( - "-source", "1.6", "-target", "1.6")) + val cai = CompilerActionInfo(project.directory, listOf(), sourceFiles, buildDir, emptyList()) javaCompiler.compile(project, context, cai) return buildDir } + /** + * 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) : List { + val result : ArrayList = project.projectProperties.get(JvmCompilerPlugin.COMPILER_ARGS)?.let { + arrayListOf().apply { addAll(it as List) } + } ?: arrayListOf() + if (! result.contains("-source")) with(result) { + addAll(listOf("-source", "1.6")) + } + if (! result.contains("-target")) with(result) { + addAll(listOf("-target", "1.6")) + } + return result + } + companion object { const val TASK_GENERATE_DEX = "generateDex" } @@ -244,16 +281,17 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler) it.jarFile.get().path }.filter { ! it.endsWith(".aar") && ! it.endsWith("android.jar") } } ?: emptyList() - RunCommand(dx).run(args + otherArgs) + AndroidCommand(project, dx).run(args + otherArgs) // // 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 { + AndroidCommand(project, aapt(project)).apply { directory = File(outClassesDex).parentFile - }.call(listOf("-v", KFiles.joinDir(File(temporaryApk(project, flavor)).absolutePath), classesDex)) + }.call(listOf("add") + listOf("-v", KFiles.joinDir(File(temporaryApk(project, flavor)).absolutePath), + classesDex)) return TaskResult() } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt index 0e8a58e4..930b740e 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt @@ -55,7 +55,7 @@ public class JavaPlugin @Inject constructor( override fun doJavadoc(project: Project, cai: CompilerActionInfo) : TaskResult { val result = if (cai.sourceFiles.size > 0) { - javaCompiler.javadoc(project, context, cai.copy(compilerArgs = compilerArgs)) + javaCompiler.javadoc(project, context, cai.copy(compilerArgs = compilerArgsFor(project))) } else { warn("Couldn't find any source files to run Javadoc on") TaskResult() @@ -66,7 +66,7 @@ public class JavaPlugin @Inject constructor( override fun doCompile(project: Project, cai: CompilerActionInfo) : TaskResult { val result = if (cai.sourceFiles.size > 0) { - javaCompiler.compile(project, context, cai.copy(compilerArgs = compilerArgs)) + javaCompiler.compile(project, context, cai.copy(compilerArgs = compilerArgsFor(project))) } else { warn("Couldn't find any source files to compile") TaskResult() @@ -82,7 +82,7 @@ public class JavaPlugin @Inject constructor( copyResources(project, JvmCompilerPlugin.SOURCE_SET_TEST) val buildDir = makeOutputTestDir(project) javaCompiler.compile(project, context, CompilerActionInfo(project.directory, testDependencies(project), - sourceFiles, buildDir, compilerArgs)) + sourceFiles, buildDir, compilerArgsFor(project))) } else { warn("Couldn't find any tests to compile") TaskResult() @@ -99,12 +99,14 @@ public fun javaProject(vararg project: Project, init: JavaProject.() -> Unit): J } } -class JavaCompilerConfig { +class JavaCompilerConfig(val project: Project) { fun args(vararg options: String) { - (Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs(*options) + (Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs(project, *options) } } @Directive -fun Project.javaCompiler(init: JavaCompilerConfig.() -> Unit) = JavaCompilerConfig().init() +fun Project.javaCompiler(init: JavaCompilerConfig.() -> Unit) = let { + JavaCompilerConfig(it).init() +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProjectInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProjectInfo.kt index ba74e206..d1e22d06 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProjectInfo.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProjectInfo.kt @@ -8,8 +8,8 @@ import com.google.inject.Singleton @Singleton class JavaProjectInfo : IProjectInfo { override val sourceDirectory = "java" - override val defaultSourceDirectories = hashSetOf("src/main/java", "src/main/resources") - override val defaultTestDirectories = hashSetOf("src/test/java", "src/test/resources") + override val defaultSourceDirectories = hashSetOf("src/main/java", "src/main/resources", "src/main/res") + override val defaultTestDirectories = hashSetOf("src/test/java", "src/test/resources", "src/test/res") private fun generate(type: String, name: String, value: Any) = " public static $type $name = $value;" diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt index 7eb15106..5a9aa9ac 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt @@ -85,7 +85,7 @@ class KotlinPlugin @Inject constructor( return kotlinCompilePrivate { classpath(cpList.map { it.jarFile.get().absolutePath }) sourceFiles(sources) - compilerArgs(compilerArgs) + compilerArgs(compilerArgsFor(project)) output = outputDirectory }.compile(project, context) } @@ -120,11 +120,13 @@ fun kotlinProject(vararg project: Project, init: KotlinProject.() -> Unit): Kotl } } -class KotlinCompilerConfig { +class KotlinCompilerConfig(val project: Project) { fun args(vararg options: String) { - (Kobalt.findPlugin("kotlin") as JvmCompilerPlugin).addCompilerArgs(*options) + (Kobalt.findPlugin("kotlin") as JvmCompilerPlugin).addCompilerArgs(project, *options) } } @Directive -fun Project.kotlinCompiler(init: KotlinCompilerConfig.() -> Unit) = KotlinCompilerConfig().init() +fun Project.kotlinCompiler(init: KotlinCompilerConfig.() -> Unit) = let { + KotlinCompilerConfig(it).init() +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProjectInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProjectInfo.kt index 6be7d7a1..e466a712 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProjectInfo.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProjectInfo.kt @@ -8,8 +8,8 @@ import com.google.inject.Singleton @Singleton class KotlinProjectInfo : IProjectInfo { override val sourceDirectory = "kotlin" - override val defaultSourceDirectories = hashSetOf("src/main/kotlin", "src/main/resources") - override val defaultTestDirectories = hashSetOf("src/test/kotlin", "src/test/resources") + override val defaultSourceDirectories = hashSetOf("src/main/kotlin", "src/main/resources", "src/main/res") + override val defaultTestDirectories = hashSetOf("src/test/kotlin", "src/test/resources", "src/test/res") private fun generate(type: String, name: String, value: Any) = " val $name : $type = $value" diff --git a/src/main/resources/META-INF/kobalt-plugin.xml b/src/main/resources/META-INF/kobalt-plugin.xml index efe2133d..17a241ff 100644 --- a/src/main/resources/META-INF/kobalt-plugin.xml +++ b/src/main/resources/META-INF/kobalt-plugin.xml @@ -28,6 +28,7 @@ com.beust.kobalt.plugin.android.AndroidPlugin + com.beust.kobalt.plugin.android.AndroidPlugin com.beust.kobalt.plugin.apt.AptPlugin \ No newline at end of file diff --git a/src/test/kotlin/com/beust/kobalt/VariantTest.kt b/src/test/kotlin/com/beust/kobalt/VariantTest.kt new file mode 100644 index 00000000..4e8c7c68 --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/VariantTest.kt @@ -0,0 +1,43 @@ +package com.beust.kobalt + +import com.beust.kobalt.api.buildType +import com.beust.kobalt.api.productFlavor +import com.beust.kobalt.plugin.java.JavaProject +import org.testng.Assert +import org.testng.annotations.DataProvider +import org.testng.annotations.Test +import java.util.* + +class VariantTest : KobaltTest() { + + @DataProvider(name = "projectVariants") + fun projectVariants() = arrayOf( + arrayOf(emptySet(), JavaProject().apply { + }), + arrayOf(hashSetOf("compileDev"), JavaProject().apply { + productFlavor("dev") {} + }), + arrayOf(hashSetOf("compileDev", "compileProd"), JavaProject().apply { + productFlavor("dev") {} + productFlavor("prod") {} + }), + arrayOf(hashSetOf("compileDevDebug"), JavaProject().apply { + productFlavor("dev") {} + buildType("debug") {} + }), + arrayOf(hashSetOf("compileDevRelease", "compileDevDebug", "compileProdDebug", "compileProdRelease"), + JavaProject().apply { + productFlavor("dev") {} + productFlavor("prod") {} + buildType("debug") {} + buildType("release") {} + }) + ) + + @Test(dataProvider = "projectVariants", description = + "Make sure we generate the correct dynamic tasks based on the product flavor and build types.") + fun taskNamesShouldWork(expected: Set, project: JavaProject) { + val variantNames = HashSet(Variant.allVariants(project).map { it.toTask("compile") }) + Assert.assertEquals(variantNames, expected) + } +}