| /* |
| * 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.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.ArgHandlerSourceLevel; |
| import com.google.gwt.dev.util.arg.ArgHandlerStrict; |
| import com.google.gwt.dev.util.arg.SourceLevel; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| 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 ArgHandlerSourceLevel(options)); |
| registerHandler(new ArgHandlerStrict(options)); |
| } |
| |
| @Override |
| protected String getName() { |
| return CompileModule.class.getName(); |
| } |
| } |
| |
| static class CompileModuleOptionsImpl extends PrecompileTaskOptionsImpl implements |
| CompileModuleOptions { |
| |
| private File outDir; |
| private boolean strict = false; |
| // TODO(rluble): refactor. SourceLevel should only reside in one place possibly JJSOptions. |
| private SourceLevel sourceLevel = SourceLevel.DEFAULT_SOURCE_LEVEL; |
| |
| |
| 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 SourceLevel getSourceLevel() { |
| // Source level ignored here for now. |
| return sourceLevel; |
| } |
| |
| @Override |
| public boolean isStrict() { |
| return strict; |
| } |
| |
| @Override |
| public void setOutDir(File outDir) { |
| this.outDir = outDir; |
| } |
| |
| @Override |
| public void setSourceLevel(SourceLevel sourceLevel) { |
| this.sourceLevel = sourceLevel; |
| } |
| |
| @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) { |
| 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) { |
| // Remember units already seen so we don't write the same unit into multiple |
| // archives. Also used as an optimization to keep from de-serializing the |
| // same archive twice. Key is archive URL string. Maps to the set of unit resource paths |
| // for the archive. |
| Map<String, Set<String>> unitsInArchives = new HashMap<String, Set<String>>(); |
| // Modules archived by this invocation of CompileModule. Once a compiled module is |
| // written out as an archive file, it may or may not appear on the classpath |
| // and come back with module.getAllCompilationUnitArchiveURLs(). Thus, use a second check |
| // so that the tool doesn't redundantly write the same compilation units into |
| // multiple archives. |
| Map<String, Set<String>> newlyCompiledModules = new HashMap<String, Set<String>>(); |
| File outputDir = options.getOutDir(); |
| if (!outputDir.isDirectory() && !outputDir.mkdirs()) { |
| logger.log(Type.ERROR, "Error creating directories for ouptut: " |
| + outputDir.getAbsolutePath()); |
| return false; |
| } |
| |
| CompilerContext.Builder compilerContextBuilder = new CompilerContext.Builder(); |
| CompilerContext compilerContext = compilerContextBuilder.options(options).build(); |
| |
| // 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; |
| // The units in this set already belong to an archive and should not be |
| // written out. |
| Set<String> currentModuleArchivedUnits = new HashSet<String>(); |
| try { |
| module = ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleToCompile); |
| compilerContext = compilerContextBuilder.module(module).build(); |
| } catch (Throwable e) { |
| CompilationProblemReporter.logAndTranslateException(logger, e); |
| return false; |
| } |
| |
| 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); |
| } |
| } |
| |
| // Don't re-archive previously compiled units from this invocation of CompileModule. |
| for (String compiledModuleName : newlyCompiledModules.keySet()) { |
| if (module.isInherited(compiledModuleName)) { |
| currentModuleArchivedUnits.addAll(newlyCompiledModules.get(compiledModuleName)); |
| } |
| } |
| |
| // Load up previously archived modules |
| for (URL archiveURL : archiveURLs) { |
| String archiveURLString = archiveURL.toString(); |
| Set<String> unitPaths = unitsInArchives.get(archiveURLString); |
| // Don't bother deserializing archives that have already been read. |
| if (unitPaths != null) { |
| currentModuleArchivedUnits.addAll(unitPaths); |
| continue; |
| } |
| |
| 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)) { |
| Set<String> archivedUnits = new HashSet<String>(); |
| unitsInArchives.put(archiveURLString, archivedUnits); |
| for (CompilationUnit unit : archive.getUnits().values()) { |
| archivedUnits.add(unit.getResourcePath()); |
| } |
| currentModuleArchivedUnits.addAll(archivedUnits); |
| } |
| } 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, compilerContext); |
| } catch (Throwable e) { |
| CompilationProblemReporter.logAndTranslateException(logger, e); |
| return false; |
| } |
| |
| if (options.isStrict() && compilationState.hasErrors()) { |
| logger.log(TreeLogger.ERROR, "Failed to compile " + moduleToCompile); |
| return false; |
| } |
| |
| Set<String> compiledUnits = Sets.newHashSet(); |
| CompilationUnitArchive outputArchive = new CompilationUnitArchive(moduleToCompile); |
| for (CompilationUnit unit : compilationState.getCompilationUnits()) { |
| if (!currentModuleArchivedUnits.contains(unit.getResourcePath())) { |
| outputArchive.addUnit(unit); |
| compiledUnits.add(unit.getResourcePath()); |
| } |
| } |
| newlyCompiledModules.put(moduleToCompile, compiledUnits); |
| |
| String slashedModuleName = |
| module.getName().replace('.', '/') + ModuleDefLoader.COMPILATION_UNIT_ARCHIVE_SUFFIX; |
| File outputFile = new File(outputDir, slashedModuleName); |
| outputFile.getParentFile().mkdirs(); |
| logger.log(TreeLogger.INFO, "Writing " + outputArchive.getUnits().size() + " units to " |
| + outputFile.getAbsolutePath()); |
| try { |
| outputArchive.writeToFile(outputFile); |
| } catch (IOException ex) { |
| logger.log(Type.ERROR, "Error writing module file: " + outputFile.getAbsolutePath() + ": " |
| + ex); |
| return false; |
| } |
| } |
| return true; |
| } |
| } |