All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.IncrementalBuilder Maven / Gradle / Ivy

/*
 * 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.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.impl.ResourceGeneratorUtilImpl;
import com.google.gwt.dev.BuildTarget.OutputFreshness;
import com.google.gwt.dev.cfg.Library;
import com.google.gwt.dev.cfg.LibraryGroup;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.cfg.Properties;
import com.google.gwt.dev.cfg.ResourceLoader;
import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
import com.google.gwt.dev.resource.impl.ZipFileClassPathEntry;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Incrementally builds, links, and rebuilds module trees.
 */
public class IncrementalBuilder {

  /**
   * Represents a combination of whether a build succeeded and whether output changed.
   */
  public static enum BuildResultStatus {
    FAILED(false, false), SUCCESS_NO_CHANGES(false, true), SUCCESS_WITH_CHANGES(true, true);

    private static BuildResultStatus get(boolean success) {
      return success ? SUCCESS_WITH_CHANGES : FAILED;
    }

    private boolean outputChanged;
    private boolean success;

    private BuildResultStatus(boolean outputChanged, boolean success) {
      this.outputChanged = outputChanged;
      this.success = success;
    }

    public boolean isSuccess() {
      return success;
    }

    public boolean outputChanged() {
      return outputChanged;
    }
  }

  @VisibleForTesting
  static final String NO_FILES_HAVE_CHANGED = "No files have changed; all output is still fresh.";

  @VisibleForTesting
  protected static String formatCircularModulePathMessage(List circularModulePath) {
    return "Can't compile because of a module circular reference:\n  "
        + Joiner.on("\n  ").join(circularModulePath);
  }

  private Map buildTargetsByCanonicalModuleName = Maps.newLinkedHashMap();
  private List> circularReferenceModuleNameLoops = Lists.newArrayList();
  private Properties finalProperties;
  private String genDir;
  private Set knownCircularlyReferentModuleNames = Sets.newHashSet();
  private Set moduleReferencePath = Sets.newLinkedHashSet();
  private String outputDir;
  private final ResourceLoader resourceLoader;
  private BuildTarget rootBuildTarget;
  private ModuleDef rootModule;
  private final String rootModuleName;
  private String warDir;
  private BuildTargetOptions buildTargetOptions = new BuildTargetOptions() {

    @Override
    public Properties getFinalProperties() {
      return IncrementalBuilder.this.finalProperties;
    }

    @Override
    public String getGenDir() {
      return IncrementalBuilder.this.genDir;
    }

    @Override
    public String getOutputDir() {
      return IncrementalBuilder.this.outputDir;
    }

    @Override
    public ResourceLoader getResourceLoader() {
      return IncrementalBuilder.this.resourceLoader;
    }

    @Override
    public String getWarDir() {
      return IncrementalBuilder.this.warDir;
    }
  };

  public IncrementalBuilder(String rootModuleName, String warDir, String libDir, String genDir,
      ResourceLoader resourceLoader) {
    this.rootModuleName = rootModuleName;
    this.warDir = warDir;
    this.outputDir = libDir;
    this.genDir = genDir;
    this.resourceLoader = resourceLoader;
  }

  public BuildResultStatus build(TreeLogger logger) {
    try {
      logger = logger.branch(TreeLogger.INFO, "Performing an incremental build");

      CompilerContext compilerContext = new CompilerContext.Builder().compileMonolithic(false)
          .libraryGroup(LibraryGroup.fromLibraries(Lists. newArrayList(), false)).build();
      long beforeLoadRootModuleMs = System.currentTimeMillis();
      rootModule = ModuleDefLoader.loadFromResources(logger, compilerContext, rootModuleName,
          resourceLoader, false);
      finalProperties = rootModule.getProperties();
      long loadRootModuleDurationMs = System.currentTimeMillis() - beforeLoadRootModuleMs;
      logger.log(TreeLogger.INFO, String.format(
          "%.3fs -- Parsing and loading root module definition in %s",
          loadRootModuleDurationMs / 1000d, rootModuleName));

      long beforeCreateTargetGraphMs = System.currentTimeMillis();
      rootBuildTarget = createBuildTarget(logger, rootModuleName);
      rootBuildTarget.setModule(rootModule);
      long createdTargetGraphDurationMs = System.currentTimeMillis() - beforeCreateTargetGraphMs;
      logger.log(TreeLogger.INFO, String.format("%.3fs -- Creating target graph (%s targets)",
          createdTargetGraphDurationMs / 1000d, buildTargetsByCanonicalModuleName.size()));

      if (!circularReferenceModuleNameLoops.isEmpty()) {
        for (List circularReferenceModuleNameLoop : circularReferenceModuleNameLoops) {
          logger.log(TreeLogger.ERROR,
              formatCircularModulePathMessage(circularReferenceModuleNameLoop));
        }
        throw new UnableToCompleteException();
      }
      logLoadedBuildTargetGraph(logger, buildTargetsByCanonicalModuleName);

      long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
      ModuleDefLoader.clearModuleCache();
      rootBuildTarget.computeOutputFreshness(logger);
      long computeOutputFreshnessDurationMs =
          System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
      logger.log(TreeLogger.INFO, String.format("%.3fs -- Computing per-target output freshness",
          computeOutputFreshnessDurationMs / 1000d));

      TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
      boolean success = rootBuildTarget.link(branch);
      return BuildResultStatus.get(success);
    } catch (UnableToCompleteException e) {
      // The real cause has been logged.
      return BuildResultStatus.FAILED;
    }
  }

  public String getRootModuleName() {
    if (rootModule == null) {
      return "UNKNOWN";
    }
    return rootModule.getName();
  }

  public boolean isRootModuleKnown() {
    return rootModule != null;
  }

  public BuildResultStatus rebuild(TreeLogger logger) {
    logger = logger.branch(TreeLogger.INFO, "Performing an incremental rebuild");

    ResourceOracleImpl.clearCache();
    ZipFileClassPathEntry.clearCache();
    ModuleDefLoader.clearModuleCache();
    ResourceGeneratorUtilImpl.clearGeneratedFilesByName();

    long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
    forgetAllOutputFreshness();
    rootBuildTarget.computeOutputFreshness(logger);
    long computeOutputFreshnessDurationMs =
        System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
    logger.log(TreeLogger.INFO, String.format("%.3fs -- Computing per-target output freshness",
        computeOutputFreshnessDurationMs / 1000d));

    if (rootBuildTarget.isOutputFreshAndGood()) {
      logger.log(TreeLogger.INFO, NO_FILES_HAVE_CHANGED);
      return BuildResultStatus.SUCCESS_NO_CHANGES;
    }

    TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
    boolean success = rootBuildTarget.link(branch);
    return BuildResultStatus.get(success);
  }

  public void setWarDir(String warDir) {
    this.warDir = warDir;
  }

  @VisibleForTesting
  void clean() {
    File[] files = new File(outputDir).listFiles();
    if (files == null) {
      // nothing to delete
      return;
    }
    for (File file : files) {
      file.delete();
    }
  }

  private BuildTarget createBuildTarget(String canonicalModuleName, BuildTarget... buildTargets) {
    if (!buildTargetsByCanonicalModuleName.containsKey(canonicalModuleName)) {
      buildTargetsByCanonicalModuleName.put(canonicalModuleName,
          new BuildTarget(canonicalModuleName, buildTargetOptions, buildTargets));
    }
    return buildTargetsByCanonicalModuleName.get(canonicalModuleName);
  }

  private BuildTarget createBuildTarget(TreeLogger logger, String moduleName)
      throws UnableToCompleteException {
    if (isCircularlyReferent(moduleName)) {
      // Allow the target graph creation to continue so that all of the circular reference loops can
      // be gathered.
      return null;
    }
    if (buildTargetsByCanonicalModuleName.containsKey(moduleName)) {
      return buildTargetsByCanonicalModuleName.get(moduleName);
    }

    logger.log(TreeLogger.SPAM, String.format("Adding target %s to build graph.", moduleName));
    moduleReferencePath.add(moduleName);

    List dependencyBuildTargets = Lists.newArrayList();
    for (String dependencyModuleName : rootModule.getDirectDependencies(moduleName)) {
      dependencyBuildTargets.add(createBuildTarget(logger, dependencyModuleName));
    }
    moduleReferencePath.remove(moduleName);

    return createBuildTarget(moduleName, dependencyBuildTargets.toArray(new BuildTarget[0]));
  }

  private void forgetAllOutputFreshness() {
    for (BuildTarget buildTarget : buildTargetsByCanonicalModuleName.values()) {
      buildTarget.setOutputFreshness(OutputFreshness.UNKNOWN);
    }
  }

  private boolean isCircularlyReferent(String potentialDuplicateModuleName) {
    if (knownCircularlyReferentModuleNames.contains(potentialDuplicateModuleName)) {
      return true;
    }
    if (!moduleReferencePath.contains(potentialDuplicateModuleName)) {
      return false;
    }

    List circularModuleReferencePath = Lists.newArrayList(moduleReferencePath);

    // Attach the duplicate module name to the end of the loop.
    circularModuleReferencePath.add(potentialDuplicateModuleName);

    List annotatedCircularModuleReferencePath = Lists.newArrayList();
    // The current module path only includes libraries but the connections between libraries might
    // be silently flowing through filesets. Add filesets to the path so that the output is more
    // readable.
    for (int moduleNameIndex = 0; moduleNameIndex < circularModuleReferencePath.size() - 1;
        moduleNameIndex++) {
      String thisModuleName = circularModuleReferencePath.get(moduleNameIndex);
      String nextModuleName = circularModuleReferencePath.get(moduleNameIndex + 1);

      annotatedCircularModuleReferencePath.add(
          thisModuleName + (thisModuleName.equals(potentialDuplicateModuleName) ? " " : ""));

      List fileSetPath = rootModule.getFileSetPathBetween(thisModuleName, nextModuleName);
      if (fileSetPath != null) {
        for (String fileSetModuleName : fileSetPath) {
          annotatedCircularModuleReferencePath.add(fileSetModuleName + " ");
        }
      }
    }

    // Attach the duplicate module name to the end of the loop.
    annotatedCircularModuleReferencePath.add(potentialDuplicateModuleName + " ");

    knownCircularlyReferentModuleNames.addAll(annotatedCircularModuleReferencePath);
    circularReferenceModuleNameLoops.add(annotatedCircularModuleReferencePath);
    return true;
  }

  private void logLoadedBuildTargetGraph(TreeLogger logger,
      Map buildTargetsByCanonicalModuleName) {
    logger.log(TreeLogger.SPAM, "Loaded build target graph:");
    for (String canonicalModuleName : buildTargetsByCanonicalModuleName.keySet()) {
      logger.log(TreeLogger.SPAM, "\t" + canonicalModuleName);
      BuildTarget gwtTarget = buildTargetsByCanonicalModuleName.get(canonicalModuleName);
      for (BuildTarget dependencyBuildTarget : gwtTarget.getDependencyBuildTargets()) {
        logger.log(TreeLogger.SPAM, "\t\t" + dependencyBuildTarget.getCanonicalModuleName());
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy