// 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.MiddlemanFactory; import com.google.devtools.build.lib.analysis.RuleContext; 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.CppHelper.PregreppedHeader; 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.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; /** * Immutable store of information needed for C++ compilation that is aggregated across dependencies. */ @Immutable @AutoCodec // TODO(b/77669139): Rename to CcCompilationContext. public final class CcCompilationContext implements CcCompilationContextApi { /** An empty {@code CcCompilationContext}. */ public static final CcCompilationContext EMPTY = new Builder(null).build(); private final CommandLineCcCompilationContext commandLineCcCompilationContext; private final NestedSet declaredIncludeDirs; private final NestedSet declaredIncludeWarnDirs; private final NestedSet declaredIncludeSrcs; /** * Module maps from direct dependencies. */ private final NestedSet directModuleMaps; /** Non-code mandatory compilation inputs. */ private final NestedSet nonCodeInputs; private final NestedSet pregreppedHdrs; private final ModuleInfo moduleInfo; private final ModuleInfo picModuleInfo; private final CppModuleMap cppModuleMap; private final CppModuleMap verificationModuleMap; private final boolean propagateModuleMapAsActionInput; // Derived from depsContexts. private final ImmutableSet compilationPrerequisites; @AutoCodec.Instantiator @VisibleForSerialization CcCompilationContext( CommandLineCcCompilationContext commandLineCcCompilationContext, ImmutableSet compilationPrerequisites, NestedSet declaredIncludeDirs, NestedSet declaredIncludeWarnDirs, NestedSet declaredIncludeSrcs, NestedSet pregreppedHdrs, NestedSet nonCodeInputs, ModuleInfo moduleInfo, ModuleInfo picModuleInfo, NestedSet directModuleMaps, CppModuleMap cppModuleMap, @Nullable CppModuleMap verificationModuleMap, boolean propagateModuleMapAsActionInput) { Preconditions.checkNotNull(commandLineCcCompilationContext); this.commandLineCcCompilationContext = commandLineCcCompilationContext; this.declaredIncludeDirs = declaredIncludeDirs; this.declaredIncludeWarnDirs = declaredIncludeWarnDirs; this.declaredIncludeSrcs = declaredIncludeSrcs; this.directModuleMaps = directModuleMaps; this.pregreppedHdrs = pregreppedHdrs; this.moduleInfo = moduleInfo; this.picModuleInfo = picModuleInfo; this.cppModuleMap = cppModuleMap; this.nonCodeInputs = nonCodeInputs; this.verificationModuleMap = verificationModuleMap; this.compilationPrerequisites = compilationPrerequisites; this.propagateModuleMapAsActionInput = propagateModuleMapAsActionInput; } /** * Returns the transitive compilation prerequisites consolidated into middlemen prerequisites, or * an empty set if there are no prerequisites. * *

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). * *

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 * (using {@link Builder#addCompilationPrerequisites(Iterable)}, {@link * Builder#mergeDependentCcCompilationContext(CcCompilationContext)}, and {@link * Builder#mergeDependentCcCompilationContexts(Iterable)}, they are consolidated into a single * middleman Artifact when {@link Builder#build()} is called. * *

The returned set can be empty if there are no prerequisites. Usually it contains a single * middleman, but if LIPO is used there can be two. */ public ImmutableSet 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}). */ public ImmutableList 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}). */ public ImmutableList 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}). */ public ImmutableList getSystemIncludeDirs() { return commandLineCcCompilationContext.systemIncludeDirs; } /** * Returns the immutable set of declared include directories, relative to a "-I" or "-iquote" * directory" (possibly empty but never null). */ public NestedSet getDeclaredIncludeDirs() { return declaredIncludeDirs; } /** * Returns the immutable set of include directories, relative to a "-I" or "-iquote" directory", * from which inclusion will produce a warning (possibly empty but never null). */ public NestedSet getDeclaredIncludeWarnDirs() { return declaredIncludeWarnDirs; } /** * 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 getDeclaredIncludeSrcs() { return declaredIncludeSrcs; } /** Returns headers given as textual_hdrs in this target. */ public ImmutableSet getTextualHdrs() { return moduleInfo.textualHeaders; } /** * Returns the immutable pairs of (header file, pregrepped header file). The value artifacts * (pregrepped header file) are generated by {@link ExtractInclusionAction}. */ NestedSet getPregreppedHeaders() { return pregreppedHdrs; } public NestedSet getTransitiveModules(boolean usePic) { return usePic ? picModuleInfo.transitiveModules : moduleInfo.transitiveModules; } public Set getModularHeaders(boolean usePic) { ModuleInfo info = usePic ? picModuleInfo : moduleInfo; Set result = new HashSet<>(); for (TransitiveModuleHeaders moduleHeaders : info.transitiveModuleHeaders) { result.addAll(moduleHeaders.headers); } // Remove headers belonging to this module. result.removeAll(info.modularHeaders); result.removeAll(info.textualHeaders); return Collections.unmodifiableSet(result); } public Collection getUsedModules( boolean usePic, Set usedHeaders) { return usePic ? picModuleInfo.getUsedModules(usedHeaders) : moduleInfo.getUsedModules(usedHeaders); } /** * Returns the immutable set of additional transitive inputs needed for * compilation, like C++ module map artifacts. */ public NestedSet getAdditionalInputs() { NestedSetBuilder builder = NestedSetBuilder.stableOrder(); builder.addTransitive(directModuleMaps); builder.addTransitive(nonCodeInputs); if (cppModuleMap != null && propagateModuleMapAsActionInput) { builder.add(cppModuleMap.getArtifact()); } return builder.build(); } /** * @return modules maps from direct dependencies. */ public NestedSet getDirectModuleMaps() { return directModuleMaps; } /** * @return all declared headers of the current module if the current target * is compiled as a module. */ protected Set getHeaderModuleSrcs() { return new ImmutableSet.Builder() .addAll(moduleInfo.modularHeaders) .addAll(moduleInfo.textualHeaders) .build(); } /** * Returns the set of defines needed to compile this target (possibly empty * but never null). This includes definitions from the transitive deps closure * for the target. The order of the returned collection is deterministic. */ public ImmutableList getDefines() { return commandLineCcCompilationContext.defines; } /** * Returns a {@code CcCompilationContext} that is based on a given {@code CcCompilationContext} * but returns empty sets for {@link #getDeclaredIncludeDirs()} and {@link * #getDeclaredIncludeWarnDirs()}. */ public static CcCompilationContext disallowUndeclaredHeaders( CcCompilationContext ccCompilationContext) { return new CcCompilationContext( ccCompilationContext.commandLineCcCompilationContext, ccCompilationContext.compilationPrerequisites, NestedSetBuilder.emptySet(Order.STABLE_ORDER), NestedSetBuilder.emptySet(Order.STABLE_ORDER), ccCompilationContext.declaredIncludeSrcs, ccCompilationContext.pregreppedHdrs, ccCompilationContext.nonCodeInputs, ccCompilationContext.moduleInfo, ccCompilationContext.picModuleInfo, ccCompilationContext.directModuleMaps, ccCompilationContext.cppModuleMap, ccCompilationContext.verificationModuleMap, ccCompilationContext.propagateModuleMapAsActionInput); } /** * Returns the context for a LIPO compile action. This uses the include dirs and defines of the * library, but the declared inclusion dirs/srcs from both the library and the owner binary. * *

TODO(bazel-team): this might make every LIPO target have an unnecessary large set of * inclusion dirs/srcs. The correct behavior would be to merge only the contexts of actual * referred targets (as listed in .imports file). * *

Undeclared inclusion checking ({@link #getDeclaredIncludeDirs()}, {@link * #getDeclaredIncludeWarnDirs()}, and {@link #getDeclaredIncludeSrcs()}) needs to use the union * of the contexts of the involved source files. * *

For include and define command line flags ({@link #getIncludeDirs()} {@link * #getQuoteIncludeDirs()}, {@link #getSystemIncludeDirs()}, and {@link #getDefines()}) LIPO * compilations use the same values as non-LIPO compilation. * *

Include scanning is not handled by this method. See {@code * IncludeScannable#getAuxiliaryScannables()} instead. * * @param ownerCcCompilationContext the {@code CcCompilationContext} of the owner binary * @param libCcCompilationContext the {@code CcCompilationContext} of the library */ public static CcCompilationContext mergeForLipo( CcCompilationContext ownerCcCompilationContext, CcCompilationContext libCcCompilationContext) { ImmutableSet.Builder prerequisites = ImmutableSet.builder(); prerequisites.addAll(ownerCcCompilationContext.compilationPrerequisites); prerequisites.addAll(libCcCompilationContext.compilationPrerequisites); ModuleInfo.Builder moduleInfo = new ModuleInfo.Builder(); moduleInfo.merge(ownerCcCompilationContext.moduleInfo); moduleInfo.merge(libCcCompilationContext.moduleInfo); ModuleInfo.Builder picModuleInfo = new ModuleInfo.Builder(); picModuleInfo.merge(ownerCcCompilationContext.picModuleInfo); picModuleInfo.merge(libCcCompilationContext.picModuleInfo); return new CcCompilationContext( libCcCompilationContext.commandLineCcCompilationContext, prerequisites.build(), mergeSets( ownerCcCompilationContext.declaredIncludeDirs, libCcCompilationContext.declaredIncludeDirs), mergeSets( ownerCcCompilationContext.declaredIncludeWarnDirs, libCcCompilationContext.declaredIncludeWarnDirs), mergeSets( ownerCcCompilationContext.declaredIncludeSrcs, libCcCompilationContext.declaredIncludeSrcs), mergeSets(ownerCcCompilationContext.pregreppedHdrs, libCcCompilationContext.pregreppedHdrs), mergeSets(ownerCcCompilationContext.nonCodeInputs, libCcCompilationContext.nonCodeInputs), moduleInfo.build(), picModuleInfo.build(), mergeSets( ownerCcCompilationContext.directModuleMaps, libCcCompilationContext.directModuleMaps), libCcCompilationContext.cppModuleMap, libCcCompilationContext.verificationModuleMap, libCcCompilationContext.propagateModuleMapAsActionInput); } /** * Return a nested set containing all elements from {@code s1} and {@code s2}. */ private static NestedSet mergeSets(NestedSet s1, NestedSet s2) { NestedSetBuilder builder = NestedSetBuilder.stableOrder(); builder.addTransitive(s1); builder.addTransitive(s2); return builder.build(); } /** @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; } /** * The parts of the {@code CcCompilationContext} that influence the command line of compilation * actions. */ @Immutable @AutoCodec @VisibleForSerialization static class CommandLineCcCompilationContext { private final ImmutableList includeDirs; private final ImmutableList quoteIncludeDirs; private final ImmutableList systemIncludeDirs; private final ImmutableList defines; CommandLineCcCompilationContext( ImmutableList includeDirs, ImmutableList quoteIncludeDirs, ImmutableList systemIncludeDirs, ImmutableList defines) { this.includeDirs = includeDirs; this.quoteIncludeDirs = quoteIncludeDirs; this.systemIncludeDirs = systemIncludeDirs; this.defines = defines; } } /** Builder class for {@link CcCompilationContext}. */ public static class Builder { private String purpose; private final Set compilationPrerequisites = new LinkedHashSet<>(); private final Set includeDirs = new LinkedHashSet<>(); private final Set quoteIncludeDirs = new LinkedHashSet<>(); private final Set systemIncludeDirs = new LinkedHashSet<>(); private final NestedSetBuilder declaredIncludeDirs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder declaredIncludeWarnDirs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder declaredIncludeSrcs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder pregreppedHdrs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder nonCodeInputs = NestedSetBuilder.stableOrder(); private final ModuleInfo.Builder moduleInfo = new ModuleInfo.Builder(); private final ModuleInfo.Builder picModuleInfo = new ModuleInfo.Builder(); private final NestedSetBuilder directModuleMaps = NestedSetBuilder.stableOrder(); private final Set defines = new LinkedHashSet<>(); private CppModuleMap cppModuleMap; private CppModuleMap verificationModuleMap; private boolean propagateModuleMapAsActionInput = true; /** The rule that owns the context */ private final RuleContext ruleContext; /** Creates a new builder for a {@link CcCompilationContext} instance. */ public Builder(RuleContext ruleContext) { this.ruleContext = ruleContext; } /** * 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#createErrorPropagatingMiddleman */ 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.addAll( otherCcCompilationContext.getTransitiveCompilationPrerequisites()); includeDirs.addAll(otherCcCompilationContext.getIncludeDirs()); quoteIncludeDirs.addAll(otherCcCompilationContext.getQuoteIncludeDirs()); systemIncludeDirs.addAll(otherCcCompilationContext.getSystemIncludeDirs()); declaredIncludeDirs.addTransitive(otherCcCompilationContext.getDeclaredIncludeDirs()); declaredIncludeWarnDirs.addTransitive(otherCcCompilationContext.getDeclaredIncludeWarnDirs()); declaredIncludeSrcs.addTransitive(otherCcCompilationContext.getDeclaredIncludeSrcs()); pregreppedHdrs.addTransitive(otherCcCompilationContext.getPregreppedHeaders()); moduleInfo.addTransitive(otherCcCompilationContext.moduleInfo); picModuleInfo.addTransitive(otherCcCompilationContext.picModuleInfo); 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.addAll(otherCcCompilationContext.getDefines()); 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 targets) { for (CcCompilationContext target : targets) { mergeDependentCcCompilationContext(target); } return this; } /** * Adds multiple compilation prerequisites. * *

There are two kinds of "compilation prerequisites": declared header files and pregrepped * headers. */ public Builder addCompilationPrerequisites(Iterable prerequisites) { // LIPO collector must not add compilation prerequisites in order to avoid // the creation of a middleman action. for (Artifact prerequisite : prerequisites) { String basename = prerequisite.getFilename(); Preconditions.checkArgument(!Link.OBJECT_FILETYPES.matches(basename)); Preconditions.checkArgument(!Link.ARCHIVE_LIBRARY_FILETYPES.matches(basename)); Preconditions.checkArgument(!Link.SHARED_LIBRARY_FILETYPES.matches(basename)); } Iterables.addAll(compilationPrerequisites, prerequisites); 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}) or * absolute. Before it is stored, the include directory is normalized. */ public Builder addIncludeDir(PathFragment includeDir) { includeDirs.add(includeDir); return this; } /** * Add multiple include directories to be added with "-I". These can be * either relative to the exec root (see {@link * com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot}) or absolute. The * entries are normalized before they are stored. */ public Builder addIncludeDirs(Iterable includeDirs) { for (PathFragment includeDir : includeDirs) { addIncludeDir(includeDir); } 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}) or absolute. Before it * is stored, the include directory is normalized. */ public Builder addQuoteIncludeDir(PathFragment quoteIncludeDir) { quoteIncludeDirs.add(quoteIncludeDir); return this; } /** * Add a single include directory to be added with "-isystem". It can be * either relative to the exec root (see {@link * com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot}) or absolute. Before it * is stored, the include directory is normalized. */ public Builder addSystemIncludeDir(PathFragment systemIncludeDir) { systemIncludeDirs.add(systemIncludeDir); 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; } /** * Add a single declared include directory, relative to a "-I" or "-iquote" * directory", from which inclusion will produce a warning. */ public Builder addDeclaredIncludeWarnDir(PathFragment dir) { declaredIncludeWarnDirs.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. * *

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. * *

Filters out fileset directory artifacts, which are not valid inputs. */ public Builder addDeclaredIncludeSrcs(Collection declaredIncludeSrcs) { for (Artifact source : declaredIncludeSrcs) { addDeclaredIncludeSrc(source); } return this; } public Builder addModularHdrs(Collection headers) { this.moduleInfo.addHeaders(headers); this.picModuleInfo.addHeaders(headers); return this; } public Builder addTextualHdrs(Collection headers) { this.moduleInfo.addTextualHeaders(headers); this.picModuleInfo.addTextualHeaders(headers); return this; } /** * Add a map of generated source or header Artifact to an output Artifact after grepping the * file for include statements. */ public Builder addPregreppedHeaders(List pregrepped) { addCompilationPrerequisites( pregrepped .stream() .map(pregreppedHeader -> pregreppedHeader.greppedHeader()) .collect(Collectors.toList())); this.pregreppedHdrs.addAll(pregrepped); return this; } /** Add a set of required non-code compilation input. */ public Builder addNonCodeInputs(Iterable 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(Iterable defines) { Iterables.addAll(this.defines, 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. */ public Builder setHeaderModule(Artifact headerModule) { this.moduleInfo.setHeaderModule(headerModule); return this; } /** * Sets the C++ header module in pic mode. * @param picHeaderModule The .pic.pcm file generated for this library. */ public Builder setPicHeaderModule(Artifact picHeaderModule) { this.picModuleInfo.setHeaderModule(picHeaderModule); return this; } /** Builds the {@link CcCompilationContext}. */ public CcCompilationContext build() { return build( ruleContext == null ? null : ruleContext.getActionOwner(), ruleContext == null ? null : ruleContext.getAnalysisEnvironment().getMiddlemanFactory()); } @VisibleForTesting // productionVisibility = Visibility.PRIVATE public CcCompilationContext build(ActionOwner owner, MiddlemanFactory middlemanFactory) { Preconditions.checkState( Objects.equals(moduleInfo.textualHeaders, picModuleInfo.textualHeaders), "Module and PIC module's textual headers are expected to be identical"); // We don't create middlemen in LIPO collector subtree, because some target CT // will do that instead. Artifact prerequisiteStampFile = (ruleContext != null && ruleContext.getFragment(CppConfiguration.class).isLipoContextCollector()) ? getMiddlemanArtifact(middlemanFactory) : createMiddleman(owner, middlemanFactory); return new CcCompilationContext( new CommandLineCcCompilationContext( ImmutableList.copyOf(includeDirs), ImmutableList.copyOf(quoteIncludeDirs), ImmutableList.copyOf(systemIncludeDirs), ImmutableList.copyOf(defines)), prerequisiteStampFile == null ? ImmutableSet.of() : ImmutableSet.of(prerequisiteStampFile), declaredIncludeDirs.build(), declaredIncludeWarnDirs.build(), declaredIncludeSrcs.build(), pregreppedHdrs.build(), nonCodeInputs.build(), moduleInfo.build(), picModuleInfo.build(), directModuleMaps.build(), cppModuleMap, verificationModuleMap, propagateModuleMapAsActionInput); } /** * Creates a middleman for the compilation prerequisites. * * @return the middleman or null if there are no prerequisites */ private Artifact createMiddleman(ActionOwner owner, MiddlemanFactory middlemanFactory) { if (compilationPrerequisites.isEmpty()) { return null; } // Compilation prerequisites gathered in the compilationPrerequisites // must be generated prior to executing 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 scheduling edge dependency. However, those prerequisites should // have no effect on the decision whether C++ compilation should happen in // the first place - only 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 *.d file would be missing, forcing // compilation to occur. If *.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. So we use an error propagating middleman. // Such middleman will be ignored by the dependency checker yet will still // represent an edge in the action dependency graph - forcing proper execution // order and error propagation. String name = cppModuleMap != null ? cppModuleMap.getName() : ruleContext.getLabel().toString(); return middlemanFactory.createErrorPropagatingMiddleman( owner, name, purpose, ImmutableList.copyOf(compilationPrerequisites), ruleContext.getConfiguration().getMiddlemanDirectory( ruleContext.getRule().getRepository())); } /** * Returns the same set of artifacts as createMiddleman() would, but without * actually creating middlemen. */ private Artifact getMiddlemanArtifact(MiddlemanFactory middlemanFactory) { if (compilationPrerequisites.isEmpty()) { return null; } return middlemanFactory.getErrorPropagatingMiddlemanArtifact( ruleContext.getLabel().toString(), purpose, ruleContext.getConfiguration().getMiddlemanDirectory( ruleContext.getRule().getRepository())); } } /** * Gathers data about the direct and transitive .pcm files belonging to this context. Can be to * either gather data on PIC or on no-PIC .pcm files. */ @Immutable @AutoCodec public static final class ModuleInfo { /** * The module built for this context. If null, then no module is being compiled for this * context. */ private final Artifact headerModule; /** All header files that are compiled into this module. */ private final ImmutableSet modularHeaders; /** All header files that are contained in this module. */ private final ImmutableSet textualHeaders; /** * All transitive modules that this context depends on, excluding headerModule. */ private final NestedSet transitiveModules; /** * All information about mapping transitive headers to transitive modules. */ public final NestedSet transitiveModuleHeaders; public ModuleInfo( Artifact headerModule, ImmutableSet modularHeaders, ImmutableSet textualHeaders, NestedSet transitiveModules, NestedSet transitiveModuleHeaders) { this.headerModule = headerModule; this.modularHeaders = modularHeaders; this.textualHeaders = textualHeaders; this.transitiveModules = transitiveModules; this.transitiveModuleHeaders = transitiveModuleHeaders; } public Collection getUsedModules(Set usedHeaders) { List result = new ArrayList<>(); for (TransitiveModuleHeaders transitiveModule : transitiveModuleHeaders) { if (transitiveModule.module.equals(headerModule)) { // 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. continue; } boolean providesUsedHeader = false; for (Artifact header : transitiveModule.headers) { if (usedHeaders.contains(header)) { providesUsedHeader = true; break; } } if (providesUsedHeader) { result.add(transitiveModule); } } return result; } /** * Builder class for {@link ModuleInfo}. */ public static class Builder { private Artifact headerModule = null; private final Set modularHeaders = new LinkedHashSet<>(); private final Set textualHeaders = new LinkedHashSet<>(); private final NestedSetBuilder transitiveModules = NestedSetBuilder.stableOrder(); private final NestedSetBuilder transitiveModuleHeaders = NestedSetBuilder.stableOrder(); public Builder setHeaderModule(Artifact headerModule) { this.headerModule = headerModule; return this; } public Builder addHeaders(Collection 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 headers) { this.textualHeaders.addAll(headers); return this; } /** * Merges a {@link ModuleInfo} into this one. In contrast to addTransitive, this doesn't add * the dependent module to transitiveModules, but just merges the transitive sets. The main * usage is to merge multiple {@link ModuleInfo} instances for Lipo. */ public Builder merge(ModuleInfo other) { if (headerModule == null) { headerModule = other.headerModule; } modularHeaders.addAll(other.modularHeaders); textualHeaders.addAll(other.textualHeaders); transitiveModules.addTransitive(other.transitiveModules); transitiveModuleHeaders.addTransitive(other.transitiveModuleHeaders); return this; } /** * Adds the {@link ModuleInfo} of a dependency and builds up the transitive data structures. */ public Builder addTransitive(ModuleInfo moduleInfo) { if (moduleInfo.headerModule != null) { transitiveModules.add(moduleInfo.headerModule); } transitiveModules.addTransitive(moduleInfo.transitiveModules); transitiveModuleHeaders.addTransitive(moduleInfo.transitiveModuleHeaders); return this; } public ModuleInfo build() { ImmutableSet modularHeaders = ImmutableSet.copyOf(this.modularHeaders); NestedSet transitiveModules = this.transitiveModules.build(); if (headerModule != null) { transitiveModuleHeaders.add( new TransitiveModuleHeaders(headerModule, modularHeaders, transitiveModules)); } return new ModuleInfo( headerModule, modularHeaders, ImmutableSet.copyOf(this.textualHeaders), transitiveModules, transitiveModuleHeaders.build()); } } } /** Collects data for a specific module in a special format that makes pruning easy. */ @Immutable @AutoCodec public static final class TransitiveModuleHeaders { /** * The module that we are calculating information for. */ private final Artifact module; /** * The headers compiled into this module. */ private final ImmutableSet headers; /** * This nested set contains 'module' as well as all targets it transitively depends on. * If any of the 'headers' is used, all of these modules a required for the compilation. */ private final NestedSet transitiveModules; public TransitiveModuleHeaders( Artifact module, ImmutableSet headers, NestedSet transitiveModules) { this.module = module; this.headers = headers; this.transitiveModules = transitiveModules; } public Artifact getModule() { return module; } public Collection getTransitiveModules() { return transitiveModules.toCollection(); } } }