blob: 076fbdb7cbde7b1575474fcff374df854ef3e371 [file] [log] [blame]
// 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.SpawnResult;
import com.google.devtools.build.lib.actions.SpawnStrategy;
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.packages.StarlarkSemanticsOptions;
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,
NestedSet<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());
SpawnStrategy strategy = actionExecutionContext.getContext(SpawnStrategy.class);
spawnResults = strategy.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();
}
boolean siblingRepositoryLayout =
actionExecutionContext
.getOptions()
.getOptions(StarlarkSemanticsOptions.class)
.experimentalSiblingRepositoryLayout;
discoveredInputs =
discoveryBuilder
.build()
.discoverInputsFromDependencies(
execRoot, scanningContext.getArtifactResolver(), siblingRepositoryLayout);
}
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;
}
}