blob: 7a9305a34dd21e653014064d553204d68de4cac6 [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.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RuleErrorConsumer;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
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.vfs.PathFragment;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
* Builder class to construct C++ compile actions.
*/
public class CppCompileActionBuilder {
public static final UUID GUID = UUID.fromString("97493805-894f-493a-be66-9a698f45c31d");
private final ActionOwner owner;
private boolean shareable;
private final BuildConfigurationValue configuration;
private CcToolchainFeatures.FeatureConfiguration featureConfiguration;
private CcToolchainVariables variables = CcToolchainVariables.EMPTY;
private Artifact sourceFile;
private final NestedSetBuilder<Artifact> mandatoryInputsBuilder;
private Artifact outputFile;
private Artifact dwoFile;
private Artifact ltoIndexingFile;
private Artifact dotdFile;
private Artifact diagnosticsFile;
private Artifact gcnoFile;
private CcCompilationContext ccCompilationContext = CcCompilationContext.EMPTY;
private final List<String> pluginOpts = new ArrayList<>();
private CoptsFilter coptsFilter = CoptsFilter.alwaysPasses();
private ImmutableList<PathFragment> extraSystemIncludePrefixes = ImmutableList.of();
private boolean usePic;
private UUID actionClassId = GUID;
private final CppConfiguration cppConfiguration;
private final ArrayList<Artifact> additionalIncludeScanningRoots;
private Boolean shouldScanIncludes;
private Map<String, String> executionInfo = new LinkedHashMap<>();
private CppSemantics cppSemantics;
private final CcToolchainProvider ccToolchain;
@Nullable private final Artifact grepIncludes;
private ActionEnvironment env;
private final boolean codeCoverageEnabled;
@Nullable private String actionName;
private ImmutableList<Artifact> builtinIncludeFiles;
private NestedSet<Artifact> inputsForInvalidation = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private NestedSet<Artifact> additionalPrunableHeaders =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private ImmutableList<PathFragment> builtinIncludeDirectories;
private ImmutableList<Artifact> additionalOutputs = ImmutableList.of();
// New fields need to be added to the copy constructor.
/** Creates a builder from a rule and configuration. */
public CppCompileActionBuilder(
ActionConstructionContext actionConstructionContext,
@Nullable Artifact grepIncludes,
CcToolchainProvider ccToolchain,
BuildConfigurationValue configuration) {
this.owner = actionConstructionContext.getActionOwner();
this.shareable = false;
this.configuration = configuration;
this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
this.mandatoryInputsBuilder = NestedSetBuilder.stableOrder();
this.additionalIncludeScanningRoots = new ArrayList<>();
this.env = configuration.getActionEnvironment();
this.codeCoverageEnabled = configuration.isCodeCoverageEnabled();
this.ccToolchain = ccToolchain;
this.builtinIncludeDirectories = ccToolchain.getBuiltInIncludeDirectories();
this.grepIncludes = grepIncludes;
}
/**
* Creates a builder that is a copy of another builder.
*/
public CppCompileActionBuilder(CppCompileActionBuilder other) {
this.owner = other.owner;
this.shareable = other.shareable;
this.featureConfiguration = other.featureConfiguration;
this.sourceFile = other.sourceFile;
this.mandatoryInputsBuilder = NestedSetBuilder.<Artifact>stableOrder()
.addTransitive(other.mandatoryInputsBuilder.build());
this.inputsForInvalidation = other.inputsForInvalidation;
this.additionalIncludeScanningRoots = new ArrayList<>();
this.additionalIncludeScanningRoots.addAll(other.additionalIncludeScanningRoots);
this.outputFile = other.outputFile;
this.dwoFile = other.dwoFile;
this.ltoIndexingFile = other.ltoIndexingFile;
this.dotdFile = other.dotdFile;
this.gcnoFile = other.gcnoFile;
this.ccCompilationContext = other.ccCompilationContext;
this.pluginOpts.addAll(other.pluginOpts);
this.coptsFilter = other.coptsFilter;
this.extraSystemIncludePrefixes = ImmutableList.copyOf(other.extraSystemIncludePrefixes);
this.actionClassId = other.actionClassId;
this.cppConfiguration = other.cppConfiguration;
this.configuration = other.configuration;
this.usePic = other.usePic;
this.shouldScanIncludes = other.shouldScanIncludes;
this.executionInfo = new LinkedHashMap<>(other.executionInfo);
this.env = other.env;
this.codeCoverageEnabled = other.codeCoverageEnabled;
this.cppSemantics = other.cppSemantics;
this.ccToolchain = other.ccToolchain;
this.actionName = other.actionName;
this.grepIncludes = other.grepIncludes;
this.builtinIncludeDirectories = other.builtinIncludeDirectories;
this.additionalOutputs = ImmutableList.copyOf(other.additionalOutputs);
}
public CppCompileActionBuilder setSourceFile(Artifact sourceFile) {
Preconditions.checkState(
this.sourceFile == null,
"New source file %s trying to overwrite old source file %s",
sourceFile,
this.sourceFile);
return setSourceFileUnchecked(sourceFile);
}
public CppCompileActionBuilder setSourceFile(Artifact.TreeFileArtifact sourceFile) {
Preconditions.checkState(
!(this.sourceFile instanceof Artifact.TreeFileArtifact),
"New source file %s trying to overwrite old source file %s also a tree file artifact",
sourceFile,
this.sourceFile);
return setSourceFileUnchecked(sourceFile);
}
@CanIgnoreReturnValue
private CppCompileActionBuilder setSourceFileUnchecked(Artifact sourceFile) {
this.sourceFile = sourceFile;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setAdditionalOutputs(ImmutableList<Artifact> additionalOutputs) {
this.additionalOutputs = additionalOutputs;
return this;
}
public Artifact getSourceFile() {
return sourceFile;
}
public CcCompilationContext getCcCompilationContext() {
return ccCompilationContext;
}
public NestedSet<Artifact> getMandatoryInputs() {
return mandatoryInputsBuilder.build();
}
public String getActionName() {
if (actionName != null) {
return actionName;
}
PathFragment sourcePath = sourceFile.getExecPath();
if (CppFileTypes.CPP_MODULE_MAP.matches(sourcePath)) {
return CppActionNames.CPP_MODULE_COMPILE;
} else if (CppFileTypes.CPP_HEADER.matches(sourcePath)) {
// TODO(bazel-team): Handle C headers that probably don't work in C++ mode.
if (featureConfiguration.isEnabled(CppRuleClasses.PARSE_HEADERS)) {
return CppActionNames.CPP_HEADER_PARSING;
}
// CcCommon.collectCAndCppSources() ensures we do not add headers to
// the compilation artifacts unless 'parse_headers' is set.
throw new IllegalStateException();
} else if (CppFileTypes.C_SOURCE.matches(sourcePath)) {
return CppActionNames.C_COMPILE;
} else if (CppFileTypes.CPP_SOURCE.matches(sourcePath)) {
return CppActionNames.CPP_COMPILE;
} else if (CppFileTypes.OBJC_SOURCE.matches(sourcePath)) {
return CppActionNames.OBJC_COMPILE;
} else if (CppFileTypes.OBJCPP_SOURCE.matches(sourcePath)) {
return CppActionNames.OBJCPP_COMPILE;
} else if (CppFileTypes.ASSEMBLER.matches(sourcePath)) {
return CppActionNames.ASSEMBLE;
} else if (CppFileTypes.ASSEMBLER_WITH_C_PREPROCESSOR.matches(sourcePath)) {
return CppActionNames.PREPROCESS_ASSEMBLE;
} else if (CppFileTypes.CLIF_INPUT_PROTO.matches(sourcePath)) {
return CppActionNames.CLIF_MATCH;
} else if (CppFileTypes.CPP_MODULE.matches(sourcePath)) {
return CppActionNames.CPP_MODULE_CODEGEN;
}
// CcCompilationHelper ensures CppCompileAction only gets instantiated for supported file types.
throw new IllegalStateException();
}
/**
* Builds the Action as configured and performs some validations on the action. Uses {@link
* RuleContext#throwWithRuleError(String)} to report errors. Prefer this method over {@link
* CppCompileActionBuilder#buildOrThrowIllegalStateException()} whenever possible (meaning
* whenever you have access to {@link RuleContext}).
*
* <p>This method may be called multiple times to create multiple compile actions (usually after
* calling some setters to modify the generated action).
*/
CppCompileAction buildOrThrowRuleError(RuleErrorConsumer ruleErrorConsumer)
throws RuleErrorException {
try {
return buildAndVerify();
} catch (UnconfiguredActionConfigException e) {
throw ruleErrorConsumer.throwWithRuleError(e.getMessage());
}
}
/**
* Builds the Action as configured and performs some validations on the action. Throws {@link
* IllegalStateException} to report errors. Prefer {@link
* CppCompileActionBuilder#buildOrThrowRuleError(RuleErrorConsumer)} over this method whenever
* possible (meaning whenever you have access to {@link RuleContext}).
*
* <p>This method may be called multiple times to create multiple compile actions (usually after
* calling some setters to modify the generated action).
*/
public CppCompileAction buildOrThrowIllegalStateException() {
try {
return buildAndVerify();
} catch (UnconfiguredActionConfigException e) {
throw new IllegalStateException(e);
}
}
static final class UnconfiguredActionConfigException extends Exception {
private UnconfiguredActionConfigException(String actionName) {
super(String.format("Expected action_config for '%s' to be configured", actionName));
}
}
/**
* Builds the Action as configured and performs some validations on the action. Uses given {@link
* Consumer} to collect validation errors.
*/
public CppCompileAction buildAndVerify() throws UnconfiguredActionConfigException {
// This must be set either to false or true by CppSemantics, otherwise someone forgot to call
// finalizeCompileActionBuilder on this builder.
Preconditions.checkNotNull(shouldScanIncludes);
Preconditions.checkNotNull(featureConfiguration);
boolean useHeaderModules = useHeaderModules();
String actionName = getActionName();
if (featureConfiguration.actionIsConfigured(actionName)) {
for (String executionRequirement :
featureConfiguration.getToolRequirementsForAction(actionName)) {
executionInfo.put(executionRequirement, "");
}
} else {
throw new UnconfiguredActionConfigException(actionName);
}
NestedSet<Artifact> realMandatoryInputs = buildMandatoryInputs();
NestedSet<Artifact> prunableHeaders = additionalPrunableHeaders;
configuration.modifyExecutionInfo(
executionInfo,
CppCompileAction.actionNameToMnemonic(
actionName, featureConfiguration, cppConfiguration.useCppCompileHeaderMnemonic()));
// Copying the collections is needed to make the builder reusable.
CppCompileAction action;
action =
new CppCompileAction(
owner,
featureConfiguration,
variables,
sourceFile,
cppConfiguration,
shareable,
shouldScanIncludes,
usePic,
useHeaderModules,
realMandatoryInputs,
buildInputsForInvalidation(),
getBuiltinIncludeFiles(),
prunableHeaders,
outputFile,
dotdFile,
diagnosticsFile,
gcnoFile,
dwoFile,
ltoIndexingFile,
env,
ccCompilationContext,
coptsFilter,
ImmutableList.copyOf(additionalIncludeScanningRoots),
actionClassId,
ImmutableMap.copyOf(executionInfo),
actionName,
cppSemantics,
builtinIncludeDirectories,
grepIncludes,
additionalOutputs);
return action;
}
private ImmutableList<Artifact> getBuiltinIncludeFiles() {
ImmutableList.Builder<Artifact> result = ImmutableList.builder();
result.addAll(ccToolchain.getBuiltinIncludeFiles(cppConfiguration));
if (builtinIncludeFiles != null) {
result.addAll(builtinIncludeFiles);
}
return result.build();
}
/**
* Returns the list of mandatory inputs for the {@link CppCompileAction} as configured.
*/
NestedSet<Artifact> buildMandatoryInputs() {
NestedSetBuilder<Artifact> realMandatoryInputsBuilder = NestedSetBuilder.compileOrder();
realMandatoryInputsBuilder.addTransitive(mandatoryInputsBuilder.build());
realMandatoryInputsBuilder.addAll(getBuiltinIncludeFiles());
if (useHeaderModules() && !shouldScanIncludes) {
realMandatoryInputsBuilder.addTransitive(ccCompilationContext.getTransitiveModules(usePic));
}
ccCompilationContext.addAdditionalInputs(realMandatoryInputsBuilder);
realMandatoryInputsBuilder.add(Preconditions.checkNotNull(sourceFile));
if (grepIncludes != null) {
realMandatoryInputsBuilder.add(grepIncludes);
}
if (!shouldScanIncludes && dotdFile == null) {
realMandatoryInputsBuilder.addTransitive(ccCompilationContext.getDeclaredIncludeSrcs());
realMandatoryInputsBuilder.addTransitive(additionalPrunableHeaders);
}
return realMandatoryInputsBuilder.build();
}
NestedSet<Artifact> getPrunableHeaders() {
return additionalPrunableHeaders;
}
NestedSet<Artifact> buildInputsForInvalidation() {
return NestedSetBuilder.<Artifact>stableOrder()
.addTransitive(this.inputsForInvalidation)
.addTransitive(ccCompilationContext.getTransitiveCompilationPrerequisites())
.build();
}
private boolean useHeaderModules(Artifact sourceFile) {
Preconditions.checkNotNull(featureConfiguration);
Preconditions.checkNotNull(sourceFile);
return featureConfiguration.isEnabled(CppRuleClasses.USE_HEADER_MODULES)
&& (sourceFile.isFileType(CppFileTypes.CPP_SOURCE)
|| sourceFile.isFileType(CppFileTypes.CPP_HEADER)
|| sourceFile.isFileType(CppFileTypes.CPP_MODULE_MAP));
}
private boolean useHeaderModules() {
return useHeaderModules(sourceFile);
}
/**
* Set action name that is used to pick the right action_config and features from {@link
* FeatureConfiguration}. By default the action name is decided from the source filetype.
*/
@CanIgnoreReturnValue
public CppCompileActionBuilder setActionName(String actionName) {
Preconditions.checkState(
this.actionName == null,
"New actionName %s trying to overwrite old name %s",
actionName,
this.actionName);
this.actionName = actionName;
return this;
}
/** Sets the feature configuration to be used for the action. */
@CanIgnoreReturnValue
public CppCompileActionBuilder setFeatureConfiguration(
FeatureConfiguration featureConfiguration) {
Preconditions.checkNotNull(featureConfiguration);
this.featureConfiguration = featureConfiguration;
return this;
}
FeatureConfiguration getFeatureConfiguration() {
return featureConfiguration;
}
/** Sets the feature build variables to be used for the action. */
@CanIgnoreReturnValue
public CppCompileActionBuilder setVariables(CcToolchainVariables variables) {
this.variables = variables;
return this;
}
/** Returns the build variables to be used for the action. */
public CcToolchainVariables getVariables() {
return variables;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder addExecutionInfo(Map<String, String> executionInfo) {
this.executionInfo.putAll(executionInfo);
return this;
}
Map<String, String> getExecutionInfo() {
return executionInfo;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setActionClassId(UUID uuid) {
this.actionClassId = uuid;
return this;
}
UUID getActionClassId() {
return actionClassId;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder addMandatoryInputs(NestedSet<Artifact> artifacts) {
mandatoryInputsBuilder.addTransitive(artifacts);
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder addMandatoryInputs(List<Artifact> artifacts) {
mandatoryInputsBuilder.addAll(artifacts);
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder addTransitiveMandatoryInputs(NestedSet<Artifact> artifacts) {
mandatoryInputsBuilder.addTransitive(artifacts);
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder addAdditionalIncludeScanningRoots(
List<Artifact> additionalIncludeScanningRoots) {
this.additionalIncludeScanningRoots.addAll(additionalIncludeScanningRoots);
return this;
}
public boolean useDotdFile(Artifact sourceFile) {
return CppFileTypes.headerDiscoveryRequired(sourceFile) && !useHeaderModules(sourceFile);
}
public boolean dotdFilesEnabled() {
return cppSemantics.needsDotdInputPruning(configuration)
&& !featureConfiguration.isEnabled(CppRuleClasses.PARSE_SHOWINCLUDES);
}
public boolean serializedDiagnosticsFilesEnabled() {
return featureConfiguration.isEnabled(CppRuleClasses.SERIALIZED_DIAGNOSTICS_FILE);
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setOutputs(
Artifact outputFile, Artifact dotdFile, Artifact diagnosticsFile) {
this.outputFile = outputFile;
this.dotdFile = dotdFile;
this.diagnosticsFile = diagnosticsFile;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setOutputs(
ActionConstructionContext actionConstructionContext,
RuleErrorConsumer ruleErrorConsumer,
Label label,
ArtifactCategory outputCategory,
String outputName)
throws RuleErrorException {
this.outputFile =
CppHelper.getCompileOutputArtifact(
actionConstructionContext,
label,
CppHelper.getArtifactNameForCategory(ccToolchain, outputCategory, outputName),
configuration);
if (dotdFilesEnabled() && useDotdFile(sourceFile)) {
String dotdFileName = CppHelper.getDotdFileName(ccToolchain, outputCategory, outputName);
dotdFile =
CppHelper.getCompileOutputArtifact(
actionConstructionContext, label, dotdFileName, configuration);
} else {
dotdFile = null;
}
if (serializedDiagnosticsFilesEnabled()) {
String diagnosticsFileName =
CppHelper.getDiagnosticsFileName(ccToolchain, outputCategory, outputName);
diagnosticsFile =
CppHelper.getCompileOutputArtifact(
actionConstructionContext, label, diagnosticsFileName, configuration);
} else {
diagnosticsFile = null;
}
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setDwoFile(Artifact dwoFile) {
this.dwoFile = dwoFile;
return this;
}
/**
* Set the minimized bitcode file emitted by this (ThinLTO) compilation that can be used in place
* of the full bitcode outputFile in the LTO indexing step.
*/
@CanIgnoreReturnValue
public CppCompileActionBuilder setLtoIndexingFile(Artifact ltoIndexingFile) {
this.ltoIndexingFile = ltoIndexingFile;
return this;
}
public Artifact getOutputFile() {
return outputFile;
}
public Artifact getDotdFile() {
return this.dotdFile;
}
public Artifact getDiagnosticsFile() {
return this.diagnosticsFile;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setGcnoFile(Artifact gcnoFile) {
this.gcnoFile = gcnoFile;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setCcCompilationContext(
CcCompilationContext ccCompilationContext) {
this.ccCompilationContext = ccCompilationContext;
return this;
}
/** Sets whether the CompileAction should use pic mode. */
@CanIgnoreReturnValue
public CppCompileActionBuilder setPicMode(boolean usePic) {
this.usePic = usePic;
return this;
}
/** Sets the CppSemantics for this compile. */
@CanIgnoreReturnValue
public CppCompileActionBuilder setSemantics(CppSemantics semantics) {
this.cppSemantics = semantics;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setShareable(boolean shareable) {
this.shareable = shareable;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setShouldScanIncludes(boolean shouldScanIncludes) {
this.shouldScanIncludes = shouldScanIncludes;
return this;
}
public boolean getShouldScanIncludes() {
return shouldScanIncludes;
}
public CcToolchainProvider getToolchain() {
return ccToolchain;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setCoptsFilter(CoptsFilter coptsFilter) {
this.coptsFilter = Preconditions.checkNotNull(coptsFilter);
return this;
}
CoptsFilter getCoptsFilter() {
return coptsFilter;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setBuiltinIncludeFiles(
ImmutableList<Artifact> builtinIncludeFiles) {
this.builtinIncludeFiles = builtinIncludeFiles;
return this;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setInputsForInvalidation(
NestedSet<Artifact> inputsForInvalidation) {
this.inputsForInvalidation = inputsForInvalidation;
return this;
}
public PathFragment getRealOutputFilePath() {
return getOutputFile().getExecPath();
}
/** Do not use! This method is only intended for testing. */
@CanIgnoreReturnValue
@VisibleForTesting
public CppCompileActionBuilder setActionEnvironment(ActionEnvironment env) {
this.env = env;
return this;
}
ActionEnvironment getActionEnvironment() {
return env;
}
@CanIgnoreReturnValue
public CppCompileActionBuilder setAdditionalPrunableHeaders(
NestedSet<Artifact> additionalPrunableHeaders) {
this.additionalPrunableHeaders = Preconditions.checkNotNull(additionalPrunableHeaders);
return this;
}
@CanIgnoreReturnValue
@VisibleForTesting
public CppCompileActionBuilder setBuiltinIncludeDirectories(
ImmutableList<PathFragment> builtinIncludeDirectories) {
this.builtinIncludeDirectories = builtinIncludeDirectories;
return this;
}
ImmutableList<PathFragment> getBuiltinIncludeDirectories() {
return builtinIncludeDirectories;
}
public boolean shouldCompileHeaders() {
Preconditions.checkNotNull(featureConfiguration);
return ccToolchain.shouldProcessHeaders(featureConfiguration, cppConfiguration);
}
@Nullable
public Artifact getGrepIncludes() {
return grepIncludes;
}
}