summaryrefslogtreecommitdiff
path: root/compiler/src/main/java
diff options
context:
space:
mode:
authorYigit Boyar <yboyar@google.com>2018-03-07 09:32:00 -0800
committerYigit Boyar <yboyar@google.com>2018-03-09 14:08:47 -0800
commit213ad9c850a29829f2f61f0cec73944f5f178d05 (patch)
tree6db4db79b052a9d3a7fcf5eb47150fcf567d292c /compiler/src/main/java
parentb5afc9d80f4b7b7c49e1058632d9399ac1db92f3 (diff)
downloaddata-binding-213ad9c850a29829f2f61f0cec73944f5f178d05.tar.gz
Support v1 dependencies in v1 compilation
This CL adds backward compatibility into data binding v2 such that it can consume dependencies that were compiled with v1. We achieve this in 2 steps: * Inside the gradle task, we deserialize v1 layout info and convert it into GenClassLog. This allows gradle task to generate the right code. (albeit, it might depend on not yet existing classes but that is how v1 works anyways). * Then in the annotation processor, we reload the same layout info, create a CompilerChef that thinks it is in v1 and let it generate the code. Finally, we create 1 mapper for all v1 compat classes. All code generated for v1 is stripped for libraries. This is necessary because if 2 separate libraries in an app depend on the same v1 lbirary, both of them will generate code for it, causing duplicate class issues. For apps compiled w/ v2 and has v2 dependencies, cost is almost none (is just the gradle task has 1 more dependency). For apps compiled w/ v1 and has v1 dependencies, it is as slow as v1 is, + a bit more since we end up writing mode classes than we would but it is negligible. Bug: 74264651 Test: MultiModuleTestApp (gradle invokes it w/ v1 - v2 setup) Change-Id: I0e04e7f04b67eb010d4a1bf32e0fce32586a4b90
Diffstat (limited to 'compiler/src/main/java')
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java7
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java48
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressionsFromV1Compat.kt110
-rw-r--r--compiler/src/main/java/android/databinding/tool/CompilerChef.java57
-rw-r--r--compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java8
5 files changed, 204 insertions, 26 deletions
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
index 7252dcbf..8382d127 100644
--- a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
@@ -73,7 +73,7 @@ public class ProcessDataBinding extends AbstractProcessor {
private boolean doProcess(RoundEnvironment roundEnv) {
if (mProcessingSteps == null) {
readArguments();
- initProcessingSteps();
+ initProcessingSteps(processingEnv);
}
if (mCompilerArgs == null) {
return false;
@@ -108,7 +108,7 @@ public class ProcessDataBinding extends AbstractProcessor {
return SourceVersion.latest();
}
- private void initProcessingSteps() {
+ private void initProcessingSteps(ProcessingEnvironment processingEnv) {
final ProcessBindable processBindable = new ProcessBindable();
mProcessingSteps = Arrays.asList(
new ProcessMethodAdapters(),
@@ -141,7 +141,8 @@ public class ProcessDataBinding extends AbstractProcessor {
return;
}
mWrittenMapper = true;
- mChef.writeDataBinderMapper(mCompilerArgs, mBRVariableLookup, mModulePackages);
+ mChef.writeDataBinderMapper(processingEnv, mCompilerArgs, mBRVariableLookup,
+ mModulePackages);
}
@Override
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
index 1006f335..d8b9bbef 100644
--- a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
@@ -17,11 +17,11 @@
package android.databinding.annotationprocessor;
import android.databinding.tool.CompilerChef;
+import android.databinding.tool.Context;
import android.databinding.tool.DataBindingCompilerArgs;
import android.databinding.tool.LayoutXmlProcessor;
import android.databinding.tool.processing.Scope;
import android.databinding.tool.processing.ScopedException;
-import android.databinding.tool.reflection.SdkUtil;
import android.databinding.tool.store.GenClassInfoLog;
import android.databinding.tool.store.ResourceBundle;
import android.databinding.tool.util.GenerationalClassUtil;
@@ -29,9 +29,13 @@ import android.databinding.tool.util.L;
import android.databinding.tool.util.LoggedErrorException;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.util.StringUtils;
+import android.databinding.tool.writer.BindingMapperWriter;
+import android.databinding.tool.writer.BindingMapperWriterV2;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
@@ -50,11 +54,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.TypeElement;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
@@ -72,6 +78,8 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
resourceBundle = new ResourceBundle(args.getModulePackage());
final List<IntermediateV2> intermediateList;
GenClassInfoLog infoLog = null;
+ @Nullable
+ CompilerChef v1CompatChef = null;
if (args.isEnableV2()) {
try {
infoLog = ResourceBundle.loadClassInfoFromFolder(
@@ -83,6 +91,12 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
}
resourceBundle.addDependencyLayouts(infoLog);
intermediateList = Collections.emptyList();
+ v1CompatChef = new ProcessExpressionsFromV1Compat(
+ processingEnvironment,
+ args,
+ loadDependencyIntermediates(),
+ getWriter()
+ ).generate();
} else {
intermediateList = loadDependencyIntermediates();
for (Intermediate intermediate : intermediateList) {
@@ -109,7 +123,7 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
}
// generate them here so that bindable parser can read
try {
- writeResourceBundle(resourceBundle, args, infoLog);
+ writeResourceBundle(resourceBundle, args, infoLog, v1CompatChef);
} catch (Throwable t) {
L.e(t, "cannot generate view binders");
}
@@ -210,9 +224,11 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
private void writeResourceBundle(
ResourceBundle resourceBundle,
DataBindingCompilerArgs compilerArgs,
- @Nullable GenClassInfoLog classInfoLog) {
+ @Nullable GenClassInfoLog classInfoLog,
+ @NonNull CompilerChef v1CompatChef) {
final CompilerChef compilerChef = CompilerChef.createChef(resourceBundle,
getWriter(), compilerArgs);
+ compilerChef.setV1CompatChef(v1CompatChef);
compilerChef.sealModels();
// write this only if we are compiling an app or a library test app.
// even if data binding is enabled for tests, we should not re-generate this.
@@ -237,6 +253,10 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
}
if (compilerArgs.isLibrary() && !compilerArgs.isTestVariant()) {
Set<String> classNames = compilerChef.getClassesToBeStripped();
+ if (v1CompatChef != null) {
+ classNames.addAll(v1CompatChef.getClassesToBeStripped());
+ classNames.add(BindingMapperWriter.V1_COMPAT_QNAME);
+ }
String out = Joiner.on(StringUtils.LINE_SEPARATOR).join(classNames);
L.d("Writing list of classes to %s . \nList:%s",
compilerArgs.getExportClassListTo(), out);
@@ -259,8 +279,6 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
public static class IntermediateV1 implements Intermediate {
- transient Unmarshaller mUnmarshaller;
-
// name to xml content map
Map<String, String> mLayoutInfoMap = new HashMap<String, String>();
@@ -268,29 +286,31 @@ public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
public Intermediate upgrade() {
final IntermediateV2 updated = new IntermediateV2();
updated.mLayoutInfoMap = mLayoutInfoMap;
- updated.mUnmarshaller = mUnmarshaller;
return updated;
}
@Override
public void appendTo(ResourceBundle resourceBundle, boolean fromSource) throws
JAXBException {
- if (mUnmarshaller == null) {
- JAXBContext context = JAXBContext
- .newInstance(ResourceBundle.LayoutFileBundle.class);
- mUnmarshaller = context.createUnmarshaller();
- }
+ extractBundles().forEach(layoutFileBundle -> {
+ resourceBundle.addLayoutBundle(layoutFileBundle, fromSource);
+ });
+ }
+
+ public List<ResourceBundle.LayoutFileBundle> extractBundles() throws JAXBException {
+ List<ResourceBundle.LayoutFileBundle> bundles = new ArrayList<>();
for (String content : mLayoutInfoMap.values()) {
final InputStream is = IOUtils.toInputStream(content);
try {
- final ResourceBundle.LayoutFileBundle bundle
- = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is);
- resourceBundle.addLayoutBundle(bundle, fromSource);
+ final ResourceBundle.LayoutFileBundle bundle = ResourceBundle.LayoutFileBundle
+ .fromXML(is);
+ bundles.add(bundle);
L.d("loaded layout info file %s", bundle);
} finally {
IOUtils.closeQuietly(is);
}
}
+ return bundles;
}
public void addEntry(String name, String contents) {
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressionsFromV1Compat.kt b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressionsFromV1Compat.kt
new file mode 100644
index 00000000..4321b55a
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressionsFromV1Compat.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.annotationprocessor
+
+import android.databinding.tool.CompilerChef
+import android.databinding.tool.DataBindingCompilerArgs
+import android.databinding.tool.store.ResourceBundle
+import android.databinding.tool.writer.BindingMapperWriterV2
+import android.databinding.tool.writer.JavaFileWriter
+import java.util.HashMap
+import javax.annotation.processing.ProcessingEnvironment
+
+/**
+ * Loads intermediates from dependencies which are in V1, generates their code and creates a
+ * compiler chef specific to them. (to be able to treat them like a v2 dependency).
+ */
+class ProcessExpressionsFromV1Compat(
+ private val processingEnvironment: ProcessingEnvironment,
+ private val args : DataBindingCompilerArgs,
+ private val intermediates : List<ProcessExpressions.IntermediateV2>,
+ private val writer : JavaFileWriter) {
+ /**
+ * Returns a CompilerChef if we find V1 dependencies and generate code for them.
+ * Null if all dependencies are in v2
+ */
+ fun generate(): CompilerChef? {
+ // This is tricky:
+ // in libraries, we should generate them in a stripped way so that if same dependency
+ // shows up in 2 different path, it wont blow the final app
+ // if we are an app, we should generate it.
+ val isModuleInV2Lookup = HashMap<String, Boolean>()
+
+ fun isModuleInV2(modulePackage : String) : Boolean {
+ return isModuleInV2Lookup.getOrPut(modulePackage) {
+ val mapperClass = BindingMapperWriterV2.createMapperQName(modulePackage)
+ // check if mapper exists for it
+ val typeElement =
+ processingEnvironment.elementUtils.getTypeElement(mapperClass)
+ typeElement != null
+ }
+ }
+ // mapping from key (layoutName) to generated code QName (or base class)
+ val classMapping = mutableMapOf<String, String>()
+ val compatBundle = ResourceBundle(args.modulePackage)
+ intermediates
+ .flatMap {
+ it.extractBundles()
+ }
+ .filterNot { layoutFileBundle ->
+ isModuleInV2(layoutFileBundle.modulePackage)
+ }
+ .forEach {
+ classMapping[it.fileName] = it.fullBindingClass
+ compatBundle.addLayoutBundle(it, false)
+ }
+ return writeResourceBundle(
+ resourceBundle = compatBundle,
+ compilerArgs = args.copyAsV1(COMPAT_PACKAGE)
+ )
+ }
+
+ /**
+ * Generates the code for v1 compat, returns the newly generated CompilerChef.
+ * Returns null if we don't generate anything.
+ */
+ private fun writeResourceBundle(
+ resourceBundle: ResourceBundle,
+ compilerArgs: DataBindingCompilerArgs): CompilerChef? {
+ val compilerChef = CompilerChef.createChef(
+ resourceBundle,
+ writer, compilerArgs
+ )
+ compilerChef.sealModels()
+ // write this only if we are compiling an app or a library test app.
+ // even if data binding is enabled for tests, we should not re-generate this.
+ if (compilerChef.hasAnythingToGenerate()) {
+ if (!compilerArgs.isEnableV2) {
+ compilerChef.writeViewBinderInterfaces(compilerArgs.isLibrary
+ && !compilerArgs.isTestVariant)
+ }
+ if (compilerArgs.isApp != compilerArgs.isTestVariant
+ || compilerArgs.isEnabledForTests && !compilerArgs.isLibrary
+ || compilerArgs.isEnableV2
+ ) {
+ compilerChef.writeViewBinders(compilerArgs.minApi)
+ }
+ } else {
+ return null
+ }
+ return compilerChef
+ }
+
+ companion object {
+ const val COMPAT_PACKAGE = "android.databinding.v1Compat"
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/tool/CompilerChef.java b/compiler/src/main/java/android/databinding/tool/CompilerChef.java
index b747d8b4..45638966 100644
--- a/compiler/src/main/java/android/databinding/tool/CompilerChef.java
+++ b/compiler/src/main/java/android/databinding/tool/CompilerChef.java
@@ -32,6 +32,8 @@ import android.databinding.tool.writer.BindingMapperWriterV2;
import android.databinding.tool.writer.JavaFileWriter;
import android.databinding.tool.writer.MergedBindingMapperWriter;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
@@ -43,6 +45,11 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
/**
* Chef class for compiler.
@@ -79,10 +86,17 @@ public class CompilerChef {
private ResourceBundle mResourceBundle;
private DataBinder mDataBinder;
private boolean mEnableV2;
+ // the compiler chef we create for V1 dependencies
+ @Nullable
+ private CompilerChef mV1CompatChef;
private CompilerChef() {
}
+ public void setV1CompatChef(CompilerChef v1CompatChef) {
+ mV1CompatChef = v1CompatChef;
+ }
+
public static CompilerChef createChef(
ResourceBundle bundle,
JavaFileWriter fileWriter,
@@ -171,6 +185,7 @@ public class CompilerChef {
}
public void writeDataBinderMapper(
+ ProcessingEnvironment processingEnv,
DataBindingCompilerArgs compilerArgs,
Map<String, Integer> brValueLookup,
List<String> modulePackages) {
@@ -186,6 +201,9 @@ public class CompilerChef {
if (generateMapper) {
writeMapperForModule(compilerArgs, brValueLookup);
}
+ if (mV1CompatChef != null) {
+ writeMapperForV1Compat(compilerArgs, brValueLookup);
+ }
// merged mapper is the one generated for the whole app that includes the mappers
// generated for individual modules.
@@ -198,7 +216,7 @@ public class CompilerChef {
generateMergedMapper = false;
}
if (generateMergedMapper) {
- writeMergedMapper(compilerArgs, modulePackages);
+ writeMergedMapper(processingEnv, compilerArgs, modulePackages);
}
} else {
final String pkg = "android.databinding";
@@ -213,17 +231,52 @@ public class CompilerChef {
}
}
+ private void writeMapperForV1Compat(
+ DataBindingCompilerArgs compilerArgs,
+ Map<String, Integer> brValueLookup) {
+ BindingMapperWriter dbr = new BindingMapperWriter(
+ BindingMapperWriter.V1_COMPAT_MAPPER_PKG,
+ BindingMapperWriter.V1_COMPAT_MAPPER_NAME,
+ mV1CompatChef.getLayoutBinders(),
+ compilerArgs);
+ mFileWriter.writeToFile(
+ BindingMapperWriter.V1_COMPAT_MAPPER_PKG + "." + dbr.getClassName(),
+ dbr.write(brValueLookup));
+ }
+
+ public List<LayoutBinder> getLayoutBinders() {
+ return mDataBinder.getLayoutBinders();
+ }
+
/**
* Writes the mapper android.databinding.DataBinderMapperImpl which is a merged mapper
* that includes all mappers from dependencies.
*/
private void writeMergedMapper(
+ ProcessingEnvironment processingEnv,
DataBindingCompilerArgs compilerArgs,
List<String> modulePackages) {
+ // figure out which mappers exists as they may not exist for v1 libs.
+ List<String> availableDependencyModules = modulePackages.stream()
+ .filter(modulePackage -> {
+ if (modulePackage.equals(compilerArgs.getModulePackage())) {
+ // mine will be generated
+ return true;
+ }
+ String mapper = BindingMapperWriterV2.createMapperQName(modulePackage);
+ TypeElement impl = processingEnv
+ .getElementUtils()
+ .getTypeElement(mapper);
+ return impl != null;
+ }).collect(Collectors.toList());
Set<String> featurePackageIds = loadFeaturePackageIds(compilerArgs);
StringBuilder sb = new StringBuilder();
MergedBindingMapperWriter mergedBindingMapperWriter =
- new MergedBindingMapperWriter(modulePackages, compilerArgs, featurePackageIds);
+ new MergedBindingMapperWriter(
+ availableDependencyModules,
+ compilerArgs,
+ featurePackageIds,
+ mV1CompatChef != null);
TypeSpec mergedMapperSpec = mergedBindingMapperWriter.write();
try {
JavaFile.builder(mergedBindingMapperWriter.getPkg(), mergedMapperSpec)
diff --git a/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java b/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java
index d1959bef..a87885bc 100644
--- a/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java
+++ b/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java
@@ -69,13 +69,7 @@ public class GenerationalClassUtil {
}
private GenerationalClassUtil(DataBindingCompilerArgs args) {
- if (args.isEnableV2()) {
- mEnabledExtensions = new ExtensionFilter[]{ExtensionFilter.BR,
- ExtensionFilter.SETTER_STORE};
- } else {
- mEnabledExtensions = new ExtensionFilter[]{ExtensionFilter.BR, ExtensionFilter.LAYOUT,
- ExtensionFilter.SETTER_STORE};
- }
+ mEnabledExtensions = ExtensionFilter.values();
if (StringUtils.isNotBlank(args.getAarOutFolder())) {
mIncrementalOutDir = new File(args.getAarOutFolder(),
DataBindingBuilder.INCREMENTAL_BIN_AAR_DIR);