// 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.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.analysis.TransitiveInfoProvider; 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.util.Pair; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; 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 public final class CppCompilationContext implements TransitiveInfoProvider { /** An empty compilation context. */ public static final CppCompilationContext EMPTY = new Builder(null).build(); private final CommandLineContext commandLineContext; private final NestedSet<PathFragment> declaredIncludeDirs; private final NestedSet<PathFragment> declaredIncludeWarnDirs; private final NestedSet<Artifact> declaredIncludeSrcs; /** * Module maps from direct dependencies. */ private final NestedSet<Artifact> directModuleMaps; /** Non-code mandatory compilation inputs. */ private final NestedSet<Artifact> nonCodeInputs; private final NestedSet<Pair<Artifact, Artifact>> 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<Artifact> compilationPrerequisites; private CppCompilationContext( CommandLineContext commandLineContext, ImmutableSet<Artifact> compilationPrerequisites, NestedSet<PathFragment> declaredIncludeDirs, NestedSet<PathFragment> declaredIncludeWarnDirs, NestedSet<Artifact> declaredIncludeSrcs, NestedSet<Pair<Artifact, Artifact>> pregreppedHdrs, NestedSet<Artifact> nonCodeInputs, ModuleInfo moduleInfo, ModuleInfo picModuleInfo, NestedSet<Artifact> directModuleMaps, CppModuleMap cppModuleMap, @Nullable CppModuleMap verificationModuleMap, boolean propagateModuleMapAsActionInput) { Preconditions.checkNotNull(commandLineContext); this.commandLineContext = commandLineContext; 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. * * <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 (using * {@link Builder#addCompilationPrerequisites(Iterable)}, * {@link Builder#mergeDependentContext(CppCompilationContext)}, and * {@link Builder#mergeDependentContexts(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, but if LIPO is used there can be two. */ public ImmutableSet<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}). */ public ImmutableList<PathFragment> getIncludeDirs() { return commandLineContext.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<PathFragment> getQuoteIncludeDirs() { return commandLineContext.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<PathFragment> getSystemIncludeDirs() { return commandLineContext.systemIncludeDirs; } /** * Returns the immutable set of declared include directories, relative to a * "-I" or "-iquote" directory" (possibly empty but never null). The returned * collection may contain duplicate elements. * * <p>Note: The iteration order of this list is preserved as ide_build_info * writes these directories and sources out and the ordering will help when * used by consumers. */ public NestedSet<PathFragment> 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). The returned collection may contain duplicate * elements. * * <p>Note: The iteration order of this list is preserved as ide_build_info * writes these directories and sources out and the ordering will help when * used by consumers. */ public NestedSet<PathFragment> getDeclaredIncludeWarnDirs() { return declaredIncludeWarnDirs; } /** * Returns the immutable set of headers that have been declared in the * {@code src} or {@code headers attribute} (possibly empty but never null). * The returned collection may contain duplicate elements. * * <p>Note: The iteration order of this list is preserved as ide_build_info * writes these directories and sources out and the ordering will help when * used by consumers. */ public NestedSet<Artifact> getDeclaredIncludeSrcs() { return declaredIncludeSrcs; } /** * Returns the immutable pairs of (header file, pregrepped header file). The value artifacts * (pregrepped header file) are generated by {@link ExtractInclusionAction}. */ NestedSet<Pair<Artifact, Artifact>> getPregreppedHeaders() { return pregreppedHdrs; } public NestedSet<Artifact> getTransitiveModules(boolean usePic) { return usePic ? picModuleInfo.transitiveModules : moduleInfo.transitiveModules; } public Collection<TransitiveModuleHeaders> getUsedModules( boolean usePic, Set<Artifact> 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<Artifact> getAdditionalInputs() { NestedSetBuilder<Artifact> 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<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(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<String> getDefines() { return commandLineContext.defines; } /** * Returns a context that is based on a given context but returns empty sets * for {@link #getDeclaredIncludeDirs()} and {@link #getDeclaredIncludeWarnDirs()}. */ public static CppCompilationContext disallowUndeclaredHeaders(CppCompilationContext context) { return new CppCompilationContext( context.commandLineContext, context.compilationPrerequisites, NestedSetBuilder.<PathFragment>emptySet(Order.STABLE_ORDER), NestedSetBuilder.<PathFragment>emptySet(Order.STABLE_ORDER), context.declaredIncludeSrcs, context.pregreppedHdrs, context.nonCodeInputs, context.moduleInfo, context.picModuleInfo, context.directModuleMaps, context.cppModuleMap, context.verificationModuleMap, context.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. * * <p>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). * * <p>Undeclared inclusion checking ({@link #getDeclaredIncludeDirs()}, * {@link #getDeclaredIncludeWarnDirs()}, and * {@link #getDeclaredIncludeSrcs()}) needs to use the union of the contexts * of the involved source files. * * <p>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. * * <p>Include scanning is not handled by this method. See * {@code IncludeScannable#getAuxiliaryScannables()} instead. * * @param ownerContext the compilation context of the owner binary * @param libContext the compilation context of the library */ public static CppCompilationContext mergeForLipo(CppCompilationContext ownerContext, CppCompilationContext libContext) { ImmutableSet.Builder<Artifact> prerequisites = ImmutableSet.builder(); prerequisites.addAll(ownerContext.compilationPrerequisites); prerequisites.addAll(libContext.compilationPrerequisites); ModuleInfo.Builder moduleInfo = new ModuleInfo.Builder(); moduleInfo.merge(ownerContext.moduleInfo); moduleInfo.merge(libContext.moduleInfo); ModuleInfo.Builder picModuleInfo = new ModuleInfo.Builder(); picModuleInfo.merge(ownerContext.picModuleInfo); picModuleInfo.merge(libContext.picModuleInfo); return new CppCompilationContext( libContext.commandLineContext, prerequisites.build(), mergeSets(ownerContext.declaredIncludeDirs, libContext.declaredIncludeDirs), mergeSets(ownerContext.declaredIncludeWarnDirs, libContext.declaredIncludeWarnDirs), mergeSets(ownerContext.declaredIncludeSrcs, libContext.declaredIncludeSrcs), mergeSets(ownerContext.pregreppedHdrs, libContext.pregreppedHdrs), mergeSets(ownerContext.nonCodeInputs, libContext.nonCodeInputs), moduleInfo.build(), picModuleInfo.build(), mergeSets(ownerContext.directModuleMaps, libContext.directModuleMaps), libContext.cppModuleMap, libContext.verificationModuleMap, libContext.propagateModuleMapAsActionInput); } /** * Return a nested set containing all elements from {@code s1} and {@code s2}. */ private static <T> NestedSet<T> mergeSets(NestedSet<T> s1, NestedSet<T> s2) { NestedSetBuilder<T> 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 compilation context that influence the command line of * compilation actions. */ @Immutable private static class CommandLineContext { private final ImmutableList<PathFragment> includeDirs; private final ImmutableList<PathFragment> quoteIncludeDirs; private final ImmutableList<PathFragment> systemIncludeDirs; private final ImmutableList<String> defines; CommandLineContext(ImmutableList<PathFragment> includeDirs, ImmutableList<PathFragment> quoteIncludeDirs, ImmutableList<PathFragment> systemIncludeDirs, ImmutableList<String> defines) { this.includeDirs = includeDirs; this.quoteIncludeDirs = quoteIncludeDirs; this.systemIncludeDirs = systemIncludeDirs; this.defines = defines; } } /** * Builder class for {@link CppCompilationContext}. */ public static class Builder { private String purpose; private final Set<Artifact> compilationPrerequisites = new LinkedHashSet<>(); private final Set<PathFragment> includeDirs = new LinkedHashSet<>(); private final Set<PathFragment> quoteIncludeDirs = new LinkedHashSet<>(); private final Set<PathFragment> systemIncludeDirs = new LinkedHashSet<>(); private final NestedSetBuilder<PathFragment> declaredIncludeDirs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder<PathFragment> declaredIncludeWarnDirs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder<Artifact> declaredIncludeSrcs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder<Pair<Artifact, Artifact>> pregreppedHdrs = NestedSetBuilder.stableOrder(); private final NestedSetBuilder<Artifact> nonCodeInputs = NestedSetBuilder.stableOrder(); private final ModuleInfo.Builder moduleInfo = new ModuleInfo.Builder(); private final ModuleInfo.Builder picModuleInfo = new ModuleInfo.Builder(); private final NestedSetBuilder<Artifact> directModuleMaps = NestedSetBuilder.stableOrder(); private final Set<String> 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 CppCompilationContext} instance. */ public Builder(RuleContext ruleContext) { this.ruleContext = ruleContext; } /** * Overrides the purpose of this context. This is useful if a Target * needs more than one CppCompilationContext. (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 context of a dependency into this one by adding the contents * of all of its attributes. */ public Builder mergeDependentContext(CppCompilationContext otherContext) { Preconditions.checkNotNull(otherContext); compilationPrerequisites.addAll(otherContext.getTransitiveCompilationPrerequisites()); includeDirs.addAll(otherContext.getIncludeDirs()); quoteIncludeDirs.addAll(otherContext.getQuoteIncludeDirs()); systemIncludeDirs.addAll(otherContext.getSystemIncludeDirs()); declaredIncludeDirs.addTransitive(otherContext.getDeclaredIncludeDirs()); declaredIncludeWarnDirs.addTransitive(otherContext.getDeclaredIncludeWarnDirs()); declaredIncludeSrcs.addTransitive(otherContext.getDeclaredIncludeSrcs()); pregreppedHdrs.addTransitive(otherContext.getPregreppedHeaders()); moduleInfo.addTransitive(otherContext.moduleInfo); picModuleInfo.addTransitive(otherContext.picModuleInfo); nonCodeInputs.addTransitive(otherContext.nonCodeInputs); // All module maps of direct dependencies are inputs to the current compile independently of // the build type. if (otherContext.getCppModuleMap() != null) { directModuleMaps.add(otherContext.getCppModuleMap().getArtifact()); } defines.addAll(otherContext.getDefines()); return this; } /** * Merges the context of some targets into this one by adding the contents * of all of their attributes. Targets that do not implement * {@link CppCompilationContext} are ignored. */ public Builder mergeDependentContexts(Iterable<CppCompilationContext> targets) { for (CppCompilationContext target : targets) { mergeDependentContext(target); } return this; } /** * Adds multiple compilation prerequisites. * * <p>There are two kinds of "compilation prerequisites": declared header files and pregrepped * headers. */ public Builder addCompilationPrerequisites(Iterable<Artifact> 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.normalize()); 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<PathFragment> 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.normalize()); 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.normalize()); 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. * * <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(Collection<Artifact> declaredIncludeSrcs) { for (Artifact source : declaredIncludeSrcs) { addDeclaredIncludeSrc(source); } return this; } public Builder addModularHdrs(Collection<Artifact> headers) { this.moduleInfo.addHeaders(headers); this.picModuleInfo.addHeaders(headers); return this; } public Builder addTextualHdrs(Collection<Artifact> 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 addPregreppedHeaderMap(Map<Artifact, Artifact> pregrepped) { addCompilationPrerequisites(pregrepped.values()); for (Map.Entry<Artifact, Artifact> entry : pregrepped.entrySet()) { this.pregreppedHdrs.add(Pair.of(entry.getKey(), entry.getValue())); } 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(Iterable<String> 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 CppCompilationContext}. */ public CppCompilationContext build() { return build( ruleContext == null ? null : ruleContext.getActionOwner(), ruleContext == null ? null : ruleContext.getAnalysisEnvironment().getMiddlemanFactory()); } @VisibleForTesting // productionVisibility = Visibility.PRIVATE public CppCompilationContext build(ActionOwner owner, MiddlemanFactory middlemanFactory) { // 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 CppCompilationContext( new CommandLineContext( ImmutableList.copyOf(includeDirs), ImmutableList.copyOf(quoteIncludeDirs), ImmutableList.copyOf(systemIncludeDirs), ImmutableList.copyOf(defines)), prerequisiteStampFile == null ? ImmutableSet.<Artifact>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 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<Artifact> modularHeaders; /** All header files that are contained in this module. */ private final ImmutableSet<Artifact> textualHeaders; /** * All transitive modules that this context depends on, excluding headerModule. */ private final NestedSet<Artifact> transitiveModules; /** * All information about mapping transitive headers to transitive modules. */ public final NestedSet<TransitiveModuleHeaders> transitiveModuleHeaders; public ModuleInfo( Artifact headerModule, ImmutableSet<Artifact> modularHeaders, ImmutableSet<Artifact> textualHeaders, NestedSet<Artifact> transitiveModules, NestedSet<TransitiveModuleHeaders> transitiveModuleHeaders) { this.headerModule = headerModule; this.modularHeaders = modularHeaders; this.textualHeaders = textualHeaders; this.transitiveModules = transitiveModules; this.transitiveModuleHeaders = transitiveModuleHeaders; } public Collection<TransitiveModuleHeaders> getUsedModules(Set<Artifact> usedHeaders) { List<TransitiveModuleHeaders> 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<Artifact> modularHeaders = new LinkedHashSet<>(); private final Set<Artifact> textualHeaders = new LinkedHashSet<>(); private final NestedSetBuilder<Artifact> transitiveModules = NestedSetBuilder.stableOrder(); private final NestedSetBuilder<TransitiveModuleHeaders> transitiveModuleHeaders = NestedSetBuilder.stableOrder(); public Builder setHeaderModule(Artifact headerModule) { this.headerModule = headerModule; return this; } public Builder addHeaders(Collection<Artifact> headers) { this.modularHeaders.addAll(headers); return this; } public Builder addTextualHeaders(Collection<Artifact> 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<Artifact> modularHeaders = ImmutableSet.copyOf(this.modularHeaders); NestedSet<Artifact> 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 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<Artifact> 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<Artifact> transitiveModules; public TransitiveModuleHeaders( Artifact module, ImmutableSet<Artifact> headers, NestedSet<Artifact> transitiveModules) { this.module = module; this.headers = headers; this.transitiveModules = transitiveModules; } public Artifact getModule() { return module; } public Collection<Artifact> getTransitiveModules() { return transitiveModules.toCollection(); } } }