| /* |
| * 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; |
| } |
| } |