Adds optional incremental compile to SuperDevMode.

Change-Id: Id85b6ef83864eb4d4d9b8fd2434ed3f4adf5c642
Review-Link: https://gwt-review.googlesource.com/#/c/7142/
diff --git a/dev/BUILD b/dev/BUILD
index 9d16278..841a50b 100644
--- a/dev/BUILD
+++ b/dev/BUILD
@@ -317,6 +317,24 @@
     ],
 )
 
+gwt_test(
+    name = "tests_dev.IncrementalBuilderTest",
+    data = [
+        "//third_party/java_src/gwt:gwt-dev-full.jar",
+        "//third_party/java_src/gwt:gwt-user-full",
+        "//third_party/java_src/gwt/svn/trunk/user:incremental-build-system-test-res.jar",
+    ],
+    jvm_flags = [
+        "-Dgwt.cp=third_party/java_src/gwt/svn/trunk/user/incremental-build-system-test-res.jar\;third_party/java_src/gwt/gwt-dev-full.jar\;third_party/java_src/gwt/gwt-user-full.jar",
+        "-ea",
+    ],
+    main_class = "com.google.gwt.dev.IncrementalBuilderTest",
+    deps = [
+        ":dev-test-code",
+        "//third_party/java_src/gwt/svn/trunk/user:incremental-build-system-test-res",
+    ],
+)
+
 # The "dev" directory is tested by loose tests.  Some of them fail because of
 # classpath expectations that are invalid in Google's distributed build
 # environment.
@@ -329,6 +347,11 @@
     exclude = [
         "core/test/**/ClassPathEntryTest.java",
         "core/test/**/ResourceOracleImplTest.java",
+    ] +
+    # Handled separately because it needs a custom test target with
+    # custom classpath.
+    [
+        "core/test/**/IncrementalBuilderTest.java",
     ],
 )
 
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
index da0a65c..d473f17 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -92,6 +92,9 @@
       System.out.println();
       System.out.println("The code server is ready.");
       System.out.println("Next, visit: " + url);
+    } catch (UnableToCompleteException e) {
+      // Already logged.
+      System.exit(1);
     } catch (Throwable t) {
       t.printStackTrace();
       System.exit(1);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
index c273114..a143bd6 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
@@ -17,6 +17,7 @@
 package com.google.gwt.dev.codeserver;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.js.JsNamespaceOption;
 import com.google.gwt.dev.util.arg.OptionOptimize;
@@ -37,23 +38,31 @@
   private final List<String> moduleNames;
   private final SourceLevel sourceLevel;
   private final boolean failOnError;
-  private final boolean strictResources;
+  private final boolean strictPublicResources;
+  private final boolean strictSourceResources;
   private final TreeLogger.Type logLevel;
 
   CompilerOptionsImpl(CompileDir compileDir, List<String> moduleNames, SourceLevel sourceLevel,
-      boolean failOnError, boolean strictResources, TreeLogger.Type logLevel) {
+      boolean failOnError, boolean strictSourceResources, boolean strictPublicResources,
+      TreeLogger.Type logLevel) {
     this.compileDir = compileDir;
-    this.libraryPaths = ImmutableList.<String>of();
+    this.libraryPaths = ImmutableList.<String> of();
     this.moduleNames = Lists.newArrayList(moduleNames);
     this.sourceLevel = sourceLevel;
     this.failOnError = failOnError;
-    this.strictResources = strictResources;
+    this.strictSourceResources = strictSourceResources;
+    this.strictPublicResources = strictPublicResources;
     this.logLevel = logLevel;
   }
 
   @Override
-  public boolean enforceStrictResources() {
-    return strictResources;
+  public boolean enforceStrictPublicResources() {
+    return strictPublicResources;
+  }
+
+  @Override
+  public boolean enforceStrictSourceResources() {
+    return strictSourceResources;
   }
 
   @Override
@@ -67,6 +76,11 @@
   }
 
   @Override
+  public Properties getFinalProperties() {
+    return null; // handling this in a different way
+  }
+
+  @Override
   public int getFragmentCount() {
     return -1;
   }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
index f4dd082..77aa526 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
@@ -42,6 +42,7 @@
  * <p>These flags are EXPERIMENTAL and subject to change.</p>
  */
 public class Options {
+  private boolean compileIncremental = false;
   private boolean noPrecompile = false;
   private boolean isCompileTest = false;
   private File workDir;
@@ -117,6 +118,13 @@
   }
 
   /**
+   * Whether to compile a series of reusable libraries that are linked at the end.
+   */
+  boolean shouldCompileIncremental() {
+    return compileIncremental;
+  }
+
+  /**
    * Whether the codeServer should start without precompiling modules.
    */
   boolean getNoPrecompile() {
@@ -202,6 +210,7 @@
       registerHandler(new ModuleNameArgument());
       registerHandler(new FailOnErrorFlag());
       registerHandler(new StrictResourcesFlag());
+      registerHandler(new CompileIncrementalFlag());
       registerHandler(new ArgHandlerSourceLevel(new OptionSourceLevel() {
         @Override
         public SourceLevel getSourceLevel() {
@@ -256,6 +265,35 @@
     }
   }
 
+  private class CompileIncrementalFlag extends ArgHandlerFlag {
+
+    @Override
+    public String getLabel() {
+      return "incremental";
+    }
+
+    @Override
+    public String getPurposeSnippet() {
+      return "Compile and link the application as a set of separate libraries.";
+    }
+
+    @Override
+    public boolean setFlag(boolean value) {
+      compileIncremental = value;
+      return true;
+    }
+
+    @Override
+    public boolean getDefaultValue() {
+      return false;
+    }
+
+    @Override
+    public boolean isExperimental() {
+      return true;
+    }
+  }
+
   private class CompileTestFlag extends ArgHandlerFlag {
 
     @Override
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
index b86810c..00b79c1 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -23,6 +23,8 @@
 import com.google.gwt.dev.Compiler;
 import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.CompilerOptions;
+import com.google.gwt.dev.IncrementalBuilder;
+import com.google.gwt.dev.IncrementalBuilder.BuildResultStatus;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
@@ -48,8 +50,10 @@
  * Recompiles a GWT module on demand.
  */
 class Recompiler {
+
   private final AppSpace appSpace;
   private final String originalModuleName;
+  private IncrementalBuilder incrementalBuilder;
   private final TreeLogger logger;
   private String serverPrefix;
   private int compilesDone = 0;
@@ -58,6 +62,8 @@
   private AtomicReference<String> moduleName = new AtomicReference<String>(null);
 
   private final AtomicReference<CompileDir> lastBuild = new AtomicReference<CompileDir>();
+  private CompileDir publishedCompileDir;
+  private boolean listenerFailed;
   private final AtomicReference<ResourceLoader> resourceLoader =
       new AtomicReference<ResourceLoader>();
   private final CompilerContext.Builder compilerContextBuilder = new CompilerContext.Builder();
@@ -90,7 +96,7 @@
     CompileDir compileDir = makeCompileDir(compileId);
     TreeLogger compileLogger = makeCompileLogger(compileDir);
 
-    boolean listenerFailed = false;
+    listenerFailed = false;
     try {
       options.getRecompileListener().startedCompile(originalModuleName, compileId, compileDir);
     } catch (Exception e) {
@@ -100,25 +106,14 @@
 
     boolean success = false;
     try {
-      CompilerOptions compilerOptions = new CompilerOptionsImpl(
-          compileDir, options.getModuleNames(), options.getSourceLevel(),
-          options.isFailOnError(), options.enforceStrictResources(), options.getLogLevel());
-      compilerContext = compilerContextBuilder.options(compilerOptions).build();
-      ModuleDef module = loadModule(compileLogger, bindingProperties);
-
-      // Propagates module rename.
-      String newModuleName = module.getName();
-      moduleName.set(newModuleName);
-      compilerOptions = new CompilerOptionsImpl(
-          compileDir, Lists.newArrayList(newModuleName), options.getSourceLevel(),
-          options.isFailOnError(), options.enforceStrictResources(), options.getLogLevel());
-      compilerContext = compilerContextBuilder.options(compilerOptions).build();
-
-      success = new Compiler(compilerOptions).run(compileLogger, module);
-      lastBuild.set(compileDir); // makes compile log available over HTTP
+      if (options.shouldCompileIncremental()) {
+        success = compileIncremental(compileLogger, compileDir);
+      } else {
+        success = compileMonolithic(compileLogger, bindingProperties, compileDir);
+      }
     } finally {
       try {
-        options.getRecompileListener().finishedCompile(originalModuleName, compileId, success);
+        options.getRecompileListener().finishedCompile(originalModuleName, compilesDone, success);
       } catch (Exception e) {
         compileLogger.log(TreeLogger.Type.WARN, "listener threw exception", e);
         listenerFailed = true;
@@ -131,13 +126,14 @@
     }
 
     long elapsedTime = System.currentTimeMillis() - startTime;
-    compileLogger.log(TreeLogger.Type.INFO, "Compile completed in " + elapsedTime + " ms");
+    compileLogger.log(TreeLogger.Type.INFO,
+        String.format("%.3fs total -- Compile completed", elapsedTime / 1000d));
 
     if (options.isCompileTest() && listenerFailed) {
       throw new UnableToCompleteException();
     }
 
-    return compileDir;
+    return publishedCompileDir;
   }
 
   synchronized CompileDir noCompile() throws UnableToCompleteException {
@@ -175,6 +171,65 @@
     return compileDir;
   }
 
+  private boolean compileIncremental(TreeLogger compileLogger, CompileDir compileDir) {
+    BuildResultStatus buildResultStatus;
+    // Perform a compile.
+    if (incrementalBuilder == null) {
+      // If it's the first compile.
+      ResourceLoader resources = ResourceLoaders.forClassLoader(Thread.currentThread());
+      resources = ResourceLoaders.forPathAndFallback(options.getSourcePath(), resources);
+      this.resourceLoader.set(resources);
+
+      incrementalBuilder = new IncrementalBuilder(originalModuleName,
+          compileDir.getWarDir().getPath(), compileDir.getWorkDir().getPath(),
+          compileDir.getGenDir().getPath(), resourceLoader.get());
+      buildResultStatus = incrementalBuilder.build(compileLogger);
+    } else {
+      // If it's a rebuild.
+      incrementalBuilder.setWarDir(compileDir.getWarDir().getPath());
+      buildResultStatus = incrementalBuilder.rebuild(compileLogger);
+    }
+
+    if (incrementalBuilder.isRootModuleKnown()) {
+      moduleName.set(incrementalBuilder.getRootModuleName());
+    }
+    // Unlike a monolithic compile, the incremental builder can successfully build but have no new
+    // output (for example when no files have changed). So it's important to only publish the new
+    // compileDir if it actually contains output.
+    if (buildResultStatus.isSuccess() && buildResultStatus.outputChanged()) {
+      publishedCompileDir = compileDir;
+    }
+    lastBuild.set(compileDir); // makes compile log available over HTTP
+
+    return buildResultStatus.isSuccess();
+  }
+
+  private boolean compileMonolithic(TreeLogger compileLogger, Map<String, String> bindingProperties,
+      CompileDir compileDir) throws UnableToCompleteException {
+    CompilerOptions compilerOptions = new CompilerOptionsImpl(compileDir,
+        options.getModuleNames(), options.getSourceLevel(), options.isFailOnError(),
+        options.enforceStrictResources(), options.enforceStrictResources(),
+        options.getLogLevel());
+    compilerContext = compilerContextBuilder.options(compilerOptions).build();
+    ModuleDef module = loadModule(compileLogger, bindingProperties);
+
+    // Propagates module rename.
+    String newModuleName = module.getName();
+    moduleName.set(newModuleName);
+    compilerOptions = new CompilerOptionsImpl(compileDir, Lists.newArrayList(newModuleName),
+        options.getSourceLevel(), options.isFailOnError(), options.enforceStrictResources(),
+        options.enforceStrictResources(), options.getLogLevel());
+    compilerContext = compilerContextBuilder.options(compilerOptions).build();
+
+    boolean success = new Compiler(compilerOptions).run(compileLogger, module);
+    if (success) {
+      publishedCompileDir = compileDir;
+    }
+    lastBuild.set(compileDir); // makes compile log available over HTTP
+
+    return success;
+  }
+
   /**
    * Returns the log from the last compile. (It may be a failed build.)
    */
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
index ce2afb2..fb15e08 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
@@ -18,6 +18,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.CompilerOptions;
+import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.js.JsNamespaceOption;
 import com.google.gwt.dev.util.arg.SourceLevel;
@@ -94,7 +95,12 @@
   }
 
   @Override
-  public final void setEnforceStrictResources(boolean strictResources) {
+  public void setEnforceStrictPublicResources(boolean strictPublicResources) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void setEnforceStrictSourceResources(boolean strictSourceResources) {
     throw new UnsupportedOperationException();
   }
 
@@ -104,6 +110,11 @@
   }
 
   @Override
+  public final void setFinalProperties(Properties finalProperties) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   public void setFragmentCount(int numFragments) {
     throw new UnsupportedOperationException();
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/impl/ResourceGeneratorUtilImpl.java b/dev/core/src/com/google/gwt/core/ext/impl/ResourceGeneratorUtilImpl.java
index a675b56..6d60b9d 100644
--- a/dev/core/src/com/google/gwt/core/ext/impl/ResourceGeneratorUtilImpl.java
+++ b/dev/core/src/com/google/gwt/core/ext/impl/ResourceGeneratorUtilImpl.java
@@ -50,6 +50,10 @@
     generatedFilesByName.put(name, file);
   }
 
+  public static void clearGeneratedFilesByName() {
+    generatedFilesByName.clear();
+  }
+
   /**
    * Returns the previously recorded generated file with the given name.
    */
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
index 03478bf..823bb60 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
@@ -16,6 +16,7 @@
 package com.google.gwt.core.ext.linker.impl;
 
 import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
 import java.io.Serializable;
@@ -60,7 +61,7 @@
     return ary;
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   final int[] ends;
   final int[] starts;
 
diff --git a/dev/core/src/com/google/gwt/dev/BuildTarget.java b/dev/core/src/com/google/gwt/dev/BuildTarget.java
new file mode 100644
index 0000000..a2f1d31
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/BuildTarget.java
@@ -0,0 +1,220 @@
+/*
+ * 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.TreeLogger;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.RuntimeRebindRuleGenerator;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Represents a module in a module tree, knows how to build that module into an output library and
+ * checks output library freshness.
+ */
+class BuildTarget {
+
+  public enum OutputFreshness {
+    FRESH, STALE, UNKNOWN;
+  }
+
+  public static final Function<BuildTarget, String> LIBRARY_PATH_FUNCTION =
+      new Function<BuildTarget, String>() {
+        @Override
+        public String apply(@Nullable BuildTarget buildTarget) {
+          return buildTarget.computeLibraryPath();
+        }
+      };
+
+  @VisibleForTesting
+  public static String formatCompilingModuleMessage(String canonicalModuleName) {
+    return "Compiling module " + canonicalModuleName;
+  }
+
+  @VisibleForTesting
+  public static String formatReusingCachedLibraryMessage(String canonicalModuleName) {
+    return "Reusing cached library for " + canonicalModuleName;
+  }
+
+  private final BuildTargetOptions buildTargetOptions;
+  private final String canonicalModuleName;
+  private final List<BuildTarget> dependencyBuildTargets;
+  private ModuleDef module;
+  private OutputFreshness outputFreshness = OutputFreshness.UNKNOWN;
+  private Set<BuildTarget> transitiveDependencyBuildTargets;
+
+  BuildTarget(String canonicalModuleName, BuildTargetOptions buildTargetOptions,
+      BuildTarget... dependencyBuildTargets) {
+    this.canonicalModuleName = canonicalModuleName;
+    this.buildTargetOptions = buildTargetOptions;
+    this.dependencyBuildTargets = Arrays.asList(dependencyBuildTargets);
+  }
+
+  public boolean build(TreeLogger logger) {
+    return build(logger, false);
+  }
+
+  public boolean build(TreeLogger logger, boolean link) {
+    if (outputFreshness == OutputFreshness.FRESH) {
+      logger.log(TreeLogger.SPAM, formatReusingCachedLibraryMessage(canonicalModuleName));
+      return true;
+    }
+
+    // Build all my dependencies before myself.
+    for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
+      // If any dependency fails to build.
+      if (!dependencyBuildTarget.build(logger)) {
+        // Then I have failed to build as well.
+        return false;
+      }
+    }
+
+    TreeLogger branch =
+        logger.branch(TreeLogger.INFO, formatCompilingModuleMessage(canonicalModuleName));
+    boolean buildSucceeded;
+    try {
+      RuntimeRebindRuleGenerator.RUNTIME_REBIND_RULE_SOURCES_BY_SHORT_NAME.clear();
+      LibraryCompiler libraryCompiler = new LibraryCompiler(computeCompileOptions(link));
+      libraryCompiler.setResourceLoader(buildTargetOptions.getResourceLoader());
+      buildSucceeded = libraryCompiler.run(branch);
+      module = libraryCompiler.getModule();
+    } catch (Throwable t) {
+      logger.log(TreeLogger.ERROR, t.getMessage());
+      return false;
+    }
+    outputFreshness = OutputFreshness.FRESH;
+    return buildSucceeded;
+  }
+
+  public CompilerOptions computeCompileOptions(boolean link) {
+    CompilerOptions compilerOptions = new CompilerOptionsImpl();
+    // Must compile the canonical name, not name, since after module-renames there may be more
+    // than one module in the classpath with the same name and we don't want to find and recompile
+    // the same one over and over.
+    compilerOptions.setModuleNames(Lists.newArrayList(canonicalModuleName));
+    compilerOptions.setLink(link);
+    compilerOptions.setLogLevel(TreeLogger.ERROR);
+    compilerOptions.setGenDir(new File(buildTargetOptions.getGenDir()));
+    compilerOptions.setWorkDir(new File(buildTargetOptions.getOutputDir()));
+    compilerOptions.setLibraryPaths(Lists.newArrayList(
+        Iterables.transform(getTransitiveDependencyBuildTargets(), LIBRARY_PATH_FUNCTION)));
+    compilerOptions.setFinalProperties(buildTargetOptions.getFinalProperties());
+
+    if (!link) {
+      compilerOptions.setOutputLibraryPath(computeLibraryPath());
+    } else {
+      compilerOptions.setWarDir(new File(buildTargetOptions.getWarDir()));
+    }
+    return compilerOptions;
+  }
+
+  public String computeLibraryPath() {
+    return buildTargetOptions.getOutputDir() + "/" + canonicalModuleName + ".gwtlib";
+  }
+
+  public void computeOutputFreshness(TreeLogger logger) {
+    if (outputFreshness != OutputFreshness.UNKNOWN) {
+      return;
+    }
+
+    for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
+      dependencyBuildTarget.computeOutputFreshness(logger);
+    }
+
+    if (module == null) {
+      logger.log(TreeLogger.SPAM,
+          "Library " + canonicalModuleName + " is stale: the module hasn't been loaded yet");
+      outputFreshness = OutputFreshness.STALE;
+      return;
+    }
+
+    for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
+      if (dependencyBuildTarget.outputFreshness == OutputFreshness.STALE) {
+        logger.log(TreeLogger.SPAM,
+            "Library " + canonicalModuleName + " is stale: has a stale dependency");
+        outputFreshness = OutputFreshness.STALE;
+        return;
+      }
+    }
+
+    File libraryFile = new File(computeLibraryPath());
+    if (!libraryFile.exists()) {
+      logger.log(TreeLogger.SPAM,
+          "Library " + canonicalModuleName + " is stale: the library file is missing");
+      outputFreshness = OutputFreshness.STALE;
+      return;
+    }
+    long libraryFileLastModified = libraryFile.lastModified();
+    module.refresh();
+    if (libraryFileLastModified < module.getResourceLastModified()) {
+      Set<Resource> newerResources = module.getResourcesNewerThan(libraryFileLastModified);
+      TreeLogger branch = logger.branch(TreeLogger.SPAM,
+          "Library " + canonicalModuleName + " is stale: library is older than some resource(s)");
+      for (Resource newerResource : newerResources) {
+        branch.log(TreeLogger.SPAM, newerResource.getPath() + " has changed");
+      }
+      outputFreshness = OutputFreshness.STALE;
+      return;
+    }
+
+    logger.log(TreeLogger.SPAM, "Library " + canonicalModuleName + " is fresh");
+    outputFreshness = OutputFreshness.FRESH;
+  }
+
+  public String getCanonicalModuleName() {
+    return canonicalModuleName;
+  }
+
+  public List<BuildTarget> getDependencyBuildTargets() {
+    return dependencyBuildTargets;
+  }
+
+  public Set<BuildTarget> getTransitiveDependencyBuildTargets() {
+    if (transitiveDependencyBuildTargets == null) {
+      transitiveDependencyBuildTargets = Sets.newHashSet();
+      transitiveDependencyBuildTargets.addAll(dependencyBuildTargets);
+      for (BuildTarget buildTarget : dependencyBuildTargets) {
+        transitiveDependencyBuildTargets.addAll(buildTarget.getTransitiveDependencyBuildTargets());
+      }
+    }
+    return transitiveDependencyBuildTargets;
+  }
+
+  public boolean isOutputFresh() {
+    return outputFreshness == OutputFreshness.FRESH;
+  }
+
+  public boolean link(TreeLogger logger) {
+    return build(logger, true);
+  }
+
+  public void setModule(ModuleDef module) {
+    this.module = module;
+  }
+
+  public void setOutputFreshness(OutputFreshness outputFreshness) {
+    this.outputFreshness = outputFreshness;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/BuildTargetOptions.java b/dev/core/src/com/google/gwt/dev/BuildTargetOptions.java
new file mode 100644
index 0000000..210557a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/BuildTargetOptions.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dev.cfg.Properties;
+import com.google.gwt.dev.cfg.ResourceLoader;
+
+/**
+ * An interface for classes that can answer build target option questions.
+ */
+public interface BuildTargetOptions {
+
+  /**
+   * Returns the property object whose values should be considered "final" when deciding which
+   * generators to run during library compilation.
+   */
+  Properties getFinalProperties();
+
+  /**
+   * Returns the directory path into which to dump a copy of any generated source.
+   */
+  String getGenDir();
+
+  /**
+   * Returns the directory path the compiler is free to use to write temporary files which are not
+   * part of final link output.
+   */
+  String getOutputDir();
+
+  /**
+   * Returns the resource loader to use when finding and loading compilable source, build resources
+   * such as PNG and CSS files and public resources such as HTML files.
+   */
+  ResourceLoader getResourceLoader();
+
+  /**
+   * Returns the directory path into which the compiler should write linked output.
+   */
+  String getWarDir();
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index 5de9faa..8a0a96a 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -203,11 +203,15 @@
       Precompilation precompilation, Permutation[] perms, int localWorkers,
       List<PersistenceBackedObject<PermutationResult>> resultFiles)
       throws UnableToCompleteException {
-    final TreeLogger branch = logger.branch(TreeLogger.INFO,
-        "Compiling " + perms.length + " permutation" + (perms.length > 1 ? "s" : ""));
+    boolean shouldCompileMonolithic = compilerContext.shouldCompileMonolithic();
+    final TreeLogger branch = shouldCompileMonolithic ? (logger.branch(
+        TreeLogger.INFO,
+        "Compiling " + perms.length + " permutation" + (perms.length > 1 ? "s" : ""))) : logger;
     PermutationWorkerFactory.compilePermutations(
         branch, compilerContext, precompilation, perms, localWorkers, resultFiles);
-    logger.log(TreeLogger.INFO, "Compile of permutations succeeded");
+    if (shouldCompileMonolithic) {
+      logger.log(TreeLogger.INFO, "Compile of permutations succeeded");
+    }
   }
 
   public static void main(String[] args) {
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java
index 1313332..58b03e2 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev;
 
+import com.google.gwt.dev.util.arg.OptionFinalProperties;
 import com.google.gwt.dev.util.arg.OptionLibraryPaths;
 import com.google.gwt.dev.util.arg.OptionLink;
 import com.google.gwt.dev.util.arg.OptionLogLevel;
@@ -26,5 +27,5 @@
  * A common set of options for all compile tasks.
  */
 public interface CompileTaskOptions extends OptionModuleName, OptionLogLevel, OptionWorkDir,
-    OptionOutputLibraryPath, OptionLibraryPaths, OptionLink {
+    OptionOutputLibraryPath, OptionLibraryPaths, OptionFinalProperties, OptionLink {
 }
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
index 8a5cc41..3ac04cd 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev;
 
 import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.cfg.Properties;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -26,6 +27,7 @@
  */
 class CompileTaskOptionsImpl implements CompileTaskOptions {
 
+  private Properties finalProperties;
   private List<String> libraryPaths = new ArrayList<String>();
   private boolean link;
   private Type logLevel;
@@ -51,6 +53,7 @@
     setWorkDir(other.getWorkDir());
     setLibraryPaths(other.getLibraryPaths());
     setOutputLibraryPath(other.getOutputLibraryPath());
+    setFinalProperties(other.getFinalProperties());
     setLink(other.shouldLink());
   }
 
@@ -59,6 +62,11 @@
   }
 
   @Override
+  public Properties getFinalProperties() {
+    return finalProperties;
+  }
+
+  @Override
   public List<String> getLibraryPaths() {
     return libraryPaths;
   }
@@ -84,6 +92,11 @@
   }
 
   @Override
+  public void setFinalProperties(Properties finalProperties) {
+    this.finalProperties = finalProperties;
+  }
+
+  @Override
   public void setLibraryPaths(List<String> libraryPaths) {
     this.libraryPaths.clear();
     this.libraryPaths.addAll(libraryPaths);
diff --git a/dev/core/src/com/google/gwt/dev/CompilerContext.java b/dev/core/src/com/google/gwt/dev/CompilerContext.java
index 48b7bd9..98f4046 100644
--- a/dev/core/src/com/google/gwt/dev/CompilerContext.java
+++ b/dev/core/src/com/google/gwt/dev/CompilerContext.java
@@ -24,7 +24,8 @@
 import com.google.gwt.dev.javac.MemoryUnitCache;
 import com.google.gwt.dev.javac.UnitCache;
 import com.google.gwt.dev.resource.ResourceOracle;
-import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+import com.google.gwt.dev.util.TinyCompileSummary;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.util.Set;
 
@@ -153,8 +154,8 @@
   private boolean compileMonolithic = true;
   private LibraryGroup libraryGroup = new ImmutableLibraryGroup();
   private LibraryWriter libraryWriter = new NullLibraryWriter();
-
   private ModuleDef module;
+  private TinyCompileSummary tinyCompileSummary = new TinyCompileSummary();
 
   // TODO(stalcup): split this into module parsing, precompilation, compilation, and linking option
   // sets.
@@ -163,50 +164,6 @@
   private ResourceOracle sourceResourceOracle;
   private UnitCache unitCache = new MemoryUnitCache();
 
-  /**
-   * Walks the parts of the library dependency graph that have not run the given generator
-   * referenced by name and accumulates and returns a map from binding property name to newly legal
-   * values that were declared in those libraries.<br />
-   *
-   * The resulting map represents the set of binding property changes that have not yet been taken
-   * into account in the output of a particular generator and which may need to trigger the
-   * re-execution of said generator.
-   */
-  public Multimap<String, String> gatherNewBindingPropertyValuesForGenerator(String generatorName) {
-    Multimap<String, String> newBindingPropertyValues =
-        getLibraryGroup().gatherNewBindingPropertyValuesForGenerator(generatorName);
-    newBindingPropertyValues.putAll(libraryWriter.getNewBindingPropertyValuesByName());
-    return newBindingPropertyValues;
-  }
-
-  /**
-   * Walks the parts of the library dependency graph that have not run the given generator
-   * referenced by name and accumulates and returns a map from configuration property name to newly
-   * set values that were declared in those libraries.<br />
-   *
-   * The resulting map represents the set of configuration property value changes that have not yet
-   * been taken into account in the output of a particular generator and which may need to trigger
-   * the re-execution of said generator.
-   */
-  public Multimap<String, String> gatherNewConfigurationPropertyValuesForGenerator(
-      String generatorName) {
-    Multimap<String, String> newConfigurationPropertyValues =
-        getLibraryGroup().gatherNewConfigurationPropertyValuesForGenerator(generatorName);
-    newConfigurationPropertyValues.putAll(libraryWriter.getNewConfigurationPropertyValuesByName());
-    return newConfigurationPropertyValues;
-  }
-
-  public Set<String> gatherNewReboundTypeNamesForGenerator(String generatorName) {
-    Set<String> newReboundTypeNames =
-        getLibraryGroup().gatherNewReboundTypeSourceNamesForGenerator(generatorName);
-    newReboundTypeNames.addAll(libraryWriter.getReboundTypeSourceNames());
-    return newReboundTypeNames;
-  }
-
-  public Set<String> gatherOldReboundTypeNamesForGenerator(String generatorName) {
-    return getLibraryGroup().gatherOldReboundTypeSourceNamesForGenerator(generatorName);
-  }
-
   public ResourceOracle getBuildResourceOracle() {
     return buildResourceOracle;
   }
@@ -227,10 +184,34 @@
     return options;
   }
 
+  /**
+   * Returns the set of source names of rebound types that have been processed by the given
+   * Generator.
+   */
+  public Set<String> getProcessedReboundTypeSourceNames(String generatorName) {
+    Set<String> processedReboundTypeSourceNames = Sets.newHashSet();
+    processedReboundTypeSourceNames.addAll(
+        getLibraryWriter().getProcessedReboundTypeSourceNames(generatorName));
+    processedReboundTypeSourceNames.addAll(
+        getLibraryGroup().getProcessedReboundTypeSourceNames(generatorName));
+    return processedReboundTypeSourceNames;
+  }
+
   public ResourceOracle getPublicResourceOracle() {
     return publicResourceOracle;
   }
 
+  /**
+   * Returns the set of source names of types for which GWT.create() rebind has been requested. The
+   * types may or may not yet have been processed by some Generators.
+   */
+  public Set<String> getReboundTypeSourceNames() {
+    Set<String> reboundTypeSourceNames = Sets.newHashSet();
+    reboundTypeSourceNames.addAll(getLibraryWriter().getReboundTypeSourceNames());
+    reboundTypeSourceNames.addAll(getLibraryGroup().getReboundTypeSourceNames());
+    return reboundTypeSourceNames;
+  }
+
   public ResourceOracle getSourceResourceOracle() {
     return sourceResourceOracle;
   }
@@ -242,4 +223,8 @@
   public boolean shouldCompileMonolithic() {
     return compileMonolithic;
   }
+
+  public TinyCompileSummary getTinyCompileSummary() {
+    return tinyCompileSummary;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/IncrementalBuilder.java b/dev/core/src/com/google/gwt/dev/IncrementalBuilder.java
new file mode 100644
index 0000000..bd0151f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/IncrementalBuilder.java
@@ -0,0 +1,319 @@
+/*
+ * 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.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.impl.ResourceGeneratorUtilImpl;
+import com.google.gwt.dev.BuildTarget.OutputFreshness;
+import com.google.gwt.dev.cfg.Library;
+import com.google.gwt.dev.cfg.LibraryGroup;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.cfg.Properties;
+import com.google.gwt.dev.cfg.ResourceLoader;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
+import com.google.gwt.dev.resource.impl.ZipFileClassPathEntry;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Incrementally builds, links, and rebuilds module trees.
+ */
+public class IncrementalBuilder {
+
+  /**
+   * Represents a combination of whether a build succeeded and whether output changed.
+   */
+  public static enum BuildResultStatus {
+    FAILED(false, false), SUCCESS_NO_CHANGES(false, true), SUCCESS_WITH_CHANGES(true, true);
+
+    private static BuildResultStatus get(boolean success) {
+      return success ? SUCCESS_WITH_CHANGES : FAILED;
+    }
+
+    private boolean outputChanged;
+    private boolean success;
+
+    private BuildResultStatus(boolean outputChanged, boolean success) {
+      this.outputChanged = outputChanged;
+      this.success = success;
+    }
+
+    public boolean isSuccess() {
+      return success;
+    }
+
+    public boolean outputChanged() {
+      return outputChanged;
+    }
+  }
+
+  @VisibleForTesting
+  static final String NO_FILES_HAVE_CHANGED = "No files have changed; all output is still fresh.";
+
+  @VisibleForTesting
+  protected static String formatCircularModulePathMessage(List<String> circularModulePath) {
+    return "Can't compile because of a module circular reference:\n  "
+        + Joiner.on("\n  ").join(circularModulePath);
+  }
+
+  private Map<String, BuildTarget> buildTargetsByCanonicalModuleName = Maps.newLinkedHashMap();
+  private List<List<String>> circularReferenceModuleNameLoops = Lists.newArrayList();
+  private Properties finalProperties;
+  private String genDir;
+  private Set<String> knownCircularlyReferentModuleNames = Sets.newHashSet();
+  private Set<String> moduleReferencePath = Sets.newLinkedHashSet();
+  private String outputDir;
+  private final ResourceLoader resourceLoader;
+  private BuildTarget rootBuildTarget;
+  private ModuleDef rootModule;
+  private final String rootModuleName;
+  private String warDir;
+  private BuildTargetOptions buildTargetOptions = new BuildTargetOptions() {
+
+    @Override
+    public Properties getFinalProperties() {
+      return IncrementalBuilder.this.finalProperties;
+    }
+
+    @Override
+    public String getGenDir() {
+      return IncrementalBuilder.this.genDir;
+    }
+
+    @Override
+    public String getOutputDir() {
+      return IncrementalBuilder.this.outputDir;
+    }
+
+    @Override
+    public ResourceLoader getResourceLoader() {
+      return IncrementalBuilder.this.resourceLoader;
+    }
+
+    @Override
+    public String getWarDir() {
+      return IncrementalBuilder.this.warDir;
+    }
+  };
+
+  public IncrementalBuilder(String rootModuleName, String warDir, String libDir, String genDir,
+      ResourceLoader resourceLoader) {
+    this.rootModuleName = rootModuleName;
+    this.warDir = warDir;
+    this.outputDir = libDir;
+    this.genDir = genDir;
+    this.resourceLoader = resourceLoader;
+  }
+
+  public BuildResultStatus build(TreeLogger logger) {
+    try {
+      logger = logger.branch(TreeLogger.INFO, "Performing an incremental build");
+
+      CompilerContext compilerContext = new CompilerContext.Builder().compileMonolithic(false)
+          .libraryGroup(LibraryGroup.fromLibraries(Lists.<Library> newArrayList(), false)).build();
+      long beforeLoadRootModuleMs = System.currentTimeMillis();
+      rootModule = ModuleDefLoader.loadFromResources(logger, compilerContext, rootModuleName,
+          resourceLoader, false);
+      finalProperties = rootModule.getProperties();
+      long loadRootModuleDurationMs = System.currentTimeMillis() - beforeLoadRootModuleMs;
+      logger.log(TreeLogger.INFO, String.format(
+          "%.3fs -- Parsing and loading root module definition in %s",
+          loadRootModuleDurationMs / 1000d, rootModuleName));
+
+      long beforeCreateTargetGraphMs = System.currentTimeMillis();
+      rootBuildTarget = createBuildTarget(logger, rootModuleName);
+      rootBuildTarget.setModule(rootModule);
+      long createdTargetGraphDurationMs = System.currentTimeMillis() - beforeCreateTargetGraphMs;
+      logger.log(TreeLogger.INFO, String.format("%.3fs -- Creating target graph (%s targets)",
+          createdTargetGraphDurationMs / 1000d, buildTargetsByCanonicalModuleName.size()));
+
+      if (!circularReferenceModuleNameLoops.isEmpty()) {
+        for (List<String> circularReferenceModuleNameLoop : circularReferenceModuleNameLoops) {
+          logger.log(TreeLogger.ERROR,
+              formatCircularModulePathMessage(circularReferenceModuleNameLoop));
+        }
+        throw new UnableToCompleteException();
+      }
+      logLoadedBuildTargetGraph(logger, buildTargetsByCanonicalModuleName);
+
+      long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
+      ModuleDefLoader.clearModuleCache();
+      rootBuildTarget.computeOutputFreshness(logger);
+      long computeOutputFreshnessDurationMs =
+          System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
+      logger.log(TreeLogger.INFO, String.format("%.3fs -- Computing per-target output freshness",
+          computeOutputFreshnessDurationMs / 1000d));
+
+      TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
+      boolean success = rootBuildTarget.link(branch);
+      return BuildResultStatus.get(success);
+    } catch (UnableToCompleteException e) {
+      // The real cause has been logged.
+      return BuildResultStatus.FAILED;
+    }
+  }
+
+  public String getRootModuleName() {
+    if (rootModule == null) {
+      return "UNKNOWN";
+    }
+    return rootModule.getName();
+  }
+
+  public boolean isRootModuleKnown() {
+    return rootModule != null;
+  }
+
+  public BuildResultStatus rebuild(TreeLogger logger) {
+    logger = logger.branch(TreeLogger.INFO, "Performing an incremental rebuild");
+
+    ResourceOracleImpl.clearCache();
+    ZipFileClassPathEntry.clearCache();
+    ModuleDefLoader.clearModuleCache();
+    ResourceGeneratorUtilImpl.clearGeneratedFilesByName();
+
+    long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
+    forgetAllOutputFreshness();
+    rootBuildTarget.computeOutputFreshness(logger);
+    long computeOutputFreshnessDurationMs =
+        System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
+    logger.log(TreeLogger.INFO, String.format("%.3fs -- Computing per-target output freshness",
+        computeOutputFreshnessDurationMs / 1000d));
+
+    if (rootBuildTarget.isOutputFresh()) {
+      logger.log(TreeLogger.INFO, NO_FILES_HAVE_CHANGED);
+      return BuildResultStatus.SUCCESS_NO_CHANGES;
+    }
+
+    TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
+    boolean success = rootBuildTarget.link(branch);
+    return BuildResultStatus.get(success);
+  }
+
+  public void setWarDir(String warDir) {
+    this.warDir = warDir;
+  }
+
+  @VisibleForTesting
+  void clean() {
+    File[] files = new File(outputDir).listFiles();
+    if (files == null) {
+      // nothing to delete
+      return;
+    }
+    for (File file : files) {
+      file.delete();
+    }
+  }
+
+  private BuildTarget createBuildTarget(String canonicalModuleName, BuildTarget... buildTargets) {
+    if (!buildTargetsByCanonicalModuleName.containsKey(canonicalModuleName)) {
+      buildTargetsByCanonicalModuleName.put(canonicalModuleName,
+          new BuildTarget(canonicalModuleName, buildTargetOptions, buildTargets));
+    }
+    return buildTargetsByCanonicalModuleName.get(canonicalModuleName);
+  }
+
+  private BuildTarget createBuildTarget(TreeLogger logger, String moduleName)
+      throws UnableToCompleteException {
+    if (isCircularlyReferent(moduleName)) {
+      // Allow the target graph creation to continue so that all of the circular reference loops can
+      // be gathered.
+      return null;
+    }
+    if (buildTargetsByCanonicalModuleName.containsKey(moduleName)) {
+      return buildTargetsByCanonicalModuleName.get(moduleName);
+    }
+
+    logger.log(TreeLogger.SPAM, String.format("Adding target %s to build graph.", moduleName));
+    moduleReferencePath.add(moduleName);
+
+    List<BuildTarget> dependencyBuildTargets = Lists.newArrayList();
+    for (String dependencyModuleName : rootModule.getDirectDependencies(moduleName)) {
+      dependencyBuildTargets.add(createBuildTarget(logger, dependencyModuleName));
+    }
+    moduleReferencePath.remove(moduleName);
+
+    return createBuildTarget(moduleName, dependencyBuildTargets.toArray(new BuildTarget[0]));
+  }
+
+  private void forgetAllOutputFreshness() {
+    for (BuildTarget buildTarget : buildTargetsByCanonicalModuleName.values()) {
+      buildTarget.setOutputFreshness(OutputFreshness.UNKNOWN);
+    }
+  }
+
+  private boolean isCircularlyReferent(String potentialDuplicateModuleName) {
+    if (knownCircularlyReferentModuleNames.contains(potentialDuplicateModuleName)) {
+      return true;
+    }
+    if (!moduleReferencePath.contains(potentialDuplicateModuleName)) {
+      return false;
+    }
+
+    List<String> circularModuleReferencePath = Lists.newArrayList(moduleReferencePath);
+
+    // Attach the duplicate module name to the end of the loop.
+    circularModuleReferencePath.add(potentialDuplicateModuleName);
+
+    List<String> annotatedCircularModuleReferencePath = Lists.newArrayList();
+    // The current module path only includes libraries but the connections between libraries might
+    // be silently flowing through filesets. Add filesets to the path so that the output is more
+    // readable.
+    for (int moduleNameIndex = 0; moduleNameIndex < circularModuleReferencePath.size() - 1;
+        moduleNameIndex++) {
+      String thisModuleName = circularModuleReferencePath.get(moduleNameIndex);
+      String nextModuleName = circularModuleReferencePath.get(moduleNameIndex + 1);
+
+      annotatedCircularModuleReferencePath.add(
+          thisModuleName + (thisModuleName.equals(potentialDuplicateModuleName) ? " <loop>" : ""));
+
+      List<String> fileSetPath = rootModule.getFileSetPathBetween(thisModuleName, nextModuleName);
+      if (fileSetPath != null) {
+        for (String fileSetModuleName : fileSetPath) {
+          annotatedCircularModuleReferencePath.add(fileSetModuleName + " <fileset>");
+        }
+      }
+    }
+
+    // Attach the duplicate module name to the end of the loop.
+    annotatedCircularModuleReferencePath.add(potentialDuplicateModuleName + " <loop>");
+
+    knownCircularlyReferentModuleNames.addAll(annotatedCircularModuleReferencePath);
+    circularReferenceModuleNameLoops.add(annotatedCircularModuleReferencePath);
+    return true;
+  }
+
+  private void logLoadedBuildTargetGraph(TreeLogger logger,
+      Map<String, BuildTarget> buildTargetsByCanonicalModuleName) {
+    logger.log(TreeLogger.SPAM, "Loaded build target graph:");
+    for (String canonicalModuleName : buildTargetsByCanonicalModuleName.keySet()) {
+      logger.log(TreeLogger.SPAM, "\t" + canonicalModuleName);
+      BuildTarget gwtTarget = buildTargetsByCanonicalModuleName.get(canonicalModuleName);
+      for (BuildTarget dependencyBuildTarget : gwtTarget.getDependencyBuildTargets()) {
+        logger.log(TreeLogger.SPAM, "\t\t" + dependencyBuildTarget.getCanonicalModuleName());
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/LibraryCompiler.java b/dev/core/src/com/google/gwt/dev/LibraryCompiler.java
index 2505d03..406248c 100644
--- a/dev/core/src/com/google/gwt/dev/LibraryCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/LibraryCompiler.java
@@ -25,13 +25,17 @@
 import com.google.gwt.dev.cfg.LibraryWriter;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.cfg.ResourceLoader;
+import com.google.gwt.dev.cfg.ResourceLoaders;
 import com.google.gwt.dev.cfg.ZipLibrary;
 import com.google.gwt.dev.cfg.ZipLibraryWriter;
-import com.google.gwt.dev.javac.LibraryGroupUnitCache;
+import com.google.gwt.dev.javac.UnitCacheSingleton;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.PermutationResult;
+import com.google.gwt.dev.url.CloseableJarHandlerFactory;
 import com.google.gwt.dev.util.Memory;
 import com.google.gwt.dev.util.PersistenceBackedObject;
+import com.google.gwt.dev.util.TinyCompileSummary;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerDeployDir;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
@@ -60,16 +64,13 @@
 
 /**
  * Executable compiler entry point that constructs a library from an input module and its library
- * dependencies.<br />
- *
+ * dependencies.
+ * <p>
  * When compiling the top-level module, you should also pass the -link and -war flags to create the
- * GWT application.<br />
- *
+ * GWT application.
+ * <p>
  * This is an alternative to Compiler.java which does a full world compile that neither reads nor
- * writes libraries.<br />
- *
- * EXPERIMENTAL: does not yet work as it depends on changes which have not yet been reviewed and
- * committed.
+ * writes libraries.
  */
 public class LibraryCompiler {
 
@@ -123,17 +124,48 @@
   private LibraryGroup libraryGroup;
   private ModuleDef module;
   private Permutation[] permutations;
+  private ResourceLoader resourceLoader = ResourceLoaders.forClassLoader(Thread.currentThread());
+  private Library resultLibrary;
 
   public LibraryCompiler(CompilerOptions compilerOptions) {
     this.compilerOptions = new CompilerOptionsImpl(compilerOptions);
     this.compilerContext =
         compilerContextBuilder.options(this.compilerOptions).compileMonolithic(false).build();
+    CloseableJarHandlerFactory.installOverride();
+  }
+
+  ModuleDef getModule() {
+    return module;
+  }
+
+  boolean run(TreeLogger logger) {
+    try {
+      normalizeOptions(logger);
+      loadLibraries(logger);
+      loadModule(logger);
+      compileModule(logger);
+      if (compilerOptions.shouldLink()) {
+        linkLibraries(logger);
+      }
+      return true;
+    } catch (UnableToCompleteException e) {
+      // The real cause has been logged.
+      return false;
+    } finally {
+      // Close all zip files, otherwise the JVM may crash on linux.
+      compilerContext.getLibraryGroup().close();
+      if (resultLibrary != null) {
+        resultLibrary.close();
+      }
+    }
+  }
+
+  void setResourceLoader(ResourceLoader resourceLoader) {
+    this.resourceLoader = resourceLoader;
   }
 
   private void compileModule(TreeLogger logger) throws UnableToCompleteException {
     long beforeCompileMs = System.currentTimeMillis();
-    TreeLogger branch =
-        logger.branch(TreeLogger.INFO, "Compiling module " + module.getCanonicalName());
     LibraryWriter libraryWriter = compilerContext.getLibraryWriter();
 
     try {
@@ -145,7 +177,11 @@
         }
       }
 
-      Precompilation precompilation = Precompile.precompile(branch, compilerContext);
+      Precompilation precompilation = Precompile.precompile(logger, compilerContext);
+      if (precompilation == null) {
+        // The real cause has been logged.
+        throw new UnableToCompleteException();
+      }
       // TODO(stalcup): move to precompile() after params are refactored
       if (!compilerOptions.shouldSaveSource()) {
         precompilation.removeSourceArtifacts(logger);
@@ -157,9 +193,8 @@
       List<PersistenceBackedObject<PermutationResult>> permutationResultFiles =
           new ArrayList<PersistenceBackedObject<PermutationResult>>();
       permutationResultFiles.add(libraryWriter.getPermutationResultHandle());
-      CompilePerms.compile(
-          branch, compilerContext, precompilation, permutations, compilerOptions.getLocalWorkers(),
-          permutationResultFiles);
+      CompilePerms.compile(logger, compilerContext, precompilation, permutations,
+          compilerOptions.getLocalWorkers(), permutationResultFiles);
       compilePermutationsEvent.end();
 
       generatedArtifacts = precompilation.getGeneratedArtifacts();
@@ -170,8 +205,23 @@
     }
 
     long durationMs = System.currentTimeMillis() - beforeCompileMs;
-    branch.log(TreeLogger.INFO,
-        "Library compilation succeeded -- " + String.format("%.3f", durationMs / 1000d) + "s");
+    TreeLogger detailBranch = logger.branch(TreeLogger.INFO,
+        String.format("%.3fs -- Translating Java to Javascript", durationMs / 1000d));
+
+    TinyCompileSummary tinyCompileSummary = compilerContext.getTinyCompileSummary();
+    boolean shouldWarn =
+        tinyCompileSummary.getTypesForGeneratorsCount() + tinyCompileSummary.getTypesForAstCount()
+        > 1500;
+    String recommendation = shouldWarn ? " This module should probably be split into smaller "
+        + "modules or should trigger fewer generators since its current size hurts "
+        + "incremental compiles." : "";
+    detailBranch.log(shouldWarn ? TreeLogger.WARN : TreeLogger.INFO, String.format(
+        "There were %s static source files, %s generated source files, %s types loaded for "
+        + "generators and %s types loaded for AST construction. %s",
+        tinyCompileSummary.getStaticSourceFilesCount(),
+        tinyCompileSummary.getGeneratedSourceFilesCount(),
+        tinyCompileSummary.getTypesForGeneratorsCount(), tinyCompileSummary.getTypesForAstCount(),
+        recommendation));
   }
 
   private void linkLibraries(TreeLogger logger) throws UnableToCompleteException {
@@ -180,7 +230,6 @@
 
     // Load up the library that was just created so that it's possible to get a read-reference to
     // its contained PermutationResult.
-    Library resultLibrary;
     try {
       resultLibrary = new ZipLibrary(compilerOptions.getOutputLibraryPath());
     } catch (IncompatibleLibraryVersionException e) {
@@ -202,26 +251,20 @@
     File absPath = new File(compilerOptions.getWarDir(), module.getName());
     absPath = absPath.getAbsoluteFile();
 
-    String logMessage = "Linking into " + absPath;
-    if (compilerOptions.getExtraDir() != null) {
-      File absExtrasPath = new File(compilerOptions.getExtraDir(), module.getName());
-      absExtrasPath = absExtrasPath.getAbsoluteFile();
-      logMessage += "; Writing extras to " + absExtrasPath;
-    }
-    TreeLogger branch = logger.branch(TreeLogger.TRACE, logMessage);
     try {
-      Link.link(branch,
-          module, compilerContext.getPublicResourceOracle(), generatedArtifacts, permutations,
-          resultFiles, libraryPermutationResults, compilerOptions, compilerOptions);
+      Link.link(TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(),
+          generatedArtifacts, permutations, resultFiles, libraryPermutationResults, compilerOptions,
+          compilerOptions);
+      long durationMs = System.currentTimeMillis() - beforeLinkMs;
+      logger.log(TreeLogger.INFO,
+          String.format("%.3fs -- Successfully linking application", durationMs / 1000d));
     } catch (IOException e) {
-      // The real cause has been logged.
+      long durationMs = System.currentTimeMillis() - beforeLinkMs;
+      logger.log(TreeLogger.INFO,
+          String.format("%.3fs -- Failing to link application", durationMs / 1000d));
       throw new UnableToCompleteException();
     }
     linkEvent.end();
-
-    long durationMs = System.currentTimeMillis() - beforeLinkMs;
-    branch.log(TreeLogger.INFO,
-        "Library link succeeded -- " + String.format("%.3f", durationMs / 1000d) + "s");
   }
 
   private void loadLibraries(TreeLogger logger) throws UnableToCompleteException {
@@ -231,15 +274,31 @@
       logger.log(TreeLogger.ERROR, e.getMessage());
       throw new UnableToCompleteException();
     }
-    compilerContext = compilerContextBuilder.libraryGroup(libraryGroup)
-        .libraryWriter(new ZipLibraryWriter(compilerOptions.getOutputLibraryPath()))
-        .unitCache(new LibraryGroupUnitCache(libraryGroup)).build();
+    libraryGroup.verify(logger);
+
+    try {
+      CloseableJarHandlerFactory.closeStreams(compilerOptions.getOutputLibraryPath());
+    } catch (IOException e) {
+      logger.log(TreeLogger.WARN, String.format("Failed to close old connections to %s. "
+          + "Repeated incremental compiles in the same JVM process may fail.",
+          compilerOptions.getOutputLibraryPath()));
+    }
+
+    ZipLibraryWriter zipLibraryWriter =
+        new ZipLibraryWriter(compilerOptions.getOutputLibraryPath());
+    compilerContext = compilerContextBuilder.libraryGroup(libraryGroup).libraryWriter(
+        zipLibraryWriter).unitCache(UnitCacheSingleton.get(logger, compilerOptions.getWorkDir()))
+        .build();
   }
 
   private void loadModule(TreeLogger logger) throws UnableToCompleteException {
-    module = ModuleDefLoader.loadFromClassPath(
-        logger, compilerContext, compilerOptions.getModuleNames().get(0), false);
+    long beforeLoadModuleMs = System.currentTimeMillis();
+    module = ModuleDefLoader.loadFromResources(logger, compilerContext,
+        compilerOptions.getModuleNames().get(0), resourceLoader, false);
     compilerContext = compilerContextBuilder.module(module).build();
+    long durationMs = System.currentTimeMillis() - beforeLoadModuleMs;
+    logger.log(TreeLogger.INFO,
+        String.format("%.3fs -- Parsing and loading module definition", durationMs / 1000d));
   }
 
   private void normalizeOptions(TreeLogger logger) throws UnableToCompleteException {
@@ -250,7 +309,7 @@
     // Current optimization passes are not safe with only partial data.
     compilerOptions.setOptimizationLevel(OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
     // Protects against rampant overlapping source inclusion.
-    compilerOptions.setEnforceStrictResources(true);
+    compilerOptions.setEnforceStrictSourceResources(true);
     // Ensures that output JS identifiers are named consistently in all modules.
     compilerOptions.setOutput(JsOutputOption.DETAILED);
     // Code splitting isn't possible when you can't trace the entire control flow.
@@ -281,20 +340,4 @@
     // Optimize early since permutation compiles will run in process.
     compilerOptions.setOptimizePrecompile(true);
   }
-
-  private boolean run(TreeLogger logger) {
-    try {
-      normalizeOptions(logger);
-      loadLibraries(logger);
-      loadModule(logger);
-      compileModule(logger);
-      if (compilerOptions.shouldLink()) {
-        linkLibraries(logger);
-      }
-      return true;
-    } catch (UnableToCompleteException e) {
-      // The real cause has been logged.
-      return false;
-    }
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
index 26d4acb..3dc7ec5 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
@@ -70,8 +70,13 @@
   }
 
   @Override
-  public boolean enforceStrictResources() {
-    return jjsOptions.enforceStrictResources();
+  public boolean enforceStrictSourceResources() {
+    return jjsOptions.enforceStrictSourceResources();
+  }
+
+  @Override
+  public boolean enforceStrictPublicResources() {
+    return jjsOptions.enforceStrictPublicResources();
   }
 
   @Override
@@ -257,8 +262,13 @@
   }
 
   @Override
-  public void setEnforceStrictResources(boolean strictResources) {
-    jjsOptions.setEnforceStrictResources(strictResources);
+  public void setEnforceStrictSourceResources(boolean strictSourceResources) {
+    jjsOptions.setEnforceStrictSourceResources(strictSourceResources);
+  }
+  @Override
+
+  public void setEnforceStrictPublicResources(boolean strictPublicResources) {
+    jjsOptions.setEnforceStrictPublicResources(strictPublicResources);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
index 564b5e9..04f1df5 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dev.util.collect.IdentityHashSet;
 import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.dev.util.collect.Sets;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 
 import java.util.ArrayList;
@@ -119,6 +120,24 @@
     values.addFirst(value);
   }
 
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof BindingProperty) {
+      BindingProperty that = (BindingProperty) object;
+      return Objects.equal(this.name, that.name)
+          && Objects.equal(this.collapsedValues, that.collapsedValues)
+          && Objects.equal(this.conditionalValues, that.conditionalValues)
+          && Objects.equal(this.definedValues, that.definedValues)
+          && Objects.equal(this.fallback, that.fallback)
+          && Objects.equal(this.getFallbackValuesMap(), that.getFallbackValuesMap())
+          && Objects.equal(this.fallbackValues, that.fallbackValues)
+          && Objects.equal(this.provider, that.provider)
+          && Objects.equal(this.providerGenerator, that.providerGenerator)
+          && Objects.equal(this.rootCondition, that.rootCondition);
+    }
+    return false;
+  }
+
   /**
    * Returns the set of allowed values in sorted order when a certain condition
    * is satisfied.
@@ -245,6 +264,12 @@
     return rootCondition;
   }
 
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(name, collapsedValues, conditionalValues, definedValues, fallback,
+        getFallbackValuesMap(), fallbackValues, provider, providerGenerator, rootCondition);
+  }
+
   public boolean hasTargetLibraryDefinedValues() {
     return !targetLibraryDefinedValues.isEmpty();
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java b/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
index a923abd..8b5b6cd 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.util.collect.Sets;
 import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.base.Strings;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
@@ -30,7 +31,7 @@
  */
 public abstract class CompoundCondition extends Condition {
 
-  private final Conditions conditions = new Conditions();
+  protected final Conditions conditions = new Conditions();
 
   public CompoundCondition(Condition... conditions) {
     for (Condition condition : conditions) {
@@ -38,6 +39,15 @@
     }
   }
 
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof CompoundCondition) {
+      CompoundCondition that = (CompoundCondition) object;
+      return Objects.equal(this.conditions, that.conditions);
+    }
+    return false;
+  }
+
   public Conditions getConditions() {
     return conditions;
   }
@@ -52,6 +62,11 @@
   }
 
   @Override
+  public int hashCode() {
+    return Objects.hashCode(conditions);
+  }
+
+  @Override
   public String toSource() {
     List<String> conditionSources = Lists.newArrayList();
 
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
index c81a136..f036129 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 import java.util.Iterator;
 
@@ -32,6 +33,20 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionAll) {
+      ConditionAll that = (ConditionAll) object;
+      return Objects.equal(this.conditions, that.conditions);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(conditions);
+  }
+
+  @Override
   protected boolean doEval(TreeLogger logger, DeferredBindingQuery query)
       throws UnableToCompleteException {
     for (Iterator<Condition> iter = getConditions().iterator(); iter.hasNext();) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
index 8aa7ede..dd6f9c9 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 import java.util.Iterator;
 
@@ -31,6 +32,20 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionAny) {
+      ConditionAny that = (ConditionAny) object;
+      return Objects.equal(this.conditions, that.conditions);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(conditions);
+  }
+
+  @Override
   protected boolean doEval(TreeLogger logger, DeferredBindingQuery query)
       throws UnableToCompleteException {
     for (Iterator<Condition> iter = getConditions().iterator(); iter.hasNext();) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
index 02bc7ef..2ccb5c5 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 import java.util.Iterator;
 
@@ -32,6 +33,20 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionNone) {
+      ConditionNone that = (ConditionNone) object;
+      return Objects.equal(this.conditions, that.conditions);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(conditions);
+  }
+
+  @Override
   public String toSource() {
     return "!(" + super.toSource() + ")";
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenLinkerAdded.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenLinkerAdded.java
index 82ed112..4d8fc5f 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenLinkerAdded.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenLinkerAdded.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.cfg;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 /**
  * A condition that is true when the active linkers include the one specified.
@@ -29,9 +30,24 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionWhenLinkerAdded) {
+      ConditionWhenLinkerAdded that = (ConditionWhenLinkerAdded) object;
+      return Objects.equal(this.linkerName, that.linkerName);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(linkerName);
+  }
+
+  @Override
   public String toSource() {
-    throw new UnsupportedOperationException(
-        "Can't perform runtime rebinding with linker presence based conditions");
+    // TODO(stalcup): implement real runtime linker presence detection or else delete this already
+    // deprecated class...
+    return "false";
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
index 4b370d6..0298a3f 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.util.collect.Sets;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 import java.util.List;
 import java.util.Set;
@@ -42,11 +43,26 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionWhenPropertyIs) {
+      ConditionWhenPropertyIs that = (ConditionWhenPropertyIs) object;
+      return Objects.equal(this.propName, that.propName)
+          && Objects.equal(this.value, that.value);
+    }
+    return false;
+  }
+
+  @Override
   public Set<String> getRequiredProperties() {
     return Sets.create(propName);
   }
 
   @Override
+  public int hashCode() {
+    return Objects.hashCode(propName, value);
+  }
+
+  @Override
   public String toSource() {
     return String.format(
         "@com.google.gwt.lang.RuntimePropertyRegistry::getPropertyValue(*)(\"%s\") == \"%s\"",
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
index c30f54a..559aa68 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.javac.CompilationProblemReporter;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 /**
  * A deferred binding condition to determine whether the type being rebound is
@@ -37,11 +38,25 @@
     this.assignableToTypeName = assignableToTypeName;
   }
 
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionWhenTypeAssignableTo) {
+      ConditionWhenTypeAssignableTo that = (ConditionWhenTypeAssignableTo) object;
+      return Objects.equal(this.assignableToTypeName, that.assignableToTypeName);
+    }
+    return false;
+  }
+
   public String getAssignableToTypeName() {
     return assignableToTypeName;
   }
 
   @Override
+  public int hashCode() {
+    return Objects.hashCode(assignableToTypeName);
+  }
+
+  @Override
   public String toSource() {
     // Should only be used in non-monolithic (library) compiles. Dynamic cast checks are only safe
     // when exhaustive cast maps are available as is the case with library compiles.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
index 038b5ea..ec6edac 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.cfg;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 /**
  * A deferred binding condition to determine whether the type being rebound is
@@ -32,6 +33,20 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConditionWhenTypeIs) {
+      ConditionWhenTypeIs that = (ConditionWhenTypeIs) object;
+      return Objects.equal(this.exactTypeSourceName, that.exactTypeSourceName);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(exactTypeSourceName);
+  }
+
+  @Override
   public String toSource() {
     return String.format("requestTypeClass == @%s::class", exactTypeSourceName);
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Conditions.java b/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
index 097b631..8f7f991 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.cfg;
 
 import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 
 import java.io.Serializable;
 import java.util.Iterator;
@@ -36,6 +37,20 @@
   }
 
   @Override
+  public boolean equals(Object object) {
+    if (object instanceof Conditions) {
+      Conditions that = (Conditions) object;
+      return Objects.equal(this.list, that.list);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(list);
+  }
+
+  @Override
   public Iterator<Condition> iterator() {
     return list.iterator();
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java b/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java
index 29afedc..e01257a 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 
 import java.util.ArrayList;
@@ -42,6 +43,11 @@
     }
   }
 
+  public void addTargetLibraryValue(String value) {
+    addValue(value);
+    targetLibraryValues.add(value);
+  }
+
   public void addValue(String value) {
     if (!allowMultipleValues) {
       throw new IllegalStateException(
@@ -50,11 +56,6 @@
     values.add(value);
   }
 
-  public void addTargetLibraryValue(String value) {
-    addValue(value);
-    targetLibraryValues.add(value);
-  }
-
   public boolean allowsMultipleValues() {
     return allowMultipleValues;
   }
@@ -63,6 +64,17 @@
     values.clear();
   }
 
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof ConfigurationProperty) {
+      ConfigurationProperty that = (ConfigurationProperty) object;
+      return Objects.equal(this.name, that.name)
+          && Objects.equal(this.allowMultipleValues, that.allowMultipleValues)
+          && Objects.equal(this.values, that.values);
+    }
+    return false;
+  }
+
   public List<String> getTargetLibraryValues() {
     return ImmutableList.copyOf(targetLibraryValues);
   }
@@ -78,6 +90,11 @@
     return Collections.unmodifiableList(values);
   }
 
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(name, allowMultipleValues, values);
+  }
+
   public boolean hasTargetLibraryValues() {
     return !targetLibraryValues.isEmpty();
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Libraries.java b/dev/core/src/com/google/gwt/dev/cfg/Libraries.java
index 9c91b2a..64a8e96 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Libraries.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Libraries.java
@@ -60,14 +60,11 @@
       "nestedBinaryNamesByEnclosingName.txt";
   public static final String NESTED_SOURCE_NAMES_BY_ENCLOSING_NAME_ENTRY_NAME =
       "nestedSourceNamesByEnclosingName.txt";
-  public static final String NEW_BINDING_PROPERTY_VALUES_BY_NAME_ENTRY_NAME =
-      "newBindingPropertyValuesByName.txt";
-  public static final String NEW_CONFIGURATION_PROPERTY_VALUES_BY_NAME_ENTRY_NAME =
-      "newConfigurationPropertyValuesByName.txt";
   public static final String PERMUTATION_RESULT_ENTRY_NAME = "permutationResult.ser";
   public static final String PUBLIC_RESOURCE_PATHS_ENTRY_NAME = "publicResourcePaths.txt";
-  public static final String RAN_GENERATOR_NAMES_ENTRY_NAME = "ranGeneratorNames.txt";
   public static final String REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME = "reboundTypeSourceNames.txt";
+  public static final String PROCESSED_REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME =
+      "processedReboundTypeSourceNames.txt";
   public static final String REGULAR_CLASS_FILE_PATHS_ENTRY_NAME = "regularClassFilePaths.txt";
   public static final String REGULAR_COMPILATION_UNIT_TYPE_SOURCE_NAMES_ENTRY_NAME =
       "regularCompilationUnitTypeSourceNames.txt";
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Library.java b/dev/core/src/com/google/gwt/dev/cfg/Library.java
index 2eb0f76..9fdb01b 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Library.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Library.java
@@ -37,6 +37,11 @@
 public interface Library {
 
   /**
+   * Closes all read streams.
+   */
+  void close();
+
+  /**
    * Returns a resource handle or null for the provided path.
    */
   Resource getBuildResourceByPath(String buildResourcePath);
@@ -93,18 +98,6 @@
   Multimap<String, String> getNestedSourceNamesByCompilationUnitName();
 
   /**
-   * Returns a mapping from binding property name to a list of values which were made legal for that
-   * binding property by this library. Facilitates partial generator execution.
-   */
-  Multimap<String, String> getNewBindingPropertyValuesByName();
-
-  /**
-   * Returns a mapping from configuration property name to a value or list of values which were set
-   * for that configuration property by this library. Facilitates partial generator execution.
-   */
-  Multimap<String, String> getNewConfigurationPropertyValuesByName();
-
-  /**
    * Returns a handle to the serialized permutation result of this library. Final linking relies on
    * the permutation result contents.
    */
@@ -113,6 +106,12 @@
   PersistenceBackedObject<PermutationResult> getPermutationResultHandle();
 
   /**
+   * Returns a mapping from generator name to the set of source names of types that have been
+   * processed by that generator in this library.
+   */
+  Multimap<String, String> getProcessedReboundTypeSourceNamesByGenerator();
+
+  /**
    * Returns a resource handle or null for the provided path.
    */
   Resource getPublicResourceByPath(String path);
@@ -124,13 +123,6 @@
   Set<String> getPublicResourcePaths();
 
   /**
-   * Returns the set of names of generators which were executed for this library and thus whose
-   * output is current for this library and all of its libraries. Facilitates partial generator
-   * execution.
-   */
-  Set<String> getRanGeneratorNames();
-
-  /**
    * Returns the set of source names of types which are the subject of GWT.create() calls in source
    * code for this library. This list of types is needed for generator execution and reconstructing
    * this list from source would be very costly.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/LibraryGroup.java b/dev/core/src/com/google/gwt/dev/cfg/LibraryGroup.java
index bdd47d3..418e44c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/LibraryGroup.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/LibraryGroup.java
@@ -13,6 +13,8 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.dev.cfg.Libraries.IncompatibleLibraryVersionException;
 import com.google.gwt.dev.javac.CompilationUnit;
@@ -20,20 +22,20 @@
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.PersistenceBackedObject;
-import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
-import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+import com.google.gwt.thirdparty.guava.common.collect.SetMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -85,6 +87,21 @@
     }
   }
 
+  private static final Comparator<Library> LIBRARY_NAME_COMPARATOR = new Comparator<Library>() {
+    @Override
+    public int compare(Library thisLibrary, Library thatLibrary) {
+      return thisLibrary.getLibraryName().compareTo(thatLibrary.getLibraryName());
+    }
+  };
+
+  @VisibleForTesting
+  public static String formatDuplicateCompilationUnitMessage(String compilationUnitTypeSourceName,
+      String oldLibraryName, String newLibraryName) {
+    return String.format("Compilation units must be unique but '%s' is being "
+        + "provided by both the '%s' and '%s' library.", compilationUnitTypeSourceName,
+        oldLibraryName, newLibraryName);
+  }
+
   /**
    * Factory function that constructs and returns a library group from a list of libraries.
    */
@@ -97,7 +114,6 @@
     libraryGroup.buildLibraryIndexes(verifyLibraryReferences);
     return libraryGroup;
   }
-
   /**
    * Factory function that constructs and returns a library group from a list of zip library paths.
    */
@@ -120,9 +136,10 @@
   private Map<String, Library> librariesByName;
   private Map<String, Library> librariesByPublicResourcePath;
   private List<PersistenceBackedObject<PermutationResult>> permutationResultHandles;
+  private SetMultimap<String, String> processedReboundTypeSourceNamesByGenerator =
+      HashMultimap.create();
   private Set<String> reboundTypeSourceNames;
   private List<Library> rootLibraries;
-
   private Set<String> superSourceCompilationUnitTypeSourceNames;
 
   protected LibraryGroup() {
@@ -130,6 +147,15 @@
   }
 
   /**
+   * Closes all read streams.
+   */
+  public void close() {
+    for (Library library : libraries) {
+      library.close();
+    }
+  }
+
+  /**
    * Returns whether a build resource is is available at the given path.
    */
   public boolean containsBuildResource(String buildResourcePath) {
@@ -146,61 +172,6 @@
   }
 
   /**
-   * Walks the parts of the library dependency graph that have not run the given generator
-   * referenced by name and accumulates and returns a map from binding property name to newly legal
-   * values that were declared in those libraries.
-   */
-  public Multimap<String, String> gatherNewBindingPropertyValuesForGenerator(String generatorName) {
-    Multimap<String, String> newBindingPropertyValuesByName = LinkedHashMultimap.create();
-    for (Library libraryPendingGeneratorRun : gatherLibrariesForGenerator(generatorName, false)) {
-      newBindingPropertyValuesByName.putAll(
-          libraryPendingGeneratorRun.getNewBindingPropertyValuesByName());
-    }
-    return newBindingPropertyValuesByName;
-  }
-
-  /**
-   * Walks the parts of the library dependency graph that have not run the given generator
-   * referenced by name and accumulates and returns a map from configuration property name to newly
-   * set values that were declared in those libraries.
-   */
-  public Multimap<String, String> gatherNewConfigurationPropertyValuesForGenerator(
-      String generatorName) {
-    Multimap<String, String> newConfigurationPropertyValuesByName = LinkedHashMultimap.create();
-    for (Library libraryPendingGeneratorRun : gatherLibrariesForGenerator(generatorName, false)) {
-      newConfigurationPropertyValuesByName.putAll(
-          libraryPendingGeneratorRun.getNewConfigurationPropertyValuesByName());
-    }
-    return newConfigurationPropertyValuesByName;
-  }
-
-  /**
-   * Walks the parts of the library dependency graph that have not run the given generator
-   * referenced by name and accumulates and returns a set of newly rebound type names.
-   */
-  public Set<String> gatherNewReboundTypeSourceNamesForGenerator(String generatorName) {
-    Set<String> newReboundTypeSourceNames = Sets.newHashSet();
-    List<Library> unprocessedLibraries = gatherLibrariesForGenerator(generatorName, false);
-    for (Library unprocessedLibrary : unprocessedLibraries) {
-      newReboundTypeSourceNames.addAll(unprocessedLibrary.getReboundTypeSourceNames());
-    }
-    return newReboundTypeSourceNames;
-  }
-
-  /**
-   * Walks the parts of the library dependency graph that have already run the given generator
-   * referenced by name and accumulates and returns the set of old rebound type names.
-   */
-  public Set<String> gatherOldReboundTypeSourceNamesForGenerator(String generatorName) {
-    Set<String> oldReboundTypeSourceNames = Sets.newHashSet();
-    List<Library> processedLibraries = gatherLibrariesForGenerator(generatorName, true);
-    for (Library processedLibrary : processedLibraries) {
-      oldReboundTypeSourceNames.addAll(processedLibrary.getReboundTypeSourceNames());
-    }
-    return oldReboundTypeSourceNames;
-  }
-
-  /**
    * Returns the resource referenced by name if present or null;
    */
   public Resource getBuildResourceByPath(String buildResourcePath) {
@@ -295,7 +266,23 @@
   }
 
   /**
-   * Returns the resource referenced by name if present or null;
+   * Returns a list of source names of types which have already been rebound by the given generator.
+   */
+  public Set<String> getProcessedReboundTypeSourceNames(String generatorName) {
+    if (!processedReboundTypeSourceNamesByGenerator.containsKey(generatorName)) {
+      Set<String> processedReboundTypeSourceNames = Sets.newHashSet();
+      for (Library library : libraries) {
+        processedReboundTypeSourceNames.addAll(
+            library.getProcessedReboundTypeSourceNamesByGenerator().get(generatorName));
+      }
+      processedReboundTypeSourceNamesByGenerator.putAll(generatorName,
+          Collections.unmodifiableSet(processedReboundTypeSourceNames));
+    }
+    return processedReboundTypeSourceNamesByGenerator.get(generatorName);
+  }
+
+  /**
+   * Returns the resource referenced by name if present or null.
    */
   public Resource getPublicResourceByPath(String path) {
     if (!getLibrariesByPublicResourcePath().containsKey(path)) {
@@ -322,7 +309,8 @@
       for (Library library : libraries) {
         reboundTypeSourceNames.addAll(library.getReboundTypeSourceNames());
       }
-      reboundTypeSourceNames = Collections.unmodifiableSet(reboundTypeSourceNames);
+      reboundTypeSourceNames =
+          Collections.unmodifiableSet(reboundTypeSourceNames);
     }
     return reboundTypeSourceNames;
   }
@@ -344,27 +332,33 @@
     return superSourceCompilationUnitTypeSourceNames;
   }
 
-  // VisibleForTesting
+  public void verify(TreeLogger logger) throws UnableToCompleteException {
+    try {
+      // Forces the building of the mapping from source names of compilation unit types to the
+      // library that provides them, so that there is an opportunity to notice and reject duplicate
+      // providing libraries.
+      getLibrariesByCompilationUnitTypeSourceName();
+    } catch (CollidingCompilationUnitException e) {
+      logger.log(TreeLogger.ERROR, e.getMessage());
+      throw new UnableToCompleteException();
+    }
+  }
+
+  @VisibleForTesting
   List<Library> getLibraries() {
     return libraries;
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   List<Library> getLibraries(Collection<String> libraryNames) {
     return Lists.newArrayList(
         Maps.filterKeys(librariesByName, Predicates.in(libraryNames)).values());
   }
 
-  private Iterable<Library> asLibraries(Set<String> libraryNames) {
-    return Iterables.transform(libraryNames, new Function<String, Library>() {
-        @Override
-      public Library apply(String libraryName) {
-        return librariesByName.get(libraryName);
-      }
-    });
-  }
-
   private void buildLibraryIndexes(boolean verifyLibraryReferences) {
+    // Start processing with a consistent library order to ensure consistently ordered output.
+    Collections.sort(libraries, LIBRARY_NAME_COMPARATOR);
+
     librariesByName = Maps.newLinkedHashMap();
     for (Library library : libraries) {
       if (librariesByName.containsKey(library.getLibraryName())) {
@@ -375,8 +369,8 @@
       librariesByName.put(library.getLibraryName(), library);
     }
 
-    Multimap<Library, Library> parentLibrariesByChildLibrary = HashMultimap.create();
-    Multimap<Library, Library> childLibrariesByParentLibrary = HashMultimap.create();
+    Multimap<Library, Library> parentLibrariesByChildLibrary = LinkedHashMultimap.create();
+    Multimap<Library, Library> childLibrariesByParentLibrary = LinkedHashMultimap.create();
     for (Library parentLibrary : libraries) {
       for (String childLibraryName : parentLibrary.getDependencyLibraryNames()) {
         Library childLibrary = librariesByName.get(childLibraryName);
@@ -424,38 +418,16 @@
   }
 
   /**
-   * Walks the library dependency graph and collects a list of libraries that either have or have
-   * not run the given generator depending on the given gatherNotProcessed boolean.
+   * Throws an exception if the referenced compilation unit (which is being provided by the
+   * referenced new library) is already being provided by some older library.
    */
-  private List<Library> gatherLibrariesForGenerator(
-      String generatorName, boolean gatherLibrariesThatHaveAlreadyRunThisGenerator) {
-    Set<Library> exploredLibraries = Sets.newHashSet();
-    LinkedList<Library> unexploredLibraries = Lists.newLinkedList();
-    List<Library> librariesForGenerator = Lists.newArrayList();
-
-    unexploredLibraries.addAll(rootLibraries);
-    while (!unexploredLibraries.isEmpty()) {
-      Library library = unexploredLibraries.removeFirst();
-      exploredLibraries.add(library);
-
-      boolean libraryHasAlreadyRunThisGenerator =
-          library.getRanGeneratorNames().contains(generatorName);
-      if (!gatherLibrariesThatHaveAlreadyRunThisGenerator && libraryHasAlreadyRunThisGenerator) {
-        // don't gather this one
-        continue;
-      }
-
-      // gather this library
-      librariesForGenerator.add(library);
-      for (Library dependencyLibrary : asLibraries(library.getDependencyLibraryNames())) {
-        if (exploredLibraries.contains(dependencyLibrary)) {
-          continue;
-        }
-        unexploredLibraries.add(dependencyLibrary);
-      }
+  private void assertUniquelyProvided(Library newLibrary, String compilationUnitTypeSourceName) {
+    if (librariesByCompilationUnitTypeSourceName.containsKey(compilationUnitTypeSourceName)) {
+      Library oldLibrary =
+          librariesByCompilationUnitTypeSourceName.get(compilationUnitTypeSourceName);
+      throw new CollidingCompilationUnitException(formatDuplicateCompilationUnitMessage(
+          compilationUnitTypeSourceName, oldLibrary.getLibraryName(), newLibrary.getLibraryName()));
     }
-
-    return librariesForGenerator;
   }
 
   private Map<String, Library> getLibrariesByBuildResourcePath() {
@@ -536,7 +508,6 @@
     return Collections.unmodifiableMap(librariesByCompilationUnitTypeBinaryName);
   }
 
-  // TODO(stalcup): throw an error if more than one version of a type is provided.
   private Map<String, Library> getLibrariesByCompilationUnitTypeSourceName() {
     if (librariesByCompilationUnitTypeSourceName == null) {
       librariesByCompilationUnitTypeSourceName = Maps.newLinkedHashMap();
@@ -545,7 +516,7 @@
       for (Library library : libraries) {
         for (String compilationUnitTypeSourceName :
             library.getRegularCompilationUnitTypeSourceNames()) {
-          checkCompilationUnitUnique(library, compilationUnitTypeSourceName);
+          assertUniquelyProvided(library, compilationUnitTypeSourceName);
           librariesByCompilationUnitTypeSourceName.put(compilationUnitTypeSourceName, library);
 
           Collection<String> nestedTypeSourceNames = library
@@ -560,7 +531,7 @@
       for (Library library : libraries) {
         for (String superSourceCompilationUnitTypeSourceName :
             library.getSuperSourceCompilationUnitTypeSourceNames()) {
-          checkCompilationUnitUnique(library, superSourceCompilationUnitTypeSourceName);
+          assertUniquelyProvided(library, superSourceCompilationUnitTypeSourceName);
           librariesByCompilationUnitTypeSourceName.put(superSourceCompilationUnitTypeSourceName,
               library);
 
@@ -576,18 +547,6 @@
     return Collections.unmodifiableMap(librariesByCompilationUnitTypeSourceName);
   }
 
-  private void checkCompilationUnitUnique(Library newLibrary,
-      String compilationUnitTypeSourceName) {
-    if (librariesByCompilationUnitTypeSourceName.containsKey(compilationUnitTypeSourceName)) {
-      Library oldLibrary =
-          librariesByCompilationUnitTypeSourceName.get(compilationUnitTypeSourceName);
-      throw new CollidingCompilationUnitException(String.format(
-          "Compilation units must be unique but '%s' is being "
-          + "provided by both the '%s' and '%s' library.", compilationUnitTypeSourceName,
-          oldLibrary.getLibraryName(), newLibrary.getLibraryName()));
-    }
-  }
-
   private Map<String, Library> getLibrariesByPublicResourcePath() {
     if (librariesByPublicResourcePath == null) {
       librariesByPublicResourcePath = Maps.newLinkedHashMap();
diff --git a/dev/core/src/com/google/gwt/dev/cfg/LibraryWriter.java b/dev/core/src/com/google/gwt/dev/cfg/LibraryWriter.java
index 570e57a..119a32b 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/LibraryWriter.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/LibraryWriter.java
@@ -18,7 +18,6 @@
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.PersistenceBackedObject;
-import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 
 import java.util.Set;
 
@@ -59,49 +58,40 @@
   void addGeneratedArtifacts(ArtifactSet generatedArtifacts);
 
   /**
-   * Registers newly legal values on some binding property.
-   */
-  void addNewBindingPropertyValuesByName(String propertyName, Iterable<String> propertyValues);
-
-  /**
-   * Sets the newly current value(s) on some configuration property.
-   */
-  void addNewConfigurationPropertyValuesByName(
-      String propertyName, Iterable<String> propertyValues);
-
-  /**
    * Adds a public resource (such as a html, css, or png file).
    */
   void addPublicResource(Resource publicResource);
 
   /**
-   * Marks a generator as having been executed for this library and all sub libraries.
-   */
-  void addRanGeneratorName(String generatorName);
-
-  /**
-   * Returns the map of binding property names to newly legal values.
-   */
-  Multimap<String, String> getNewBindingPropertyValuesByName();
-
-  /**
-   * Returns a map of configuration property names to newly set value(s).
-   */
-  Multimap<String, String> getNewConfigurationPropertyValuesByName();
-
-  /**
    * Returns a handle to the permutation result object that was constructed as part of the
    * compilation for this library.
    */
   PersistenceBackedObject<PermutationResult> getPermutationResultHandle();
 
   /**
+   * Returns the set of source names of rebound types that have been processed by the given
+   * Generator.
+   */
+  Set<String> getProcessedReboundTypeSourceNames(String generatorName);
+
+  /**
    * Returns the set of source names of types which are the subject of GWT.create() calls in source
    * code for this library.
    */
   Set<String> getReboundTypeSourceNames();
 
   /**
+   * Registers the type (by it's source name) as having been processed by the given generator.
+   */
+  void markReboundTypeProcessed(String processedReboundTypeSourceName, String generatorName);
+
+  /**
+   * Records the set of names of types which are the subject of GWT.create() calls in source code
+   * for this library.
+   */
+  void markReboundTypesProcessed(Set<String> reboundTypeSourceNames);
+
+  /**
    * Records the library name.<br />
    *
    * Library names are the way that libraries reference one another as dependencies and should be
@@ -110,12 +100,6 @@
   void setLibraryName(String libraryName);
 
   /**
-   * Records the set of names of types which are the subject of GWT.create() calls in source code
-   * for this library.
-   */
-  void setReboundTypeSourceNames(Set<String> reboundTypeSourceNames);
-
-  /**
    * Finishes writing all library contents and closes the library.
    */
   void write();
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index 264abe0..7f7a373 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -37,11 +37,14 @@
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.Iterators;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.thirdparty.guava.common.collect.Queues;
@@ -59,6 +62,7 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -101,6 +105,44 @@
     EXTERNAL_LIBRARY, TARGET_LIBRARY
   }
 
+  /**
+   * Encapsulates two String names which conceptually represent the starting and ending points of a
+   * path.
+   * <p>
+   * For example in the following module dependency path ["com.acme.MyApp",
+   * "com.acme.widgets.Widgets", "com.google.gwt.user.User"] the fromName is "com.acme.MyApp" and
+   * the toName is "com.google.gwt.user.User" and together they form a PathEndNames instance.
+   * <p>
+   * The purpose of the class is to serve as a key by which to lookup the fileset path that connects
+   * two libraries. It makes it possible to insert fileset entries into a circular module reference
+   * report.
+   */
+  private static class LibraryDependencyEdge {
+
+    private String fromName;
+    private String toName;
+
+    public LibraryDependencyEdge(String fromName, String toName) {
+      this.fromName = fromName;
+      this.toName = toName;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+      if (object instanceof LibraryDependencyEdge) {
+        LibraryDependencyEdge that = (LibraryDependencyEdge) object;
+        return Objects.equal(this.fromName, that.fromName)
+            && Objects.equal(this.toName, that.toName);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(fromName, toName);
+    }
+  }
+
   private static final ResourceFilter NON_JAVA_RESOURCES = new ResourceFilter() {
     @Override
     public boolean allows(String path) {
@@ -135,6 +177,13 @@
     return true;
   }
 
+  private static LinkedList<String> createExtendedCopy(LinkedList<String> list,
+      String extendingElement) {
+    LinkedList<String> extendedCopy = Lists.newLinkedList(list);
+    extendedCopy.add(extendingElement);
+    return extendedCopy;
+  }
+
   /**
    * All resources found on the public path, specified by <public> directives in
    * modules (or the implicit ./public directory). Marked 'lazy' because it does not
@@ -165,6 +214,11 @@
 
   private final DefaultFilters defaultFilters;
 
+  /**
+   * The canonical module names of dependency library modules per depending library module.
+   */
+  private Multimap<String, String> directDependencyModuleNamesByModuleName = HashMultimap.create();
+
   private final List<String> entryPointTypeNames = new ArrayList<String>();
 
   /**
@@ -172,6 +226,20 @@
    */
   private final Set<String> externalLibraryCanonicalModuleNames = Sets.newLinkedHashSet();
 
+  /**
+   * Records the canonical module names for filesets.
+   */
+  private Set<String> filesetModuleNames = Sets.newHashSet();
+
+  /**
+   * A mapping from a pair of starting and ending module names of a path to the ordered list of
+   * names of fileset modules that connect those end points. The mapping allows one to discover what
+   * path of filesets are connecting a pair of libraries when those libraries do not directly depend
+   * on one another. Multiple paths may exist; this map contains only one of them.
+   */
+  private ListMultimap<LibraryDependencyEdge, String> filesetPathPerEdge =
+      ArrayListMultimap.create();
+
   private final Set<File> gwtXmlFiles = new HashSet<File>();
 
   private final Set<String> inheritedModules = new HashSet<String>();
@@ -207,17 +275,6 @@
    */
   private String nameOverride;
 
-
-  /**
-   * The canonical module names of dependency library modules per depending library module.
-   */
-  private Multimap<String, String> directDependentsModuleNamesByModuleName = HashMultimap.create();
-
-  /**
-   * Records the canonical module names for filesets.
-   */
-  private Set<String> filesetModuleNames = Sets.newHashSet();
-
   private final Properties properties = new Properties();
 
   private PathPrefixSet publicPrefixSet = new PathPrefixSet();
@@ -263,17 +320,10 @@
   }
 
   /**
-   * Register a {@code dependentModuleName} as a directDependent of {@code currentModuleName}
+   * Register a {@code dependencyModuleName} as a directDependency of {@code currentModuleName}
    */
-  public void addDirectDependent(String currentModuleName, String dependentModuleName) {
-    directDependentsModuleNamesByModuleName.put(currentModuleName, dependentModuleName);
-  }
-
-  /**
-   * Registers a module as a fileset.
-   */
-  public void addFileset(String filesetModuleName) {
-    filesetModuleNames.add(filesetModuleName);
+  public void addDirectDependency(String currentModuleName, String dependencyModuleName) {
+    directDependencyModuleNamesByModuleName.put(currentModuleName, dependencyModuleName);
   }
 
   public synchronized void addEntryPointTypeName(String typeName) {
@@ -283,6 +333,13 @@
     entryPointTypeNames.add(typeName);
   }
 
+  /**
+   * Registers a module as a fileset.
+   */
+  public void addFileset(String filesetModuleName) {
+    filesetModuleNames.add(filesetModuleName);
+  }
+
   public void addGwtXmlFile(File xmlFile) {
     gwtXmlFiles.add(xmlFile);
   }
@@ -580,7 +637,7 @@
    */
   public Collection<String> getDirectDependencies(String libraryModuleName) {
     assert !filesetModuleNames.contains(libraryModuleName);
-    return directDependentsModuleNamesByModuleName.get(libraryModuleName);
+    return directDependencyModuleNamesByModuleName.get(libraryModuleName);
   }
 
   public synchronized String[] getEntryPointTypeNames() {
@@ -596,6 +653,10 @@
     return externalLibraryCanonicalModuleNames;
   }
 
+  public List<String> getFileSetPathBetween(String fromModuleName, String toModuleName) {
+    return filesetPathPerEdge.get(new LibraryDependencyEdge(fromModuleName, toModuleName));
+  }
+
   public synchronized String getFunctionName() {
     return getName().replace('.', '_');
   }
@@ -838,12 +899,12 @@
     }
   }
 
-  private void addExternalLibraryCanonicalModuleName(String canonicalModuleName) {
+  private void addExternalLibraryCanonicalModuleName(String externalLibraryCanonicalModuleName) {
     // Ignore circular dependencies on self.
-    if (canonicalModuleName.equals(getCanonicalName())) {
+    if (externalLibraryCanonicalModuleName.equals(getCanonicalName())) {
       return;
     }
-    externalLibraryCanonicalModuleNames.add(canonicalModuleName);
+    externalLibraryCanonicalModuleNames.add(externalLibraryCanonicalModuleName);
   }
 
   private boolean attributeIsForTargetLibrary() {
@@ -882,16 +943,21 @@
    * Reduce the direct dependency graph to exclude filesets.
    */
   private void computeLibraryDependencyGraph() {
-    for (String moduleName : Lists.newArrayList(directDependentsModuleNamesByModuleName.keySet())) {
+    for (String moduleName : Lists.newArrayList(directDependencyModuleNamesByModuleName.keySet())) {
       Set<String> libraryModules = Sets.newHashSet();
       Set<String> filesetsProcessed = Sets.newHashSet();
 
       // Direct dependents might be libraries or fileset, so add them to a queue of modules
       // to process.
-      Queue<String> modulesToProcess =
-          Queues.newArrayDeque(directDependentsModuleNamesByModuleName.get(moduleName));
-      while (!modulesToProcess.isEmpty()) {
-        String dependentModuleName = modulesToProcess.poll();
+      Queue<LinkedList<String>> modulePathsToProcess = Queues.newArrayDeque();
+      Collection<String> directDependencyModuleNames =
+          directDependencyModuleNamesByModuleName.get(moduleName);
+      for (String directDependencyModuleName : directDependencyModuleNames) {
+        modulePathsToProcess.add(Lists.newLinkedList(ImmutableList.of(directDependencyModuleName)));
+      }
+      while (!modulePathsToProcess.isEmpty()) {
+        LinkedList<String> dependentModuleNamePath = modulePathsToProcess.poll();
+        String dependentModuleName = dependentModuleNamePath.getLast();
 
         boolean isLibrary = !filesetModuleNames.contains(dependentModuleName);
         if (isLibrary) {
@@ -899,6 +965,10 @@
           // the library itself.
           if (!moduleName.equals(dependentModuleName)) {
             libraryModules.add(dependentModuleName);
+
+            dependentModuleNamePath.removeLast();
+            filesetPathPerEdge.putAll(new LibraryDependencyEdge(moduleName, dependentModuleName),
+                dependentModuleNamePath);
           }
           continue;
         }
@@ -907,16 +977,18 @@
 
         // Get the dependencies of the dependent module under consideration and add all those
         // that have not been already processed to the queue of modules to process.
-        Set<String> notAlreadyProcessed =
-            Sets.newHashSet(directDependentsModuleNamesByModuleName.get(dependentModuleName));
-        notAlreadyProcessed.removeAll(filesetsProcessed);
-        modulesToProcess.addAll(notAlreadyProcessed);
+        Set<String> unProcessedModules =
+            Sets.newHashSet(directDependencyModuleNamesByModuleName.get(dependentModuleName));
+        unProcessedModules.removeAll(filesetsProcessed);
+        for (String unProcessedModule : unProcessedModules) {
+          modulePathsToProcess.add(createExtendedCopy(dependentModuleNamePath, unProcessedModule));
+        }
       }
       // Rewrite the dependents with the set just computed.
-      directDependentsModuleNamesByModuleName.replaceValues(moduleName, libraryModules);
+      directDependencyModuleNamesByModuleName.replaceValues(moduleName, libraryModules);
     }
     // Remove all fileset entries.
-    directDependentsModuleNamesByModuleName.removeAll(filesetModuleNames);
+    directDependencyModuleNamesByModuleName.removeAll(filesetModuleNames);
   }
 
   private synchronized void ensureResourcesScanned() {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index fae7600..f95723d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.dev.util.xml.ReflectiveParser;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
 import com.google.gwt.util.tools.Utility;
 
@@ -164,17 +165,6 @@
       libraryWriter.setLibraryName(module.getCanonicalName());
       libraryWriter.addDependencyLibraryNames(module.getExternalLibraryCanonicalModuleNames());
 
-      // Records binding property defined values that were newly created in this library.
-      for (BindingProperty bindingProperty : module.getProperties().getBindingProperties()) {
-        libraryWriter.addNewBindingPropertyValuesByName(
-            bindingProperty.getName(), bindingProperty.getTargetLibraryDefinedValues());
-      }
-      // Records configuration property value changes that occurred in this library.
-      for (ConfigurationProperty configurationProperty :
-          module.getProperties().getConfigurationProperties()) {
-        libraryWriter.addNewConfigurationPropertyValuesByName(
-            configurationProperty.getName(), configurationProperty.getTargetLibraryValues());
-      }
       // Saves non java and gwt.xml build resources, like PNG and CSS files.
       for (Resource buildResource : module.getBuildResourceOracle().getResources()) {
         if (buildResource.getPath().endsWith(".java")
@@ -275,8 +265,12 @@
     this.resourceLoader = loader;
   }
 
-  public boolean enforceStrictResources() {
-    return compilerContext.getOptions().enforceStrictResources();
+  public boolean enforceStrictSourceResources() {
+    return compilerContext.getOptions().enforceStrictSourceResources();
+  }
+
+  public boolean enforceStrictPublicResources() {
+    return compilerContext.getOptions().enforceStrictPublicResources();
   }
 
   /**
@@ -352,8 +346,7 @@
       }
     }
     if (moduleURL == null) {
-      logger.log(TreeLogger.ERROR,"Unable to find '" + resName + "' on your classpath; "
-          + "could be a typo, or maybe you forgot to include a classpath entry for source?");
+      logger.log(TreeLogger.ERROR, formatUnableToFindModuleMessage(resName));
       throw new UnableToCompleteException();
     }
 
@@ -390,4 +383,10 @@
       Utility.close(r);
     }
   }
+
+  @VisibleForTesting
+  public static String formatUnableToFindModuleMessage(String moduleResourcePath) {
+    return "Unable to find '" + moduleResourcePath + "' on your classpath; "
+        + "could be a typo, or maybe you forgot to include a classpath entry for source?";
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index afb2cba..8ccff12 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -500,7 +500,7 @@
     @SuppressWarnings("unused") // called reflectively
     protected Schema __inherits_begin(String name)
         throws UnableToCompleteException {
-      moduleDef.addDirectDependent(moduleName, name);
+      moduleDef.addDirectDependency(moduleName, name);
       loader.nestedLoad(logger, name, moduleDef);
       return null;
     }
@@ -1444,25 +1444,32 @@
   }
 
   @SuppressWarnings("unused")
-  protected Schema __module_begin(NullableName renameTo, String type) {
+  protected Schema __module_begin(NullableName renameTo, String type)
+      throws UnableToCompleteException {
     ModuleType moduleType = ModuleType.valueOf(type.toUpperCase(Locale.ENGLISH));
     moduleDef.enterModule(moduleType, moduleName);
+
+    // All modules implicitly depend on com.google.gwt.core.Core. Processing of this dependency
+    // needs to occur early so that subsequent processing has the opportunity to override property
+    // values.
+    bodySchema.__inherits_begin("com.google.gwt.core.Core");
+
     return bodySchema;
   }
 
   protected void __module_end(NullableName renameTo, String type) {
-    if (!loader.enforceStrictResources()) {
-      // If we're not being strict about resources and no dependencies have been added, go ahead and
-      // implicitly add "client" and "public" resource dependencies.
-      if (!foundExplicitSourceOrSuperSource) {
-        bodySchema.addSourcePackage(modulePackageAsPath, "client", Empty.STRINGS,
-            Empty.STRINGS, Empty.STRINGS, true, true, false);
-      }
+    // If we're not being strict about source resources and no source paths have been added, go
+    // ahead and implicitly add the "client" directory.
+    if (!loader.enforceStrictSourceResources() && !foundExplicitSourceOrSuperSource) {
+      bodySchema.addSourcePackage(modulePackageAsPath, "client", Empty.STRINGS,
+          Empty.STRINGS, Empty.STRINGS, true, true, false);
+    }
 
-      if (!foundAnyPublic) {
-        bodySchema.addPublicPackage(modulePackageAsPath, "public", Empty.STRINGS,
-            Empty.STRINGS, Empty.STRINGS, true, true);
-      }
+    // If we're not being strict about public resources and no public paths have been added, go
+    // ahead and implicitly add the "public" directory.
+    if (!loader.enforceStrictPublicResources() && !foundAnyPublic) {
+      bodySchema.addPublicPackage(modulePackageAsPath, "public", Empty.STRINGS,
+          Empty.STRINGS, Empty.STRINGS, true, true);
     }
 
     // We do this in __module_end so this value is never inherited
diff --git a/dev/core/src/com/google/gwt/dev/cfg/NullLibraryWriter.java b/dev/core/src/com/google/gwt/dev/cfg/NullLibraryWriter.java
index a20ce56..01a0f41 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/NullLibraryWriter.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/NullLibraryWriter.java
@@ -19,9 +19,6 @@
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.PersistenceBackedObject;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
-import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultimap;
-import com.google.gwt.thirdparty.guava.common.collect.Multimap;
-import com.google.gwt.thirdparty.guava.common.collect.Multimaps;
 
 import java.util.Set;
 
@@ -31,9 +28,6 @@
  */
 public class NullLibraryWriter implements LibraryWriter {
 
-  private Multimap<String, String> newBindingPropertyValuesByName = LinkedHashMultimap.create();
-  private Multimap<String, String> newConfigurationPropertyValuesByName =
-      LinkedHashMultimap.create();
   private Set<String> strings = ImmutableSet.of();
 
   @Override
@@ -57,51 +51,35 @@
   }
 
   @Override
-  public void addNewBindingPropertyValuesByName(String propertyName,
-      Iterable<String> propertyValues) {
-    newBindingPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
-  public void addNewConfigurationPropertyValuesByName(String propertyName,
-      Iterable<String> propertyValues) {
-    newConfigurationPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
   public void addPublicResource(Resource publicResource) {
   }
 
   @Override
-  public void addRanGeneratorName(String generatorName) {
-  }
-
-  @Override
-  public Multimap<String, String> getNewBindingPropertyValuesByName() {
-    return Multimaps.unmodifiableMultimap(newBindingPropertyValuesByName);
-  }
-
-  @Override
-  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
-    return Multimaps.unmodifiableMultimap(newConfigurationPropertyValuesByName);
-  }
-
-  @Override
   public PersistenceBackedObject<PermutationResult> getPermutationResultHandle() {
     return null;
   }
 
   @Override
+  public Set<String> getProcessedReboundTypeSourceNames(String generatorName) {
+    return null;
+  }
+
+  @Override
   public Set<String> getReboundTypeSourceNames() {
     return strings;
   }
 
   @Override
-  public void setLibraryName(String libraryName) {
+  public void markReboundTypeProcessed(String processedReboundTypeSourceName,
+      String generatorName) {
   }
 
   @Override
-  public void setReboundTypeSourceNames(Set<String> reboundTypeSourceNames) {
+  public void markReboundTypesProcessed(Set<String> reboundTypeSourceNames) {
+  }
+
+  @Override
+  public void setLibraryName(String libraryName) {
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Property.java b/dev/core/src/com/google/gwt/dev/cfg/Property.java
index c87bc06..02da80d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Property.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Property.java
@@ -33,24 +33,11 @@
     return name.compareTo(o.name);
   }
 
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof Property)) {
-      return false;
-    }
-    return name.equals(((Property) o).name);
-  }
-
   public String getName() {
     return name;
   }
 
   @Override
-  public int hashCode() {
-    return name.hashCode();
-  }
-
-  @Override
   public String toString() {
     return name;
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java b/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
index 24ca2fa..be67bd1 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.thirdparty.guava.common.base.Objects;
+
 import java.io.Serializable;
 
 /**
@@ -28,7 +30,21 @@
     this.body = body;
   }
 
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof PropertyProvider) {
+      PropertyProvider that = (PropertyProvider) object;
+      return Objects.equal(this.body, that.body);
+    }
+    return false;
+  }
+
   public String getBody() {
     return body;
   }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(body);
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java b/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
index c5cd3e4..2aa71bb 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
@@ -97,6 +97,11 @@
       out.println("public class " + typeName + " {");
 
       for (BindingProperty bindingProperty : newBindingProperties) {
+        // If nothing about the binding property was set or created in the current module.
+        if (bindingProperty.getTargetLibraryDefinedValues().isEmpty()) {
+          // Then its previously created property provider is still valid.
+          continue;
+        }
         createPropertyProviderClass(logger, out, bindingProperty);
       }
       // TODO(stalcup): create configuration property providers.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
index a7e09e5..dd41a9a 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
@@ -23,8 +23,11 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.typemodel.LibraryTypeOracle.UnsupportedTypeOracleAccess;
 import com.google.gwt.dev.javac.typemodel.TypeOracle;
 import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -120,8 +123,8 @@
    * gather runtime rebind rules for all corresponding pairs of property values and Generator
    * output.
    */
-  public void generate(
-      TreeLogger logger, Properties moduleProperties, GeneratorContext context, String typeName) {
+  public void generate(TreeLogger logger, Properties moduleProperties, GeneratorContext context,
+      String typeName) throws UnableToCompleteException {
     Map<Map<String, String>, String> resultTypeNamesByProperties =
         computeResultTypeNamesByProperties(
             logger, moduleProperties, (StandardGeneratorContext) context, typeName);
@@ -157,6 +160,36 @@
     return context.runGeneratorIncrementally(logger, generatorClass, typeName);
   }
 
+  public boolean relevantPropertiesAreFinal(Properties currentProperties,
+      Properties finalProperties) {
+    if (accessedPropertyNames.equals(ALL_PROPERTIES)) {
+      // Generator must be assumed to depend on all properties.
+      for (BindingProperty finalProperty : finalProperties.getBindingProperties()) {
+        Property currentProperty = currentProperties.find(finalProperty.getName());
+        if (!Objects.equal(finalProperty, currentProperty)) {
+          return false;
+        }
+      }
+      for (ConfigurationProperty finalProperty : finalProperties.getConfigurationProperties()) {
+        Property currentProperty = currentProperties.find(finalProperty.getName());
+        if (!Objects.equal(finalProperty, currentProperty)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // Generator defines a limited set of properties it cares about.
+    for (String accessedPropertyName : accessedPropertyNames) {
+      Property finalProperty = finalProperties.find(accessedPropertyName);
+      Property currentProperty = currentProperties.find(accessedPropertyName);
+      if (!Objects.equal(finalProperty, currentProperty)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   @Override
   public String toString() {
     return "<generate-with class='" + generatorClass.getName() + "'/>";
@@ -182,7 +215,7 @@
     return "return " + rootCondition.toSource() + ";";
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   protected Generator getGenerator() {
     if (generator == null) {
       try {
@@ -203,81 +236,82 @@
    * values. Log corresponding pairs of property values and Generator output.
    */
   private Map<Map<String, String>, String> computeResultTypeNamesByProperties(TreeLogger logger,
-      Properties moduleProperties, StandardGeneratorContext context, String typeName) {
-    try {
-      Map<Map<String, String>, String> resultTypeNamesByProperties = Maps.newHashMap();
-      DynamicPropertyOracle dynamicPropertyOracle =
-          new DynamicPropertyOracle(moduleProperties);
+      Properties moduleProperties, StandardGeneratorContext context, String typeName)
+      throws UnableToCompleteException {
+    Map<Map<String, String>, String> resultTypeNamesByProperties = Maps.newHashMap();
+    DynamicPropertyOracle dynamicPropertyOracle = new DynamicPropertyOracle(moduleProperties);
 
-      // Maybe prime the pump.
-      if (!accessedPropertyNames.equals(ALL_PROPERTIES)) {
-        for (String accessedPropertyName : accessedPropertyNames) {
-          try {
-            dynamicPropertyOracle.getSelectionProperty(logger, accessedPropertyName);
-          } catch (BadPropertyValueException e) {
-            // ignore
-          }
+    // Maybe prime the pump.
+    if (!accessedPropertyNames.equals(ALL_PROPERTIES)) {
+      for (String accessedPropertyName : accessedPropertyNames) {
+        try {
+          dynamicPropertyOracle.getSelectionProperty(logger, accessedPropertyName);
+        } catch (BadPropertyValueException e) {
+          // ignore
         }
       }
-      boolean needsAllTypesIfRun = contentDependsOnTypes() && context.isGlobalCompile();
-      TypeOracle typeModelTypeOracle =
-          (com.google.gwt.dev.javac.typemodel.TypeOracle) context.getTypeOracle();
-
-      context.reset();
-      context.setPropertyOracle(dynamicPropertyOracle);
-
-      context.setCurrentGenerator(generatorClass);
-
-      do {
-        resultTypeNamesByProperties.clear();
-        context.reset();
-        Properties accessedProperties = new Properties();
-
-        List<BindingProperty> accessedPropertiesList =
-            new ArrayList<BindingProperty>(dynamicPropertyOracle.getAccessedProperties());
-        for (BindingProperty bindingProperty : accessedPropertiesList) {
-          accessedProperties.addBindingProperty(bindingProperty);
-        }
-        PropertyPermutations permutationsOfAccessedProperties =
-            new PropertyPermutations(accessedProperties, Sets.<String>newHashSet());
-
-        for (int permutationId = 0; permutationId < permutationsOfAccessedProperties.size();
-            permutationId++) {
-          String[] orderedPropertyValues =
-              permutationsOfAccessedProperties.getOrderedPropertyValues(permutationId);
-          BindingProperty[] orderedProperties =
-              permutationsOfAccessedProperties.getOrderedProperties();
-
-          dynamicPropertyOracle.reset();
-          for (int propertyIndex = 0; propertyIndex < orderedPropertyValues.length;
-              propertyIndex++) {
-            dynamicPropertyOracle.prescribePropertyValue(
-                orderedProperties[propertyIndex].getName(), orderedPropertyValues[propertyIndex]);
-          }
-
-          if (!isApplicable(logger, context, typeName)) {
-            continue;
-          }
-          if (needsAllTypesIfRun) {
-            typeModelTypeOracle.ensureAllLoaded();
-          }
-          String resultTypeName = getGenerator().generate(logger, context, typeName);
-          if (resultTypeName != null) {
-            // Some generators only run to create resource artifacts and don't actually participate
-            // in the requestType->resultType rebind process.
-            resultTypeNamesByProperties.put(
-                dynamicPropertyOracle.getPrescribedPropertyValuesByName(), resultTypeName);
-          }
-
-          if (dynamicPropertyOracle.haveAccessedPropertiesChanged()) {
-            break;
-          }
-        }
-      } while (dynamicPropertyOracle.haveAccessedPropertiesChanged());
-
-      return resultTypeNamesByProperties;
-    } catch (UnableToCompleteException e) {
-      throw new InternalCompilerException(e.getMessage());
     }
+    boolean needsAllTypesIfRun = contentDependsOnTypes() && context.isGlobalCompile();
+    TypeOracle typeModelTypeOracle =
+        (com.google.gwt.dev.javac.typemodel.TypeOracle) context.getTypeOracle();
+
+    context.setPropertyOracle(dynamicPropertyOracle);
+
+    context.setCurrentGenerator(generatorClass);
+
+    do {
+      resultTypeNamesByProperties.clear();
+      Properties accessedProperties = new Properties();
+
+      List<BindingProperty> accessedPropertiesList =
+          new ArrayList<BindingProperty>(dynamicPropertyOracle.getAccessedProperties());
+      for (BindingProperty bindingProperty : accessedPropertiesList) {
+        accessedProperties.addBindingProperty(bindingProperty);
+      }
+      PropertyPermutations permutationsOfAccessedProperties =
+          new PropertyPermutations(accessedProperties, Sets.<String> newHashSet());
+
+      for (int permutationId = 0; permutationId < permutationsOfAccessedProperties.size();
+          permutationId++) {
+        String[] orderedPropertyValues =
+            permutationsOfAccessedProperties.getOrderedPropertyValues(permutationId);
+        BindingProperty[] orderedProperties =
+            permutationsOfAccessedProperties.getOrderedProperties();
+
+        dynamicPropertyOracle.reset();
+        for (int propertyIndex = 0; propertyIndex < orderedPropertyValues.length; propertyIndex++) {
+          dynamicPropertyOracle.prescribePropertyValue(orderedProperties[propertyIndex].getName(),
+              orderedPropertyValues[propertyIndex]);
+        }
+
+        if (!isApplicable(logger, context, typeName)) {
+          continue;
+        }
+        if (needsAllTypesIfRun) {
+          typeModelTypeOracle.ensureAllLoaded();
+        }
+        String resultTypeName;
+        try {
+          resultTypeName = getGenerator().generate(logger, context, typeName);
+        } catch (UnsupportedTypeOracleAccess e) {
+          logger.log(TreeLogger.ERROR, String.format(
+              "TypeOracle error when running generator '%s' in an incremental compile: %s",
+              getName(), e.getMessage()));
+          throw new UnableToCompleteException();
+        }
+        if (resultTypeName != null) {
+          // Some generators only run to create resource artifacts and don't actually participate
+          // in the requestType->resultType rebind process.
+          resultTypeNamesByProperties.put(dynamicPropertyOracle.getPrescribedPropertyValuesByName(),
+              resultTypeName);
+        }
+
+        if (dynamicPropertyOracle.haveAccessedPropertiesChanged()) {
+          break;
+        }
+      }
+    } while (dynamicPropertyOracle.haveAccessedPropertiesChanged());
+
+    return resultTypeNamesByProperties;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ZipLibrary.java b/dev/core/src/com/google/gwt/dev/cfg/ZipLibrary.java
index e16e6b4..7461c16 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ZipLibrary.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ZipLibrary.java
@@ -69,6 +69,28 @@
       }
     }
 
+    private void close() {
+      try {
+        // Close our ZipFile.
+        zipFile.close();
+        zipFile = null;
+      } catch (IOException e) {
+        // nothing to be done about it.
+      }
+    }
+
+    private String decode(String string) {
+      string = decodeCharacter(string, Libraries.VALUE_SEPARATOR);
+      string = decodeCharacter(string, Libraries.LINE_SEPARATOR);
+      string = decodeCharacter(string, Libraries.KEY_VALUE_SEPARATOR);
+      string = decodeCharacter(string, Libraries.ENCODE_PREFIX);
+      return string;
+    }
+
+    private String decodeCharacter(String string, char character) {
+      return string.replace(Libraries.ENCODE_PREFIX + Integer.toString(character), character + "");
+    }
+
     private InputStream getClassFileStream(String classFilePath) {
       ZipEntry classFileEntry =
           zipFile.getEntry(Libraries.computeClassFileEntryName(classFilePath));
@@ -160,12 +182,8 @@
       return readStringMultimap(Libraries.NESTED_SOURCE_NAMES_BY_ENCLOSING_NAME_ENTRY_NAME);
     }
 
-    private Multimap<String, String> readNewBindingPropertyValuesByName() {
-      return readStringMultimap(Libraries.NEW_BINDING_PROPERTY_VALUES_BY_NAME_ENTRY_NAME);
-    }
-
-    private Multimap<String, String> readNewConfigurationPropertyValuesByName() {
-      return readStringMultimap(Libraries.NEW_CONFIGURATION_PROPERTY_VALUES_BY_NAME_ENTRY_NAME);
+    private Multimap<String, String> readProcessedReboundTypeSourceNamesByGenerator() {
+      return readStringMultimap(Libraries.PROCESSED_REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME);
     }
 
     private Resource readPublicResourceByPath(String path) {
@@ -177,10 +195,6 @@
       return readStringSet(Libraries.PUBLIC_RESOURCE_PATHS_ENTRY_NAME);
     }
 
-    private Set<String> readRanGeneratorNames() {
-      return readStringSet(Libraries.RAN_GENERATOR_NAMES_ENTRY_NAME);
-    }
-
     private Set<String> readReboundTypeSourceNames() {
       return readStringSet(Libraries.REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME);
     }
@@ -232,18 +246,6 @@
       return stringMultimap;
     }
 
-    private String decode(String string) {
-      string = decodeCharacter(string, Libraries.VALUE_SEPARATOR);
-      string = decodeCharacter(string, Libraries.LINE_SEPARATOR);
-      string = decodeCharacter(string, Libraries.KEY_VALUE_SEPARATOR);
-      string = decodeCharacter(string, Libraries.ENCODE_PREFIX);
-      return string;
-    }
-
-    private String decodeCharacter(String string, char character) {
-      return string.replace(Libraries.ENCODE_PREFIX + Integer.toString(character), character + "");
-    }
-
     private Set<String> readStringSet(String entryName) {
       return Collections.unmodifiableSet(Sets.newLinkedHashSet(readStringList(entryName)));
     }
@@ -282,12 +284,10 @@
   private String libraryName;
   private Multimap<String, String> nestedBinaryNamesByCompilationUnitName;
   private Multimap<String, String> nestedSourceNamesByCompilationUnitName;
-  private Multimap<String, String> newBindingPropertyValuesByName;
-  private Multimap<String, String> newConfigurationPropertyValuesByName;
   private ZipEntryBackedObject<PermutationResult> permutationResultHandle;
+  private Multimap<String, String> processedReboundTypeSourceNamesByGenerator;
   private Set<String> publicResourcePaths;
   private Map<String, Resource> publicResourcesByPath = Maps.newHashMap();
-  private Set<String> ranGeneratorNames;
   private Set<String> reboundTypeSourceNames;
   private Set<String> regularCompilationUnitTypeSourceNames;
   private Set<String> superSourceClassFilePaths;
@@ -303,6 +303,11 @@
   }
 
   @Override
+  public void close() {
+    zipLibraryReader.close();
+  }
+
+  @Override
   public Resource getBuildResourceByPath(String path) {
     if (!buildResourcesByPath.containsKey(path)) {
       buildResourcesByPath.put(path, zipLibraryReader.readBuildResourceByPath(path));
@@ -419,24 +424,6 @@
   }
 
   @Override
-  public Multimap<String, String> getNewBindingPropertyValuesByName() {
-    if (newBindingPropertyValuesByName == null) {
-      newBindingPropertyValuesByName =
-          Multimaps.unmodifiableMultimap(zipLibraryReader.readNewBindingPropertyValuesByName());
-    }
-    return newBindingPropertyValuesByName;
-  }
-
-  @Override
-  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
-    if (newConfigurationPropertyValuesByName == null) {
-      newConfigurationPropertyValuesByName = Multimaps.unmodifiableMultimap(
-          zipLibraryReader.readNewConfigurationPropertyValuesByName());
-    }
-    return newConfigurationPropertyValuesByName;
-  }
-
-  @Override
   public ZipEntryBackedObject<PermutationResult> getPermutationResultHandle() {
     if (permutationResultHandle == null) {
       permutationResultHandle = zipLibraryReader.getPermutationResultHandle();
@@ -445,6 +432,15 @@
   }
 
   @Override
+  public Multimap<String, String> getProcessedReboundTypeSourceNamesByGenerator() {
+    if (processedReboundTypeSourceNamesByGenerator == null) {
+      processedReboundTypeSourceNamesByGenerator = Multimaps.unmodifiableMultimap(
+          zipLibraryReader.readProcessedReboundTypeSourceNamesByGenerator());
+    }
+    return processedReboundTypeSourceNamesByGenerator;
+  }
+
+  @Override
   public Resource getPublicResourceByPath(String path) {
     if (!publicResourcesByPath.containsKey(path)) {
       publicResourcesByPath.put(path, zipLibraryReader.readPublicResourceByPath(path));
@@ -461,14 +457,6 @@
   }
 
   @Override
-  public Set<String> getRanGeneratorNames() {
-    if (ranGeneratorNames == null) {
-      ranGeneratorNames = Collections.unmodifiableSet(zipLibraryReader.readRanGeneratorNames());
-    }
-    return ranGeneratorNames;
-  }
-
-  @Override
   public Set<String> getReboundTypeSourceNames() {
     if (reboundTypeSourceNames == null) {
       reboundTypeSourceNames =
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ZipLibraryWriter.java b/dev/core/src/com/google/gwt/dev/cfg/ZipLibraryWriter.java
index 3b54c2b..bcbef44 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ZipLibraryWriter.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ZipLibraryWriter.java
@@ -25,10 +25,11 @@
 import com.google.gwt.dev.util.Name.InternalName;
 import com.google.gwt.dev.util.ZipEntryBackedObject;
 import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
-import com.google.gwt.thirdparty.guava.common.collect.Multimaps;
+import com.google.gwt.thirdparty.guava.common.collect.SetMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 import com.google.gwt.thirdparty.guava.common.io.ByteStreams;
 
@@ -73,6 +74,11 @@
       zipFile = new File(zipFileName);
     }
 
+    public void writeProcessedReboundTypeSourceNamesForGenerators() {
+      writeStringMultimap(Libraries.PROCESSED_REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME,
+          processedReboundTypeSourceNamesByGenerator);
+    }
+
     private void createFileIfMissing() {
       if (!zipFile.exists()) {
         try {
@@ -97,6 +103,18 @@
       }
     }
 
+    private String encode(String string) {
+      string = encodeCharacter(string, Libraries.ENCODE_PREFIX);
+      string = encodeCharacter(string, Libraries.KEY_VALUE_SEPARATOR);
+      string = encodeCharacter(string, Libraries.LINE_SEPARATOR);
+      string = encodeCharacter(string, Libraries.VALUE_SEPARATOR);
+      return string;
+    }
+
+    private String encodeCharacter(String string, char character) {
+      return string.replace(character + "", Libraries.ENCODE_PREFIX + Integer.toString(character));
+    }
+
     private synchronized void ensureFileReady() {
       if (fileReady) {
         return;
@@ -157,10 +175,8 @@
         writePublicResourcePaths();
 
         // Generator related
-        writeNewBindingPropertyValuesByName();
-        writeNewConfigurationPropertyValuesByName();
         writeReboundTypeSourceNames();
-        writeRanGeneratorNames();
+        writeProcessedReboundTypeSourceNamesForGenerators();
         writeGeneratedArtifactPaths();
         writeGeneratedArtifacts();
       } finally {
@@ -254,16 +270,6 @@
           nestedBinaryNamesByCompilationUnitName);
     }
 
-    private void writeNewBindingPropertyValuesByName() {
-      writeStringMultimap(
-          Libraries.NEW_BINDING_PROPERTY_VALUES_BY_NAME_ENTRY_NAME, newBindingPropertyValuesByName);
-    }
-
-    private void writeNewConfigurationPropertyValuesByName() {
-      writeStringMultimap(Libraries.NEW_CONFIGURATION_PROPERTY_VALUES_BY_NAME_ENTRY_NAME,
-          newConfigurationPropertyValuesByName);
-    }
-
     private void writePublicResourcePaths() {
       writeStringSet(Libraries.PUBLIC_RESOURCE_PATHS_ENTRY_NAME, publicResourcesByPath.keySet());
     }
@@ -272,10 +278,6 @@
       writeResources("public", Libraries.DIRECTORY_PUBLIC_RESOURCES, publicResourcesByPath);
     }
 
-    private void writeRanGeneratorNames() {
-      writeStringSet(Libraries.RAN_GENERATOR_NAMES_ENTRY_NAME, ranGeneratorNames);
-    }
-
     private void writeReboundTypeSourceNames() {
       writeStringSet(Libraries.REBOUND_TYPE_SOURCE_NAMES_ENTRY_NAME, reboundTypeSourceNames);
     }
@@ -337,18 +339,6 @@
       }
     }
 
-    private String encode(String string) {
-      string = encodeCharacter(string, Libraries.ENCODE_PREFIX);
-      string = encodeCharacter(string, Libraries.KEY_VALUE_SEPARATOR);
-      string = encodeCharacter(string, Libraries.LINE_SEPARATOR);
-      string = encodeCharacter(string, Libraries.VALUE_SEPARATOR);
-      return string;
-    }
-
-    private String encodeCharacter(String string, char character) {
-      return string.replace(character + "", Libraries.ENCODE_PREFIX + Integer.toString(character));
-    }
-
     private void writeStringSet(String entryName, Set<String> stringSet) {
       createZipEntry(entryName);
       Set<String> encodedStringSet = Sets.newHashSet();
@@ -378,15 +368,13 @@
       LinkedHashMultimap.create();
   private Multimap<String, String> nestedSourceNamesByCompilationUnitName =
       LinkedHashMultimap.create();
-  private Multimap<String, String> newBindingPropertyValuesByName = LinkedHashMultimap.create();
-  private Multimap<String, String> newConfigurationPropertyValuesByName =
-      LinkedHashMultimap.create();
   private ZipEntryBackedObject<PermutationResult> permutationResultHandle;
+  private SetMultimap<String, String> processedReboundTypeSourceNamesByGenerator =
+      HashMultimap.create();
   private Map<String, Resource> publicResourcesByPath = Maps.newHashMap();
-  private Set<String> ranGeneratorNames = Sets.newHashSet();
-  private Set<String> reboundTypeSourceNames = Sets.newHashSet();
   private Set<String> regularClassFilePaths = Sets.newHashSet();
   private Set<String> regularCompilationUnitTypeSourceNames = Sets.newLinkedHashSet();
+  private Set<String> reboundTypeSourceNames = Sets.newHashSet();
   private Set<String> superSourceClassFilePaths = Sets.newHashSet();
   private Set<String> superSourceCompilationUnitTypeSourceNames = Sets.newLinkedHashSet();
   private ZipWriter zipWriter;
@@ -454,38 +442,11 @@
   }
 
   @Override
-  public void addNewBindingPropertyValuesByName(
-      String propertyName, Iterable<String> propertyValues) {
-    newBindingPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
-  public void addNewConfigurationPropertyValuesByName(
-      String propertyName, Iterable<String> propertyValues) {
-    newConfigurationPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
   public void addPublicResource(Resource publicResource) {
     publicResourcesByPath.put(publicResource.getPath(), publicResource);
   }
 
   @Override
-  public void addRanGeneratorName(String generatorName) {
-    ranGeneratorNames.add(generatorName);
-  }
-
-  @Override
-  public Multimap<String, String> getNewBindingPropertyValuesByName() {
-    return Multimaps.unmodifiableMultimap(newBindingPropertyValuesByName);
-  }
-
-  @Override
-  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
-    return Multimaps.unmodifiableMultimap(newConfigurationPropertyValuesByName);
-  }
-
-  @Override
   public ZipEntryBackedObject<PermutationResult> getPermutationResultHandle() {
     if (permutationResultHandle == null) {
       permutationResultHandle = zipWriter.getPermutationResultHandle();
@@ -494,18 +455,29 @@
   }
 
   @Override
+  public Set<String> getProcessedReboundTypeSourceNames(String generatorName) {
+    return processedReboundTypeSourceNamesByGenerator.get(generatorName);
+  }
+
+  @Override
   public Set<String> getReboundTypeSourceNames() {
     return Collections.unmodifiableSet(reboundTypeSourceNames);
   }
 
   @Override
-  public void setLibraryName(String libraryName) {
-    this.libraryName = libraryName;
+  public void markReboundTypesProcessed(Set<String> reboundTypeSourceNames) {
+    this.reboundTypeSourceNames = reboundTypeSourceNames;
   }
 
   @Override
-  public void setReboundTypeSourceNames(Set<String> reboundTypeSourceNames) {
-    this.reboundTypeSourceNames = reboundTypeSourceNames;
+  public void markReboundTypeProcessed(String processedReboundTypeSourceName,
+      String generatorName) {
+    processedReboundTypeSourceNamesByGenerator.put(generatorName, processedReboundTypeSourceName);
+  }
+
+  @Override
+  public void setLibraryName(String libraryName) {
+    this.libraryName = libraryName;
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
index ad39740..29c715f 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
@@ -23,6 +23,7 @@
 import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -53,8 +54,14 @@
    */
   protected final Map<String, CompilationUnit> unitMap = new HashMap<String, CompilationUnit>();
 
+  private int cachedGeneratedSourceCount = 0;
+
+  private int cachedStaticSourceCount = 0;
+
   private final CompileMoreLater compileMoreLater;
 
+  private CompilerContext compilerContext;
+
   /**
    * Unmodifiable view of {@link #classFileMap}.
    */
@@ -78,6 +85,10 @@
   private final Collection<CompilationUnit> exposedUnits =
       Collections.unmodifiableCollection(unitMap.values());
 
+  private int generatedSourceCount = 0;
+
+  private int staticSourceCount = 0;
+
   /**
    * Our type oracle.
    */
@@ -88,8 +99,6 @@
    */
   private final CompilationUnitTypeOracleUpdater typeOracleUpdater;
 
-  private CompilerContext compilerContext;
-
   CompilationState(TreeLogger logger, CompilerContext compilerContext,
       TypeOracle typeOracle, CompilationUnitTypeOracleUpdater typeOracleUpdater,
       Collection<CompilationUnit> units, CompileMoreLater compileMoreLater) {
@@ -114,7 +123,7 @@
           + generatedUnits.size() + "' new generated units");
       generatedUnitsAddEvent.addData("# new generated units", "" + generatedUnits.size());
       Collection<CompilationUnit> newUnits = compileMoreLater.addGeneratedTypes(
-          logger, generatedUnits);
+          logger, generatedUnits, this);
       assimilateUnits(logger, newUnits, true);
     } finally {
       generatedUnitsAddEvent.end();
@@ -138,6 +147,14 @@
     }
   }
 
+  public int getCachedGeneratedSourceCount() {
+    return cachedGeneratedSourceCount;
+  }
+
+  public int getCachedStaticSourceCount() {
+    return cachedStaticSourceCount;
+  }
+
   /**
    * Returns a map of all compiled classes by internal name.
    */
@@ -166,11 +183,22 @@
   public Collection<CompilationUnit> getCompilationUnits() {
     return exposedUnits;
   }
+  public int getGeneratedSourceCount() {
+    return generatedSourceCount;
+  }
+  public int getStaticSourceCount() {
+    return staticSourceCount;
+  }
 
   public TypeOracle getTypeOracle() {
     return typeOracle;
   }
 
+  @VisibleForTesting
+  public Map<String, CompiledClass> getValidClasses() {
+    return compileMoreLater.getValidClasses();
+  }
+
   /**
    * Whether any errors were encountered while building this compilation state.
    */
@@ -183,6 +211,22 @@
     return false;
   }
 
+  public void incrementCachedGeneratedSourceCount(int extraCachedGeneratedSourceCount) {
+    cachedGeneratedSourceCount += extraCachedGeneratedSourceCount;
+  }
+
+  public void incrementCachedStaticSourceCount(int extraCachedStaticSourceCount) {
+    cachedStaticSourceCount += extraCachedStaticSourceCount;
+  }
+
+  public void incrementGeneratedSourceCount(int extraGeneratedSourceCount) {
+    generatedSourceCount += extraGeneratedSourceCount;
+  }
+
+  public void incrementStaticSourceCount(int extraStaticSourceCount) {
+    staticSourceCount += extraStaticSourceCount;
+  }
+
   /**
    * For testing.
    */
@@ -199,20 +243,16 @@
         classFileMapBySource.put(compiledClass.getSourceName(), compiledClass);
       }
     }
-    CompilationUnitInvalidator.retainValidUnits(logger, units, compileMoreLater.getValidClasses());
 
     // Performed after compilation unit invalidator because only valid units should be saved in the
     // library.
     if (saveInLibrary) {
+      CompilationUnitInvalidator.retainValidUnits(logger, units,
+          compileMoreLater.getValidClasses());
       for (CompilationUnit compilationUnit : units) {
         compilerContext.getLibraryWriter().addCompilationUnit(compilationUnit);
       }
     }
     typeOracleUpdater.addNewUnits(logger, units);
   }
-
-  // VisibleForTesting
-  public Map<String, CompiledClass> getValidClasses() {
-    return compileMoreLater.getValidClasses();
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
index aacba1a..15f33e0 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -238,10 +238,12 @@
      * UnableToCompleteException.
      */
     public Collection<CompilationUnit> addGeneratedTypes(TreeLogger logger,
-        Collection<GeneratedUnit> generatedUnits) throws UnableToCompleteException {
+        Collection<GeneratedUnit> generatedUnits, CompilationState compilationState)
+        throws UnableToCompleteException {
       Event event = SpeedTracerLogger.start(DevModeEventType.CSB_ADD_GENERATED_TYPES);
       try {
-        return doBuildGeneratedTypes(logger, compilerContext, generatedUnits, this);
+        return doBuildGeneratedTypes(logger, compilerContext, generatedUnits, compilationState,
+            this);
       } finally {
         event.end();
       }
@@ -556,9 +558,11 @@
       }
       builders.add(builder);
     }
+    int cachedSourceCount = cachedUnits.size();
+    int sourceCount = resources.size();
     if (logger.isLoggable(TreeLogger.TRACE)) {
-      logger.log(TreeLogger.TRACE, "Found " + cachedUnits.size() + " cached/archived units.  Used "
-          + cachedUnits.size() + " / " + resources.size() + " units from cache.");
+      logger.log(TreeLogger.TRACE, "Found " + cachedSourceCount + " cached/archived units.  Used "
+          + cachedSourceCount + " / " + sourceCount + " units from cache.");
     }
 
     Collection<CompilationUnit> resultUnits = compileMoreLater.compile(
@@ -576,8 +580,11 @@
       typeOracleUpdater = ((LibraryTypeOracle) typeOracle).getTypeOracleUpdater();
     }
 
-    return new CompilationState(logger, compilerContext, typeOracle, typeOracleUpdater,
-        resultUnits, compileMoreLater);
+    CompilationState compilationState = new CompilationState(logger, compilerContext, typeOracle,
+        typeOracleUpdater, resultUnits, compileMoreLater);
+    compilationState.incrementStaticSourceCount(sourceCount);
+    compilationState.incrementCachedStaticSourceCount(cachedSourceCount);
+    return compilationState;
   }
 
   public CompilationState doBuildFrom(
@@ -593,7 +600,8 @@
    */
   synchronized Collection<CompilationUnit> doBuildGeneratedTypes(TreeLogger logger,
       CompilerContext compilerContext, Collection<GeneratedUnit> generatedUnits,
-      CompileMoreLater compileMoreLater) throws UnableToCompleteException {
+      CompilationState compilationState, CompileMoreLater compileMoreLater)
+      throws UnableToCompleteException {
     UnitCache unitCache = compilerContext.getUnitCache();
 
     // Units we definitely want to build.
@@ -619,6 +627,8 @@
       }
       builders.add(builder);
     }
+    compilationState.incrementGeneratedSourceCount(builders.size() + cachedUnits.size());
+    compilationState.incrementCachedGeneratedSourceCount(cachedUnits.size());
     return compileMoreLater.compile(logger, compilerContext, builders,
         cachedUnits, CompilerEventType.JDT_COMPILER_CSB_GENERATED);
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitTypeOracleUpdater.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitTypeOracleUpdater.java
index a7ae4d7..af507d5 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitTypeOracleUpdater.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitTypeOracleUpdater.java
@@ -50,6 +50,7 @@
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.io.PrintWriter;
@@ -423,7 +424,7 @@
     indexTypes();
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   void indexTypes() {
     Event finishEvent =
         SpeedTracerLogger.start(CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Finish");
@@ -455,7 +456,7 @@
     addNewTypesDontIndex(logger, typeDataList, argsLookup);
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   public Resolver getMockResolver() {
     return new CompilationUnitTypeOracleResolver(
         new TypeOracleBuildContext(new MethodArgNamesLookup()));
@@ -465,7 +466,7 @@
     return typeOracle;
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   public Map<String, JRealClassType> getTypesByInternalName() {
     return typesByInternalName;
   }
@@ -886,8 +887,9 @@
 
     // Process methods
     for (CollectMethodData method : classData.getMethods()) {
-      if (!resolveMethod(logger, unresolvedType, method, typeParamLookup, context)) {
-        logger.log(TreeLogger.WARN, "Unable to resolve method " + method);
+      TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving method " + method.getName());
+      if (!resolveMethod(branch, unresolvedType, method, typeParamLookup, context)) {
+        // Already logged.
         return false;
       }
     }
@@ -896,8 +898,9 @@
     // Track the next enum ordinal across resolveField calls.
     int[] nextEnumOrdinal = new int[] {0};
     for (CollectFieldData field : classData.getFields()) {
-      if (!resolveField(logger, unresolvedType, field, typeParamLookup, nextEnumOrdinal, context)) {
-        logger.log(TreeLogger.WARN, "Unable to resolve field " + field);
+      TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving field " + field.getName());
+      if (!resolveField(branch, unresolvedType, field, typeParamLookup, nextEnumOrdinal, context)) {
+        // Already logged.
         return false;
       }
     }
@@ -1006,21 +1009,27 @@
     addModifierBits(jfield, mapBits(ASM_TO_SHARED_MODIFIERS, field.getAccess()));
 
     String signature = field.getSignature();
-    JType fieldType;
+    JType fieldJType;
     if (signature != null) {
       SignatureReader reader = new SignatureReader(signature);
       JType[] fieldTypeRef = new JType[1];
       reader.acceptType(new ResolveTypeSignature(
           context.resolver, logger, fieldTypeRef, typeParamLookup, null));
-      fieldType = fieldTypeRef[0];
-
+      fieldJType = fieldTypeRef[0];
+      if (fieldJType == null) {
+        logger.log(TreeLogger.ERROR, "Unable to resolve type in field signature " + signature);
+        return false;
+      }
     } else {
-      fieldType = resolveType(Type.getType(field.getDesc()));
+      Type fieldType = Type.getType(field.getDesc());
+      fieldJType = resolveType(fieldType);
+      if (fieldJType == null) {
+        logger.log(TreeLogger.ERROR, "Unable to resolve type " + fieldType.getInternalName()
+            + " of field " + field.getName());
+        return false;
+      }
     }
-    if (fieldType == null) {
-      return false;
-    }
-    setFieldType(jfield, fieldType);
+    setFieldType(jfield, fieldJType);
     return true;
   }
 
@@ -1085,6 +1094,7 @@
           methodData.getArgNames(), methodData.hasActualArgNames(), context.allMethodArgs);
       reader.accept(methodResolver);
       if (!methodResolver.finish()) {
+        logger.log(TreeLogger.ERROR, "Failed to resolve.");
         return false;
       }
     } else {
@@ -1092,19 +1102,23 @@
         Type returnType = Type.getReturnType(methodData.getDesc());
         JType returnJType = resolveType(returnType);
         if (returnJType == null) {
+          logger.log(TreeLogger.ERROR,
+              "Unable to resolve return type " + returnType.getInternalName());
           return false;
         }
         setReturnType(method, returnJType);
       }
 
       if (!resolveParameters(logger, method, methodData, context)) {
+        // Already logged.
         return false;
       }
     }
     // The signature might not actually include the exceptions if they don't
     // include a type variable, so resolveThrows is always used (it does
     // nothing if there are already exceptions defined)
-    if (!resolveThrows(method, methodData)) {
+    if (!resolveThrows(logger, method, methodData)) {
+      // Already logged.
       return false;
     }
     typeParamLookup.popScope();
@@ -1142,8 +1156,11 @@
     }
     List<CollectAnnotationData>[] paramAnnot = methodData.getArgAnnotations();
     for (int i = 0; i < argTypes.length; ++i) {
-      JType argType = resolveType(argTypes[i]);
-      if (argType == null) {
+      Type argType = argTypes[i];
+      JType argJType = resolveType(argType);
+      if (argJType == null) {
+        logger.log(TreeLogger.ERROR, "Unable to resolve type " + argType.getInternalName()
+            + " of argument " + methodData.getArgNames()[i]);
         return false;
       }
       // Try to resolve annotations, ignore any that fail.
@@ -1151,19 +1168,23 @@
           new HashMap<Class<? extends Annotation>, Annotation>();
       resolveAnnotations(logger, paramAnnot[i], declaredAnnotations);
 
-      newParameter(method, argType, argNames[i], declaredAnnotations, argNamesAreReal);
+      newParameter(method, argJType, argNames[i], declaredAnnotations, argNamesAreReal);
     }
     return true;
   }
 
-  private boolean resolveThrows(JAbstractMethod method, CollectMethodData methodData) {
+  private boolean resolveThrows(TreeLogger logger, JAbstractMethod method,
+      CollectMethodData methodData) {
     if (method.getThrows().length == 0) {
       for (String exceptionName : methodData.getExceptions()) {
-        JType exceptionType = resolveType(Type.getObjectType(exceptionName));
-        if (exceptionType == null) {
+        Type exceptionType = Type.getObjectType(exceptionName);
+        JType exceptionJType = resolveType(exceptionType);
+        if (exceptionJType == null) {
+          logger.log(TreeLogger.ERROR,
+              "Unable to resolve type " + exceptionType.getInternalName() + " of thrown exception");
           return false;
         }
-        addThrows(method, (JClassType) exceptionType);
+        addThrows(method, (JClassType) exceptionJType);
       }
     }
     return true;
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
index 6fe5512..e0e7e25 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dev.util.DiskCache;
 import com.google.gwt.dev.util.DiskCacheToken;
 import com.google.gwt.dev.util.StringInterner;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
@@ -87,7 +88,7 @@
   /**
    * For mock construction in tests.
    */
-  // VisibleForTesting
+  @VisibleForTesting
   protected CompiledClass(CompiledClass enclosingClass,
       String internalName, String sourceName) {
     this.enclosingClass = enclosingClass;
diff --git a/dev/core/src/com/google/gwt/dev/javac/typemodel/LibraryTypeOracle.java b/dev/core/src/com/google/gwt/dev/javac/typemodel/LibraryTypeOracle.java
index a3d8fe7..098b77f 100644
--- a/dev/core/src/com/google/gwt/dev/javac/typemodel/LibraryTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/javac/typemodel/LibraryTypeOracle.java
@@ -26,6 +26,20 @@
  */
 public class LibraryTypeOracle extends TypeOracle {
 
+  /**
+   * An exception indicating that the Library flavor of TypeOracle does not support the requested
+   * behavior. This runtime exception can be thrown during LibraryTypeOracle access (from
+   * Generators) and is expected to be caught in the incremental Generator execution framework in
+   * RuleGenerateWith. It would be an error for this exception to ever become visible beyond those
+   * bounds.
+   */
+  public static class UnsupportedTypeOracleAccess extends RuntimeException {
+
+    public UnsupportedTypeOracleAccess(String message) {
+      super(message);
+    }
+  }
+
   private boolean allLoaded;
   private CompilerContext compilerContext;
   private CompilationUnitTypeOracleUpdater typeOracleUpdater;
@@ -53,7 +67,7 @@
 
   @Override
   public JPackage findPackage(String pkgName) {
-    throw new UnsupportedOperationException("Packages can't be lazily loaded from libraries.");
+    throw new UnsupportedTypeOracleAccess("Packages can't be lazily loaded from libraries.");
   }
 
   @Override
@@ -87,12 +101,12 @@
 
   @Override
   public JPackage getPackage(String pkgName) throws NotFoundException {
-    throw new UnsupportedOperationException("Packages can't be lazily loaded from libraries.");
+    throw new UnsupportedTypeOracleAccess("Packages can't be lazily loaded from libraries.");
   }
 
   @Override
   public JPackage[] getPackages() {
-    throw new UnsupportedOperationException("Packages can't be lazily loaded from libraries.");
+    throw new UnsupportedTypeOracleAccess("Packages can't be lazily loaded from libraries.");
   }
 
   public CompilationUnitTypeOracleUpdater getTypeOracleUpdater() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
index 10a3a52..484e32c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -39,7 +39,8 @@
 import com.google.gwt.dev.util.arg.OptionSoycEnabled;
 import com.google.gwt.dev.util.arg.OptionSoycHtmlDisabled;
 import com.google.gwt.dev.util.arg.OptionStrict;
-import com.google.gwt.dev.util.arg.OptionStrictResources;
+import com.google.gwt.dev.util.arg.OptionStrictPublicResources;
+import com.google.gwt.dev.util.arg.OptionStrictSourceResources;
 
 /**
  * Controls options for the {@link JavaToJavaScriptCompiler}.
@@ -48,9 +49,9 @@
     OptionClusterSimilarFunctions, OptionDisableClassMetadata, OptionDisableCastChecking,
     OptionEnableAssertions, OptionInlineLiteralParameters, OptionOptimizeDataflow,
     OptionRunAsyncEnabled, OptionScriptStyle, OptionSoycEnabled, OptionSoycDetailed,
-    OptionJsonSoycEnabled,
-    OptionOptimizePrecompile, OptionOrdinalizeEnums, OptionRemoveDuplicateFunctions, OptionStrict,
-    OptionStrictResources, OptionSoycHtmlDisabled, OptionEnableClosureCompiler,
-    OptionFragmentsMerge, OptionFragmentCount, OptionSourceLevel,
-    OptionNamespace, OptionCheckedMode {
+    OptionJsonSoycEnabled, OptionOptimizePrecompile, OptionOrdinalizeEnums,
+    OptionRemoveDuplicateFunctions, OptionStrict, OptionStrictSourceResources,
+    OptionStrictPublicResources, OptionSoycHtmlDisabled, OptionEnableClosureCompiler,
+    OptionFragmentsMerge, OptionFragmentCount, OptionSourceLevel, OptionNamespace,
+    OptionCheckedMode {
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
index 8d7a0b9..113699c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -51,7 +51,8 @@
   private boolean soycExtra = false;
   private boolean soycHtmlDisabled = false;
   private boolean strict = false;
-  private boolean strictResources = false;
+  private boolean strictSourceResources = false;
+  private boolean strictPublicResources = false;
 
   public JJSOptionsImpl() {
   }
@@ -83,14 +84,20 @@
     setJsonSoycEnabled(other.isJsonSoycEnabled());
     setSoycHtmlDisabled(other.isSoycHtmlDisabled());
     setStrict(other.isStrict());
-    setEnforceStrictResources(other.enforceStrictResources());
+    setEnforceStrictSourceResources(other.enforceStrictSourceResources());
+    setEnforceStrictPublicResources(other.enforceStrictPublicResources());
     setSourceLevel(other.getSourceLevel());
     setNamespace(other.getNamespace());
   }
 
   @Override
-  public boolean enforceStrictResources() {
-    return strictResources;
+  public boolean enforceStrictSourceResources() {
+    return strictSourceResources;
+  }
+
+  @Override
+  public boolean enforceStrictPublicResources() {
+    return strictPublicResources;
   }
 
   @Override
@@ -235,8 +242,13 @@
   }
 
   @Override
-  public void setEnforceStrictResources(boolean strictResources) {
-    this.strictResources = strictResources;
+  public void setEnforceStrictSourceResources(boolean strictSourceResources) {
+    this.strictSourceResources = strictSourceResources;
+  }
+
+  @Override
+  public void setEnforceStrictPublicResources(boolean strictPublicResources) {
+    this.strictPublicResources = strictPublicResources;
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 2babe4b..ddcc783 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -41,6 +41,7 @@
 import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.EntryMethodHolderGenerator;
+import com.google.gwt.dev.cfg.LibraryGroup.CollidingCompilationUnitException;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.javac.CompilationProblemReporter;
 import com.google.gwt.dev.javac.CompilationState;
@@ -130,6 +131,7 @@
 import com.google.gwt.dev.util.Memory;
 import com.google.gwt.dev.util.Name.SourceName;
 import com.google.gwt.dev.util.Pair;
+import com.google.gwt.dev.util.TinyCompileSummary;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.OptionOptimize;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
@@ -137,6 +139,7 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.soyc.SoycDashboard;
 import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 
@@ -232,7 +235,7 @@
             new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());
 
         // TODO(stalcup): hide metrics gathering in a callback or subclass
-        if (logger.isLoggable(TreeLogger.INFO)) {
+        if (compilerContext.shouldCompileMonolithic() && logger.isLoggable(TreeLogger.INFO)) {
           logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "...");
         }
         printPermutationTrace(permutation);
@@ -904,6 +907,12 @@
      */
     private String buildEntryMethodHolder(StandardGeneratorContext context,
         Set<String> allRootTypes) throws UnableToCompleteException {
+      // If there are no entry points.
+      if (entryPointTypeNames.length == 0) {
+        // Then there's no need to generate an EntryMethodHolder class to launch them.
+        return null;
+      }
+
       EntryMethodHolderGenerator entryMethodHolderGenerator = new EntryMethodHolderGenerator();
       String entryMethodHolderTypeName =
           entryMethodHolderGenerator.generate(logger, context, module.getCanonicalName());
@@ -929,6 +938,19 @@
       if (options.isSoycEnabled() || options.isJsonSoycEnabled()) {
         SourceInfoCorrelator.exec(jprogram);
       }
+
+      // Gathers simple metrics that can highlight overly-large modules in an incremental compile.
+      TinyCompileSummary tinyCompileSummary = compilerContext.getTinyCompileSummary();
+      tinyCompileSummary.setTypesForGeneratorsCount(
+          rpo.getGeneratorContext().getTypeOracle().getTypes().length);
+      tinyCompileSummary.setTypesForAstCount(jprogram.getDeclaredTypes().size());
+      tinyCompileSummary.setStaticSourceFilesCount(compilationState.getStaticSourceCount());
+      tinyCompileSummary.setGeneratedSourceFilesCount(compilationState.getGeneratedSourceCount());
+      tinyCompileSummary.setCachedStaticSourceFilesCount(
+          compilationState.getCachedStaticSourceCount());
+      tinyCompileSummary.setCachedGeneratedSourceFilesCount(
+          compilationState.getCachedGeneratedSourceCount());
+
       // Free up memory.
       rpo.clear();
       jprogram.typeOracle.computeBeforeAST();
@@ -1101,16 +1123,30 @@
 
     private void unifyJavaAst(Set<String> allRootTypes, String entryMethodHolderTypeName)
         throws UnableToCompleteException {
-      UnifyAst unifyAst = new UnifyAst(logger, compilerContext, jprogram, jsProgram, rpo);
+      UnifyAst unifyAst;
+      try {
+        unifyAst = new UnifyAst(logger, compilerContext, jprogram, jsProgram, rpo);
+      } catch (CollidingCompilationUnitException e) {
+        logger.log(TreeLogger.ERROR, e.getMessage());
+        throw new UnableToCompleteException();
+      }
       // Makes JProgram aware of these types so they can be accessed via index.
       unifyAst.addRootTypes(allRootTypes);
       // Must synthesize entryPoint.onModuleLoad() calls because some EntryPoint classes are
       // private.
-      synthesizeEntryMethodHolderInit(unifyAst, entryMethodHolderTypeName);
+      if (entryMethodHolderTypeName != null) {
+        // Only synthesize the init method in the EntryMethodHolder class, if there is an
+        // EntryMethodHolder class.
+        synthesizeEntryMethodHolderInit(unifyAst, entryMethodHolderTypeName);
+      }
       // Ensures that unification traversal starts from these methods.
       jprogram.addEntryMethod(jprogram.getIndexedMethod("Impl.registerEntry"));
-      jprogram.addEntryMethod(jprogram.getIndexedMethod(
-          SourceName.getShortClassName(entryMethodHolderTypeName) + ".init"));
+      if (entryMethodHolderTypeName != null) {
+        // Only register the init method in the EntryMethodHolder class as an entry method, if there
+        // is an EntryMethodHolder class.
+        jprogram.addEntryMethod(jprogram.getIndexedMethod(
+            SourceName.getShortClassName(entryMethodHolderTypeName) + ".init"));
+      }
       unifyAst.exec();
     }
   }
@@ -1213,7 +1249,7 @@
 
   protected final PrecompileTaskOptions options;
 
-  // VisibleForTesting
+  @VisibleForTesting
   JProgram jprogram;
 
   public JavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
index 020f3ff..1124453 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
@@ -29,6 +29,7 @@
 import com.google.gwt.dev.cfg.RuleReplaceWithFallback;
 import com.google.gwt.dev.cfg.Rules;
 import com.google.gwt.dev.cfg.RuntimeRebindRegistratorGenerator;
+import com.google.gwt.dev.cfg.RuntimeRebindRuleGenerator;
 import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.javac.CompiledClass;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
@@ -60,10 +61,7 @@
 import com.google.gwt.dev.resource.impl.FileResource;
 import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.dev.util.Pair;
-import com.google.gwt.thirdparty.guava.common.base.Predicate;
-import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
-import com.google.gwt.thirdparty.guava.common.collect.Multimap;
-import com.google.gwt.thirdparty.guava.common.collect.SetMultimap;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.io.File;
@@ -86,7 +84,7 @@
  */
 public class LibraryJavaToJavaScriptCompiler extends JavaToJavaScriptCompiler {
 
-  // VisibleForTesting
+  @VisibleForTesting
   class LibraryPermutationCompiler extends PermutationCompiler {
 
     public LibraryPermutationCompiler(Permutation permutation) {
@@ -139,14 +137,9 @@
     }
   }
 
-  // VisibleForTesting
+  @VisibleForTesting
   class LibraryPrecompiler extends Precompiler {
 
-    private Set<String> badRebindCombinations = Sets.newHashSet();
-    private SetMultimap<String, String> generatorNamesByPreviouslyReboundTypeName =
-        HashMultimap.create();
-    private Set<String> previouslyReboundTypeNames = Sets.newHashSet();
-
     public LibraryPrecompiler(RebindPermutationOracle rpo, String[] entryPointTypeNames) {
       super(rpo, entryPointTypeNames);
     }
@@ -175,7 +168,7 @@
       jprogram = new JProgram(false);
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     protected JDeclaredType ensureFullTypeLoaded(JDeclaredType type) {
       return findTypeBySourceName(BinaryName.toSourceName(type.getName()));
     }
@@ -214,7 +207,7 @@
       return null;
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     protected Set<JDeclaredType> gatherReboundTypes(RebindPermutationOracle rpo) {
       Collection<CompilationUnit> compilationUnits =
           rpo.getCompilationState().getCompilationUnits();
@@ -237,7 +230,7 @@
       return rpo.getGeneratorContext();
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     protected Set<String> getTypeNames(Set<JDeclaredType> types) {
       Set<String> typeNames = Sets.newHashSet();
       for (JDeclaredType type : types) {
@@ -249,48 +242,23 @@
     /**
      * Runs a particular generator on the provided set of rebound types. Takes care to guard against
      * duplicate work during reruns as generation approaches a fixed point.
-     *
-     * @return whether a fixed point was reached.
      */
-    // VisibleForTesting
-    protected boolean runGenerator(RuleGenerateWith generatorRule, Set<String> reboundTypeNames)
+    @VisibleForTesting
+    protected void runGenerator(RuleGenerateWith generatorRule, Set<String> reboundTypeNames)
         throws UnableToCompleteException {
-      boolean fixedPoint = true;
-      StandardGeneratorContext generatorContext = getGeneratorContext();
-      removePreviouslyReboundCombinations(generatorRule.getName(), reboundTypeNames);
-      reboundTypeNames.removeAll(previouslyReboundTypeNames);
-
       for (String reboundTypeName : reboundTypeNames) {
-        if (badRebindCombinations.contains(generatorRule.getName() + "-" + reboundTypeName)) {
-          continue;
-        }
-        generatorNamesByPreviouslyReboundTypeName.put(reboundTypeName, generatorRule.getName());
-        reboundTypeName = reboundTypeName.replace("$", ".");
-        generatorRule.generate(logger, module.getProperties(), generatorContext, reboundTypeName);
-
-        if (generatorContext.isDirty()) {
-          fixedPoint = false;
-          previouslyReboundTypeNames.add(reboundTypeName);
-          // Ensure that cascading generations rerun properly.
-          for (String generatedTypeName : generatorContext.getGeneratedUnitMap().keySet()) {
-            generatorNamesByPreviouslyReboundTypeName.removeAll(generatedTypeName);
-          }
-          generatorContext.finish(logger);
-        } else {
-          badRebindCombinations.add(generatorRule.getName() + "-" + reboundTypeName);
-        }
+        generatorRule.generate(logger, module.getProperties(), getGeneratorContext(),
+            BinaryName.toSourceName(reboundTypeName));
       }
-
-      return fixedPoint;
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     protected void runGeneratorsToFixedPoint(RebindPermutationOracle rpo)
         throws UnableToCompleteException {
       boolean fixedPoint;
       do {
         compilerContext.getLibraryWriter()
-            .setReboundTypeSourceNames(getTypeNames(gatherReboundTypes(rpo)));
+            .markReboundTypesProcessed(getTypeNames(gatherReboundTypes(rpo)));
 
         fixedPoint = runGenerators();
       } while (!fixedPoint);
@@ -307,7 +275,7 @@
       }
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     void buildFallbackRuntimeRebindRules(Set<JDeclaredType> reboundTypes)
         throws UnableToCompleteException {
       // Create fallback rebinds.
@@ -328,7 +296,7 @@
       }
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     void buildPropertyProviderRegistrator(Set<String> allRootTypes,
         SortedSet<BindingProperty> bindingProperties,
         SortedSet<ConfigurationProperty> configurationProperties) throws UnableToCompleteException {
@@ -348,8 +316,14 @@
       generatorContext.finish(logger);
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     void buildRuntimeRebindRegistrator(Set<String> allRootTypes) throws UnableToCompleteException {
+      // If no runtime rebind rules were created for this library.
+      if (RuntimeRebindRuleGenerator.RUNTIME_REBIND_RULE_SOURCES_BY_SHORT_NAME.isEmpty()) {
+        // Then there's no need to generate a registrator to attach them to the runtime registry.
+        return;
+      }
+
       RuntimeRebindRegistratorGenerator runtimeRebindRegistratorGenerator =
           new RuntimeRebindRegistratorGenerator();
       StandardGeneratorContext generatorContext = getGeneratorContext();
@@ -366,7 +340,7 @@
       generatorContext.finish(logger);
     }
 
-    // VisibleForTesting
+    @VisibleForTesting
     void buildSimpleRuntimeRebindRules(Rules rules) throws UnableToCompleteException {
       // Create rebinders for rules specified in the module.
       Iterator<Rule> iterator = rules.iterator();
@@ -379,35 +353,6 @@
       }
     }
 
-    private boolean relevantPropertiesHaveChanged(RuleGenerateWith generatorRule) {
-      // Gather binding and configuration property values that have been changed in the part of
-      // the library dependency tree on which this generator has not yet run.
-      Multimap<String, String> newConfigurationPropertyValues =
-          compilerContext.gatherNewConfigurationPropertyValuesForGenerator(generatorRule.getName());
-      Multimap<String, String> newBindingPropertyValues =
-          compilerContext.gatherNewBindingPropertyValuesForGenerator(generatorRule.getName());
-
-      return generatorRule.caresAboutProperties(newConfigurationPropertyValues.keySet())
-          || generatorRule.caresAboutProperties(newBindingPropertyValues.keySet());
-    }
-
-    /**
-     * Generator output can create opportunities for further generator execution, so runGenerators()
-     * is repeated to a fixed point. But previously handled generator/reboundType pairs should be
-     * ignored.
-     */
-    private void removePreviouslyReboundCombinations(
-        final String generatorName, Set<String> newReboundTypeNames) {
-      newReboundTypeNames.removeAll(
-          Sets.newHashSet(Sets.filter(newReboundTypeNames, new Predicate<String>() {
-            @Override
-            public boolean apply(String newReboundTypeName) {
-              return generatorNamesByPreviouslyReboundTypeName.containsEntry(
-                  newReboundTypeName, generatorName);
-            }
-          })));
-    }
-
     /**
      * Figures out which generators should run based on the current state and runs them. Generator
      * execution can create new opportunities for further generator execution so this function
@@ -416,10 +361,11 @@
      * Returns whether a fixed point was reached.
      */
     private boolean runGenerators() throws UnableToCompleteException {
-      boolean fixedPoint = true;
       boolean globalCompile = compilerContext.getOptions().shouldLink();
       Set<Rule> generatorRules = Sets.newHashSet(module.getGeneratorRules());
 
+      TreeLogger branch = logger.branch(TreeLogger.SPAM, "running generators");
+
       for (Rule rule : generatorRules) {
         RuleGenerateWith generatorRule = (RuleGenerateWith) rule;
         String generatorName = generatorRule.getName();
@@ -428,27 +374,53 @@
           // Type unstable generators can only be safely run in the global phase.
           // TODO(stalcup): modify type unstable generators such that their output is no longer
           // unstable.
+          branch.log(TreeLogger.SPAM,
+              "skipping generator " + generatorName + " since it can only run in the global phase");
           continue;
         }
 
-        // Run generator for new rebound types.
-        Set<String> newReboundTypeNames =
-            compilerContext.gatherNewReboundTypeNamesForGenerator(generatorName);
-        fixedPoint &= runGenerator(generatorRule, newReboundTypeNames);
-
-        // If the content of generator output varies when some relevant properties change and some
-        // relevant properties have changed.
-        if (relevantPropertiesHaveChanged(generatorRule)) {
-          // Rerun the generator on old rebound types to replace old stale output.
-          Set<String> oldReboundTypeNames =
-              compilerContext.gatherOldReboundTypeNamesForGenerator(generatorName);
-          fixedPoint &= runGenerator(generatorRule, oldReboundTypeNames);
+        if (!generatorRule.relevantPropertiesAreFinal(module.getProperties(),
+            options.getFinalProperties())) {
+          // Some property(s) that this generator cares about have not yet reached their final
+          // value. Running the generator now would be wasted effort as it would just need to be run
+          // again later anyway.
+          branch.log(TreeLogger.SPAM, "skipping generator " + generatorName
+              + " since properties it cares about have not reached their final values.");
+          continue;
         }
 
-        compilerContext.getLibraryWriter().addRanGeneratorName(generatorName);
+        Set<String> reboundTypes = Sets.newHashSet(compilerContext.getReboundTypeSourceNames());
+        Set<String> processedReboundTypeSourceNamesForGenerator =
+            compilerContext.getProcessedReboundTypeSourceNames(generatorName);
+
+        Set<String> unprocessedReboundTypeSourceNames = Sets.newHashSet(reboundTypes);
+        unprocessedReboundTypeSourceNames.removeAll(processedReboundTypeSourceNamesForGenerator);
+        if (unprocessedReboundTypeSourceNames.isEmpty()) {
+          // All the requested rebound types have already been processed by this generator.
+          branch.log(TreeLogger.SPAM, "skipping generator " + generatorName
+              + " since it has already processed all requested rebound types.");
+          continue;
+        }
+
+        branch.log(TreeLogger.SPAM, "running generator " + generatorName + " on "
+            + unprocessedReboundTypeSourceNames.size() + " not yet processed rebound types");
+        runGenerator(generatorRule, unprocessedReboundTypeSourceNames);
+
+        // Marks the previously unprocessed types as processed.
+        for (String unprocessedReboundTypeSourceName : unprocessedReboundTypeSourceNames) {
+          compilerContext.getLibraryWriter().markReboundTypeProcessed(
+              unprocessedReboundTypeSourceName, generatorName);
+        }
       }
 
-      return fixedPoint;
+      // If there is output.
+      if (getGeneratorContext().isDirty()) {
+        // Compile and assimilate it.
+        getGeneratorContext().finish(logger);
+        return false;
+      }
+
+      return true;
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
index 9358401..07719cb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -38,10 +38,8 @@
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.impl.MakeCallsStatic.CreateStaticImplsVisitor;
 import com.google.gwt.dev.jjs.impl.MakeCallsStatic.StaticCallConverter;
-import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
-import com.google.gwt.thirdparty.guava.common.collect.Multiset;
 
 import java.util.List;
 import java.util.Map;
@@ -250,11 +248,6 @@
    * Contains the Cast.instanceofArray method.
    */
   private final JMethod isJavaArray;
-  /**
-   * Key is the method signature, value is the number of unique instances with
-   * the same signature.
-   */
-  private Multiset<String> jsoMethodInstances = HashMultiset.create();
 
   /**
    * Contains the set of devirtualizing methods that replace polymorphic calls
@@ -274,8 +267,7 @@
   private JMethod createDevirtualMethodFor(JMethod method, JDeclaredType inClass) {
     SourceInfo sourceInfo = method.getSourceInfo().makeChild();
 
-    int methodCount = jsoMethodInstances.add(method.getSignature(),1);
-    String  prefix = method.getName() + (methodCount == 0 ? "" : methodCount);
+    String prefix = computeEscapedSignature(method.getSignature());
     JMethod devirtualMethod = new JMethod(sourceInfo, prefix + "__devirtual$",
         inClass, method.getType(), false, true, true, AccessModifier.PUBLIC);
     devirtualMethod.setBody(new JMethodBody(sourceInfo));
@@ -296,6 +288,15 @@
     return devirtualMethod;
   }
 
+  /**
+   * A normal method signature contains characters that are not valid in a method name. If you want
+   * to construct a method name based on an existing method signature then those characters need to
+   * be escaped.
+   */
+  private static String computeEscapedSignature(String methodSignature) {
+    return methodSignature.replaceAll("[\\<\\>\\(\\)\\;\\/\\[]", "_");
+  }
+
   private Devirtualizer(JProgram program) {
     this.program = program;
     this.isJavaStringMethod = program.getIndexedMethod("Cast.isJavaString");
@@ -316,7 +317,6 @@
 
     RewriteVirtualDispatches rewriter = new RewriteVirtualDispatches();
     rewriter.accept(program);
-    assert (rewriter.didChange());
   }
 
   /**
@@ -341,7 +341,7 @@
    */
   private static JExpression constructMinimalCondition(JMethod checkMethod, JVariableRef target,
       JMethodCall trueDispatch, JExpression falseDispatch) {
-    // TODO(rluble): Maybe we should emit sligthly different code in checked mode, so that if
+    // TODO(rluble): Maybe we should emit slightly different code in checked mode, so that if
     // no condition is met an exception would be thrown rather than cascading.
     if (falseDispatch == null && trueDispatch == null) {
       return null;
@@ -494,8 +494,14 @@
       // It is an interface implemented by String or arrays, place it in Object.
       devirtualMethodEnclosingClass = program.getTypeJavaLangObject();
     }
-    // The class where the method is being place is in the current compile.
-    assert !program.isReferenceOnly(devirtualMethodEnclosingClass);
+    // Devirtualization of external methods stays external and devirtualization of internal methods
+    // stays internal.
+    assert program.isReferenceOnly(devirtualMethodEnclosingClass)
+        == program.isReferenceOnly(method.getEnclosingType());
+    // TODO(stalcup): devirtualization is modifying both internal and external types. Really
+    // external types should never be modified. Change the point at which types are saved into
+    // libraries to be after normalization has occurred, so that no further modification is
+    // necessary when loading external types.
     JMethod devirtualMethod = createDevirtualMethodFor(method, devirtualMethodEnclosingClass);
 
     /**
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 fa17743..72e6d84 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
@@ -121,6 +121,8 @@
 // TODO: SOYC correlations.
 // TODO(stalcup): perform only binary name based lookups so that libraries
 // don't need to index compilation units by both source and binary name
+// TODO(stalcup): shrink the translate/flowInto graph for reference only types to eliminate
+// unnecessary loading of types and increase performance.
 public class UnifyAst {
 
   /**
@@ -453,7 +455,7 @@
 
       ArrayList<JExpression> instantiationExpressions = new ArrayList<JExpression>(answers.size());
       for (String answer : answers) {
-        JDeclaredType answerType = findType(answer, sourceNameBasedTypeLocator);
+        JDeclaredType answerType = internalFindType(answer, sourceNameBasedTypeLocator);
         if (answerType == null) {
           error(gwtCreateCall, "Rebind result '" + answer + "' could not be found");
           return null;
@@ -579,6 +581,13 @@
   private final CompilationState compilationState;
   private final Map<String, CompiledClass> compiledClassesByInternalName;
   private final Map<String, CompiledClass> compiledClassesBySourceName;
+  /**
+   * JVisitor interferes with any exceptions thrown inside of a visitor traversal call tree so any
+   * time UnifyAst wants to log an error and end operation care it should be done by manually
+   * logging an error line and setting errorsFound to true. Adequate checking is already in place to
+   * interpret this as ending further exploration and errorsFound = true is already being converted
+   * to an UnableToCompleteException at the UnifyAst public function boundaries
+   */
   private boolean errorsFound = false;
   private final Set<CompilationUnit> failedUnits = new IdentityHashSet<CompilationUnit>();
   private final Map<String, JField> fieldMap = new HashMap<String, JField>();
@@ -633,9 +642,10 @@
 
   public void addRootTypes(Collection<String> sourceTypeNames) throws UnableToCompleteException {
     for (String sourceTypeName : sourceTypeNames) {
-      findType(sourceTypeName, sourceNameBasedTypeLocator);
+      internalFindType(sourceTypeName, sourceNameBasedTypeLocator);
     }
     if (errorsFound) {
+      // Already logged.
       throw new UnableToCompleteException();
     }
   }
@@ -647,7 +657,7 @@
   public void buildEverything() throws UnableToCompleteException {
     for (String internalName : compiledClassesByInternalName.keySet()) {
       String typeName = InternalName.toBinaryName(internalName);
-      findType(typeName, binaryNameBasedTypeLocator);
+      internalFindType(typeName, binaryNameBasedTypeLocator);
     }
 
     for (JDeclaredType type : program.getDeclaredTypes()) {
@@ -695,7 +705,7 @@
       // Trace execution from all types supplied as source and resolve references.
       Set<String> internalNames = ImmutableSet.copyOf(compiledClassesByInternalName.keySet());
       for (String internalName : internalNames) {
-        JDeclaredType type = findType(internalName, internalNameBasedTypeLocator);
+        JDeclaredType type = internalFindType(internalName, internalNameBasedTypeLocator);
         instantiate(type);
         for (JField field : type.getFields()) {
           flowInto(field);
@@ -718,9 +728,6 @@
     mapApi(program.getTypeJavaLangString());
     flowInto(methodMap.get("java.lang.String.valueOf(C)Ljava/lang/String;"));
 
-    // Additional pre-optimization code gen.
-    // TODO: roll these into this class?
-
     // EnumNameObfuscator
     flowInto(program.getIndexedMethod("Enum.obfuscatedName"));
 
@@ -755,6 +762,7 @@
     }
     computeOverrides();
     if (errorsFound) {
+      // Already logged.
       throw new UnableToCompleteException();
     }
   }
@@ -807,12 +815,8 @@
       program.addType(referenceOnlyType);
       program.addReferenceOnlyType(referenceOnlyType);
     }
-    // Flow into the signature of each contained method, so that method call references from
-    // inside this library to functions on these external types can resolve.
-    for (JDeclaredType t : types) {
-      for (JMethod method : t.getMethods()) {
-        flowInto(method);
-      }
+    for (JDeclaredType referenceOnlyType : types) {
+      resolveType(referenceOnlyType);
     }
   }
 
@@ -1214,7 +1218,18 @@
     type.resolve(resolvedInterfaces, resolvedRescues);
   }
 
-  public JDeclaredType findType(String typeName, NameBasedTypeLocator nameBasedTypeLocator) {
+  public JDeclaredType findType(String typeName, NameBasedTypeLocator nameBasedTypeLocator)
+      throws UnableToCompleteException {
+    JDeclaredType type = internalFindType(typeName, nameBasedTypeLocator);
+    if (errorsFound) {
+      // Already logged.
+      throw new UnableToCompleteException();
+    }
+    return type;
+  }
+
+  private JDeclaredType internalFindType(String typeName,
+      NameBasedTypeLocator nameBasedTypeLocator) {
     if (nameBasedTypeLocator.resolvedTypeIsAvailable(typeName)) {
       return nameBasedTypeLocator.getResolvedType(typeName);
     }
@@ -1225,9 +1240,10 @@
     }
 
     if (compilerContext.shouldCompileMonolithic()) {
-      throw new NoClassDefFoundError(String.format(
-          "Could not find %s in types compiled from source. "
+      logger.log(TreeLogger.ERROR, String.format("Could not find %s in types compiled from source. "
           + "It was either unavailable or failed to compile.", typeName));
+      errorsFound = true;
+      return null;
     }
 
     if (nameBasedTypeLocator.libraryCompilationUnitIsAvailable(typeName)) {
@@ -1235,10 +1251,12 @@
       return nameBasedTypeLocator.getResolvedType(typeName);
     }
 
-    throw new NoClassDefFoundError(String.format(
+    logger.log(TreeLogger.ERROR, String.format(
         "Could not find %s in types compiled from source or in provided dependency libraries. "
         + "Either the source file was unavailable, failed to compile or there is a missing "
         + "dependency.", typeName));
+    errorsFound = true;
+    return null;
   }
 
   private void staticInitialize(JDeclaredType type) {
@@ -1280,14 +1298,12 @@
     if (!type.isExternal()) {
       return type;
     }
-
     String typeName = type.getName();
-    JDeclaredType newType = findType(typeName, binaryNameBasedTypeLocator);
+    JDeclaredType newType = internalFindType(typeName, binaryNameBasedTypeLocator);
     if (newType == null) {
       assert errorsFound;
       return type;
     }
-
     assert !newType.isExternal();
     return newType;
   }
diff --git a/dev/core/src/com/google/gwt/dev/url/CloseableJarHandler.java b/dev/core/src/com/google/gwt/dev/url/CloseableJarHandler.java
new file mode 100644
index 0000000..4d76854
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/url/CloseableJarHandler.java
@@ -0,0 +1,295 @@
+/*
+ * 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.url;
+
+import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Opens connections to Zip files when requested and gathers them in a list for later closing.
+ * <p>
+ * This closing functionality makes it possible to run untrusted plugin code (for example
+ * Generators) and still guarantee that there will be no accidental attempts to read from old and
+ * deleted Zip files using stale InputStreams.
+ */
+@SuppressWarnings("restriction")
+public class CloseableJarHandler extends sun.net.www.protocol.jar.Handler {
+
+  /**
+   * Passes through all URLConnection access but collects all created InputStreams.
+   */
+  private class CloseableUrlConnection extends URLConnection {
+
+    private JarURLConnection jarUrlConnection;
+
+    public CloseableUrlConnection(URL jarUrl, JarURLConnection jarUrlConnection) {
+      super(jarUrl);
+      this.jarUrlConnection = jarUrlConnection;
+    }
+
+    @Override
+    public void addRequestProperty(String key, String value) {
+      jarUrlConnection.addRequestProperty(key, value);
+    }
+
+    @Override
+    public void connect() throws IOException {
+      jarUrlConnection.connect();
+    }
+
+    @Override
+    public boolean getAllowUserInteraction() {
+      return jarUrlConnection.getAllowUserInteraction();
+    }
+
+    @Override
+    public int getConnectTimeout() {
+      return jarUrlConnection.getConnectTimeout();
+    }
+
+    @Override
+    public Object getContent() throws IOException {
+      return jarUrlConnection.getContent();
+    }
+
+    @Override
+    public Object getContent(Class[] classes) throws IOException {
+      return jarUrlConnection.getContent(classes);
+    }
+
+    @Override
+    public String getContentEncoding() {
+      return jarUrlConnection.getContentEncoding();
+    }
+
+    @Override
+    public int getContentLength() {
+      return jarUrlConnection.getContentLength();
+    }
+
+    @Override
+    public long getContentLengthLong() {
+      return jarUrlConnection.getContentLengthLong();
+    }
+
+    @Override
+    public String getContentType() {
+      return jarUrlConnection.getContentType();
+    }
+
+    @Override
+    public long getDate() {
+      return jarUrlConnection.getDate();
+    }
+
+    @Override
+    public boolean getDefaultUseCaches() {
+      return jarUrlConnection.getDefaultUseCaches();
+    }
+
+    @Override
+    public boolean getDoInput() {
+      return jarUrlConnection.getDoInput();
+    }
+
+    @Override
+    public boolean getDoOutput() {
+      return jarUrlConnection.getDoOutput();
+    }
+
+    @Override
+    public long getExpiration() {
+      return jarUrlConnection.getExpiration();
+    }
+
+    @Override
+    public String getHeaderField(int n) {
+      return jarUrlConnection.getHeaderField(n);
+    }
+
+    @Override
+    public String getHeaderField(String name) {
+      return jarUrlConnection.getHeaderField(name);
+    }
+
+    @Override
+    public long getHeaderFieldDate(String name, long defaultValue) {
+      return jarUrlConnection.getHeaderFieldDate(name, defaultValue);
+    }
+
+    @Override
+    public int getHeaderFieldInt(String name, int defaultValue) {
+      return jarUrlConnection.getHeaderFieldInt(name, defaultValue);
+    }
+
+    @Override
+    public String getHeaderFieldKey(int n) {
+      return jarUrlConnection.getHeaderFieldKey(n);
+    }
+
+    @Override
+    public long getHeaderFieldLong(String name, long defaultValue) {
+      return jarUrlConnection.getHeaderFieldLong(name, defaultValue);
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaderFields() {
+      return jarUrlConnection.getHeaderFields();
+    }
+
+    @Override
+    public long getIfModifiedSince() {
+      return jarUrlConnection.getIfModifiedSince();
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+      InputStream inputStream = jarUrlConnection.getInputStream();
+      URL jarFileURL = jarUrlConnection.getJarFileURL();
+      inputStreamsByJarFilePath.put(jarFileURL.getFile(), inputStream);
+      return inputStream;
+    }
+
+    @Override
+    public long getLastModified() {
+      return jarUrlConnection.getLastModified();
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+      return jarUrlConnection.getOutputStream();
+    }
+
+    @Override
+    public Permission getPermission() throws IOException {
+      return jarUrlConnection.getPermission();
+    }
+
+    @Override
+    public int getReadTimeout() {
+      return jarUrlConnection.getReadTimeout();
+    }
+
+    @Override
+    public Map<String, List<String>> getRequestProperties() {
+      return jarUrlConnection.getRequestProperties();
+    }
+
+    @Override
+    public String getRequestProperty(String key) {
+      return jarUrlConnection.getRequestProperty(key);
+    }
+
+    @Override
+    public URL getURL() {
+      return jarUrlConnection.getURL();
+    }
+
+    @Override
+    public boolean getUseCaches() {
+      return jarUrlConnection.getUseCaches();
+    }
+
+    @Override
+    public void setAllowUserInteraction(boolean allowuserinteraction) {
+      jarUrlConnection.setAllowUserInteraction(allowuserinteraction);
+    }
+
+    @Override
+    public void setConnectTimeout(int timeout) {
+      jarUrlConnection.setConnectTimeout(timeout);
+    }
+
+    @Override
+    public void setDefaultUseCaches(boolean defaultusecaches) {
+      jarUrlConnection.setDefaultUseCaches(defaultusecaches);
+    }
+
+    @Override
+    public void setDoInput(boolean doinput) {
+      jarUrlConnection.setDoInput(doinput);
+    }
+
+    @Override
+    public void setDoOutput(boolean dooutput) {
+      jarUrlConnection.setDoOutput(dooutput);
+    }
+
+    @Override
+    public void setIfModifiedSince(long ifmodifiedsince) {
+      jarUrlConnection.setIfModifiedSince(ifmodifiedsince);
+    }
+
+    @Override
+    public void setReadTimeout(int timeout) {
+      jarUrlConnection.setReadTimeout(timeout);
+    }
+
+    @Override
+    public void setRequestProperty(String key, String value) {
+      jarUrlConnection.setRequestProperty(key, value);
+    }
+
+    @Override
+    public void setUseCaches(boolean usecaches) {
+      jarUrlConnection.setUseCaches(usecaches);
+    }
+
+    @Override
+    public String toString() {
+      return jarUrlConnection.toString();
+    }
+  }
+
+  private Multimap<String, InputStream> inputStreamsByJarFilePath = HashMultimap.create();
+
+  /**
+   * Closes all InputStreams that were created by the URL system that point at the given Jar file.
+   */
+  public void closeStreams(String jarFilePath) throws IOException {
+    Collection<InputStream> inputStreams = inputStreamsByJarFilePath.get(jarFilePath);
+    if (inputStreams == null) {
+      return;
+    }
+
+    for (InputStream inputStream : inputStreams) {
+      inputStream.close();
+    }
+    inputStreamsByJarFilePath.removeAll(jarFilePath);
+  }
+
+  @Override
+  protected URLConnection openConnection(URL jarUrl) throws IOException {
+    // Let the Jar system do the heavy lifting of opening the connection.
+    JarURLConnection jarUrlConnection = (JarURLConnection) super.openConnection(jarUrl);
+    // Ensures that when all connections have been closed the cached Zip file references will be
+    // cleared.
+    jarUrlConnection.setUseCaches(false);
+
+    // Wrap the jar url connection in a way that will collect all created InputStreams so that they
+    // can be closed.
+    return new CloseableUrlConnection(jarUrl, jarUrlConnection);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/url/CloseableJarHandlerFactory.java b/dev/core/src/com/google/gwt/dev/url/CloseableJarHandlerFactory.java
new file mode 100644
index 0000000..90c11ea
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/url/CloseableJarHandlerFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * A factory that constructs URL Stream Handlers and extends Jar protocol handling by making it
+ * possible to close all InputStreams for a given Zip file.
+ * <p>
+ * This closing functionality makes it possible to run untrusted plugin code (for example
+ * Generators) and still guarantee that there will be no accidental attempts to read from old and
+ * deleted Zip files using stale InputStreams.
+ */
+public class CloseableJarHandlerFactory implements URLStreamHandlerFactory {
+
+  /**
+   * Takes over the URL connection opening system so that Zip file connections can be gathered and
+   * then closed before Zip files are replaced on disk. This avoids a JVM crash on Linux that can
+   * occur if a third party plugin (ie. Generator) fails to close its InputStreams.
+   */
+  private static CloseableJarHandlerFactory closeableJarHandlerFactory;
+
+  /**
+   * Closes all InputStreams that were created by the URL system that point at the given Jar file.
+   */
+  public static void closeStreams(String jarFilePath) throws IOException {
+    closeableJarHandlerFactory.closeableJarHandler.closeStreams(jarFilePath);
+  }
+
+  /**
+   * Ensures that the standard UrlStreamHandlerFactory has been replaced.
+   */
+  public static synchronized void installOverride() {
+    if (closeableJarHandlerFactory == null) {
+      closeableJarHandlerFactory = new CloseableJarHandlerFactory();
+      URL.setURLStreamHandlerFactory(closeableJarHandlerFactory);
+    }
+  }
+
+  private static URLStreamHandler createHandlerForProtocol(String protocol) {
+    String className = String.format("sun.net.www.protocol.%s.Handler", protocol);
+    try {
+      return (URLStreamHandler) Class.forName(className).newInstance();
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("Couldn't find handler for protocol: " + protocol);
+    } catch (InstantiationException e) {
+      throw new RuntimeException("Handler isn't not instantiable for protocol: " + protocol);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException("Handler isn't not accessible for protocol: " + protocol);
+    }
+  }
+
+  private CloseableJarHandler closeableJarHandler = new CloseableJarHandler();
+
+  private CloseableJarHandlerFactory() {
+    // Force access through the public static functions.
+  }
+
+  @Override
+  public URLStreamHandler createURLStreamHandler(String protocol) {
+    if (protocol.equals("jar")) {
+      // Override the url stream handler for Jar files.
+      return closeableJarHandler;
+    } else {
+      // Regular url stream handling for other protocols.
+      return createHandlerForProtocol(protocol);
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/TinyCompileSummary.java b/dev/core/src/com/google/gwt/dev/util/TinyCompileSummary.java
new file mode 100644
index 0000000..e1a6e08
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/TinyCompileSummary.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util;
+
+/**
+ * A small set of compile metrics that should be used to warn incremental compile users when a
+ * module is getting too large.
+ */
+public class TinyCompileSummary {
+
+  private int typesForGeneratorsCount;
+  private int typesForAstCount;
+  private int staticSourceFilesCount;
+  private int generatedSourceFilesCount;
+  private int cachedStaticSourceFilesCount;
+  private int cachedGeneratedSourceFilesCount;
+
+  public int getTypesForGeneratorsCount() {
+    return typesForGeneratorsCount;
+  }
+
+  public void setTypesForGeneratorsCount(int typesForGeneratorsCount) {
+    this.typesForGeneratorsCount = typesForGeneratorsCount;
+  }
+
+  public int getTypesForAstCount() {
+    return typesForAstCount;
+  }
+
+  public void setTypesForAstCount(int typesForAstCount) {
+    this.typesForAstCount = typesForAstCount;
+  }
+
+  public int getStaticSourceFilesCount() {
+    return staticSourceFilesCount;
+  }
+
+  public void setStaticSourceFilesCount(int staticSourceFilesCount) {
+    this.staticSourceFilesCount = staticSourceFilesCount;
+  }
+
+  public int getGeneratedSourceFilesCount() {
+    return generatedSourceFilesCount;
+  }
+
+  public void setGeneratedSourceFilesCount(int generatedSourceFilesCount) {
+    this.generatedSourceFilesCount = generatedSourceFilesCount;
+  }
+
+  public int getCachedStaticSourceFilesCount() {
+    return cachedStaticSourceFilesCount;
+  }
+
+  public void setCachedStaticSourceFilesCount(int cachedStaticSourceFilesCount) {
+    this.cachedStaticSourceFilesCount = cachedStaticSourceFilesCount;
+  }
+
+  public int getCachedGeneratedSourceFilesCount() {
+    return cachedGeneratedSourceFilesCount;
+  }
+
+  public void setCachedGeneratedSourceFilesCount(int cachedGeneratedSourceFilesCount) {
+    this.cachedGeneratedSourceFilesCount = cachedGeneratedSourceFilesCount;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerStrictResources.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerStrictResources.java
index c31e9a2..4f9eb18 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerStrictResources.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerStrictResources.java
@@ -24,17 +24,22 @@
  */
 public class ArgHandlerStrictResources extends ArgHandlerFlag {
 
-  private final OptionStrictResources options;
+  private final OptionStrictSourceResources optionStrictSourceResources;
+  private final OptionStrictPublicResources optionStrictPublicResources;
 
-  public ArgHandlerStrictResources(OptionStrictResources options) {
-    this.options = options;
+  public <T extends OptionStrictSourceResources &
+                    OptionStrictPublicResources> ArgHandlerStrictResources(T options) {
+    this.optionStrictSourceResources = options;
+    this.optionStrictPublicResources = options;
 
     addTagValue("-XstrictResources", true);
   }
 
   @Override
   public boolean getDefaultValue() {
-    return options.enforceStrictResources();
+    assert optionStrictSourceResources.enforceStrictSourceResources()
+        == optionStrictPublicResources.enforceStrictPublicResources();
+    return optionStrictSourceResources.enforceStrictSourceResources();
   }
 
   @Override
@@ -55,7 +60,8 @@
 
   @Override
   public boolean setFlag(boolean value) {
-    options.setEnforceStrictResources(value);
+    optionStrictSourceResources.setEnforceStrictSourceResources(value);
+    optionStrictPublicResources.setEnforceStrictPublicResources(value);
     return true;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionFinalProperties.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionFinalProperties.java
new file mode 100644
index 0000000..14f63fe
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionFinalProperties.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util.arg;
+
+import com.google.gwt.dev.cfg.Properties;
+
+/**
+ * Option providing a set of properties representing the final property state that will be reached
+ * at the root of a library compile tree.
+ * <p>
+ * Library compilation uses this as a guide for how long it must delay generator execution to avoid
+ * having to rerun the same generator again later when properties it depends on change.
+ */
+public interface OptionFinalProperties {
+
+  /**
+   * Returns the final properties.
+   */
+  Properties getFinalProperties();
+
+  /**
+   * Sets the list of paths to input libraries.
+   */
+  void setFinalProperties(Properties properties);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictPublicResources.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictPublicResources.java
new file mode 100644
index 0000000..fa3058b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictPublicResources.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util.arg;
+
+/**
+ * Whether to be strict about public resource loading by implicitly adding "public" as a public
+ * folder for every module.
+ */
+public interface OptionStrictPublicResources {
+
+  /**
+   * Returns true if the compiler should not implicitly add a "public" dependency.
+   */
+  boolean enforceStrictPublicResources();
+
+  /**
+   * Sets whether the compiler should not implicitly add a "public" dependency.
+   */
+  void setEnforceStrictPublicResources(boolean strictPublicResources);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictResources.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictResources.java
deleted file mode 100644
index 541628d..0000000
--- a/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictResources.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2013 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.util.arg;
-
-/**
- * Whether to be strict about resource loading and in particular whether to implicitly add "client"
- * and "public" resource dependencies when none are mentioned.
- */
-public interface OptionStrictResources {
-
-  /**
-   * Returns true if the compiler should not implicitly add "client" and "public" resource
-   * dependencies when none are mentioned.
-   */
-  boolean enforceStrictResources();
-
-  /**
-   * Sets whether the compiler should not implicitly add "client" and "public" resource dependencies
-   * when none are mentioned.
-   */
-  void setEnforceStrictResources(boolean strictResources);
-}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictSourceResources.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictSourceResources.java
new file mode 100644
index 0000000..e1a49d3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionStrictSourceResources.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util.arg;
+
+/**
+ * Whether to be strict about source resource loading by implicitly adding "client" as a source
+ * folder for every module.
+ */
+public interface OptionStrictSourceResources {
+
+  /**
+   * Returns true if the compiler should not implicitly add a "client" dependency.
+   */
+  boolean enforceStrictSourceResources();
+
+  /**
+   * Sets whether the compiler should not implicitly add a "client" dependency.
+   */
+  void setEnforceStrictSourceResources(boolean strictSourceResources);
+}
diff --git a/dev/core/test/com/google/gwt/dev/FooBarGenerator.java b/dev/core/test/com/google/gwt/dev/FooBarGenerator.java
new file mode 100644
index 0000000..33239cd
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/FooBarGenerator.java
@@ -0,0 +1,41 @@
+/*
+ * 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 for running in tests, converts class Foo to Bar.
+ */
+@RunsLocal
+public class FooBarGenerator extends Generator {
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+      throws UnableToCompleteException {
+    PrintWriter pw = context.tryCreate(logger, "com.google.gwt.dev", "Bar");
+    if (pw != null) {
+      pw.println("package com.google.gwt.dev;");
+      pw.println("public class Bar {}");
+      context.commit(logger, pw);
+    }
+    return "com.google.gwt.dev.Bar";
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/IncrementalBuilderTest.java b/dev/core/test/com/google/gwt/dev/IncrementalBuilderTest.java
new file mode 100644
index 0000000..6634fac
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/IncrementalBuilderTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.TreeLogger;
+import com.google.gwt.dev.cfg.LibraryGroup;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.cfg.ResourceLoader;
+import com.google.gwt.dev.cfg.ResourceLoaders;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.io.Files;
+import com.google.gwt.thirdparty.guava.common.io.Resources;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link IncrementalBuilder}.
+ */
+public class IncrementalBuilderTest extends TestCase {
+
+  private static void assertBuildResult(String rootModuleName, boolean expectedBuildSuccess,
+      String... expectedErrorMessages) throws MalformedURLException {
+    UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
+    loggerBuilder.setLowestLogLevel(TreeLogger.INFO);
+    for (String expectedErrorMessage : expectedErrorMessages) {
+      loggerBuilder.expectError(expectedErrorMessage, null);
+    }
+    UnitTestTreeLogger testLogger = loggerBuilder.createLogger();
+
+    IncrementalBuilder incrementalBuilder =
+        createIncrementalBuilder(rootModuleName, createGwtClassPathResourceLoader());
+    incrementalBuilder.clean();
+
+    boolean actualBuildSuccess = incrementalBuilder.build(testLogger).isSuccess();
+
+    assertEquals(expectedBuildSuccess, actualBuildSuccess);
+    testLogger.assertLogEntriesContainExpected();
+  }
+
+  private static ResourceLoader createGwtClassPathResourceLoader() throws MalformedURLException {
+    String gwtClassPathProperty = System.getProperty("gwt.cp");
+    String[] gwtClassPathStrings = gwtClassPathProperty.split(";");
+    final List<URL> gwtClassPathEntries = Lists.newArrayList();
+    for (String gwtClassPathString : gwtClassPathStrings) {
+      gwtClassPathEntries.add(new File(gwtClassPathString).toURI().toURL());
+    }
+    ResourceLoader gwtClassPathResourceLoader = new ResourceLoader() {
+        @Override
+      public List<URL> getClassPath() {
+        return gwtClassPathEntries;
+      }
+
+        @Override
+      public URL getResource(String resourceName) {
+        return Thread.currentThread().getContextClassLoader().getResource(resourceName);
+      }
+    };
+    return gwtClassPathResourceLoader;
+  }
+
+  private static ResourceLoader createGwtClassPathResourceLoaderWithMocks(
+      String... mockResourceNames) throws FileNotFoundException, IOException {
+    final ResourceLoader gwtClassPathResourceLoader = createGwtClassPathResourceLoader();
+    File mockResourcesDirectory = Files.createTempDir();
+
+    for (String mockResourceName : mockResourceNames) {
+      File mockFooFile = new File(mockResourcesDirectory, mockResourceName);
+      mockFooFile.getParentFile().mkdirs();
+      URL realFooResource = gwtClassPathResourceLoader.getResource(mockResourceName);
+      Resources.copy(realFooResource, new FileOutputStream(mockFooFile));
+    }
+
+    return ResourceLoaders.forPathAndFallback(Lists.newArrayList(mockResourcesDirectory),
+        gwtClassPathResourceLoader);
+  }
+
+  private static IncrementalBuilder createIncrementalBuilder(String rootModuleName,
+      ResourceLoader resourceLoader) {
+    String warDir = Files.createTempDir().getAbsolutePath();
+    String libDir = "/tmp/gwt/lib";
+    String genDir = "/tmp/gwt/gen";
+    IncrementalBuilder incrementalBuilder =
+        new IncrementalBuilder(rootModuleName, warDir, libDir, genDir, resourceLoader);
+    return incrementalBuilder;
+  }
+
+  public void testBuildRebuildEditRebuild() throws FileNotFoundException, IOException {
+    // Setup logging expectations for a full compile with no caching.
+    UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
+    loggerBuilder.setLowestLogLevel(TreeLogger.INFO);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage("com.google.gwt.core.Core"),
+        null);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleBottom"), null);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleMid"), null);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleTop"), null);
+    UnitTestTreeLogger testLogger = loggerBuilder.createLogger();
+
+    // Prepare the compiler.
+    ResourceLoader resourceLoader = createGwtClassPathResourceLoaderWithMocks(
+        "com/google/gwt/dev/testdata/incrementalbuildsystem/Foo.java");
+    IncrementalBuilder incrementalBuilder = createIncrementalBuilder(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleTop", resourceLoader);
+    incrementalBuilder.clean();
+
+    // Compile.
+    boolean actualBuildSuccess = incrementalBuilder.build(testLogger).isSuccess();
+
+    // Assert compile succeeded and was a full compile with no caching.
+    assertEquals(true, actualBuildSuccess);
+    testLogger.assertLogEntriesContainExpected();
+
+    // Setup logging expectations for a fully cached compile.
+    loggerBuilder = new UnitTestTreeLogger.Builder();
+    loggerBuilder.setLowestLogLevel(TreeLogger.INFO);
+    loggerBuilder.expectInfo(IncrementalBuilder.NO_FILES_HAVE_CHANGED, null);
+    testLogger = loggerBuilder.createLogger();
+
+    // Recompile without changes.
+    actualBuildSuccess = incrementalBuilder.rebuild(testLogger).isSuccess();
+
+    // Assert compile succeeded and was fully cached.
+    assertEquals(true, actualBuildSuccess);
+    testLogger.assertLogEntriesContainExpected();
+
+    // Change modification date of the Foo.java seen by SimpleMid.gwt.xml
+    File fooFile = new File(resourceLoader.getResource(
+        "com/google/gwt/dev/testdata/incrementalbuildsystem/Foo.java").getPath());
+    fooFile.setLastModified(System.currentTimeMillis() + 60 * 1000);
+
+    // Setup logging expectations for partially cached recompile.
+    loggerBuilder = new UnitTestTreeLogger.Builder();
+    loggerBuilder.setLowestLogLevel(TreeLogger.SPAM);
+    loggerBuilder.expectSpam(
+        BuildTarget.formatReusingCachedLibraryMessage("com.google.gwt.core.Core"), null);
+    loggerBuilder.expectSpam(BuildTarget.formatReusingCachedLibraryMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleBottom"), null);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleMid"), null);
+    loggerBuilder.expectInfo(BuildTarget.formatCompilingModuleMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleTop"), null);
+    testLogger = loggerBuilder.createLogger();
+
+    // Recompile with changes.
+    actualBuildSuccess = incrementalBuilder.rebuild(testLogger).isSuccess();
+
+    // Assert compile succeeded and was partially cached.
+    assertEquals(true, actualBuildSuccess);
+    testLogger.assertLogEntriesContainExpected();
+  }
+
+  public void testCircularReference() throws MalformedURLException {
+    List<String> circularModulePath = Arrays.asList(new String[] {
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.CircularRoot",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.CircularB <loop>",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.CircularC",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.CircularFilesetD <fileset>",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.CircularB <loop>"});
+    assertBuildResult("com.google.gwt.dev.testdata.incrementalbuildsystem.CircularRoot", false,
+        IncrementalBuilder.formatCircularModulePathMessage(circularModulePath));
+  }
+
+  public void testDuplicateGeneratorOutput() throws MalformedURLException {
+    String duplicateCompilationUnitError = LibraryGroup.formatDuplicateCompilationUnitMessage(
+        "com.google.gwt.dev.Bar", "com.google.gwt.dev.testdata.incrementalbuildsystem.ParallelLeft",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.ParallelRight");
+    assertBuildResult("com.google.gwt.dev.testdata.incrementalbuildsystem.ParallelRoot", false,
+        duplicateCompilationUnitError);
+  }
+
+  public void testDuplicateSourceInclusion() throws MalformedURLException {
+    String duplicateCompilationUnitError = LibraryGroup.formatDuplicateCompilationUnitMessage(
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.Foo",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.DuplicateLeft",
+        "com.google.gwt.dev.testdata.incrementalbuildsystem.DuplicateRight");
+    assertBuildResult("com.google.gwt.dev.testdata.incrementalbuildsystem.DuplicateRoot", false,
+        duplicateCompilationUnitError);
+  }
+
+  public void testUnableToFindModule() throws MalformedURLException {
+    String unableToFindModuleMessage = ModuleDefLoader.formatUnableToFindModuleMessage(
+        "com/google/gwt/dev/testdata/incrementalbuildsystem/NoSuchModule.gwt.xml");
+    assertBuildResult("com.google.gwt.dev.testdata.incrementalbuildsystem.NoSuchModule", false,
+        unableToFindModuleMessage);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java b/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
index 0c62fe1..cb7e542 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
@@ -105,14 +105,6 @@
   }
 
   public void testToSource() {
-    // Linker assertions are not supported.
-    try {
-      new ConditionWhenLinkerAdded("com.some.Linker").toSource();
-      fail("expected linker condition source conversion to fail.");
-    } catch (UnsupportedOperationException e) {
-      // Expected behavior.
-    }
-
     // Compound conditions collapse effectively empty slots.
     assertEquals("((requestTypeClass == @com.google.gwt.Foo::class))", new ConditionAll(
         new ConditionAny(), new ConditionWhenTypeIs("com.google.gwt.Foo")).toSource());
diff --git a/dev/core/test/com/google/gwt/dev/cfg/DynamicPropertyOracleTest.java b/dev/core/test/com/google/gwt/dev/cfg/DynamicPropertyOracleTest.java
index 095cc79..d0a358e 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/DynamicPropertyOracleTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/DynamicPropertyOracleTest.java
@@ -60,13 +60,13 @@
     assertEquals(1, dynamicPropertyOracle.getAccessedProperties().size());
 
     // Finds XML Constrained.
-    dynamicPropertyOracle.reset();
+    dynamicPropertyOracle = new DynamicPropertyOracle(properties);
     userAgentProperty.addDefinedValue(new ConditionWhenLinkerAdded("foo"), "webkit");
     userAgentProperty.addDefinedValue(new ConditionWhenLinkerAdded("bar"), "webkit");
     userAgentProperty.addDefinedValue(new ConditionWhenLinkerAdded("baz"), "webkit");
     assertEquals(
         "webkit", dynamicPropertyOracle.getSelectionProperty(null, "user.agent").getCurrentValue());
-    assertFalse(dynamicPropertyOracle.haveAccessedPropertiesChanged());
+    assertTrue(dynamicPropertyOracle.haveAccessedPropertiesChanged());
     assertEquals(1, dynamicPropertyOracle.getAccessedProperties().size());
 
     // Finds first defined.
diff --git a/dev/core/test/com/google/gwt/dev/cfg/LibraryGroupTest.java b/dev/core/test/com/google/gwt/dev/cfg/LibraryGroupTest.java
index 6f7c2c9..a314cb1 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/LibraryGroupTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/LibraryGroupTest.java
@@ -127,7 +127,8 @@
     }
 
     // Show that the library group collects and returns them all.
-    assertTrue(expectedReboundTypeSourceNames.equals(libraryGroup.getReboundTypeSourceNames()));
+    assertTrue(
+        expectedReboundTypeSourceNames.equals(libraryGroup.getReboundTypeSourceNames()));
   }
 
   public void testLibraryLinkOrder() {
@@ -154,89 +155,26 @@
     assertEquals(libraries.size(), linkOrderLibraries.size());
   }
 
-  /**
-   * See buildVariedPropertyGeneratorLibraryGroup() for test library group structure.
-   */
-  public void testPropertyCollectionByGenerator() {
-    LibraryGroup libraryGroup = buildVariedPropertyGeneratorLibraryGroup();
-
-    // Collect "new" legal binding property values from the perspective of each generator.
-    Collection<String> newUserAgentsForUserAgentAsserter =
-        libraryGroup.gatherNewBindingPropertyValuesForGenerator("UserAgentAsserter").get(
-            "user.agent");
-    Collection<String> newUserAgentsForLocalizedDatePickerGenerator =
-        libraryGroup.gatherNewBindingPropertyValuesForGenerator("LocalizedDatePickerGenerator").get(
-            "user.agent");
-    Collection<String> newLocalesForLocalizedDatePickerGenerator =
-        libraryGroup.gatherNewBindingPropertyValuesForGenerator("LocalizedDatePickerGenerator").get(
-            "locale");
-
-    // Verify that the results are as expected.
-    assertEquals(
-        Sets.newHashSet("webkit_phone", "webkit_tablet"), newUserAgentsForUserAgentAsserter);
-    assertEquals(Sets.newHashSet("webkit_phone", "webkit_tablet", "webkit", "mozilla", "ie"),
-        newUserAgentsForLocalizedDatePickerGenerator);
-    assertEquals(Sets.newHashSet("ru"), newLocalesForLocalizedDatePickerGenerator);
-  }
-
-  /**
-   * <pre>
-   * root library: {
-   *   user.agent: [webkit_phone, webkit_tablet],
-   *   locale: [ru]
-   *   ranGenerators: [],
-   *   libraries: [
-   *     sub library 1: {
-   *       user.agent: [webkit, mozilla, ie],
-   *       locale: [],
-   *       ranGenerators: [UserAgentAsserter]
-   *     },
-   *     sub library 2: {
-   *       user.agent: [],
-   *       locale: [en, fr],
-   *       ranGenerators: [LocalizedDatePickerGenerator]
-   *     }
-   *   ]
-   * }
-   * </pre>
-   */
-  private LibraryGroup buildVariedPropertyGeneratorLibraryGroup() {
-    String generatorNameOne = "UserAgentAsserter";
-    String generatorNameTwo = "LocalizedDatePickerGenerator";
-    return buildVariedPropertyGeneratorLibraryGroup(
-        generatorNameOne, Sets.<String>newHashSet(), generatorNameTwo, Sets.<String>newHashSet());
-  }
-
-  public static LibraryGroup buildVariedPropertyGeneratorLibraryGroup(String generatorNameOne,
-      Set<String> reboundTypeSourceNamesOne, String generatorNameTwo,
-      Set<String> reboundTypeSourceNamesTwo) {
+  public static LibraryGroup buildVariedPropertyGeneratorLibraryGroup(
+      Set<String> reboundTypeSourceNamesOne, Set<String> reboundTypeSourceNamesTwo) {
     // A root library that adds more legal user.agent and locale values but for which no generators
     // have been run.
     MockLibrary rootLibrary = new MockLibrary("RootLibrary");
-    rootLibrary.getDependencyLibraryNames()
-        .addAll(Lists.newArrayList("SubLibrary1", "SubLibrary2"));
-    rootLibrary.getNewBindingPropertyValuesByName()
-        .putAll("user.agent", Lists.newArrayList("webkit_phone", "webkit_tablet"));
-    rootLibrary.getNewBindingPropertyValuesByName().putAll("locale", Lists.newArrayList("ru"));
+    rootLibrary.getDependencyLibraryNames().addAll(
+        Lists.newArrayList("SubLibrary1", "SubLibrary2"));
 
     // A library that adds legal user.agent values and has already run the UserAgentAsserter
     // generator.
     MockLibrary subLibrary1 = new MockLibrary("SubLibrary1");
-    subLibrary1.getNewBindingPropertyValuesByName()
-        .putAll("user.agent", Lists.newArrayList("webkit", "mozilla", "ie"));
-    subLibrary1.getRanGeneratorNames().add(generatorNameOne);
     subLibrary1.getReboundTypeSourceNames().addAll(reboundTypeSourceNamesOne);
 
     // A library that adds legal locale values and has already run the LocaleMessageGenerator
     // generator.
     MockLibrary subLibrary2 = new MockLibrary("SubLibrary2");
-    subLibrary2.getNewBindingPropertyValuesByName()
-        .putAll("locale", Lists.newArrayList("en", "fr"));
-    subLibrary2.getRanGeneratorNames().add(generatorNameTwo);
     subLibrary2.getReboundTypeSourceNames().addAll(reboundTypeSourceNamesTwo);
 
     LibraryGroup libraryGroup = LibraryGroup.fromLibraries(
-        Lists.<Library>newArrayList(rootLibrary, subLibrary1, subLibrary2), true);
+        Lists.<Library> newArrayList(rootLibrary, subLibrary1, subLibrary2), true);
     return libraryGroup;
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/cfg/MockLibrary.java b/dev/core/test/com/google/gwt/dev/cfg/MockLibrary.java
index 370ff64..dc150fe 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/MockLibrary.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/MockLibrary.java
@@ -20,7 +20,6 @@
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.Name.InternalName;
 import com.google.gwt.dev.util.ZipEntryBackedObject;
-import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
@@ -68,10 +67,8 @@
   private String libraryName;
   private Multimap<String, String> nestedBinaryNamesByCompilationUnitName = HashMultimap.create();
   private Multimap<String, String> nestedSourceNamesByCompilationUnitName = HashMultimap.create();
-  private Multimap<String, String> newBindingPropertyValuesByName = ArrayListMultimap.create();
-  private Multimap<String, String> newConfigurationPropertyValuesByName =
-      ArrayListMultimap.create();
-  private Set<String> ranGeneratorNames = Sets.newHashSet();
+  private Multimap<String, String> processedReboundTypeSourceNamesByGenerator =
+      HashMultimap.create();
   private Set<String> reboundTypeNames = Sets.newHashSet();
   private Set<String> superSourceCompilationUnitTypeNames = Sets.newHashSet();
 
@@ -117,6 +114,10 @@
   }
 
   @Override
+  public void close() {
+  }
+
+  @Override
   public Resource getBuildResourceByPath(String path) {
     return null;
   }
@@ -172,21 +173,16 @@
   }
 
   @Override
-  public Multimap<String, String> getNewBindingPropertyValuesByName() {
-    return newBindingPropertyValuesByName;
-  }
-
-  @Override
-  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
-    return newConfigurationPropertyValuesByName;
-  }
-
-  @Override
   public ZipEntryBackedObject<PermutationResult> getPermutationResultHandle() {
     return null;
   }
 
   @Override
+  public Multimap<String, String> getProcessedReboundTypeSourceNamesByGenerator() {
+    return processedReboundTypeSourceNamesByGenerator;
+  }
+
+  @Override
   public Resource getPublicResourceByPath(String path) {
     return null;
   }
@@ -197,16 +193,6 @@
   }
 
   @Override
-  public Set<String> getRanGeneratorNames() {
-    return ranGeneratorNames;
-  }
-
-  @Override
-  public Set<String> getReboundTypeSourceNames() {
-    return reboundTypeNames;
-  }
-
-  @Override
   public Set<String> getRegularClassFilePaths() {
     return null;
   }
@@ -217,6 +203,11 @@
   }
 
   @Override
+  public Set<String> getReboundTypeSourceNames() {
+    return reboundTypeNames;
+  }
+
+  @Override
   public Set<String> getSuperSourceClassFilePaths() {
     return null;
   }
diff --git a/dev/core/test/com/google/gwt/dev/cfg/MockLibraryWriter.java b/dev/core/test/com/google/gwt/dev/cfg/MockLibraryWriter.java
index 98ab7c8..f998354 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/MockLibraryWriter.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/MockLibraryWriter.java
@@ -18,8 +18,8 @@
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.ZipEntryBackedObject;
-import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
-import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.SetMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.util.Set;
@@ -32,9 +32,8 @@
   private Set<String> buildResourcePaths = Sets.newHashSet();
   private Set<String> dependencyLibraryNames = Sets.newHashSet();
   private String libraryName;
-  private Multimap<String, String> newBindingPropertyValuesByName = ArrayListMultimap.create();
-  private Multimap<String, String> newConfigurationPropertyValuesByName =
-      ArrayListMultimap.create();
+  private SetMultimap<String, String> processedReboundTypeSourceNamesByGenerator =
+      HashMultimap.create();
   private Set<String> reboundTypeNames;
 
   @Override
@@ -61,25 +60,9 @@
   }
 
   @Override
-  public void addNewBindingPropertyValuesByName(
-      String propertyName, Iterable<String> propertyValues) {
-    newBindingPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
-  public void addNewConfigurationPropertyValuesByName(
-      String propertyName, Iterable<String> propertyValues) {
-    newConfigurationPropertyValuesByName.putAll(propertyName, propertyValues);
-  }
-
-  @Override
   public void addPublicResource(Resource publicResource) {
   }
 
-  @Override
-  public void addRanGeneratorName(String generatorName) {
-  }
-
   public Set<String> getBuildResourcePaths() {
     return buildResourcePaths;
   }
@@ -93,36 +76,37 @@
   }
 
   @Override
-  public Multimap<String, String> getNewBindingPropertyValuesByName() {
-    return newBindingPropertyValuesByName;
-  }
-
-  @Override
-  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
-    return newConfigurationPropertyValuesByName;
-  }
-
-  @Override
   public ZipEntryBackedObject<PermutationResult> getPermutationResultHandle() {
     return null;
   }
 
   @Override
+  public Set<String> getProcessedReboundTypeSourceNames(String generatorName) {
+    return processedReboundTypeSourceNamesByGenerator.get(generatorName);
+  }
+
+  @Override
   public Set<String> getReboundTypeSourceNames() {
     return reboundTypeNames;
   }
 
   @Override
-  public void setLibraryName(String libraryName) {
-    this.libraryName = libraryName;
+  public void markReboundTypeProcessed(String processedReboundTypeSourceName,
+      String generatorName) {
+    processedReboundTypeSourceNamesByGenerator.put(generatorName, processedReboundTypeSourceName);
   }
 
   @Override
-  public void setReboundTypeSourceNames(Set<String> reboundTypeSourceNames) {
+  public void markReboundTypesProcessed(Set<String> reboundTypeSourceNames) {
     this.reboundTypeNames = reboundTypeSourceNames;
   }
 
   @Override
+  public void setLibraryName(String libraryName) {
+    this.libraryName = libraryName;
+  }
+
+  @Override
   public void write() {
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
index 7936c3c..5de014f 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
@@ -22,6 +22,7 @@
 import com.google.gwt.dev.javac.testing.impl.MockResource;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -30,8 +31,8 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Set;
 
 /**
  * Test for the module def loading
@@ -39,13 +40,14 @@
 public class ModuleDefLoaderTest extends TestCase {
 
   private CompilerContext compilerContext;
-  private CompilerContext.Builder compilerContextBuilder = new CompilerContext.Builder();
+  private CompilerContext.Builder compilerContextBuilder;
   private MockLibraryWriter mockLibraryWriter = new MockLibraryWriter();
 
   public void assertHonorsStrictResources(boolean strictResources)
       throws UnableToCompleteException {
     TreeLogger logger = TreeLogger.NULL;
-    compilerContext.getOptions().setEnforceStrictResources(strictResources);
+    compilerContext.getOptions().setEnforceStrictSourceResources(strictResources);
+    compilerContext.getOptions().setEnforceStrictPublicResources(strictResources);
     ModuleDef emptyModule = ModuleDefLoader.loadFromClassPath(
         logger, compilerContext, "com.google.gwt.dev.cfg.testdata.merging.Empty");
     Resource sourceFile =
@@ -153,6 +155,9 @@
 
   public void testLoadFromLibraryGroup() throws UnableToCompleteException, IOException,
       IncompatibleLibraryVersionException {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
+    logger.setMaxDetail(TreeLogger.INFO);
+
     // Create the library zip file.
     File zipFile = File.createTempFile("FooLib", ".gwtlib");
     zipFile.deleteOnExit();
@@ -175,11 +180,11 @@
     // Prepare the LibraryGroup and ResourceLoader.
     compilerContext = compilerContextBuilder.libraryGroup(
         LibraryGroup.fromLibraries(Lists.<Library> newArrayList(zipLibrary), false)).build();
-    ResourceLoader resourceLoader =
-        ResourceLoaders.wrap(new URLClassLoader(new URL[] {zipFile.toURL()}, null));
+    ResourceLoader resourceLoader = ResourceLoaders.forPathAndFallback(Lists.newArrayList(zipFile),
+        ResourceLoaders.forClassLoader(Thread.currentThread()));
 
     // Will throw an exception if com.google.gwt.user.User can't be found and parsed.
-    ModuleDefLoader.loadFromResources(TreeLogger.NULL, compilerContext, "com.google.gwt.user.User",
+    ModuleDefLoader.loadFromResources(logger, compilerContext, "com.google.gwt.user.User",
         resourceLoader, false);
   }
 
@@ -191,14 +196,17 @@
     ModuleDef six = ModuleDefLoader.loadFromClassPath(
         logger, compilerContext, "com.google.gwt.dev.cfg.testdata.dependents.Six");
 
-    assertTrue(six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Six")
-        .equals(ImmutableSet.of("com.google.gwt.dev.cfg.testdata.dependents.Five")));
-    assertTrue("Contains " + six.getDirectDependencies(
-        "com.google.gwt.dev.cfg.testdata.dependents.Five"),
-        six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Five")
-        .equals(ImmutableSet.of("com.google.gwt.dev.cfg.testdata.dependents.One",
-            "com.google.gwt.dev.cfg.testdata.dependents.Two",
-            "com.google.gwt.dev.cfg.testdata.dependents.Four")));
+    Collection<String> sixActualDependencies =
+        six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Six");
+    Collection<String> fiveActualDependencies =
+        six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Five");
+
+    assertEquals(ImmutableSet.of("com.google.gwt.core.Core",
+        "com.google.gwt.dev.cfg.testdata.dependents.Five"), sixActualDependencies);
+    assertEquals("Contains " + fiveActualDependencies, ImmutableSet.of(
+        "com.google.gwt.core.Core", "com.google.gwt.dev.cfg.testdata.dependents.One",
+        "com.google.gwt.dev.cfg.testdata.dependents.Two",
+        "com.google.gwt.dev.cfg.testdata.dependents.Four"), fiveActualDependencies);
   }
 
   /**
@@ -293,9 +301,13 @@
     ModuleDef one = ModuleDefLoader.loadFromClassPath(logger, compilerContext,
         "com.google.gwt.dev.cfg.testdata.merging.One");
 
-    // Sees the logo.png image but not the java source file.
-    assertEquals(one.getBuildResourceOracle().getPathNames(),
-        Sets.newHashSet("com/google/gwt/dev/cfg/testdata/merging/resources/logo.png"));
+    Set<String> visibleResourcePaths = one.getBuildResourceOracle().getPathNames();
+    // Sees the logo.png image.
+    assertTrue(visibleResourcePaths.contains(
+        "com/google/gwt/dev/cfg/testdata/merging/resources/logo.png"));
+    // But not the java source file.
+    assertFalse(visibleResourcePaths.contains(
+        "com/google/gwt/dev/cfg/testdata/merging/resources/NotAResource.java"));
   }
 
   public void testSeparateLibraryModuleReferences() throws UnableToCompleteException {
@@ -310,7 +322,8 @@
         "com/google/gwt/dev/cfg/testdata/separate/libraryone/LibraryOne.gwt.xml"),
         mockLibraryWriter.getBuildResourcePaths());
     // The library writer was given LibraryTwo as a dependency library.
-    assertEquals(Sets.newHashSet("com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
+    assertEquals(Sets.newHashSet("com.google.gwt.core.Core",
+        "com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
         mockLibraryWriter.getDependencyLibraryNames());
   }
 
@@ -333,7 +346,8 @@
         "com.google.gwt.dev.cfg.testdata.separate.filesetone.FileSetOne"),
         libraryOneModule.getTargetLibraryCanonicalModuleNames());
     // The module sees the referenced library module as a "library" module.
-    assertEquals(Sets.newHashSet("com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
+    assertEquals(Sets.newHashSet("com.google.gwt.core.Core",
+        "com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
         libraryOneModule.getExternalLibraryCanonicalModuleNames());
   }
 
@@ -379,16 +393,8 @@
       }
       assertEquals(Sets.newHashSet(bindingProperty.getDefinedValues()),
           Sets.newHashSet("yes", "no", "maybe"));
-      assertEquals(Sets.newHashSet(bindingProperty.getTargetLibraryDefinedValues()),
-          Sets.newHashSet("maybe"));
     }
 
-    // Library one added a new defined value of "maybe" for the "libraryTwoProperty" binding
-    // property.
-    assertEquals(Sets.newHashSet(
-        mockLibraryWriter.getNewBindingPropertyValuesByName().get("libraryTwoProperty")),
-        Sets.newHashSet("maybe"));
-
     // Library one sees all defined values for the "libraryTwoConfigProperty" property and knows
     // which one was defined in this target library.
     for (ConfigurationProperty configurationProperty :
@@ -400,11 +406,6 @@
       assertEquals(Sets.newHashSet(configurationProperty.getTargetLibraryValues()),
           Sets.newHashSet("false"));
     }
-
-    // Library one added a new defined value of "maybe" for the "libraryTwoConfigProperty"
-    // property.
-    assertEquals(Sets.newHashSet(mockLibraryWriter.getNewConfigurationPropertyValuesByName().get(
-        "libraryTwoConfigProperty")), Sets.newHashSet("false"));
   }
 
   private void assertErrorsWhenLoading(String moduleName, String... errorMessages) {
@@ -430,6 +431,7 @@
   protected void setUp() throws Exception {
     super.setUp();
     ModuleDefLoader.getModulesCache().clear();
+    compilerContextBuilder = new CompilerContext.Builder();
     compilerContext = compilerContextBuilder.libraryWriter(mockLibraryWriter).build();
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ZipLibrariesTest.java b/dev/core/test/com/google/gwt/dev/cfg/ZipLibrariesTest.java
index 858ddfc..3f919ca 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ZipLibrariesTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ZipLibrariesTest.java
@@ -87,10 +87,6 @@
     String expectedLibraryName = "BazLib";
     final String expectedResourceContents =
         "<html><head><title>Index</title></head><body>Hi</body></html>";
-    Set<String> expectedRanGeneratorNames =
-        Sets.newHashSet("UiBinderGenerator", "PlatinumGenerator");
-    Set<String> expectedUserAgentConfigurationValues = Sets.newHashSet("webkit");
-    Set<String> expectedLocaleConfigurationValues = Sets.newHashSet("en,default,en_US", "fr");
     Set<String> expectedDependencyLibraryNames = Sets.newHashSet("FooLib", "BarLib");
     oracle.add(BAR, SUPER_FOO, JdtCompilerTest.OUTER_INNER);
     rebuildCompilationState();
@@ -109,13 +105,6 @@
         return expectedResourceContents;
       }
     });
-    zipLibraryWriter.addNewConfigurationPropertyValuesByName("user.agent",
-        expectedUserAgentConfigurationValues);
-    zipLibraryWriter.addNewConfigurationPropertyValuesByName("locale",
-        expectedLocaleConfigurationValues);
-    for (String generatorName : expectedRanGeneratorNames) {
-      zipLibraryWriter.addRanGeneratorName(generatorName);
-    }
     zipLibraryWriter.addDependencyLibraryNames(expectedDependencyLibraryNames);
     for (CompilationUnit compilationUnit : compilationUnits) {
       zipLibraryWriter.addCompilationUnit(compilationUnit);
@@ -133,11 +122,6 @@
     assertEquals(expectedLibraryName, zipLibrary.getLibraryName());
     assertEquals(expectedResourceContents,
         Util.readStreamAsString(zipLibrary.getPublicResourceByPath("index.html").openContents()));
-    assertEquals(expectedRanGeneratorNames, zipLibrary.getRanGeneratorNames());
-    assertEquals(expectedUserAgentConfigurationValues,
-        zipLibrary.getNewConfigurationPropertyValuesByName().get("user.agent"));
-    assertEquals(expectedLocaleConfigurationValues,
-        zipLibrary.getNewConfigurationPropertyValuesByName().get("locale"));
     assertEquals(expectedDependencyLibraryNames, zipLibrary.getDependencyLibraryNames());
 
     // CompilationUnit
diff --git a/dev/core/test/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompilerTest.java b/dev/core/test/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompilerTest.java
index 0bda11e..ffc4797 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompilerTest.java
@@ -21,6 +21,8 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.dev.CompilerContext;
+import com.google.gwt.dev.CompilerOptionsImpl;
+import com.google.gwt.dev.PrecompileTaskOptions;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.Condition;
 import com.google.gwt.dev.cfg.ConditionWhenPropertyIs;
@@ -168,6 +170,7 @@
 
     @Override
     public ArtifactSet finish(TreeLogger logger) throws UnableToCompleteException {
+      dirty = false;
       // Don't actually compile generated source code;
       return new ArtifactSet();
     }
@@ -241,11 +244,11 @@
       }
 
       @Override
-      protected boolean runGenerator(RuleGenerateWith generatorRule,
+      protected void runGenerator(RuleGenerateWith generatorRule,
           Set<String> reboundTypeSourceNames) throws UnableToCompleteException {
         processedReboundTypeSourceNames.addAll(reboundTypeSourceNames);
         runCountByGeneratorName.incrementAndGet(generatorRule.getName());
-        return super.runGenerator(generatorRule, reboundTypeSourceNames);
+        super.runGenerator(generatorRule, reboundTypeSourceNames);
       }
     }
 
@@ -360,12 +363,12 @@
     Properties properties = new Properties();
     BindingProperty userAgentProperty = properties.createBinding("user.agent");
     userAgentProperty.setProvider(new PropertyProvider("return navigator.userAgent;"));
-    userAgentProperty.addDefinedValue(userAgentProperty.getRootCondition(), "mozilla");
-    userAgentProperty.addDefinedValue(userAgentProperty.getRootCondition(), "webkit");
+    userAgentProperty.addTargetLibraryDefinedValue(userAgentProperty.getRootCondition(), "mozilla");
+    userAgentProperty.addTargetLibraryDefinedValue(userAgentProperty.getRootCondition(), "webkit");
     BindingProperty flavorProperty = properties.createBinding("flavor");
     flavorProperty.setProvider(new PropertyProvider("return window.properties.flavor;"));
-    flavorProperty.addDefinedValue(flavorProperty.getRootCondition(), "Vanilla");
-    flavorProperty.addDefinedValue(flavorProperty.getRootCondition(), "Chocolate");
+    flavorProperty.addTargetLibraryDefinedValue(flavorProperty.getRootCondition(), "Vanilla");
+    flavorProperty.addTargetLibraryDefinedValue(flavorProperty.getRootCondition(), "Chocolate");
     ConfigurationProperty emulateStackProperty =
         properties.createConfiguration("emulateStack", false);
     emulateStackProperty.setValue("TRUE");
@@ -428,12 +431,12 @@
         .getConditions().add(new ConditionWhenTypeEndsWith("Messages"));
     module.addRule(localeMessageGenerateRule);
     LibraryGroup libraryGroup = LibraryGroupTest.buildVariedPropertyGeneratorLibraryGroup(
-        "com.google.gwt.dev.jjs.LibraryJavaToJavaScriptCompilerTest.BrowserShimGenerator",
         Sets.newHashSet("com.google.ChromeMessages"),
-        "com.google.gwt.dev.jjs.LibraryJavaToJavaScriptCompilerTest.LocaleMessageGenerator",
         Sets.newHashSet("com.google.WindowShim"));
+    PrecompileTaskOptions options = new CompilerOptionsImpl();
+    options.setFinalProperties(module.getProperties());
     compilerContext = new CompilerContext.Builder().libraryGroup(libraryGroup)
-        .libraryWriter(libraryWriter).module(module).build();
+        .libraryWriter(libraryWriter).module(module).options(options).build();
     finishSetUpWithCompilerContext();
 
     // Analyzes properties and generators in the library group and watches output in the generator
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/DevirtualizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/DevirtualizerTest.java
index 9b102bd..2382f7c 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/DevirtualizerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/DevirtualizerTest.java
@@ -74,10 +74,10 @@
     // the method used for val2 and val3.
     StringBuffer expected = new StringBuffer();
     expected.append("int result = ");
-    expected.append("EntryPoint$Jso1.a__devirtual$(EntryPoint.val1) + ");
-    expected.append("EntryPoint$Jso1.a__devirtual$(EntryPoint.val2) + ");
-    expected.append("EntryPoint$Jso2.a1__devirtual$(EntryPoint.val3) + ");
-    expected.append("EntryPoint$Jso2.a1__devirtual$(EntryPoint.val4);");
+    expected.append("EntryPoint$Jso1.a__I__devirtual$(EntryPoint.val1) + ");
+    expected.append("EntryPoint$Jso1.a__I__devirtual$(EntryPoint.val2) + ");
+    expected.append("EntryPoint$Jso2.a__I__devirtual$(EntryPoint.val3) + ");
+    expected.append("EntryPoint$Jso2.a__I__devirtual$(EntryPoint.val4);");
 
     Result result = optimize("void", code.toString());
     // Asserts that a() method calls were redirected to the devirtualized version.
@@ -120,13 +120,14 @@
     // String.length are devirtualized separately.
     StringBuffer expected = new StringBuffer();
     expected.append("int result = ");
-    expected.append(
+    expected.append(String.format(
         // Methods in Comparable and CharSequence end up in String even if used by a JSO.
-        "String.compareTo__devirtual$(EntryPoint.javaVal, EntryPoint.javaVal) + " +
-        "String.compareTo__devirtual$(EntryPoint.jsoVal, EntryPoint.jsoVal) + " +
-        "String.compareTo__devirtual$(EntryPoint.stringVal, EntryPoint.stringVal) + " +
-        "String.length__devirtual$(EntryPoint.stringCharSeq) + " +
-        "String.length1__devirtual$(EntryPoint.aString);");
+        "String.compareTo_%s__I__devirtual$(EntryPoint.javaVal, EntryPoint.javaVal) + " +
+        "String.compareTo_%s__I__devirtual$(EntryPoint.jsoVal, EntryPoint.jsoVal) + " +
+        "String.compareTo_%s__I__devirtual$(EntryPoint.stringVal, EntryPoint.stringVal) + " +
+        "String.length__I__devirtual$(EntryPoint.stringCharSeq) + " +
+        "String.length__I__devirtual$(EntryPoint.aString);", "Ljava_lang_Object",
+        "Ljava_lang_Object", "Ljava_lang_Object"));
 
     Result result = optimize("void", code.toString());
     result.intoString(expected.toString());
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml b/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
index f6677a6..b106b5c 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
@@ -1,6 +1,6 @@
 <module rename-to="showcase">
   <!-- Inherit the core Web Toolkit stuff. -->
-  <inherits name="com.google.gwt.core.Core"/>
+  <inherits name='com.google.gwt.core.Core'/>
   <inherits name='com.google.gwt.user.User'/>
   <inherits name="com.google.gwt.i18n.I18N"/>
   <inherits name="com.google.gwt.i18n.CldrLocales"/>
@@ -28,4 +28,6 @@
   <set-property-fallback name="locale" value="en"/>
   <set-configuration-property name="locale.cookie" value="SHOWCASE_LOCALE"/>
   <set-configuration-property name="locale.useragent" value="Y"/>
+
+  <source path="client" />
 </module>
diff --git a/user/BUILD b/user/BUILD
index 6d77783..1dd9061 100644
--- a/user/BUILD
+++ b/user/BUILD
@@ -144,6 +144,32 @@
     ],
 )
 
+# Packages just IncrementalBuilder test resource into a jar with correct java root.
+AugmentedJar(
+    name = "incremental-build-system-test-res",
+    srcs = [],
+    added_root_globs = {
+        "test": [
+            "test/com/google/gwt/dev/testdata/incrementalbuildsystem/*",
+        ],
+    },
+    # We only need the Java classes so don't wait for the rest of :gwt-dev.
+    build_deps = [
+        "//third_party/java_src/gwt:gwt-user-full",
+        "//third_party/java_src/gwt/svn/tools:dev_deps",
+        "//third_party/java_src/gwt/svn/trunk/dev:compiler.standalone.super",
+        "//third_party/java_src/gwt/svn/trunk/dev:gwt-dev-classes",
+    ],
+    constraints = [],
+    dojarjar = 1,
+    exclude_glob = [
+        "**/package.html",
+        "**/package-info.java",
+    ],
+    output_name = "incremental-build-system-test-res.jar",
+    wrap_javalibrary = 0,
+)
+
 # See //third_party/java/gwt:regexp
 java_library(
     name = "jvm_regexp",
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java b/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
index 4aaadae..a9ddbbb 100644
--- a/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
+++ b/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2011 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
@@ -16,6 +16,7 @@
 package com.google.gwt.safecss.shared;
 
 import com.google.gwt.core.shared.GWT;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
 
 import java.util.HashMap;
@@ -24,11 +25,11 @@
 /**
  * SafeStyles utilities whose implementation differs between Development and
  * Production Mode.
- * 
+ *
  * <p>
  * This class has a super-source peer that provides the Production Mode
  * implementation.
- * 
+ *
  * <p>
  * Do not use this class - it is used for implementation only, and its methods
  * may change in the future.
@@ -50,21 +51,21 @@
 
   /**
    * Check if the specified style property name is valid.
-   * 
+   *
    * <p>
    * NOTE: This method does <em>NOT</em> guarantee the safety of a style name.
    * It looks for common errors, but does not check for every possible error. It
    * is intended to help validate a string that the user has already asserted is
    * safe.
    * </p>
-   * 
+   *
    * @param name the name to check
    * @return null if valid, an error string if not
    * @see <a
    *      href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS
    *      2.1 identifiers</a>
    */
-  // VisibleForTesting.
+  @VisibleForTesting
   public static String isValidStyleName(String name) {
     // Empty name.
     if (name == null || name.isEmpty()) {
@@ -101,20 +102,20 @@
 
   /**
    * Check if the specified style property value is valid.
-   * 
+   *
    * <p>
    * NOTE: This method does <em>NOT</em> guarantee the safety of a style value.
    * It looks for common errors, but does not check for every possible error. It
    * is intended to help validate a string that the user has already asserted is
    * safe.
    * </p>
-   * 
+   *
    * @param value the value to check
    * @return null if valid, an error string if not
    * @see <a href="http://www.w3.org/TR/CSS21/syndata.html#declaration">CSS 2.1
    *      declarations and properties</a>
    */
-  // VisibleForTesting.
+  @VisibleForTesting
   public static String isValidStyleValue(String value) {
     // Empty value.
     if (value == null || value.length() == 0) {
@@ -166,8 +167,8 @@
 
         /*
          * Almost all tokens are valid within a URL.
-         * 
-         * 
+         *
+         *
          * TODO(jlabanca): Check for unescaped quotes and whitespace in URLs.
          * Quotes and whitespace (other than the optional ones that wrap the
          * URL) should be escaped.
@@ -243,7 +244,7 @@
 
   /**
    * Checks if the provided string is a valid style property name.
-   * 
+   *
    * @param name the style name
    * @see <a
    *      href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS
@@ -260,7 +261,7 @@
 
   /**
    * Checks if the provided string is a valid style property value.
-   * 
+   *
    * @param value the style value
    * @see <a href="http://www.w3.org/TR/CSS21/syndata.html#declaration">CSS 2.1
    *      declarations and properties</a>
@@ -279,7 +280,7 @@
    * {@link #maybeCheckValidStyleName(String)} and
    * {@link #maybeCheckValidStyleValue(String)} should perform their checks in a
    * server-side environment.
-   * 
+   *
    * @param check if true, perform server-side checks.
    */
   public static void setForceCheckValidStyle(boolean check) {
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularB.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularB.gwt.xml
new file mode 100644
index 0000000..f19c870
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularB.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.CircularC" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularC.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularC.gwt.xml
new file mode 100644
index 0000000..b5eae77
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularC.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.CircularFilesetD" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularFilesetD.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularFilesetD.gwt.xml
new file mode 100644
index 0000000..3e9a9e2
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularFilesetD.gwt.xml
@@ -0,0 +1,3 @@
+<module type="fileset">
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.CircularB" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularRoot.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularRoot.gwt.xml
new file mode 100644
index 0000000..402002f
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/CircularRoot.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.CircularB" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateLeft.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateLeft.gwt.xml
new file mode 100644
index 0000000..b115103
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateLeft.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <source path="" includes="Foo.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRight.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRight.gwt.xml
new file mode 100644
index 0000000..b115103
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRight.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <source path="" includes="Foo.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRoot.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRoot.gwt.xml
new file mode 100644
index 0000000..f94563c
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/DuplicateRoot.gwt.xml
@@ -0,0 +1,4 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.DuplicateLeft" />
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.DuplicateRight" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/Foo.java b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/Foo.java
new file mode 100644
index 0000000..0d9d7c8
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/Foo.java
@@ -0,0 +1,20 @@
+/*
+ * 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.testdata.incrementalbuildsystem;
+
+/**
+ * A test class that exists only to be replaced during compilation.
+ */
+public class Foo {
+}
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/LeftFooCreator.java b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/LeftFooCreator.java
new file mode 100644
index 0000000..aed6caa
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/LeftFooCreator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.testdata.incrementalbuildsystem;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * A test class that exists only to trigger generation on Foo.
+ */
+public class LeftFooCreator {
+
+  private Object foo = GWT.create(Foo.class);
+}
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelLeft.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelLeft.gwt.xml
new file mode 100644
index 0000000..7a4d98b
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelLeft.gwt.xml
@@ -0,0 +1,4 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.ProvidesFoo" />
+  <source path="" includes="LeftFooCreator.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRight.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRight.gwt.xml
new file mode 100644
index 0000000..6dfff21
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRight.gwt.xml
@@ -0,0 +1,4 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.ProvidesFoo" />
+  <source path="" includes="RightFooCreator.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRoot.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRoot.gwt.xml
new file mode 100644
index 0000000..ed30116
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ParallelRoot.gwt.xml
@@ -0,0 +1,5 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.ParallelLeft" />
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.ParallelRight" />
+  <source path="" includes="SomeClass.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ProvidesFoo.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ProvidesFoo.gwt.xml
new file mode 100644
index 0000000..2f8caf4
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/ProvidesFoo.gwt.xml
@@ -0,0 +1,6 @@
+<module>
+  <source path="" includes="Foo.java" />
+  <generate-with class="com.google.gwt.dev.FooBarGenerator">
+    <when-type-is class="com.google.gwt.dev.testdata.incrementalbuildsystem.Foo"/>
+  </generate-with>
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/RightFooCreator.java b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/RightFooCreator.java
new file mode 100644
index 0000000..a78bbee
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/RightFooCreator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.testdata.incrementalbuildsystem;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * A test class that exists only to trigger generation on Foo.
+ */
+public class RightFooCreator {
+
+  private Object foo = GWT.create(Foo.class);
+}
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleBottom.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleBottom.gwt.xml
new file mode 100644
index 0000000..bcf06b3
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleBottom.gwt.xml
@@ -0,0 +1,2 @@
+<module>
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleMid.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleMid.gwt.xml
new file mode 100644
index 0000000..85c1023
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleMid.gwt.xml
@@ -0,0 +1,4 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleBottom" />
+  <source path="" includes="Foo.java" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleTop.gwt.xml b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleTop.gwt.xml
new file mode 100644
index 0000000..e752079
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SimpleTop.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+  <inherits name="com.google.gwt.dev.testdata.incrementalbuildsystem.SimpleMid" />
+</module>
diff --git a/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SomeClass.java b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SomeClass.java
new file mode 100644
index 0000000..082ca98
--- /dev/null
+++ b/user/test/com/google/gwt/dev/testdata/incrementalbuildsystem/SomeClass.java
@@ -0,0 +1,20 @@
+/*
+ * 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.testdata.incrementalbuildsystem;
+
+/**
+ * A test class that exists only to give a test module something to compile.
+ */
+public class SomeClass {
+}