| /* |
| * 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 |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.core.ext.linker.impl; |
| |
| import com.google.gwt.core.ext.Linker; |
| import com.google.gwt.core.ext.LinkerContext; |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.ext.linker.ArtifactSet; |
| import com.google.gwt.core.ext.linker.ConfigurationProperty; |
| import com.google.gwt.core.ext.linker.EmittedArtifact; |
| import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility; |
| import com.google.gwt.core.ext.linker.LinkerOrder; |
| import com.google.gwt.core.ext.linker.LinkerOrder.Order; |
| import com.google.gwt.core.ext.linker.PublicResource; |
| import com.google.gwt.core.ext.linker.SelectionProperty; |
| import com.google.gwt.dev.cfg.BindingProperty; |
| import com.google.gwt.dev.cfg.ModuleDef; |
| import com.google.gwt.dev.cfg.Property; |
| import com.google.gwt.dev.cfg.Script; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.jjs.JJSOptions; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.js.JsObfuscateNamer; |
| import com.google.gwt.dev.js.JsParser; |
| import com.google.gwt.dev.js.JsParserException; |
| import com.google.gwt.dev.js.JsPrettyNamer; |
| import com.google.gwt.dev.js.JsSourceGenerationVisitor; |
| import com.google.gwt.dev.js.JsStringInterner; |
| import com.google.gwt.dev.js.JsSymbolResolver; |
| import com.google.gwt.dev.js.JsUnusedFunctionRemover; |
| import com.google.gwt.dev.js.JsVerboseNamer; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsFunction; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsScope; |
| import com.google.gwt.dev.util.DefaultTextOutput; |
| import com.google.gwt.dev.util.OutputFileSet; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| /** |
| * An implementation of {@link LinkerContext} that is initialized from a |
| * {@link ModuleDef}. |
| */ |
| public class StandardLinkerContext extends Linker implements LinkerContext { |
| |
| /** |
| * Applies the {@link JsStringInterner} optimization to each top-level |
| * function defined within a JsProgram. |
| */ |
| private static class TopFunctionStringInterner extends JsModVisitor { |
| |
| public static boolean exec(JsProgram program) { |
| TopFunctionStringInterner v = new TopFunctionStringInterner(program); |
| v.accept(program); |
| return v.didChange(); |
| } |
| |
| private final JsProgram program; |
| |
| public TopFunctionStringInterner(JsProgram program) { |
| this.program = program; |
| } |
| |
| @Override |
| public boolean visit(JsFunction x, JsContext ctx) { |
| didChange |= JsStringInterner.exec(program, x.getBody(), x.getScope(), true); |
| return false; |
| } |
| } |
| |
| public static final Comparator<ConfigurationProperty> CONFIGURATION_PROPERTY_COMPARATOR = |
| new Comparator<ConfigurationProperty>() { |
| public int compare(ConfigurationProperty o1, ConfigurationProperty o2) { |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }; |
| |
| static final Comparator<SelectionProperty> SELECTION_PROPERTY_COMPARATOR = new Comparator<SelectionProperty>() { |
| public int compare(SelectionProperty o1, SelectionProperty o2) { |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }; |
| |
| private final SortedSet<ConfigurationProperty> configurationProperties; |
| |
| private final JJSOptions jjsOptions; |
| |
| private final List<Class<? extends Linker>> linkerClasses; |
| private Linker[] linkers; |
| private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>(); |
| private final String moduleFunctionName; |
| private final long moduleLastModified; |
| private final String moduleName; |
| |
| private final Map<String, StandardSelectionProperty> propertiesByName = new HashMap<String, StandardSelectionProperty>(); |
| |
| private final SortedSet<SelectionProperty> selectionProperties; |
| |
| public StandardLinkerContext(TreeLogger logger, ModuleDef module, |
| JJSOptions jjsOptions) throws UnableToCompleteException { |
| logger = logger.branch(TreeLogger.DEBUG, |
| "Constructing StandardLinkerContext", null); |
| |
| this.jjsOptions = jjsOptions; |
| this.moduleFunctionName = module.getFunctionName(); |
| this.moduleName = module.getName(); |
| this.moduleLastModified = module.lastModified(); |
| |
| // Sort the linkers into the order they should actually run. |
| linkerClasses = new ArrayList<Class<? extends Linker>>(); |
| |
| // Get all the pre-linkers first. |
| for (Class<? extends Linker> linkerClass : module.getActiveLinkers()) { |
| Order order = linkerClass.getAnnotation(LinkerOrder.class).value(); |
| assert (order != null); |
| if (order == Order.PRE) { |
| linkerClasses.add(linkerClass); |
| } |
| } |
| |
| // Get the primary linker. |
| Class<? extends Linker> primary = module.getActivePrimaryLinker(); |
| if (primary == null) { |
| logger.log( |
| TreeLogger.ERROR, |
| "Primary linker is null. Does your module " |
| + "inherit from com.google.gwt.core.Core or com.google.gwt.user.User?"); |
| } else { |
| linkerClasses.add(module.getActivePrimaryLinker()); |
| } |
| |
| // Get all the post-linkers IN REVERSE ORDER. |
| { |
| List<Class<? extends Linker>> postLinkerClasses = new ArrayList<Class<? extends Linker>>(); |
| for (Class<? extends Linker> linkerClass : module.getActiveLinkers()) { |
| Order order = linkerClass.getAnnotation(LinkerOrder.class).value(); |
| assert (order != null); |
| if (order == Order.POST) { |
| postLinkerClasses.add(linkerClass); |
| } |
| } |
| Collections.reverse(postLinkerClasses); |
| linkerClasses.addAll(postLinkerClasses); |
| } |
| |
| resetLinkers(logger); |
| |
| for (Map.Entry<String, Class<? extends Linker>> entry : module.getLinkers().entrySet()) { |
| linkerShortNames.put(entry.getValue(), entry.getKey()); |
| } |
| |
| /* |
| * This will make all private PublicResources and GeneratedResources appear |
| * in the root of the module auxiliary directory. |
| */ |
| linkerShortNames.put(this.getClass(), ""); |
| |
| // Break ModuleDef properties out into LinkerContext interfaces |
| { |
| SortedSet<ConfigurationProperty> mutableConfigurationProperties = new TreeSet<ConfigurationProperty>( |
| CONFIGURATION_PROPERTY_COMPARATOR); |
| SortedSet<SelectionProperty> mutableSelectionProperties = new TreeSet<SelectionProperty>( |
| SELECTION_PROPERTY_COMPARATOR); |
| for (Property p : module.getProperties()) { |
| // Create a new view |
| if (p instanceof com.google.gwt.dev.cfg.ConfigurationProperty) { |
| StandardConfigurationProperty newProp = new StandardConfigurationProperty( |
| (com.google.gwt.dev.cfg.ConfigurationProperty) p); |
| mutableConfigurationProperties.add(newProp); |
| if (logger.isLoggable(TreeLogger.SPAM)) { |
| logger.log(TreeLogger.SPAM, |
| "Added configuration property " + newProp, null); |
| } |
| } else if (p instanceof BindingProperty) { |
| StandardSelectionProperty newProp = new StandardSelectionProperty( |
| (BindingProperty) p); |
| mutableSelectionProperties.add(newProp); |
| propertiesByName.put(newProp.getName(), newProp); |
| if (logger.isLoggable(TreeLogger.SPAM)) { |
| logger.log(TreeLogger.SPAM, "Added selection property " + newProp, |
| null); |
| } |
| } else { |
| logger.log(TreeLogger.ERROR, "Unknown property type " |
| + p.getClass().getName()); |
| } |
| } |
| selectionProperties = Collections.unmodifiableSortedSet(mutableSelectionProperties); |
| configurationProperties = Collections.unmodifiableSortedSet(mutableConfigurationProperties); |
| } |
| } |
| |
| public boolean allLinkersAreShardable() { |
| return findUnshardableLinkers().isEmpty(); |
| } |
| |
| /** |
| * Find all linkers that are not updated to support running generators on |
| * compilations shards. |
| */ |
| public List<Linker> findUnshardableLinkers() { |
| List<Linker> problemLinkers = new ArrayList<Linker>(); |
| |
| for (Linker linker : linkers) { |
| if (!linker.isShardable()) { |
| problemLinkers.add(linker); |
| } |
| } |
| return problemLinkers; |
| } |
| |
| /** |
| * Convert all static resources in the specified module to artifacts. |
| */ |
| public ArtifactSet getArtifactsForPublicResources(TreeLogger logger, |
| ModuleDef module) { |
| ArtifactSet artifacts = new ArtifactSet(); |
| for (String path : module.getAllPublicFiles()) { |
| String partialPath = path.replace(File.separatorChar, '/'); |
| PublicResource resource = new StandardPublicResource(partialPath, |
| module.findPublicFile(path)); |
| artifacts.add(resource); |
| if (logger.isLoggable(TreeLogger.SPAM)) { |
| logger.log(TreeLogger.SPAM, "Added public resource " + resource, null); |
| } |
| } |
| |
| { |
| int index = 0; |
| for (Script script : module.getScripts()) { |
| String url = script.getSrc(); |
| artifacts.add(new StandardScriptReference(url, index++)); |
| if (logger.isLoggable(TreeLogger.SPAM)) { |
| logger.log(TreeLogger.SPAM, "Added script " + url, null); |
| } |
| } |
| } |
| |
| { |
| int index = 0; |
| for (String style : module.getStyles()) { |
| artifacts.add(new StandardStylesheetReference(style, index++)); |
| if (logger.isLoggable(TreeLogger.SPAM)) { |
| logger.log(TreeLogger.SPAM, "Added style " + style, null); |
| } |
| } |
| } |
| return artifacts; |
| } |
| |
| public SortedSet<ConfigurationProperty> getConfigurationProperties() { |
| return configurationProperties; |
| } |
| |
| @Override |
| public String getDescription() { |
| return "Root Linker"; |
| } |
| |
| /** |
| * Return the full path for an artifact produced by <code>linkertype</code> |
| * that has the specified partial path. The full path will be the linker's |
| * short name, as defined in the module file, followed by the given partial |
| * path. |
| */ |
| public String getExtraPathForLinker(Class<? extends Linker> linkerType, |
| String partialPath) { |
| assert linkerShortNames.containsKey(linkerType) : linkerType.getName() |
| + " unknown"; |
| return linkerShortNames.get(linkerType) + '/' + partialPath; |
| } |
| |
| public String getModuleFunctionName() { |
| return moduleFunctionName; |
| } |
| |
| public long getModuleLastModified() { |
| return moduleLastModified; |
| } |
| |
| public String getModuleName() { |
| return moduleName; |
| } |
| |
| public SortedSet<SelectionProperty> getProperties() { |
| return selectionProperties; |
| } |
| |
| public StandardSelectionProperty getProperty(String name) { |
| return propertiesByName.get(name); |
| } |
| |
| public ArtifactSet invokeFinalLink(TreeLogger logger, ArtifactSet artifacts) |
| throws UnableToCompleteException { |
| for (Linker linker : linkers) { |
| if (linker.isShardable()) { |
| TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE, |
| "Invoking Linker " + linker.getDescription(), null); |
| artifacts = linker.link(linkerLogger, this, artifacts, false); |
| } |
| } |
| return artifacts; |
| } |
| |
| /** |
| * Run linkers that have not been updated for the shardable API. |
| */ |
| public ArtifactSet invokeLegacyLinkers(TreeLogger logger, |
| ArtifactSet artifacts) throws UnableToCompleteException { |
| ArtifactSet workingArtifacts = new ArtifactSet(artifacts); |
| |
| for (Linker linker : linkers) { |
| if (!linker.isShardable()) { |
| TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE, |
| "Invoking Linker " + linker.getDescription(), null); |
| workingArtifacts.freeze(); |
| try { |
| workingArtifacts = linker.link(linkerLogger, this, workingArtifacts); |
| } catch (Throwable e) { |
| linkerLogger.log(TreeLogger.ERROR, "Failed to link", e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| } |
| return workingArtifacts; |
| } |
| |
| /** |
| * Invoke the shardable linkers on one permutation result. Those linkers run |
| * with the precompile artifacts as input. |
| */ |
| public ArtifactSet invokeLinkForOnePermutation(TreeLogger logger, |
| StandardCompilationResult permResult, ArtifactSet permArtifacts) |
| throws UnableToCompleteException { |
| ArtifactSet workingArtifacts = new ArtifactSet(permArtifacts); |
| workingArtifacts.add(permResult); |
| |
| for (Linker linker : linkers) { |
| if (linker.isShardable()) { |
| TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE, |
| "Invoking Linker " + linker.getDescription(), null); |
| try { |
| workingArtifacts.freeze(); |
| workingArtifacts = linker.link(logger, this, workingArtifacts, true); |
| } catch (Throwable e) { |
| linkerLogger.log(TreeLogger.ERROR, "Failed to link", e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| } |
| |
| /* |
| * Reset linkers so that they don't accidentally carry any state across |
| * permutations |
| */ |
| resetLinkers(logger); |
| |
| workingArtifacts.freeze(); |
| return workingArtifacts; |
| } |
| |
| public ArtifactSet invokeRelink(TreeLogger logger, |
| ArtifactSet newlyGeneratedArtifacts) throws UnableToCompleteException { |
| ArtifactSet workingArtifacts = new ArtifactSet(newlyGeneratedArtifacts); |
| |
| for (Linker linker : linkers) { |
| TreeLogger linkerLogger = logger.branch(TreeLogger.TRACE, |
| "Invoking relink on Linker " + linker.getDescription(), null); |
| workingArtifacts.freeze(); |
| try { |
| workingArtifacts = linker.relink(linkerLogger, this, workingArtifacts); |
| } catch (Throwable e) { |
| linkerLogger.log(TreeLogger.ERROR, "Failed to relink", e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| return workingArtifacts; |
| } |
| |
| public boolean isOutputCompact() { |
| return jjsOptions.getOutput().shouldMinimize(); |
| } |
| |
| @Override |
| public ArtifactSet link(TreeLogger logger, LinkerContext context, |
| ArtifactSet artifacts) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public String optimizeJavaScript(TreeLogger logger, String program) |
| throws UnableToCompleteException { |
| logger = logger.branch(TreeLogger.DEBUG, "Attempting to optimize JS", null); |
| Reader r = new StringReader(program); |
| JsProgram jsProgram = new JsProgram(); |
| JsScope topScope = jsProgram.getScope(); |
| JsName funcName = topScope.declareName(getModuleFunctionName()); |
| funcName.setObfuscatable(false); |
| |
| try { |
| SourceInfo sourceInfo = jsProgram.createSourceInfo(1, |
| "StandardLinkerContext.optimizeJavaScript"); |
| JsParser.parseInto(sourceInfo, topScope, jsProgram.getGlobalBlock(), r); |
| } catch (IOException e) { |
| throw new RuntimeException("Unexpected error reading in-memory stream", e); |
| } catch (JsParserException e) { |
| logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e); |
| throw new UnableToCompleteException(); |
| } |
| |
| JsSymbolResolver.exec(jsProgram); |
| JsUnusedFunctionRemover.exec(jsProgram); |
| |
| switch (jjsOptions.getOutput()) { |
| case OBFUSCATED: |
| /* |
| * We can't apply the regular JsStringInterner to the JsProgram that |
| * we've just created. In the normal case, the JsStringInterner adds an |
| * additional statement to the program's global JsBlock, however we |
| * don't know exactly what the form and structure of our JsProgram are, |
| * so we'll limit the scope of the modifications to each top-level |
| * function within the program. |
| */ |
| TopFunctionStringInterner.exec(jsProgram); |
| JsObfuscateNamer.exec(jsProgram, null); |
| break; |
| case PRETTY: |
| // We don't intern strings in pretty mode to improve readability |
| JsPrettyNamer.exec(jsProgram, null); |
| break; |
| case DETAILED: |
| // As above with OBFUSCATED |
| TopFunctionStringInterner.exec(jsProgram); |
| JsVerboseNamer.exec(jsProgram, null); |
| break; |
| default: |
| throw new InternalCompilerException("Unknown output mode"); |
| } |
| |
| DefaultTextOutput out = new DefaultTextOutput( |
| jjsOptions.getOutput().shouldMinimize()); |
| JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out); |
| v.accept(jsProgram); |
| return out.toString(); |
| } |
| |
| /** |
| * Emit EmittedArtifacts artifacts onto <code>out</code>. Does not close |
| * <code>out</code>. |
| * |
| * @param logger where to log progress |
| * @param artifacts the artifacts to emit |
| * @param visibility the level of visibility of artifacts to output |
| * @param out where to emit the artifact contents |
| */ |
| public void produceOutput(TreeLogger logger, ArtifactSet artifacts, |
| Visibility visibility, OutputFileSet out) |
| throws UnableToCompleteException { |
| logger = logger.branch(TreeLogger.TRACE, "Linking " + visibility |
| + " artifacts into " + out.getPathDescription(), null); |
| |
| for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) { |
| TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG, |
| "Emitting resource " + artifact.getPartialPath(), null); |
| |
| if (!artifact.getVisibility().matches(visibility)) { |
| continue; |
| } |
| |
| String partialPath = artifact.getPartialPath(); |
| if (artifact.getVisibility() != Visibility.Public) { |
| // Any non-public linker will have their artifacts stored in a directory |
| // named after the linker. |
| partialPath = getExtraPathForLinker(artifact.getLinker(), partialPath); |
| if (partialPath.startsWith("/")) { |
| partialPath = partialPath.substring(1); |
| } |
| } |
| try { |
| OutputStream artifactStream = out.openForWrite(partialPath, |
| artifact.getLastModified()); |
| artifact.writeTo(artifactLogger, artifactStream); |
| artifactStream.close(); |
| } catch (IOException e) { |
| artifactLogger.log(TreeLogger.ERROR, |
| "Fatal error emitting artifact: " + artifact.getPartialPath(), e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| } |
| |
| /** |
| * (Re)instantiate all linkers. |
| */ |
| private void resetLinkers(TreeLogger logger) throws UnableToCompleteException { |
| linkers = new Linker[linkerClasses.size()]; |
| int i = 0; |
| for (Class<? extends Linker> linkerClass : linkerClasses) { |
| try { |
| linkers[i++] = linkerClass.newInstance(); |
| } catch (InstantiationException e) { |
| logger.log(TreeLogger.ERROR, "Unable to create Linker", e); |
| throw new UnableToCompleteException(); |
| } catch (IllegalAccessException e) { |
| logger.log(TreeLogger.ERROR, "Unable to create Linker", e); |
| throw new UnableToCompleteException(); |
| } |
| } |
| } |
| } |