| // Copyright 2018 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.includescanning; | |
| import static com.google.common.collect.ImmutableMap.toImmutableMap; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.flogger.GoogleLogger; | |
| import com.google.common.util.concurrent.ListenableFuture; | |
| import com.google.common.util.concurrent.MoreExecutors; | |
| import com.google.common.util.concurrent.ThreadFactoryBuilder; | |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; | |
| import com.google.devtools.build.lib.actions.ActionExecutionMetadata; | |
| import com.google.devtools.build.lib.actions.ActionGraph; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.ArtifactFactory; | |
| import com.google.devtools.build.lib.actions.ArtifactResolver; | |
| import com.google.devtools.build.lib.actions.ExecException; | |
| import com.google.devtools.build.lib.analysis.BlazeDirectories; | |
| import com.google.devtools.build.lib.buildtool.BuildRequest; | |
| import com.google.devtools.build.lib.buildtool.BuildRequestOptions; | |
| import com.google.devtools.build.lib.concurrent.ExecutorUtil; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile; | |
| import com.google.devtools.build.lib.events.Event; | |
| 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.includescanning.IncludeParser.Inclusion; | |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; | |
| import com.google.devtools.build.lib.profiler.Profiler; | |
| import com.google.devtools.build.lib.rules.cpp.CppIncludeExtractionContext; | |
| import com.google.devtools.build.lib.rules.cpp.CppIncludeScanningContext; | |
| import com.google.devtools.build.lib.rules.cpp.CppOptions; | |
| import com.google.devtools.build.lib.rules.cpp.IncludeScanner.IncludeScanningHeaderData; | |
| import com.google.devtools.build.lib.rules.cpp.SwigIncludeScanningContext; | |
| import com.google.devtools.build.lib.runtime.BlazeModule; | |
| import com.google.devtools.build.lib.runtime.BlazeRuntime; | |
| import com.google.devtools.build.lib.runtime.Command; | |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; | |
| import com.google.devtools.build.lib.runtime.WorkspaceBuilder; | |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; | |
| import com.google.devtools.build.lib.server.FailureDetails.IncludeScanning; | |
| import com.google.devtools.build.lib.skyframe.EphemeralCheckIfOutputConsumed; | |
| import com.google.devtools.build.lib.skyframe.MutableSupplier; | |
| import com.google.devtools.build.lib.util.AbruptExitException; | |
| import com.google.devtools.build.lib.util.DetailedExitCode; | |
| import com.google.devtools.build.lib.vfs.IORuntimeException; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.skyframe.SkyFunction; | |
| import com.google.devtools.build.skyframe.SkyFunctionName; | |
| import com.google.devtools.common.options.OptionsBase; | |
| import java.io.IOException; | |
| import java.util.Collection; | |
| import java.util.Set; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ConcurrentMap; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.SynchronousQueue; | |
| import java.util.concurrent.ThreadPoolExecutor; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.function.Supplier; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Module that provides implementations of {@link CppIncludeExtractionContext}, | |
| * {@link CppIncludeScanningContext}, and {@link SwigIncludeScanningContext}. | |
| */ | |
| public class IncludeScanningModule extends BlazeModule { | |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | |
| private final MutableSupplier<SpawnIncludeScanner> spawnIncludeScannerSupplier = | |
| new MutableSupplier<>(); | |
| private final MutableSupplier<ArtifactFactory> artifactFactory = new MutableSupplier<>(); | |
| private IncludeScannerLifecycleManager lifecycleManager; | |
| @Nullable | |
| protected PathFragment getIncludeHintsFilename() { | |
| return null; | |
| } | |
| @Override | |
| @ThreadHostile | |
| public void registerActionContexts( | |
| ModuleActionContextRegistry.Builder registryBuilder, | |
| CommandEnvironment env, | |
| BuildRequest buildRequest) { | |
| registryBuilder | |
| .register(CppIncludeExtractionContext.class, new CppIncludeExtractionContextImpl(env)) | |
| .register(SwigIncludeScanningContext.class, lifecycleManager.getSwigActionContext()) | |
| .register(CppIncludeScanningContext.class, lifecycleManager.getCppActionContext()); | |
| registryBuilder | |
| .restrictTo(CppIncludeExtractionContext.class, "") | |
| .restrictTo(SwigIncludeScanningContext.class, "") | |
| .restrictTo(CppIncludeScanningContext.class, ""); | |
| } | |
| @Override | |
| @ThreadHostile | |
| public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) { | |
| lifecycleManager = | |
| new IncludeScannerLifecycleManager( | |
| env, request, spawnIncludeScannerSupplier, getIncludeHintsFilename() != null); | |
| builder.addExecutorLifecycleListener(lifecycleManager); | |
| } | |
| @Override | |
| public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) { | |
| return "build".equals(command.name()) | |
| ? ImmutableList.of(IncludeScanningOptions.class) | |
| : ImmutableList.of(); | |
| } | |
| @Override | |
| public void beforeCommand(CommandEnvironment env) { | |
| CppOptions cppOptions = env.getOptions().getOptions(CppOptions.class); | |
| if (cppOptions != null && cppOptions.experimentalIncludeScanning) { | |
| env.getReporter() | |
| .handle(Event.warn("Include scanning enabled. This feature is unsupported.")); | |
| } | |
| artifactFactory.set(env.getSkyframeBuildView().getArtifactFactory()); | |
| } | |
| @Override | |
| public void afterCommand() { | |
| spawnIncludeScannerSupplier.set(null); | |
| artifactFactory.set(null); | |
| lifecycleManager = null; | |
| } | |
| @Override | |
| public void workspaceInit( | |
| BlazeRuntime runtime, BlazeDirectories directories, WorkspaceBuilder builder) { | |
| builder.addSkyFunctions(getSkyFunctions(getIncludeHintsFilename())); | |
| } | |
| @VisibleForTesting | |
| public static ImmutableMap<SkyFunctionName, SkyFunction> getSkyFunctions( | |
| @Nullable PathFragment includeHintsFile) { | |
| ImmutableMap.Builder<SkyFunctionName, SkyFunction> skyFunctions = ImmutableMap.builder(); | |
| if (includeHintsFile != null) { | |
| skyFunctions.put( | |
| IncludeScanningSkyFunctions.INCLUDE_HINTS, new IncludeHintsFunction(includeHintsFile)); | |
| } | |
| return skyFunctions.buildOrThrow(); | |
| } | |
| /** | |
| * Implementation of {@link CppIncludeExtractionContext}. | |
| */ | |
| public static final class CppIncludeExtractionContextImpl implements CppIncludeExtractionContext { | |
| private final CommandEnvironment env; | |
| CppIncludeExtractionContextImpl(CommandEnvironment env) { | |
| this.env = env; | |
| } | |
| @Override | |
| public ArtifactResolver getArtifactResolver() { | |
| return env.getSkyframeBuildView().getArtifactFactory(); | |
| } | |
| } | |
| /** | |
| * SwigIncludeScanningContextImpl implements SwigIncludeScanningContext. | |
| */ | |
| public static final class SwigIncludeScanningContextImpl implements SwigIncludeScanningContext { | |
| private final CommandEnvironment env; | |
| private final Supplier<SpawnIncludeScanner> spawnScannerSupplier; | |
| private final Supplier<ExecutorService> includePool; | |
| private final ConcurrentMap<Artifact, ListenableFuture<Collection<Inclusion>>> cache = | |
| new ConcurrentHashMap<>(); | |
| SwigIncludeScanningContextImpl( | |
| CommandEnvironment env, | |
| Supplier<SpawnIncludeScanner> spawnScannerSupplier, | |
| Supplier<ExecutorService> includePool) { | |
| this.env = env; | |
| this.spawnScannerSupplier = spawnScannerSupplier; | |
| this.includePool = includePool; | |
| } | |
| @Override | |
| public void extractSwigIncludes( | |
| Set<Artifact> includes, | |
| ActionExecutionMetadata actionExecutionMetadata, | |
| ActionExecutionContext actionExecContext, | |
| Artifact source, | |
| ImmutableSet<Artifact> legalOutputPaths, | |
| ImmutableList<PathFragment> swigIncludePaths, | |
| Artifact grepIncludes) | |
| throws IOException, ExecException, InterruptedException { | |
| SwigIncludeScanner scanner = | |
| new SwigIncludeScanner( | |
| includePool.get(), | |
| spawnScannerSupplier.get(), | |
| cache, | |
| swigIncludePaths, | |
| env.getDirectories(), | |
| env.getSkyframeBuildView().getArtifactFactory(), | |
| env.getExecRoot()); | |
| // For Swig include scanning, just point to the output file in the map. | |
| ImmutableMap<PathFragment, Artifact> pathToDeclaredHeader = | |
| legalOutputPaths.stream() | |
| .collect( | |
| toImmutableMap( | |
| Artifact::getExecPath, | |
| artifact -> artifact, | |
| // Headers may be generated by shared actions. If both shared actions' outputs | |
| // are present, just use the first (b/304564144). | |
| (a, b) -> a)); | |
| try { | |
| scanner.processAsync( | |
| source, | |
| ImmutableList.of(source), | |
| new IncludeScanningHeaderData.Builder( | |
| pathToDeclaredHeader, /* modularHeaders= */ ImmutableSet.of()) | |
| .build(), | |
| ImmutableList.of(), | |
| includes, | |
| actionExecutionMetadata, | |
| actionExecContext, | |
| grepIncludes); | |
| } catch (IORuntimeException e) { | |
| throw e.getCauseIOException(); | |
| } catch (NoSuchPackageException e) { | |
| throw new IllegalStateException("Swig has no hints! For " + source, e); | |
| } | |
| } | |
| } | |
| /** | |
| * Lifecycle manager for the include scanner. Maintains an {@linkplain IncludeScannerSupplier | |
| * supplier} which can be used to access the (potentially shared) scanners and exposes {@linkplain | |
| * #getSwigActionContext() action} {@linkplain #getCppActionContext() contexts} based on them. | |
| */ | |
| private static final class IncludeScannerLifecycleManager implements ExecutorLifecycleListener { | |
| private final CommandEnvironment env; | |
| private final IncludeScanningOptions options; | |
| private final boolean useIncludeHints; | |
| private final Supplier<SpawnIncludeScanner> spawnScannerSupplier; | |
| private IncludeScannerSupplier includeScannerSupplier; | |
| private ExecutorService includePool; | |
| IncludeScannerLifecycleManager( | |
| CommandEnvironment env, | |
| BuildRequest buildRequest, | |
| MutableSupplier<SpawnIncludeScanner> spawnScannerSupplier, | |
| boolean useIncludeHints) { | |
| this.env = env; | |
| this.options = buildRequest.getOptions(IncludeScanningOptions.class); | |
| this.useIncludeHints = useIncludeHints; | |
| spawnScannerSupplier.set( | |
| new SpawnIncludeScanner( | |
| env.getExecRoot(), | |
| options.experimentalRemoteExtractionThreshold, | |
| env.getSyscallCache())); | |
| this.spawnScannerSupplier = spawnScannerSupplier; | |
| env.getEventBus().register(this); | |
| } | |
| private CppIncludeScanningContextImpl getCppActionContext() { | |
| return new CppIncludeScanningContextImpl(() -> includeScannerSupplier); | |
| } | |
| private SwigIncludeScanningContextImpl getSwigActionContext() { | |
| return new SwigIncludeScanningContextImpl(env, spawnScannerSupplier, () -> includePool); | |
| } | |
| @Override | |
| public void executionPhaseStarting( | |
| ActionGraph unusedActionGraph, | |
| Supplier<ImmutableSet<Artifact>> unusedTopLevelArtifacts, | |
| @Nullable EphemeralCheckIfOutputConsumed unusedCheck) | |
| throws AbruptExitException, InterruptedException { | |
| IncludeParser.HintsRules hintsRules; | |
| if (useIncludeHints) { | |
| try (var sc = Profiler.instance().profile("evaluateSkyKeyForExecutionSetup")) { | |
| hintsRules = | |
| (IncludeParser.HintsRules) | |
| env.getSkyframeExecutor() | |
| .evaluateSkyKeyForExecutionSetup( | |
| env.getReporter(), IncludeHintsFunction.INCLUDE_HINTS_KEY); | |
| } catch (ExecException e) { | |
| throw new AbruptExitException( | |
| DetailedExitCode.of( | |
| FailureDetail.newBuilder() | |
| .setMessage("could not initialize include hints: " + e.getMessage()) | |
| .setIncludeScanning( | |
| IncludeScanning.newBuilder() | |
| .setCode(IncludeScanning.Code.INITIALIZE_INCLUDE_HINTS_ERROR)) | |
| .build()), | |
| e); | |
| } | |
| } else { | |
| hintsRules = IncludeParser.HintsRules.EMPTY; | |
| } | |
| includeScannerSupplier.init( | |
| new IncludeParser( | |
| new IncludeParser.Hints( | |
| hintsRules, | |
| env.getSyscallCache(), | |
| env.getSkyframeBuildView().getArtifactFactory()))); | |
| } | |
| @Override | |
| public void executionPhaseEnding() { | |
| if (options.experimentalReuseIncludeScanningThreads) { | |
| if (includePool != null && !includePool.isShutdown()) { | |
| ExecutorUtil.uninterruptibleShutdownNow(includePool); | |
| } | |
| includePool = null; | |
| } | |
| } | |
| @Override | |
| public void executorCreated() { | |
| var buildRequestOptions = env.getOptions().getOptions(BuildRequestOptions.class); | |
| var useAsyncExecution = buildRequestOptions != null && buildRequestOptions.useAsyncExecution; | |
| int threads = options.includeScanningParallelism; | |
| if (useAsyncExecution) { | |
| includePool = | |
| Executors.newThreadPerTaskExecutor( | |
| Thread.ofVirtual().name("Include scanner ", 0).factory()); | |
| } else if (threads > 0) { | |
| logger.atInfo().log("Include scanning configured to use a pool with %d threads", threads); | |
| if (options.experimentalReuseIncludeScanningThreads) { | |
| includePool = | |
| new ThreadPoolExecutor( | |
| threads, | |
| threads, | |
| 0L, | |
| TimeUnit.SECONDS, | |
| new SynchronousQueue<>(), | |
| new ThreadFactoryBuilder().setNameFormat("Include scanner %d").build(), | |
| (r, e) -> r.run()); | |
| } else { | |
| includePool = ExecutorUtil.newSlackPool(threads, "Include scanner"); | |
| } | |
| } else { | |
| logger.atInfo().log("Include scanning configured to use a direct executor"); | |
| includePool = MoreExecutors.newDirectExecutorService(); | |
| } | |
| includeScannerSupplier = | |
| new IncludeScannerSupplier( | |
| env.getDirectories(), | |
| includePool, | |
| env.getSkyframeBuildView().getArtifactFactory(), | |
| spawnScannerSupplier, | |
| env.getExecRoot()); | |
| spawnScannerSupplier.get().setOutputService(env.getOutputService()); | |
| spawnScannerSupplier.get().setInMemoryOutput(options.inMemoryIncludesFiles); | |
| } | |
| } | |
| } |