aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java1476
1 files changed, 1476 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
new file mode 100644
index 0000000000..29b4c236b2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -0,0 +1,1476 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.devtools.build.lib.skyframe;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionCacheChecker;
+import com.google.devtools.build.lib.actions.ActionExecutionStatusReporter;
+import com.google.devtools.build.lib.actions.ActionInputFileCache;
+import com.google.devtools.build.lib.actions.ActionLogBufferPathGenerator;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceManager;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.Aspect;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.BuildView.Options;
+import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.DependencyResolver.Dependency;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
+import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
+import com.google.devtools.build.lib.analysis.WorkspaceStatusAction.Factory;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationKey;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.Preprocessor;
+import com.google.devtools.build.lib.packages.RuleVisibility;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
+import com.google.devtools.build.lib.pkgcache.PackageManager;
+import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
+import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader;
+import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ActionCompletedReceiver;
+import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ProgressSupplier;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.util.ResourceUsage;
+import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
+import com.google.devtools.build.lib.vfs.BatchStat;
+import com.google.devtools.build.lib.vfs.ModifiedFileSet;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.lib.vfs.UnixGlob;
+import com.google.devtools.build.skyframe.BuildDriver;
+import com.google.devtools.build.skyframe.CycleInfo;
+import com.google.devtools.build.skyframe.CyclesReporter;
+import com.google.devtools.build.skyframe.Differencer;
+import com.google.devtools.build.skyframe.ErrorInfo;
+import com.google.devtools.build.skyframe.EvaluationProgressReceiver;
+import com.google.devtools.build.skyframe.EvaluationResult;
+import com.google.devtools.build.skyframe.Injectable;
+import com.google.devtools.build.skyframe.MemoizingEvaluator;
+import com.google.devtools.build.skyframe.MemoizingEvaluator.EvaluatorSupplier;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * A helper object to support Skyframe-driven execution.
+ *
+ * <p>This object is mostly used to inject external state, such as the executor engine or
+ * some additional artifacts (workspace status and build info artifacts) into SkyFunctions
+ * for use during the build.
+ */
+public abstract class SkyframeExecutor {
+ private final EvaluatorSupplier evaluatorSupplier;
+ protected MemoizingEvaluator memoizingEvaluator;
+ private final MemoizingEvaluator.EmittedEventState emittedEventState =
+ new MemoizingEvaluator.EmittedEventState();
+ protected final Reporter reporter;
+ private final PackageFactory pkgFactory;
+ private final WorkspaceStatusAction.Factory workspaceStatusActionFactory;
+ private final BlazeDirectories directories;
+ @Nullable
+ private BatchStat batchStatter;
+
+ // TODO(bazel-team): Figure out how to handle value builders that block internally. Blocking
+ // operations may need to be handled in another (bigger?) thread pool. Also, we should detect
+ // the number of cores and use that as the thread-pool size for CPU-bound operations.
+ // I just bumped this to 200 to get reasonable execution phase performance; that may cause
+ // significant overhead for CPU-bound processes (i.e. analysis). [skyframe-analysis]
+ @VisibleForTesting
+ public static final int DEFAULT_THREAD_COUNT = 200;
+
+ // Stores Packages between reruns of the PackageFunction (because of missing dependencies,
+ // within the same evaluate() run) to avoid loading the same package twice (first time loading
+ // to find subincludes and declare value dependencies).
+ // TODO(bazel-team): remove this cache once we have skyframe-native package loading
+ // [skyframe-loading]
+ private final ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache =
+ Maps.newConcurrentMap();
+ private final AtomicInteger numPackagesLoaded = new AtomicInteger(0);
+
+ protected SkyframeBuildView skyframeBuildView;
+ private EventHandler errorEventListener;
+ private ActionLogBufferPathGenerator actionLogBufferPathGenerator;
+
+ protected BuildDriver buildDriver;
+
+ // AtomicReferences are used here as mutable boxes shared with value builders.
+ private final AtomicBoolean showLoadingProgress = new AtomicBoolean();
+ protected final AtomicReference<UnixGlob.FilesystemCalls> syscalls =
+ new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS);
+ protected final AtomicReference<PathPackageLocator> pkgLocator =
+ new AtomicReference<>();
+ protected final AtomicReference<ImmutableSet<String>> deletedPackages =
+ new AtomicReference<>(ImmutableSet.<String>of());
+ private final AtomicReference<EventBus> eventBus = new AtomicReference<>();
+
+ private final ImmutableList<BuildInfoFactory> buildInfoFactories;
+ // Under normal circumstances, the artifact factory persists for the life of a Blaze server, but
+ // since it is not yet created when we create the value builders, we have to use a supplier,
+ // initialized when the build view is created.
+ private final MutableSupplier<ArtifactFactory> artifactFactory = new MutableSupplier<>();
+ // Used to give to WriteBuildInfoAction via a supplier. Relying on BuildVariableValue.BUILD_ID
+ // would be preferable, but we have no way to have the Action depend on that value directly.
+ // Having the BuildInfoFunction own the supplier is currently not possible either, because then
+ // it would be invalidated on every build, since it would depend on the build id value.
+ private MutableSupplier<UUID> buildId = new MutableSupplier<>();
+
+ protected boolean active = true;
+ private final PackageManager packageManager;
+
+ private final Preprocessor.Factory.Supplier preprocessorFactorySupplier;
+ private Preprocessor.Factory preprocessorFactory;
+
+ protected final TimestampGranularityMonitor tsgm;
+
+ private final ResourceManager resourceManager;
+
+ /** Used to lock evaluator on legacy calls to get existing values. */
+ private final Object valueLookupLock = new Object();
+ private final AtomicReference<ActionExecutionStatusReporter> statusReporterRef =
+ new AtomicReference<>();
+ private final SkyframeActionExecutor skyframeActionExecutor;
+ protected SkyframeProgressReceiver progressReceiver;
+ private final AtomicReference<CyclesReporter> cyclesReporter = new AtomicReference<>();
+
+ private final Set<Path> immutableDirectories;
+
+ private BinTools binTools = null;
+ private boolean needToInjectEmbeddedArtifacts = true;
+ private boolean needToInjectPrecomputedValuesForAnalysis = true;
+ protected int modifiedFiles;
+ private final Predicate<PathFragment> allowedMissingInputs;
+
+ private final ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions;
+ private final ImmutableList<PrecomputedValue.Injected> extraPrecomputedValues;
+
+ protected SkyframeIncrementalBuildMonitor incrementalBuildMonitor =
+ new SkyframeIncrementalBuildMonitor();
+
+ private MutableSupplier<ConfigurationFactory> configurationFactory = new MutableSupplier<>();
+ private MutableSupplier<Map<String, String>> clientEnv = new MutableSupplier<>();
+ private MutableSupplier<ImmutableList<ConfigurationFragmentFactory>> configurationFragments =
+ new MutableSupplier<>();
+ private MutableSupplier<Set<Package>> configurationPackages = new MutableSupplier<>();
+ private SkyKey configurationSkyKey = null;
+
+ private static final Logger LOG = Logger.getLogger(SkyframeExecutor.class.getName());
+
+ protected SkyframeExecutor(
+ Reporter reporter,
+ EvaluatorSupplier evaluatorSupplier,
+ PackageFactory pkgFactory,
+ TimestampGranularityMonitor tsgm,
+ BlazeDirectories directories,
+ Factory workspaceStatusActionFactory,
+ ImmutableList<BuildInfoFactory> buildInfoFactories,
+ Set<Path> immutableDirectories,
+ Predicate<PathFragment> allowedMissingInputs,
+ Preprocessor.Factory.Supplier preprocessorFactorySupplier,
+ ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions,
+ ImmutableList<PrecomputedValue.Injected> extraPrecomputedValues) {
+ // Strictly speaking, these arguments are not required for initialization, but all current
+ // callsites have them at hand, so we might as well set them during construction.
+ this.reporter = Preconditions.checkNotNull(reporter);
+ this.evaluatorSupplier = evaluatorSupplier;
+ this.pkgFactory = pkgFactory;
+ this.pkgFactory.setSyscalls(syscalls);
+ this.tsgm = tsgm;
+ this.workspaceStatusActionFactory = workspaceStatusActionFactory;
+ this.packageManager = new SkyframePackageManager(
+ new SkyframePackageLoader(), new SkyframeTransitivePackageLoader(),
+ new SkyframeTargetPatternEvaluator(this), syscalls, cyclesReporter, pkgLocator,
+ numPackagesLoaded, this);
+ this.errorEventListener = this.reporter;
+ this.resourceManager = ResourceManager.instance();
+ this.skyframeActionExecutor = new SkyframeActionExecutor(reporter, resourceManager, eventBus,
+ statusReporterRef);
+ this.directories = Preconditions.checkNotNull(directories);
+ this.buildInfoFactories = buildInfoFactories;
+ this.immutableDirectories = immutableDirectories;
+ this.allowedMissingInputs = allowedMissingInputs;
+ this.preprocessorFactorySupplier = preprocessorFactorySupplier;
+ this.extraSkyFunctions = extraSkyFunctions;
+ this.extraPrecomputedValues = extraPrecomputedValues;
+ }
+
+ private ImmutableMap<SkyFunctionName, SkyFunction> skyFunctions(
+ Root buildDataDirectory,
+ PackageFactory pkgFactory,
+ Predicate<PathFragment> allowedMissingInputs) {
+ ExternalFilesHelper externalFilesHelper = new ExternalFilesHelper(pkgLocator,
+ immutableDirectories);
+ // We use an immutable map builder for the nice side effect that it throws if a duplicate key
+ // is inserted.
+ ImmutableMap.Builder<SkyFunctionName, SkyFunction> map = ImmutableMap.builder();
+ map.put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction());
+ map.put(SkyFunctions.FILE_STATE, new FileStateFunction(tsgm, externalFilesHelper));
+ map.put(SkyFunctions.DIRECTORY_LISTING_STATE,
+ new DirectoryListingStateFunction(externalFilesHelper));
+ map.put(SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS,
+ new FileSymlinkCycleUniquenessFunction());
+ map.put(SkyFunctions.FILE, new FileFunction(pkgLocator, externalFilesHelper));
+ map.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
+ map.put(SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction(deletedPackages));
+ map.put(SkyFunctions.CONTAINING_PACKAGE_LOOKUP, new ContainingPackageLookupFunction());
+ map.put(SkyFunctions.AST_FILE_LOOKUP, new ASTFileLookupFunction(
+ pkgLocator, packageManager, pkgFactory.getRuleClassProvider()));
+ map.put(SkyFunctions.SKYLARK_IMPORTS_LOOKUP, new SkylarkImportLookupFunction(
+ pkgFactory.getRuleClassProvider(), pkgFactory));
+ map.put(SkyFunctions.GLOB, new GlobFunction());
+ map.put(SkyFunctions.TARGET_PATTERN, new TargetPatternFunction(pkgLocator));
+ map.put(SkyFunctions.RECURSIVE_PKG, new RecursivePkgFunction());
+ map.put(SkyFunctions.PACKAGE, new PackageFunction(
+ reporter, pkgFactory, packageManager, showLoadingProgress, packageFunctionCache,
+ eventBus, numPackagesLoaded));
+ map.put(SkyFunctions.TARGET_MARKER, new TargetMarkerFunction());
+ map.put(SkyFunctions.TRANSITIVE_TARGET, new TransitiveTargetFunction());
+ map.put(SkyFunctions.CONFIGURED_TARGET,
+ new ConfiguredTargetFunction(new BuildViewProvider()));
+ map.put(SkyFunctions.ASPECT, new AspectFunction(new BuildViewProvider()));
+ map.put(SkyFunctions.POST_CONFIGURED_TARGET,
+ new PostConfiguredTargetFunction(new BuildViewProvider()));
+ map.put(SkyFunctions.CONFIGURATION_COLLECTION, new ConfigurationCollectionFunction(
+ configurationFactory, clientEnv, configurationPackages));
+ map.put(SkyFunctions.CONFIGURATION_FRAGMENT, new ConfigurationFragmentFunction(
+ configurationFragments, configurationPackages));
+ map.put(SkyFunctions.WORKSPACE_FILE, new WorkspaceFileFunction(pkgFactory));
+ map.put(SkyFunctions.TARGET_COMPLETION, new TargetCompletionFunction(eventBus));
+ map.put(SkyFunctions.TEST_COMPLETION, new TestCompletionFunction());
+ map.put(SkyFunctions.ARTIFACT, new ArtifactFunction(allowedMissingInputs));
+ map.put(SkyFunctions.BUILD_INFO_COLLECTION, new BuildInfoCollectionFunction(artifactFactory,
+ buildDataDirectory));
+ map.put(SkyFunctions.BUILD_INFO, new WorkspaceStatusFunction());
+ map.put(SkyFunctions.COVERAGE_REPORT, new CoverageReportFunction());
+ map.put(SkyFunctions.ACTION_EXECUTION,
+ new ActionExecutionFunction(skyframeActionExecutor, tsgm));
+ map.put(SkyFunctions.RECURSIVE_FILESYSTEM_TRAVERSAL,
+ new RecursiveFilesystemTraversalFunction());
+ map.put(SkyFunctions.FILESET_ENTRY, new FilesetEntryFunction());
+ map.putAll(extraSkyFunctions);
+ return map.build();
+ }
+
+ @ThreadCompatible
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ protected void checkActive() {
+ Preconditions.checkState(active);
+ }
+
+ public void setFileCache(ActionInputFileCache fileCache) {
+ this.skyframeActionExecutor.setFileCache(fileCache);
+ }
+
+ public void dump(boolean summarize, PrintStream out) {
+ memoizingEvaluator.dump(summarize, out);
+ }
+
+ public abstract void dumpPackages(PrintStream out);
+
+ public void setBatchStatter(@Nullable BatchStat batchStatter) {
+ this.batchStatter = batchStatter;
+ }
+
+ /**
+ * Notify listeners about changed files, and release any associated memory afterwards.
+ */
+ public void drainChangedFiles() {
+ incrementalBuildMonitor.alertListeners(getEventBus());
+ incrementalBuildMonitor = null;
+ }
+
+ @VisibleForTesting
+ public BuildDriver getDriverForTesting() {
+ return buildDriver;
+ }
+
+ /**
+ * This method exists only to allow a module to make a top-level Skyframe call during the
+ * transition to making it fully Skyframe-compatible. Do not add additional callers!
+ */
+ public <E extends Exception> SkyValue evaluateSkyKeyForCodeMigration(final SkyKey key,
+ final Class<E> clazz) throws E {
+ try {
+ return callUninterruptibly(new Callable<SkyValue>() {
+ @Override
+ public SkyValue call() throws E, InterruptedException {
+ synchronized (valueLookupLock) {
+ // We evaluate in keepGoing mode because in the case that the graph does not store its
+ // edges, nokeepGoing builds are not allowed, whereas keepGoing builds are always
+ // permitted.
+ EvaluationResult<ActionLookupValue> result = buildDriver.evaluate(
+ ImmutableList.of(key), true, ResourceUsage.getAvailableProcessors(),
+ errorEventListener);
+ if (!result.hasError()) {
+ return Preconditions.checkNotNull(result.get(key), "%s %s", result, key);
+ }
+ ErrorInfo errorInfo = Preconditions.checkNotNull(result.getError(key),
+ "%s %s", key, result);
+ Throwables.propagateIfPossible(errorInfo.getException(), clazz);
+ if (errorInfo.getException() != null) {
+ throw new IllegalStateException(errorInfo.getException());
+ }
+ throw new IllegalStateException(errorInfo.toString());
+ }
+ }
+ });
+ } catch (Exception e) {
+ Throwables.propagateIfPossible(e, clazz);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ class BuildViewProvider {
+ /**
+ * Returns the current {@link SkyframeBuildView} instance.
+ */
+ SkyframeBuildView getSkyframeBuildView() {
+ return skyframeBuildView;
+ }
+ }
+
+ /**
+ * Must be called before the {@link SkyframeExecutor} can be used (should only be called in
+ * factory methods and as an implementation detail of {@link #resetEvaluator}).
+ */
+ protected void init() {
+ progressReceiver = new SkyframeProgressReceiver();
+ Map<SkyFunctionName, SkyFunction> skyFunctions = skyFunctions(
+ directories.getBuildDataDirectory(), pkgFactory, allowedMissingInputs);
+ memoizingEvaluator = evaluatorSupplier.create(
+ skyFunctions, evaluatorDiffer(), progressReceiver, emittedEventState,
+ hasIncrementalState());
+ buildDriver = newBuildDriver();
+ }
+
+ /**
+ * Reinitializes the Skyframe evaluator, dropping all previously computed values.
+ *
+ * <p>Be careful with this method as it also deletes all injected values. You need to make sure
+ * that any necessary precomputed values are reinjected before the next build. Constants can be
+ * put in {@link #reinjectConstantValuesLazily}.
+ */
+ public void resetEvaluator() {
+ init();
+ emittedEventState.clear();
+ if (skyframeBuildView != null) {
+ skyframeBuildView.clearLegacyData();
+ }
+ reinjectConstantValuesLazily();
+ }
+
+ protected abstract Differencer evaluatorDiffer();
+
+ protected abstract BuildDriver newBuildDriver();
+
+ /**
+ * Values whose values are known at startup and guaranteed constant are still wiped from the
+ * evaluator when we create a new one, so they must be re-injected each time we create a new
+ * evaluator.
+ */
+ private void reinjectConstantValuesLazily() {
+ needToInjectEmbeddedArtifacts = true;
+ needToInjectPrecomputedValuesForAnalysis = true;
+ }
+
+ /**
+ * Deletes all ConfiguredTarget values from the Skyframe cache. This is done to save memory (e.g.
+ * on a configuration change); since the configuration is part of the key, these key/value pairs
+ * will be sitting around doing nothing until the configuration changes back to the previous
+ * value.
+ *
+ * <p>The next evaluation will delete all invalid values.
+ */
+ public abstract void dropConfiguredTargets();
+
+ /**
+ * Removes ConfigurationFragmentValuess and ConfigurationCollectionValues from the cache.
+ */
+ @VisibleForTesting
+ public void invalidateConfigurationCollection() {
+ invalidate(SkyFunctionName.functionIsIn(ImmutableSet.of(SkyFunctions.CONFIGURATION_FRAGMENT,
+ SkyFunctions.CONFIGURATION_COLLECTION)));
+ }
+
+ /**
+ * Decides if graph edges should be stored for this build. If not, re-creates the graph to not
+ * store graph edges. Necessary conditions to not store graph edges are:
+ * (1) batch (since incremental builds are not possible);
+ * (2) skyframe build (since otherwise the memory savings are too slight to bother);
+ * (3) keep-going (since otherwise bubbling errors up may require edges of done nodes);
+ * (4) discard_analysis_cache (since otherwise user isn't concerned about saving memory this way).
+ */
+ public void decideKeepIncrementalState(boolean batch, Options viewOptions) {
+ // Assume incrementality.
+ }
+
+ public boolean hasIncrementalState() {
+ return true;
+ }
+
+ @VisibleForTesting
+ protected abstract Injectable injectable();
+
+ /**
+ * Saves memory by clearing analysis objects from Skyframe. If using legacy execution, actually
+ * deletes the relevant values. If using Skyframe execution, clears their data without deleting
+ * them (they will be deleted on the next build).
+ */
+ public abstract void clearAnalysisCache(Collection<ConfiguredTarget> topLevelTargets);
+
+ /**
+ * Injects the contents of the computed tools/defaults package.
+ */
+ @VisibleForTesting
+ public void setupDefaultPackage(String defaultsPackageContents) {
+ PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.set(injectable(), defaultsPackageContents);
+ }
+
+ /**
+ * Injects the top-level artifact options.
+ */
+ public void injectTopLevelContext(TopLevelArtifactContext options) {
+ PrecomputedValue.TOP_LEVEL_CONTEXT.set(injectable(), options);
+ }
+
+ public void injectWorkspaceStatusData() {
+ PrecomputedValue.WORKSPACE_STATUS_KEY.set(injectable(),
+ workspaceStatusActionFactory.createWorkspaceStatusAction(
+ artifactFactory.get(), WorkspaceStatusValue.ARTIFACT_OWNER, buildId));
+ }
+
+ public void injectCoverageReportData(Action action) {
+ PrecomputedValue.COVERAGE_REPORT_KEY.set(injectable(), action);
+ }
+
+ /**
+ * Sets the default visibility.
+ */
+ private void setDefaultVisibility(RuleVisibility defaultVisibility) {
+ PrecomputedValue.DEFAULT_VISIBILITY.set(injectable(), defaultVisibility);
+ }
+
+ private void maybeInjectPrecomputedValuesForAnalysis() {
+ if (needToInjectPrecomputedValuesForAnalysis) {
+ injectBuildInfoFactories();
+ injectExtraPrecomputedValues();
+ needToInjectPrecomputedValuesForAnalysis = false;
+ }
+ }
+
+ private void injectExtraPrecomputedValues() {
+ for (PrecomputedValue.Injected injected : extraPrecomputedValues) {
+ injected.inject(injectable());
+ }
+ }
+
+ /**
+ * Injects the build info factory map that will be used when constructing build info
+ * actions/artifacts. Unchanged across the life of the Blaze server, although it must be injected
+ * each time the evaluator is created.
+ */
+ private void injectBuildInfoFactories() {
+ ImmutableMap.Builder<BuildInfoKey, BuildInfoFactory> factoryMapBuilder =
+ ImmutableMap.builder();
+ for (BuildInfoFactory factory : buildInfoFactories) {
+ factoryMapBuilder.put(factory.getKey(), factory);
+ }
+ PrecomputedValue.BUILD_INFO_FACTORIES.set(injectable(), factoryMapBuilder.build());
+ }
+
+ private void setShowLoadingProgress(boolean showLoadingProgressValue) {
+ showLoadingProgress.set(showLoadingProgressValue);
+ }
+
+ @VisibleForTesting
+ public void setCommandId(UUID commandId) {
+ PrecomputedValue.BUILD_ID.set(injectable(), commandId);
+ buildId.set(commandId);
+ }
+
+ /** Returns the build-info.txt and build-changelist.txt artifacts. */
+ public Collection<Artifact> getWorkspaceStatusArtifacts() throws InterruptedException {
+ // Should already be present, unless the user didn't request any targets for analysis.
+ EvaluationResult<WorkspaceStatusValue> result = buildDriver.evaluate(
+ ImmutableList.of(WorkspaceStatusValue.SKY_KEY), /*keepGoing=*/true, /*numThreads=*/1,
+ reporter);
+ WorkspaceStatusValue value =
+ Preconditions.checkNotNull(result.get(WorkspaceStatusValue.SKY_KEY));
+ return ImmutableList.of(value.getStableArtifact(), value.getVolatileArtifact());
+ }
+
+ // TODO(bazel-team): Make this take a PackageIdentifier.
+ public Map<PathFragment, Root> getArtifactRoots(Iterable<PathFragment> execPaths) {
+ final List<SkyKey> packageKeys = new ArrayList<>();
+ for (PathFragment execPath : execPaths) {
+ Preconditions.checkArgument(!execPath.isAbsolute(), execPath);
+ packageKeys.add(ContainingPackageLookupValue.key(
+ PackageIdentifier.createInDefaultRepo(execPath)));
+ }
+
+ EvaluationResult<ContainingPackageLookupValue> result;
+ try {
+ result = callUninterruptibly(new Callable<EvaluationResult<ContainingPackageLookupValue>>() {
+ @Override
+ public EvaluationResult<ContainingPackageLookupValue> call() throws InterruptedException {
+ return buildDriver.evaluate(packageKeys, /*keepGoing=*/true, /*numThreads=*/1, reporter);
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalStateException(e); // Should never happen.
+ }
+
+ Map<PathFragment, Root> roots = new HashMap<>();
+ for (PathFragment execPath : execPaths) {
+ ContainingPackageLookupValue value = result.get(ContainingPackageLookupValue.key(
+ PackageIdentifier.createInDefaultRepo(execPath)));
+ if (value.hasContainingPackage()) {
+ roots.put(execPath, Root.asSourceRoot(value.getContainingPackageRoot()));
+ } else {
+ roots.put(execPath, null);
+ }
+ }
+ return roots;
+ }
+
+ @VisibleForTesting
+ public WorkspaceStatusAction getLastWorkspaceStatusActionForTesting() {
+ PrecomputedValue value = (PrecomputedValue) buildDriver.getGraphForTesting()
+ .getExistingValueForTesting(PrecomputedValue.WORKSPACE_STATUS_KEY.getKeyForTesting());
+ return (WorkspaceStatusAction) value.get();
+ }
+
+ /**
+ * Informs user about number of modified files (source and output files).
+ */
+ // Note, that number of modified files in some cases can be bigger than actual number of
+ // modified files for targets in current request. Skyframe may check for modification all files
+ // from previous requests.
+ protected void informAboutNumberOfModifiedFiles() {
+ LOG.info(String.format("Found %d modified files from last build", modifiedFiles));
+ }
+
+ public Reporter getReporter() {
+ return reporter;
+ }
+
+ public EventBus getEventBus() {
+ return eventBus.get();
+ }
+
+ /**
+ * The map from package names to the package root where each package was found; this is used to
+ * set up the symlink tree.
+ */
+ public ImmutableMap<PackageIdentifier, Path> getPackageRoots() {
+ // Make a map of the package names to their root paths.
+ ImmutableMap.Builder<PackageIdentifier, Path> packageRoots = ImmutableMap.builder();
+ for (Package pkg : configurationPackages.get()) {
+ packageRoots.put(pkg.getPackageIdentifier(), pkg.getSourceRoot());
+ }
+ return packageRoots.build();
+ }
+
+ @VisibleForTesting
+ ImmutableList<Path> getPathEntries() {
+ return pkgLocator.get().getPathEntries();
+ }
+
+ protected abstract void invalidate(Predicate<SkyKey> pred);
+
+ protected static Iterable<SkyKey> getSkyKeysPotentiallyAffected(
+ Iterable<PathFragment> modifiedSourceFiles, final Path pathEntry) {
+ // TODO(bazel-team): change ModifiedFileSet to work with RootedPaths instead of PathFragments.
+ Iterable<SkyKey> fileStateSkyKeys = Iterables.transform(modifiedSourceFiles,
+ new Function<PathFragment, SkyKey>() {
+ @Override
+ public SkyKey apply(PathFragment pathFragment) {
+ Preconditions.checkState(!pathFragment.isAbsolute(),
+ "found absolute PathFragment: %s", pathFragment);
+ return FileStateValue.key(RootedPath.toRootedPath(pathEntry, pathFragment));
+ }
+ });
+ // TODO(bazel-team): Strictly speaking, we only need to invalidate directory values when a file
+ // has been created or deleted, not when it has been modified. Unfortunately we
+ // do not have that information here, although fancy filesystems could provide it with a
+ // hypothetically modified DiffAwareness interface.
+ // TODO(bazel-team): Even if we don't have that information, we could avoid invalidating
+ // directories when the state of a file does not change by statting them and comparing
+ // the new filetype (nonexistent/file/symlink/directory) with the old one.
+ Iterable<SkyKey> dirListingStateSkyKeys = Iterables.transform(
+ modifiedSourceFiles,
+ new Function<PathFragment, SkyKey>() {
+ @Override
+ public SkyKey apply(PathFragment pathFragment) {
+ Preconditions.checkState(!pathFragment.isAbsolute(),
+ "found absolute PathFragment: %s", pathFragment);
+ return DirectoryListingStateValue.key(RootedPath.toRootedPath(pathEntry,
+ pathFragment.getParentDirectory()));
+ }
+ });
+ return Iterables.concat(fileStateSkyKeys, dirListingStateSkyKeys);
+ }
+
+ protected static SkyKey createFileStateKey(RootedPath rootedPath) {
+ return FileStateValue.key(rootedPath);
+ }
+
+ protected static SkyKey createDirectoryListingStateKey(RootedPath rootedPath) {
+ return DirectoryListingStateValue.key(rootedPath);
+ }
+
+ /**
+ * Creates a FileValue pointing of type directory. No matter that the rootedPath points to a
+ * symlink.
+ *
+ * <p> Use it with caution as it would prevent invalidation when the destination file in the
+ * symlink changes.
+ */
+ protected static FileValue createFileDirValue(RootedPath rootedPath) {
+ return FileValue.value(rootedPath, FileStateValue.DIRECTORY_FILE_STATE_NODE,
+ rootedPath, FileStateValue.DIRECTORY_FILE_STATE_NODE);
+ }
+
+ /**
+ * Sets the packages that should be treated as deleted and ignored.
+ */
+ @VisibleForTesting // productionVisibility = Visibility.PRIVATE
+ public abstract void setDeletedPackages(Iterable<String> pkgs);
+
+ /**
+ * Prepares the evaluator for loading.
+ *
+ * <p>MUST be run before every incremental build.
+ */
+ @VisibleForTesting // productionVisibility = Visibility.PRIVATE
+ public void preparePackageLoading(PathPackageLocator pkgLocator, RuleVisibility defaultVisibility,
+ boolean showLoadingProgress,
+ String defaultsPackageContents, UUID commandId) {
+ Preconditions.checkNotNull(pkgLocator);
+ setActive(true);
+
+ maybeInjectPrecomputedValuesForAnalysis();
+ setCommandId(commandId);
+ setShowLoadingProgress(showLoadingProgress);
+ setDefaultVisibility(defaultVisibility);
+ setupDefaultPackage(defaultsPackageContents);
+ setPackageLocator(pkgLocator);
+
+ syscalls.set(new PerBuildSyscallCache());
+ checkPreprocessorFactory();
+ emittedEventState.clear();
+
+ // If the PackageFunction was interrupted, there may be stale entries here.
+ packageFunctionCache.clear();
+ numPackagesLoaded.set(0);
+
+ // Reset the stateful SkyframeCycleReporter, which contains cycles from last run.
+ cyclesReporter.set(createCyclesReporter());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void setPackageLocator(PathPackageLocator pkgLocator) {
+ PathPackageLocator oldLocator = this.pkgLocator.getAndSet(pkgLocator);
+ PrecomputedValue.PATH_PACKAGE_LOCATOR.set(injectable(), pkgLocator);
+
+ if (!pkgLocator.equals(oldLocator)) {
+ // The package path is read not only by SkyFunctions but also by some other code paths.
+ // We need to take additional steps to keep the corresponding data structures in sync.
+ // (Some of the additional steps are carried out by ConfiguredTargetValueInvalidationListener,
+ // and some by BuildView#buildHasIncompatiblePackageRoots and #updateSkyframe.)
+ onNewPackageLocator(oldLocator, pkgLocator);
+ }
+ }
+
+ protected abstract void onNewPackageLocator(PathPackageLocator oldLocator,
+ PathPackageLocator pkgLocator);
+
+ private void checkPreprocessorFactory() {
+ if (preprocessorFactory == null) {
+ Preprocessor.Factory newPreprocessorFactory = preprocessorFactorySupplier.getFactory(
+ packageManager);
+ pkgFactory.setPreprocessorFactory(newPreprocessorFactory);
+ preprocessorFactory = newPreprocessorFactory;
+ } else if (!preprocessorFactory.isStillValid()) {
+ Preprocessor.Factory newPreprocessorFactory = preprocessorFactorySupplier.getFactory(
+ packageManager);
+ invalidate(SkyFunctionName.functionIs(SkyFunctions.PACKAGE));
+ pkgFactory.setPreprocessorFactory(newPreprocessorFactory);
+ preprocessorFactory = newPreprocessorFactory;
+ }
+ }
+
+ /**
+ * Specifies the current {@link SkyframeBuildView} instance. This should only be set once over the
+ * lifetime of the Blaze server, except in tests.
+ */
+ public void setSkyframeBuildView(SkyframeBuildView skyframeBuildView) {
+ this.skyframeBuildView = skyframeBuildView;
+ setConfigurationSkyKey(configurationSkyKey);
+ this.artifactFactory.set(skyframeBuildView.getArtifactFactory());
+ if (skyframeBuildView.getWarningListener() != null) {
+ setErrorEventListener(skyframeBuildView.getWarningListener());
+ }
+ }
+
+ /**
+ * Sets the eventBus to use for posting events.
+ */
+ public void setEventBus(EventBus eventBus) {
+ this.eventBus.set(eventBus);
+ }
+
+ /**
+ * Sets the eventHandler to use for reporting errors.
+ */
+ public void setErrorEventListener(EventHandler eventHandler) {
+ this.errorEventListener = eventHandler;
+ }
+
+ /**
+ * Sets the path for action log buffers.
+ */
+ public void setActionOutputRoot(Path actionOutputRoot) {
+ Preconditions.checkNotNull(actionOutputRoot);
+ this.actionLogBufferPathGenerator = new ActionLogBufferPathGenerator(actionOutputRoot);
+ this.skyframeActionExecutor.setActionLogBufferPathGenerator(actionLogBufferPathGenerator);
+ }
+
+ private void setConfigurationSkyKey(SkyKey skyKey) {
+ this.configurationSkyKey = skyKey;
+ if (skyframeBuildView != null) {
+ skyframeBuildView.setConfigurationSkyKey(skyKey);
+ }
+ }
+
+ @VisibleForTesting
+ public void setConfigurationDataForTesting(BuildOptions options,
+ BlazeDirectories directories, ConfigurationFactory configurationFactory) {
+ SkyKey skyKey = ConfigurationCollectionValue.key(options, ImmutableSet.<String>of());
+ setConfigurationSkyKey(skyKey);
+ PrecomputedValue.BLAZE_DIRECTORIES.set(injectable(), directories);
+ this.configurationFactory.set(configurationFactory);
+ this.configurationFragments.set(ImmutableList.copyOf(configurationFactory.getFactories()));
+ this.configurationPackages.set(Sets.<Package>newConcurrentHashSet());
+ }
+
+ @VisibleForTesting
+ public BuildConfigurationCollection createConfigurations(
+ ConfigurationFactory configurationFactory, BuildConfigurationKey configurationKey)
+ throws InvalidConfigurationException, InterruptedException {
+ return createConfigurations(false, configurationFactory, configurationKey);
+ }
+
+ /**
+ * Asks the Skyframe evaluator to build the value for BuildConfigurationCollection and
+ * returns result. Also invalidates {@link PrecomputedValue#TEST_ENVIRONMENT_VARIABLES} and
+ * {@link PrecomputedValue#BLAZE_DIRECTORIES} if they have changed.
+ */
+ public BuildConfigurationCollection createConfigurations(boolean keepGoing,
+ ConfigurationFactory configurationFactory, BuildConfigurationKey configurationKey)
+ throws InvalidConfigurationException, InterruptedException {
+
+ this.configurationPackages.set(Sets.<Package>newConcurrentHashSet());
+ this.clientEnv.set(configurationKey.getClientEnv());
+ this.configurationFactory.set(configurationFactory);
+ this.configurationFragments.set(ImmutableList.copyOf(configurationFactory.getFactories()));
+ BuildOptions buildOptions = configurationKey.getBuildOptions();
+ Map<String, String> testEnv = BuildConfiguration.getTestEnv(
+ buildOptions.get(BuildConfiguration.Options.class).testEnvironment,
+ configurationKey.getClientEnv());
+ // TODO(bazel-team): find a way to use only BuildConfigurationKey instead of
+ // TestEnvironmentVariables and BlazeDirectories. There is a problem only with
+ // TestEnvironmentVariables because BuildConfigurationKey stores client environment variables
+ // and we don't want to rebuild everything when any variable changes.
+ PrecomputedValue.TEST_ENVIRONMENT_VARIABLES.set(injectable(), testEnv);
+ PrecomputedValue.BLAZE_DIRECTORIES.set(injectable(), configurationKey.getDirectories());
+
+ SkyKey skyKey = ConfigurationCollectionValue.key(configurationKey.getBuildOptions(),
+ configurationKey.getMultiCpu());
+ setConfigurationSkyKey(skyKey);
+ EvaluationResult<ConfigurationCollectionValue> result = buildDriver.evaluate(
+ Arrays.asList(skyKey), keepGoing, DEFAULT_THREAD_COUNT, errorEventListener);
+ if (result.hasError()) {
+ Throwable e = result.getError(skyKey).getException();
+ // Wrap loading failed exceptions
+ if (e instanceof NoSuchThingException) {
+ e = new InvalidConfigurationException(e);
+ }
+ Throwables.propagateIfInstanceOf(e, InvalidConfigurationException.class);
+ throw new IllegalStateException(
+ "Unknown error during ConfigurationCollectionValue evaluation", e);
+ }
+ Preconditions.checkState(result.values().size() == 1,
+ "Result of evaluate() must contain exactly one value %s", result);
+ ConfigurationCollectionValue configurationValue =
+ Iterables.getOnlyElement(result.values());
+ this.configurationPackages.set(
+ Sets.newConcurrentHashSet(configurationValue.getConfigurationPackages()));
+ return configurationValue.getConfigurationCollection();
+ }
+
+ private Iterable<ActionLookupValue> getActionLookupValues() {
+ // This filter keeps subclasses of ActionLookupValue.
+ return Iterables.filter(memoizingEvaluator.getDoneValues().values(), ActionLookupValue.class);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Map<SkyKey, ActionLookupValue> getActionLookupValueMap() {
+ return (Map) Maps.filterValues(memoizingEvaluator.getDoneValues(),
+ Predicates.instanceOf(ActionLookupValue.class));
+ }
+
+ /**
+ * Checks the actions in Skyframe for conflicts between their output artifacts. Delegates to
+ * {@link SkyframeActionExecutor#findAndStoreArtifactConflicts} to do the work, since any
+ * conflicts found will only be reported during execution.
+ */
+ ImmutableMap<Action, SkyframeActionExecutor.ConflictException> findArtifactConflicts()
+ throws InterruptedException {
+ if (skyframeBuildView.isSomeConfiguredTargetEvaluated()
+ || skyframeBuildView.isSomeConfiguredTargetInvalidated()) {
+ // This operation is somewhat expensive, so we only do it if the graph might have changed in
+ // some way -- either we analyzed a new target or we invalidated an old one.
+ skyframeActionExecutor.findAndStoreArtifactConflicts(getActionLookupValues());
+ skyframeBuildView.resetEvaluatedConfiguredTargetFlag();
+ // The invalidated configured targets flag will be reset later in the evaluate() call.
+ }
+ return skyframeActionExecutor.badActions();
+ }
+
+ /**
+ * Asks the Skyframe evaluator to build the given artifacts and targets, and to test the
+ * given test targets.
+ */
+ public EvaluationResult<?> buildArtifacts(
+ Executor executor,
+ Set<Artifact> artifactsToBuild,
+ Collection<ConfiguredTarget> targetsToBuild,
+ Collection<ConfiguredTarget> targetsToTest,
+ boolean exclusiveTesting,
+ boolean keepGoing,
+ boolean explain,
+ int numJobs,
+ ActionCacheChecker actionCacheChecker,
+ @Nullable EvaluationProgressReceiver executionProgressReceiver) throws InterruptedException {
+ checkActive();
+ Preconditions.checkState(actionLogBufferPathGenerator != null);
+
+ skyframeActionExecutor.prepareForExecution(executor, keepGoing, explain, actionCacheChecker);
+
+ resourceManager.resetResourceUsage();
+ try {
+ progressReceiver.executionProgressReceiver = executionProgressReceiver;
+ Iterable<SkyKey> artifactKeys = ArtifactValue.mandatoryKeys(artifactsToBuild);
+ Iterable<SkyKey> targetKeys = TargetCompletionValue.keys(targetsToBuild);
+ Iterable<SkyKey> testKeys = TestCompletionValue.keys(targetsToTest, exclusiveTesting);
+ return buildDriver.evaluate(Iterables.concat(artifactKeys, targetKeys, testKeys), keepGoing,
+ numJobs, errorEventListener);
+ } finally {
+ progressReceiver.executionProgressReceiver = null;
+ // Also releases thread locks.
+ resourceManager.resetResourceUsage();
+ skyframeActionExecutor.executionOver();
+ }
+ }
+
+ @VisibleForTesting
+ public void prepareBuildingForTestingOnly(Executor executor, boolean keepGoing, boolean explain,
+ ActionCacheChecker checker) {
+ skyframeActionExecutor.prepareForExecution(executor, keepGoing, explain, checker);
+ }
+
+ EvaluationResult<TargetPatternValue> targetPatterns(Iterable<SkyKey> patternSkyKeys,
+ boolean keepGoing, EventHandler eventHandler) throws InterruptedException {
+ checkActive();
+ return buildDriver.evaluate(patternSkyKeys, keepGoing, DEFAULT_THREAD_COUNT,
+ eventHandler);
+ }
+
+ /**
+ * Returns the {@link ConfiguredTarget}s corresponding to the given keys.
+ *
+ * <p>For use for legacy support from {@code BuildView} only.
+ *
+ * <p>If a requested configured target is in error, the corresponding value is omitted from the
+ * returned list.
+ */
+ @ThreadSafety.ThreadSafe
+ public ImmutableList<ConfiguredTarget> getConfiguredTargets(Iterable<Dependency> keys) {
+ checkActive();
+ if (skyframeBuildView == null) {
+ // If build view has not yet been initialized, no configured targets can have been created.
+ // This is most likely to happen after a failed loading phase.
+ return ImmutableList.of();
+ }
+ final List<SkyKey> skyKeys = new ArrayList<>();
+ for (Dependency key : keys) {
+ skyKeys.add(ConfiguredTargetValue.key(key.getLabel(), key.getConfiguration()));
+ for (Class<? extends ConfiguredAspectFactory> aspect : key.getAspects()) {
+ skyKeys.add(AspectValue.key(key.getLabel(), key.getConfiguration(), aspect));
+ }
+ }
+
+ EvaluationResult<SkyValue> result;
+ try {
+ result = callUninterruptibly(new Callable<EvaluationResult<SkyValue>>() {
+ @Override
+ public EvaluationResult<SkyValue> call() throws Exception {
+ synchronized (valueLookupLock) {
+ try {
+ skyframeBuildView.enableAnalysis(true);
+ return buildDriver.evaluate(skyKeys, false, DEFAULT_THREAD_COUNT,
+ errorEventListener);
+ } finally {
+ skyframeBuildView.enableAnalysis(false);
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalStateException(e); // Should never happen.
+ }
+
+ ImmutableList.Builder<ConfiguredTarget> cts = ImmutableList.builder();
+
+ DependentNodeLoop:
+ for (Dependency key : keys) {
+ SkyKey configuredTargetKey = ConfiguredTargetValue.key(
+ key.getLabel(), key.getConfiguration());
+ if (result.get(configuredTargetKey) == null) {
+ continue;
+ }
+
+ ConfiguredTarget configuredTarget =
+ ((ConfiguredTargetValue) result.get(configuredTargetKey)).getConfiguredTarget();
+ List<Aspect> aspects = new ArrayList<>();
+
+ for (Class<? extends ConfiguredAspectFactory> aspect : key.getAspects()) {
+ SkyKey aspectKey = AspectValue.key(key.getLabel(), key.getConfiguration(), aspect);
+ if (result.get(aspectKey) == null) {
+ continue DependentNodeLoop;
+ }
+
+ aspects.add(((AspectValue) result.get(aspectKey)).get());
+ }
+
+ cts.add(RuleConfiguredTarget.mergeAspects(configuredTarget, aspects));
+ }
+
+ return cts.build();
+ }
+
+ /**
+ * Returns a particular configured target.
+ *
+ * <p>Used only for testing.
+ */
+ @VisibleForTesting
+ @Nullable
+ public ConfiguredTarget getConfiguredTargetForTesting(
+ Label label, BuildConfiguration configuration) {
+ if (memoizingEvaluator.getExistingValueForTesting(
+ PrecomputedValue.WORKSPACE_STATUS_KEY.getKeyForTesting()) == null) {
+ injectWorkspaceStatusData();
+ }
+ return Iterables.getFirst(getConfiguredTargets(ImmutableList.of(
+ new Dependency(label, configuration))), null);
+ }
+
+ /**
+ * Invalidates Skyframe values corresponding to the given set of modified files under the given
+ * path entry.
+ *
+ * <p>May throw an {@link InterruptedException}, which means that no values have been invalidated.
+ */
+ @VisibleForTesting
+ public abstract void invalidateFilesUnderPathForTesting(ModifiedFileSet modifiedFileSet,
+ Path pathEntry) throws InterruptedException;
+
+ /**
+ * Invalidates SkyFrame values that may have failed for transient reasons.
+ */
+ public abstract void invalidateTransientErrors();
+
+ @VisibleForTesting
+ public TimestampGranularityMonitor getTimestampGranularityMonitorForTesting() {
+ return tsgm;
+ }
+
+ /**
+ * Configures a given set of configured targets.
+ */
+ public EvaluationResult<ConfiguredTargetValue> configureTargets(
+ List<ConfiguredTargetKey> values, boolean keepGoing) throws InterruptedException {
+ checkActive();
+
+ // Make sure to not run too many analysis threads. This can cause memory thrashing.
+ return buildDriver.evaluate(ConfiguredTargetValue.keys(values), keepGoing,
+ ResourceUsage.getAvailableProcessors(), errorEventListener);
+ }
+
+ /**
+ * Post-process the targets. Values in the EvaluationResult are known to be transitively
+ * error-free from action conflicts.
+ */
+ public EvaluationResult<PostConfiguredTargetValue> postConfigureTargets(
+ List<ConfiguredTargetKey> values, boolean keepGoing,
+ ImmutableMap<Action, SkyframeActionExecutor.ConflictException> badActions)
+ throws InterruptedException {
+ checkActive();
+ PrecomputedValue.BAD_ACTIONS.set(injectable(), badActions);
+ // Make sure to not run too many analysis threads. This can cause memory thrashing.
+ EvaluationResult<PostConfiguredTargetValue> result =
+ buildDriver.evaluate(PostConfiguredTargetValue.keys(values), keepGoing,
+ ResourceUsage.getAvailableProcessors(), errorEventListener);
+
+ // Remove all post-configured target values immediately for memory efficiency. We are OK with
+ // this mini-phase being non-incremental as the failure mode of action conflict is rare.
+ memoizingEvaluator.delete(SkyFunctionName.functionIs(SkyFunctions.POST_CONFIGURED_TARGET));
+
+ return result;
+ }
+
+ /**
+ * Returns a Skyframe-based {@link SkyframeTransitivePackageLoader} implementation.
+ */
+ @VisibleForTesting
+ public TransitivePackageLoader pkgLoader() {
+ checkActive();
+ return new SkyframeLabelVisitor(new SkyframeTransitivePackageLoader(), cyclesReporter);
+ }
+
+ class SkyframeTransitivePackageLoader {
+ /**
+ * Loads the specified {@link TransitiveTargetValue}s.
+ */
+ EvaluationResult<TransitiveTargetValue> loadTransitiveTargets(
+ Iterable<Target> targetsToVisit, Iterable<Label> labelsToVisit, boolean keepGoing)
+ throws InterruptedException {
+ List<SkyKey> valueNames = new ArrayList<>();
+ for (Target target : targetsToVisit) {
+ valueNames.add(TransitiveTargetValue.key(target.getLabel()));
+ }
+ for (Label label : labelsToVisit) {
+ valueNames.add(TransitiveTargetValue.key(label));
+ }
+
+ return buildDriver.evaluate(valueNames, keepGoing, DEFAULT_THREAD_COUNT,
+ errorEventListener);
+ }
+
+ public Set<Package> retrievePackages(Set<PackageIdentifier> packageIds) {
+ final List<SkyKey> valueNames = new ArrayList<>();
+ for (PackageIdentifier pkgId : packageIds) {
+ valueNames.add(PackageValue.key(pkgId));
+ }
+
+ try {
+ return callUninterruptibly(new Callable<Set<Package>>() {
+ @Override
+ public Set<Package> call() throws Exception {
+ EvaluationResult<PackageValue> result = buildDriver.evaluate(
+ valueNames, false, ResourceUsage.getAvailableProcessors(), errorEventListener);
+ Preconditions.checkState(!result.hasError(),
+ "unexpected errors: %s", result.errorMap());
+ Set<Package> packages = Sets.newHashSet();
+ for (PackageValue value : result.values()) {
+ packages.add(value.getPackage());
+ }
+ return packages;
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+ }
+
+ /**
+ * Returns the generating {@link Action} of the given {@link Artifact}.
+ *
+ * <p>For use for legacy support from {@code BuildView} only.
+ */
+ @ThreadSafety.ThreadSafe
+ public Action getGeneratingAction(final Artifact artifact) {
+ if (artifact.isSourceArtifact()) {
+ return null;
+ }
+
+ try {
+ return callUninterruptibly(new Callable<Action>() {
+ @Override
+ public Action call() throws InterruptedException {
+ ArtifactOwner artifactOwner = artifact.getArtifactOwner();
+ Preconditions.checkState(artifactOwner instanceof ActionLookupValue.ActionLookupKey,
+ "%s %s", artifact, artifactOwner);
+ SkyKey actionLookupKey =
+ ActionLookupValue.key((ActionLookupValue.ActionLookupKey) artifactOwner);
+
+ synchronized (valueLookupLock) {
+ // Note that this will crash (attempting to run a configured target value builder after
+ // analysis) after a failed --nokeep_going analysis in which the configured target that
+ // failed was a (transitive) dependency of the configured target that should generate
+ // this action. We don't expect callers to query generating actions in such cases.
+ EvaluationResult<ActionLookupValue> result = buildDriver.evaluate(
+ ImmutableList.of(actionLookupKey), false, ResourceUsage.getAvailableProcessors(),
+ errorEventListener);
+ return result.hasError()
+ ? null
+ : result.get(actionLookupKey).getGeneratingAction(artifact);
+ }
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalStateException("Error getting generating action: " + artifact.prettyPrint(),
+ e);
+ }
+ }
+
+ public PackageManager getPackageManager() {
+ return packageManager;
+ }
+
+ class SkyframePackageLoader {
+ /**
+ * Looks up a particular package (mostly used after the loading phase, so packages should
+ * already be present, but occasionally used pre-loading phase). Use should be discouraged,
+ * since this cannot be used inside a Skyframe evaluation, and concurrent calls are
+ * synchronized.
+ *
+ * <p>Note that this method needs to be synchronized since InMemoryMemoizingEvaluator.evaluate()
+ * method does not support concurrent calls.
+ */
+ Package getPackage(EventHandler eventHandler, PackageIdentifier pkgName)
+ throws InterruptedException, NoSuchPackageException {
+ synchronized (valueLookupLock) {
+ SkyKey key = PackageValue.key(pkgName);
+ // Any call to this method post-loading phase should either be error-free or be in a
+ // keep_going build, since otherwise the build would have failed during loading. Thus
+ // we set keepGoing=true unconditionally.
+ EvaluationResult<PackageValue> result =
+ buildDriver.evaluate(ImmutableList.of(key), /*keepGoing=*/true,
+ DEFAULT_THREAD_COUNT, eventHandler);
+ if (result.hasError()) {
+ ErrorInfo error = result.getError();
+ if (!Iterables.isEmpty(error.getCycleInfo())) {
+ reportCycles(result.getError().getCycleInfo(), key);
+ // This can only happen if a package is freshly loaded outside of the target parsing
+ // or loading phase
+ throw new BuildFileContainsErrorsException(pkgName.toString(),
+ "Cycle encountered while loading package " + pkgName);
+ }
+ Throwable e = error.getException();
+ // PackageFunction should be catching, swallowing, and rethrowing all transitive
+ // errors as NoSuchPackageExceptions.
+ Throwables.propagateIfInstanceOf(e, NoSuchPackageException.class);
+ throw new IllegalStateException("Unexpected Exception type from PackageValue for '"
+ + pkgName + "'' with root causes: " + Iterables.toString(error.getRootCauses()), e);
+ }
+ return result.get(key).getPackage();
+ }
+ }
+
+ Package getLoadedPackage(final PackageIdentifier pkgName) throws NoSuchPackageException {
+ // Note that in Skyframe there is no way to tell if the package has been loaded before or not,
+ // so this will never throw for packages that are not loaded. However, no code currently
+ // relies on having the exception thrown.
+ try {
+ return callUninterruptibly(new Callable<Package>() {
+ @Override
+ public Package call() throws Exception {
+ return getPackage(errorEventListener, pkgName);
+ }
+ });
+ } catch (NoSuchPackageException e) {
+ if (e.getPackage() != null) {
+ return e.getPackage();
+ }
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e); // Should never happen.
+ }
+ }
+
+ /**
+ * Returns whether the given package should be consider deleted and thus should be ignored.
+ */
+ public boolean isPackageDeleted(String packageName) {
+ return deletedPackages.get().contains(packageName);
+ }
+
+ /** Same as {@link PackageManager#partiallyClear}. */
+ void partiallyClear() {
+ packageFunctionCache.clear();
+ }
+ }
+
+ /**
+ * Calls the given callable uninterruptibly.
+ *
+ * <p>If the callable throws {@link InterruptedException}, calls it again, until the callable
+ * returns a result. Sets the {@code currentThread().interrupted()} bit if the callable threw
+ * {@link InterruptedException} at least once.
+ *
+ * <p>This is almost identical to {@code Uninterruptibles#getUninterruptibly}.
+ */
+ protected static final <T> T callUninterruptibly(Callable<T> callable) throws Exception {
+ boolean interrupted = false;
+ try {
+ while (true) {
+ try {
+ return callable.call();
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public MemoizingEvaluator getEvaluatorForTesting() {
+ return memoizingEvaluator;
+ }
+
+ /**
+ * Stores the set of loaded packages and, if needed, evicts ConfiguredTarget values.
+ *
+ * <p>The set represents all packages from the transitive closure of the top-level targets from
+ * the latest build.
+ */
+ @ThreadCompatible
+ public abstract void updateLoadedPackageSet(Set<PackageIdentifier> loadedPackages);
+
+ public void sync(PackageCacheOptions packageCacheOptions, Path workingDirectory,
+ String defaultsPackageContents, UUID commandId) throws InterruptedException,
+ AbruptExitException{
+
+ preparePackageLoading(
+ createPackageLocator(packageCacheOptions, directories.getWorkspace(), workingDirectory),
+ packageCacheOptions.defaultVisibility, packageCacheOptions.showLoadingProgress,
+ defaultsPackageContents, commandId);
+ setDeletedPackages(ImmutableSet.copyOf(packageCacheOptions.deletedPackages));
+
+ incrementalBuildMonitor = new SkyframeIncrementalBuildMonitor();
+ invalidateTransientErrors();
+ }
+
+ protected PathPackageLocator createPackageLocator(PackageCacheOptions packageCacheOptions,
+ Path workspace, Path workingDirectory) throws AbruptExitException{
+ return PathPackageLocator.create(
+ packageCacheOptions.packagePath, getReporter(), workspace, workingDirectory);
+ }
+
+ private CyclesReporter createCyclesReporter() {
+ return new CyclesReporter(
+ new TransitiveTargetCycleReporter(packageManager),
+ new ActionArtifactCycleReporter(packageManager),
+ new SkylarkModuleCycleReporter());
+ }
+
+ CyclesReporter getCyclesReporter() {
+ return cyclesReporter.get();
+ }
+
+ /** Convenience method with same semantics as {@link CyclesReporter#reportCycles}. */
+ public void reportCycles(Iterable<CycleInfo> cycles, SkyKey topLevelKey) {
+ getCyclesReporter().reportCycles(cycles, topLevelKey, errorEventListener);
+ }
+
+ public void setActionExecutionProgressReportingObjects(@Nullable ProgressSupplier supplier,
+ @Nullable ActionCompletedReceiver completionReceiver,
+ @Nullable ActionExecutionStatusReporter statusReporter) {
+ skyframeActionExecutor.setActionExecutionProgressReportingObjects(supplier, completionReceiver);
+ this.statusReporterRef.set(statusReporter);
+ }
+
+ /**
+ * This should be called at most once in the lifetime of the SkyframeExecutor (except for
+ * tests), and it should be called before the execution phase.
+ */
+ void setArtifactFactoryAndBinTools(ArtifactFactory artifactFactory, BinTools binTools) {
+ this.artifactFactory.set(artifactFactory);
+ this.binTools = binTools;
+ }
+
+ public void prepareExecution(boolean checkOutputFiles) throws AbruptExitException,
+ InterruptedException {
+ maybeInjectEmbeddedArtifacts();
+
+ if (checkOutputFiles) {
+ // Detect external modifications in the output tree.
+ FilesystemValueChecker fsnc = new FilesystemValueChecker(memoizingEvaluator, tsgm);
+ invalidateDirtyActions(fsnc.getDirtyActionValues(batchStatter));
+ modifiedFiles += fsnc.getNumberOfModifiedOutputFiles();
+ }
+ informAboutNumberOfModifiedFiles();
+ }
+
+ protected abstract void invalidateDirtyActions(Iterable<SkyKey> dirtyActionValues);
+
+ @VisibleForTesting void maybeInjectEmbeddedArtifacts() throws AbruptExitException {
+ // The blaze client already ensures that the contents of the embedded binaries never change,
+ // so we just need to make sure that the appropriate artifacts are present in the skyframe
+ // graph.
+
+ if (!needToInjectEmbeddedArtifacts) {
+ return;
+ }
+
+ Preconditions.checkNotNull(artifactFactory.get());
+ Preconditions.checkNotNull(binTools);
+ Map<SkyKey, SkyValue> values = Maps.newHashMap();
+ // Blaze separately handles the symlinks that target these binaries. See BinTools#setupTool.
+ for (Artifact artifact : binTools.getAllEmbeddedArtifacts(artifactFactory.get())) {
+ FileArtifactValue fileArtifactValue;
+ try {
+ fileArtifactValue = FileArtifactValue.create(artifact);
+ } catch (IOException e) {
+ // See ExtractData in blaze.cc.
+ String message = "Error: corrupt installation: file " + artifact.getPath() + " missing. "
+ + "Please remove '" + directories.getInstallBase() + "' and try again.";
+ throw new AbruptExitException(message, ExitCode.LOCAL_ENVIRONMENTAL_ERROR, e);
+ }
+ values.put(ArtifactValue.key(artifact, /*isMandatory=*/true), fileArtifactValue);
+ }
+ injectable().inject(values);
+ needToInjectEmbeddedArtifacts = false;
+ }
+
+ /**
+ * Mark dirty values for deletion if they've been dirty for longer than N versions.
+ *
+ * <p>Specifying a value N means, if the current version is V and a value was dirtied (and
+ * has remained so) in version U, and U + N &lt;= V, then the value will be marked for deletion
+ * and purged in version V+1.
+ */
+ public abstract void deleteOldNodes(long versionWindowForDirtyGc);
+
+ /**
+ * A progress received to track analysis invalidation and update progress messages.
+ */
+ protected class SkyframeProgressReceiver implements EvaluationProgressReceiver {
+ /**
+ * This flag is needed in order to avoid invalidating legacy data when we clear the
+ * analysis cache because of --discard_analysis_cache flag. For that case we want to keep
+ * the legacy data but get rid of the Skyframe data.
+ */
+ protected boolean ignoreInvalidations = false;
+ /** This receiver is only needed for execution, so it is null otherwise. */
+ @Nullable EvaluationProgressReceiver executionProgressReceiver = null;
+
+ @Override
+ public void invalidated(SkyValue value, InvalidationState state) {
+ if (ignoreInvalidations) {
+ return;
+ }
+ if (skyframeBuildView != null) {
+ skyframeBuildView.getInvalidationReceiver().invalidated(value, state);
+ }
+ }
+
+ @Override
+ public void enqueueing(SkyKey skyKey) {
+ if (ignoreInvalidations) {
+ return;
+ }
+ if (skyframeBuildView != null) {
+ skyframeBuildView.getInvalidationReceiver().enqueueing(skyKey);
+ }
+ if (executionProgressReceiver != null) {
+ executionProgressReceiver.enqueueing(skyKey);
+ }
+ }
+
+ @Override
+ public void evaluated(SkyKey skyKey, SkyValue value, EvaluationState state) {
+ if (ignoreInvalidations) {
+ return;
+ }
+ if (skyframeBuildView != null) {
+ skyframeBuildView.getInvalidationReceiver().evaluated(skyKey, value, state);
+ }
+ if (executionProgressReceiver != null) {
+ executionProgressReceiver.evaluated(skyKey, value, state);
+ }
+ }
+ }
+
+}