1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 00:17:11 -07:00

Better variant support.

This commit is contained in:
Cedric Beust 2015-11-21 02:49:59 -08:00
parent 354668b6a3
commit e16892dbdb
10 changed files with 194 additions and 60 deletions

View file

@ -12,7 +12,16 @@ class Variant(val productFlavor: ProductFlavorConfig? = null, val buildType: Bui
val isDefault : Boolean val isDefault : Boolean
get() = productFlavor == null && buildType == null 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<File> {
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<File> { fun sourceDirectories(project: Project) : List<File> {
val sourceDirectories = project.sourceDirectories.map { File(it) } 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<Variant> {
val result = arrayListOf<Variant>()
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
}
}
} }

View file

@ -32,20 +32,15 @@ abstract public class BasePlugin : IPlugin {
*/ */
protected fun addVariantTasks(project: Project, taskName: String, runAfter : List<String>, protected fun addVariantTasks(project: Project, taskName: String, runAfter : List<String>,
runTask: (Project) -> TaskResult) { runTask: (Project) -> TaskResult) {
project.productFlavors.keys.forEach { Variant.allVariants(project).forEach { variant ->
val pf = project.productFlavors.get(it) val taskName = variant.toTask(taskName)
project.buildTypes.keys.forEach { btName -> addTask(project, taskName, taskName,
val bt = project.buildTypes[btName] runAfter = runAfter.map { variant.toTask(it) },
val variant = Variant(pf, bt) task = { p: Project ->
val taskName = variant.toTask(taskName) context.variant = variant
addTask(project, taskName, taskName, runTask(project)
runAfter = runAfter.map { variant.toTask(it) }, TaskResult()
task = { p: Project -> })
context.variant = Variant(pf, bt)
runTask(project)
TaskResult()
})
}
} }
} }

View file

@ -34,6 +34,9 @@ abstract class JvmCompilerPlugin @Inject constructor(
@ExportedProjectProperty(doc = "Projects this project depends on", type = "List<ProjectDescription>") @ExportedProjectProperty(doc = "Projects this project depends on", type = "List<ProjectDescription>")
const val DEPENDENT_PROJECTS = "dependentProjects" const val DEPENDENT_PROJECTS = "dependentProjects"
@ExportedProjectProperty(doc = "Compiler args", type = "List<String>")
const val COMPILER_ARGS = "compilerArgs"
const val TASK_CLEAN = "clean" const val TASK_CLEAN = "clean"
const val TASK_TEST = "test" const val TASK_TEST = "test"
@ -133,10 +136,19 @@ abstract class JvmCompilerPlugin @Inject constructor(
} }
} }
protected val compilerArgs = arrayListOf<String>() private val compilerArgs = hashMapOf<String, List<String>>()
fun addCompilerArgs(vararg args: String) { protected fun compilerArgsFor(project: Project) : List<String> {
compilerArgs.addAll(args) val result = project.projectProperties.get(COMPILER_ARGS)
if (result != null) {
return result as List<String>
} else {
return emptyList()
}
}
fun addCompilerArgs(project: Project, vararg args: String) {
project.projectProperties.put(COMPILER_ARGS, arrayListOf(*args))
} }
fun findSourceFiles(dir: String, sourceDirectories: Collection<String>): List<String> { fun findSourceFiles(dir: String, sourceDirectories: Collection<String>): List<String> {

View file

@ -2,6 +2,7 @@ package com.beust.kobalt.plugin.android
import com.beust.kobalt.OperatingSystem import com.beust.kobalt.OperatingSystem
import com.beust.kobalt.TaskResult import com.beust.kobalt.TaskResult
import com.beust.kobalt.Variant
import com.beust.kobalt.api.* import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.Directive import com.beust.kobalt.api.annotation.Directive
import com.beust.kobalt.api.annotation.Task import com.beust.kobalt.api.annotation.Task
@ -23,6 +24,7 @@ import java.io.File
import java.net.URI import java.net.URI
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
class AndroidConfig(var compileSdkVersion : String = "23", class AndroidConfig(var compileSdkVersion : String = "23",
var buildToolsVersion : String = "23.0.1", var buildToolsVersion : String = "23.0.1",
@ -42,21 +44,20 @@ val Project.isAndroid : Boolean
@Singleton @Singleton
public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler) public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler)
: ConfigPlugin<AndroidConfig>(), IClasspathContributor, IRepoContributor { : ConfigPlugin<AndroidConfig>(), IClasspathContributor, IRepoContributor, ICompilerFlagContributor {
override val name = "android" override val name = "android"
fun isAndroid(project: Project) = configurationFor(project) != null fun isAndroid(project: Project) = configurationFor(project) != null
override fun apply(project: Project, context: KobaltContext) { override fun apply(project: Project, context: KobaltContext) {
super.apply(project, context) super.apply(project, context)
log(1, "Applying plug-in Android on project $project")
if (accept(project)) { if (accept(project)) {
project.compileDependencies.add(FileDependency(androidJar(project).toString())) project.compileDependencies.add(FileDependency(androidJar(project).toString()))
} }
context.pluginInfo.classpathContributors.add(this) context.pluginInfo.classpathContributors.add(this)
// TODO: Find a more flexible way of enabling this, e.g. creating a contributor for it // 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() return TaskResult()
} }
open class AndroidCommand(androidHome: String, command: String) : RunCommand(command) { inner open class AndroidCommand(project: Project, command: String, cwd: File = File(project.directory))
init { : RunCommand(command) {
env.put("ANDROID_HOME", androidHome)
}
}
inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String,
cwd: File = File(project.directory)) : AndroidCommand(androidHome(project), aapt) {
init { init {
env.put("ANDROID_HOME", androidHome(project))
directory = cwd directory = cwd
} }
fun call(args: List<String>) = run(arrayListOf(aaptCommand) + args, fun call(args: List<String>) = run(args,
successCallback = { output ->
log(1, "$command succeeded:")
output.forEach {
log(1, " $it")
}
},
errorCallback = { output -> errorCallback = { output ->
error("Error running $aaptCommand:") error("Error running $command:")
output.forEach { output.forEach {
error(" $it") 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) { private fun generateR(project: Project, generated: Path, aapt: String, resDir: String) {
val compileSdkVersion = compileSdkVersion(project) val compileSdkVersion = compileSdkVersion(project)
val androidJar = Paths.get(androidHome(project), "platforms", "android-$compileSdkVersion", "android.jar") val androidJar = Paths.get(androidHome(project), "platforms", "android-$compileSdkVersion", "android.jar")
val applicationId = configurationFor(project)?.applicationId!! val applicationId = configurationFor(project)?.applicationId!!
val manifestDir = Paths.get(project.directory, "app", "src", "main").toString() val manifests = findManifests(context.variant)
val manifest = Paths.get(manifestDir, "AndroidManifest.xml")
val crunchedPngDir = KFiles.joinAndMakeDir(intermediates(project).toString(), "res", flavor) 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", "-v",
"-S", "app/src/main/res",//resourceDir,
"-S", resDir,
"-C", crunchedPngDir "-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", "-f",
"--no-crunch", "--no-crunch",
"-I", androidJar.toString(), "-I", androidJar.toString(),
"-M", manifest.toString(),
"-S", "app/src/main/res",
"-S", resDir,
// where to find more assets // where to find more assets
"-A", KFiles.joinAndMakeDir(intermediates(project).toString(), "assets", flavor), "-A", KFiles.joinAndMakeDir(intermediates(project).toString(), "assets", flavor),
"-m", // create directory "-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 // Copy all the resources from this aar into the same intermediate directory
log(2, "Copying the resources to $resDir") 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 { private fun compile(project: Project, rDirectory: String): File {
val sourceFiles = arrayListOf(Paths.get(rDirectory, "R.java").toFile().path) val sourceFiles = arrayListOf(Paths.get(rDirectory, "R.java").toFile().path)
val c = sourceFiles.javaClass
val buildDir = Paths.get(project.buildDirectory, "generated", "classes").toFile() val buildDir = Paths.get(project.buildDirectory, "generated", "classes").toFile()
val cai = CompilerActionInfo(project.directory, listOf(), sourceFiles, buildDir, listOf( val cai = CompilerActionInfo(project.directory, listOf(), sourceFiles, buildDir, emptyList())
"-source", "1.6", "-target", "1.6"))
javaCompiler.compile(project, context, cai) javaCompiler.compile(project, context, cai)
return buildDir 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<String> {
val result : ArrayList<String> = project.projectProperties.get(JvmCompilerPlugin.COMPILER_ARGS)?.let {
arrayListOf<String>().apply { addAll(it as List<String>) }
} ?: arrayListOf<String>()
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 { companion object {
const val TASK_GENERATE_DEX = "generateDex" const val TASK_GENERATE_DEX = "generateDex"
} }
@ -244,16 +281,17 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler)
it.jarFile.get().path it.jarFile.get().path
}.filter { ! it.endsWith(".aar") && ! it.endsWith("android.jar") } }.filter { ! it.endsWith(".aar") && ! it.endsWith("android.jar") }
} ?: emptyList() } ?: emptyList()
RunCommand(dx).run(args + otherArgs) AndroidCommand(project, dx).run(args + otherArgs)
// //
// Add classes.dex to existing .ap_ // Add classes.dex to existing .ap_
// Because aapt doesn't handle directory moving, we need to cd to classes.dex's directory so // 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_. // 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 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() return TaskResult()
} }

View file

@ -55,7 +55,7 @@ public class JavaPlugin @Inject constructor(
override fun doJavadoc(project: Project, cai: CompilerActionInfo) : TaskResult { override fun doJavadoc(project: Project, cai: CompilerActionInfo) : TaskResult {
val result = val result =
if (cai.sourceFiles.size > 0) { if (cai.sourceFiles.size > 0) {
javaCompiler.javadoc(project, context, cai.copy(compilerArgs = compilerArgs)) javaCompiler.javadoc(project, context, cai.copy(compilerArgs = compilerArgsFor(project)))
} else { } else {
warn("Couldn't find any source files to run Javadoc on") warn("Couldn't find any source files to run Javadoc on")
TaskResult() TaskResult()
@ -66,7 +66,7 @@ public class JavaPlugin @Inject constructor(
override fun doCompile(project: Project, cai: CompilerActionInfo) : TaskResult { override fun doCompile(project: Project, cai: CompilerActionInfo) : TaskResult {
val result = val result =
if (cai.sourceFiles.size > 0) { if (cai.sourceFiles.size > 0) {
javaCompiler.compile(project, context, cai.copy(compilerArgs = compilerArgs)) javaCompiler.compile(project, context, cai.copy(compilerArgs = compilerArgsFor(project)))
} else { } else {
warn("Couldn't find any source files to compile") warn("Couldn't find any source files to compile")
TaskResult() TaskResult()
@ -82,7 +82,7 @@ public class JavaPlugin @Inject constructor(
copyResources(project, JvmCompilerPlugin.SOURCE_SET_TEST) copyResources(project, JvmCompilerPlugin.SOURCE_SET_TEST)
val buildDir = makeOutputTestDir(project) val buildDir = makeOutputTestDir(project)
javaCompiler.compile(project, context, CompilerActionInfo(project.directory, testDependencies(project), javaCompiler.compile(project, context, CompilerActionInfo(project.directory, testDependencies(project),
sourceFiles, buildDir, compilerArgs)) sourceFiles, buildDir, compilerArgsFor(project)))
} else { } else {
warn("Couldn't find any tests to compile") warn("Couldn't find any tests to compile")
TaskResult() 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) { fun args(vararg options: String) {
(Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs(*options) (Kobalt.findPlugin("java") as JvmCompilerPlugin).addCompilerArgs(project, *options)
} }
} }
@Directive @Directive
fun Project.javaCompiler(init: JavaCompilerConfig.() -> Unit) = JavaCompilerConfig().init() fun Project.javaCompiler(init: JavaCompilerConfig.() -> Unit) = let {
JavaCompilerConfig(it).init()
}

View file

@ -8,8 +8,8 @@ import com.google.inject.Singleton
@Singleton @Singleton
class JavaProjectInfo : IProjectInfo { class JavaProjectInfo : IProjectInfo {
override val sourceDirectory = "java" override val sourceDirectory = "java"
override val defaultSourceDirectories = hashSetOf("src/main/java", "src/main/resources") override val defaultSourceDirectories = hashSetOf("src/main/java", "src/main/resources", "src/main/res")
override val defaultTestDirectories = hashSetOf("src/test/java", "src/test/resources") override val defaultTestDirectories = hashSetOf("src/test/java", "src/test/resources", "src/test/res")
private fun generate(type: String, name: String, value: Any) = private fun generate(type: String, name: String, value: Any) =
" public static $type $name = $value;" " public static $type $name = $value;"

View file

@ -85,7 +85,7 @@ class KotlinPlugin @Inject constructor(
return kotlinCompilePrivate { return kotlinCompilePrivate {
classpath(cpList.map { it.jarFile.get().absolutePath }) classpath(cpList.map { it.jarFile.get().absolutePath })
sourceFiles(sources) sourceFiles(sources)
compilerArgs(compilerArgs) compilerArgs(compilerArgsFor(project))
output = outputDirectory output = outputDirectory
}.compile(project, context) }.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) { fun args(vararg options: String) {
(Kobalt.findPlugin("kotlin") as JvmCompilerPlugin).addCompilerArgs(*options) (Kobalt.findPlugin("kotlin") as JvmCompilerPlugin).addCompilerArgs(project, *options)
} }
} }
@Directive @Directive
fun Project.kotlinCompiler(init: KotlinCompilerConfig.() -> Unit) = KotlinCompilerConfig().init() fun Project.kotlinCompiler(init: KotlinCompilerConfig.() -> Unit) = let {
KotlinCompilerConfig(it).init()
}

View file

@ -8,8 +8,8 @@ import com.google.inject.Singleton
@Singleton @Singleton
class KotlinProjectInfo : IProjectInfo { class KotlinProjectInfo : IProjectInfo {
override val sourceDirectory = "kotlin" override val sourceDirectory = "kotlin"
override val defaultSourceDirectories = hashSetOf("src/main/kotlin", "src/main/resources") override val defaultSourceDirectories = hashSetOf("src/main/kotlin", "src/main/resources", "src/main/res")
override val defaultTestDirectories = hashSetOf("src/test/kotlin", "src/test/resources") override val defaultTestDirectories = hashSetOf("src/test/kotlin", "src/test/resources", "src/test/res")
private fun generate(type: String, name: String, value: Any) = private fun generate(type: String, name: String, value: Any) =
" val $name : $type = $value" " val $name : $type = $value"

View file

@ -28,6 +28,7 @@
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name> <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
</repo-contributors> </repo-contributors>
<compiler-flag-contributors> <compiler-flag-contributors>
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
<class-name>com.beust.kobalt.plugin.apt.AptPlugin</class-name> <class-name>com.beust.kobalt.plugin.apt.AptPlugin</class-name>
</compiler-flag-contributors> </compiler-flag-contributors>
</kobalt-plugin> </kobalt-plugin>

View file

@ -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<String>(), 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<String>, project: JavaProject) {
val variantNames = HashSet(Variant.allVariants(project).map { it.toTask("compile") })
Assert.assertEquals(variantNames, expected)
}
}