| // Copyright 2014 The Bazel Authors. 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.buildtool; |
| |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Stopwatch; |
| 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.ImmutableSortedSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.flogger.GoogleLogger; |
| 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.ActionGraph; |
| import com.google.devtools.build.lib.actions.ActionInputPrefetcher; |
| import com.google.devtools.build.lib.actions.ArtifactFactory; |
| import com.google.devtools.build.lib.actions.BuildFailedException; |
| import com.google.devtools.build.lib.actions.DynamicStrategyRegistry; |
| import com.google.devtools.build.lib.actions.Executor; |
| import com.google.devtools.build.lib.actions.PackageRoots; |
| import com.google.devtools.build.lib.actions.ResourceManager; |
| import com.google.devtools.build.lib.actions.ResourceSet; |
| import com.google.devtools.build.lib.actions.TestExecException; |
| import com.google.devtools.build.lib.actions.cache.ActionCache; |
| import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics; |
| import com.google.devtools.build.lib.analysis.AnalysisResult; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; |
| import com.google.devtools.build.lib.analysis.WorkspaceStatusAction; |
| import com.google.devtools.build.lib.analysis.actions.SymlinkTreeActionContext; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; |
| import com.google.devtools.build.lib.analysis.test.TestActionContext; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.ConvenienceSymlink; |
| import com.google.devtools.build.lib.buildtool.BuildRequestOptions.ConvenienceSymlinksMode; |
| import com.google.devtools.build.lib.buildtool.buildevent.ConvenienceSymlinksIdentifiedEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecRootPreparedEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecutionPhaseCompleteEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecutionProgressReceiverAvailableEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecutionStartingEvent; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.events.EventKind; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.exec.BlazeExecutor; |
| import com.google.devtools.build.lib.exec.CheckUpToDateFilter; |
| import com.google.devtools.build.lib.exec.ExecutionOptions; |
| import com.google.devtools.build.lib.exec.ExecutorBuilder; |
| import com.google.devtools.build.lib.exec.ExecutorLifecycleListener; |
| import com.google.devtools.build.lib.exec.ModuleActionContextRegistry; |
| import com.google.devtools.build.lib.exec.RemoteLocalFallbackRegistry; |
| import com.google.devtools.build.lib.exec.SpawnStrategyRegistry; |
| import com.google.devtools.build.lib.exec.SpawnStrategyResolver; |
| import com.google.devtools.build.lib.exec.SymlinkTreeStrategy; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| import com.google.devtools.build.lib.profiler.AutoProfiler; |
| import com.google.devtools.build.lib.profiler.GoogleAutoProfilerUtils; |
| import com.google.devtools.build.lib.profiler.ProfilePhase; |
| import com.google.devtools.build.lib.profiler.Profiler; |
| import com.google.devtools.build.lib.profiler.ProfilerTask; |
| import com.google.devtools.build.lib.profiler.SilentCloseable; |
| import com.google.devtools.build.lib.runtime.BlazeModule; |
| import com.google.devtools.build.lib.runtime.BlazeRuntime; |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.Execution; |
| import com.google.devtools.build.lib.server.FailureDetails.Execution.Code; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; |
| import com.google.devtools.build.lib.skyframe.Builder; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; |
| import com.google.devtools.build.lib.skyframe.PackageRootsNoSymlinkCreation; |
| import com.google.devtools.build.lib.skyframe.SkyframeExecutor; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.vfs.ModifiedFileSet; |
| import com.google.devtools.build.lib.vfs.OutputService; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.time.Duration; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| import javax.annotation.Nullable; |
| |
| /** |
| * This class manages the execution phase. The entry point is {@link #executeBuild}. |
| * |
| * <p>This is only intended for use by {@link BuildTool}. |
| * |
| * <p>This class contains an ActionCache, and refers to the Blaze Runtime's BuildView and |
| * PackageCache. |
| * |
| * @see BuildTool |
| * @see com.google.devtools.build.lib.analysis.BuildView |
| */ |
| public class ExecutionTool { |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); |
| |
| private final CommandEnvironment env; |
| private final BlazeRuntime runtime; |
| private final BuildRequest request; |
| private BlazeExecutor executor; |
| private final ActionInputPrefetcher prefetcher; |
| private final ImmutableSet<ExecutorLifecycleListener> executorLifecycleListeners; |
| private final SpawnStrategyRegistry spawnStrategyRegistry; |
| private final ModuleActionContextRegistry actionContextRegistry; |
| |
| ExecutionTool(CommandEnvironment env, BuildRequest request) |
| throws AbruptExitException, InterruptedException { |
| this.env = env; |
| this.runtime = env.getRuntime(); |
| this.request = request; |
| |
| try { |
| env.getExecRoot().createDirectoryAndParents(); |
| } catch (IOException e) { |
| throw createExitException("Execroot creation failed", Code.EXECROOT_CREATION_FAILURE, e); |
| } |
| |
| ExecutorBuilder executorBuilder = new ExecutorBuilder(); |
| ModuleActionContextRegistry.Builder actionContextRegistryBuilder = |
| ModuleActionContextRegistry.builder(); |
| SpawnStrategyRegistry.Builder spawnStrategyRegistryBuilder = SpawnStrategyRegistry.builder(); |
| actionContextRegistryBuilder.register(SpawnStrategyResolver.class, new SpawnStrategyResolver()); |
| |
| for (BlazeModule module : runtime.getBlazeModules()) { |
| try (SilentCloseable ignored = Profiler.instance().profile(module + ".executorInit")) { |
| module.executorInit(env, request, executorBuilder); |
| } |
| |
| try (SilentCloseable ignored = |
| Profiler.instance().profile(module + ".registerActionContexts")) { |
| module.registerActionContexts(actionContextRegistryBuilder, env, request); |
| } |
| |
| try (SilentCloseable ignored = |
| Profiler.instance().profile(module + ".registerSpawnStrategies")) { |
| module.registerSpawnStrategies(spawnStrategyRegistryBuilder, env); |
| } |
| } |
| actionContextRegistryBuilder.register( |
| SymlinkTreeActionContext.class, |
| new SymlinkTreeStrategy(env.getOutputService(), env.getBlazeWorkspace().getBinTools())); |
| // TODO(philwo) - the ExecutionTool should not add arbitrary dependencies on its own, instead |
| // these dependencies should be added to the ActionContextConsumer of the module that actually |
| // depends on them. |
| actionContextRegistryBuilder |
| .restrictTo(WorkspaceStatusAction.Context.class, "") |
| .restrictTo(SymlinkTreeActionContext.class, ""); |
| |
| this.prefetcher = executorBuilder.getActionInputPrefetcher(); |
| this.executorLifecycleListeners = executorBuilder.getExecutorLifecycleListeners(); |
| |
| // There are many different SpawnActions, and we want to control the action context they use |
| // independently from each other, for example, to run genrules locally and Java compile action |
| // in prod. Thus, for SpawnActions, we decide the action context to use not only based on the |
| // context class, but also the mnemonic of the action. |
| ExecutionOptions options = request.getOptions(ExecutionOptions.class); |
| // TODO(jmmv): This should live in some testing-related Blaze module, not here. |
| actionContextRegistryBuilder.restrictTo(TestActionContext.class, options.testStrategy); |
| |
| SpawnStrategyRegistry spawnStrategyRegistry = spawnStrategyRegistryBuilder.build(); |
| actionContextRegistryBuilder.register(SpawnStrategyRegistry.class, spawnStrategyRegistry); |
| actionContextRegistryBuilder.register(DynamicStrategyRegistry.class, spawnStrategyRegistry); |
| actionContextRegistryBuilder.register(RemoteLocalFallbackRegistry.class, spawnStrategyRegistry); |
| |
| this.actionContextRegistry = actionContextRegistryBuilder.build(); |
| this.spawnStrategyRegistry = spawnStrategyRegistry; |
| } |
| |
| Executor getExecutor() throws AbruptExitException { |
| if (executor == null) { |
| executor = createExecutor(); |
| for (ExecutorLifecycleListener executorLifecycleListener : executorLifecycleListeners) { |
| executorLifecycleListener.executorCreated(); |
| } |
| } |
| return executor; |
| } |
| |
| /** Creates an executor for the current set of blaze runtime, execution options, and request. */ |
| private BlazeExecutor createExecutor() { |
| return new BlazeExecutor( |
| runtime.getFileSystem(), |
| env.getExecRoot(), |
| getReporter(), |
| runtime.getClock(), |
| runtime.getBugReporter(), |
| request, |
| actionContextRegistry, |
| spawnStrategyRegistry); |
| } |
| |
| void init() throws AbruptExitException { |
| getExecutor(); |
| } |
| |
| void shutdown() { |
| for (ExecutorLifecycleListener executorLifecycleListener : executorLifecycleListeners) { |
| executorLifecycleListener.executionPhaseEnding(); |
| } |
| } |
| |
| TestActionContext getTestActionContext() { |
| return actionContextRegistry.getContext(TestActionContext.class); |
| } |
| |
| /** |
| * Sets up for execution. |
| * |
| * <p>b/199053098: This method concentrates the setup steps for execution, which were previously |
| * scattered over several classes. We need this in order to merge analysis & execution phases. |
| * TODO(b/199053098): Minimize code duplication with the main code path. TODO(b/213040766): Write |
| * tests for these setup steps. |
| */ |
| public void prepareForExecution( |
| UUID buildId, |
| Set<ConfiguredTargetKey> builtTargets, |
| Set<AspectKey> builtAspects, |
| ImmutableSortedSet<String> notSymlinkedInExecrootDirectories) |
| throws AbruptExitException, BuildFailedException, InterruptedException { |
| init(); |
| BuildRequestOptions options = request.getBuildOptions(); |
| |
| SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor(); |
| // TODO(b/199053098): Support symlink forest. |
| List<Root> pkgPathEntries = env.getPackageLocator().getPathEntries(); |
| Preconditions.checkState( |
| pkgPathEntries.size() == 1, |
| "--experimental_merged_skyframe_analysis_execution requires a single package path entry." |
| + " Found a list of size: %s", |
| pkgPathEntries.size()); |
| |
| try (SilentCloseable c = Profiler.instance().profile("preparingExecroot")) { |
| Root singleSourceRoot = Iterables.getOnlyElement(pkgPathEntries); |
| PackageRoots noSymlinkPackageRoots = new PackageRootsNoSymlinkCreation(singleSourceRoot); |
| try { |
| SymlinkForest.eagerlyPlantSymlinkForestSinglePackagePath( |
| getExecRoot(), |
| singleSourceRoot.asPath(), |
| /*prefix=*/ env.getDirectories().getProductName() + "-", |
| notSymlinkedInExecrootDirectories, |
| request.getOptions(BuildLanguageOptions.class).experimentalSiblingRepositoryLayout); |
| } catch (IOException e) { |
| throw new AbruptExitException( |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage("Failed to prepare the symlink forest") |
| .setSymlinkForest( |
| FailureDetails.SymlinkForest.newBuilder() |
| .setCode(FailureDetails.SymlinkForest.Code.CREATION_FAILED)) |
| .build()), |
| e); |
| } |
| env.getEventBus().post(new ExecRootPreparedEvent(noSymlinkPackageRoots.getPackageRootsMap())); |
| env.getSkyframeBuildView() |
| .getArtifactFactory() |
| .setPackageRoots(noSymlinkPackageRoots.getPackageRootLookup()); |
| } |
| |
| OutputService outputService = env.getOutputService(); |
| ModifiedFileSet modifiedOutputFiles = ModifiedFileSet.EVERYTHING_MODIFIED; |
| if (outputService != null) { |
| try (SilentCloseable c = Profiler.instance().profile("outputService.startBuild")) { |
| modifiedOutputFiles = |
| outputService.startBuild( |
| env.getReporter(), buildId, request.getBuildOptions().finalizeActions); |
| } |
| } else { |
| // TODO(bazel-team): this could be just another OutputService |
| try (SilentCloseable c = Profiler.instance().profile("startLocalOutputBuild")) { |
| startLocalOutputBuild(); |
| } |
| } |
| if (outputService == null || !outputService.actionFileSystemType().inMemoryFileSystem()) { |
| // Must be created after the output path is created above. |
| createActionLogDirectory(); |
| } |
| |
| ActionCache actionCache = null; |
| if (options.useActionCache) { |
| actionCache = getActionCache(); |
| actionCache.resetStatistics(); |
| } |
| SkyframeBuilder skyframeBuilder; |
| try (SilentCloseable c = Profiler.instance().profile("createBuilder")) { |
| skyframeBuilder = |
| (SkyframeBuilder) |
| createBuilder(request, actionCache, skyframeExecutor, modifiedOutputFiles); |
| } |
| try (SilentCloseable c = Profiler.instance().profile("configureActionExecutor")) { |
| skyframeExecutor.configureActionExecutor( |
| skyframeBuilder.getFileCache(), skyframeBuilder.getActionInputPrefetcher()); |
| } |
| // TODO(b/199053098): Setup progress reporting objects in SkyframeActionExecutor. |
| try (SilentCloseable c = |
| Profiler.instance().profile("prepareSkyframeActionExecutorForExecution")) { |
| skyframeExecutor.prepareSkyframeActionExecutorForExecution( |
| env.getReporter(), executor, request, skyframeBuilder.getActionCacheChecker()); |
| } |
| |
| // Note that executionProgressReceiver accesses builtTargets concurrently (after wrapping in a |
| // synchronized collection), so unsynchronized access to this variable is unsafe while it runs. |
| // TODO(leba): count test actions |
| ExecutionProgressReceiver executionProgressReceiver = |
| new ExecutionProgressReceiver( |
| Preconditions.checkNotNull(builtTargets), Preconditions.checkNotNull(builtAspects), 0); |
| skyframeExecutor |
| .getEventBus() |
| .post(new ExecutionProgressReceiverAvailableEvent(executionProgressReceiver)); |
| |
| ActionExecutionStatusReporter statusReporter = |
| ActionExecutionStatusReporter.create(env.getReporter(), skyframeExecutor.getEventBus()); |
| // TODO(leba): Add watchdog support. |
| skyframeExecutor.setActionExecutionProgressReportingObjects( |
| executionProgressReceiver, executionProgressReceiver, statusReporter); |
| skyframeExecutor.setExecutionProgressReceiver(executionProgressReceiver); |
| |
| for (ExecutorLifecycleListener executorLifecycleListener : executorLifecycleListeners) { |
| try (SilentCloseable c = |
| Profiler.instance().profile(executorLifecycleListener + ".executionPhaseStarting")) { |
| executorLifecycleListener.executionPhaseStarting(null, () -> null); |
| } |
| } |
| |
| try (SilentCloseable c = Profiler.instance().profile("configureResourceManager")) { |
| configureResourceManager(env.getLocalResourceManager(), request); |
| } |
| } |
| |
| /** |
| * Performs the execution phase (phase 3) of the build, in which the Builder is applied to the |
| * action graph to bring the targets up to date. (This function will return prior to |
| * execution-proper if --nobuild was specified.) |
| * |
| * @param buildId UUID of the build id |
| * @param analysisResult the analysis phase output |
| * @param buildResult the mutable build result |
| * @param packageRoots package roots collected from loading phase and BuildConfigurationCollection |
| * creation. May be empty if {@link |
| * SkyframeExecutor#getForcedSingleSourceRootIfNoExecrootSymlinkCreation} is false. |
| */ |
| void executeBuild( |
| UUID buildId, |
| AnalysisResult analysisResult, |
| BuildResult buildResult, |
| PackageRoots packageRoots, |
| TopLevelArtifactContext topLevelArtifactContext, |
| boolean useEventBasedBuildCompletionStatus) |
| throws BuildFailedException, InterruptedException, TestExecException, AbruptExitException { |
| Stopwatch timer = Stopwatch.createStarted(); |
| prepare(packageRoots, analysisResult.getNonSymlinkedDirectoriesUnderExecRoot()); |
| |
| ActionGraph actionGraph = analysisResult.getActionGraph(); |
| |
| OutputService outputService = env.getOutputService(); |
| ModifiedFileSet modifiedOutputFiles = ModifiedFileSet.EVERYTHING_MODIFIED; |
| if (outputService != null) { |
| try (SilentCloseable c = Profiler.instance().profile("outputService.startBuild")) { |
| modifiedOutputFiles = |
| outputService.startBuild( |
| env.getReporter(), buildId, request.getBuildOptions().finalizeActions); |
| } |
| } else { |
| // TODO(bazel-team): this could be just another OutputService |
| try (SilentCloseable c = Profiler.instance().profile("startLocalOutputBuild")) { |
| startLocalOutputBuild(); |
| } |
| } |
| |
| if (outputService == null || !outputService.actionFileSystemType().inMemoryFileSystem()) { |
| // Must be created after the output path is created above. |
| createActionLogDirectory(); |
| } |
| |
| handleConvenienceSymlinks(analysisResult); |
| |
| BuildRequestOptions options = request.getBuildOptions(); |
| ActionCache actionCache = null; |
| if (options.useActionCache) { |
| actionCache = getActionCache(); |
| actionCache.resetStatistics(); |
| } |
| SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor(); |
| Builder builder; |
| try (SilentCloseable c = Profiler.instance().profile("createBuilder")) { |
| builder = createBuilder(request, actionCache, skyframeExecutor, modifiedOutputFiles); |
| } |
| |
| // |
| // Execution proper. All statements below are logically nested in |
| // begin/end pairs. No early returns or exceptions please! |
| // |
| |
| Collection<ConfiguredTarget> configuredTargets = buildResult.getActualTargets(); |
| try (SilentCloseable c = Profiler.instance().profile("ExecutionStartingEvent")) { |
| env.getEventBus().post(new ExecutionStartingEvent(configuredTargets)); |
| } |
| |
| getReporter().handle(Event.progress("Building...")); |
| |
| // Conditionally record dependency-checker log: |
| ExplanationHandler explanationHandler = |
| installExplanationHandler( |
| request.getBuildOptions().explanationPath, request.getOptionsDescription()); |
| |
| // TODO(b/227138583): Remove these. |
| Set<ConfiguredTargetKey> builtTargets = new HashSet<>(); |
| Set<AspectKey> builtAspects = new HashSet<>(); |
| |
| if (request.isRunningInEmacs()) { |
| // The syntax of this message is tightly constrained by lisp/progmodes/compile.el in emacs |
| request |
| .getOutErr() |
| .printErrLn(runtime.getProductName() + ": Entering directory `" + getExecRoot() + "/'"); |
| } |
| |
| Throwable catastrophe = null; |
| boolean buildCompleted = false; |
| try { |
| if (request.getViewOptions().discardAnalysisCache |
| || !skyframeExecutor.tracksStateForIncrementality()) { |
| // Free memory by removing cache entries that aren't going to be needed. |
| try (SilentCloseable c = Profiler.instance().profile("clearAnalysisCache")) { |
| env.getSkyframeBuildView() |
| .clearAnalysisCache( |
| analysisResult.getTargetsToBuild(), analysisResult.getAspectsMap().keySet()); |
| } |
| } |
| |
| for (ExecutorLifecycleListener executorLifecycleListener : executorLifecycleListeners) { |
| try (SilentCloseable c = |
| Profiler.instance().profile(executorLifecycleListener + ".executionPhaseStarting")) { |
| executorLifecycleListener.executionPhaseStarting( |
| actionGraph, |
| // If this supplier is ever consumed by more than one ActionContextProvider, it can be |
| // pulled out of the loop and made a memoizing supplier. |
| () -> TopLevelArtifactHelper.findAllTopLevelArtifacts(analysisResult)); |
| } |
| } |
| skyframeExecutor.drainChangedFiles(); |
| |
| try (SilentCloseable c = Profiler.instance().profile("configureResourceManager")) { |
| configureResourceManager(env.getLocalResourceManager(), request); |
| } |
| |
| Profiler.instance().markPhase(ProfilePhase.EXECUTE); |
| boolean shouldTrustRemoteArtifacts = |
| env.getOutputService() != null && env.getOutputService().shouldTrustRemoteArtifacts(); |
| builder.buildArtifacts( |
| env.getReporter(), |
| analysisResult.getArtifactsToBuild(), |
| analysisResult.getParallelTests(), |
| analysisResult.getExclusiveTests(), |
| analysisResult.getTargetsToBuild(), |
| analysisResult.getTargetsToSkip(), |
| analysisResult.getAspectsMap().keySet(), |
| executor, |
| builtTargets, |
| builtAspects, |
| request, |
| env.getBlazeWorkspace().getLastExecutionTimeRange(), |
| topLevelArtifactContext, |
| shouldTrustRemoteArtifacts); |
| buildCompleted = true; |
| } catch (BuildFailedException | TestExecException e) { |
| buildCompleted = true; |
| throw e; |
| } catch (Error | RuntimeException e) { |
| catastrophe = e; |
| } finally { |
| // These may flush logs, which may help if there is a catastrophic failure. |
| for (ExecutorLifecycleListener executorLifecycleListener : executorLifecycleListeners) { |
| executorLifecycleListener.executionPhaseEnding(); |
| } |
| |
| // Handlers process these events and others (e.g. CommandCompleteEvent), even in the event of |
| // a catastrophic failure. Posting these is consistent with other behavior. |
| env.getEventBus().post(skyframeExecutor.createExecutionFinishedEvent()); |
| |
| env.getEventBus() |
| .post(new ExecutionPhaseCompleteEvent(timer.stop().elapsed(TimeUnit.MILLISECONDS))); |
| |
| if (catastrophe != null) { |
| Throwables.throwIfUnchecked(catastrophe); |
| } |
| // NOTE: No finalization activities below will run in the event of a catastrophic error! |
| |
| env.recordLastExecutionTime(); |
| |
| if (request.isRunningInEmacs()) { |
| request |
| .getOutErr() |
| .printErrLn(runtime.getProductName() + ": Leaving directory `" + getExecRoot() + "/'"); |
| } |
| if (buildCompleted) { |
| getReporter().handle(Event.progress("Building complete.")); |
| } |
| |
| if (buildCompleted) { |
| saveActionCache(actionCache); |
| } |
| |
| if (useEventBasedBuildCompletionStatus) { |
| builtTargets = env.getBuildResultListener().getBuiltTargets(); |
| builtAspects = env.getBuildResultListener().getBuiltAspects(); |
| } |
| |
| try (SilentCloseable c = Profiler.instance().profile("Show results")) { |
| buildResult.setSuccessfulTargets( |
| determineSuccessfulTargets(configuredTargets, builtTargets)); |
| buildResult.setSuccessfulAspects( |
| determineSuccessfulAspects(analysisResult.getAspectsMap().keySet(), builtAspects)); |
| buildResult.setSkippedTargets(analysisResult.getTargetsToSkip()); |
| BuildResultPrinter buildResultPrinter = new BuildResultPrinter(env); |
| buildResultPrinter.showBuildResult( |
| request, |
| buildResult, |
| configuredTargets, |
| analysisResult.getTargetsToSkip(), |
| analysisResult.getAspectsMap()); |
| } |
| |
| try (SilentCloseable c = Profiler.instance().profile("Show artifacts")) { |
| if (request.getBuildOptions().showArtifacts) { |
| BuildResultPrinter buildResultPrinter = new BuildResultPrinter(env); |
| buildResultPrinter.showArtifacts( |
| request, configuredTargets, analysisResult.getAspectsMap().values()); |
| } |
| } |
| |
| if (explanationHandler != null) { |
| uninstallExplanationHandler(explanationHandler); |
| try { |
| explanationHandler.close(); |
| } catch (IOException _ignored) { |
| // Ignored |
| } |
| } |
| // Finalize output service last, so that if we do throw an exception, we know all the other |
| // code has already run. |
| if (env.getOutputService() != null) { |
| boolean isBuildSuccessful = |
| buildResult.getSuccessfulTargets().size() == configuredTargets.size(); |
| env.getOutputService().finalizeBuild(isBuildSuccessful); |
| } |
| } |
| } |
| |
| private void prepare( |
| PackageRoots packageRoots, ImmutableSortedSet<String> nonSymlinkedDirectoriesUnderExecRoot) |
| throws AbruptExitException, InterruptedException { |
| Optional<ImmutableMap<PackageIdentifier, Root>> packageRootMap = |
| packageRoots.getPackageRootsMap(); |
| if (packageRootMap.isPresent()) { |
| // Prepare for build. |
| Profiler.instance().markPhase(ProfilePhase.PREPARE); |
| |
| // Plant the symlink forest. |
| try (SilentCloseable c = Profiler.instance().profile("plantSymlinkForest")) { |
| SymlinkForest symlinkForest = |
| new SymlinkForest( |
| packageRootMap.get(), |
| getExecRoot(), |
| runtime.getProductName(), |
| nonSymlinkedDirectoriesUnderExecRoot, |
| request.getOptions(BuildLanguageOptions.class).experimentalSiblingRepositoryLayout); |
| symlinkForest.plantSymlinkForest(); |
| } catch (IOException e) { |
| throw new AbruptExitException( |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage("Source forest creation failed") |
| .setSymlinkForest( |
| FailureDetails.SymlinkForest.newBuilder() |
| .setCode(FailureDetails.SymlinkForest.Code.CREATION_FAILED)) |
| .build()), |
| e); |
| } |
| } |
| env.getEventBus().post(new ExecRootPreparedEvent(packageRootMap)); |
| } |
| |
| private static void logDeleteTreeFailure( |
| Path directory, String description, IOException deleteTreeFailure) { |
| logger.atWarning().withCause(deleteTreeFailure).log( |
| "Failed to delete %s '%s'", description, directory); |
| if (directory.exists()) { |
| try { |
| Collection<Path> entries = directory.getDirectoryEntries(); |
| StringBuilder directoryDetails = |
| new StringBuilder("'") |
| .append(directory) |
| .append("' contains ") |
| .append(entries.size()) |
| .append(" entries:"); |
| for (Path entry : entries) { |
| directoryDetails.append(" '").append(entry.getBaseName()).append("'"); |
| } |
| logger.atWarning().log("%s", directoryDetails); |
| } catch (IOException e) { |
| logger.atWarning().withCause(e).log("'%s' exists but could not be read", directory); |
| } |
| } else { |
| logger.atWarning().log("'%s' does not exist", directory); |
| } |
| } |
| |
| private void createActionLogDirectory() throws AbruptExitException { |
| Path directory = env.getActionTempsDirectory(); |
| if (directory.exists()) { |
| try { |
| directory.deleteTree(); |
| } catch (IOException e) { |
| // TODO(b/140567980): Remove when we determine the cause of occasional deleteTree() failure. |
| logDeleteTreeFailure(directory, "action output directory", e); |
| throw createExitException( |
| "Couldn't delete action output directory", |
| Code.TEMP_ACTION_OUTPUT_DIRECTORY_DELETION_FAILURE, |
| e); |
| } |
| } |
| |
| try { |
| directory.createDirectoryAndParents(); |
| } catch (IOException e) { |
| throw createExitException( |
| "Couldn't create action output directory", |
| Code.TEMP_ACTION_OUTPUT_DIRECTORY_CREATION_FAILURE, |
| e); |
| } |
| |
| try { |
| env.getPersistentActionOutsDirectory().createDirectoryAndParents(); |
| } catch (IOException e) { |
| throw createExitException( |
| "Couldn't create persistent action output directory", |
| Code.PERSISTENT_ACTION_OUTPUT_DIRECTORY_CREATION_FAILURE, |
| e); |
| } |
| } |
| |
| /** |
| * Obtains the {@link BuildConfigurationValue} for a given {@link BuildOptions} for the purpose of |
| * symlink creation. |
| * |
| * <p>In the event of a {@link InvalidConfigurationException}, a warning is emitted and null is |
| * returned. |
| */ |
| @Nullable |
| private static BuildConfigurationValue getConfiguration( |
| SkyframeExecutor executor, Reporter reporter, BuildOptions options) { |
| try { |
| return executor.getConfiguration(reporter, options, /*keepGoing=*/ false); |
| } catch (InvalidConfigurationException e) { |
| reporter.handle( |
| Event.warn( |
| "Couldn't get configuration for convenience symlink creation: " + e.getMessage())); |
| return null; |
| } |
| } |
| |
| /** |
| * Handles what action to perform on the convenience symlinks. If the the mode is {@link |
| * ConvenienceSymlinksMode#IGNORE}, then skip any creating or cleaning of convenience symlinks. |
| * Otherwise, manage the convenience symlinks and then post a {@link |
| * ConvenienceSymlinksIdentifiedEvent} build event. |
| */ |
| private void handleConvenienceSymlinks(AnalysisResult analysisResult) { |
| ImmutableList<ConvenienceSymlink> convenienceSymlinks = ImmutableList.of(); |
| if (request.getBuildOptions().experimentalConvenienceSymlinks |
| != ConvenienceSymlinksMode.IGNORE) { |
| convenienceSymlinks = createConvenienceSymlinks(request.getBuildOptions(), analysisResult); |
| } |
| if (request.getBuildOptions().experimentalConvenienceSymlinksBepEvent) { |
| env.getEventBus().post(new ConvenienceSymlinksIdentifiedEvent(convenienceSymlinks)); |
| } |
| } |
| |
| /** |
| * Creates convenience symlinks based on the target configurations. |
| * |
| * <p>Exactly what target configurations we consider depends on the value of {@code |
| * --use_top_level_targets_for_symlinks}. If this flag is false, we use the top-level target |
| * configuration as represented by the command line prior to processing any target. If the flag is |
| * true, we instead use the configurations OF the top-level targets -- meaning that we account for |
| * the effects of any rule transitions these targets may have. |
| * |
| * <p>For each type of convenience symlink, if all the considered configurations agree on what |
| * path the symlink should point to, it gets created; otherwise, the symlink is not created, and |
| * in fact gets removed if it was already present from a previous invocation. |
| */ |
| private ImmutableList<ConvenienceSymlink> createConvenienceSymlinks( |
| BuildRequestOptions buildRequestOptions, AnalysisResult analysisResult) { |
| SkyframeExecutor executor = env.getSkyframeExecutor(); |
| Reporter reporter = env.getReporter(); |
| |
| // Gather configurations to consider. |
| Set<BuildConfigurationValue> targetConfigurations = |
| buildRequestOptions.useTopLevelTargetsForSymlinks() |
| ? analysisResult.getTargetsToBuild().stream() |
| .map(ConfiguredTarget::getConfigurationKey) |
| .filter(Objects::nonNull) |
| .distinct() |
| .map((key) -> executor.getConfiguration(reporter, key)) |
| .collect(toImmutableSet()) |
| : ImmutableSet.copyOf( |
| analysisResult.getConfigurationCollection().getTargetConfigurations()); |
| |
| String productName = runtime.getProductName(); |
| try (SilentCloseable c = |
| Profiler.instance().profile("OutputDirectoryLinksUtils.createOutputDirectoryLinks")) { |
| return OutputDirectoryLinksUtils.createOutputDirectoryLinks( |
| runtime.getRuleClassProvider().getSymlinkDefinitions(), |
| buildRequestOptions, |
| env.getWorkspaceName(), |
| env.getWorkspace(), |
| env.getDirectories(), |
| getReporter(), |
| targetConfigurations, |
| options -> getConfiguration(executor, reporter, options), |
| productName); |
| } |
| } |
| |
| /** Prepare for a local output build. */ |
| private void startLocalOutputBuild() throws AbruptExitException { |
| try (SilentCloseable c = Profiler.instance().profile("Starting local output build")) { |
| Path outputPath = env.getDirectories().getOutputPath(env.getWorkspaceName()); |
| Path localOutputPath = env.getDirectories().getLocalOutputPath(); |
| |
| if (outputPath.isSymbolicLink()) { |
| try { |
| // Remove the existing symlink first. |
| outputPath.delete(); |
| if (localOutputPath.exists()) { |
| // Pre-existing local output directory. Move to outputPath. |
| localOutputPath.renameTo(outputPath); |
| } |
| } catch (IOException e) { |
| throw createExitException( |
| "Couldn't handle local output directory symlinks", |
| Code.LOCAL_OUTPUT_DIRECTORY_SYMLINK_FAILURE, |
| e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * If a path is supplied, creates and installs an ExplanationHandler. Returns an instance on |
| * success. Reports an error and returns null otherwise. |
| */ |
| private ExplanationHandler installExplanationHandler( |
| PathFragment explanationPath, String allOptions) { |
| if (explanationPath == null) { |
| return null; |
| } |
| ExplanationHandler handler; |
| try { |
| handler = |
| new ExplanationHandler( |
| getWorkspace().getRelative(explanationPath).getOutputStream(), allOptions); |
| } catch (IOException e) { |
| getReporter() |
| .handle( |
| Event.warn( |
| String.format( |
| "Cannot write explanation of rebuilds to file '%s': %s", |
| explanationPath, e.getMessage()))); |
| return null; |
| } |
| getReporter() |
| .handle(Event.info("Writing explanation of rebuilds to '" + explanationPath + "'")); |
| getReporter().addHandler(handler); |
| return handler; |
| } |
| |
| /** Uninstalls the specified ExplanationHandler (if any) and closes the log file. */ |
| private void uninstallExplanationHandler(ExplanationHandler handler) { |
| if (handler != null) { |
| getReporter().removeHandler(handler); |
| handler.log.close(); |
| } |
| } |
| |
| /** |
| * An ErrorEventListener implementation that records DEPCHECKER events into a log file, iff the |
| * --explain flag is specified during a build. |
| */ |
| private static class ExplanationHandler implements EventHandler, AutoCloseable { |
| private final PrintWriter log; |
| |
| private ExplanationHandler(OutputStream log, String optionsDescription) { |
| this.log = new PrintWriter(new OutputStreamWriter(log, StandardCharsets.UTF_8)); |
| this.log.println("Build options: " + optionsDescription); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| this.log.close(); |
| } |
| |
| @Override |
| public void handle(Event event) { |
| if (event.getKind() == EventKind.DEPCHECKER) { |
| log.println(event.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Computes the result of the build. Sets the list of successful (up-to-date) targets in the |
| * request object. |
| * |
| * @param configuredTargets The configured targets whose artifacts are to be built. |
| */ |
| static ImmutableSet<ConfiguredTarget> determineSuccessfulTargets( |
| Collection<ConfiguredTarget> configuredTargets, Set<ConfiguredTargetKey> builtTargets) { |
| // Maintain the ordering by copying builtTargets into an ImmutableSet.Builder in the same |
| // iteration order as configuredTargets. |
| ImmutableSet.Builder<ConfiguredTarget> successfulTargets = ImmutableSet.builder(); |
| for (ConfiguredTarget target : configuredTargets) { |
| if (builtTargets.contains( |
| ConfiguredTargetKey.builder() |
| .setConfiguredTarget(target) |
| .setConfigurationKey(target.getConfigurationKey()) |
| .build())) { |
| successfulTargets.add(target); |
| } |
| } |
| return successfulTargets.build(); |
| } |
| |
| static ImmutableSet<AspectKey> determineSuccessfulAspects( |
| ImmutableSet<AspectKey> aspects, Set<AspectKey> builtAspects) { |
| // Maintain the ordering. |
| return aspects.stream().filter(builtAspects::contains).collect(ImmutableSet.toImmutableSet()); |
| } |
| |
| /** Get action cache if present or reload it from the on-disk cache. */ |
| private ActionCache getActionCache() throws AbruptExitException { |
| try { |
| return env.getBlazeWorkspace().getOrLoadPersistentActionCache(getReporter()); |
| } catch (IOException e) { |
| String message = |
| String.format( |
| "Couldn't create action cache: %s. If error persists, use 'bazel clean'.", |
| e.getMessage()); |
| throw new AbruptExitException( |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setActionCache( |
| FailureDetails.ActionCache.newBuilder() |
| .setCode(FailureDetails.ActionCache.Code.INITIALIZATION_FAILURE)) |
| .build()), |
| e); |
| } |
| } |
| |
| private Builder createBuilder( |
| BuildRequest request, |
| @Nullable ActionCache actionCache, |
| SkyframeExecutor skyframeExecutor, |
| ModifiedFileSet modifiedOutputFiles) { |
| BuildRequestOptions options = request.getBuildOptions(); |
| |
| skyframeExecutor.setActionOutputRoot( |
| env.getActionTempsDirectory(), env.getPersistentActionOutsDirectory()); |
| |
| Predicate<Action> executionFilter = |
| CheckUpToDateFilter.fromOptions(request.getOptions(ExecutionOptions.class)); |
| ArtifactFactory artifactFactory = env.getSkyframeBuildView().getArtifactFactory(); |
| return new SkyframeBuilder( |
| skyframeExecutor, |
| env.getLocalResourceManager(), |
| new ActionCacheChecker( |
| actionCache, |
| artifactFactory, |
| skyframeExecutor.getActionKeyContext(), |
| executionFilter, |
| ActionCacheChecker.CacheConfig.builder() |
| .setEnabled(options.useActionCache) |
| .setVerboseExplanations(options.verboseExplanations) |
| .setStoreOutputMetadata(options.actionCacheStoreOutputMetadata) |
| .build()), |
| request.getPackageOptions().checkOutputFiles |
| // Do not skip invalidation in case the output tree is empty -- this can happen |
| // after it's cleaned or corrupted. |
| || modifiedOutputFiles.treatEverythingAsDeleted() |
| ? modifiedOutputFiles |
| : ModifiedFileSet.NOTHING_MODIFIED, |
| env.getFileCache(), |
| prefetcher, |
| env.getRuntime().getBugReporter()); |
| } |
| |
| @VisibleForTesting |
| public static void configureResourceManager(ResourceManager resourceMgr, BuildRequest request) { |
| ExecutionOptions options = request.getOptions(ExecutionOptions.class); |
| resourceMgr.setPrioritizeLocalActions(options.prioritizeLocalActions); |
| resourceMgr.setUseLocalMemoryEstimate(options.localMemoryEstimate); |
| resourceMgr.setAvailableResources( |
| ResourceSet.create( |
| options.localRamResources, |
| options.localCpuResources, |
| options.usingLocalTestJobs() ? options.localTestJobs : Integer.MAX_VALUE)); |
| } |
| |
| /** |
| * Writes the action cache files to disk, reporting any errors that occurred during writing and |
| * capturing statistics. |
| */ |
| private void saveActionCache(@Nullable ActionCache actionCache) { |
| ActionCacheStatistics.Builder builder = ActionCacheStatistics.newBuilder(); |
| |
| if (actionCache != null) { |
| actionCache.mergeIntoActionCacheStatistics(builder); |
| |
| AutoProfiler p = |
| GoogleAutoProfilerUtils.profiledAndLogged("Saving action cache", ProfilerTask.INFO); |
| try { |
| builder.setSizeInBytes(actionCache.save()); |
| } catch (IOException e) { |
| builder.setSizeInBytes(0); |
| getReporter().handle(Event.error("I/O error while writing action log: " + e.getMessage())); |
| } finally { |
| builder.setSaveTimeInMs(Duration.ofNanos(p.completeAndGetElapsedTimeNanos()).toMillis()); |
| } |
| } |
| |
| env.getEventBus().post(builder.build()); |
| } |
| |
| private Reporter getReporter() { |
| return env.getReporter(); |
| } |
| |
| private Path getWorkspace() { |
| return env.getWorkspace(); |
| } |
| |
| private Path getExecRoot() { |
| return env.getExecRoot(); |
| } |
| |
| private static AbruptExitException createExitException( |
| String message, Code detailedCode, IOException e) { |
| return new AbruptExitException( |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setExecution(Execution.newBuilder().setCode(detailedCode)) |
| .build()), |
| e); |
| } |
| } |