/*
* 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(compilerContext, 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;
}
}