blob: 3741ba9554b57fc6779a1c7c2e68a9a8cb2590cc [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.databinding
import org.gradle.api.Plugin
import org.gradle.api.Project
import groovy.lang.Closure
import org.codehaus.groovy.runtime.MethodClosure
import org.gradle.api.Task
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.api.ApplicationVariantImpl
import com.android.build.gradle.internal.variant.ApplicationVariantData
import kotlin.properties.Delegates
import java.net.URLClassLoader
import groovy.lang.MetaClass
import java.io.File
import java.util.ArrayList
import org.gradle.api.internal.tasks.compile.DefaultJavaCompileSpec
import org.gradle.api.tasks.WorkResult
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.file.DefaultSourceDirectorySet
import org.gradle.api.tasks.compile.CompileOptions
import org.gradle.internal.Factory
import org.gradle.api.AntBuilder
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager
import org.gradle.api.internal.tasks.compile.DefaultJavaCompilerFactory
import javax.tools.JavaCompiler
import javax.tools.ToolProvider
import java.util.Arrays
import org.apache.commons.io.FileUtils
import com.android.databinding.reflection.ModelAnalyzer
import com.android.databinding.writer.JavaFileWriter
import java.io.FileInputStream
import java.io.FileOutputStream
import org.apache.commons.io.IOUtils
import java.io.FileWriter
import java.io.ByteArrayOutputStream
import org.apache.commons.codec.binary.Base64
import com.android.builder.model.ApiVersion
import com.android.databinding.util.Log
import org.gradle.api.Action
class DataBinderPlugin : Plugin<Project> {
inner class GradleFileWriter(var outputBase: String) : JavaFileWriter {
override fun writeToFile(canonicalName: String, contents: String) {
val f = File("$outputBase/${canonicalName.replaceAll("\\.", "/")}.java")
log("Asked to write to ${canonicalName}. outputting to:${f.getAbsolutePath()}")
f.getParentFile().mkdirs()
f.writeText(contents, "utf-8")
}
}
var xmlProcessor: LayoutXmlProcessor by Delegates.notNull()
var project: Project by Delegates.notNull()
var generatedBinderSrc: File by Delegates.notNull()
var generatedBinderOut: File by Delegates.notNull()
var androidJar: File by Delegates.notNull()
var variantData: ApplicationVariantData by Delegates.notNull()
var codeGenTargetFolder: File by Delegates.notNull()
var viewBinderSource: File by Delegates.notNull()
var sdkDir: File by Delegates.notNull()
val viewBinderSourceRoot by Delegates.lazy {
File(project.getBuildDir(), "databinder")
}
var fileWriter: GradleFileWriter by Delegates.notNull()
val viewBinderCompileOutput by Delegates.lazy { File(viewBinderSourceRoot, "out") }
override fun apply(project: Project?) {
if (project == null) return
val generateIntermediateFile = MethodClosure(this, "generateIntermediateFile")
val preprocessLayoutFiles = MethodClosure(this, "preprocessLayoutFiles")
this.project = project
project.afterEvaluate {
// TODO read from app
val variants = arrayListOf("Debug")
xmlProcessor = createXmlProcessor(project)
log("after eval")
//processDebugResources
variants.forEach { variant ->
val processResTasks = it.getTasksByName("process${variant}Resources", true)
processResTasks.forEach {
Log.d { "${it} depends on ${it.getDependsOn()}" }
}
project.getTasks().create("processDataBinding${variant}Resources",
javaClass<DataBindingProcessLayoutsTask>(),
object : Action<DataBindingProcessLayoutsTask> {
override fun execute(task: DataBindingProcessLayoutsTask) {
task.xmlProcessor = xmlProcessor
task.sdkDir = sdkDir
processResTasks.forEach {
// until we add these as a new source folder,
// do it the old way
// TODO uncomment this and comment below
// it.dependsOn(task)
it.doFirst(preprocessLayoutFiles)
it.doLast(generateIntermediateFile)
}
processResTasks.forEach {
it.getDependsOn().filterNot { it == task }.forEach {
Log.d { "adding dependency on ${it} for ${task}" }
task.dependsOn(it)
}
}
}
});
}
}
}
fun log(s: String) {
System.out.println("PLOG: $s")
}
fun createXmlProcessor(p: Project): LayoutXmlProcessor {
val ss = p.getExtensions().getByName("android") as AppExtension
sdkDir = ss.getSdkDirectory()
val minSdkVersion = ss.getDefaultConfig().getMinSdkVersion()
androidJar = File(ss.getSdkDirectory().getAbsolutePath()
+ "/platforms/${ss.getCompileSdkVersion()}/android.jar")
log("creating parser!")
log("project build dir:${p.getBuildDir()}")
val clazz = javaClass<ApplicationVariantImpl>()
val field = clazz.getDeclaredField("variantData")
field.setAccessible(true)
var appVariant = ss.getApplicationVariants().first { it is ApplicationVariantImpl }
variantData = field.get(appVariant) as ApplicationVariantData
val packageName = variantData.generateRClassTask.getPackageForR()
val sources = variantData.getJavaSources()
sources.forEach({
log("source: ${it}");
})
val resourceFolders = arrayListOf(variantData.mergeResourcesTask.getOutputDir())
log("MERGE RES OUTPUT ${variantData.mergeResourcesTask.getOutputDir()}")
//TODO
codeGenTargetFolder = variantData.generateRClassTask.getSourceOutputDir()
val resGenTargetFolder = variantData.generateRClassTask.getResDir()
variantData.addJavaSourceFoldersToModel(codeGenTargetFolder)
variantData.addJavaSourceFoldersToModel(viewBinderSourceRoot)
val jCompileTask = variantData.javaCompileTask
val dexTask = variantData.dexTask
val options = jCompileTask.getOptions()
log("compile options: ${options.optionMap()}")
viewBinderSource = File(viewBinderSourceRoot.getAbsolutePath() + "/src")
viewBinderSource.mkdirs()
variantData.registerJavaGeneratingTask(project.task("dataBinderDummySourceGenTask",
MethodClosure(this, "dummySourceGenTask")),
File(viewBinderSourceRoot.getAbsolutePath() + "/src/"))
viewBinderCompileOutput.mkdirs()
log("view binder source will be ${viewBinderSource}")
log("adding out dir to input files ${viewBinderCompileOutput}")
var inputFiles = dexTask.getInputFiles()
var inputDir = dexTask.getInputDir()
log("current input files for dex are ${inputFiles} or dir ${inputDir}")
if (inputDir != null && inputFiles == null) {
inputFiles = arrayListOf(inputDir)
dexTask.setInputDir(null)
}
inputFiles.add(viewBinderCompileOutput)
dexTask.setInputFiles(inputFiles)
dexTask.doFirst(MethodClosure(this, "preDexAnalysis"))
val writerOutBase = codeGenTargetFolder.getAbsolutePath();
fileWriter = GradleFileWriter(writerOutBase)
return LayoutXmlProcessor(packageName, resourceFolders, fileWriter,
minSdkVersion.getApiLevel())
}
fun dummySourceGenTask(o: Any?) {
System.out.println("running dummySourceGenTask")
}
fun preDexAnalysis(o: Any?) {
val jCompileTask = variantData.javaCompileTask
val dexTask = variantData.dexTask
log("dex task files: ${dexTask.getInputFiles()} ${dexTask.getInputFiles().javaClass}")
log("compile CP: ${jCompileTask.getClasspath().getAsPath()}")
val jarUrl = androidJar.toURI().toURL()
val androidClassLoader = URLClassLoader(array(jarUrl))
val cpFiles = arrayListOf<File>()
cpFiles.addAll(dexTask.getInputFiles())
cpFiles.addAll(jCompileTask.getClasspath().getFiles())
}
fun preprocessLayoutFiles(o: Any?) {
xmlProcessor.processResources()
}
fun generateIntermediateFile(o: Any?) {
xmlProcessor.writeIntermediateFile(sdkDir)
}
}