blob: d3a12f7ee6f357a5481521ec71dbff19e91f623d [file] [log] [blame]
/*
* Copyright 2014 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.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.RuntimeRebindRuleGenerator;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Represents a module in a module tree, knows how to build that module into an output library and
* checks output library freshness.
* <p>
* Build requests will first build all dependency libraries (even if some fail). But the current
* target will only be built if all dependency library builds were successful.
*/
class BuildTarget {
/**
* Represents a combination of whether output is current and whether output is good. For example
* if a build is run and it fails, then output is fresh and does not need to be rebuilt but it is
* also known to be bad.
*/
public enum OutputFreshness {
FRESH_KNOWN_BAD, FRESH_KNOWN_GOOD, STALE, UNKNOWN
}
public static final Function<BuildTarget, String> LIBRARY_PATH_FUNCTION =
new Function<BuildTarget, String>() {
@Override
public String apply(@Nullable BuildTarget buildTarget) {
return buildTarget.computeLibraryPath();
}
};
@VisibleForTesting
public static String formatCompilingModuleMessage(String canonicalModuleName) {
return "\n" + "Compiling module " + canonicalModuleName;
}
@VisibleForTesting
public static String formatReusingCachedLibraryMessage(String canonicalModuleName) {
return "Reusing cached library for " + canonicalModuleName;
}
private final BuildTargetOptions buildTargetOptions;
private final String canonicalModuleName;
private final List<BuildTarget> dependencyBuildTargets;
private ModuleDef module;
private OutputFreshness outputFreshness = OutputFreshness.UNKNOWN;
private Set<BuildTarget> transitiveDependencyBuildTargets;
BuildTarget(String canonicalModuleName, BuildTargetOptions buildTargetOptions,
BuildTarget... dependencyBuildTargets) {
this.canonicalModuleName = canonicalModuleName;
this.buildTargetOptions = buildTargetOptions;
this.dependencyBuildTargets = Arrays.asList(dependencyBuildTargets);
}
public boolean build(TreeLogger logger) {
return build(logger, false);
}
public boolean build(TreeLogger logger, boolean link) {
if (outputFreshness == OutputFreshness.FRESH_KNOWN_GOOD
|| outputFreshness == OutputFreshness.FRESH_KNOWN_BAD) {
logger.log(TreeLogger.SPAM, formatReusingCachedLibraryMessage(canonicalModuleName));
return outputFreshness == OutputFreshness.FRESH_KNOWN_GOOD;
}
boolean dependencyBuildsSucceeded = true;
// Build all my dependencies before myself.
for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
// If any dependency fails to build then I have failed to build as well.
dependencyBuildsSucceeded &= dependencyBuildTarget.build(logger);
}
if (!dependencyBuildsSucceeded) {
outputFreshness = OutputFreshness.FRESH_KNOWN_BAD;
return false; // Build failed.
}
TreeLogger branch =
logger.branch(TreeLogger.INFO, formatCompilingModuleMessage(canonicalModuleName));
boolean thisBuildSucceeded;
try {
RuntimeRebindRuleGenerator.RUNTIME_REBIND_RULE_SOURCES_BY_SHORT_NAME.clear();
LibraryCompiler libraryCompiler = new LibraryCompiler(computeCompileOptions(link));
libraryCompiler.setResourceLoader(buildTargetOptions.getResourceLoader());
thisBuildSucceeded = libraryCompiler.run(branch);
module = libraryCompiler.getModule();
} catch (Throwable t) {
logger.log(TreeLogger.ERROR, t.getMessage());
outputFreshness = OutputFreshness.FRESH_KNOWN_BAD;
return false;
}
outputFreshness =
thisBuildSucceeded ? OutputFreshness.FRESH_KNOWN_GOOD : OutputFreshness.FRESH_KNOWN_BAD;
return thisBuildSucceeded;
}
public CompilerOptions computeCompileOptions(boolean link) {
CompilerOptions compilerOptions = new CompilerOptionsImpl();
// Must compile the canonical name, not name, since after module-renames there may be more
// than one module in the classpath with the same name and we don't want to find and recompile
// the same one over and over.
compilerOptions.setModuleNames(Lists.newArrayList(canonicalModuleName));
compilerOptions.setLink(link);
compilerOptions.setLogLevel(TreeLogger.ERROR);
compilerOptions.setGenDir(new File(buildTargetOptions.getGenDir()));
compilerOptions.setWorkDir(new File(buildTargetOptions.getOutputDir()));
compilerOptions.setLibraryPaths(Lists.newArrayList(
Iterables.transform(getTransitiveDependencyBuildTargets(), LIBRARY_PATH_FUNCTION)));
compilerOptions.setFinalProperties(buildTargetOptions.getFinalProperties());
if (!link) {
compilerOptions.setOutputLibraryPath(computeLibraryPath());
} else {
compilerOptions.setWarDir(new File(buildTargetOptions.getWarDir()));
}
return compilerOptions;
}
public String computeLibraryPath() {
return buildTargetOptions.getOutputDir() + "/" + canonicalModuleName + ".gwtlib";
}
public void computeOutputFreshness(TreeLogger logger) {
if (outputFreshness != OutputFreshness.UNKNOWN) {
return;
}
for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
dependencyBuildTarget.computeOutputFreshness(logger);
}
if (module == null) {
logger.log(TreeLogger.SPAM,
"Library " + canonicalModuleName + " is stale: the module hasn't been loaded yet");
outputFreshness = OutputFreshness.STALE;
return;
}
for (BuildTarget dependencyBuildTarget : dependencyBuildTargets) {
if (dependencyBuildTarget.outputFreshness == OutputFreshness.STALE) {
logger.log(TreeLogger.SPAM,
"Library " + canonicalModuleName + " is stale: has a stale dependency");
outputFreshness = OutputFreshness.STALE;
return;
}
}
File libraryFile = new File(computeLibraryPath());
if (!libraryFile.exists()) {
logger.log(TreeLogger.SPAM,
"Library " + canonicalModuleName + " is stale: the library file is missing");
outputFreshness = OutputFreshness.STALE;
return;
}
long libraryFileLastModified = libraryFile.lastModified();
module.refresh();
if (libraryFileLastModified < module.getResourceLastModified()) {
Set<Resource> newerResources = module.getResourcesNewerThan(libraryFileLastModified);
TreeLogger branch = logger.branch(TreeLogger.SPAM,
"Library " + canonicalModuleName + " is stale: library is older than some resource(s)");
for (Resource newerResource : newerResources) {
branch.log(TreeLogger.SPAM, newerResource.getPath() + " has changed");
}
outputFreshness = OutputFreshness.STALE;
return;
}
logger.log(TreeLogger.SPAM, "Library " + canonicalModuleName + " is fresh");
outputFreshness = OutputFreshness.FRESH_KNOWN_GOOD;
}
public String getCanonicalModuleName() {
return canonicalModuleName;
}
public List<BuildTarget> getDependencyBuildTargets() {
return dependencyBuildTargets;
}
public Set<BuildTarget> getTransitiveDependencyBuildTargets() {
if (transitiveDependencyBuildTargets == null) {
transitiveDependencyBuildTargets = Sets.newHashSet();
transitiveDependencyBuildTargets.addAll(dependencyBuildTargets);
for (BuildTarget buildTarget : dependencyBuildTargets) {
transitiveDependencyBuildTargets.addAll(buildTarget.getTransitiveDependencyBuildTargets());
}
}
return transitiveDependencyBuildTargets;
}
public boolean isOutputFreshAndGood() {
return outputFreshness == OutputFreshness.FRESH_KNOWN_GOOD;
}
public boolean link(TreeLogger logger) {
return build(logger, true);
}
public void setModule(ModuleDef module) {
this.module = module;
}
public void setOutputFreshness(OutputFreshness outputFreshness) {
this.outputFreshness = outputFreshness;
}
}