diff options
11 files changed, 217 insertions, 79 deletions
diff --git a/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java b/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java index b30cb344..2561d73e 100644 --- a/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java +++ b/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java @@ -32,6 +32,11 @@ public @interface BindingBuildInfo { * The folder that includes xml files which are exported by aapt or gradle plugin from layout files */ String layoutInfoDir(); + + /** + * The file to which the list of generated classes should be exported + */ + String exportClassListTo(); boolean isLibrary(); boolean enableDebugLogs() default false; } diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java index 0e1c11eb..1ffe8d20 100644 --- a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java +++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java @@ -16,8 +16,11 @@ package android.databinding.annotationprocessor; +import com.google.common.base.Preconditions; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import android.databinding.BindingBuildInfo; import android.databinding.tool.CompilerChef; @@ -34,6 +37,7 @@ import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; @@ -94,7 +98,8 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { for (Intermediate intermediate : intermediates) { intermediate.appendTo(resourceBundle); } - writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk()); + writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(), + buildInfo.exportClassListTo()); } private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) { @@ -120,7 +125,7 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { } private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule, - int minSdk) + int minSdk, String exportClassNamesTo) throws JAXBException { CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter()); if (compilerChef.hasAnythingToGenerate()) { @@ -130,7 +135,21 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { compilerChef.writeViewBinders(minSdk); } } - if (!forLibraryModule) { + if (forLibraryModule && exportClassNamesTo == null) { + L.e("When compiling a library module, build info must include exportClassListTo path"); + } + if (forLibraryModule) { + Set<String> classNames = compilerChef.getWrittenClassNames(); + String out = StringUtils.join(classNames, System.getProperty("line.separator")); + + L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out); + try { + FileUtils.write(new File(exportClassNamesTo), + out); + } catch (IOException e) { + L.e(e, "Cannot create list of written classes"); + } + } else { compilerChef.writeDbrFile(minSdk); } } diff --git a/compiler/src/main/java/android/databinding/tool/CompilerChef.java b/compiler/src/main/java/android/databinding/tool/CompilerChef.java index 6c011e46..cb307e56 100644 --- a/compiler/src/main/java/android/databinding/tool/CompilerChef.java +++ b/compiler/src/main/java/android/databinding/tool/CompilerChef.java @@ -18,6 +18,8 @@ import android.databinding.tool.util.L; import android.databinding.tool.writer.DataBinderWriter; import android.databinding.tool.writer.JavaFileWriter; +import java.util.Set; + /** * Chef class for compiler. * @@ -87,6 +89,11 @@ public class CompilerChef { mDataBinder.writeBinders(minSdk); } + public Set<String> getWrittenClassNames() { + ensureDataBinder(); + return mDataBinder.getWrittenClassNames(); + } + public interface BindableHolder { void addVariable(String variableName, String containingClassName); } diff --git a/compiler/src/main/java/android/databinding/tool/DataBinder.java b/compiler/src/main/java/android/databinding/tool/DataBinder.java index 49acdb8f..877b4c37 100644 --- a/compiler/src/main/java/android/databinding/tool/DataBinder.java +++ b/compiler/src/main/java/android/databinding/tool/DataBinder.java @@ -34,6 +34,8 @@ public class DataBinder { private JavaFileWriter mFileWriter; + Set<String> writtenClasses = new HashSet<String>(); + public DataBinder(ResourceBundle resourceBundle) { L.d("reading resource bundle into data binder"); for (Map.Entry<String, List<ResourceBundle.LayoutFileBundle>> entry : @@ -48,16 +50,16 @@ public class DataBinder { } public void writerBaseClasses(boolean isLibrary) { - Set<String> writtenFiles = new HashSet<String>(); for (LayoutBinder layoutBinder : mLayoutBinders) { if (isLibrary || layoutBinder.hasVariations()) { String className = layoutBinder.getClassName(); - if (writtenFiles.contains(className)) { + String canonicalName = layoutBinder.getPackage() + "." + className; + if (writtenClasses.contains(canonicalName)) { continue; } - mFileWriter.writeToFile(layoutBinder.getPackage() + "." + className, - layoutBinder.writeViewBinderBaseClass()); - writtenFiles.add(className); + L.d("writing data binder base %s", canonicalName); + mFileWriter.writeToFile(canonicalName, layoutBinder.writeViewBinderBaseClass()); + writtenClasses.add(canonicalName); } } } @@ -65,12 +67,17 @@ public class DataBinder { public void writeBinders(int minSdk) { for (LayoutBinder layoutBinder : mLayoutBinders) { String className = layoutBinder.getImplementationName(); - L.d("writing data binder %s", className); - mFileWriter.writeToFile(layoutBinder.getPackage() + "." + className, - layoutBinder.writeViewBinder(minSdk)); + String canonicalName = layoutBinder.getPackage() + "." + className; + L.d("writing data binder %s", canonicalName); + writtenClasses.add(canonicalName); + mFileWriter.writeToFile(canonicalName, layoutBinder.writeViewBinder(minSdk)); } } + public Set<String> getWrittenClassNames() { + return writtenClasses; + } + public void setFileWriter(JavaFileWriter fileWriter) { mFileWriter = fileWriter; } diff --git a/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java b/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java index faef66a3..503b3cfd 100644 --- a/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java +++ b/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java @@ -161,20 +161,25 @@ public class LayoutXmlProcessor { return name.toString(); } - public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir) { - writeInfoClass(sdkDir, xmlOutDir, false); + public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, + /*Nullable*/ File exportClassListTo) { + writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false); } - public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, boolean enableDebugLogs) { + public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo, + boolean enableDebugLogs) { final String sdkPath = sdkDir == null ? null : StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath()); final Class annotation = BindingBuildInfo.class; final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath()); + final String exportClassListToPath = exportClassListTo == null ? "" : + StringEscapeUtils.escapeJava(exportClassListTo.getAbsolutePath()); String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" + "import " + annotation.getCanonicalName() + ";\n\n" + "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " + "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " + "sdkRoot=" + "\"" + (sdkPath == null ? "" : sdkPath) + "\"," + "layoutInfoDir=\"" + layoutInfoPath + "\"," + + "exportClassListTo=\"" + exportClassListToPath + "\"," + "isLibrary=" + mIsLibrary + "," + "minSdk=" + mMinSdk + "," + "enableDebugLogs=" + enableDebugLogs + ")\n" + diff --git a/compiler/src/main/java/android/databinding/tool/MakeCopy.java b/compiler/src/main/java/android/databinding/tool/MakeCopy.java index 314db47e..f143e21d 100644 --- a/compiler/src/main/java/android/databinding/tool/MakeCopy.java +++ b/compiler/src/main/java/android/databinding/tool/MakeCopy.java @@ -174,7 +174,8 @@ public class MakeCopy { try { xmlProcessor.processResources(minSdk); xmlProcessor.writeLayoutInfoFiles(xmlDir); - xmlProcessor.writeInfoClass(null, xmlDir); + // TODO Looks like make does not support excluding from libs ? + xmlProcessor.writeInfoClass(null, xmlDir, null); Map<String, List<LayoutFileBundle>> bundles = xmlProcessor.getResourceBundle().getLayoutBundles(); if (isLibrary) { diff --git a/gradlePlugin/build.gradle b/gradlePlugin/build.gradle index 2ffab0ca..83aa27cc 100644 --- a/gradlePlugin/build.gradle +++ b/gradlePlugin/build.gradle @@ -46,6 +46,10 @@ dependencies { compile project(":compiler") } +compileJava { + options.compilerArgs = ["-proc:none"] +} + uploadArchives { repositories { mavenDeployer { diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java new file mode 100644 index 00000000..cfc7a98a --- /dev/null +++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 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 android.databinding.tool; + +import com.google.common.base.Preconditions; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.bundling.Jar; + +import android.databinding.tool.util.L; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +/** + * Task to exclude generated classes from the Jar task of a library project + */ +public class DataBindingExcludeGeneratedTask extends DefaultTask { + private String appPackage; + private String infoClassQualifiedName; + @Input + private File generatedClassListFile; + private boolean isLibrary; + + private org.gradle.api.tasks.bundling.Jar packageTask; + private final String EXCLUDE_PATTERN = "android/databinding/layouts/*.*"; + + public void setAppPackage(String appPackage) { + this.appPackage = appPackage; + } + + public void setInfoClassQualifiedName(String infoClassQualifiedName) { + this.infoClassQualifiedName = infoClassQualifiedName; + } + + public void setLibrary(boolean isLibrary) { + this.isLibrary = isLibrary; + } + + public void setPackageTask(Jar packageTask) { + this.packageTask = packageTask; + } + + public void setGeneratedClassListFile(File generatedClassListFile) { + this.generatedClassListFile = generatedClassListFile; + } + + public String getAppPackage() { + return appPackage; + } + + public String getInfoClassQualifiedName() { + return infoClassQualifiedName; + } + + public File getGeneratedClassListFile() { + return generatedClassListFile; + } + + @TaskAction + public void excludeGenerated() { + L.d("Excluding generated classes from jar. Is library ? %s", isLibrary); + String appPkgAsClass = appPackage.replace('.', '/'); + String infoClassAsClass = infoClassQualifiedName.replace('.', '/'); + exclude(infoClassAsClass + ".class"); + exclude(EXCLUDE_PATTERN); + if (isLibrary) { + exclude(appPkgAsClass + "/BR.*"); + List<String> generatedClasses = readGeneratedClasses(); + for (String klass : generatedClasses) { + exclude(klass.replace('.', '/') + ".class"); + } + } + L.d("Excluding generated classes from library jar is done."); + } + + private void exclude(String pattern) { + L.d("exclude %s", pattern); + packageTask.exclude(pattern); + } + + private List<String> readGeneratedClasses() { + Preconditions.checkNotNull(generatedClassListFile, "Data binding exclude generated task" + + " is not configured properly"); + Preconditions.checkArgument(generatedClassListFile.exists(), + "Generated class list does not exist %s", generatedClassListFile.getAbsolutePath()); + FileInputStream fis = null; + try { + fis = new FileInputStream(generatedClassListFile); + return IOUtils.readLines(fis); + } catch (FileNotFoundException e) { + L.e(e, "Unable to read generated class list from %s", + generatedClassListFile.getAbsoluteFile()); + } catch (IOException e) { + L.e(e, "Unexpected exception while reading %s", + generatedClassListFile.getAbsoluteFile()); + } finally { + IOUtils.closeQuietly(fis); + } + Preconditions.checkState(false, "Could not read data binding generated class list"); + return null; + } +} diff --git a/gradlePlugin/src/main/kotlin/DataBindingExportInfoTask.kt b/gradlePlugin/src/main/kotlin/DataBindingExportInfoTask.kt index 776e4263..ee99c6b4 100644 --- a/gradlePlugin/src/main/kotlin/DataBindingExportInfoTask.kt +++ b/gradlePlugin/src/main/kotlin/DataBindingExportInfoTask.kt @@ -25,10 +25,11 @@ open class DataBindingExportInfoTask : DefaultTask() { var xmlProcessor: LayoutXmlProcessor by Delegates.notNull() var sdkDir : File by Delegates.notNull() var xmlOutFolder : File by Delegates.notNull() + var exportClassListTo : File? = null var enableDebugLogs = false - [TaskAction] + @TaskAction public fun doIt() { L.d("running process layouts task %s", getName()) - xmlProcessor.writeInfoClass(sdkDir, xmlOutFolder, enableDebugLogs) + xmlProcessor.writeInfoClass(sdkDir, xmlOutFolder, exportClassListTo, enableDebugLogs) } }
\ No newline at end of file diff --git a/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt b/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt index ec8c348c..3bc0e0fb 100644 --- a/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt +++ b/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt @@ -27,7 +27,7 @@ open class DataBindingProcessLayoutsTask : DefaultTask() { var xmlOutFolder : File by Delegates.notNull() var minSdk: kotlin.Int by Delegates.notNull() - [TaskAction] + @TaskAction public fun doIt() { L.d("running process layouts task %s", getName()) xmlProcessor.processResources(minSdk) diff --git a/gradlePlugin/src/main/kotlin/plugin.kt b/gradlePlugin/src/main/kotlin/plugin.kt index a5df389c..e63e32df 100644 --- a/gradlePlugin/src/main/kotlin/plugin.kt +++ b/gradlePlugin/src/main/kotlin/plugin.kt @@ -37,6 +37,7 @@ import com.android.build.gradle.api.TestVariant import com.android.build.gradle.internal.variant.TestVariantData import com.android.build.gradle.internal.api.TestVariantImpl import com.android.ide.common.res2.ResourceSet +import com.google.common.base.Preconditions import org.apache.commons.io.IOUtils import org.gradle.api.artifacts import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency @@ -54,12 +55,11 @@ import org.gradle.api.plugins.ExtraPropertiesExtension open class DataBinderPlugin : Plugin<Project> { - val XPATH_BINDING_CLASS = "/layout/data/@class" var logger : Logger by Delegates.notNull() inner class GradleFileWriter(var outputBase: String) : JavaFileWriter() { override fun writeToFile(canonicalName: String, contents: String) { - val f = File("$outputBase/${canonicalName.replaceAll("\\.", "/")}.java") + val f = File("$outputBase/${canonicalName.replace("\\.".toRegex(), "/")}.java") logD("Asked to write to ${canonicalName}. outputting to:${f.getAbsolutePath()}") f.getParentFile().mkdirs() f.writeText(contents, "utf-8") @@ -189,11 +189,12 @@ open class DataBinderPlugin : Plugin<Project> { minSdkVersion.getApiLevel(), isLibrary) val processResTask = generateRTask val xmlOutDir = File("${project.getBuildDir()}/layout-info/${configuration.getDirName()}") + val generatedClassListOut = if (isLibrary) File(xmlOutDir, "_generated.txt") else null logD("xml output for ${variantData} is ${xmlOutDir}") val layoutTaskName = "dataBindingLayouts${processResTask.getName().capitalize()}" val infoClassTaskName = "dataBindingInfoClass${processResTask.getName().capitalize()}" - var processLayoutsTask : DataBindingProcessLayoutsTask? = null + var processLayoutsTask: DataBindingProcessLayoutsTask? = null project.getTasks().create(layoutTaskName, javaClass<DataBindingProcessLayoutsTask>(), object : Action<DataBindingProcessLayoutsTask> { @@ -217,76 +218,40 @@ open class DataBinderPlugin : Plugin<Project> { }) project.getTasks().create(infoClassTaskName, javaClass<DataBindingExportInfoTask>(), - object : Action<DataBindingExportInfoTask>{ + object : Action<DataBindingExportInfoTask> { override fun execute(task: DataBindingExportInfoTask) { task.dependsOn(processLayoutsTask!!) task.dependsOn(processResTask) task.xmlProcessor = xmlProcessor task.sdkDir = sdkDir task.xmlOutFolder = xmlOutDir + task.exportClassListTo = generatedClassListOut task.enableDebugLogs = logger.isEnabled(LogLevel.DEBUG) variantData.registerJavaGeneratingTask(task, codeGenTargetFolder) } }) + val packageJarTaskName = "package${fullName.capitalize()}Jar" + val packageTask = project.getTasks().findByName(packageJarTaskName) + if (packageTask is org.gradle.api.tasks.bundling.Jar) { + val removeGeneratedTaskName = "dataBindingExcludeGeneratedFrom${packageTask.getName().capitalize()}" + if (project.getTasks().findByName(removeGeneratedTaskName) == null) { + val javaCompileTask = variantData.javaCompileTask + Preconditions.checkNotNull(javaCompileTask) - if (isLibrary) { - val resourceSets = variantData.mergeResourcesTask.getInputResourceSets() - val customBindings = getCustomBindings(resourceSets, packageName) - val packageJarTaskName = "package${fullName.capitalize()}Jar" - val packageTask = project.getTasks().findByName(packageJarTaskName) - if (packageTask !is org.gradle.api.tasks.bundling.Jar) { - throw RuntimeException("cannot find package task in $project $variantData project $packageJarTaskName") - } - val excludePattern = "android/databinding/layouts/*.*" - val appPkgAsClass = packageName.replace('.', '/') - packageTask.exclude(excludePattern) - packageTask.exclude("$appPkgAsClass/databinding/*") - packageTask.exclude("$appPkgAsClass/BR.*") - packageTask.exclude(xmlProcessor.getInfoClassFullName().replace('.', '/') + ".class") - customBindings.forEach { - packageTask.exclude("${it.replace('.', '/')}.class") - } - logD("excludes ${packageTask.getExcludes()}") - } - } - - fun getCustomBindings(resourceSets : List<ResourceSet>, packageName: String) : List<String> { - val xPathFactory = XPathFactory.newInstance() - val xPath = xPathFactory.newXPath() - val expr = xPath.compile(XPATH_BINDING_CLASS); - val customBindings = ArrayList<String>() - - resourceSets.forEach { set -> - set.getSourceFiles().forEach({ res -> - res.listFiles(object : FileFilter { - override fun accept(file: File?): Boolean { - return file != null && file.isDirectory() && - file.getName().toLowerCase().startsWith("layout") - } - })?.forEach { layoutDir -> - - layoutDir.listFiles(object : FileFilter { - override fun accept(file: File?): Boolean { - return file != null && !file.isDirectory() && - file.getName().toLowerCase().endsWith(".xml") - } - })?.forEach { xmlFile: File -> - val document = parseXml(xmlFile) - val bindingClass = expr.evaluate(document) - if (bindingClass != null && !bindingClass.isEmpty()) { - if (bindingClass.startsWith('.')) { - customBindings.add("${packageName}${bindingClass}") - } else if (bindingClass.contains(".")) { - customBindings.add(bindingClass) - } else { - customBindings.add( - "${packageName}.databinding.${bindingClass}") + project.getTasks().create(removeGeneratedTaskName, + javaClass<DataBindingExcludeGeneratedTask>(), + object : Action<DataBindingExcludeGeneratedTask> { + override fun execute(task: DataBindingExcludeGeneratedTask) { + packageTask.dependsOn(task) + task.dependsOn(javaCompileTask) + task.setAppPackage(packageName) + task.setInfoClassQualifiedName(xmlProcessor.getInfoClassFullName()) + task.setPackageTask(packageTask) + task.setLibrary(isLibrary) + task.setGeneratedClassListFile(generatedClassListOut) } - } - } - } - }) + }) + } } - return customBindings } } |