0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev;
0017:
0018: import com.google.gwt.core.ext.TreeLogger;
0019: import com.google.gwt.core.ext.UnableToCompleteException;
0020: import com.google.gwt.core.ext.TreeLogger.Type;
0021: import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
0022: import com.google.gwt.core.ext.typeinfo.JClassType;
0023: import com.google.gwt.core.ext.typeinfo.TypeOracle;
0024: import com.google.gwt.dev.cfg.Compilation;
0025: import com.google.gwt.dev.cfg.CompilationSchema;
0026: import com.google.gwt.dev.cfg.Compilations;
0027: import com.google.gwt.dev.cfg.ModuleDef;
0028: import com.google.gwt.dev.cfg.ModuleDefLoader;
0029: import com.google.gwt.dev.cfg.Properties;
0030: import com.google.gwt.dev.cfg.Property;
0031: import com.google.gwt.dev.cfg.PropertyPermutations;
0032: import com.google.gwt.dev.cfg.Rules;
0033: import com.google.gwt.dev.cfg.StaticPropertyOracle;
0034: import com.google.gwt.dev.jdt.CacheManager;
0035: import com.google.gwt.dev.jdt.RebindPermutationOracle;
0036: import com.google.gwt.dev.jdt.StandardSourceOracle;
0037: import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
0038: import com.google.gwt.dev.jjs.JJSOptions;
0039: import com.google.gwt.dev.jjs.JsOutputOption;
0040: import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
0041: import com.google.gwt.dev.shell.StandardRebindOracle;
0042: import com.google.gwt.dev.util.DefaultTextOutput;
0043: import com.google.gwt.dev.util.SelectionScriptGenerator;
0044: import com.google.gwt.dev.util.Util;
0045: import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
0046: import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
0047: import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
0048: import com.google.gwt.dev.util.arg.ArgHandlerTreeLoggerFlag;
0049: import com.google.gwt.dev.util.log.AbstractTreeLogger;
0050: import com.google.gwt.dev.util.log.DetachedTreeLoggerWindow;
0051: import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
0052: import com.google.gwt.dev.util.xml.ReflectiveParser;
0053: import com.google.gwt.util.tools.ArgHandlerDisableAggressiveOptimization;
0054: import com.google.gwt.util.tools.ArgHandlerExtra;
0055: import com.google.gwt.util.tools.ArgHandlerFlag;
0056: import com.google.gwt.util.tools.ArgHandlerOutDir;
0057: import com.google.gwt.util.tools.ToolBase;
0058: import com.google.gwt.util.tools.Utility;
0059:
0060: import org.w3c.dom.Document;
0061: import org.w3c.dom.Element;
0062:
0063: import java.io.File;
0064: import java.io.FileNotFoundException;
0065: import java.io.FileReader;
0066: import java.io.FilenameFilter;
0067: import java.io.UnsupportedEncodingException;
0068: import java.net.URL;
0069: import java.util.ArrayList;
0070: import java.util.HashMap;
0071: import java.util.HashSet;
0072: import java.util.Iterator;
0073: import java.util.List;
0074: import java.util.Map;
0075: import java.util.Set;
0076:
0077: import javax.xml.parsers.DocumentBuilder;
0078: import javax.xml.parsers.DocumentBuilderFactory;
0079: import javax.xml.parsers.ParserConfigurationException;
0080:
0081: /**
0082: * The main executable entry point for the GWT Java to JavaScript compiler.
0083: */
0084: public class GWTCompiler extends ToolBase {
0085:
0086: private class ArgHandlerModuleName extends ArgHandlerExtra {
0087:
0088: @Override
0089: public boolean addExtraArg(String arg) {
0090: setModuleName(arg);
0091: return true;
0092: }
0093:
0094: @Override
0095: public String getPurpose() {
0096: return "Specifies the name of the module to compile";
0097: }
0098:
0099: @Override
0100: public String[] getTagArgs() {
0101: return new String[] { "module" };
0102: }
0103:
0104: @Override
0105: public boolean isRequired() {
0106: return true;
0107: }
0108: }
0109:
0110: /**
0111: * Argument handler for making the compiler run in "validation" mode.
0112: */
0113: private class ArgHandlerValidateOnlyFlag extends ArgHandlerFlag {
0114:
0115: public String getPurpose() {
0116: return "Validate all source code, but do not compile";
0117: }
0118:
0119: public String getTag() {
0120: return "-validateOnly";
0121: }
0122:
0123: public boolean setFlag() {
0124: jjsOptions.setValidateOnly(true);
0125: return true;
0126: }
0127: }
0128:
0129: /**
0130: * Used to smartly deal with rebind across the production of an entire
0131: * permutation, including cache checking and recording the inputs and outputs
0132: * into a {@link Compilation}.
0133: */
0134: private class CompilationRebindOracle extends StandardRebindOracle {
0135:
0136: private final Map<String, String> cache = new HashMap<String, String>();
0137:
0138: private Compilation compilation;
0139:
0140: public CompilationRebindOracle() {
0141: super (typeOracle, propOracle, module, rules, genDir,
0142: outDir, cacheManager);
0143: }
0144:
0145: /**
0146: * Overridden so that we can selectively record inputs and outputs to derive
0147: * the cache key for a compilation. Note that the cache gets invalidated if
0148: * the propOracle changes state.
0149: */
0150: @Override
0151: public String rebind(TreeLogger logger, String in)
0152: throws UnableToCompleteException {
0153: String out = cache.get(in);
0154: if (out == null) {
0155: // Actually do the work, then cache it.
0156: //
0157: out = super .rebind(logger, in);
0158: cache.put(in, out);
0159: } else {
0160: // Was cached.
0161: //
0162: String msg = "Rebind answer for '" + in
0163: + "' found in cache " + out;
0164: logger.log(TreeLogger.DEBUG, msg, null);
0165: }
0166:
0167: if (compilation != null
0168: && compilation.recordDecision(in, out)) {
0169: List<JClassType> genTypes = generatedTypesByResultTypeName
0170: .get(out);
0171: if (genTypes != null) {
0172: for (JClassType genType : genTypes) {
0173: String sourceHash = genType.getTypeHash();
0174: String genTypeName = genType
0175: .getQualifiedSourceName();
0176: compilation.recordGeneratedTypeHash(
0177: genTypeName, sourceHash);
0178: }
0179: }
0180: }
0181:
0182: return out;
0183: }
0184:
0185: public void recordInto(Compilation compilation) {
0186: this .compilation = compilation;
0187: }
0188: }
0189:
0190: private class DistillerRebindPermutationOracle implements
0191: RebindPermutationOracle {
0192:
0193: private final StandardRebindOracle rebindOracle = new StandardRebindOracle(
0194: typeOracle, propOracle, module, rules, genDir, outDir,
0195: cacheManager) {
0196:
0197: /**
0198: * Record generated types.
0199: */
0200: @Override
0201: protected void onGeneratedTypes(String result,
0202: JClassType[] genTypes) {
0203: List<JClassType> list = new ArrayList<JClassType>();
0204: Util.addAll(list, genTypes);
0205: Object existing = generatedTypesByResultTypeName.put(
0206: result, list);
0207: assert (existing == null) : "Internal error: redundant notification of generated types";
0208: }
0209: };
0210:
0211: public String[] getAllPossibleRebindAnswers(TreeLogger logger,
0212: String requestTypeName)
0213: throws UnableToCompleteException {
0214:
0215: String msg = "Computing all possible rebind results for '"
0216: + requestTypeName + "'";
0217: logger = logger.branch(TreeLogger.DEBUG, msg, null);
0218:
0219: Set<String> answers = new HashSet<String>();
0220:
0221: Property[] orderedProps = perms.getOrderedProperties();
0222: for (Iterator<String[]> iter = perms.iterator(); iter
0223: .hasNext();) {
0224: String[] orderedPropValues = iter.next();
0225:
0226: // Create a snapshot of the property values by setting their values
0227: // in the property oracle. Because my rebindOracle uses the shared
0228: // generator context (which in turns uses the propOracle), this
0229: // has the effect we're after. It isn't reentrant, though, so don't
0230: // expect to call this recursively.
0231: propOracle.setPropertyValues(orderedProps,
0232: orderedPropValues);
0233:
0234: // Ask the rebind oracle.
0235: logProperties(logger, orderedProps, orderedPropValues);
0236: String resultTypeName = rebindOracle.rebind(logger,
0237: requestTypeName);
0238: answers.add(resultTypeName);
0239: }
0240: return Util.toArray(String.class, answers);
0241: }
0242: }
0243:
0244: private static final String EXT_CACHE_XML = ".cache.xml";
0245:
0246: public static void main(String[] args) {
0247: /*
0248: * NOTE: main always exits with a call to System.exit to terminate any
0249: * non-daemon threads that were started in Generators. Typically, this is to
0250: * shutdown AWT related threads, since the contract for their termination is
0251: * still implementation-dependent.
0252: */
0253: GWTCompiler compiler = new GWTCompiler();
0254: if (compiler.processArgs(args)) {
0255: if (compiler.run()) {
0256: // Exit w/ success code.
0257: System.exit(0);
0258: }
0259: }
0260: // Exit w/ non-success code.
0261: System.exit(1);
0262: }
0263:
0264: /**
0265: * Returns the fully-qualified main type name of a compilation unit.
0266: */
0267: private static String makeTypeName(CompilationUnitProvider cup) {
0268: if (cup.getPackageName().length() > 0) {
0269: return cup.getPackageName() + "." + cup.getMainTypeName();
0270: } else {
0271: return cup.getMainTypeName();
0272: }
0273: }
0274:
0275: private final CacheManager cacheManager;
0276:
0277: private Compilations compilations = new Compilations();
0278:
0279: private String[] declEntryPts;
0280:
0281: private File genDir;
0282:
0283: private Map<String, List<JClassType>> generatedTypesByResultTypeName = new HashMap<String, List<JClassType>>();
0284:
0285: private JavaToJavaScriptCompiler jjs;
0286:
0287: private JJSOptions jjsOptions = new JJSOptions();
0288:
0289: private Type logLevel;
0290:
0291: private ModuleDef module;
0292:
0293: private String moduleName;
0294:
0295: private File outDir;
0296:
0297: private PropertyPermutations perms;
0298:
0299: private Properties properties;
0300:
0301: private StaticPropertyOracle propOracle = new StaticPropertyOracle();
0302:
0303: private RebindPermutationOracle rebindPermOracle;
0304:
0305: private Rules rules;
0306:
0307: private StandardSourceOracle sourceOracle;
0308:
0309: private TypeOracle typeOracle;
0310:
0311: private boolean useGuiLogger;
0312:
0313: public GWTCompiler() {
0314: this (null);
0315: }
0316:
0317: public GWTCompiler(CacheManager cacheManager) {
0318: registerHandler(new ArgHandlerLogLevel() {
0319: @Override
0320: public void setLogLevel(Type level) {
0321: logLevel = level;
0322: }
0323: });
0324:
0325: registerHandler(new ArgHandlerGenDir() {
0326: @Override
0327: public void setDir(File dir) {
0328: genDir = dir;
0329: }
0330: });
0331:
0332: registerHandler(new ArgHandlerOutDir() {
0333: @Override
0334: public void setDir(File dir) {
0335: outDir = dir;
0336: }
0337: });
0338:
0339: registerHandler(new ArgHandlerTreeLoggerFlag() {
0340: @Override
0341: public boolean setFlag() {
0342: useGuiLogger = true;
0343: return true;
0344: }
0345: });
0346:
0347: registerHandler(new ArgHandlerModuleName());
0348:
0349: registerHandler(new ArgHandlerScriptStyle(jjsOptions));
0350:
0351: registerHandler(new ArgHandlerDisableAggressiveOptimization() {
0352: @Override
0353: public boolean setFlag() {
0354: GWTCompiler.this .setAggressivelyOptimize(false);
0355: return true;
0356: }
0357: });
0358:
0359: registerHandler(new ArgHandlerValidateOnlyFlag());
0360:
0361: this .cacheManager = cacheManager;
0362: }
0363:
0364: public void distill(TreeLogger logger, ModuleDef moduleDef)
0365: throws UnableToCompleteException {
0366: this .module = moduleDef;
0367:
0368: // Set up all the initial state.
0369: checkModule(logger);
0370:
0371: // Tweak the output directory so that output lives under the module name.
0372: outDir = new File(outDir, module.getName());
0373:
0374: rules = module.getRules();
0375: typeOracle = module.getTypeOracle(logger);
0376: sourceOracle = new StandardSourceOracle(typeOracle);
0377: if (jjsOptions.isValidateOnly()) {
0378: // Pretend that every single compilation unit is an entry point.
0379: CompilationUnitProvider[] compilationUnits = module
0380: .getCompilationUnits();
0381: declEntryPts = new String[compilationUnits.length];
0382: for (int i = 0; i < compilationUnits.length; ++i) {
0383: CompilationUnitProvider cup = compilationUnits[i];
0384: declEntryPts[i] = makeTypeName(cup);
0385: }
0386: } else {
0387: // Use the real entry points.
0388: declEntryPts = module.getEntryPointTypeNames();
0389: }
0390: rebindPermOracle = new DistillerRebindPermutationOracle();
0391: properties = module.getProperties();
0392: perms = new PropertyPermutations(properties);
0393: WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
0394: sourceOracle, rebindPermOracle);
0395: jjs = new JavaToJavaScriptCompiler(logger, frontEnd,
0396: declEntryPts, jjsOptions);
0397:
0398: if (!jjsOptions.isValidateOnly()) {
0399: /*
0400: * See what permutations already exist on disk and are up to date. Skip
0401: * this for validation mode since we want to recompile everything.
0402: */
0403: initCompilations(logger);
0404: }
0405:
0406: // Compile for every permutation of properties.
0407: //
0408: SelectionScriptGenerator selGen = compilePermutations(logger);
0409:
0410: if (jjsOptions.isValidateOnly()) {
0411: logger.log(TreeLogger.INFO, "Validation succeeded", null);
0412: return;
0413: }
0414:
0415: // Generate a selection script to pick the right permutation.
0416: //
0417: writeSelectionScripts(logger, selGen);
0418:
0419: // Copy all public files into the output directory.
0420: //
0421: copyPublicFiles(logger);
0422:
0423: logger.log(TreeLogger.INFO, "Compilation succeeded", null);
0424: }
0425:
0426: public File getGenDir() {
0427: return genDir;
0428: }
0429:
0430: public Type getLogLevel() {
0431: return logLevel;
0432: }
0433:
0434: public String getModuleName() {
0435: return moduleName;
0436: }
0437:
0438: public boolean getUseGuiLogger() {
0439: return useGuiLogger;
0440: }
0441:
0442: public void setAggressivelyOptimize(boolean aggressive) {
0443: jjsOptions.setAggressivelyOptimize(aggressive);
0444: }
0445:
0446: public void setCompilerOptions(JJSOptions options) {
0447: jjsOptions.copyFrom(options);
0448: }
0449:
0450: public void setGenDir(File dir) {
0451: genDir = dir;
0452: }
0453:
0454: public void setLogLevel(Type level) {
0455: this .logLevel = level;
0456: }
0457:
0458: public void setModuleName(String name) {
0459: moduleName = name;
0460: }
0461:
0462: public void setOutDir(File outDir) {
0463: this .outDir = outDir;
0464: }
0465:
0466: public void setStyleDetailed() {
0467: jjsOptions.setOutput(JsOutputOption.DETAILED);
0468: }
0469:
0470: public void setStyleObfuscated() {
0471: jjsOptions.setOutput(JsOutputOption.OBFUSCATED);
0472: }
0473:
0474: public void setStylePretty() {
0475: jjsOptions.setOutput(JsOutputOption.PRETTY);
0476: }
0477:
0478: /**
0479: * Ensure the module has at least one entry point (except in validation mode).
0480: */
0481: private void checkModule(TreeLogger logger)
0482: throws UnableToCompleteException {
0483: if (!jjsOptions.isValidateOnly()
0484: && module.getEntryPointTypeNames().length == 0) {
0485: logger.log(TreeLogger.ERROR,
0486: "Module has no entry points defined", null);
0487: throw new UnableToCompleteException();
0488: }
0489: }
0490:
0491: private SelectionScriptGenerator compilePermutations(
0492: TreeLogger logger) throws UnableToCompleteException {
0493: if (jjsOptions.isValidateOnly()) {
0494: logger = logger.branch(TreeLogger.INFO,
0495: "Validating compilation", null);
0496: } else {
0497: logger = logger.branch(TreeLogger.INFO, "Compiling into "
0498: + outDir, null);
0499: }
0500: Property[] orderedProps = perms.getOrderedProperties();
0501: SelectionScriptGenerator selGen = new SelectionScriptGenerator(
0502: module, orderedProps);
0503: int permNumber = 1;
0504: for (Iterator<String[]> iter = perms.iterator(); iter.hasNext(); ++permNumber) {
0505:
0506: String[] orderedPropValues = iter.next();
0507: String strongName = realizePermutation(logger,
0508: orderedProps, orderedPropValues, permNumber);
0509: if (!jjsOptions.isValidateOnly()) {
0510: selGen.recordSelection(orderedPropValues, strongName);
0511: }
0512: }
0513: return selGen;
0514: }
0515:
0516: private void copyPublicFiles(TreeLogger logger)
0517: throws UnableToCompleteException {
0518: TreeLogger branch = null;
0519: boolean anyCopied = false;
0520: String[] files = module.getAllPublicFiles();
0521: for (int i = 0; i < files.length; ++i) {
0522: URL from = module.findPublicFile(files[i]);
0523: File to = new File(outDir, files[i]);
0524: boolean copied = Util.copy(logger, from, to);
0525: if (copied) {
0526: if (!anyCopied) {
0527: branch = logger.branch(TreeLogger.INFO,
0528: "Copying all files found on public path",
0529: null);
0530: if (!logger.isLoggable(TreeLogger.TRACE)) {
0531: branch = null;
0532: }
0533: anyCopied = true;
0534: }
0535:
0536: if (branch != null) {
0537: branch.log(TreeLogger.TRACE, to.getAbsolutePath(),
0538: null);
0539: }
0540: }
0541: }
0542: }
0543:
0544: private String getHtmlPrefix() {
0545: DefaultTextOutput out = new DefaultTextOutput(jjsOptions
0546: .getOutput().shouldMinimize());
0547: out.print("<html>");
0548: out.newlineOpt();
0549:
0550: // Setup the well-known variables.
0551: //
0552: out.print("<head><script>");
0553: out.newlineOpt();
0554: out.print("var $gwt_version = \"" + About.GWT_VERSION_NUM
0555: + "\";");
0556: out.newlineOpt();
0557: out.print("var $wnd = parent;");
0558: out.newlineOpt();
0559: out.print("var $doc = $wnd.document;");
0560: out.newlineOpt();
0561: out.print("var $moduleName, $moduleBase;");
0562: out.newlineOpt();
0563: out.print("</script></head>");
0564: out.newlineOpt();
0565: out.print("<body>");
0566: out.newlineOpt();
0567:
0568: // Begin a script block inside the body. It's commented out so that the
0569: // browser won't mistake strings containing "<script>" for actual script.
0570: out.print("<script><!--");
0571: out.newline();
0572: return out.toString();
0573: }
0574:
0575: private String getHtmlSuffix() {
0576: DefaultTextOutput out = new DefaultTextOutput(jjsOptions
0577: .getOutput().shouldMinimize());
0578: String moduleFunction = module.getName().replace('.', '_');
0579:
0580: // Generate the call to tell the bootstrap code that we're ready to go.
0581: out.newlineOpt();
0582: out.print("if ($wnd." + moduleFunction + ") $wnd."
0583: + moduleFunction + ".onScriptLoad();");
0584: out.newline();
0585: out.print("--></script></body></html>");
0586: out.newlineOpt();
0587:
0588: return out.toString();
0589: }
0590:
0591: private String getJsPrefix() {
0592: DefaultTextOutput out = new DefaultTextOutput(jjsOptions
0593: .getOutput().shouldMinimize());
0594:
0595: out.print("(function(){");
0596: out.newlineOpt();
0597:
0598: // Setup the well-known variables.
0599: //
0600: out.print("var $gwt_version = \"" + About.GWT_VERSION_NUM
0601: + "\";");
0602: out.newlineOpt();
0603: out.print("var $wnd = window;");
0604: out.newlineOpt();
0605: out.print("var $doc = $wnd.document;");
0606: out.newlineOpt();
0607: out.print("var $moduleName, $moduleBase;");
0608: out.newlineOpt();
0609:
0610: return out.toString();
0611: }
0612:
0613: private String getJsSuffix() {
0614: DefaultTextOutput out = new DefaultTextOutput(jjsOptions
0615: .getOutput().shouldMinimize());
0616: String moduleFunction = module.getName().replace('.', '_');
0617:
0618: // Generate the call to tell the bootstrap code that we're ready to go.
0619: out.newlineOpt();
0620: out.print("if (" + moduleFunction + ") {");
0621: out.newlineOpt();
0622: out.print(" var __gwt_initHandlers = " + moduleFunction
0623: + ".__gwt_initHandlers;");
0624: out.print(" " + moduleFunction + ".onScriptLoad(gwtOnLoad);");
0625: out.newlineOpt();
0626: out.print("}");
0627: out.newlineOpt();
0628: out.print("})();");
0629: out.newlineOpt();
0630:
0631: return out.toString();
0632: }
0633:
0634: /**
0635: * This has to run after JJS exists which means that all rebind perms have
0636: * happened and thus the type oracle knows about everything.
0637: */
0638: private void initCompilations(TreeLogger logger)
0639: throws UnableToCompleteException {
0640: File[] cacheXmls = outDir.listFiles(new FilenameFilter() {
0641: public boolean accept(File dir, String name) {
0642: return name.endsWith(EXT_CACHE_XML);
0643: }
0644: });
0645:
0646: if (cacheXmls == null) {
0647: return;
0648: }
0649:
0650: long newestCup = jjs
0651: .getLastModifiedTimeOfNewestCompilationUnit();
0652: for (int i = 0; i < cacheXmls.length; i++) {
0653: File cacheXml = cacheXmls[i];
0654: String fn = cacheXml.getName();
0655: String strongName = fn.substring(0, fn.length()
0656: - EXT_CACHE_XML.length());
0657:
0658: // Make sure the cached script is not out of date.
0659: //
0660: long cacheXmlLastMod = cacheXml.lastModified();
0661: if (cacheXmlLastMod < newestCup) {
0662: // It is out of date; no need to even parse the XML.
0663: //
0664: String msg = "Compilation '" + fn
0665: + "' is out of date and will be removed";
0666: logger.log(TreeLogger.TRACE, msg, null);
0667: Util.deleteFilesStartingWith(outDir, strongName);
0668: continue;
0669: }
0670:
0671: // It is up-to-date, so we at least can load it.
0672: // We still need to verify that the source for generated types hasn't
0673: // changed.
0674: //
0675: TreeLogger branch = logger.branch(TreeLogger.DEBUG,
0676: "Loading cached compilation: " + cacheXml, null);
0677: Compilation c = new Compilation();
0678: c.setStrongName(strongName);
0679: CompilationSchema schema = new CompilationSchema(c);
0680: FileReader r = null;
0681: Throwable caught = null;
0682: try {
0683: r = new FileReader(cacheXml);
0684: ReflectiveParser.parse(logger, schema, r);
0685: } catch (FileNotFoundException e) {
0686: caught = e;
0687: } catch (UnableToCompleteException e) {
0688: caught = e;
0689: } finally {
0690: Utility.close(r);
0691: }
0692:
0693: if (caught != null) {
0694: String msg = "Unable to load the cached file";
0695: branch.log(TreeLogger.WARN, msg, caught);
0696: continue;
0697: }
0698:
0699: // Check that the hash code of the generated sources for this compilation
0700: // matches the current generated source for the same type.
0701: //
0702: boolean isBadCompilation = false;
0703: String[] genTypes = c.getGeneratedTypeNames();
0704: for (int j = 0; j < genTypes.length; j++) {
0705: String genTypeName = genTypes[j];
0706: String cachedHash = c.getTypeHash(genTypeName);
0707: JClassType genType = typeOracle.findType(genTypeName);
0708: if (genType == null) {
0709: // This cache entry refers to a type that no longer seems to exist.
0710: // Remove it.
0711: //
0712: String msg = "Compilation '"
0713: + fn
0714: + "' refers to generated type '"
0715: + genTypeName
0716: + "' which no longer exists; cache entry will be removed";
0717: branch.log(TreeLogger.TRACE, msg, null);
0718: Util.deleteFilesStartingWith(outDir, strongName);
0719: isBadCompilation = true;
0720: break;
0721: }
0722:
0723: String currentHash = genType.getTypeHash();
0724:
0725: if (!cachedHash.equals(currentHash)) {
0726: String msg = "Compilation '"
0727: + fn
0728: + "' was compiled with a different version of generated source for '"
0729: + genTypeName
0730: + "'; cache entry will be removed";
0731: branch.log(TreeLogger.TRACE, msg, null);
0732: Util.deleteFilesStartingWith(outDir, strongName);
0733: isBadCompilation = true;
0734: break;
0735: }
0736: }
0737:
0738: if (!isBadCompilation) {
0739: // Okay -- this compilation should be a cache candidate.
0740: compilations.add(c);
0741: }
0742: }
0743: }
0744:
0745: private void logProperties(TreeLogger logger, Property[] props,
0746: String[] values) {
0747: if (logger.isLoggable(TreeLogger.DEBUG)) {
0748: logger = logger.branch(TreeLogger.DEBUG,
0749: "Setting properties", null);
0750: for (int i = 0; i < props.length; i++) {
0751: String name = props[i].getName();
0752: String value = values[i];
0753: logger
0754: .log(TreeLogger.TRACE, name + " = " + value,
0755: null);
0756: }
0757: }
0758: }
0759:
0760: /**
0761: * Attempts to compile with a single permutation of properties. The result can
0762: * be one of the following:
0763: * <ul>
0764: * <li>There is an existing compilation having the same deferred binding
0765: * results (and thus would create identical output); compilation is skipped
0766: * <li>No existing compilation unit matches, so the compilation proceeds
0767: * </ul>
0768: */
0769: private String realizePermutation(TreeLogger logger,
0770: Property[] currentProps, String[] currentValues,
0771: int permNumber) throws UnableToCompleteException {
0772: String msg = "Analyzing permutation #" + permNumber;
0773: logger = logger.branch(TreeLogger.TRACE, msg, null);
0774:
0775: logProperties(logger, currentProps, currentValues);
0776:
0777: // Create a rebind oracle that will record decisions so that we can cache
0778: // them and avoid future computations.
0779: //
0780: CompilationRebindOracle rebindOracle = new CompilationRebindOracle();
0781:
0782: // Tell the property provider above about the current property values.
0783: // Note that the rebindOracle is actually sensitive to these values because
0784: // in its ctor is uses propOracle as its property oracle.
0785: //
0786: propOracle.setPropertyValues(currentProps, currentValues);
0787:
0788: // Check to see if we already have this compilation.
0789: // This will have the effect of filling the rebind oracle's cache.
0790: //
0791: String[] entryPts = module.getEntryPointTypeNames();
0792: Compilation cached = compilations.find(logger, rebindOracle,
0793: entryPts);
0794: if (cached != null) {
0795: msg = "Matches existing compilation "
0796: + cached.getStrongName();
0797: logger.log(TreeLogger.TRACE, msg, null);
0798: return cached.getStrongName();
0799: }
0800:
0801: // Now attach a compilation into which we can record the particular inputs
0802: // and outputs used by this compile process.
0803: //
0804: Compilation compilation = new Compilation();
0805: rebindOracle.recordInto(compilation);
0806:
0807: // Create JavaScript.
0808: //
0809: String js = jjs.compile(logger, rebindOracle);
0810: if (jjsOptions.isValidateOnly()) {
0811: // We're done, there's no actual script to write.
0812: assert (js == null);
0813: return null;
0814: }
0815:
0816: // Create a wrapper and an unambiguous name for the file.
0817: //
0818: String strongName = writeHtmlAndJsWithStrongName(logger, js);
0819:
0820: // Write out a cache control file that correlates to this script.
0821: //
0822: compilation.setStrongName(strongName);
0823: writeCacheFile(logger, compilation);
0824:
0825: // Add this compilation to the list of known compilations.
0826: //
0827: compilations.add(compilation);
0828: return compilation.getStrongName();
0829: }
0830:
0831: /**
0832: * Runs the compiler. If a gui-based TreeLogger is used, this method will not
0833: * return until its window is closed by the user.
0834: *
0835: * @return success from the compiler, <code>true</code> if the compile
0836: * completed without errors, <code>false</code> otherwise.
0837: */
0838: private boolean run() {
0839: // Set any platform specific system properties.
0840: BootStrapPlatform.setSystemProperties();
0841:
0842: if (useGuiLogger) {
0843: // Initialize a tree logger window.
0844: DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow
0845: .getInstance("Build Output for " + moduleName, 800,
0846: 600, true);
0847:
0848: // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
0849: BootStrapPlatform.maybeInitializeAWT();
0850:
0851: final AbstractTreeLogger logger = loggerWindow.getLogger();
0852: final boolean[] success = new boolean[1];
0853:
0854: // Compiler will be spawned onto a second thread, UI thread for tree
0855: // logger will remain on the main.
0856: Thread compilerThread = new Thread(new Runnable() {
0857: public void run() {
0858: success[0] = GWTCompiler.this .run(logger);
0859: }
0860: });
0861:
0862: compilerThread.setName("GWTCompiler Thread");
0863: compilerThread.start();
0864: loggerWindow.run();
0865:
0866: // Even if the tree logger window is closed, we wait for the compiler
0867: // to finish.
0868: waitForThreadToTerminate(compilerThread);
0869:
0870: return success[0];
0871: } else {
0872: return run(new PrintWriterTreeLogger());
0873: }
0874: }
0875:
0876: private boolean run(AbstractTreeLogger logger) {
0877: try {
0878: logger.setMaxDetail(logLevel);
0879:
0880: ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(
0881: logger, moduleName);
0882: distill(logger, moduleDef);
0883: return true;
0884: } catch (UnableToCompleteException e) {
0885: // We intentionally don't pass in the exception here since the real
0886: // cause has been logged.
0887: logger.log(TreeLogger.ERROR, "Build failed", null);
0888: return false;
0889: }
0890: }
0891:
0892: /**
0893: * Waits for a thread to terminate before it returns. This method is a
0894: * non-cancellable task, in that it will defer thread interruption until it is
0895: * done.
0896: *
0897: * @param godot the thread that is being waited on.
0898: */
0899: private void waitForThreadToTerminate(final Thread godot) {
0900: // Goetz pattern for non-cancellable tasks.
0901: // http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html
0902: boolean isInterrupted = false;
0903: try {
0904: while (true) {
0905: try {
0906: godot.join();
0907: return;
0908: } catch (InterruptedException e) {
0909: isInterrupted = true;
0910: }
0911: }
0912: } finally {
0913: if (isInterrupted) {
0914: Thread.currentThread().interrupt();
0915: }
0916: }
0917: }
0918:
0919: private void writeCacheFile(TreeLogger logger,
0920: Compilation compilation) throws UnableToCompleteException {
0921: // Create and write the cache file.
0922: // The format matches the one read in consumeCacheEntry().
0923: //
0924: DocumentBuilderFactory dbf = DocumentBuilderFactory
0925: .newInstance();
0926: DocumentBuilder db;
0927: try {
0928: db = dbf.newDocumentBuilder();
0929: } catch (ParserConfigurationException e) {
0930: logger.log(TreeLogger.ERROR,
0931: "Unable to construct cache entry XML", e);
0932: throw new UnableToCompleteException();
0933: }
0934: Document doc = db.newDocument();
0935:
0936: // <cache-entry ...>
0937: Element docElem = doc.createElement("cache-entry");
0938: doc.appendChild(docElem);
0939:
0940: // <generated-type-hash ...>
0941: String[] genTypeNames = compilation.getGeneratedTypeNames();
0942: for (int i = 0; i < genTypeNames.length; i++) {
0943: String genTypeName = genTypeNames[i];
0944: String hash = compilation.getTypeHash(genTypeName);
0945: Element childElem = doc
0946: .createElement("generated-type-hash");
0947: docElem.appendChild(childElem);
0948: childElem.setAttribute("class", genTypeName);
0949: childElem.setAttribute("hash", hash);
0950: }
0951:
0952: // deferred binding decisions
0953: String[] inputs = compilation.getRebindInputs();
0954: for (int i = 0, n = inputs.length; i < n; ++i) {
0955: String in = inputs[i];
0956: String out = compilation.getRebindOutput(in);
0957:
0958: // <rebind-decision ...>
0959: Element childElem = doc.createElement("rebind-decision");
0960: docElem.appendChild(childElem);
0961: childElem.setAttribute("in", in);
0962: childElem.setAttribute("out", out);
0963: }
0964:
0965: // Persist it.
0966: //
0967: String strongName = compilation.getStrongName();
0968: File cacheFile = new File(outDir, strongName + EXT_CACHE_XML);
0969: byte[] bytes = Util.toXmlUtf8(doc);
0970: Util.writeBytesToFile(logger, cacheFile, bytes);
0971:
0972: String msg = "Compilation metadata written to "
0973: + cacheFile.getAbsolutePath();
0974: logger.log(TreeLogger.TRACE, msg, null);
0975: }
0976:
0977: /**
0978: * Writes the script to 1) an HTML file containing the script and 2) a
0979: * JavaScript file containing the script. Contenst are encoded as UTF-8, and
0980: * the filenames will be an MD5 hash of the script contents.
0981: *
0982: * @return the base part of the strong name, which can be used to create other
0983: * files with different extensions
0984: */
0985: private String writeHtmlAndJsWithStrongName(TreeLogger logger,
0986: String js) throws UnableToCompleteException {
0987: try {
0988: byte[] scriptBytes = js.getBytes("UTF-8");
0989: String strongName = Util.computeStrongName(scriptBytes);
0990: {
0991: byte[] prefix = getHtmlPrefix().getBytes("UTF-8");
0992: byte[] suffix = getHtmlSuffix().getBytes("UTF-8");
0993: File outFile = new File(outDir, strongName
0994: + ".cache.html");
0995: Util.writeBytesToFile(logger, outFile, new byte[][] {
0996: prefix, scriptBytes, suffix });
0997: String msg = "Compilation written to "
0998: + outFile.getAbsolutePath();
0999: logger.log(TreeLogger.TRACE, msg, null);
1000: }
1001: {
1002: byte[] prefix = getJsPrefix().getBytes("UTF-8");
1003: byte[] suffix = getJsSuffix().getBytes("UTF-8");
1004: File outFile = new File(outDir, strongName
1005: + ".cache.js");
1006: Util.writeBytesToFile(logger, outFile, new byte[][] {
1007: prefix, scriptBytes, suffix });
1008: String msg = "Compilation written to "
1009: + outFile.getAbsolutePath();
1010: logger.log(TreeLogger.TRACE, msg, null);
1011: }
1012: return strongName;
1013: } catch (UnsupportedEncodingException e) {
1014: logger.log(TreeLogger.ERROR,
1015: "Unable to encode compiled script as UTF-8", e);
1016: throw new UnableToCompleteException();
1017: }
1018: }
1019:
1020: private void writeSelectionScripts(TreeLogger logger,
1021: SelectionScriptGenerator selGen) {
1022: {
1023: String html = selGen.generateSelectionScript(jjsOptions
1024: .getOutput().shouldMinimize(), false);
1025: String fn = module.getName() + ".nocache.js";
1026: File selectionFile = new File(outDir, fn);
1027: Util.writeStringAsFile(selectionFile, html);
1028: String msg = "Compilation selection script written to "
1029: + selectionFile.getAbsolutePath();
1030: logger.log(TreeLogger.TRACE, msg, null);
1031: }
1032: {
1033: String html = selGen.generateSelectionScript(jjsOptions
1034: .getOutput().shouldMinimize(), true);
1035: String fn = module.getName() + "-xs.nocache.js";
1036: File selectionFile = new File(outDir, fn);
1037: Util.writeStringAsFile(selectionFile, html);
1038: String msg = "Compilation selection script written to "
1039: + selectionFile.getAbsolutePath();
1040: logger.log(TreeLogger.TRACE, msg, null);
1041: }
1042: }
1043: }
|