| /* |
| * 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.codeserver; |
| |
| import com.google.gwt.core.ext.Linker; |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.linker.CrossSiteIframeLinker; |
| import com.google.gwt.core.linker.IFrameLinker; |
| import com.google.gwt.dev.Compiler; |
| import com.google.gwt.dev.CompilerContext; |
| import com.google.gwt.dev.CompilerOptions; |
| import com.google.gwt.dev.cfg.BindingProperty; |
| import com.google.gwt.dev.cfg.ConfigurationProperty; |
| import com.google.gwt.dev.cfg.ModuleDef; |
| import com.google.gwt.dev.cfg.ModuleDefLoader; |
| import com.google.gwt.dev.cfg.Property; |
| import com.google.gwt.dev.cfg.ResourceLoader; |
| import com.google.gwt.dev.cfg.ResourceLoaders; |
| import com.google.gwt.dev.javac.CompilationStateBuilder; |
| import com.google.gwt.dev.resource.impl.ResourceOracleImpl; |
| import com.google.gwt.dev.resource.impl.ZipFileClassPathEntry; |
| import com.google.gwt.dev.util.log.CompositeTreeLogger; |
| import com.google.gwt.dev.util.log.PrintWriterTreeLogger; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Recompiles a GWT module on demand. |
| */ |
| class Recompiler { |
| private final AppSpace appSpace; |
| private final String originalModuleName; |
| private final TreeLogger logger; |
| private String serverPrefix; |
| private int compilesDone = 0; |
| |
| // after renaming |
| private AtomicReference<String> moduleName = new AtomicReference<String>(null); |
| |
| private final AtomicReference<CompileDir> lastBuild = new AtomicReference<CompileDir>(); |
| private final AtomicReference<ResourceLoader> resourceLoader = |
| new AtomicReference<ResourceLoader>(); |
| private final CompilerContext.Builder compilerContextBuilder = new CompilerContext.Builder(); |
| private CompilerContext compilerContext; |
| private Options options; |
| |
| Recompiler(AppSpace appSpace, String moduleName, Options options, TreeLogger logger) { |
| this.appSpace = appSpace; |
| this.originalModuleName = moduleName; |
| this.options = options; |
| this.logger = logger; |
| this.serverPrefix = options.getPreferredHost() + ":" + options.getPort(); |
| compilerContext = compilerContextBuilder.build(); |
| } |
| |
| synchronized CompileDir compile(Map<String, String> bindingProperties) |
| throws UnableToCompleteException { |
| if (compilesDone == 0) { |
| System.setProperty("java.awt.headless", "true"); |
| if (System.getProperty("gwt.speedtracerlog") == null) { |
| System.setProperty("gwt.speedtracerlog", |
| appSpace.getSpeedTracerLogFile().getAbsolutePath()); |
| } |
| CompilationStateBuilder.init(logger, appSpace.getUnitCacheDir()); |
| } |
| |
| long startTime = System.currentTimeMillis(); |
| int compileId = ++compilesDone; |
| CompileDir compileDir = makeCompileDir(compileId); |
| TreeLogger compileLogger = makeCompileLogger(compileDir); |
| |
| boolean listenerFailed = false; |
| try { |
| options.getRecompileListener().startedCompile(originalModuleName, compileId, compileDir); |
| } catch (Exception e) { |
| compileLogger.log(TreeLogger.Type.WARN, "listener threw exception", e); |
| listenerFailed = true; |
| } |
| |
| boolean success = false; |
| try { |
| CompilerOptions compilerOptions = new CompilerOptionsImpl( |
| compileDir, options.getModuleNames(), options.getSourceLevel(), |
| options.enforceStrictResources()); |
| compilerContext = compilerContextBuilder.options(compilerOptions).build(); |
| ModuleDef module = loadModule(compileLogger, bindingProperties); |
| |
| // Propagates module rename. |
| String newModuleName = module.getName(); |
| moduleName.set(newModuleName); |
| compilerOptions = new CompilerOptionsImpl( |
| compileDir, Lists.newArrayList(newModuleName), options.getSourceLevel(), |
| options.enforceStrictResources()); |
| compilerContext = compilerContextBuilder.options(compilerOptions).build(); |
| |
| success = new Compiler(compilerOptions).run(compileLogger, module); |
| lastBuild.set(compileDir); // makes compile log available over HTTP |
| } finally { |
| try { |
| options.getRecompileListener().finishedCompile(originalModuleName, compileId, success); |
| } catch (Exception e) { |
| compileLogger.log(TreeLogger.Type.WARN, "listener threw exception", e); |
| listenerFailed = true; |
| } |
| } |
| |
| if (!success) { |
| compileLogger.log(TreeLogger.Type.ERROR, "Compiler returned " + success); |
| throw new UnableToCompleteException(); |
| } |
| |
| long elapsedTime = System.currentTimeMillis() - startTime; |
| compileLogger.log(TreeLogger.Type.INFO, "Compile completed in " + elapsedTime + " ms"); |
| |
| if (options.isCompileTest() && listenerFailed) { |
| throw new UnableToCompleteException(); |
| } |
| |
| return compileDir; |
| } |
| |
| synchronized CompileDir noCompile() throws UnableToCompleteException { |
| long startTime = System.currentTimeMillis(); |
| CompileDir compileDir = makeCompileDir(++compilesDone); |
| TreeLogger compileLogger = makeCompileLogger(compileDir); |
| |
| ModuleDef module = loadModule(compileLogger, new HashMap<String, String>()); |
| String newModuleName = module.getName(); // includes any rename. |
| moduleName.set(newModuleName); |
| |
| lastBuild.set(compileDir); |
| |
| try { |
| // Prepare directory. |
| File outputDir = new File( |
| compileDir.getWarDir().getCanonicalPath() + "/" + getModuleName()); |
| if (!outputDir.exists()) { |
| outputDir.mkdir(); |
| } |
| |
| // Creates a "module_name.nocache.js" that just forces a recompile. |
| String moduleScript = PageUtil.loadResource(Recompiler.class, "nomodule.nocache.js"); |
| moduleScript = moduleScript.replace("__MODULE_NAME__", getModuleName()); |
| PageUtil.writeFile(outputDir.getCanonicalPath() + "/" + getModuleName() + ".nocache.js", |
| moduleScript); |
| |
| } catch (IOException e) { |
| compileLogger.log(TreeLogger.Type.ERROR, "Error creating uncompiled module.", e); |
| } |
| long elapsedTime = System.currentTimeMillis() - startTime; |
| compileLogger.log(TreeLogger.Type.INFO, "Module setup completed in " + elapsedTime + " ms"); |
| return compileDir; |
| } |
| |
| /** |
| * Returns the log from the last compile. (It may be a failed build.) |
| */ |
| File getLastLog() { |
| return lastBuild.get().getLogFile(); |
| } |
| |
| String getModuleName() { |
| return moduleName.get(); |
| } |
| |
| ResourceLoader getResourceLoader() { |
| return resourceLoader.get(); |
| } |
| |
| private TreeLogger makeCompileLogger(CompileDir compileDir) |
| throws UnableToCompleteException { |
| try { |
| PrintWriterTreeLogger fileLogger = |
| new PrintWriterTreeLogger(compileDir.getLogFile()); |
| fileLogger.setMaxDetail(TreeLogger.Type.INFO); |
| return new CompositeTreeLogger(logger, fileLogger); |
| } catch (IOException e) { |
| logger.log(TreeLogger.ERROR, "unable to open log file: " + compileDir.getLogFile(), e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| |
| private ModuleDef loadModule(TreeLogger logger, Map<String, String> bindingProperties) |
| throws UnableToCompleteException { |
| |
| // make sure we get the latest version of any modified jar |
| ZipFileClassPathEntry.clearCache(); |
| ResourceOracleImpl.clearCache(); |
| ModuleDefLoader.clearModuleCache(); |
| |
| ResourceLoader resources = ResourceLoaders.forClassLoader(Thread.currentThread()); |
| resources = ResourceLoaders.forPathAndFallback(options.getSourcePath(), resources); |
| this.resourceLoader.set(resources); |
| |
| ModuleDef moduleDef = ModuleDefLoader.loadFromResources( |
| logger, compilerContext, originalModuleName, resources, true, true); |
| compilerContext = compilerContextBuilder.module(moduleDef).build(); |
| |
| // We need a cross-site linker. Automatically replace the default linker. |
| if (IFrameLinker.class.isAssignableFrom(moduleDef.getActivePrimaryLinker())) { |
| moduleDef.addLinker("xsiframe"); |
| } |
| |
| // Check that we have a compatible linker. |
| Class<? extends Linker> linker = moduleDef.getActivePrimaryLinker(); |
| if (! CrossSiteIframeLinker.class.isAssignableFrom(linker)) { |
| logger.log(TreeLogger.ERROR, |
| "linkers other than CrossSiteIFrameLinker aren't supported. Found: " + linker.getName()); |
| throw new UnableToCompleteException(); |
| } |
| |
| // Print a nice error if the superdevmode hook isn't present |
| if (moduleDef.getProperties().find("devModeRedirectEnabled") == null) { |
| throw new RuntimeException("devModeRedirectEnabled isn't set for module: " + |
| moduleDef.getName()); |
| } |
| |
| // Disable the redirect hook here to make sure we don't have an infinite loop. |
| // (There is another check in the JavaScript, but just in case.) |
| overrideConfig(moduleDef, "devModeRedirectEnabled", "false"); |
| |
| // Normally the GWT bootstrap script installs GWT code by calling eval() with a string of |
| // JavaScript, so that it can control the scope that the code runs in. Sourcemaps don't seem |
| // to be working in Chrome when we do this, so turn it off for now. |
| // TODO(cromwellian) remove when Chrome is fixed. |
| overrideConfig(moduleDef, "installScriptJs", |
| "com/google/gwt/core/ext/linker/impl/installScriptDirect.js"); |
| overrideConfig(moduleDef, "installCode", "false"); |
| |
| // override computeScriptBase.js to enable the "Compile" button |
| overrideConfig(moduleDef, "computeScriptBaseJs", |
| "com/google/gwt/dev/codeserver/computeScriptBase.js"); |
| // Fix bug with SDM and Chrome 24+ where //@ sourceURL directives cause X-SourceMap header to be ignored |
| // Frustratingly, Chrome won't canonicalize a relative URL |
| overrideConfig(moduleDef, "includeSourceMapUrl", "http://" + serverPrefix + |
| WebServer.sourceMapLocationForModule(moduleDef.getName())); |
| |
| // If present, set some config properties back to defaults. |
| // (Needed for Google's server-side linker.) |
| maybeOverrideConfig(moduleDef, "includeBootstrapInPrimaryFragment", "false"); |
| maybeOverrideConfig(moduleDef, "permutationsJs", |
| "com/google/gwt/core/ext/linker/impl/permutations.js"); |
| maybeOverrideConfig(moduleDef, "propertiesJs", |
| "com/google/gwt/core/ext/linker/impl/properties.js"); |
| |
| for (Map.Entry<String, String> entry : bindingProperties.entrySet()) { |
| String propName = entry.getKey(); |
| String propValue = entry.getValue(); |
| logger.log(TreeLogger.Type.INFO, "binding: " + propName + "=" + propValue); |
| maybeSetBinding(moduleDef, propName, propValue); |
| } |
| |
| overrideBinding(moduleDef, "compiler.useSourceMaps", "true"); |
| overrideBinding(moduleDef, "superdevmode", "on"); |
| return moduleDef; |
| } |
| |
| /** |
| * Sets a binding unless it's hard-coded in the GWT application. |
| */ |
| private static void maybeSetBinding(ModuleDef module, String propName, String newValue) { |
| Property prop = module.getProperties().find(propName); |
| if (prop instanceof BindingProperty) { |
| BindingProperty binding = (BindingProperty) prop; |
| |
| if (binding.isAllowedValue(newValue)) { |
| binding.setAllowedValues(binding.getRootCondition(), newValue); |
| } |
| } |
| } |
| |
| /** |
| * Sets a binding even if it's set to a different value in the GWT application. |
| */ |
| private static void overrideBinding(ModuleDef module, String propName, String newValue) { |
| Property prop = module.getProperties().find(propName); |
| if (prop instanceof BindingProperty) { |
| BindingProperty binding = (BindingProperty) prop; |
| binding.setAllowedValues(binding.getRootCondition(), newValue); |
| } |
| } |
| |
| private static boolean maybeOverrideConfig(ModuleDef module, String propName, String newValue) { |
| Property prop = module.getProperties().find(propName); |
| if (prop instanceof ConfigurationProperty) { |
| ConfigurationProperty config = (ConfigurationProperty) prop; |
| config.setValue(newValue); |
| return true; |
| } |
| return false; |
| } |
| |
| private static void overrideConfig(ModuleDef module, String propName, String newValue) { |
| if (!maybeOverrideConfig(module, propName, newValue)) { |
| throw new RuntimeException("not found: " + propName); |
| } |
| } |
| |
| private CompileDir makeCompileDir(int compileId) |
| throws UnableToCompleteException { |
| return CompileDir.create(appSpace.getCompileDir(compileId), logger); |
| } |
| } |