blob: 5080b223d96dd429920214b72ef3e5d1cca875b2 [file] [log] [blame]
/*
* 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();
}
}
}
}