blob: 322ed53c729aa78b78c9810fb94240cccb30d5e9 [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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.AbstractAction;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ArtifactResolver;
import com.google.devtools.build.lib.actions.CommandAction;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.Executor;
import com.google.devtools.build.lib.actions.PackageRootResolutionException;
import com.google.devtools.build.lib.actions.PackageRootResolver;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.extra.CppCompileInfo;
import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.ExecutionInfoSpecifier;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.collect.CollectionUtils;
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.events.EventHandler;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CppCompileActionContext.Reply;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
import com.google.devtools.build.lib.util.DependencySet;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
/** Action that represents some kind of C++ compilation step. */
@ThreadCompatible
public class CppCompileAction extends AbstractAction
implements IncludeScannable, ExecutionInfoSpecifier, CommandAction {
/**
* Represents logic that determines if an artifact is a special input, meaning that it may require
* additional inputs when it is compiled or may not be available to other actions.
*/
public interface SpecialInputsHandler {
/** Returns if {@code includedFile} is special, so may not be available to other actions. */
boolean isSpecialFile(Artifact includedFile);
/** Returns the set of files to be added for an included file (as returned in the .d file). */
Collection<Artifact> getInputsForIncludedFile(
Artifact includedFile, ArtifactResolver artifactResolver);
}
static final SpecialInputsHandler VOID_SPECIAL_INPUTS_HANDLER =
new SpecialInputsHandler() {
@Override
public boolean isSpecialFile(Artifact includedFile) {
return false;
}
@Override
public Collection<Artifact> getInputsForIncludedFile(
Artifact includedFile, ArtifactResolver artifactResolver) {
return ImmutableList.of();
}
};
private static final int VALIDATION_DEBUG = 0; // 0==none, 1==warns/errors, 2==all
private static final boolean VALIDATION_DEBUG_WARN = VALIDATION_DEBUG >= 1;
/**
* A string constant for the c compilation action.
*/
public static final String C_COMPILE = "c-compile";
/**
* A string constant for the c++ compilation action.
*/
public static final String CPP_COMPILE = "c++-compile";
/**
* A string constant for the objc compilation action.
*/
public static final String OBJC_COMPILE = "objc-compile";
/**
* A string constant for the objc++ compile action.
*/
public static final String OBJCPP_COMPILE = "objc++-compile";
/**
* A string constant for the c++ header parsing.
*/
public static final String CPP_HEADER_PARSING = "c++-header-parsing";
/**
* A string constant for the c++ header preprocessing.
*/
public static final String CPP_HEADER_PREPROCESSING = "c++-header-preprocessing";
/**
* A string constant for the c++ module compilation action.
* Note: currently we don't support C module compilation.
*/
public static final String CPP_MODULE_COMPILE = "c++-module-compile";
/**
* A string constant for the assembler actions.
*/
public static final String ASSEMBLE = "assemble";
public static final String PREPROCESS_ASSEMBLE = "preprocess-assemble";
/**
* A string constant for the clif actions. Bazel enables different features of the toolchain based
* on the name of the action. This name enables the clif_matcher feature, which switches the
* "compiler" to the clif_matcher and adds some additional arguments as described in the CROSSTOOL
* file.
*/
public static final String CLIF_MATCH = "clif-match";
// TODO(ulfjack): this is only used to get the local shell environment and to check if coverage is
// enabled. Move those two things to local fields and drop this. Accessing anything other than
// these fields can impact correctness!
private final BuildConfiguration configuration;
protected final Artifact outputFile;
private final Label sourceLabel;
private final Artifact optionalSourceFile;
private final NestedSet<Artifact> mandatoryInputs;
private final boolean shouldScanIncludes;
private final boolean shouldPruneModules;
private final boolean usePic;
private final CppCompilationContext context;
private final Iterable<IncludeScannable> lipoScannables;
private final ImmutableList<Artifact> builtinIncludeFiles;
// A list of files to include scan that are not source files, pcm files, lipo scannables, or
// included via a command-line "-include file.h". Actions that use non C++ files as source
// files--such as Clif--may use this mechanism.
private final ImmutableList<Artifact> additionalIncludeScannables;
@VisibleForTesting public final CppCompileCommandLine cppCompileCommandLine;
private final ImmutableSet<String> executionRequirements;
private final ImmutableMap<String, String> environment;
@VisibleForTesting final CppConfiguration cppConfiguration;
private final FeatureConfiguration featureConfiguration;
protected final Class<? extends CppCompileActionContext> actionContext;
private final SpecialInputsHandler specialInputsHandler;
private final CppSemantics semantics;
/**
* Identifier for the actual execution time behavior of the action.
*
* <p>Required because the behavior of this class can be modified by injecting code in the
* constructor or by inheritance, and we want to have different cache keys for those.
*/
private final UUID actionClassId;
// This can be read/written from multiple threads, and so accesses should be synchronized.
@GuardedBy("this")
private boolean inputsKnown = false;
/**
* Set when the action prepares for execution. Used to preserve state between preparation and
* execution.
*/
private Collection<Artifact> additionalInputs = null;
private CcToolchainFeatures.Variables overwrittenVariables = null;
private ImmutableList<Artifact> resolvedInputs = ImmutableList.<Artifact>of();
/**
* Creates a new action to compile C/C++ source files.
*
* @param owner the owner of the action, usually the configured target that emitted it
* @param features TODO(bazel-team): Add parameter description.
* @param featureConfiguration TODO(bazel-team): Add parameter description.
* @param variables TODO(bazel-team): Add parameter description.
* @param sourceFile the source file that should be compiled. {@code mandatoryInputs} must contain
* this file
* @param shouldScanIncludes a boolean indicating whether scanning of {@code sourceFile} is to be
* performed looking for inclusions.
* @param usePic TODO(bazel-team): Add parameter description.
* @param sourceLabel the label of the rule the source file is generated by
* @param mandatoryInputs any additional files that need to be present for the compilation to
* succeed, can be empty but not null, for example, extra sources for FDO.
* @param outputFile the object file that is written as result of the compilation, or the fake
* object for {@link FakeCppCompileAction}s
* @param dotdFile the .d file that is generated as a side-effect of compilation
* @param gcnoFile the coverage notes that are written in coverage mode, can be null
* @param dwoFile the .dwo output file where debug information is stored for Fission builds (null
* if Fission mode is disabled)
* @param optionalSourceFile an additional optional source file (null if unneeded)
* @param configuration the build configurations
* @param cppConfiguration TODO(bazel-team): Add parameter description.
* @param context the compilation context
* @param actionContext TODO(bazel-team): Add parameter description.
* @param copts options for the compiler
* @param coptsFilter regular expression to remove options from {@code copts}
* @param specialInputsHandler TODO(bazel-team): Add parameter description.
* @param lipoScannables List of artifacts to include-scan when this action is a lipo action
* @param additionalIncludeScannables list of additional artifacts to include-scan
* @param actionClassId TODO(bazel-team): Add parameter description
* @param executionRequirements out-of-band hints to be passed to the execution backend to signal
* platform requirements
* @param environment TODO(bazel-team): Add parameter description
* @param actionName a string giving the name of this action for the purpose of toolchain
* evaluation
* @param ruleContext The rule-context that produced this action
*/
protected CppCompileAction(
ActionOwner owner,
// TODO(bazel-team): Eventually we will remove 'features'; all functionality in 'features'
// will be provided by 'featureConfiguration'.
ImmutableList<String> features,
FeatureConfiguration featureConfiguration,
CcToolchainFeatures.Variables variables,
Artifact sourceFile,
boolean shouldScanIncludes,
boolean shouldPruneModules,
boolean usePic,
Label sourceLabel,
NestedSet<Artifact> mandatoryInputs,
Artifact outputFile,
DotdFile dotdFile,
@Nullable Artifact gcnoFile,
@Nullable Artifact dwoFile,
Artifact optionalSourceFile,
BuildConfiguration configuration,
CppConfiguration cppConfiguration,
CppCompilationContext context,
Class<? extends CppCompileActionContext> actionContext,
ImmutableList<String> copts,
Predicate<String> coptsFilter,
SpecialInputsHandler specialInputsHandler,
Iterable<IncludeScannable> lipoScannables,
ImmutableList<Artifact> additionalIncludeScannables,
UUID actionClassId,
ImmutableSet<String> executionRequirements,
ImmutableMap<String, String> environment,
String actionName,
RuleContext ruleContext,
CppSemantics semantics) {
super(
owner,
createInputs(
ruleContext,
mandatoryInputs,
context.getTransitiveCompilationPrerequisites(),
optionalSourceFile,
lipoScannables),
CollectionUtils.asListWithoutNulls(
outputFile, (dotdFile == null ? null : dotdFile.artifact()), gcnoFile, dwoFile));
this.configuration = configuration;
this.sourceLabel = sourceLabel;
this.outputFile = Preconditions.checkNotNull(outputFile);
this.optionalSourceFile = optionalSourceFile;
this.context = context;
this.specialInputsHandler = specialInputsHandler;
this.cppConfiguration = cppConfiguration;
this.featureConfiguration = featureConfiguration;
// inputsKnown begins as the logical negation of shouldScanIncludes.
// When scanning includes, the inputs begin as not known, and become
// known after inclusion scanning. When *not* scanning includes,
// the inputs are as declared, hence known, and remain so.
this.shouldScanIncludes = shouldScanIncludes;
this.shouldPruneModules = shouldPruneModules;
this.usePic = usePic;
this.inputsKnown = !shouldScanIncludes;
this.cppCompileCommandLine =
new CppCompileCommandLine(
sourceFile, dotdFile, copts, coptsFilter, features, variables, actionName);
this.actionContext = actionContext;
this.lipoScannables = lipoScannables;
this.actionClassId = actionClassId;
this.executionRequirements = executionRequirements;
this.environment = environment;
// We do not need to include the middleman artifact since it is a generated
// artifact and will definitely exist prior to this action execution.
this.mandatoryInputs = mandatoryInputs;
this.builtinIncludeFiles = CppHelper.getToolchain(ruleContext).getBuiltinIncludeFiles();
this.semantics = semantics;
if (semantics.needsIncludeValidation()) {
verifyIncludePaths(ruleContext);
}
this.additionalIncludeScannables = ImmutableList.copyOf(additionalIncludeScannables);
}
/**
* Verifies that the include paths of this action are within the limits of the execution root.
*/
private void verifyIncludePaths(RuleContext ruleContext) {
if (ruleContext == null) {
return;
}
Iterable<PathFragment> ignoredDirs = getValidationIgnoredDirs();
// We currently do not check the output of:
// - getQuoteIncludeDirs(): those only come from includes attributes, and are checked in
// CcCommon.getIncludeDirsFromIncludesAttribute().
// - getBuiltinIncludeDirs(): while in practice this doesn't happen, bazel can be configured
// to use an absolute system root, in which case the builtin include dirs might be absolute.
for (PathFragment include : Iterables.concat(getIncludeDirs(), getSystemIncludeDirs())) {
// Ignore headers from built-in include directories.
if (FileSystemUtils.startsWithAny(include, ignoredDirs)) {
continue;
}
if (include.isAbsolute()
|| !PathFragment.EMPTY_FRAGMENT.getRelative(include).normalize().isNormalized()) {
ruleContext.ruleError(
"The include path '" + include + "' references a path outside of the execution root.");
}
}
}
private static NestedSet<Artifact> createInputs(
RuleContext ruleContext,
NestedSet<Artifact> mandatoryInputs,
Set<Artifact> prerequisites,
Artifact optionalSourceFile,
Iterable<IncludeScannable> lipoScannables) {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
if (optionalSourceFile != null) {
builder.add(optionalSourceFile);
}
builder.addAll(prerequisites);
builder.addAll(CppHelper.getToolchain(ruleContext).getBuiltinIncludeFiles());
builder.addTransitive(mandatoryInputs);
if (lipoScannables != null && lipoScannables.iterator().hasNext()) {
// We need to add "legal generated scanner files" coming through LIPO scannables here. These
// usually contain pre-grepped source files, i.e. files just containing the #include lines
// extracted from generated files. With LIPO, some of these files can be accessed, even though
// there is no direct dependency on them. Adding the artifacts as inputs to this compile
// action ensures that the action generating them is actually executed.
for (IncludeScannable lipoScannable : lipoScannables) {
for (Artifact value : lipoScannable.getLegalGeneratedScannerFileMap().values()) {
if (value != null) {
builder.add(value);
}
}
}
}
return builder.build();
}
/**
* Whether we should do "include scanning". Note that this does *not* mean whether we should parse
* the .d files to determine which include files were used during compilation. Instead, this means
* whether we should a) run the pre-execution include scanner (see {@code IncludeScanningContext})
* if one exists and b) whether the action inputs should be modified to match the results of that
* pre-execution scanning and (if enabled) again after execution to match the results of the .d
* file parsing.
*
* <p>This does *not* have anything to do with "hdrs_check".
*/
public boolean shouldScanIncludes() {
return shouldScanIncludes;
}
@Override
public List<PathFragment> getBuiltInIncludeDirectories() {
return cppConfiguration.getBuiltInIncludeDirectories();
}
@Nullable
@Override
public List<Artifact> getBuiltInIncludeFiles() {
return builtinIncludeFiles;
}
public List<Artifact> getadditionalIncludeScannables() {
return additionalIncludeScannables;
}
public String getHostSystemName() {
return cppConfiguration.getHostSystemName();
}
@Override
public NestedSet<Artifact> getMandatoryInputs() {
return mandatoryInputs;
}
@Override
public synchronized boolean inputsKnown() {
return inputsKnown;
}
/**
* Returns the list of additional inputs found by dependency discovery, during action preparation,
* and clears the stored list. {@link #prepare} must be called before this method is called, on
* each action execution.
*/
public Collection<? extends ActionInput> getAdditionalInputs() {
Collection<? extends ActionInput> result = Preconditions.checkNotNull(additionalInputs);
additionalInputs = null;
return result;
}
@VisibleForTesting
public void setResolvedInputsForTesting(ImmutableList<Artifact> resolvedInputs) {
this.resolvedInputs = resolvedInputs;
}
@Override
public boolean discoversInputs() {
return true;
}
@Nullable
@Override
public Iterable<Artifact> discoverInputs(ActionExecutionContext actionExecutionContext)
throws ActionExecutionException, InterruptedException {
Executor executor = actionExecutionContext.getExecutor();
Collection<Artifact> initialResult;
if (!shouldScanIncludes) {
return null;
}
try {
initialResult = executor.getContext(actionContext)
.findAdditionalInputs(this, actionExecutionContext);
} catch (ExecException e) {
throw e.toActionExecutionException("Include scanning of rule '" + getOwner().getLabel() + "'",
executor.getVerboseFailures(), this);
}
if (initialResult == null) {
// We will find inputs during execution. Store an empty list to show we did try to discover
// inputs and return null to inform the caller that inputs will be discovered later.
this.additionalInputs = ImmutableList.of();
return null;
}
if (shouldPruneModules) {
Set<Artifact> initialResultSet = Sets.newLinkedHashSet(initialResult);
List<String> usedModulePaths = Lists.newArrayList();
for (Artifact usedModule : context.getUsedModules(usePic, initialResultSet)) {
initialResultSet.add(usedModule);
usedModulePaths.add(usedModule.getExecPathString());
}
CcToolchainFeatures.Variables.Builder variableBuilder =
new CcToolchainFeatures.Variables.Builder();
variableBuilder.addSequenceVariable("module_files", usedModulePaths);
this.overwrittenVariables = variableBuilder.build();
initialResult = initialResultSet;
}
this.additionalInputs = initialResult;
// In some cases, execution backends need extra files for each included file. Add them
// to the set of inputs the caller may need to be aware of.
Collection<Artifact> result = new HashSet<>();
ArtifactResolver artifactResolver =
executor.getContext(IncludeScanningContext.class).getArtifactResolver();
for (Artifact artifact : initialResult) {
result.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver));
}
for (Artifact artifact : getInputs()) {
result.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver));
}
// TODO(ulfjack): This only works if include scanning is enabled; the cleanup is in progress,
// and this needs to be fixed before we can even consider disabling it.
resolvedInputs = ImmutableList.copyOf(result);
if (result.isEmpty()) {
result = initialResult;
} else {
result.addAll(initialResult);
}
return result;
}
@Override
public Artifact getPrimaryInput() {
return getSourceFile();
}
@Override
public Artifact getPrimaryOutput() {
return getOutputFile();
}
/**
* Returns the path of the c/cc source for gcc.
*/
public final Artifact getSourceFile() {
return cppCompileCommandLine.sourceFile;
}
/**
* Returns the path where gcc should put its result.
*/
public Artifact getOutputFile() {
return outputFile;
}
protected PathFragment getInternalOutputFile() {
return outputFile.getExecPath();
}
@Override
public Map<Artifact, Artifact> getLegalGeneratedScannerFileMap() {
Map<Artifact, Artifact> legalOuts = new HashMap<>();
for (Artifact a : context.getDeclaredIncludeSrcs()) {
if (!a.isSourceArtifact()) {
legalOuts.put(a, null);
}
}
for (Pair<Artifact, Artifact> pregreppedSrcs : context.getPregreppedHeaders()) {
Artifact hdr = pregreppedSrcs.getFirst();
Preconditions.checkState(!hdr.isSourceArtifact(), hdr);
legalOuts.put(hdr, pregreppedSrcs.getSecond());
}
return Collections.unmodifiableMap(legalOuts);
}
/**
* Returns the path where gcc should put the discovered dependency
* information.
*/
public DotdFile getDotdFile() {
return cppCompileCommandLine.dotdFile;
}
@VisibleForTesting
public CppCompilationContext getContext() {
return context;
}
@Override
public List<PathFragment> getQuoteIncludeDirs() {
return context.getQuoteIncludeDirs();
}
@Override
public List<PathFragment> getIncludeDirs() {
ImmutableList.Builder<PathFragment> result = ImmutableList.builder();
result.addAll(context.getIncludeDirs());
for (String opt : cppCompileCommandLine.copts) {
if (opt.startsWith("-I") && opt.length() > 2) {
// We insist on the combined form "-Idir".
result.add(new PathFragment(opt.substring(2)));
}
}
return result.build();
}
@Override
public List<PathFragment> getSystemIncludeDirs() {
// TODO(bazel-team): parsing the command line flags here couples us to gcc-style compiler
// command lines; use a different way to specify system includes (for example through a
// system_includes attribute in cc_toolchain); note that that would disallow users from
// specifying system include paths via the copts attribute.
// Currently, this works together with the include_paths features because getCommandLine() will
// get the system include paths from the CppCompilationContext instead.
ImmutableList.Builder<PathFragment> result = ImmutableList.builder();
List<String> compilerOptions = getCompilerOptions();
for (int i = 0; i < compilerOptions.size(); i++) {
String opt = compilerOptions.get(i);
if (opt.startsWith("-isystem")) {
if (opt.length() > 8) {
result.add(new PathFragment(opt.substring(8).trim()));
} else if (i + 1 < compilerOptions.size()) {
i++;
result.add(new PathFragment(compilerOptions.get(i)));
} else {
System.err.println("WARNING: dangling -isystem flag in options for " + prettyPrint());
}
}
}
return result.build();
}
@Override
public List<String> getCmdlineIncludes() {
ImmutableList.Builder<String> cmdlineIncludes = ImmutableList.builder();
List<String> args = getArgv();
for (Iterator<String> argi = args.iterator(); argi.hasNext();) {
String arg = argi.next();
if (arg.equals("-include") && argi.hasNext()) {
cmdlineIncludes.add(argi.next());
}
}
return cmdlineIncludes.build();
}
@Override
public Artifact getMainIncludeScannerSource() {
return CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath())
? Iterables.getFirst(context.getHeaderModuleSrcs(), null)
: getSourceFile();
}
@Override
public Collection<Artifact> getIncludeScannerSources() {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
if (CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath())) {
// If this is an action that compiles the header module itself, the source we build is the
// module map, and we need to include-scan all headers that are referenced in the module map.
// We need to do include scanning as long as we want to support building code bases that are
// not fully strict layering clean.
builder.addAll(context.getHeaderModuleSrcs());
} else {
builder.add(getSourceFile());
builder.addAll(additionalIncludeScannables);
}
return builder.build().toCollection();
}
@Override
public Iterable<IncludeScannable> getAuxiliaryScannables() {
return lipoScannables;
}
/**
* Returns the list of "-D" arguments that should be used by this gcc
* invocation. Only used for testing.
*/
@VisibleForTesting
public ImmutableCollection<String> getDefines() {
return context.getDefines();
}
@Override
public ImmutableMap<String, String> getEnvironment() {
Map<String, String> environment = new LinkedHashMap<>(configuration.getLocalShellEnvironment());
if (configuration.isCodeCoverageEnabled()) {
environment.put("PWD", "/proc/self/cwd");
}
environment.putAll(this.environment);
environment.putAll(cppCompileCommandLine.getEnvironment());
// TODO(bazel-team): Check (crosstool) host system name instead of using OS.getCurrent.
if (OS.getCurrent() == OS.WINDOWS) {
// TODO(bazel-team): Both GCC and clang rely on their execution directories being on
// PATH, otherwise they fail to find dependent DLLs (and they fail silently...). On
// the other hand, Windows documentation says that the directory of the executable
// is always searched for DLLs first. Not sure what to make of it.
// Other options are to forward the system path (brittle), or to add a PATH field to
// the crosstool file.
//
// @see com.google.devtools.build.lib.rules.cpp.CppLinkAction#getEnvironment
environment.put("PATH", cppConfiguration.getToolPathFragment(Tool.GCC).getParentDirectory()
.getPathString());
}
return ImmutableMap.copyOf(environment);
}
/**
* Returns a new, mutable list of command and arguments (argv) to be passed
* to the gcc subprocess.
*/
public final List<String> getArgv() {
return getArgv(getInternalOutputFile());
}
@Override
public List<String> getArguments() {
return getArgv();
}
protected final List<String> getArgv(PathFragment outputFile) {
return cppCompileCommandLine.getArgv(outputFile, overwrittenVariables);
}
@Override
public boolean extraActionCanAttach() {
return cppConfiguration.alwaysAttachExtraActions()
|| !specialInputsHandler.isSpecialFile(getPrimaryInput());
}
@Override
public ExtraActionInfo.Builder getExtraActionInfo() {
CppCompileInfo.Builder info = CppCompileInfo.newBuilder();
info.setTool(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString());
for (String option : getCompilerOptions()) {
info.addCompilerOption(option);
}
info.setOutputFile(outputFile.getExecPathString());
info.setSourceFile(getSourceFile().getExecPathString());
if (inputsKnown()) {
info.addAllSourcesAndHeaders(Artifact.toExecPaths(getInputs()));
} else {
info.addSourcesAndHeaders(getSourceFile().getExecPathString());
info.addAllSourcesAndHeaders(
Artifact.toExecPaths(context.getDeclaredIncludeSrcs()));
}
return super.getExtraActionInfo()
.setExtension(CppCompileInfo.cppCompileInfo, info.build());
}
/**
* Returns the compiler options.
*/
@VisibleForTesting
public List<String> getCompilerOptions() {
return cppCompileCommandLine.getCompilerOptions(/*updatedVariables=*/null);
}
@Override
public Map<String, String> getExecutionInfo() {
ImmutableMap.Builder<String, String> result = ImmutableMap.<String, String>builder();
for (String requirement : executionRequirements) {
result.put(requirement, "");
}
return result.build();
}
/**
* Enforce that the includes actually visited during the compile were properly
* declared in the rules.
*
* <p>The technique is to walk through all of the reported includes that gcc
* emits into the .d file, and verify that they came from acceptable
* relative include directories. This is done in two steps:
*
* <p>First, each included file is stripped of any include path prefix from
* {@code quoteIncludeDirs} to produce an effective relative include dir+name.
*
* <p>Second, the remaining directory is looked up in {@code declaredIncludeDirs},
* a list of acceptable dirs. This list contains a set of dir fragments that
* have been calculated by the configured target to be allowable for inclusion
* by this source. If no match is found, an error is reported and an exception
* is thrown.
*
* @throws ActionExecutionException iff there was an undeclared dependency
*/
@VisibleForTesting
public void validateInclusions(
Iterable<Artifact> inputsForValidation,
ArtifactExpander artifactExpander,
EventHandler eventHandler)
throws ActionExecutionException {
IncludeProblems errors = new IncludeProblems();
IncludeProblems warnings = new IncludeProblems();
Set<Artifact> allowedIncludes = new HashSet<>();
for (Artifact input : mandatoryInputs) {
if (input.isMiddlemanArtifact() || input.isTreeArtifact()) {
artifactExpander.expand(input, allowedIncludes);
}
allowedIncludes.add(input);
}
allowedIncludes.addAll(resolvedInputs);
if (optionalSourceFile != null) {
allowedIncludes.add(optionalSourceFile);
}
Iterable<PathFragment> ignoreDirs = getValidationIgnoredDirs();
// Copy the sets to hash sets for fast contains checking.
// Avoid immutable sets here to limit memory churn.
Set<PathFragment> declaredIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeDirs());
Set<PathFragment> warnIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeWarnDirs());
Set<Artifact> declaredIncludeSrcs = Sets.newHashSet(getDeclaredIncludeSrcs());
for (Artifact input : inputsForValidation) {
if (context.getTransitiveCompilationPrerequisites().contains(input)
|| allowedIncludes.contains(input)) {
continue; // ignore our fixed source in mandatoryInput: we just want includes
}
// Ignore headers from built-in include directories.
if (FileSystemUtils.startsWithAny(input.getExecPath(), ignoreDirs)) {
continue;
}
if (!isDeclaredIn(input, declaredIncludeDirs, declaredIncludeSrcs)) {
// This call can never match the declared include sources (they would be matched above).
// There are no declared include sources we need to warn about, so use an empty set here.
if (isDeclaredIn(input, warnIncludeDirs, ImmutableSet.<Artifact>of())) {
warnings.add(input.getPath().toString());
} else {
errors.add(input.getPath().toString());
}
}
}
if (VALIDATION_DEBUG_WARN) {
synchronized (System.err) {
if (VALIDATION_DEBUG >= 2 || errors.hasProblems() || warnings.hasProblems()) {
if (errors.hasProblems()) {
System.err.println("ERROR: Include(s) were not in declared srcs:");
} else if (warnings.hasProblems()) {
System.err.println("WARN: Include(s) were not in declared srcs:");
} else {
System.err.println("INFO: Include(s) were OK for '" + getSourceFile()
+ "', declared srcs:");
}
for (Artifact a : context.getDeclaredIncludeSrcs()) {
System.err.println(" '" + a.toDetailString() + "'");
}
System.err.println(" or under declared dirs:");
for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeDirs())) {
System.err.println(" '" + f + "'");
}
System.err.println(" or under declared warn dirs:");
for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeWarnDirs())) {
System.err.println(" '" + f + "'");
}
System.err.println(" with prefixes:");
for (PathFragment dirpath : context.getQuoteIncludeDirs()) {
System.err.println(" '" + dirpath + "'");
}
}
}
}
if (warnings.hasProblems()) {
eventHandler.handle(
Event.warn(
getOwner().getLocation(),
warnings.getMessage(this, getSourceFile()))
.withTag(Label.print(getOwner().getLabel())));
}
errors.assertProblemFree(this, getSourceFile());
}
private Iterable<PathFragment> getValidationIgnoredDirs() {
List<PathFragment> cxxSystemIncludeDirs = cppConfiguration.getBuiltInIncludeDirectories();
return Iterables.concat(
cxxSystemIncludeDirs, context.getSystemIncludeDirs());
}
/**
* Returns true if an included artifact is declared in a set of allowed
* include directories. The simple case is that the artifact's parent
* directory is contained in the set, or is empty.
*
* <p>This check also supports a wildcard suffix of '**' for the cases where the
* calculations are inexact.
*
* <p>It also handles unseen non-nested-package subdirs by walking up the path looking
* for matches.
*/
private static boolean isDeclaredIn(
Artifact input, Set<PathFragment> declaredIncludeDirs, Set<Artifact> declaredIncludeSrcs) {
// First check if it's listed in "srcs". If so, then its declared & OK.
if (declaredIncludeSrcs.contains(input)) {
return true;
}
// If it's a derived artifact, then it MUST be listed in "srcs" as checked above.
// We define derived here as being not source and not under the include link tree.
if (!input.isSourceArtifact()
&& !input.getRoot().getExecPath().getBaseName().equals("include")) {
return false;
}
// Need to do dir/package matching: first try a quick exact lookup.
PathFragment includeDir = input.getRootRelativePath().getParentDirectory();
if (includeDir.segmentCount() == 0 || declaredIncludeDirs.contains(includeDir)) {
return true; // OK: quick exact match.
}
// Not found in the quick lookup: try the wildcards.
for (PathFragment declared : declaredIncludeDirs) {
if (declared.getBaseName().equals("**")) {
if (includeDir.startsWith(declared.getParentDirectory())) {
return true; // OK: under a wildcard dir.
}
}
}
// Still not found: see if it is in a subdir of a declared package.
Path root = input.getRoot().getPath();
for (Path dir = input.getPath().getParentDirectory();;) {
if (dir.getRelative("BUILD").exists()) {
return false; // Bad: this is a sub-package, not a subdir of a declared package.
}
dir = dir.getParentDirectory();
if (dir.equals(root)) {
return false; // Bad: at the top, give up.
}
if (declaredIncludeDirs.contains(dir.relativeTo(root))) {
return true; // OK: found under a declared dir.
}
}
}
/**
* Recalculates this action's live input collection, including sources, middlemen.
*
* @throws ActionExecutionException iff any errors happen during update.
*/
@VisibleForTesting
@ThreadCompatible
public final synchronized void updateActionInputs(NestedSet<Artifact> discoveredInputs)
throws ActionExecutionException {
inputsKnown = false;
NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder();
Profiler.instance().startTask(ProfilerTask.ACTION_UPDATE, this);
try {
inputs.addTransitive(mandatoryInputs);
if (optionalSourceFile != null) {
inputs.add(optionalSourceFile);
}
inputs.addAll(context.getTransitiveCompilationPrerequisites());
inputs.addTransitive(discoveredInputs);
inputsKnown = true;
} finally {
Profiler.instance().completeTask(ProfilerTask.ACTION_UPDATE);
synchronized (this) {
setInputs(inputs.build());
}
}
}
private DependencySet processDepset(Path execRoot, CppCompileActionContext.Reply reply)
throws IOException {
DotdFile dotdFile = getDotdFile();
Preconditions.checkNotNull(dotdFile);
DependencySet depSet = new DependencySet(execRoot);
// artifact() is null if we are using in-memory .d files. We also want to prepare for the
// case where we expected an in-memory .d file, but we did not get an appropriate response.
// Perhaps we produced the file locally.
if (dotdFile.artifact() != null || reply == null) {
return depSet.read(dotdFile.getPath());
} else {
// This is an in-memory .d file.
return depSet.process(reply.getContents());
}
}
/**
* Returns a collection with additional input artifacts relevant to the action by reading the
* dynamically-discovered dependency information from the .d file after the action has run.
*
* <p>Artifacts are considered inputs but not "mandatory" inputs.
*
* @param reply the reply from the compilation.
* @throws ActionExecutionException iff the .d is missing (when required), malformed, or has
* unresolvable included artifacts.
*/
@VisibleForTesting
@ThreadCompatible
public NestedSet<Artifact> discoverInputsFromDotdFiles(
Path execRoot, ArtifactResolver artifactResolver, Reply reply)
throws ActionExecutionException {
NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder();
if (getDotdFile() == null) {
return inputs.build();
}
try {
// Read .d file.
DependencySet depSet = processDepset(execRoot, reply);
// Determine prefixes of allowed absolute inclusions.
CppConfiguration toolchain = cppConfiguration;
List<Path> systemIncludePrefixes = new ArrayList<>();
for (PathFragment includePath : toolchain.getBuiltInIncludeDirectories()) {
if (includePath.isAbsolute()) {
systemIncludePrefixes.add(execRoot.getFileSystem().getPath(includePath));
}
}
// Check inclusions.
IncludeProblems problems = new IncludeProblems();
Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap();
for (Path execPath : depSet.getDependencies()) {
// Module .pcm files are generated and thus aren't declared inputs.
if (execPath.getBaseName().endsWith(".pcm")) {
continue;
}
PathFragment execPathFragment = execPath.asFragment();
if (execPathFragment.isAbsolute()) {
// Absolute includes from system paths are ignored.
if (FileSystemUtils.startsWithAny(execPath, systemIncludePrefixes)) {
continue;
}
// Since gcc is given only relative paths on the command line,
// non-system include paths here should never be absolute. If they
// are, it's probably due to a non-hermetic #include, & we should stop
// the build with an error.
if (execPath.startsWith(execRoot)) {
execPathFragment = execPath.relativeTo(execRoot); // funky but tolerable path
} else {
problems.add(execPathFragment.getPathString());
continue;
}
}
Artifact artifact = allowedDerivedInputsMap.get(execPathFragment);
if (artifact == null) {
artifact = artifactResolver.resolveSourceArtifact(execPathFragment, RepositoryName.MAIN);
}
if (artifact != null) {
inputs.add(artifact);
// In some cases, execution backends need extra files for each included file. Add them
// to the set of actual inputs.
inputs.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver));
} else {
// Abort if we see files that we can't resolve, likely caused by
// undeclared includes or illegal include constructs.
problems.add(execPathFragment.getPathString());
}
}
//TODO(b/22551695): Remove in favor of seperate implementations.
if (semantics == null || semantics.needsIncludeValidation()) {
problems.assertProblemFree(this, getSourceFile());
}
} catch (IOException e) {
// Some kind of IO or parse exception--wrap & rethrow it to stop the build.
throw new ActionExecutionException("error while parsing .d file", e, this, false);
}
return inputs.build();
}
@Override
public Iterable<Artifact> resolveInputsFromCache(
ArtifactResolver artifactResolver,
PackageRootResolver resolver,
Collection<PathFragment> inputPaths)
throws PackageRootResolutionException, InterruptedException {
// Note that this method may trigger a violation of the desirable invariant that getInputs()
// is a superset of getMandatoryInputs(). See bug about an "action not in canonical form"
// error message and the integration test test_crosstool_change_and_failure().
Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap();
List<Artifact> inputs = new ArrayList<>();
List<PathFragment> unresolvedPaths = new ArrayList<>();
for (PathFragment execPath : inputPaths) {
Artifact artifact = allowedDerivedInputsMap.get(execPath);
if (artifact != null) {
inputs.add(artifact);
} else {
// Remember this execPath, we will try to resolve it as a source artifact.
unresolvedPaths.add(execPath);
}
}
Map<PathFragment, Artifact> resolvedArtifacts =
artifactResolver.resolveSourceArtifacts(unresolvedPaths, resolver);
if (resolvedArtifacts == null) {
// We are missing some dependencies. We need to rerun this update later.
return null;
}
for (PathFragment execPath : unresolvedPaths) {
Artifact artifact = resolvedArtifacts.get(execPath);
// If PathFragment cannot be resolved into the artifact - ignore it. This could happen if
// rule definition has changed and action no longer depends on, e.g., additional source file
// in the separate package and that package is no longer referenced anywhere else.
// It is safe to ignore such paths because dependency checker would identify change in inputs
// (ignored path was used before) and will force action execution.
if (artifact != null) {
inputs.add(artifact);
}
}
return inputs;
}
@Override
public synchronized void updateInputs(Iterable<Artifact> inputs) {
inputsKnown = true;
synchronized (this) {
setInputs(inputs);
}
}
private Map<PathFragment, Artifact> getAllowedDerivedInputsMap() {
Map<PathFragment, Artifact> allowedDerivedInputMap = new HashMap<>();
addToMap(allowedDerivedInputMap, mandatoryInputs);
addToMap(allowedDerivedInputMap, getDeclaredIncludeSrcs());
addToMap(allowedDerivedInputMap, context.getTransitiveCompilationPrerequisites());
Artifact artifact = getSourceFile();
if (!artifact.isSourceArtifact()) {
allowedDerivedInputMap.put(artifact.getExecPath(), artifact);
}
return allowedDerivedInputMap;
}
private void addToMap(Map<PathFragment, Artifact> map, Iterable<Artifact> artifacts) {
for (Artifact artifact : artifacts) {
if (!artifact.isSourceArtifact()) {
map.put(artifact.getExecPath(), artifact);
}
}
}
@Override
protected String getRawProgressMessage() {
return "Compiling " + getSourceFile().prettyPrint();
}
/**
* Return the directories in which to look for headers (pertains to headers
* not specifically listed in {@code declaredIncludeSrcs}). The return value
* may contain duplicate elements.
*/
public NestedSet<PathFragment> getDeclaredIncludeDirs() {
return context.getDeclaredIncludeDirs();
}
/**
* Return the directories in which to look for headers and issue a warning.
* (pertains to headers not specifically listed in {@code
* declaredIncludeSrcs}). The return value may contain duplicate elements.
*/
public NestedSet<PathFragment> getDeclaredIncludeWarnDirs() {
return context.getDeclaredIncludeWarnDirs();
}
/**
* Return explicit header files (i.e., header files explicitly listed). The
* return value may contain duplicate elements.
*/
@Override
public NestedSet<Artifact> getDeclaredIncludeSrcs() {
if (lipoScannables != null && lipoScannables.iterator().hasNext()) {
NestedSetBuilder<Artifact> srcs = NestedSetBuilder.stableOrder();
srcs.addTransitive(context.getDeclaredIncludeSrcs());
for (IncludeScannable lipoScannable : lipoScannables) {
srcs.addTransitive(lipoScannable.getDeclaredIncludeSrcs());
}
return srcs.build();
}
return context.getDeclaredIncludeSrcs();
}
@Override
public ResourceSet estimateResourceConsumption(Executor executor) {
return executor.getContext(actionContext).estimateResourceConsumption(this);
}
@VisibleForTesting
public Class<? extends CppCompileActionContext> getActionContext() {
return actionContext;
}
/**
* Estimate resource consumption when this action is executed locally.
*/
public ResourceSet estimateResourceConsumptionLocal() {
// We use a local compile, so much of the time is spent waiting for IO,
// but there is still significant CPU; hence we estimate 50% cpu usage.
return ResourceSet.createWithRamCpuIo(/*memoryMb=*/200, /*cpuUsage=*/0.5, /*ioUsage=*/0.0);
}
@Override
public String computeKey() {
Fingerprint f = new Fingerprint();
f.addUUID(actionClassId);
f.addStringMap(getEnvironment());
f.addStrings(getArgv());
f.addStrings(executionRequirements);
/*
* getArgv() above captures all changes which affect the compilation
* command and hence the contents of the object file. But we need to
* also make sure that we reexecute the action if any of the fields
* that affect whether validateIncludes() will report an error or warning
* have changed, otherwise we might miss some errors.
*/
f.addPaths(context.getDeclaredIncludeDirs());
f.addPaths(context.getDeclaredIncludeWarnDirs());
for (Artifact declaredIncludeSrc : context.getDeclaredIncludeSrcs()) {
f.addPath(declaredIncludeSrc.getExecPath());
}
f.addInt(0); // mark the boundary between input types
for (Artifact input : getMandatoryInputs()) {
f.addPath(input.getExecPath());
}
return f.hexDigestAndReset();
}
@Override
@ThreadCompatible
public void execute(
ActionExecutionContext actionExecutionContext)
throws ActionExecutionException, InterruptedException {
Executor executor = actionExecutionContext.getExecutor();
CppCompileActionContext.Reply reply;
try {
reply = executor.getContext(actionContext).execWithReply(this, actionExecutionContext);
} catch (ExecException e) {
throw e.toActionExecutionException("C++ compilation of rule '" + getOwner().getLabel() + "'",
executor.getVerboseFailures(), this);
}
ensureCoverageNotesFilesExist();
// This is the .d file scanning part.
IncludeScanningContext scanningContext = executor.getContext(IncludeScanningContext.class);
NestedSet<Artifact> discoveredInputs =
discoverInputsFromDotdFiles(
executor.getExecRoot(), scanningContext.getArtifactResolver(), reply);
reply = null; // Clear in-memory .d files early.
// Post-execute "include scanning", which modifies the action inputs to match what the compile
// action actually used by incorporating the results of .d file parsing.
//
// We enable this when "include scanning" itself is enabled, or when hdrs_check is set to loose
// or warn, as otherwise the action might be missing inputs that the compiler used and rebuilds
// become incorrect.
//
// Note that this effectively disables post-execute "include scanning" in Bazel, because
// hdrs_check is forced to "strict" and "include scanning" is forced to off.
boolean usesStrictHdrsChecks = context.getDeclaredIncludeDirs().isEmpty()
&& context.getDeclaredIncludeWarnDirs().isEmpty();
if (shouldScanIncludes() || !usesStrictHdrsChecks) {
updateActionInputs(discoveredInputs);
}
// hdrs_check: Turning this off opens the door to incorrect builds. However, we allow it
// to accommodate the current behavior in the objc rules.
if (semantics == null || semantics.needsIncludeValidation()) {
validateInclusions(
discoveredInputs,
actionExecutionContext.getArtifactExpander(),
executor.getEventHandler());
}
}
/**
* Gcc only creates ".gcno" files if the compilation unit is non-empty.
* To ensure that the set of outputs for a CppCompileAction remains consistent
* and doesn't vary dynamically depending on the _contents_ of the input files,
* we create empty ".gcno" files if gcc didn't create them.
*/
private void ensureCoverageNotesFilesExist() throws ActionExecutionException {
for (Artifact output : getOutputs()) {
if (CppFileTypes.COVERAGE_NOTES.matches(output.getFilename()) // ".gcno"
&& !output.getPath().exists()) {
try {
FileSystemUtils.createEmptyFile(output.getPath());
} catch (IOException e) {
throw new ActionExecutionException(
"Error creating file '" + output.getPath() + "': " + e.getMessage(), e, this, false);
}
}
}
}
/**
* Provides list of include files needed for performing extra actions on this action when run
* remotely. The list of include files is created by performing a header scan on the known input
* files.
*/
@Override
public Iterable<Artifact> getInputFilesForExtraAction(
ActionExecutionContext actionExecutionContext)
throws ActionExecutionException, InterruptedException {
Collection<Artifact> scannedIncludes =
actionExecutionContext.getExecutor().getContext(actionContext)
.getScannedIncludeFiles(this, actionExecutionContext);
// Use a set to eliminate duplicates.
ImmutableSet.Builder<Artifact> result = ImmutableSet.builder();
return result.addAll(getInputs()).addAll(scannedIncludes).build();
}
@Override
public String getMnemonic() { return "CppCompile"; }
@Override
public String describeKey() {
StringBuilder message = new StringBuilder();
message.append(getProgressMessage());
message.append('\n');
// Outputting one argument per line makes it easier to diff the results.
// The first element in getArgv() is actually the command to execute.
String legend = " Command: ";
for (String argument : ShellEscaper.escapeAll(getArgv())) {
message.append(legend);
message.append(argument);
message.append('\n');
legend = " Argument: ";
}
for (PathFragment path : context.getDeclaredIncludeDirs()) {
message.append(" Declared include directory: ");
message.append(ShellEscaper.escapeString(path.getPathString()));
message.append('\n');
}
for (Artifact src : getDeclaredIncludeSrcs()) {
message.append(" Declared include source: ");
message.append(ShellEscaper.escapeString(src.getExecPathString()));
message.append('\n');
}
return message.toString();
}
/**
* The compile command line for the enclosing C++ compile action.
*/
public final class CppCompileCommandLine {
private final Artifact sourceFile;
private final DotdFile dotdFile;
private final List<String> copts;
private final Predicate<String> coptsFilter;
private final Collection<String> features;
@VisibleForTesting public final CcToolchainFeatures.Variables variables;
private final String actionName;
public CppCompileCommandLine(
Artifact sourceFile,
DotdFile dotdFile,
ImmutableList<String> copts,
Predicate<String> coptsFilter,
Collection<String> features,
CcToolchainFeatures.Variables variables,
String actionName) {
this.sourceFile = Preconditions.checkNotNull(sourceFile);
this.dotdFile = CppFileTypes.mustProduceDotdFile(sourceFile.getPath().toString())
? Preconditions.checkNotNull(dotdFile) : null;
this.copts = Preconditions.checkNotNull(copts);
this.coptsFilter = coptsFilter;
this.features = Preconditions.checkNotNull(features);
this.variables = variables;
this.actionName = actionName;
}
/**
* Returns the environment variables that should be set for C++ compile actions.
*/
protected Map<String, String> getEnvironment() {
return featureConfiguration.getEnvironmentVariables(actionName, variables);
}
protected List<String> getArgv(
PathFragment outputFile, CcToolchainFeatures.Variables overwrittenVariables) {
List<String> commandLine = new ArrayList<>();
// first: The command name.
if (!featureConfiguration.actionIsConfigured(actionName)) {
commandLine.add(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString());
} else {
commandLine.add(
featureConfiguration
.getToolForAction(actionName)
.getToolPath(cppConfiguration.getCrosstoolTopPathFragment())
.getPathString());
}
// second: The compiler options.
commandLine.addAll(getCompilerOptions(overwrittenVariables));
if (!featureConfiguration.isEnabled("compile_action_flags_in_flag_set")) {
// third: The file to compile!
commandLine.add("-c");
commandLine.add(sourceFile.getExecPathString());
// finally: The output file. (Prefixed with -o).
commandLine.add("-o");
commandLine.add(outputFile.getPathString());
}
return commandLine;
}
public List<String> getCompilerOptions(
@Nullable CcToolchainFeatures.Variables overwrittenVariables) {
List<String> options = new ArrayList<>();
CppConfiguration toolchain = cppConfiguration;
addFilteredOptions(options, toolchain.getCompilerOptions(features));
String sourceFilename = sourceFile.getExecPathString();
if (CppFileTypes.C_SOURCE.matches(sourceFilename)) {
addFilteredOptions(options, toolchain.getCOptions());
}
if (CppFileTypes.CPP_SOURCE.matches(sourceFilename)
|| CppFileTypes.CPP_HEADER.matches(sourceFilename)
|| CppFileTypes.CPP_MODULE_MAP.matches(sourceFilename)
|| CppFileTypes.CLIF_INPUT_PROTO.matches(sourceFilename)) {
addFilteredOptions(options, toolchain.getCxxOptions(features));
}
// TODO(bazel-team): This needs to be before adding getUnfilteredCompilerOptions() and after
// adding the warning flags until all toolchains are migrated; currently toolchains use the
// unfiltered compiler options to inject include paths, which is superseded by the feature
// configuration; on the other hand toolchains switch off warnings for the layering check
// that will be re-added by the feature flags.
CcToolchainFeatures.Variables updatedVariables = variables;
if (overwrittenVariables != null) {
CcToolchainFeatures.Variables.Builder variablesBuilder =
new CcToolchainFeatures.Variables.Builder();
variablesBuilder.addAll(variables);
variablesBuilder.addAll(overwrittenVariables);
updatedVariables = variablesBuilder.build();
}
addFilteredOptions(
options, featureConfiguration.getCommandLine(actionName, updatedVariables));
// Users don't expect the explicit copts to be filtered by coptsFilter, add them verbatim.
// Make sure these are added after the options from the feature configuration, so that
// those options can be overriden.
options.addAll(copts);
// Unfiltered compiler options contain system include paths. These must be added after
// the user provided options, otherwise users adding include paths will not pick up their
// own include paths first.
options.addAll(toolchain.getUnfilteredCompilerOptions(features));
// Add the options of --per_file_copt, if the label or the base name of the source file
// matches the specified regular expression filter.
for (PerLabelOptions perLabelOptions : cppConfiguration.getPerFileCopts()) {
if ((sourceLabel != null && perLabelOptions.isIncluded(sourceLabel))
|| perLabelOptions.isIncluded(sourceFile)) {
options.addAll(perLabelOptions.getOptions());
}
}
if (!featureConfiguration.isEnabled("compile_action_flags_in_flag_set")) {
if (FileType.contains(outputFile, CppFileTypes.ASSEMBLER, CppFileTypes.PIC_ASSEMBLER)) {
options.add("-S");
} else if (FileType.contains(outputFile, CppFileTypes.PREPROCESSED_C,
CppFileTypes.PREPROCESSED_CPP, CppFileTypes.PIC_PREPROCESSED_C,
CppFileTypes.PIC_PREPROCESSED_CPP)) {
options.add("-E");
}
}
return options;
}
// For each option in 'in', add it to 'out' unless it is matched by the 'coptsFilter' regexp.
private void addFilteredOptions(List<String> out, List<String> in) {
Iterables.addAll(out, Iterables.filter(in, coptsFilter));
}
}
/**
* A reference to a .d file. There are two modes:
* <ol>
* <li>an Artifact that represents a real on-disk file
* <li>just an execPath that refers to a virtual .d file that is not written to disk
* </ol>
*/
public static class DotdFile {
private final Artifact artifact;
private final PathFragment execPath;
public DotdFile(Artifact artifact) {
this.artifact = artifact;
this.execPath = null;
}
public DotdFile(PathFragment execPath) {
this.artifact = null;
this.execPath = execPath;
}
/**
* @return the Artifact or null
*/
public Artifact artifact() {
return artifact;
}
/**
* @return Gets the execPath regardless of whether this is a real Artifact
*/
public PathFragment getSafeExecPath() {
return execPath == null ? artifact.getExecPath() : execPath;
}
/**
* @return the on-disk location of the .d file or null
*/
public Path getPath() {
return artifact.getPath();
}
}
}