Reruns Generators when their output references modified types.
Change-Id: Ic7d89baf1774c5430c897d768436d8c2b329a3dd
Review-Link: https://gwt-review.googlesource.com/#/c/8881/
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index a5ecf06..edd2ae6 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -166,6 +166,8 @@
new PersistentPrettyNamerState();
private final Set<String> preambleTypeNames = Sets.newHashSet();
private final Multimap<String, String> rebinderTypeNamesByReboundTypeName = HashMultimap.create();
+ private final Multimap<String, String> reboundTypeNamesByGeneratedTypeName =
+ HashMultimap.create();
private final Multimap<String, String> reboundTypeNamesByInputResource = HashMultimap.create();
private final Set<String> rootTypeNames = Sets.newHashSet();
private final Set<String> singleJsoImplInterfaceNames = Sets.newHashSet();
@@ -185,6 +187,11 @@
this.modifiedCompilationUnitNames.addAll(modifiedCompilationUnitNames);
}
+ public void associateReboundTypeWithGeneratedType(String reboundTypeName,
+ String generatedTypeName) {
+ reboundTypeNamesByGeneratedTypeName.put(generatedTypeName, reboundTypeName);
+ }
+
/**
* Record that a Generator that was ran as a result of a GWT.create(ReboundType.class) call read a
* particular resource.
@@ -208,6 +215,7 @@
public void clearReboundTypeAssociations(String reboundTypeName) {
reboundTypeNamesByInputResource.values().remove(reboundTypeName);
+ reboundTypeNamesByGeneratedTypeName.values().remove(reboundTypeName);
}
/**
@@ -243,8 +251,16 @@
ImmutableList<String> modifiedTypeAndSubTypeNames = ImmutableList.copyOf(staleTypeNames);
appendReferencingTypes(staleTypeNames, modifiedTypeAndSubTypeNames);
appendReferencingTypes(staleTypeNames, jsoStatusChangedTypeNames);
- appendTypesThatRebindTypes(staleTypeNames, computeReboundTypesAffectedByModifiedResources());
- // TODO(stalcup): turn modifications of generator input types into type staleness.
+ staleTypeNames.addAll(
+ computeTypesThatRebindTypes(computeReboundTypesAffectedByModifiedResources()));
+ appendTypesToRegenerateStaleGeneratedTypes(staleTypeNames);
+
+ // Generator output is affected by types queried from the TypeOracle but changes in these
+ // types are not being directly supported at this time since some of them are already handled
+ // because they are referenced by the Generator output and since changes in subtype queries
+ // probably make GWTRPC output incompatible with a server anyway (and thus already forces a
+ // restart).
+
staleTypeNames.removeAll(JProgram.SYNTHETIC_TYPE_NAMES);
}
@@ -475,14 +491,32 @@
}
/**
- * Adds to staleTypeNames the set of names of types that contain GWT.create(ReboundType.class)
- * calls that rebind the given set of type names.
+ * If type Foo is a generated type and is stale this pass will append type Bar that triggers
+ * Generator Baz that regenerates type Foo.
+ * <p>
+ * This is necessary since just clearing the cache for type Foo would not be adequate to cause the
+ * recreation of its cached JS without also rerunning the Generator that creates type Foo.
*/
- private void appendTypesThatRebindTypes(Set<String> staleTypeNames,
- Set<String> reboundTypeNames) {
- for (String reboundTypeName : reboundTypeNames) {
- staleTypeNames.addAll(rebinderTypeNamesByReboundTypeName.get(reboundTypeName));
- }
+ private void appendTypesToRegenerateStaleGeneratedTypes(Set<String> staleTypeNames) {
+ Set<String> generatedTypeNames = reboundTypeNamesByGeneratedTypeName.keySet();
+
+ // Filter the current stale types list for any types that are known to be generated.
+ Set<String> staleGeneratedTypeNames = Sets.intersection(staleTypeNames, generatedTypeNames);
+ do {
+ // Accumulate staleGeneratedTypes -> generators -> generatorTriggeringTypes.
+ Set<String> generatorTriggeringTypes = computeTypesThatRebindTypes(
+ computeReboundTypesThatGenerateTypes(staleGeneratedTypeNames));
+ // Mark these generator triggering types stale.
+ staleTypeNames.addAll(generatorTriggeringTypes);
+
+ // It's possible that a generator triggering type was itself also created by a Generator.
+ // Repeat the backwards trace process till none of the newly stale types are generated types.
+ staleGeneratedTypeNames = Sets.intersection(generatorTriggeringTypes, generatedTypeNames);
+
+ // Ensure that no generated type is processed more than once, otherwise poorly written
+ // Generators could trigger an infinite loop.
+ staleGeneratedTypeNames.removeAll(staleTypeNames);
+ } while (!staleGeneratedTypeNames.isEmpty());
}
private void clearCachedTypeOutput(String staleTypeName) {
@@ -502,4 +536,25 @@
}
return affectedRebindTypeNames;
}
+
+ private Set<String> computeReboundTypesThatGenerateTypes(Set<String> staleGeneratedTypeNames) {
+ Set<String> reboundTypesThatGenerateTypes = Sets.newHashSet();
+ for (String staleGeneratedTypeName : staleGeneratedTypeNames) {
+ reboundTypesThatGenerateTypes.addAll(
+ reboundTypeNamesByGeneratedTypeName.get(staleGeneratedTypeName));
+ }
+ return reboundTypesThatGenerateTypes;
+ }
+
+ /**
+ * Returns the set of names of types that contain GWT.create(ReboundType.class) calls that rebind
+ * the given set of type names.
+ */
+ private Set<String> computeTypesThatRebindTypes(Set<String> reboundTypeNames) {
+ Set<String> typesThatRebindTypes = Sets.newHashSet();
+ for (String reboundTypeName : reboundTypeNames) {
+ typesThatRebindTypes.addAll(rebinderTypeNamesByReboundTypeName.get(reboundTypeName));
+ }
+ return typesThatRebindTypes;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
index 56db2c0..bc5dfe9 100644
--- a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
@@ -825,6 +825,10 @@
} else {
typeName = packageName + '.' + simpleTypeName;
}
+
+ compilerContext.getMinimalRebuildCache().associateReboundTypeWithGeneratedType(
+ currentRebindBinaryTypeName, typeName);
+
// Is type already known to the host?
JClassType existingType = getTypeOracle().findType(packageName, simpleTypeName);
if (existingType != null) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 0f0fc4d..2ffebc6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -790,9 +790,10 @@
JDeclaredType staleType =
internalFindType(staleTypeName, binaryNameBasedTypeLocator, false);
if (staleType == null) {
- logger.log(TreeLogger.WARN, "Wanted to recompile stale type " + staleTypeName
- + " but could not find the type instance. It is probably the output of a "
- + "generator and not yet handled by per-file compilation.");
+ // The type is Generator output and so is not usually available in the list of types
+ // provided from initial JDT compilation. The staleness marking process has already
+ // handled this type by cascading the staleness marking onto the types that contain the
+ // GWT.create() calls that process that create this type.
continue;
}
fullFlowIntoType(staleType);
diff --git a/dev/core/test/com/google/gwt/dev/BarReferencesFooGenerator.java b/dev/core/test/com/google/gwt/dev/BarReferencesFooGenerator.java
new file mode 100644
index 0000000..7b756df
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/BarReferencesFooGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * 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.google.gwt.dev;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.Generator.RunsLocal;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.io.PrintWriter;
+
+/**
+ * A simple generator that generates type Bar with a reference to type Foo.
+ * <p>
+ * This backward reference makes it possible to test invalidation of types that trigger runs of
+ * Generators that refer to types that have been modified.
+ */
+@RunsLocal
+public class BarReferencesFooGenerator extends Generator {
+
+ public static int runCount = 0;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+ throws UnableToCompleteException {
+ runCount++;
+
+ PrintWriter pw = context.tryCreate(logger, "com.foo", "Bar");
+ if (pw != null) {
+ pw.println("package com.foo;");
+ pw.println("public class Bar {");
+ pw.println(" private Foo foo = new Foo();");
+ pw.println("}");
+ context.commit(logger, pw);
+ }
+ return "com.foo.Bar";
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index 16acb14..fb349d9 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -134,7 +134,7 @@
"<entry-point class='com.foo.TestEntryPoint'/>",
"</module>");
- private MockResource generatorModuleResource =
+ private MockResource resourceReadingGeneratorModuleResource =
JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
"<module>",
"<source path=''/>",
@@ -144,6 +144,16 @@
"</generate-with>",
"</module>");
+ private MockResource barReferencesFooGeneratorModuleResource =
+ JavaResourceBase.createMockResource("com/foo/SimpleModule.gwt.xml",
+ "<module>",
+ "<source path=''/>",
+ "<entry-point class='com.foo.TestEntryPoint'/>",
+ "<generate-with class='com.google.gwt.dev.BarReferencesFooGenerator'>",
+ " <when-type-is class='java.lang.Object' />",
+ "</generate-with>",
+ "</module>");
+
private MockResource classNameToGenerateResource =
JavaResourceBase.createMockResource("com/foo/generatedClassName.txt",
"FooReplacementOne");
@@ -263,6 +273,11 @@
" public void run() {}",
"}");
+ private MockJavaResource fooResource =
+ JavaResourceBase.createMockJavaResource("com.foo.Foo",
+ "package com.foo;",
+ "public class Foo {}");
+
private MockJavaResource regularFooImplemetorResource =
JavaResourceBase.createMockJavaResource("com.foo.FooImplementor",
"package com.foo;",
@@ -381,8 +396,7 @@
assertDeterministicBuild(options);
}
- // TODO(stalcup): add recompile tests for file deletion, JSO status changes and Generator input
- // resource edits.
+ // TODO(stalcup): add recompile tests for file deletion.
public void testPerFileRecompile_noop() throws UnableToCompleteException, IOException,
InterruptedException {
@@ -442,22 +456,57 @@
checkPerFileRecompile_generatorInputResourceChange(JsOutputOption.DETAILED);
}
+ public void testPerFileRecompile_invalidatedGeneratorOutputRerunsGenerator()
+ throws UnableToCompleteException, IOException, InterruptedException {
+ // BarReferencesFoo Generator hasn't run yet.
+ assertEquals(0, BarReferencesFooGenerator.runCount);
+
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ List<MockResource> sharedResources =
+ Lists.newArrayList(barReferencesFooGeneratorModuleResource, generatorEntryPointResource);
+ JsOutputOption output = JsOutputOption.PRETTY;
+
+ List<MockResource> originalResources = Lists.newArrayList(sharedResources);
+ originalResources.add(fooResource);
+
+ // Compile the app with original files, modify a file and do a per-file recompile.
+ MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
+ File relinkApplicationDir = Files.createTempDir();
+ compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule", originalResources,
+ relinkMinimalRebuildCache, output);
+
+ // BarReferencesFoo Generator has now been run once.
+ assertEquals(1, BarReferencesFooGenerator.runCount);
+
+ // Recompile with no changes, which should not trigger any Generator runs.
+ compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
+ Lists.<MockResource> newArrayList(), relinkMinimalRebuildCache, output);
+
+ // Since there were no changes BarReferencesFoo Generator was not run again.
+ assertEquals(1, BarReferencesFooGenerator.runCount);
+
+ // Recompile with a modified Foo class, which should invalidate Bar which was generated by a
+ // GWT.create() call in the entry point.
+ compileToJs(compilerOptions, relinkApplicationDir, "com.foo.SimpleModule",
+ Lists.<MockResource> newArrayList(fooResource), relinkMinimalRebuildCache, output);
+
+ // BarReferencesFoo Generator was run again.
+ assertEquals(2, BarReferencesFooGenerator.runCount);
+ }
+
public void testPerFileRecompile_carriesOverGeneratorArtifacts() throws UnableToCompleteException,
IOException, InterruptedException {
// Foo Generator hasn't run yet.
assertEquals(0, FooResourceGenerator.runCount);
CompilerOptions compilerOptions = new CompilerOptionsImpl();
- List<MockResource> sharedResources = Lists.newArrayList(generatorModuleResource,
+ List<MockResource> sharedResources = Lists.newArrayList(resourceReadingGeneratorModuleResource,
generatorEntryPointResource, fooInterfaceResource, classNameToGenerateResource);
JsOutputOption output = JsOutputOption.PRETTY;
List<MockResource> originalResources = Lists.newArrayList(sharedResources);
originalResources.add(nonJsoFooResource);
- List<MockResource> modifiedResources = Lists.newArrayList(sharedResources);
- modifiedResources.add(nonJsoFooResource);
-
// Compile the app with original files, modify a file and do a per-file recompile.
MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
File relinkApplicationDir = Files.createTempDir();
@@ -570,7 +619,7 @@
compilerOptions.setUseDetailedTypeIds(true);
checkRecompiledModifiedApp(compilerOptions, "com.foo.SimpleModule", Lists.newArrayList(
- generatorModuleResource, generatorEntryPointResource, fooInterfaceResource,
+ resourceReadingGeneratorModuleResource, generatorEntryPointResource, fooInterfaceResource,
nonJsoFooResource), classNameToGenerateResource, modifiedClassNameToGenerateResource,
outputOption);
}