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

Remove the Android plug-in from the core.

It's now in its own project.
This commit is contained in:
Cedric Beust 2015-12-26 00:16:53 +04:00
parent 05d4571a9a
commit 643c85f1a4
9 changed files with 1 additions and 1035 deletions

View file

@ -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

View file

@ -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<String>) = 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())
}
})
}

View file

@ -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())
}
}

View file

@ -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<ActivityXml> = arrayListOf()
}
class ActivityXml {
@XmlAttribute(namespace = AndroidManifest.NAMESPACE, name = "name") @JvmField
var name: String? = null
@XmlElement(name = "intent-filter") @JvmField
var intentFilters: List<IntentFilter> = 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
}

View file

@ -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<AndroidConfig>(), 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<String>()
@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<File>) : List<String> {
log(2, "Predexing")
val result = arrayListOf<String>()
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<String>) = 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<File> {
log(2, "Exploding aars")
val result = arrayListOf<File>()
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<String>) : List<String> {
if (isAndroid(project)) {
var found = currentFlags.any { it == "-source" || it == "-target" }
val result = arrayListOf<String>().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<String>, error: List<String>) =
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<String>, error: List<String>)
= 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<String, IClasspathDependency>()
// IClasspathContributor
override fun entriesFor(project: Project?): Collection<IClasspathDependency> {
return if (project != null) {
classpathEntries.get(project.name) ?: emptyList()
} else {
emptyList()
}
}
// IRepoContributor
override fun reposFor(project: Project?): List<HostConfig> {
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<IClasspathDependency>): 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<IClasspathDependency>): List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
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<File>()
// ISourceDirectoryContributor
override fun sourceDirectoriesFor(project: Project, context: KobaltContext): List<File> = extraSourceDirectories
// IBuildConfigFieldContributor
override fun fieldsFor(project: Project, context: KobaltContext): List<BuildConfigField> {
val result = arrayListOf<BuildConfigField>()
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<DynamicTask> = 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<String, SigningConfig>()
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)
}
}

View file

@ -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()
}

View file

@ -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<File>,
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<LibraryRequest>()
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<IClasspathDependency>)
: List<ManifestDependency> {
val result = arrayListOf<ManifestDependency>()
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<ManifestDependency> {
return createLibraryDependencies(project, it.directDependencies())
}
})
it.directDependencies()
}
return result
}
private fun processAssets(project: Project, variant: Variant, androidBuilder: AndroidBuilder,
aarDependencies: List<File>) {
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<File>, 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<File>, rDirectory: String,
processOutputHandler: KobaltProcessOutputHandler) {
logWrap(2, " Merging resources...", "done") {
val aaptOptions = object : AaptOptions {
override fun getAdditionalParameters() = emptyList<String>()
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()
}
}

View file

@ -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)
}

View file

@ -5,7 +5,6 @@
<!-- Classes within this tag are instantiated and the introspected by Kobalt. Whenever they -->
<!-- are found to implement one of IPluginActor's interfaces, they are added as such -->
<class-name>com.beust.kobalt.plugin.java.JavaPlugin</class-name>
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
<class-name>com.beust.kobalt.plugin.application.ApplicationPlugin</class-name>
<class-name>com.beust.kobalt.plugin.KobaltPlugin</class-name>
<class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>