| // 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.rules.cpp; |
| |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.AbstractAction; |
| import com.google.devtools.build.lib.actions.ActionEnvironment; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.ActionResult; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.CommandLineExpansionException; |
| import com.google.devtools.build.lib.actions.ExecException; |
| import com.google.devtools.build.lib.actions.ResourceSet; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.SpawnActionContext; |
| import com.google.devtools.build.lib.actions.SpawnResult; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; |
| import com.google.devtools.build.lib.util.ShellEscaper; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.UUID; |
| import java.util.logging.Logger; |
| |
| /** |
| * Action that represents a fake C++ compilation step. |
| */ |
| @ThreadCompatible |
| public class FakeCppCompileAction extends CppCompileAction { |
| |
| private static final Logger logger = Logger.getLogger(FakeCppCompileAction.class.getName()); |
| |
| public static final UUID GUID = UUID.fromString("8ab63589-be01-4a39-b770-b98ae8b03493"); |
| |
| private final PathFragment tempOutputFile; |
| |
| FakeCppCompileAction( |
| ActionOwner owner, |
| FeatureConfiguration featureConfiguration, |
| CcToolchainVariables variables, |
| Artifact sourceFile, |
| CppConfiguration cppConfiguration, |
| boolean shareable, |
| boolean shouldScanIncludes, |
| boolean shouldPruneModules, |
| boolean usePic, |
| boolean useHeaderModules, |
| NestedSet<Artifact> mandatoryInputs, |
| Iterable<Artifact> inputsForInvalidation, |
| ImmutableList<Artifact> builtinIncludeFiles, |
| NestedSet<Artifact> prunableHeaders, |
| Artifact outputFile, |
| PathFragment tempOutputFile, |
| Artifact dotdFile, |
| ActionEnvironment env, |
| CcCompilationContext ccCompilationContext, |
| CoptsFilter nocopts, |
| CppSemantics cppSemantics, |
| ImmutableList<PathFragment> builtInIncludeDirectories, |
| ImmutableMap<String, String> executionInfo, |
| Artifact grepIncludes) { |
| super( |
| owner, |
| featureConfiguration, |
| variables, |
| sourceFile, |
| cppConfiguration, |
| shareable, |
| shouldScanIncludes, |
| shouldPruneModules, |
| usePic, |
| useHeaderModules, |
| mandatoryInputs, |
| inputsForInvalidation, |
| builtinIncludeFiles, |
| prunableHeaders, |
| outputFile, |
| dotdFile, |
| /* gcnoFile=*/ null, |
| /* dwoFile=*/ null, |
| /* ltoIndexingFile=*/ null, |
| env, |
| // We only allow inclusion of header files explicitly declared in |
| // "srcs", so we only use declaredIncludeSrcs, not declaredIncludeDirs. |
| // (Disallowing use of undeclared headers for cc_fake_binary is needed |
| // because the header files get included in the runfiles for the |
| // cc_fake_binary and for the negative compilation tests that depend on |
| // the cc_fake_binary, and the runfiles must be determined at analysis |
| // time, so they can't depend on the contents of the ".d" file.) |
| CcCompilationContext.disallowUndeclaredHeaders(ccCompilationContext), |
| nocopts, |
| /* additionalIncludeScanningRoots=*/ ImmutableList.of(), |
| GUID, |
| executionInfo, |
| CppActionNames.CPP_COMPILE, |
| cppSemantics, |
| builtInIncludeDirectories, |
| grepIncludes); |
| this.tempOutputFile = Preconditions.checkNotNull(tempOutputFile); |
| } |
| |
| @Override |
| @ThreadCompatible |
| public ActionResult execute(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException, InterruptedException { |
| setModuleFileFlags(); |
| List<SpawnResult> spawnResults; |
| // First, do a normal compilation, to generate the ".d" file. The generated object file is built |
| // to a temporary location (tempOutputFile) and ignored afterwards. |
| logger.info("Generating " + getDotdFile()); |
| byte[] dotDContents = null; |
| try { |
| Spawn spawn = createSpawn(actionExecutionContext.getClientEnv()); |
| SpawnActionContext context = actionExecutionContext.getContext(SpawnActionContext.class); |
| spawnResults = context.exec(spawn, actionExecutionContext); |
| // The SpawnActionContext guarantees that the first list entry is the successful one. |
| dotDContents = getDotDContents(spawnResults.get(0)); |
| } catch (ExecException e) { |
| throw e.toActionExecutionException( |
| "C++ compilation of rule '" + getOwner().getLabel() + "'", |
| actionExecutionContext.getVerboseFailures(), |
| this); |
| } finally { |
| clearAdditionalInputs(); |
| } |
| CppIncludeExtractionContext scanningContext = |
| actionExecutionContext.getContext(CppIncludeExtractionContext.class); |
| Path execRoot = actionExecutionContext.getExecRoot(); |
| |
| NestedSet<Artifact> discoveredInputs; |
| if (getDotdFile() == null) { |
| discoveredInputs = NestedSetBuilder.<Artifact>stableOrder().build(); |
| } else { |
| HeaderDiscovery.Builder discoveryBuilder = |
| new HeaderDiscovery.Builder() |
| .setAction(this) |
| .setSourceFile(getSourceFile()) |
| .setDependencies( |
| processDepset(actionExecutionContext, execRoot, dotDContents).getDependencies()) |
| .setPermittedSystemIncludePrefixes(getPermittedSystemIncludePrefixes(execRoot)) |
| .setAllowedDerivedinputs(getAllowedDerivedInputs()); |
| |
| if (needsIncludeValidation) { |
| discoveryBuilder.shouldValidateInclusions(); |
| } |
| |
| discoveredInputs = |
| discoveryBuilder |
| .build() |
| .discoverInputsFromDependencies(execRoot, scanningContext.getArtifactResolver()); |
| } |
| |
| dotDContents = null; // Garbage collect in-memory .d contents. |
| |
| // Even cc_fake_binary rules need to properly declare their dependencies... |
| // In fact, they need to declare their dependencies even more than cc_binary rules do. |
| // CcCommonConfiguredTarget passes in an empty set of declaredIncludeDirs, |
| // so this check below will only allow inclusion of header files that are explicitly |
| // listed in the "srcs" of the cc_fake_binary or in the "srcs" of a cc_library that it |
| // depends on. |
| try { |
| validateInclusions(actionExecutionContext, discoveredInputs); |
| } catch (ActionExecutionException e) { |
| // TODO(bazel-team): (2009) make this into an error, once most of the current warnings |
| // are fixed. |
| actionExecutionContext |
| .getEventHandler() |
| .handle( |
| Event.warn( |
| getOwner().getLocation(), |
| e.getMessage() + ";\n this warning may eventually become an error")); |
| } |
| |
| if (discoversInputs()) { |
| updateActionInputs(discoveredInputs); |
| } else { |
| Preconditions.checkState( |
| discoveredInputs.isEmpty(), |
| "Discovered inputs without discovering inputs? %s %s", |
| discoveredInputs, |
| this); |
| } |
| |
| // Generate a fake ".o" file containing the command line needed to generate |
| // the real object file. |
| logger.info("Generating " + outputFile); |
| |
| // A cc_fake_binary rule generates fake .o files and a fake target file, |
| // which merely contain instructions on building the real target. We need to |
| // be careful to use a new set of output file names in the instructions, as |
| // to not overwrite the fake output files when someone tries to follow the |
| // instructions. As the real compilation is executed by the test from its |
| // runfiles directory (where writing is forbidden), we patch the command |
| // line to write to $TEST_TMPDIR instead. |
| final String outputPrefix = "$TEST_TMPDIR/"; |
| String argv; |
| try { |
| argv = |
| getArguments().stream() |
| .map( |
| input -> { |
| String result = ShellEscaper.escapeString(input); |
| // Replace -c <tempOutputFile> so it's -c <outputFile>. |
| if (input.equals(tempOutputFile.getPathString())) { |
| result = |
| outputPrefix + ShellEscaper.escapeString(outputFile.getExecPathString()); |
| } |
| if (input.equals(outputFile.getExecPathString()) |
| || (getDotdFile() != null |
| && input.equals(getDotdFile().getExecPathString()))) { |
| result = outputPrefix + ShellEscaper.escapeString(input); |
| } |
| return result; |
| }) |
| .collect(joining(" ")); |
| } catch (CommandLineExpansionException e) { |
| throw new ActionExecutionException( |
| "failed to generate compile command for rule '" |
| + getOwner().getLabel() |
| + ": " |
| + e.getMessage(), |
| this, |
| /* catastrophe= */ false); |
| } |
| |
| // Write the command needed to build the real .o file to the fake .o file. |
| // Generate a command to ensure that the output directory exists; otherwise |
| // the compilation would fail. |
| try { |
| // Ensure that the .d file and .o file are siblings, so that the "mkdir" below works for |
| // both. |
| Preconditions.checkState( |
| getDotdFile() == null |
| || outputFile |
| .getExecPath() |
| .getParentDirectory() |
| .equals(getDotdFile().getExecPath().getParentDirectory())); |
| FileSystemUtils.writeContent( |
| actionExecutionContext.getInputPath(outputFile), |
| ISO_8859_1, |
| actionExecutionContext.getInputPath(outputFile).getBaseName() |
| + ": " |
| + "mkdir -p " |
| + outputPrefix |
| + "$(dirname " |
| + outputFile.getExecPath() |
| + ")" |
| + " && " |
| + argv |
| + "\n"); |
| } catch (IOException e) { |
| throw new ActionExecutionException("failed to create fake compile command for rule '" |
| + getOwner().getLabel() + ": " + e.getMessage(), this, false); |
| } |
| return ActionResult.create(spawnResults); |
| } |
| |
| @Override |
| public String getMnemonic() { |
| return "FakeCppCompile"; |
| } |
| |
| @Override |
| public ResourceSet estimateResourceConsumptionLocal() { |
| return AbstractAction.DEFAULT_RESOURCE_SET; |
| } |
| } |