CompileModule creates a serialized set of compilation units to represent
a GWT module as a <module>.gwt file.  This is intended to be run when building
a library to support incremental compilation of a module.

Review at http://gwt-code-reviews.appspot.com/1448808


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10304 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/CompileModule.java b/dev/core/src/com/google/gwt/dev/CompileModule.java
new file mode 100644
index 0000000..1b870b7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompileModule.java
@@ -0,0 +1,286 @@
+/*
+ * 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
+ * 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.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.javac.CompilationProblemReporter;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.CompilationUnitArchive;
+import com.google.gwt.dev.util.Memory;
+import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerStrict;
+import com.google.gwt.dev.util.arg.OptionOutDir;
+import com.google.gwt.dev.util.arg.OptionStrict;
+import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Compiles a GWT module into a form that can be re-used by subsequent builds.
+ * 
+ * Takes all compilation units specified on the module source path and write out
+ * CachedCompilationUnits for each one into a file named <module>.gwtar (rhymes 
+ * with the musical instrument). This will reduce compile and dev mode startup 
+ * time if a .gwtar file is up to date and doesn't need to be re-built.
+ * 
+ * Most developers using the GWT SDK won't need to invoke this tool to get
+ * performance improvements. The built-in PersistentUnitCache already saves
+ * compiled modules between builds.
+ * 
+ * This tool is of use to library authors for bundling up a pre-compiled gwt
+ * library for distributions. Projects that include the library will never incur
+ * the penalty of recompiling the library.
+ * 
+ * It can also be useful in a distributed or multi-process build environment, as
+ * separate instances of CompileModule could be invoked in parallel.
+ * 
+ * CompileModule is meant to be used in conjunction with a build tool such as
+ * Apache Ant which can do gross level dependency checking of the inputs and
+ * compute the staleness of a .gwtar file. If the .gwtar file is up to date, the
+ * assumption is that this tool won't be invoked at all.
+ * 
+ * If there are dependent modules that already have their own .gwtar files, they
+ * are assumed good and loaded first. CachedCompilationUnits that already exist
+ * will not be re-written into the <module>.gwtar files.
+ * 
+ * Note: Currently, the order the modules are passed in is the order in which
+ * they will be compiled. This means you should be careful to pass in modules
+ * that depend on other modules in the same list last.
+ * 
+ * TODO(zundel): remove the manual ordering of dependencies.
+ */
+public class CompileModule {
+
+  static class ArgProcessor extends ArgProcessorBase {
+    public ArgProcessor(CompileModuleOptions options) {
+      registerHandler(new ArgHandlerLogLevel(options));
+      registerHandler(new ArgHandlerOutDir(options) {
+        @Override
+        public String[] getDefaultArgs() {
+          return new String[] {getTag(), "bin"};
+        }
+      });
+      registerHandler(new ArgHandlerModuleName(options));
+      registerHandler(new ArgHandlerStrict(options));
+    }
+
+    @Override
+    protected String getName() {
+      return CompileModule.class.getName();
+    }
+  }
+
+  interface CompileModuleOptions extends CompileTaskOptions, OptionOutDir, OptionStrict {
+  }
+
+  static class CompileModuleOptionsImpl extends CompileTaskOptionsImpl implements
+      CompileModuleOptions {
+
+    private File outDir;
+    private boolean strict = false;
+
+    public CompileModuleOptionsImpl() {
+    }
+
+    public CompileModuleOptionsImpl(CompileModuleOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(CompileModuleOptions other) {
+      super.copyFrom(other);
+      setOutDir(other.getOutDir());
+      setStrict(other.isStrict());
+    }
+
+    @Override
+    public File getOutDir() {
+      return outDir;
+    }
+
+    @Override
+    public boolean isStrict() {
+      return strict;
+    }
+
+    @Override
+    public void setOutDir(File outDir) {
+      this.outDir = outDir;
+    }
+
+    @Override
+    public void setStrict(boolean strict) {
+      this.strict = strict;
+    }
+  }
+
+  // TODO(zundel): Many classes in this package share a similar main()
+  // structure. Refactor to reduce redundancy?
+  public static void main(String[] args) {
+    Memory.initialize();
+    SpeedTracerLogger.init();
+    SpeedTracerLogger.start(CompilerEventType.COMPILE_MODULE);
+    /*
+     * NOTE: main always exits with a call to System.exit to terminate any
+     * non-daemon threads that were started in Generators. Typically, this is to
+     * shutdown AWT related threads, since the contract for their termination is
+     * still implementation-dependent.
+     */
+    final CompileModuleOptions options = new CompileModuleOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        @Override
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          return new CompileModule(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  private final CompileModuleOptionsImpl options;
+
+  public CompileModule(CompileModuleOptions options) {
+    this.options = new CompileModuleOptionsImpl(options);
+  }
+
+  /**
+   * Main loop.
+   * 
+   * For each module passed on the command line, populates the compilation state
+   * with compilation units from other archives, compiles all resources in this
+   * module, and writes out all the compilation units that are not already
+   * members of another archive into a new {@link CompilationUnitArchive} file.
+   */
+  public boolean run(final TreeLogger logger) throws UnableToCompleteException {
+
+    // TODO(zundel): There is an optimal order to compile these modules in.
+    // Modify ModuleDefLoader to be able to figure that out and sort them for
+    // us.
+
+    for (String moduleToCompile : options.getModuleNames()) {
+      ModuleDef module;
+      try {
+        module = ModuleDefLoader.loadFromClassPath(logger, moduleToCompile, false);
+      } catch (Throwable e) {
+        CompilationProblemReporter.logAndTranslateException(logger, e);
+        return false;
+      }
+
+      Set<String> archivedResourcePaths = new HashSet<String>();
+      SpeedTracerLogger.Event loadAllArchives =
+          SpeedTracerLogger.start(CompilerEventType.LOAD_ARCHIVE, "module", moduleToCompile);
+      try {
+        Collection<URL> archiveURLs = module.getAllCompilationUnitArchiveURLs();
+        if (logger.isLoggable(TreeLogger.TRACE) && archiveURLs != null) {
+          for (URL archiveURL : archiveURLs) {
+            logger.log(TreeLogger.TRACE, "Found archive: " + archiveURL);
+          }
+        }
+
+        for (URL archiveURL : archiveURLs) {
+          SpeedTracerLogger.Event loadArchive =
+              SpeedTracerLogger.start(CompilerEventType.LOAD_ARCHIVE, "dependentModule", archiveURL
+                  .toString());
+
+          try {
+            CompilationUnitArchive archive = CompilationUnitArchive.createFromURL(archiveURL);
+            // Pre-populate CompilationStateBuilder with .gwtar files
+            CompilationStateBuilder.addArchive(archive);
+
+            // Remember already archived units - we don't want to add them back.
+            if (!archive.getTopModuleName().equals(moduleToCompile)) {
+              for (CompilationUnit unit : archive.getUnits().values()) {
+                archivedResourcePaths.add(unit.getResourcePath());
+              }
+            }
+          } catch (IOException ex) {
+            logger.log(TreeLogger.WARN, "Unable to read: " + archiveURL + ". Skipping: " + ex);
+          } catch (ClassNotFoundException ex) {
+            logger
+                .log(TreeLogger.WARN, "Incompatible archive: " + archiveURL + ". Skipping: " + ex);
+          } finally {
+            loadArchive.end();
+          }
+        }
+      } finally {
+        loadAllArchives.end();
+      }
+
+      CompilationState compilationState;
+      try {
+        compilationState = module.getCompilationState(logger, !options.isStrict());
+      } catch (Throwable e) {
+        CompilationProblemReporter.logAndTranslateException(logger, e);
+        return false;
+      }
+
+      if (options.isStrict() && compilationState.hasErrors()) {
+        logger.log(TreeLogger.ERROR, "Failed to compile " + moduleToCompile);
+        return false;
+      }
+
+      CompilationUnitArchive outputModule = new CompilationUnitArchive(moduleToCompile);
+      for (CompilationUnit unit : compilationState.getCompilationUnits()) {
+        if (!archivedResourcePaths.contains(unit.getResourcePath())) {
+          outputModule.addUnit(unit);
+        }
+      }
+
+      File outputDir = options.getOutDir();
+      if (!outputDir.isDirectory() && !outputDir.mkdirs()) {
+        logger.log(Type.ERROR, "Error creating directories for ouptut: "
+            + outputDir.getAbsolutePath());
+        throw new UnableToCompleteException();
+      }
+
+      String slashedModuleName =
+          module.getName().replace('.', '/') + ModuleDefLoader.COMPILATION_UNIT_ARCHIVE_SUFFIX;
+      File outputFile = new File(outputDir, slashedModuleName);
+      outputFile.getParentFile().mkdirs();
+      logger.log(TreeLogger.INFO, "Writing " + outputModule.getUnits().size() + " units to "
+          + outputFile.getAbsolutePath());
+
+      try {
+        outputModule.writeToFile(outputFile);
+      } catch (IOException ex) {
+        logger.log(Type.ERROR, "Error writing module file: " + outputFile.getAbsolutePath() + ": "
+            + ex);
+        throw new UnableToCompleteException();
+      }
+    }
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index cff1e53..cc8b2ad 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -103,6 +103,10 @@
         ModuleDef moduleDef = loadModule(logger, moduleName, true);
         assert (moduleDef != null);
 
+        if (Boolean.valueOf(System.getProperty("gwt.usearchives"))) {
+          Precompile.preloadArchives(logger, moduleDef);
+        }        
+        
         CompilationState compilationState =
             moduleDef.getCompilationState(logger, !options.isStrict());
         ShellModuleSpaceHost host =
diff --git a/dev/core/src/com/google/gwt/dev/GwtAstBuilderUtil.java b/dev/core/src/com/google/gwt/dev/GwtAstBuilderUtil.java
index c9fe43a..18c66cf 100644
--- a/dev/core/src/com/google/gwt/dev/GwtAstBuilderUtil.java
+++ b/dev/core/src/com/google/gwt/dev/GwtAstBuilderUtil.java
@@ -26,10 +26,7 @@
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
 import com.google.gwt.dev.jjs.CorrelationFactory;
 import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
-import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
 import com.google.gwt.dev.jjs.JJSOptionsImpl;
-import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.impl.BuildTypeMap;
 import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
@@ -119,48 +116,6 @@
     }
   }
 
-  static UnableToCompleteException logAndTranslateException(TreeLogger logger, Throwable e) {
-    if (e instanceof UnableToCompleteException) {
-      // just rethrow
-      return (UnableToCompleteException) e;
-    } else if (e instanceof InternalCompilerException) {
-      TreeLogger topBranch =
-          logger.branch(TreeLogger.ERROR, "An internal compiler exception occurred", e);
-      List<NodeInfo> nodeTrace = ((InternalCompilerException) e).getNodeTrace();
-      for (NodeInfo nodeInfo : nodeTrace) {
-        SourceInfo info = nodeInfo.getSourceInfo();
-        String msg;
-        if (info != null) {
-          String fileName = info.getFileName();
-          fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
-          fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
-          msg = "at " + fileName + "(" + info.getStartLine() + "): ";
-        } else {
-          msg = "<no source info>: ";
-        }
-
-        String description = nodeInfo.getDescription();
-        if (description != null) {
-          msg += description;
-        } else {
-          msg += "<no description available>";
-        }
-        TreeLogger nodeBranch = topBranch.branch(TreeLogger.ERROR, msg, null);
-        String className = nodeInfo.getClassName();
-        if (className != null) {
-          nodeBranch.log(TreeLogger.INFO, className, null);
-        }
-      }
-      return new UnableToCompleteException();
-    } else if (e instanceof VirtualMachineError) {
-      // Always rethrow VM errors (an attempt to wrap may fail).
-      throw (VirtualMachineError) e;
-    } else {
-      logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e);
-      return new UnableToCompleteException();
-    }
-  }
-
   private static void checkForErrors(TreeLogger logger, CompilationUnitDeclaration[] goldenCuds)
       throws UnableToCompleteException {
     for (CompilationUnitDeclaration cud : goldenCuds) {
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index 8d35df5..1c6a67a 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -29,7 +29,9 @@
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.PropertyPermutations;
 import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
 import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.CompilationUnitArchive;
 import com.google.gwt.dev.jjs.AbstractCompiler;
 import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.jjs.JavaScriptCompiler;
@@ -45,7 +47,9 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.management.ManagementFactory;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -154,7 +158,8 @@
       File genDir) {
     Event validateEvent = SpeedTracerLogger.start(CompilerEventType.VALIDATE);
     try {
-      CompilationState compilationState = module.getCompilationState(logger, !jjsOptions.isStrict());
+      CompilationState compilationState =
+          module.getCompilationState(logger, !jjsOptions.isStrict());
       if (jjsOptions.isStrict() && compilationState.hasErrors()) {
         abortDueToStrictMode(logger);
       }
@@ -239,8 +244,13 @@
     // doesn't block when the library is accessed for the first time.
     new GraphicsInitThread().start();
 
+    if (Boolean.valueOf(System.getProperty("gwt.usearchives"))) {
+      preloadArchives(logger, module);
+    }
+
     try {
-      CompilationState compilationState = module.getCompilationState(logger, !jjsOptions.isStrict());
+      CompilationState compilationState =
+          module.getCompilationState(logger, !jjsOptions.isStrict());
       if (jjsOptions.isStrict() && compilationState.hasErrors()) {
         abortDueToStrictMode(logger);
       }
@@ -332,6 +342,36 @@
     }
   }
 
+  /**
+   * Load any .gwtar files into the cache before building CompilationState.
+   */
+  static void preloadArchives(TreeLogger logger, ModuleDef module) {
+    SpeedTracerLogger.Event loadArchive = SpeedTracerLogger.start(CompilerEventType.LOAD_ARCHIVE);
+    try {
+      Collection<URL> archiveURLs = module.getAllCompilationUnitArchiveURLs();
+      if (logger.isLoggable(TreeLogger.TRACE) && archiveURLs != null) {
+        for (URL archiveURL : archiveURLs) {
+          logger.log(TreeLogger.TRACE, "Found archived module: " + archiveURL);
+        }
+      }
+
+      for (URL archiveURL : archiveURLs) {
+        try {
+          CompilationUnitArchive archive = CompilationUnitArchive.createFromURL(archiveURL);
+          // Pre-populate CompilationStateBuilder with .gwt files
+          CompilationStateBuilder.addArchive(archive);
+        } catch (IOException ex) {
+          logger.log(TreeLogger.WARN, "Unable to read: " + archiveURL + ". Skipping: " + ex);
+        } catch (ClassNotFoundException ex) {
+          logger.log(TreeLogger.WARN, "Incompatible archived module: " + archiveURL
+              + ". Skipping: " + ex);
+        }
+      }
+    } finally {
+      loadArchive.end();
+    }
+  }
+
   private static void abortDueToStrictMode(TreeLogger logger) throws UnableToCompleteException {
     logger.log(TreeLogger.ERROR, "Aborting compile due to errors in some input files");
     throw new UnableToCompleteException();
@@ -423,8 +463,8 @@
                 "Precompiling on the start node, because some linkers are not updated");
         if (legacyLinkersLogger.isLoggable(TreeLogger.INFO)) {
           for (Linker linker : linkerContext.findUnshardableLinkers()) {
-            legacyLinkersLogger.log(TreeLogger.INFO, "Linker" + linker.getClass().getCanonicalName()
-                + " is not updated");
+            legacyLinkersLogger.log(TreeLogger.INFO, "Linker"
+                + linker.getClass().getCanonicalName() + " is not updated");
           }
         }
         generateOnShards = false;
@@ -447,8 +487,8 @@
         Util.writeStringAsFile(logger, new File(compilerWorkDir, PERM_COUNT_FILENAME), String
             .valueOf(numPermutations));
         if (branch.isLoggable(TreeLogger.INFO)) {
-          branch.log(TreeLogger.INFO, "Precompilation (minimal) succeeded, number of permutations: "
-            + numPermutations);
+          branch.log(TreeLogger.INFO,
+              "Precompilation (minimal) succeeded, number of permutations: " + numPermutations);
         }
       } else {
         if (options.isValidateOnly()) {
@@ -475,7 +515,7 @@
               .valueOf(permsPrecompiled));
           if (branch.isLoggable(TreeLogger.INFO)) {
             branch.log(TreeLogger.INFO, "Precompilation succeeded, number of permutations: "
-              + permsPrecompiled);
+                + permsPrecompiled);
           }
         }
       }
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 3c3cfb3..92d0e7d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 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
@@ -21,9 +21,9 @@
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationProblemReporter;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationStateBuilder;
-import com.google.gwt.dev.javac.CompilationProblemReporter;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.resource.ResourceOracle;
 import com.google.gwt.dev.resource.impl.DefaultFilters;
@@ -38,8 +38,11 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 
 import java.io.File;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -57,19 +60,22 @@
 public class ModuleDef {
 
   private static final ResourceFilter NON_JAVA_RESOURCES = new ResourceFilter() {
+    @Override
     public boolean allows(String path) {
       return !path.endsWith(".java") && !path.endsWith(".class");
     }
   };
 
-  private static final Comparator<Map.Entry<String, ?>> REV_NAME_CMP = new Comparator<Map.Entry<String, ?>>() {
-    public int compare(Map.Entry<String, ?> entry1, Map.Entry<String, ?> entry2) {
-      String key1 = entry1.getKey();
-      String key2 = entry2.getKey();
-      // intentionally reversed
-      return key2.compareTo(key1);
-    }
-  };
+  private static final Comparator<Map.Entry<String, ?>> REV_NAME_CMP =
+      new Comparator<Map.Entry<String, ?>>() {
+        @Override
+        public int compare(Map.Entry<String, ?> entry1, Map.Entry<String, ?> entry2) {
+          String key1 = entry1.getKey();
+          String key2 = entry2.getKey();
+          // intentionally reversed
+          return key2.compareTo(key1);
+        }
+      };
 
   public static boolean isValidModuleName(String moduleName) {
     String[] parts = moduleName.split("\\.");
@@ -86,6 +92,14 @@
 
   private String activePrimaryLinker;
 
+  /**
+   * A set of URLs for <module>.gwtar files found on the classpath that correspond
+   * to <module>.gwt.xml files loaded as a part of this module's nested load.
+   * 
+   * @see com.google.gwt.dev.CompileModule
+   */
+  private final List<URL> archiveURLs = new ArrayList<URL>();
+
   private boolean collapseAllProperties;
 
   private final DefaultFilters defaultFilters;
@@ -94,13 +108,26 @@
 
   private final Set<File> gwtXmlFiles = new HashSet<File>();
 
+  /**
+   * All resources found on the public path, specified by <public> directives in
+   * modules (or the implicit ./public directory). Marked 'lazy' because it does not
+   * start searching for resources until a query is made.
+   */
   private ResourceOracleImpl lazyPublicOracle;
 
+  /**
+   * A subset of lazySourceOracle, contains files other than .java and .class files.
+   */
   private ResourceOracleImpl lazyResourcesOracle;
 
+  /**
+   * Contains all files from the source path, specified by <source> and <super>
+   * directives in modules (or the implicit ./client directory).
+   */
   private ResourceOracleImpl lazySourceOracle;
 
-  private final Map<String, Class<? extends Linker>> linkerTypesByName = new LinkedHashMap<String, Class<? extends Linker>>();
+  private final Map<String, Class<? extends Linker>> linkerTypesByName =
+      new LinkedHashMap<String, Class<? extends Linker>>();
 
   private final long moduleDefCreationTime = System.currentTimeMillis();
 
@@ -156,48 +183,44 @@
     activeLinkers.add(name);
   }
 
-  public synchronized void addPublicPackage(String publicPackage,
-      String[] includeList, String[] excludeList, String[] skipList,
-      boolean defaultExcludes, boolean caseSensitive) {
+  public synchronized void addPublicPackage(String publicPackage, String[] includeList,
+      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
 
     if (lazyPublicOracle != null) {
       throw new IllegalStateException("Already normalized");
     }
-    publicPrefixSet.add(new PathPrefix(publicPackage,
-        defaultFilters.customResourceFilter(includeList, excludeList,
-            skipList, defaultExcludes, caseSensitive), true, excludeList));
+    publicPrefixSet.add(new PathPrefix(publicPackage, defaultFilters.customResourceFilter(
+        includeList, excludeList, skipList, defaultExcludes, caseSensitive), true, excludeList));
   }
 
-  public void addSourcePackage(String sourcePackage, String[] includeList,
-      String[] excludeList, String[] skipList, boolean defaultExcludes,
-      boolean caseSensitive) {
-    addSourcePackageImpl(sourcePackage, includeList, excludeList,
-        skipList, defaultExcludes, caseSensitive, false);
+  public void addSourcePackage(String sourcePackage, String[] includeList, String[] excludeList,
+      String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
+    addSourcePackageImpl(sourcePackage, includeList, excludeList, skipList, defaultExcludes,
+        caseSensitive, false);
   }
 
   public void addSourcePackageImpl(String sourcePackage, String[] includeList,
-      String[] excludeList, String[] skipList, boolean defaultExcludes,
-      boolean caseSensitive, boolean isSuperSource) {
+      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive,
+      boolean isSuperSource) {
     if (lazySourceOracle != null) {
       throw new IllegalStateException("Already normalized");
     }
-    PathPrefix pathPrefix = new PathPrefix(sourcePackage,
-        defaultFilters.customJavaFilter(includeList, excludeList, skipList,
-            defaultExcludes, caseSensitive), isSuperSource, excludeList);
+    PathPrefix pathPrefix =
+        new PathPrefix(sourcePackage, defaultFilters.customJavaFilter(includeList, excludeList,
+            skipList, defaultExcludes, caseSensitive), isSuperSource, excludeList);
     sourcePrefixSet.add(pathPrefix);
   }
 
-  public void addSuperSourcePackage(String superSourcePackage,
-      String[] includeList, String[] excludeList, String[] skipList,
-      boolean defaultExcludes, boolean caseSensitive) {
-    addSourcePackageImpl(superSourcePackage, includeList, excludeList,
-        skipList, defaultExcludes, caseSensitive, true);
+  public void addSuperSourcePackage(String superSourcePackage, String[] includeList,
+      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
+    addSourcePackageImpl(superSourcePackage, includeList, excludeList, skipList, defaultExcludes,
+        caseSensitive, true);
   }
 
   /**
    * Free up memory no longer needed in later compile stages. After calling this
-   * method, the ResourceOraclewill be empty and unusable. Calling
-   * {@link #refresh(TreeLogger)} will restore them.
+   * method, the ResourceOracle will be empty and unusable. Calling
+   * {@link #refresh()} will restore them.
    */
   public synchronized void clear() {
     if (lazySourceOracle != null) {
@@ -216,15 +239,14 @@
    * linker had been previously added to the set of active linkers, the old
    * active linker will be replaced with the new linker.
    */
-  public void defineLinker(TreeLogger logger, String name,
-      Class<? extends Linker> linker) throws UnableToCompleteException {
+  public void defineLinker(TreeLogger logger, String name, Class<? extends Linker> linker)
+      throws UnableToCompleteException {
     Class<? extends Linker> old = getLinker(name);
     if (old != null) {
       // Redefining an existing name
       if (activePrimaryLinker.equals(name)) {
         // Make sure the new one is also a primary linker
-        if (!linker.getAnnotation(LinkerOrder.class).value().equals(
-            Order.PRIMARY)) {
+        if (!linker.getAnnotation(LinkerOrder.class).value().equals(Order.PRIMARY)) {
           logger.log(TreeLogger.ERROR, "Redefining primary linker " + name
               + " with non-primary implementation " + linker.getName());
           throw new UnableToCompleteException();
@@ -232,8 +254,7 @@
 
       } else if (activeLinkers.contains(name)) {
         // Make sure it's a not a primary linker
-        if (linker.getAnnotation(LinkerOrder.class).value().equals(
-            Order.PRIMARY)) {
+        if (linker.getAnnotation(LinkerOrder.class).value().equals(Order.PRIMARY)) {
           logger.log(TreeLogger.ERROR, "Redefining non-primary linker " + name
               + " with primary implementation " + linker.getName());
           throw new UnableToCompleteException();
@@ -258,7 +279,7 @@
       /*
        * Ensure that URLs that match the servlet mapping, including those that
        * have additional path_info, get routed to the correct servlet.
-       *
+       * 
        * See "Inside Servlets", Second Edition, pg. 208
        */
       if (actual.equals(mapping) || actual.startsWith(mapping + "/")) {
@@ -271,7 +292,7 @@
   /**
    * Returns the Resource for a source file if it is found; <code>null</code>
    * otherwise.
-   *
+   * 
    * @param partialPath the partial path of the source file
    * @return the resource for the requested source file
    */
@@ -298,13 +319,22 @@
     return linkerTypesByName.get(activePrimaryLinker);
   }
 
+  /**
+   * Returns URLs to fetch archives of precompiled compilation units.
+   * 
+   * @see com.google.gwt.dev.CompileModule
+   */
+  public Collection<URL> getAllCompilationUnitArchiveURLs() {
+    return Collections.unmodifiableCollection(archiveURLs);
+  }
+
   public String[] getAllPublicFiles() {
     doRefresh();
     return lazyPublicOracle.getPathNames().toArray(Empty.STRINGS);
   }
 
   /**
-   * Strictly for statistics gathering.  There is no guarantee that the source
+   * Strictly for statistics gathering. There is no guarantee that the source
    * oracle has been initialized.
    */
   public String[] getAllSourceFiles() {
@@ -319,16 +349,17 @@
   public String getCanonicalName() {
     return name;
   }
-  
+
   public CompilationState getCompilationState(TreeLogger logger) throws UnableToCompleteException {
     return getCompilationState(logger, false);
   }
-  
+
   public synchronized CompilationState getCompilationState(TreeLogger logger, boolean suppressErrors)
       throws UnableToCompleteException {
     doRefresh();
     CompilationState compilationState =
-        CompilationStateBuilder.buildFrom(logger, lazySourceOracle.getResources(), null, suppressErrors);
+        CompilationStateBuilder.buildFrom(logger, lazySourceOracle.getResources(), null,
+            suppressErrors);
     checkForSeedTypes(logger, compilationState);
     return compilationState;
   }
@@ -367,8 +398,8 @@
       PathPrefixSet pathPrefixes = lazySourceOracle.getPathPrefixes();
       PathPrefixSet newPathPrefixes = new PathPrefixSet();
       for (PathPrefix pathPrefix : pathPrefixes.values()) {
-        newPathPrefixes.add(new PathPrefix(pathPrefix.getPrefix(),
-            NON_JAVA_RESOURCES, pathPrefix.shouldReroot()));
+        newPathPrefixes.add(new PathPrefix(pathPrefix.getPrefix(), NON_JAVA_RESOURCES, pathPrefix
+            .shouldReroot()));
       }
       lazyResourcesOracle.setPathPrefixes(newPathPrefixes);
       ResourceOracleImpl.refresh(TreeLogger.NULL, lazyResourcesOracle);
@@ -421,7 +452,7 @@
    * For convenience in hosted mode, servlets can be automatically loaded and
    * delegated to via {@link com.google.gwt.dev.shell.GWTShellServlet}. If a
    * servlet is already mapped to the specified path, it is replaced.
-   *
+   * 
    * @param path the url path at which the servlet resides
    * @param servletClassName the name of the servlet to publish
    */
@@ -448,11 +479,15 @@
     this.nameOverride = nameOverride;
   }
 
+  void addCompilationUnitArchiveURL(URL url) {
+    archiveURLs.add(url);
+  }
+
   /**
    * The final method to call when everything is setup. Before calling this
    * method, several of the getter methods may not be called. After calling this
    * method, the add methods may not be called.
-   *
+   * 
    * @param logger Logs the activity.
    */
   synchronized void normalize(TreeLogger logger) {
@@ -498,8 +533,8 @@
     moduleDefNormalize.end();
   }
 
-  private void checkForSeedTypes(TreeLogger logger,
-      CompilationState compilationState) throws UnableToCompleteException {
+  private void checkForSeedTypes(TreeLogger logger, CompilationState compilationState)
+      throws UnableToCompleteException {
     // Sanity check the seed types and don't even start it they're missing.
     boolean seedTypesMissing = false;
     TypeOracle typeOracle = compilationState.getTypeOracle();
@@ -508,8 +543,7 @@
           compilationState);
       seedTypesMissing = true;
     } else {
-      TreeLogger branch = logger.branch(TreeLogger.TRACE,
-          "Finding entry point classes", null);
+      TreeLogger branch = logger.branch(TreeLogger.TRACE, "Finding entry point classes", null);
       String[] typeNames = getEntryPointTypeNames();
       for (int i = 0; i < typeNames.length; i++) {
         String typeName = typeNames[i];
@@ -530,15 +564,15 @@
     if (!needsRefresh) {
       return;
     }
-    Event moduleDefEvent = SpeedTracerLogger.start(
-        CompilerEventType.MODULE_DEF, "phase", "refresh", "module", getName());
+    Event moduleDefEvent =
+        SpeedTracerLogger.start(CompilerEventType.MODULE_DEF, "phase", "refresh", "module",
+            getName());
     // Refresh resource oracles.
     if (lazyResourcesOracle == null) {
-      ResourceOracleImpl.refresh(TreeLogger.NULL, lazyPublicOracle,
-          lazySourceOracle);
+      ResourceOracleImpl.refresh(TreeLogger.NULL, lazyPublicOracle, lazySourceOracle);
     } else {
-      ResourceOracleImpl.refresh(TreeLogger.NULL, lazyPublicOracle,
-          lazySourceOracle, lazyResourcesOracle);
+      ResourceOracleImpl.refresh(TreeLogger.NULL, lazyPublicOracle, lazySourceOracle,
+          lazyResourcesOracle);
     }
     moduleDefEvent.end();
     needsRefresh = false;
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 9713fb4..c4365a9 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -64,6 +64,11 @@
    * Filename suffix used for GWT Module XML files.
    */
   public static final String GWT_MODULE_XML_SUFFIX = ".gwt.xml";
+  
+  /**
+   * Filename suffix used for Precompiled GWT Module files.
+   */
+  public static final String COMPILATION_UNIT_ARCHIVE_SUFFIX = ".gwtar";
 
   /**
    * Keep soft references to loaded modules so the VM can gc them when memory is
@@ -111,7 +116,7 @@
 
   /**
    * Loads a new module from the class path.  Equivalent to
-   * {@link #loadFromClassPath(logger, moduleName, false)}.
+   * {@link #loadFromClassPath(TreeLogger, String, boolean)}.
    *
    * @param logger logs the process
    * @param moduleName the module to load
@@ -187,6 +192,7 @@
   private ModuleDefLoader() {
     this.classLoader = Thread.currentThread().getContextClassLoader();
     this.strategy = new LoadStrategy() {
+      @Override
       public void load(TreeLogger logger, String moduleName, ModuleDef moduleDef)
           throws UnableToCompleteException {
         nestedLoad(logger, moduleName, moduleDef);
@@ -202,6 +208,7 @@
   private ModuleDefLoader(final String[] inherits) {
     this.classLoader = Thread.currentThread().getContextClassLoader();
     this.strategy = new LoadStrategy() {
+      @Override
       public void load(TreeLogger logger, String moduleName, ModuleDef moduleDef)
           throws UnableToCompleteException {
         for (String inherit : inherits) {
@@ -257,6 +264,11 @@
         logger.log(TreeLogger.ERROR, "Error parsing URI", e);
         throw new UnableToCompleteException();
       }
+      String compilationUnitArchiveName = slashedModuleName + ModuleDefLoader.COMPILATION_UNIT_ARCHIVE_SUFFIX;
+      URL compiledModuleURL = classLoader.getResource(compilationUnitArchiveName);
+      if (compiledModuleURL != null) {
+        moduleDef.addCompilationUnitArchiveURL(compiledModuleURL);
+      }
     }
     if (moduleURL == null) {
       String msg = "Unable to find '"
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationProblemReporter.java b/dev/core/src/com/google/gwt/dev/javac/CompilationProblemReporter.java
index f1dc0cb..0f7d450 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationProblemReporter.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationProblemReporter.java
@@ -18,6 +18,10 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
 import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
+import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.util.Messages;
 import com.google.gwt.dev.util.Util;
 
@@ -29,6 +33,7 @@
 import java.net.URL;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
@@ -47,8 +52,61 @@
   }
 
   /**
-   * Provides a meaningful error message when a type is missing from the {@link
-   * import com.google.gwt.core.ext.typeinfo.TypeOracle} or
+   * Used as a convenience to catch all exceptions thrown by the compiler. For
+   * instances of {@link InternalCompilerException}, extra diagnostics are
+   * printed.
+   * 
+   * @param logger logger used to report errors to the console
+   * @param e the exception to analyze and log
+   * @return Always returns an instance of {@link UnableToCompleteException} so
+   *         that the calling method can declare a more narrow 'throws
+   *         UnableToCompleteException'
+   */
+  public static UnableToCompleteException logAndTranslateException(TreeLogger logger, Throwable e) {
+    if (e instanceof UnableToCompleteException) {
+      // just rethrow
+      return (UnableToCompleteException) e;
+    } else if (e instanceof InternalCompilerException) {
+      TreeLogger topBranch =
+          logger.branch(TreeLogger.ERROR, "An internal compiler exception occurred", e);
+      List<NodeInfo> nodeTrace = ((InternalCompilerException) e).getNodeTrace();
+      for (NodeInfo nodeInfo : nodeTrace) {
+        SourceInfo info = nodeInfo.getSourceInfo();
+        String msg;
+        if (info != null) {
+          String fileName = info.getFileName();
+          fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
+          fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
+          msg = "at " + fileName + "(" + info.getStartLine() + "): ";
+        } else {
+          msg = "<no source info>: ";
+        }
+
+        String description = nodeInfo.getDescription();
+        if (description != null) {
+          msg += description;
+        } else {
+          msg += "<no description available>";
+        }
+        TreeLogger nodeBranch = topBranch.branch(TreeLogger.ERROR, msg, null);
+        String className = nodeInfo.getClassName();
+        if (className != null) {
+          nodeBranch.log(TreeLogger.INFO, className, null);
+        }
+      }
+      return new UnableToCompleteException();
+    } else if (e instanceof VirtualMachineError) {
+      // Always rethrow VM errors (an attempt to wrap may fail).
+      throw (VirtualMachineError) e;
+    } else {
+      logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e);
+      return new UnableToCompleteException();
+    }
+  }
+
+  /**
+   * Provides a meaningful error message when a type is missing from the
+   * {@link com.google.gwt.core.ext.typeinfo.TypeOracle} or
    * {@link com.google.gwt.dev.shell.CompilingClassLoader}.
    * 
    * @param logger logger for logging errors to the console
@@ -85,6 +143,13 @@
     }
   }
 
+  /**
+   * Walk the compilation state and report errors if they exist.
+   * 
+   * @param logger logger for reporting errors to the console
+   * @param compilationState contains units that might contain errors
+   * @param suppressErrors See {@link #reportErrors(TreeLogger, CompilationUnit, boolean)}
+   */
   public static void reportAllErrors(TreeLogger logger, CompilationState compilationState,
       boolean suppressErrors) {
     for (CompilationUnit unit : compilationState.getCompilationUnits()) {
@@ -190,6 +255,7 @@
         CompilationProblemReporter.reportErrors(logger, unit.getProblems(), unit
             .getResourceLocation(), unit.isError(), new SourceFetcher() {
 
+          @Override
           public String getSource() {
             return unit.getSource();
           }
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 36b7309..02da551 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -66,6 +66,7 @@
 
     private final class UnitProcessorImpl implements UnitProcessor {
 
+      @Override
       public void process(CompilationUnitBuilder builder, CompilationUnitDeclaration cud,
           List<CompiledClass> compiledClasses) {
         Event event = SpeedTracerLogger.start(DevModeEventType.CSB_PROCESS);
@@ -80,6 +81,7 @@
           final Set<String> jsniDeps = new HashSet<String>();
           Map<String, Binding> jsniRefs = new HashMap<String, Binding>();
           JsniChecker.check(cud, jsoState, jsniMethods, jsniRefs, new JsniChecker.TypeResolver() {
+            @Override
             public ReferenceBinding resolveType(String typeName) {
               ReferenceBinding resolveType = compiler.resolveType(typeName);
               if (resolveType != null) {
@@ -147,8 +149,6 @@
 
     private final GwtAstBuilder astBuilder = new GwtAstBuilder();
 
-    private final boolean suppressErrors;
-
     private transient LinkedBlockingQueue<CompilationUnitBuilder> buildQueue;
 
     /**
@@ -162,6 +162,8 @@
     private final JSORestrictionsChecker.CheckerState jsoState =
         new JSORestrictionsChecker.CheckerState();
 
+    private final boolean suppressErrors;
+
     public CompileMoreLater(AdditionalTypeProviderDelegate delegate, boolean suppressErrors) {
       compiler.setAdditionalTypeProviderDelegate(delegate);
       this.suppressErrors = suppressErrors;
@@ -323,7 +325,8 @@
       if (suppressErrors && errorCount > 0 && !logger.isLoggable(TreeLogger.TRACE)
           && logger.isLoggable(TreeLogger.INFO)) {
         logger.log(TreeLogger.INFO, "Ignored " + errorCount + " unit" + (errorCount > 1 ? "s" : "")
-            + " with compilation errors in first pass.  Specify -logLevel DEBUG to see all errors");
+            + " with compilation errors in first pass.\n"
+            + "Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.");
       }
       return resultUnits;
     }
@@ -331,6 +334,26 @@
 
   private static final CompilationStateBuilder instance = new CompilationStateBuilder();
 
+  /**
+   * Use previously compiled {@link CompilationUnit}s to pre-populate the unit
+   * cache.
+   */
+  public static void addArchive(CompilationUnitArchive module) {
+    UnitCache unitCache = instance.unitCache;
+    for (CompilationUnit unit : module.getUnits().values()) {
+      CompilationUnit cachedCompilationUnit = unitCache.find(unit.getResourcePath());
+      // A previously cached unit might be from the persistent cache or another
+      // archive
+      if (cachedCompilationUnit == null
+          || cachedCompilationUnit.getLastModified() < unit.getLastModified()) {
+        // TODO(zundel): mark these units as being a part of an archive.
+        // that way, the persistent unit cache won't need to bother to write
+        // them out.
+        unitCache.addArchivedUnit(unit);
+      }
+    }
+  }
+
   public static CompilationState buildFrom(TreeLogger logger, Set<Resource> resources) {
     return buildFrom(logger, resources, null, false);
   }
@@ -355,7 +378,7 @@
   }
 
   /**
-   * Called to setup the directory where the persistent {@link ComplationUnit}
+   * Called to setup the directory where the persistent {@link CompilationUnit}
    * cache should be stored. Only the first call to init() will have an effect.
    */
   public static synchronized void init(TreeLogger logger, File cacheDirectory) {
@@ -371,7 +394,7 @@
   /**
    * Build a new compilation state from a source oracle. Allow the caller to
    * specify a compiler delegate that will handle undefined names.
-   *
+   * 
    * TODO: maybe use a finer brush than to synchronize the whole thing.
    */
   public synchronized CompilationState doBuildFrom(TreeLogger logger, Set<Resource> resources,
@@ -394,13 +417,13 @@
           new ResourceCompilationUnitBuilder(typeName, resource);
 
       CompilationUnit cachedUnit = unitCache.find(resource.getPathPrefix() + resource.getPath());
-      
+
       // Try to rescue cached units from previous sessions where a jar has been
       // recompiled.
       if (cachedUnit != null && cachedUnit.getLastModified() != resource.getLastModified()) {
         unitCache.remove(cachedUnit);
-        if (cachedUnit instanceof CachedCompilationUnit && 
-            cachedUnit.getContentId().equals(builder.getContentId())) {
+        if (cachedUnit instanceof CachedCompilationUnit
+            && cachedUnit.getContentId().equals(builder.getContentId())) {
           CachedCompilationUnit updatedUnit =
               new CachedCompilationUnit((CachedCompilationUnit) cachedUnit, resource
                   .getLastModified(), resource.getLocation());
@@ -434,7 +457,7 @@
 
   /**
    * Compile new generated units into an existing state.
-   *
+   * 
    * TODO: maybe use a finer brush than to synchronize the whole thing.
    */
   synchronized Collection<CompilationUnit> doBuildGeneratedTypes(TreeLogger logger,
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index e9c8881..fd30f4f 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -175,9 +175,10 @@
 
   protected static final DiskCache diskCache = DiskCache.INSTANCE;
 
-  static final Comparator<CompilationUnit> COMPARATOR = new Comparator<CompilationUnit>() {
+  public static final Comparator<CompilationUnit> COMPARATOR = new Comparator<CompilationUnit>() {
+    @Override
     public int compare(CompilationUnit o1, CompilationUnit o2) {
-      return o1.getTypeName().compareTo(o2.getTypeName());
+      return o1.getResourcePath().compareTo(o2.getResourcePath());
     }
   };
 
@@ -210,8 +211,8 @@
   private transient Map<String, String> anonymousClassMap = null;
 
   /**
-   * Returns the unit as an instance of {@link CachedCompilationUnit}, making
-   * a copy if necessary.
+   * Returns the unit as an instance of {@link CachedCompilationUnit}, making a
+   * copy if necessary.
    */
   public abstract CachedCompilationUnit asCachedCompilationUnit();
 
@@ -293,7 +294,7 @@
    * for this unit originated. This should be unique for each unit compiled to
    * create a module.
    * 
-   * @see {@link com.google.gwt.dev.resource.Resource#getLocation()}
+   * @see com.google.gwt.dev.resource.Resource#getLocation()
    */
   public abstract String getResourceLocation();
 
@@ -301,8 +302,8 @@
    * Returns the full abstract path of the resource. If a resource has been
    * re-rooted, this path should include any path prefix that was stripped.
    * 
-   * @see {@link com.google.gwt.dev.resource.Resource#getPath()} and
-   *      {@link com.google.gwt.dev.resource.Resource#getPathPrefix()}
+   * @see com.google.gwt.dev.resource.Resource#getPath() 
+   * @see com.google.gwt.dev.resource.Resource#getPathPrefix()
    */
   public abstract String getResourcePath();
 
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitArchive.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitArchive.java
new file mode 100644
index 0000000..6cf2468
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitArchive.java
@@ -0,0 +1,138 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * This class represents a file that contains {@link CachedCompilationUnit}s
+ * that make up a module.
+ * 
+ * Intended to work with other CompilationUnitArchives on the class path to
+ * allow a project to be compiled incrementally. Therefore, one file may not
+ * contain all dependent compilation units. To get all dependent compilation
+ * units, the program will need to load all archives, plus any files not
+ * contained in any archive.
+ *
+ * No mater how the archive is created, when serialized, the output file 
+ * should be deterministic.
+ */
+public class CompilationUnitArchive implements Serializable {
+
+  public static CompilationUnitArchive createFromFile(File location) throws IOException,
+      ClassNotFoundException {
+    return createFromStream(new FileInputStream(location));
+  }
+
+  public static CompilationUnitArchive createFromStream(InputStream stream) throws IOException,
+      ClassNotFoundException {
+    ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(stream));
+    CompilationUnitArchive result = (CompilationUnitArchive) ois.readObject();
+    ois.close();
+    return result;
+  }
+
+  public static CompilationUnitArchive createFromURL(URL location) throws IOException,
+      ClassNotFoundException {
+    return createFromStream(location.openConnection().getInputStream());
+  }
+
+  private final String topModuleName;
+  private transient Map<String, CompilationUnit> units;
+
+  /**
+   * Create an archive object.  Note that data is retained in memory only until
+   * the {@link #writeToFile(File)} method is invoked.
+   * 
+   * @param topModuleName The name of the module used to compile this archive.
+   *          That is, the original parameter passed to
+   *          {@link com.google.gwt.dev.CompileModule}.
+   */
+  public CompilationUnitArchive(String topModuleName) {
+    units = new TreeMap<String, CompilationUnit>();
+    this.topModuleName = topModuleName;
+  }
+
+  /**
+   * Add a compilation unit to the archive.
+   */
+  public void addUnit(CompilationUnit unit) {
+    units.put(unit.getResourcePath(), unit);
+  }
+
+  public CompilationUnit findUnit(String resourcePath) {
+    return units.get(resourcePath);
+  }
+
+  /**
+   * The name of the module used to compile this archive. 
+   */
+  public String getTopModuleName() {
+    return topModuleName;
+  }
+
+  /**
+   * Retrieve all units stored in this archive.
+   */
+  public Map<String, CompilationUnit> getUnits() {
+    return ImmutableMap.copyOf(units);
+  }
+
+  /**
+   * Persists the units currently stored in the archive to the specified file.  The file
+   * is immediately closed. 
+   */
+  public void writeToFile(File location) throws IOException {
+    ObjectOutputStream oos =
+        new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(location)));
+    oos.writeObject(this);
+    oos.close();
+  }
+
+  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+    stream.defaultReadObject();
+    units = new TreeMap<String, CompilationUnit>();
+    CompilationUnit unitsIn[] = (CompilationUnit[]) stream.readObject();
+    for (CompilationUnit unit : unitsIn) {
+      assert unit != null;
+      addUnit(unit);
+    }
+  }
+
+  // CompilationUnits are serialized as a sorted array in order to make sure the
+  // output format is deterministic.
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.defaultWriteObject();
+    CompilationUnit unitsOut[] = units.values().toArray(new CompilationUnit[units.size()]);
+    Arrays.sort(unitsOut, CompilationUnit.COMPARATOR);
+    stream.writeObject(unitsOut);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/MemoryUnitCache.java b/dev/core/src/com/google/gwt/dev/javac/MemoryUnitCache.java
index f7c4bbf..971a1d8 100644
--- a/dev/core/src/com/google/gwt/dev/javac/MemoryUnitCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/MemoryUnitCache.java
@@ -34,8 +34,8 @@
    * Storage for a compilation unit in the map.
    */
   protected static class UnitCacheEntry {
-    private final CompilationUnit unit;
     private final UnitOrigin origin;
+    private final CompilationUnit unit;
 
     protected UnitCacheEntry(CompilationUnit unit, UnitOrigin source) {
       this.unit = unit;
@@ -44,11 +44,11 @@
 
     public UnitOrigin getOrigin() {
       return origin;
-    };
+    }
 
     public CompilationUnit getUnit() {
       return unit;
-    };
+    }
   }
 
   /**
@@ -58,6 +58,11 @@
    */
   protected static enum UnitOrigin {
     /**
+     * Unit was loaded from an archive.
+     */
+    ARCHIVE,
+
+    /**
      * Unit was loaded from persistent store.
      */
     PERSISTENT,
@@ -89,24 +94,29 @@
   /**
    * Adds a new entry into the cache.
    */
+  @Override
   public void add(CompilationUnit newUnit) {
-    UnitCacheEntry newEntry = new UnitCacheEntry(newUnit, UnitOrigin.RUN_TIME);
-    String resourcePath = newUnit.getResourcePath();
-    UnitCacheEntry oldEntry = unitMap.get(resourcePath);
-    if (oldEntry != null) {
-      remove(oldEntry.getUnit());
-    }
-    unitMap.put(resourcePath, newEntry);
-    unitMapByContentId.put(newUnit.getContentId(), newEntry);
+    add(newUnit, UnitOrigin.RUN_TIME);
+  }
+
+  /**
+   * Adds a new entry into the cache, but marks it as already coming from a
+   * persistent archive. This means it doesn't need to be saved out to disk.
+   */
+  @Override
+  public void addArchivedUnit(CompilationUnit newUnit) {
+    add(newUnit, UnitOrigin.ARCHIVE);
   }
 
   /**
    * This method is a no-op for an in-memory cache.
    */
+  @Override
   public synchronized void cleanup(final TreeLogger logger) {
     // do nothing.
   }
 
+  @Override
   public CompilationUnit find(ContentId contentId) {
     UnitCacheEntry entry = unitMapByContentId.get(contentId);
     if (entry != null) {
@@ -115,6 +125,7 @@
     return null;
   }
 
+  @Override
   public CompilationUnit find(String resourcePath) {
     UnitCacheEntry entry = unitMap.get(resourcePath);
     if (entry != null) {
@@ -123,8 +134,20 @@
     return null;
   }
 
+  @Override
   public void remove(CompilationUnit unit) {
     unitMap.remove(unit.getResourcePath());
     unitMapByContentId.remove(unit.getContentId());
   }
+
+  private void add(CompilationUnit newUnit, UnitOrigin origin) {
+    UnitCacheEntry newEntry = new UnitCacheEntry(newUnit, origin);
+    String resourcePath = newUnit.getResourcePath();
+    UnitCacheEntry oldEntry = unitMap.get(resourcePath);
+    if (oldEntry != null) {
+      remove(oldEntry.getUnit());
+    }
+    unitMap.put(resourcePath, newEntry);
+    unitMapByContentId.put(newUnit.getContentId(), newEntry);
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
index d11de53..17065dd 100644
--- a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
@@ -219,6 +219,7 @@
                 assert unitWriteQueue.size() == 0;
                 break;
               } else {
+                assert msg.unitCacheEntry.getOrigin() != UnitOrigin.ARCHIVE;
                 CompilationUnit unit = msg.unitCacheEntry.getUnit();
                 assert unit != null;
                 stream.writeObject(unit);
@@ -346,8 +347,9 @@
   public void add(CompilationUnit newUnit) {
     unitCacheMapLoader.await();
     super.add(newUnit);
+    UnitCacheEntry entry = unitMap.get(newUnit.getResourcePath());
     addCount.getAndIncrement();
-    unitWriteQueue.add(new UnitWriteMessage(unitMap.get(newUnit.getResourcePath())));
+    unitWriteQueue.add(new UnitWriteMessage(entry));
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/javac/UnitCache.java b/dev/core/src/com/google/gwt/dev/javac/UnitCache.java
index ae5fec2..c32f502 100644
--- a/dev/core/src/com/google/gwt/dev/javac/UnitCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/UnitCache.java
@@ -28,6 +28,13 @@
   void add(CompilationUnit newUnit);
 
   /**
+   * Adds a new entry into the cache, but marks it as already coming from a
+   * persistent archive. This means it doesn't need to be saved out to disk in a
+   * persistent implementation of the cache.
+   */
+  void addArchivedUnit(CompilationUnit newUnit);
+
+  /**
    * Each run of the compiler should call {@link #cleanup(TreeLogger)} when
    * finished adding units to the cache so that cache files from previous runs
    * can be purged from a persistent cache.
@@ -43,7 +50,7 @@
    * Lookup a {@link CompilationUnit} by resource path. This should include any
    * path prefix that may have been was stripped to reroot the resource.
    *
-   * @see {@link CompilationUnit#getResourcePath()}
+   * @see CompilationUnit#getResourcePath()
    */
   CompilationUnit find(String resourcePath);
 
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 819f123..bac3201 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -47,7 +47,6 @@
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
 import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
 import com.google.gwt.dev.jjs.CorrelationFactory.RealCorrelationFactory;
-import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
 import com.google.gwt.dev.jjs.UnifiedAst.AST;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
@@ -462,7 +461,7 @@
       }
       return toReturn;
     } catch (Throwable e) {
-      throw logAndTranslateException(logger, e);
+      throw CompilationProblemReporter.logAndTranslateException(logger, e);
     } finally {
       jjsCompilePermutationEvent.end();
     }
@@ -709,7 +708,7 @@
       createUnifiedAstEvent.end();
       return result;
     } catch (Throwable e) {
-      throw logAndTranslateException(logger, e);
+      throw CompilationProblemReporter.logAndTranslateException(logger, e);
     } finally {
     }
   }
@@ -1146,48 +1145,6 @@
     return v.classNames.toArray(new String[v.classNames.size()]);
   }
 
-  private static UnableToCompleteException logAndTranslateException(TreeLogger logger, Throwable e) {
-    if (e instanceof UnableToCompleteException) {
-      // just rethrow
-      return (UnableToCompleteException) e;
-    } else if (e instanceof InternalCompilerException) {
-      TreeLogger topBranch =
-          logger.branch(TreeLogger.ERROR, "An internal compiler exception occurred", e);
-      List<NodeInfo> nodeTrace = ((InternalCompilerException) e).getNodeTrace();
-      for (NodeInfo nodeInfo : nodeTrace) {
-        SourceInfo info = nodeInfo.getSourceInfo();
-        String msg;
-        if (info != null) {
-          String fileName = info.getFileName();
-          fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
-          fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
-          msg = "at " + fileName + "(" + info.getStartLine() + "): ";
-        } else {
-          msg = "<no source info>: ";
-        }
-
-        String description = nodeInfo.getDescription();
-        if (description != null) {
-          msg += description;
-        } else {
-          msg += "<no description available>";
-        }
-        TreeLogger nodeBranch = topBranch.branch(TreeLogger.ERROR, msg, null);
-        String className = nodeInfo.getClassName();
-        if (className != null && logger.isLoggable(TreeLogger.INFO)) {
-          nodeBranch.log(TreeLogger.INFO, className, null);
-        }
-      }
-      return new UnableToCompleteException();
-    } else if (e instanceof VirtualMachineError) {
-      // Always rethrow VM errors (an attempt to wrap may fail).
-      throw (VirtualMachineError) e;
-    } else {
-      logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e);
-      return new UnableToCompleteException();
-    }
-  }
-
   /*
    * This method is intended as a central location for producing optional
    * tracking output. This will be called after all optimization/normalization
diff --git a/dev/core/src/com/google/gwt/dev/util/log/speedtracer/CompilerEventType.java b/dev/core/src/com/google/gwt/dev/util/log/speedtracer/CompilerEventType.java
index abaf3c8..0d4c6f9 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/speedtracer/CompilerEventType.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/speedtracer/CompilerEventType.java
@@ -70,7 +70,8 @@
   CHECK_FOR_ERRORS("CheckForErrors", "DimGrey"), //
   GRAPHICS_INIT("Graphics2D.createGraphics()", "Blue"), //
   ANALYZE_MODULE("AnalyzeModule", "LightBlue"), //
-  COMPILE_MODULE("CompileModule", "LightBlue"); //
+  COMPILE_MODULE("CompileModule", "LightBlue"), 
+  LOAD_ARCHIVE("LoadArchive", "DarkSlateBlue"); //
 
   final String cssColor;
   final String name;
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationUnitArchiveTest.java b/dev/core/test/com/google/gwt/dev/javac/CompilationUnitArchiveTest.java
new file mode 100644
index 0000000..1ef790d
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationUnitArchiveTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.util.tools.Utility;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class CompilationUnitArchiveTest extends TestCase {
+  private static final String MOCK_TYPE_1 = "com.example.Foo";
+  private static final String MOCK_TYPE_2 = "com.example.Bar";
+  private static final String MOCK_TYPE_3 = "com.example.Baz";
+
+  /**
+   * Some build systems insist that given the same inputs, the compiler produce
+   * the same output every time.
+   */
+  public void testDeterministicOutput() throws IOException {
+    int numMockTypes = 100;
+    CompilationUnit mockUnits[] = new CompilationUnit[numMockTypes];
+    for (int i = 0; i < numMockTypes; i++) {
+      mockUnits[i] = new MockCompilationUnit("com.example.MockType" + i, "Dummy Source " + i);
+    }
+
+    File tmpDir = Utility.makeTemporaryDirectory(null, "cgmt-");
+    int numLoops = 100;
+    String lastStrongName = null;
+    for (int i = 0; i < numLoops; i++) {
+      File tmpFile = new File(tmpDir, "module" + i + ".ser");
+      tmpFile.deleteOnExit();
+      scrambleArray(mockUnits);
+      CompilationUnitArchive archive = new CompilationUnitArchive("com.example.Module");
+      for (int j = 0; j < numMockTypes; j++) {
+        archive.addUnit(mockUnits[j]);
+      }
+      archive.writeToFile(tmpFile);
+      // grab the md5 signature of the file as a string
+      byte[] bytes = Util.readFileAsBytes(tmpFile);
+      tmpFile.delete();
+      String thisStrongName = Util.computeStrongName(bytes);
+      if (lastStrongName != null) {
+        assertEquals("loop " + i, thisStrongName, lastStrongName);
+      }
+      lastStrongName = thisStrongName;
+    }
+    tmpDir.delete();
+  }
+
+  public void testReadWrite() throws IOException, ClassNotFoundException {
+    CompilationUnitArchive archive1 = new CompilationUnitArchive("com.example.Foo");
+    MockCompilationUnit unit1 = new MockCompilationUnit(MOCK_TYPE_1, "Foo");
+    MockCompilationUnit unit2 = new MockCompilationUnit(MOCK_TYPE_2, "Bar");
+    MockCompilationUnit unit3 = new MockCompilationUnit(MOCK_TYPE_3, "Baz");
+
+    archive1.addUnit(unit1);
+    archive1.addUnit(unit2);
+    archive1.addUnit(unit3);
+    
+    assertEquals(3, archive1.getUnits().size());
+    compareUnits(unit1, archive1, MOCK_TYPE_1);
+    compareUnits(unit2, archive1, MOCK_TYPE_2);
+    compareUnits(unit3, archive1, MOCK_TYPE_3);
+
+    File tmp = File.createTempFile("cu-archive-test", ".ser");
+    tmp.deleteOnExit();
+    archive1.writeToFile(tmp);
+    CompilationUnitArchive archive2 = CompilationUnitArchive.createFromFile(tmp);
+    
+    assertEquals(3, archive2.getUnits().size());
+    compareUnits(unit1, archive2, MOCK_TYPE_1);
+    compareUnits(unit2, archive2, MOCK_TYPE_2);
+    compareUnits(unit3, archive2, MOCK_TYPE_3);
+  }
+
+  private void compareUnits(MockCompilationUnit unit, CompilationUnitArchive archive, String lookupType) {
+    CompilationUnit found = archive.findUnit(unit.getResourcePath());
+    assertEquals(found.getTypeName(), lookupType);
+    assertEquals(found.getResourceLocation(), unit.getResourceLocation());
+    assertEquals(found.getSource(), unit.getSource());
+  }
+
+  private void scrambleArray(Object[] array) {
+    final int max = array.length;
+    for (int i = 0; i < max; i++) {
+      int randomIdx;
+      do {
+        randomIdx = (int) (Math.random() * (max - 1));
+      } while (i == randomIdx);
+
+      Object tmp = array[randomIdx];
+      array[randomIdx] = array[i % array.length];
+      array[i % array.length] = tmp;
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
index 2b16917..77f9e0a 100644
--- a/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
+++ b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
@@ -25,9 +25,9 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * Used by {@link MemoryUnitTest} and {@link PersistentUnitTest}.
+ * Used by {@link MemoryUnitCacheTest} and {@link PersistentUnitCacheTest}.
  */
-class MockCompilationUnit extends CompilationUnit {
+public class MockCompilationUnit extends CompilationUnit {
   private static final AtomicInteger nextTimestamp = new AtomicInteger(1);
 
   private final ContentId contentId;
diff --git a/servlet/build.xml b/servlet/build.xml
index c5cb923..c33229f 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -37,6 +37,7 @@
         <exclude name="com/google/gwt/junit/remote/**" />
         <exclude name="com/google/gwt/junit/server/**" />
         <exclude name="com/google/gwt/benchmarks/*" />
+        <exclude name="**/*.gwtar" />
       </fileset>
 
       <!-- additional dependencies (used by scripts). -->
diff --git a/user/build.xml b/user/build.xml
index bb1d8dd..579e7a6 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -141,7 +141,65 @@
     </gwt.javac>
   </target>
 
-  <target name="build" depends="compile"
+  <!-- Precompile some GWT modules to speed up end-user builds   -->
+  <!-- TODO(zundel): Find a way to precompile  all modules       -->
+  <!--               without tedious manual specification        -->
+  <target name="precompile.modules" depends="compile">
+    <outofdate>
+      <sourcefiles>
+        <fileset dir="${gwt.root}/user/src" />
+        <fileset dir="${gwt.root}/user/super" />
+        <fileset dir="${gwt.root}/dev/core/src" />
+        <fileset dir="${gwt.root}/dev/core/super" />
+        <fileset file="${gwt.dev.jar}" />
+      </sourcefiles>
+      <targetfiles>
+        <!-- TODO(zundel): this is a mechanical transform        -->
+        <!-- from module name.  There must be a better way.      -->
+        <pathelement location="${project.build}/bin/com/google/gwt/core/Core.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/json/JSON.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/regexp/RegExp.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/user/User.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/xml/XML.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/rpc/RPC.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/debug/Debug.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/place/Place.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/activity/Activity.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/web/bindery/event/Event.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/web/bindery/autobean/AutoBean.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/web/bindery/requestfactory/RequestFactory.gwtar" />
+        <pathelement location="${project.build}/bin/com/google/gwt/logging/Logging.gwtar" />
+      </targetfiles>
+      <sequential>
+        <compileModule>
+          <module>
+            <!-- Order is important!  Modules that inherit from  -->
+            <!-- others should come later in the list.           -->
+            <!-- All modules depend on Core                      -->
+            <arg value="com.google.gwt.core.Core" />
+            <arg value="com.google.gwt.json.JSON" />
+            <arg value="com.google.gwt.regexp.RegExp" />
+            <arg value="com.google.gwt.user.User" />
+    
+            <!-- Below are modules that depend on User           -->
+            <arg value="com.google.gwt.xml.XML" />
+            <arg value="com.google.gwt.rpc.RPC" />
+            <arg value="com.google.gwt.debug.Debug" />
+            <arg value="com.google.gwt.logging.Logging" />
+    
+            <arg value="com.google.gwt.place.Place" />
+            <arg value="com.google.gwt.activity.Activity" />
+    
+            <arg value="com.google.web.bindery.event.Event" />
+            <arg value="com.google.web.bindery.autobean.AutoBean" />
+            <arg value="com.google.web.bindery.requestfactory.RequestFactory" />
+          </module>
+        </compileModule>
+      </sequential>
+    </outofdate>
+  </target>
+
+  <target name="build" depends="precompile.modules"
       description="Build and package this project">
     <mkdir dir="${gwt.build.lib}" />
     <gwt.jar>
@@ -704,6 +762,31 @@
       </sequential>
     </macrodef>
 
+    <macrodef name="compileModule">
+      <element name="module" />
+      <sequential>
+        <gwt.timer name="Pre-compile module">
+          <java classname="com.google.gwt.dev.CompileModule" fork="yes" failonerror="true">
+            <classpath>
+              <pathelement location="${gwt.root}/user/src" />
+              <pathelement location="${gwt.root}/user/super" />
+              <pathelement location="${gwt.root}/dev/core/src" />
+              <pathelement location="${gwt.root}/dev/core/super" />
+              <pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
+              <pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA-sources.jar" />
+              <pathelement location="${gwt.root}/build/out/user/bin" />
+              <pathelement location="${gwt.dev.jar}" />
+            </classpath>
+            <jvmarg value="-Xmx512M" />
+            <module />
+            <arg value="-strict" />
+            <arg value="-out" />
+            <arg value="${project.build}/bin" />
+          </java>
+        </gwt.timer>
+      </sequential>
+    </macrodef>
+
     <target name="tck.report">
       <mkdir dir="${junit.out}/tck-report" />
       <mkdir dir="${junit.out}/tck-report/text" />
diff --git a/user/src/com/google/gwt/activity/Activity.gwt.xml b/user/src/com/google/gwt/activity/Activity.gwt.xml
index 78b19d0..dbdd296 100644
--- a/user/src/com/google/gwt/activity/Activity.gwt.xml
+++ b/user/src/com/google/gwt/activity/Activity.gwt.xml
@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
 <module>
-  <inherits name='com.google.gwt.user.User'/>
   <inherits name='com.google.gwt.place.Place'/>
 
   <source path="shared"/>
diff --git a/user/src/com/google/gwt/debug/Debug.gwt.xml b/user/src/com/google/gwt/debug/Debug.gwt.xml
index 338588a..9d77e6d 100644
--- a/user/src/com/google/gwt/debug/Debug.gwt.xml
+++ b/user/src/com/google/gwt/debug/Debug.gwt.xml
@@ -14,6 +14,8 @@
 
 <!-- Adds debug ID support for UIObjects.                                   -->
 <module>
+  <inherits name="com.google.gwt.core.Core" />
+  
   <!-- Enable or disable the UIObject.ensureDebugID method -->
   <define-property name="gwt.enableDebugId" values="true, false"/>
   
diff --git a/user/src/com/google/gwt/i18n/CldrLocales.gwt.xml b/user/src/com/google/gwt/i18n/CldrLocales.gwt.xml
index 6c80f23..9a109da 100644
--- a/user/src/com/google/gwt/i18n/CldrLocales.gwt.xml
+++ b/user/src/com/google/gwt/i18n/CldrLocales.gwt.xml
@@ -14,6 +14,8 @@
 
 <!-- List of all locales imported into GWT from CLDR, as runtime locales -->
 <module>
+  <inherits name="com.google.gwt.user.User"/>
+  <inherits name="com.google.gwt.i18n.I18N"/>
   <extend-configuration-property name="runtime.locales" value="aa"/>
   <extend-configuration-property name="runtime.locales" value="aa_DJ"/>
   <extend-configuration-property name="runtime.locales" value="aa_ER"/>
diff --git a/user/src/com/google/gwt/i18n/I18N.gwt.xml b/user/src/com/google/gwt/i18n/I18N.gwt.xml
index 6473a34..15f35ab 100644
--- a/user/src/com/google/gwt/i18n/I18N.gwt.xml
+++ b/user/src/com/google/gwt/i18n/I18N.gwt.xml
@@ -15,6 +15,7 @@
 <!-- Internationalization support.                                          -->
 <!--                                                                        -->
 <module>
+  <inherits name="com.google.gwt.core.Core" />
   <inherits name="com.google.gwt.regexp.RegExp"/>
   <inherits name="com.google.gwt.safehtml.SafeHtml"/>
   <source path="" includes="client/,shared/" />
diff --git a/user/src/com/google/gwt/logging/Logging.gwt.xml b/user/src/com/google/gwt/logging/Logging.gwt.xml
index 4646c37..6989f1b 100644
--- a/user/src/com/google/gwt/logging/Logging.gwt.xml
+++ b/user/src/com/google/gwt/logging/Logging.gwt.xml
@@ -13,7 +13,8 @@
 <!-- limitations under the License.                                         -->
 
 <module>
-  <inherits name='com.google.gwt.json.JSON'/>
+  <inherits name="com.google.gwt.json.JSON"/>
+  <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.logging.LogImpl"/>
   <source path="client" />
   <source path="shared" />
diff --git a/user/src/com/google/gwt/regexp/RegExp.gwt.xml b/user/src/com/google/gwt/regexp/RegExp.gwt.xml
index 285dd66..146103c 100644
--- a/user/src/com/google/gwt/regexp/RegExp.gwt.xml
+++ b/user/src/com/google/gwt/regexp/RegExp.gwt.xml
@@ -14,6 +14,7 @@
 
 <!-- regular expressions support.                                           -->
 <module>
+  <inherits name="com.google.gwt.core.Core" />
   <source path="shared" />
   <super-source path="super" />
 </module>
diff --git a/user/src/com/google/gwt/storage/Storage.gwt.xml b/user/src/com/google/gwt/storage/Storage.gwt.xml
index 98c0055..48c79de 100644
--- a/user/src/com/google/gwt/storage/Storage.gwt.xml
+++ b/user/src/com/google/gwt/storage/Storage.gwt.xml
@@ -13,8 +13,7 @@
 <!-- limitations under the License.                                         -->
 
 <module>
-  <inherits name="com.google.gwt.core.Core" />
-  <inherits name="com.google.gwt.user.UserAgent" />
+  <inherits name="com.google.gwt.user.User" />
 
   <!-- Define the storage support property -->
   <define-property name="storageSupport" values="maybe,no" />
diff --git a/user/src/com/google/gwt/view/View.gwt.xml b/user/src/com/google/gwt/view/View.gwt.xml
index 06676be..21babcb 100644
--- a/user/src/com/google/gwt/view/View.gwt.xml
+++ b/user/src/com/google/gwt/view/View.gwt.xml
@@ -14,6 +14,6 @@
   the License.
 -->
 <module>
-   <inherits name="com.google.gwt.core.Core"/>
+   <inherits name="com.google.gwt.user.User"/>
    <source path="client"/>
 </module>
diff --git a/user/src/com/google/gwt/xml/XML.gwt.xml b/user/src/com/google/gwt/xml/XML.gwt.xml
index 4eaf0d1..3c75c56 100644
--- a/user/src/com/google/gwt/xml/XML.gwt.xml
+++ b/user/src/com/google/gwt/xml/XML.gwt.xml
@@ -15,9 +15,8 @@
 <!-- XML parsing support.                                                   -->
 <!--                                                                        -->
 <module>
-  <inherits name="com.google.gwt.core.Core"/>
-  <inherits name="com.google.gwt.user.UserAgent"/>
-
+  <inherits name="com.google.gwt.user.User"/>
+  
   <!-- Fall through to this rule for all other browsers -->
   <replace-with class="com.google.gwt.xml.client.impl.XMLParserImplStandard">
     <when-type-is class="com.google.gwt.xml.client.impl.XMLParserImpl"/>
diff --git a/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
index 88b8981..fc7fbd8 100644
--- a/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
+++ b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
@@ -13,7 +13,6 @@
 
 <!-- AutoBean framework -->
 <module>
-  <inherits name="com.google.gwt.core.Core" />
   <inherits name="com.google.gwt.user.User" />
   <source path="gwt/client" />
   <source path="shared" />
diff --git a/user/src/com/google/web/bindery/event/Event.gwt.xml b/user/src/com/google/web/bindery/event/Event.gwt.xml
index 8bafd18..bd600e6 100644
--- a/user/src/com/google/web/bindery/event/Event.gwt.xml
+++ b/user/src/com/google/web/bindery/event/Event.gwt.xml
@@ -14,5 +14,7 @@
   the License.
 -->
 <module>
+  <inherits name="com.google.gwt.core.Core" />
+  
   <source path="shared" />
 </module>