|  | // 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 com.google.common.annotations.VisibleForTesting; | 
|  | import com.google.common.base.Supplier; | 
|  | 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.util.concurrent.ListenableFuture; | 
|  | import com.google.common.util.concurrent.MoreExecutors; | 
|  | import com.google.devtools.build.lib.actions.ActionContext; | 
|  | 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.actions.ExecutionStrategy; | 
|  | import com.google.devtools.build.lib.actions.ExecutorInitException; | 
|  | import com.google.devtools.build.lib.analysis.ArtifactsToOwnerLabels; | 
|  | import com.google.devtools.build.lib.analysis.BlazeDirectories; | 
|  | import com.google.devtools.build.lib.buildtool.BuildRequest; | 
|  | import com.google.devtools.build.lib.concurrent.ExecutorUtil; | 
|  | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile; | 
|  | import com.google.devtools.build.lib.exec.ActionContextProvider; | 
|  | import com.google.devtools.build.lib.exec.ExecutorBuilder; | 
|  | import com.google.devtools.build.lib.includescanning.IncludeParser.Inclusion; | 
|  | 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.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.skyframe.MutableSupplier; | 
|  | 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.ExecutionException; | 
|  | import java.util.concurrent.ExecutorService; | 
|  | import java.util.logging.Logger; | 
|  |  | 
|  | /** | 
|  | * Module that provides implementations of {@link CppIncludeExtractionContext}, | 
|  | * {@link CppIncludeScanningContext}, and {@link SwigIncludeScanningContext}. | 
|  | */ | 
|  | public class IncludeScanningModule extends BlazeModule { | 
|  | private static final Logger log = Logger.getLogger(IncludeScanningModule.class.getName()); | 
|  |  | 
|  | private static final PathFragment INCLUDE_HINTS_FILENAME = | 
|  | PathFragment.create("tools/cpp/INCLUDE_HINTS"); | 
|  |  | 
|  | private final MutableSupplier<SpawnIncludeScanner> spawnIncludeScannerSupplier = | 
|  | new MutableSupplier<>(); | 
|  | private final MutableSupplier<ArtifactFactory> artifactFactory = new MutableSupplier<>(); | 
|  |  | 
|  | protected PathFragment getIncludeHintsFilename() { | 
|  | return INCLUDE_HINTS_FILENAME; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @ThreadHostile | 
|  | public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) { | 
|  | builder.addActionContextProvider( | 
|  | new IncludeScanningActionContextProvider(env, request, spawnIncludeScannerSupplier)); | 
|  | builder | 
|  | .addStrategyByContext(CppIncludeExtractionContext.class, "") | 
|  | .addStrategyByContext(SwigIncludeScanningContext.class, "") | 
|  | .addStrategyByContext(CppIncludeScanningContext.class, ""); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) { | 
|  | return "build".equals(command.name()) | 
|  | ? ImmutableList.of(IncludeScanningOptions.class) | 
|  | : ImmutableList.<Class<? extends OptionsBase>>of(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void beforeCommand(CommandEnvironment env) { | 
|  | artifactFactory.set(env.getSkyframeBuildView().getArtifactFactory()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void afterCommand() { | 
|  | spawnIncludeScannerSupplier.set(null); | 
|  | artifactFactory.set(null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void workspaceInit( | 
|  | BlazeRuntime runtime, BlazeDirectories directories, WorkspaceBuilder builder) { | 
|  | builder.addSkyFunctions(getSkyFunctions(getIncludeHintsFilename())); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | public static ImmutableMap<SkyFunctionName, SkyFunction> getSkyFunctions( | 
|  | PathFragment includeHintsFile) { | 
|  | return ImmutableMap.of( | 
|  | IncludeScanningSkyFunctions.INCLUDE_HINTS, | 
|  | new IncludeHintsFunction(includeHintsFile)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implementation of {@link CppIncludeExtractionContext}. | 
|  | */ | 
|  | @ExecutionStrategy(contextType = CppIncludeExtractionContext.class) | 
|  | 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. | 
|  | */ | 
|  | @ExecutionStrategy(contextType = SwigIncludeScanningContext.class) | 
|  | 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<>(); | 
|  | private final boolean useAsyncIncludeScanner; | 
|  |  | 
|  | SwigIncludeScanningContextImpl( | 
|  | CommandEnvironment env, | 
|  | Supplier<SpawnIncludeScanner> spawnScannerSupplier, | 
|  | Supplier<ExecutorService> includePool, | 
|  | boolean useAsyncIncludeScanner) { | 
|  | this.env = env; | 
|  | this.spawnScannerSupplier = spawnScannerSupplier; | 
|  | this.includePool = includePool; | 
|  | this.useAsyncIncludeScanner = useAsyncIncludeScanner; | 
|  | } | 
|  |  | 
|  | @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(), | 
|  | useAsyncIncludeScanner); | 
|  | ImmutableMap.Builder<PathFragment, Artifact> pathToLegalOutputArtifact = | 
|  | ImmutableMap.builder(); | 
|  | for (Artifact path : legalOutputPaths) { | 
|  | pathToLegalOutputArtifact.put(path.getExecPath(), path); | 
|  | } | 
|  | try { | 
|  | scanner | 
|  | .processAsync( | 
|  | source, | 
|  | ImmutableList.of(source), | 
|  | // For Swig include scanning just point to the output file in the map. | 
|  | new IncludeScanningHeaderData.Builder( | 
|  | pathToLegalOutputArtifact.build(), /*modularHeaders=*/ ImmutableSet.of()) | 
|  | .build(), | 
|  | ImmutableList.of(), | 
|  | includes, | 
|  | actionExecutionMetadata, | 
|  | actionExecContext, | 
|  | grepIncludes) | 
|  | .get(); | 
|  | } catch (ExecutionException e) { | 
|  | Throwables.throwIfInstanceOf(e.getCause(), ExecException.class); | 
|  | Throwables.throwIfInstanceOf(e.getCause(), InterruptedException.class); | 
|  | if (e.getCause() instanceof IORuntimeException) { | 
|  | throw ((IORuntimeException) e.getCause()).getCauseIOException(); | 
|  | } | 
|  | Throwables.throwIfUnchecked(e.getCause()); | 
|  | throw new IllegalStateException(e.getCause()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Factory for execution strategies related to include scanning. | 
|  | */ | 
|  | public static class IncludeScanningActionContextProvider extends ActionContextProvider { | 
|  | private final CommandEnvironment env; | 
|  | private final ImmutableList<ActionContext> strategies; | 
|  | private final BuildRequest buildRequest; | 
|  |  | 
|  | private final Supplier<SpawnIncludeScanner> spawnScannerSupplier; | 
|  | private IncludeScannerSupplierImpl includeScannerSupplier; | 
|  | private ExecutorService includePool; | 
|  |  | 
|  | public IncludeScanningActionContextProvider( | 
|  | CommandEnvironment env, | 
|  | BuildRequest buildRequest, | 
|  | MutableSupplier<SpawnIncludeScanner> spawnScannerSupplier) { | 
|  | this.env = env; | 
|  | this.buildRequest = buildRequest; | 
|  |  | 
|  | IncludeScanningOptions options = buildRequest.getOptions(IncludeScanningOptions.class); | 
|  | spawnScannerSupplier.set( | 
|  | new SpawnIncludeScanner( | 
|  | env.getExecRoot(), | 
|  | options.experimentalRemoteExtractionThreshold)); | 
|  | this.spawnScannerSupplier = spawnScannerSupplier; | 
|  | this.strategies = | 
|  | ImmutableList.of( | 
|  | new CppIncludeExtractionContextImpl(env), | 
|  | new SwigIncludeScanningContextImpl( | 
|  | env, spawnScannerSupplier, () -> includePool, options.useAsyncIncludeScanner), | 
|  | new CppIncludeScanningContextImpl(() -> includeScannerSupplier)); | 
|  |  | 
|  | env.getEventBus().register(this); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Iterable<ActionContext> getActionContexts() { | 
|  | return strategies; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void executionPhaseStarting( | 
|  | ActionGraph actionGraph, | 
|  | Supplier<ArtifactsToOwnerLabels> topLevelArtifactsToAccountingGroups) | 
|  | throws ExecutorInitException, InterruptedException { | 
|  | try { | 
|  | includeScannerSupplier.init( | 
|  | new IncludeParser( | 
|  | new IncludeParser.Hints( | 
|  | (IncludeParser.HintsRules) | 
|  | env.getSkyframeExecutor() | 
|  | .evaluateSkyKeyForExecutionSetup( | 
|  | env.getReporter(), IncludeHintsFunction.INCLUDE_HINTS_KEY), | 
|  | env.getSkyframeBuildView().getArtifactFactory()))); | 
|  | } catch (ExecException e) { | 
|  | throw new ExecutorInitException("could not initialize include hints", e); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void executorCreated(Iterable<ActionContext> usedContexts) throws ExecutorInitException { | 
|  | IncludeScanningOptions options = buildRequest.getOptions(IncludeScanningOptions.class); | 
|  | int threads = options.includeScanningParallelism; | 
|  | if (threads > 0) { | 
|  | log.info( | 
|  | String.format("Include scanning configured to use a pool with %d threads", threads)); | 
|  | includePool = ExecutorUtil.newSlackPool(threads, "Include scanner"); | 
|  | } else { | 
|  | log.info("Include scanning configured to use a direct executor"); | 
|  | includePool = MoreExecutors.newDirectExecutorService(); | 
|  | } | 
|  | includeScannerSupplier = | 
|  | new IncludeScannerSupplierImpl( | 
|  | env.getDirectories(), | 
|  | includePool, | 
|  | env.getSkyframeBuildView().getArtifactFactory(), | 
|  | spawnScannerSupplier, | 
|  | env.getExecRoot(), | 
|  | options.useAsyncIncludeScanner); | 
|  |  | 
|  | spawnScannerSupplier.get().setOutputService(env.getOutputService()); | 
|  | spawnScannerSupplier.get().setInMemoryOutput(options.inMemoryIncludesFiles); | 
|  | } | 
|  | } | 
|  | } |