blob: 940f88add75003ed0443240a8e97818d3ccd3441 [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.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.MiddlemanFactory;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
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.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.cpp.IncludeScanner.IncludeScanningHeaderData;
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.skylarkbuildapi.cpp.CcCompilationContextApi;
import com.google.devtools.build.lib.syntax.Depset;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunction.Environment;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Immutable store of information needed for C++ compilation that is aggregated across dependencies.
*/
@Immutable
@AutoCodec
public final class CcCompilationContext implements CcCompilationContextApi {
/** An empty {@code CcCompilationContext}. */
public static final CcCompilationContext EMPTY =
builder(/* actionConstructionContext= */ null, /* configuration= */ null, /* label= */ null)
.build();
private final CommandLineCcCompilationContext commandLineCcCompilationContext;
private final NestedSet<PathFragment> declaredIncludeDirs;
private final NestedSet<Artifact> declaredIncludeSrcs;
/** Module maps from direct dependencies. */
private final ImmutableList<Artifact> directModuleMaps;
/** Non-code mandatory compilation inputs. */
private final NestedSet<Artifact> nonCodeInputs;
private final HeaderInfo headerInfo;
private final NestedSet<HeaderInfo> transitiveHeaderInfos;
private final NestedSet<Artifact> transitiveModules;
private final NestedSet<Artifact> transitivePicModules;
private final CppModuleMap cppModuleMap;
private final CppModuleMap verificationModuleMap;
private final boolean propagateModuleMapAsActionInput;
// Derived from depsContexts.
private final NestedSet<Artifact> compilationPrerequisites;
private final CppConfiguration.HeadersCheckingMode headersCheckingMode;
// Each pair maps the Bazel generated paths of virtual include headers back to their original path
// relative to the workspace directory.
// For example it can map
// "bazel-out/k8-fastbuild/bin/include/common/_virtual_includes/strategy/strategy.h"
// back to the path of the header in the workspace directory "include/common/strategy.h".
// This is needed only when code coverage collection is enabled, to report the actual source file
// name in the coverage output file.
private final NestedSet<Pair<String, String>> virtualToOriginalHeaders;
@AutoCodec.Instantiator
@VisibleForSerialization
CcCompilationContext(
CommandLineCcCompilationContext commandLineCcCompilationContext,
NestedSet<Artifact> compilationPrerequisites,
NestedSet<PathFragment> declaredIncludeDirs,
NestedSet<Artifact> declaredIncludeSrcs,
NestedSet<Artifact> nonCodeInputs,
HeaderInfo headerInfo,
NestedSet<HeaderInfo> transitiveHeaderInfos,
NestedSet<Artifact> transitiveModules,
NestedSet<Artifact> transitivePicModules,
ImmutableList<Artifact> directModuleMaps,
CppModuleMap cppModuleMap,
@Nullable CppModuleMap verificationModuleMap,
boolean propagateModuleMapAsActionInput,
CppConfiguration.HeadersCheckingMode headersCheckingMode,
NestedSet<Pair<String, String>> virtualToOriginalHeaders) {
Preconditions.checkNotNull(commandLineCcCompilationContext);
this.commandLineCcCompilationContext = commandLineCcCompilationContext;
this.declaredIncludeDirs = declaredIncludeDirs;
this.declaredIncludeSrcs = declaredIncludeSrcs;
this.directModuleMaps = directModuleMaps;
this.headerInfo = headerInfo;
this.transitiveHeaderInfos = transitiveHeaderInfos;
this.transitiveModules = transitiveModules;
this.transitivePicModules = transitivePicModules;
this.cppModuleMap = cppModuleMap;
this.nonCodeInputs = nonCodeInputs;
this.verificationModuleMap = verificationModuleMap;
this.compilationPrerequisites = compilationPrerequisites;
this.propagateModuleMapAsActionInput = propagateModuleMapAsActionInput;
this.headersCheckingMode = headersCheckingMode;
this.virtualToOriginalHeaders = virtualToOriginalHeaders;
}
@Override
public Depset getSkylarkDefines() {
return Depset.of(SkylarkType.STRING, getDefines());
}
@Override
public Depset getSkylarkNonTransitiveDefines() {
return Depset.of(
SkylarkType.STRING, NestedSetBuilder.wrap(Order.STABLE_ORDER, getNonTransitiveDefines()));
}
@Override
public Depset getSkylarkHeaders() {
return Depset.of(Artifact.TYPE, getDeclaredIncludeSrcs());
}
@Override
public Depset getSkylarkSystemIncludeDirs() {
return Depset.of(
SkylarkType.STRING,
NestedSetBuilder.wrap(
Order.STABLE_ORDER,
getSystemIncludeDirs().stream()
.map(PathFragment::getSafePathString)
.collect(ImmutableList.toImmutableList())));
}
@Override
public Depset getSkylarkFrameworkIncludeDirs() {
return Depset.of(
SkylarkType.STRING,
NestedSetBuilder.wrap(
Order.STABLE_ORDER,
getFrameworkIncludeDirs().stream()
.map(PathFragment::getSafePathString)
.collect(ImmutableList.toImmutableList())));
}
@Override
public Depset getSkylarkIncludeDirs() {
return Depset.of(
SkylarkType.STRING,
NestedSetBuilder.wrap(
Order.STABLE_ORDER,
getIncludeDirs().stream()
.map(PathFragment::getSafePathString)
.collect(ImmutableList.toImmutableList())));
}
@Override
public Depset getSkylarkQuoteIncludeDirs() {
return Depset.of(
SkylarkType.STRING,
NestedSetBuilder.wrap(
Order.STABLE_ORDER,
getQuoteIncludeDirs().stream()
.map(PathFragment::getSafePathString)
.collect(ImmutableList.toImmutableList())));
}
/**
* Returns the transitive compilation prerequisites consolidated into middlemen prerequisites, or
* an empty set if there are no prerequisites.
*
* <p>Transitive compilation prerequisites are the prerequisites that will be needed by all
* reverse dependencies; note that these do specifically not include any compilation prerequisites
* that are only needed by the rule itself (for example, compiled source files from the {@code
* srcs} attribute).
*
* <p>To reduce the number of edges in the action graph, we express the dependency on compilation
* prerequisites as a transitive dependency via a middleman. After they have been accumulated
* ({@link Builder#mergeDependentCcCompilationContext(CcCompilationContext)}, and {@link
* Builder#mergeDependentCcCompilationContexts(Iterable)}, they are consolidated into a single
* middleman Artifact when {@link Builder#build()} is called.
*
* <p>The returned set can be empty if there are no prerequisites. Usually, it contains a single
* middleman.
*/
public NestedSet<Artifact> getTransitiveCompilationPrerequisites() {
return compilationPrerequisites;
}
/**
* Returns the immutable list of include directories to be added with "-I" (possibly empty but
* never null). This includes the include dirs from the transitive deps closure of the target.
* This list does not contain duplicates. All fragments are either absolute or relative to the
* exec root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}).
*/
public ImmutableList<PathFragment> getIncludeDirs() {
return commandLineCcCompilationContext.includeDirs;
}
/**
* Returns the immutable list of include directories to be added with "-iquote" (possibly empty
* but never null). This includes the include dirs from the transitive deps closure of the target.
* This list does not contain duplicates. All fragments are either absolute or relative to the
* exec root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}).
*/
public ImmutableList<PathFragment> getQuoteIncludeDirs() {
return commandLineCcCompilationContext.quoteIncludeDirs;
}
/**
* Returns the immutable list of include directories to be added with "-isystem" (possibly empty
* but never null). This includes the include dirs from the transitive deps closure of the target.
* This list does not contain duplicates. All fragments are either absolute or relative to the
* exec root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}).
*/
public ImmutableList<PathFragment> getSystemIncludeDirs() {
return commandLineCcCompilationContext.systemIncludeDirs;
}
/**
* Returns the immutable list of include directories to be added with "-F" (possibly empty but
* never null). This includes the include dirs from the transitive deps closure of the target.
* This list does not contain duplicates. All fragments are either absolute or relative to the
* exec root (see {@link com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot}).
*/
public ImmutableList<PathFragment> getFrameworkIncludeDirs() {
return commandLineCcCompilationContext.frameworkIncludeDirs;
}
/**
* Returns the immutable set of declared include directories, relative to a "-I" or "-iquote"
* directory" (possibly empty but never null).
*/
public NestedSet<PathFragment> getDeclaredIncludeDirs() {
return declaredIncludeDirs;
}
/**
* Returns the immutable set of headers that have been declared in the {@code srcs} or {@code
* hdrs} attribute (possibly empty but never null).
*/
public NestedSet<Artifact> getDeclaredIncludeSrcs() {
return declaredIncludeSrcs;
}
/** Returns headers given as textual_hdrs in this target. */
public Iterable<Artifact> getTextualHdrs() {
return headerInfo.textualHeaders;
}
public ImmutableList<HeaderInfo> getTransitiveHeaderInfos() {
return transitiveHeaderInfos.toList();
}
/** Helper class for creating include scanning header data. */
public static class IncludeScanningHeaderDataHelper {
private IncludeScanningHeaderDataHelper() {}
public static void handleArtifact(
Artifact artifact,
Map<PathFragment, Artifact> pathToLegalOutputArtifact,
ArrayList<Artifact> treeArtifacts) {
if (artifact.isSourceArtifact()) {
return;
}
if (artifact.isTreeArtifact()) {
treeArtifacts.add(artifact);
return;
}
pathToLegalOutputArtifact.put(artifact.getExecPath(), artifact);
}
/**
* Enter the TreeArtifactValues in each TreeArtifact into pathToLegalOutputArtifact. Returns
* true on success.
*
* <p>If a TreeArtifact's value is missing, returns false, and leave pathToLegalOutputArtifact
* unmodified.
*/
public static boolean handleTreeArtifacts(
Environment env,
Map<PathFragment, Artifact> pathToLegalOutputArtifact,
ArrayList<Artifact> treeArtifacts)
throws InterruptedException {
if (!treeArtifacts.isEmpty()) {
Map<SkyKey, SkyValue> valueMap = env.getValues(treeArtifacts);
if (env.valuesMissing()) {
return false;
}
for (SkyValue value : valueMap.values()) {
Preconditions.checkState(value instanceof TreeArtifactValue);
TreeArtifactValue treeArtifactValue = (TreeArtifactValue) value;
for (TreeFileArtifact treeFileArtifact : treeArtifactValue.getChildren()) {
pathToLegalOutputArtifact.put(treeFileArtifact.getExecPath(), treeFileArtifact);
}
}
}
return true;
}
}
/**
* This method returns null when a required SkyValue is missing and a Skyframe restart is
* required.
*/
@Nullable
public IncludeScanningHeaderData.Builder createIncludeScanningHeaderData(
Environment env,
boolean usePic,
boolean createModularHeaders,
List<HeaderInfo> transitiveHeaderInfoList)
throws InterruptedException {
ArrayList<Artifact> treeArtifacts = new ArrayList<>();
// We'd prefer for these types to use ImmutableSet/ImmutableMap. However, constructing these is
// substantially more costly in a way that shows up in profiles.
Map<PathFragment, Artifact> pathToLegalOutputArtifact = new HashMap<>();
Set<Artifact> modularHeaders =
CompactHashSet.createWithExpectedSize(transitiveHeaderInfoList.size());
// Not using range-based for loops here and below as the additional overhead of the
// ImmutableList iterators has shown up in profiles.
for (int c = 0; c < transitiveHeaderInfoList.size(); c++) {
HeaderInfo transitiveHeaderInfo = transitiveHeaderInfoList.get(c);
boolean isModule = createModularHeaders && transitiveHeaderInfo.getModule(usePic) != null;
for (int i = 0; i < transitiveHeaderInfo.modularHeaders.size(); i++) {
Artifact a = transitiveHeaderInfo.modularHeaders.get(i);
IncludeScanningHeaderDataHelper.handleArtifact(a, pathToLegalOutputArtifact, treeArtifacts);
if (isModule) {
modularHeaders.add(a);
}
}
for (int i = 0; i < transitiveHeaderInfo.textualHeaders.size(); i++) {
Artifact a = transitiveHeaderInfo.textualHeaders.get(i);
IncludeScanningHeaderDataHelper.handleArtifact(a, pathToLegalOutputArtifact, treeArtifacts);
}
}
if (!IncludeScanningHeaderDataHelper.handleTreeArtifacts(
env, pathToLegalOutputArtifact, treeArtifacts)) {
return null;
}
removeArtifactsFromSet(modularHeaders, headerInfo.modularHeaders);
removeArtifactsFromSet(modularHeaders, headerInfo.textualHeaders);
return new IncludeScanningHeaderData.Builder(
Collections.unmodifiableMap(pathToLegalOutputArtifact),
Collections.unmodifiableSet(modularHeaders));
}
/** Simple container for a collection of headers and corresponding modules. */
public static class HeadersAndModules {
public final Collection<Artifact> headers;
public final Collection<Artifact.DerivedArtifact> modules;
HeadersAndModules(int expectedHeaderCount) {
headers = CompactHashSet.createWithExpectedSize(expectedHeaderCount);
modules = CompactHashSet.create();
}
}
/**
* Returns a list of all headers from {@code includes} that are properly declared as well as all
* the modules that they are in.
*/
public HeadersAndModules computeDeclaredHeadersAndUsedModules(
boolean usePic, Set<Artifact> includes, List<HeaderInfo> transitiveHeaderInfoList) {
HeadersAndModules result = new HeadersAndModules(includes.size());
for (int c = 0; c < transitiveHeaderInfoList.size(); c++) {
HeaderInfo transitiveHeaderInfo = transitiveHeaderInfoList.get(c);
Artifact.DerivedArtifact module = transitiveHeaderInfo.getModule(usePic);
// Not using range-based for loops here as often there is exactly one element in this list
// and the amount of garbage created by SingletonImmutableList.iterator() is significant.
for (int i = 0; i < transitiveHeaderInfo.modularHeaders.size(); i++) {
Artifact header = transitiveHeaderInfo.modularHeaders.get(i);
if (includes.contains(header)) {
if (module != null) {
result.modules.add(module);
}
result.headers.add(header);
}
}
for (int i = 0; i < transitiveHeaderInfo.textualHeaders.size(); i++) {
Artifact header = transitiveHeaderInfo.textualHeaders.get(i);
if (includes.contains(header)) {
result.headers.add(header);
}
}
}
// Do not add the module of the current rule for both:
// 1. the module compile itself
// 2. compiles of other translation units of the same rule.
result.modules.remove(headerInfo.getModule(usePic));
return result;
}
private void removeArtifactsFromSet(Set<Artifact> set, Iterable<Artifact> artifacts) {
// Do not use Iterables.removeAll() or Set.removeAll() here as with the given container sizes,
// that needlessly deteriorates to a quadratic algorithm.
for (Artifact artifact : artifacts) {
set.remove(artifact);
}
}
public NestedSet<Artifact> getTransitiveModules(boolean usePic) {
return usePic ? transitivePicModules : transitiveModules;
}
/**
* Returns the immutable set of additional transitive inputs needed for
* compilation, like C++ module map artifacts.
*/
public NestedSet<Artifact> getAdditionalInputs() {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
builder.addAll(directModuleMaps);
builder.addTransitive(nonCodeInputs);
if (cppModuleMap != null && propagateModuleMapAsActionInput) {
builder.add(cppModuleMap.getArtifact());
}
return builder.build();
}
/** @return modules maps from direct dependencies. */
public Iterable<Artifact> getDirectModuleMaps() {
return directModuleMaps;
}
/**
* @return all declared headers of the current module if the current target
* is compiled as a module.
*/
protected Set<Artifact> getHeaderModuleSrcs() {
return new ImmutableSet.Builder<Artifact>()
.addAll(headerInfo.modularHeaders)
.addAll(headerInfo.textualHeaders)
.build();
}
/**
* Returns the set of defines needed to compile this target. This includes definitions from the
* transitive deps closure for the target. The order of the returned collection is deterministic.
*/
public NestedSet<String> getDefines() {
return commandLineCcCompilationContext.defines;
}
/**
* Returns the set of defines needed to compile this target. This doesn't include definitions from
* the transitive deps closure for the target.
*/
ImmutableList<String> getNonTransitiveDefines() {
return commandLineCcCompilationContext.localDefines;
}
/**
* Returns a {@code CcCompilationContext} that is based on a given {@code CcCompilationContext}
* but returns empty sets for {@link #getDeclaredIncludeDirs()}.
*/
public static CcCompilationContext disallowUndeclaredHeaders(
CcCompilationContext ccCompilationContext) {
return new CcCompilationContext(
ccCompilationContext.commandLineCcCompilationContext,
ccCompilationContext.compilationPrerequisites,
NestedSetBuilder.emptySet(Order.STABLE_ORDER),
ccCompilationContext.declaredIncludeSrcs,
ccCompilationContext.nonCodeInputs,
ccCompilationContext.headerInfo,
ccCompilationContext.transitiveHeaderInfos,
ccCompilationContext.transitiveModules,
ccCompilationContext.transitivePicModules,
ccCompilationContext.directModuleMaps,
ccCompilationContext.cppModuleMap,
ccCompilationContext.verificationModuleMap,
ccCompilationContext.propagateModuleMapAsActionInput,
ccCompilationContext.headersCheckingMode,
ccCompilationContext.virtualToOriginalHeaders);
}
/** @return the C++ module map of the owner. */
public CppModuleMap getCppModuleMap() {
return cppModuleMap;
}
/** @return the C++ module map of the owner. */
public CppModuleMap getVerificationModuleMap() {
return verificationModuleMap;
}
public CppConfiguration.HeadersCheckingMode getHeadersCheckingMode() {
return headersCheckingMode;
}
public static ImmutableList<CcCompilationContext> getCcCompilationContexts(
Iterable<? extends TransitiveInfoCollection> deps) {
ImmutableList.Builder<CcCompilationContext> ccCompilationContextsBuilder =
ImmutableList.builder();
for (CcInfo ccInfo : AnalysisUtils.getProviders(deps, CcInfo.PROVIDER)) {
ccCompilationContextsBuilder.add(ccInfo.getCcCompilationContext());
}
return ccCompilationContextsBuilder.build();
}
public static CcCompilationContext merge(Collection<CcCompilationContext> ccCompilationContexts) {
CcCompilationContext.Builder builder =
CcCompilationContext.builder(
/* actionConstructionContext= */ null, /* configuration= */ null, /* label= */ null);
builder.mergeDependentCcCompilationContexts(ccCompilationContexts);
return builder.build();
}
public NestedSet<Pair<String, String>> getVirtualToOriginalHeaders() {
return virtualToOriginalHeaders;
}
/**
* The parts of the {@code CcCompilationContext} that influence the command line of compilation
* actions.
*/
@Immutable
@AutoCodec
@VisibleForSerialization
static class CommandLineCcCompilationContext {
private final ImmutableList<PathFragment> includeDirs;
private final ImmutableList<PathFragment> quoteIncludeDirs;
private final ImmutableList<PathFragment> systemIncludeDirs;
private final ImmutableList<PathFragment> frameworkIncludeDirs;
private final NestedSet<String> defines;
private final ImmutableList<String> localDefines;
CommandLineCcCompilationContext(
ImmutableList<PathFragment> includeDirs,
ImmutableList<PathFragment> quoteIncludeDirs,
ImmutableList<PathFragment> systemIncludeDirs,
ImmutableList<PathFragment> frameworkIncludeDirs,
NestedSet<String> defines,
ImmutableList<String> localDefines) {
this.includeDirs = includeDirs;
this.quoteIncludeDirs = quoteIncludeDirs;
this.systemIncludeDirs = systemIncludeDirs;
this.frameworkIncludeDirs = frameworkIncludeDirs;
this.defines = defines;
this.localDefines = localDefines;
}
}
/** Creates a new builder for a {@link CcCompilationContext} instance. */
public static Builder builder(
ActionConstructionContext actionConstructionContext,
BuildConfiguration configuration,
Label label) {
return new Builder(actionConstructionContext, configuration, label);
}
/** Builder class for {@link CcCompilationContext}. */
public static class Builder {
private String purpose;
private final NestedSetBuilder<Artifact> compilationPrerequisites =
NestedSetBuilder.stableOrder();
private final Set<PathFragment> includeDirs = new LinkedHashSet<>();
private final Set<PathFragment> quoteIncludeDirs = new LinkedHashSet<>();
private final Set<PathFragment> systemIncludeDirs = new LinkedHashSet<>();
private final Set<PathFragment> frameworkIncludeDirs = new LinkedHashSet<>();
private final NestedSetBuilder<PathFragment> declaredIncludeDirs =
NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> declaredIncludeSrcs =
NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> nonCodeInputs = NestedSetBuilder.stableOrder();
private final HeaderInfo.Builder headerInfoBuilder = new HeaderInfo.Builder();
private final NestedSetBuilder<HeaderInfo> transitiveHeaderInfo =
NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> transitiveModules = NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> transitivePicModules = NestedSetBuilder.stableOrder();
private final Set<Artifact> directModuleMaps = new LinkedHashSet<>();
private final NestedSetBuilder<String> defines = NestedSetBuilder.linkOrder();
private final Set<String> localDefines = new LinkedHashSet<>();
private CppModuleMap cppModuleMap;
private CppModuleMap verificationModuleMap;
private boolean propagateModuleMapAsActionInput = true;
private CppConfiguration.HeadersCheckingMode headersCheckingMode =
CppConfiguration.HeadersCheckingMode.STRICT;
private NestedSetBuilder<Pair<String, String>> virtualToOriginalHeaders =
NestedSetBuilder.stableOrder();
/** The rule that owns the context */
private final ActionConstructionContext actionConstructionContext;
private final BuildConfiguration configuration;
private final Label label;
/** Creates a new builder for a {@link CcCompilationContext} instance. */
private Builder(
ActionConstructionContext actionConstructionContext,
BuildConfiguration configuration,
Label label) {
this.actionConstructionContext = actionConstructionContext;
this.configuration = configuration;
this.label = label;
}
/**
* Overrides the purpose of this context. This is useful if a Target needs more than one
* CcCompilationContext. (The purpose is used to construct the name of the prerequisites
* middleman for the context, and all artifacts for a given Target must have distinct names.)
*
* @param purpose must be a string which is suitable for use as a filename. A single rule may
* have many middlemen with distinct purposes.
* @see MiddlemanFactory#createSchedulingDependencyMiddleman
*/
public Builder setPurpose(String purpose) {
this.purpose = purpose;
return this;
}
public String getPurpose() {
return purpose;
}
/**
* Merges the {@link CcCompilationContext} of a dependency into this one by adding the contents
* of all of its attributes.
*/
public Builder mergeDependentCcCompilationContext(
CcCompilationContext otherCcCompilationContext) {
Preconditions.checkNotNull(otherCcCompilationContext);
compilationPrerequisites.addTransitive(
otherCcCompilationContext.getTransitiveCompilationPrerequisites());
includeDirs.addAll(otherCcCompilationContext.getIncludeDirs());
quoteIncludeDirs.addAll(otherCcCompilationContext.getQuoteIncludeDirs());
systemIncludeDirs.addAll(otherCcCompilationContext.getSystemIncludeDirs());
frameworkIncludeDirs.addAll(otherCcCompilationContext.getFrameworkIncludeDirs());
declaredIncludeDirs.addTransitive(otherCcCompilationContext.getDeclaredIncludeDirs());
declaredIncludeSrcs.addTransitive(otherCcCompilationContext.getDeclaredIncludeSrcs());
transitiveHeaderInfo.addTransitive(otherCcCompilationContext.transitiveHeaderInfos);
transitiveModules.addTransitive(otherCcCompilationContext.transitiveModules);
if (otherCcCompilationContext.headerInfo.headerModule != null) {
transitiveModules.add(otherCcCompilationContext.headerInfo.headerModule);
}
transitivePicModules.addTransitive(otherCcCompilationContext.transitivePicModules);
if (otherCcCompilationContext.headerInfo.picHeaderModule != null) {
transitivePicModules.add(otherCcCompilationContext.headerInfo.picHeaderModule);
}
nonCodeInputs.addTransitive(otherCcCompilationContext.nonCodeInputs);
// All module maps of direct dependencies are inputs to the current compile independently of
// the build type.
if (otherCcCompilationContext.getCppModuleMap() != null) {
directModuleMaps.add(otherCcCompilationContext.getCppModuleMap().getArtifact());
}
defines.addTransitive(otherCcCompilationContext.getDefines());
virtualToOriginalHeaders.addTransitive(
otherCcCompilationContext.getVirtualToOriginalHeaders());
return this;
}
/**
* Merges the {@code CcCompilationContext}s of some targets into this one by adding the contents
* of all of their attributes. Targets that do not implement {@link CcCompilationContext} are
* ignored.
*/
public Builder mergeDependentCcCompilationContexts(Iterable<CcCompilationContext> targets) {
for (CcCompilationContext target : targets) {
mergeDependentCcCompilationContext(target);
}
return this;
}
/**
* Add a single include directory to be added with "-I". It can be either relative to the exec
* root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}) or absolute.
* Before it is stored, the include directory is normalized.
*/
public Builder addIncludeDir(PathFragment includeDir) {
includeDirs.add(includeDir);
return this;
}
/** See {@link #addIncludeDir(PathFragment)} */
public Builder addIncludeDirs(Iterable<PathFragment> includeDirs) {
Iterables.addAll(this.includeDirs, includeDirs);
return this;
}
/**
* Add a single include directory to be added with "-iquote". It can be either relative to the
* exec root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}) or absolute.
* Before it is stored, the include directory is normalized.
*/
public Builder addQuoteIncludeDir(PathFragment quoteIncludeDir) {
quoteIncludeDirs.add(quoteIncludeDir);
return this;
}
/** See {@link #addQuoteIncludeDir(PathFragment)} */
public Builder addQuoteIncludeDirs(Iterable<PathFragment> quoteIncludeDirs) {
Iterables.addAll(this.quoteIncludeDirs, quoteIncludeDirs);
return this;
}
/**
* Add include directories to be added with "-isystem". It can be either relative to the exec
* root (see {@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot(String)}) or absolute.
* Before it is stored, the include directory is normalized.
*/
public Builder addSystemIncludeDirs(Iterable<PathFragment> systemIncludeDirs) {
Iterables.addAll(this.systemIncludeDirs, systemIncludeDirs);
return this;
}
/** Add framewrok include directories to be added with "-F". */
public Builder addFrameworkIncludeDirs(Iterable<PathFragment> frameworkIncludeDirs) {
Iterables.addAll(this.frameworkIncludeDirs, frameworkIncludeDirs);
return this;
}
/** Add a single declared include dir, relative to a "-I" or "-iquote" directory". */
public Builder addDeclaredIncludeDir(PathFragment dir) {
declaredIncludeDirs.add(dir);
return this;
}
/**
* Adds a header that has been declared in the {@code src} or {@code headers attribute}. The
* header will also be added to the compilation prerequisites.
*
* <p>Filters out fileset directory artifacts, which are not valid inputs.
*/
public Builder addDeclaredIncludeSrc(Artifact header) {
if (!header.isFileset()) {
declaredIncludeSrcs.add(header);
compilationPrerequisites.add(header);
}
return this;
}
/**
* Adds multiple headers that have been declared in the {@code src} or {@code headers
* attribute}. The headers will also be added to the compilation prerequisites.
*
* <p>Filters out fileset directory artifacts, which are not valid inputs.
*/
public Builder addDeclaredIncludeSrcs(Iterable<Artifact> declaredIncludeSrcs) {
for (Artifact source : declaredIncludeSrcs) {
addDeclaredIncludeSrc(source);
}
return this;
}
public Builder addModularHdrs(Collection<Artifact> headers) {
this.headerInfoBuilder.addHeaders(headers);
return this;
}
public Builder addTextualHdrs(Collection<Artifact> headers) {
this.headerInfoBuilder.addTextualHeaders(headers);
return this;
}
/** Add a set of required non-code compilation input. */
public Builder addNonCodeInputs(Iterable<Artifact> inputs) {
nonCodeInputs.addAll(inputs);
return this;
}
/** Adds a single define. */
public Builder addDefine(String define) {
defines.add(define);
return this;
}
/** Adds multiple defines. */
public Builder addDefines(NestedSet<String> defines) {
this.defines.addTransitive(defines);
return this;
}
/** Adds multiple non-transitive defines. */
public Builder addNonTransitiveDefines(Iterable<String> defines) {
Iterables.addAll(this.localDefines, defines);
return this;
}
/** Sets the C++ module map. */
public Builder setCppModuleMap(CppModuleMap cppModuleMap) {
this.cppModuleMap = cppModuleMap;
return this;
}
/** Sets the C++ module map used to verify that headers are modules compatible. */
public Builder setVerificationModuleMap(CppModuleMap verificationModuleMap) {
this.verificationModuleMap = verificationModuleMap;
return this;
}
/**
* Causes the module map to be passed as an action input to dependant compilations.
*/
public Builder setPropagateCppModuleMapAsActionInput(boolean propagateModuleMap) {
this.propagateModuleMapAsActionInput = propagateModuleMap;
return this;
}
/**
* Sets the C++ header module in non-pic mode.
*
* @param headerModule The .pcm file generated for this library.
*/
Builder setHeaderModule(Artifact.DerivedArtifact headerModule) {
this.headerInfoBuilder.setHeaderModule(headerModule);
return this;
}
/**
* Sets the C++ header module in pic mode.
*
* @param picHeaderModule The .pic.pcm file generated for this library.
*/
Builder setPicHeaderModule(Artifact.DerivedArtifact picHeaderModule) {
this.headerInfoBuilder.setPicHeaderModule(picHeaderModule);
return this;
}
public Builder setHeadersCheckingMode(
CppConfiguration.HeadersCheckingMode headersCheckingMode) {
this.headersCheckingMode = headersCheckingMode;
return this;
}
public Builder addVirtualToOriginalHeaders(
NestedSet<Pair<String, String>> virtualToOriginalHeaders) {
this.virtualToOriginalHeaders.addTransitive(virtualToOriginalHeaders);
return this;
}
/** Builds the {@link CcCompilationContext}. */
public CcCompilationContext build() {
return build(
actionConstructionContext == null ? null : actionConstructionContext.getActionOwner(),
actionConstructionContext == null
? null
: actionConstructionContext.getAnalysisEnvironment().getMiddlemanFactory());
}
@VisibleForTesting // productionVisibility = Visibility.PRIVATE
public CcCompilationContext build(ActionOwner owner, MiddlemanFactory middlemanFactory) {
NestedSet<Artifact> constructedPrereq = createMiddleman(owner, middlemanFactory);
HeaderInfo headerInfo = headerInfoBuilder.build();
transitiveHeaderInfo.add(headerInfo);
return new CcCompilationContext(
new CommandLineCcCompilationContext(
ImmutableList.copyOf(includeDirs),
ImmutableList.copyOf(quoteIncludeDirs),
ImmutableList.copyOf(systemIncludeDirs),
ImmutableList.copyOf(frameworkIncludeDirs),
defines.build(),
ImmutableList.copyOf(localDefines)),
constructedPrereq,
declaredIncludeDirs.build(),
declaredIncludeSrcs.build(),
nonCodeInputs.build(),
headerInfo,
transitiveHeaderInfo.build(),
transitiveModules.build(),
transitivePicModules.build(),
ImmutableList.copyOf(directModuleMaps),
cppModuleMap,
verificationModuleMap,
propagateModuleMapAsActionInput,
headersCheckingMode,
virtualToOriginalHeaders.build());
}
/** Creates a middleman for the compilation prerequisites. */
private NestedSet<Artifact> createMiddleman(
ActionOwner owner, MiddlemanFactory middlemanFactory) {
if (middlemanFactory == null) {
// TODO(b/110873917): We don't have a middleman factory, therefore, we use the compilation
// prerequisites as they were passed to the builder, i.e. we use every header instead of a
// middle man.
return compilationPrerequisites.build();
}
if (compilationPrerequisites.isEmpty()) {
return compilationPrerequisites.build();
}
// Compilation prerequisites gathered in the compilationPrerequisites must be generated prior
// to executing a C++ compilation step that depends on them (since these prerequisites include
// all potential header files, etc that could be referenced during compilation). So there is a
// definite need to ensure a scheduling dependency. However, those prerequisites should have
// no effect on the decision whether C++ compilation should happen in the first place - only
// the CppCompileAction outputs (.o and .d files) and all files referenced by the .d file
// should be used to make that decision.
//
// If this action was never executed, then the .d file is missing, forcing compilation. If the
// .d file is present and has not changed then the only reason that would force us to
// re-compile would be change in one of the files referenced by the .d file, since no other
// files participated in the compilation.
//
// We also need to propagate errors through this dependency link. Therefore, we use a
// scheduling dependency middleman. Such a middleman will be ignored by the dependency checker
// yet still represents an edge in the action dependency graph - forcing proper execution
// order and error propagation.
String name = cppModuleMap != null ? cppModuleMap.getName() : label.toString();
Artifact prerequisiteStampFile =
middlemanFactory.createSchedulingDependencyMiddleman(
owner,
name,
purpose,
compilationPrerequisites.build(),
configuration.getMiddlemanDirectory(label.getPackageIdentifier().getRepository()));
return NestedSetBuilder.create(Order.STABLE_ORDER, prerequisiteStampFile);
}
}
/**
* Gathers data about the PIC and no-PIC .pcm files belonging to this context and the associated
* information about the headers, e.g. modular vs. textual headers and pre-grepped header files.
*/
@Immutable
@AutoCodec
public static final class HeaderInfo {
/**
* The modules built for this context. If null, then no module is being compiled for this
* context.
*/
private final Artifact.DerivedArtifact headerModule;
private final Artifact.DerivedArtifact picHeaderModule;
/** All header files that are compiled into this module. */
// Note: this change will be reverted in the migration cl.
public final ImmutableList<Artifact> modularHeaders;
/** All header files that are contained in this module. */
// Note: this change will be reverted in the migration cl.
public final ImmutableList<Artifact> textualHeaders;
HeaderInfo(
Artifact.DerivedArtifact headerModule,
Artifact.DerivedArtifact picHeaderModule,
ImmutableList<Artifact> modularHeaders,
ImmutableList<Artifact> textualHeaders) {
this.headerModule = headerModule;
this.picHeaderModule = picHeaderModule;
this.modularHeaders = modularHeaders;
this.textualHeaders = textualHeaders;
}
Artifact.DerivedArtifact getModule(boolean pic) {
return pic ? picHeaderModule : headerModule;
}
/**
* Builder class for {@link HeaderInfo}.
*/
public static class Builder {
private Artifact.DerivedArtifact headerModule = null;
private Artifact.DerivedArtifact picHeaderModule = null;
private final Set<Artifact> modularHeaders = new HashSet<>();
private final Set<Artifact> textualHeaders = new HashSet<>();
Builder setHeaderModule(Artifact.DerivedArtifact headerModule) {
this.headerModule = headerModule;
return this;
}
Builder setPicHeaderModule(Artifact.DerivedArtifact headerModule) {
this.picHeaderModule = headerModule;
return this;
}
public Builder addHeaders(Collection<Artifact> headers) {
// TODO(djasper): CPP_TEXTUAL_INCLUDEs are currently special cased here and in
// CppModuleMapAction. These should be moved to a place earlier in the Action construction.
for (Artifact header : headers) {
if (header.isFileType(CppFileTypes.CPP_TEXTUAL_INCLUDE)) {
this.textualHeaders.add(header);
} else {
this.modularHeaders.add(header);
}
}
return this;
}
public Builder addTextualHeaders(Collection<Artifact> headers) {
this.textualHeaders.addAll(headers);
return this;
}
public HeaderInfo build() {
return new HeaderInfo(
headerModule,
picHeaderModule,
ImmutableList.copyOf(modularHeaders),
ImmutableList.copyOf(textualHeaders));
}
}
}
}