aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/cpp
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
commitd08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch)
tree5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/main/java/com/google/devtools/build/lib/rules/cpp
Update from Google.
-- MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/cpp')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java635
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java678
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationOutputs.java207
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcExecutionDynamicLibrariesProvider.java49
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java395
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java905
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java357
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsProvider.java50
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsStore.java136
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingOutputs.java243
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcNativeLibraryProvider.java43
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcSpecificLinkParamsProvider.java48
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcTest.java36
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java249
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java802
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java226
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java71
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppBuildInfo.java89
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java918
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java1356
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java439
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionContext.java84
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java1691
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java174
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugFileProvider.java54
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugPackageProvider.java69
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java141
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java529
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java1074
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionContext.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java707
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMap.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java185
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java646
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java104
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppRunfilesProvider.java85
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppSemantics.java49
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationIdentifier.java132
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java327
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationOptions.java29
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/DiscoveredSourceInputsHelper.java139
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/DwoArtifactsCollector.java120
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/ExtractInclusionAction.java85
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java212
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/FdoStubAction.java70
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java679
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/HeaderTargetModuleMapProvider.java42
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/ImplementedCcPublicLibrariesProvider.java42
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeParser.java711
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java51
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScannable.java90
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java177
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanningContext.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/Link.java274
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java1121
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LinkStrategy.java35
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInput.java51
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java353
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LinkingMode.java46
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LipoContextProvider.java58
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LocalGccStrategy.java96
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/LocalLinkStrategy.java62
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/RemoteIncludeExtractor.java52
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java234
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/TransitiveLipoInfoProvider.java51
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/WriteBuildInfoHeaderAction.java194
66 files changed, 19189 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
new file mode 100644
index 0000000000..6efcd9daeb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
@@ -0,0 +1,635 @@
+// Copyright 2014 Google Inc. 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.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.Util;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.rules.test.BaselineCoverageAction;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.OsUtils;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A ConfiguredTarget for <code>cc_binary</code> rules.
+ */
+public abstract class CcBinary implements RuleConfiguredTargetFactory {
+
+ private final CppSemantics semantics;
+
+ protected CcBinary(CppSemantics semantics) {
+ this.semantics = semantics;
+ }
+
+ // TODO(bazel-team): should this use Link.SHARED_LIBRARY_FILETYPES?
+ private static final FileTypeSet SHARED_LIBRARY_FILETYPES = FileTypeSet.of(
+ CppFileTypes.SHARED_LIBRARY,
+ CppFileTypes.VERSIONED_SHARED_LIBRARY);
+
+ /**
+ * The maximum number of inputs for any single .dwp generating action. For cases where
+ * this value is exceeded, the action is split up into "batches" that fall under the limit.
+ * See {@link #createDebugPackagerActions} for details.
+ */
+ @VisibleForTesting
+ public static final int MAX_INPUTS_PER_DWP_ACTION = 100;
+
+ /**
+ * Intermediate dwps are written to this subdirectory under the main dwp's output path.
+ */
+ @VisibleForTesting
+ public static final String INTERMEDIATE_DWP_DIR = "_dwps";
+
+ private static Runfiles collectRunfiles(RuleContext context,
+ CcCommon common,
+ CcLinkingOutputs linkingOutputs,
+ CppCompilationContext cppCompilationContext,
+ LinkStaticness linkStaticness,
+ NestedSet<Artifact> filesToBuild,
+ Iterable<Artifact> fakeLinkerInputs,
+ boolean fake) {
+ Runfiles.Builder builder = new Runfiles.Builder();
+ Function<TransitiveInfoCollection, Runfiles> runfilesMapping =
+ CppRunfilesProvider.runfilesFunction(linkStaticness != LinkStaticness.DYNAMIC);
+ boolean linkshared = isLinkShared(context);
+ builder.addTransitiveArtifacts(filesToBuild);
+ // Add the shared libraries to the runfiles. This adds any shared libraries that are in the
+ // srcs of this target.
+ builder.addArtifacts(linkingOutputs.getLibrariesForRunfiles(true));
+ builder.addRunfiles(context, RunfilesProvider.DEFAULT_RUNFILES);
+ builder.add(context, runfilesMapping);
+ CcToolchainProvider toolchain = CppHelper.getToolchain(context);
+ // Add the C++ runtime libraries if linking them dynamically.
+ if (linkStaticness == LinkStaticness.DYNAMIC) {
+ builder.addTransitiveArtifacts(toolchain.getDynamicRuntimeLinkInputs());
+ }
+ // For cc_binary and cc_test rules, there is an implicit dependency on
+ // the malloc library package, which is specified by the "malloc" attribute.
+ // As the BUILD encyclopedia says, the "malloc" attribute should be ignored
+ // if linkshared=1.
+ if (!linkshared) {
+ TransitiveInfoCollection malloc = CppHelper.mallocForTarget(context);
+ builder.addTarget(malloc, RunfilesProvider.DEFAULT_RUNFILES);
+ builder.addTarget(malloc, runfilesMapping);
+ }
+
+ if (fake) {
+ // Add the object files, libraries, and linker scripts that are used to
+ // link this executable.
+ builder.addSymlinksToArtifacts(Iterables.filter(fakeLinkerInputs, Artifact.MIDDLEMAN_FILTER));
+ // The crosstool inputs for the link action are not sufficient; we also need the crosstool
+ // inputs for compilation. Node that these cannot be middlemen because Runfiles does not
+ // know how to expand them.
+ builder.addTransitiveArtifacts(toolchain.getCrosstool());
+ builder.addTransitiveArtifacts(toolchain.getLibcLink());
+ // Add the sources files that are used to compile the object files.
+ // We add the headers in the transitive closure and our own sources in the srcs
+ // attribute. We do not provide the auxiliary inputs, because they are only used when we
+ // do FDO compilation, and cc_fake_binary does not support FDO.
+ builder.addSymlinksToArtifacts(
+ Iterables.transform(common.getCAndCppSources(), Pair.<Artifact, Label>firstFunction()));
+ builder.addSymlinksToArtifacts(cppCompilationContext.getDeclaredIncludeSrcs());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public ConfiguredTarget create(RuleContext context) {
+ return CcBinary.init(semantics, context, /*fake =*/ false, /*useTestOnlyFlags =*/ false);
+ }
+
+ public static ConfiguredTarget init(CppSemantics semantics, RuleContext ruleContext, boolean fake,
+ boolean useTestOnlyFlags) {
+ ruleContext.checkSrcsSamePackage(true);
+ CcCommon common = new CcCommon(ruleContext);
+ CppConfiguration cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
+
+ LinkTargetType linkType =
+ isLinkShared(ruleContext) ? LinkTargetType.DYNAMIC_LIBRARY : LinkTargetType.EXECUTABLE;
+
+ CcLibraryHelper helper = new CcLibraryHelper(ruleContext, semantics)
+ .setLinkType(linkType)
+ .setHeadersCheckingMode(common.determineHeadersCheckingMode())
+ .addCopts(common.getCopts())
+ .setNoCopts(common.getNoCopts())
+ .addLinkopts(common.getLinkopts())
+ .addDefines(common.getDefines())
+ .addCompilationPrerequisites(common.getSharedLibrariesFromSrcs())
+ .addCompilationPrerequisites(common.getStaticLibrariesFromSrcs())
+ .addSources(common.getCAndCppSources())
+ .addPrivateHeaders(FileType.filter(
+ ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list(),
+ CppFileTypes.CPP_HEADER))
+ .addObjectFiles(common.getObjectFilesFromSrcs(false))
+ .addPicObjectFiles(common.getObjectFilesFromSrcs(true))
+ .addPicIndependentObjectFiles(common.getLinkerScripts())
+ .addDeps(ruleContext.getPrerequisites("deps", Mode.TARGET))
+ .addDeps(ImmutableList.of(CppHelper.mallocForTarget(ruleContext)))
+ .setEnableLayeringCheck(ruleContext.getFeatures().contains(CppRuleClasses.LAYERING_CHECK))
+ .addSystemIncludeDirs(common.getSystemIncludeDirs())
+ .addIncludeDirs(common.getIncludeDirs())
+ .addLooseIncludeDirs(common.getLooseIncludeDirs())
+ .setFake(fake);
+
+ CcLibraryHelper.Info info = helper.build();
+ CppCompilationContext cppCompilationContext = info.getCppCompilationContext();
+ CcCompilationOutputs ccCompilationOutputs = info.getCcCompilationOutputs();
+
+ // if cc_binary includes "linkshared=1", then gcc will be invoked with
+ // linkopt "-shared", which causes the result of linking to be a shared
+ // library. In this case, the name of the executable target should end
+ // in ".so".
+ PathFragment executableName = Util.getWorkspaceRelativePath(
+ ruleContext.getTarget(), "", OsUtils.executableExtension());
+ CppLinkAction.Builder linkActionBuilder = determineLinkerArguments(
+ ruleContext, common, cppConfiguration, ccCompilationOutputs,
+ cppCompilationContext.getCompilationPrerequisites(), fake, executableName);
+ linkActionBuilder.setUseTestOnlyFlags(useTestOnlyFlags);
+ linkActionBuilder.addNonLibraryInputs(ccCompilationOutputs.getHeaderTokenFiles());
+
+ CcToolchainProvider ccToolchain = CppHelper.getToolchain(ruleContext);
+ LinkStaticness linkStaticness = getLinkStaticness(ruleContext, common, cppConfiguration);
+ if (linkStaticness == LinkStaticness.DYNAMIC) {
+ linkActionBuilder.setRuntimeInputs(
+ ccToolchain.getDynamicRuntimeLinkMiddleman(),
+ ccToolchain.getDynamicRuntimeLinkInputs());
+ } else {
+ linkActionBuilder.setRuntimeInputs(
+ ccToolchain.getStaticRuntimeLinkMiddleman(),
+ ccToolchain.getStaticRuntimeLinkInputs());
+ // Only force a static link of libgcc if static runtime linking is enabled (which
+ // can't be true if runtimeInputs is empty).
+ // TODO(bazel-team): Move this to CcToolchain.
+ if (!ccToolchain.getStaticRuntimeLinkInputs().isEmpty()) {
+ linkActionBuilder.addLinkopt("-static-libgcc");
+ }
+ }
+
+ linkActionBuilder.setLinkType(linkType);
+ linkActionBuilder.setLinkStaticness(linkStaticness);
+ linkActionBuilder.setFake(fake);
+
+ // store immutable context now, recreate builder later
+ CppLinkAction.Context linkContext = new CppLinkAction.Context(linkActionBuilder);
+
+ CppLinkAction linkAction = linkActionBuilder.build();
+ ruleContext.registerAction(linkAction);
+ LibraryToLink outputLibrary = linkAction.getOutputLibrary();
+ Iterable<Artifact> fakeLinkerInputs =
+ fake ? linkAction.getInputs() : ImmutableList.<Artifact>of();
+ Artifact executable = outputLibrary.getArtifact();
+ CcLinkingOutputs.Builder linkingOutputsBuilder = new CcLinkingOutputs.Builder();
+ if (isLinkShared(ruleContext)) {
+ if (CppFileTypes.SHARED_LIBRARY.matches(executableName)) {
+ linkingOutputsBuilder.addDynamicLibrary(outputLibrary);
+ linkingOutputsBuilder.addExecutionDynamicLibrary(outputLibrary);
+ } else {
+ ruleContext.attributeError("linkshared", "'linkshared' used in non-shared library");
+ }
+ }
+ // Also add all shared libraries from srcs.
+ for (Artifact library : common.getSharedLibrariesFromSrcs()) {
+ LibraryToLink symlink = common.getDynamicLibrarySymlink(library, true);
+ linkingOutputsBuilder.addDynamicLibrary(symlink);
+ linkingOutputsBuilder.addExecutionDynamicLibrary(symlink);
+ }
+ CcLinkingOutputs linkingOutputs = linkingOutputsBuilder.build();
+ NestedSet<Artifact> filesToBuild = NestedSetBuilder.create(Order.STABLE_ORDER, executable);
+
+ // Create the stripped binary, but don't add it to filesToBuild; it's only built when requested.
+ Artifact strippedFile = ruleContext.getImplicitOutputArtifact(
+ CppRuleClasses.CC_BINARY_STRIPPED);
+ createStripAction(ruleContext, cppConfiguration, executable, strippedFile);
+
+ DwoArtifactsCollector dwoArtifacts =
+ collectTransitiveDwoArtifacts(ruleContext, common, cppConfiguration, ccCompilationOutputs);
+ Artifact dwpFile =
+ ruleContext.getImplicitOutputArtifact(CppRuleClasses.CC_BINARY_DEBUG_PACKAGE);
+ createDebugPackagerActions(ruleContext, cppConfiguration, dwpFile, dwoArtifacts);
+
+ // The debug package should include the dwp file only if it was explicitly requested.
+ Artifact explicitDwpFile = dwpFile;
+ if (!cppConfiguration.useFission()) {
+ explicitDwpFile = null;
+ }
+
+ // TODO(bazel-team): Do we need to put original shared libraries (along with
+ // mangled symlinks) into the RunfilesSupport object? It does not seem
+ // logical since all symlinked libraries will be linked anyway and would
+ // not require manual loading but if we do, then we would need to collect
+ // their names and use a different constructor below.
+ Runfiles runfiles = collectRunfiles(ruleContext, common, linkingOutputs,
+ cppCompilationContext, linkStaticness, filesToBuild, fakeLinkerInputs, fake);
+ RunfilesSupport runfilesSupport = RunfilesSupport.withExecutable(
+ ruleContext, runfiles, executable, ruleContext.getConfiguration().buildRunfiles());
+
+ TransitiveLipoInfoProvider transitiveLipoInfo;
+ if (cppConfiguration.isLipoContextCollector()) {
+ transitiveLipoInfo = common.collectTransitiveLipoLabels(ccCompilationOutputs);
+ } else {
+ transitiveLipoInfo = TransitiveLipoInfoProvider.EMPTY;
+ }
+
+ RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext);
+ common.addTransitiveInfoProviders(
+ ruleBuilder, filesToBuild, ccCompilationOutputs, cppCompilationContext, linkingOutputs,
+ dwoArtifacts, transitiveLipoInfo);
+
+ Map<Artifact, IncludeScannable> scannableMap = new LinkedHashMap<>();
+ if (cppConfiguration.isLipoContextCollector()) {
+ for (IncludeScannable scannable : transitiveLipoInfo.getTransitiveIncludeScannables()) {
+ // These should all be CppCompileActions, which should have only one source file.
+ // This is also checked when they are put into the nested set.
+ Artifact source =
+ Iterables.getOnlyElement(scannable.getIncludeScannerSources());
+ scannableMap.put(source, scannable);
+ }
+ }
+
+ return ruleBuilder
+ .add(RunfilesProvider.class, RunfilesProvider.simple(runfiles))
+ .add(
+ CppDebugPackageProvider.class,
+ new CppDebugPackageProvider(strippedFile, executable, explicitDwpFile))
+ .setRunfilesSupport(runfilesSupport, executable)
+ .setBaselineCoverageArtifacts(createBaselineCoverageArtifacts(
+ ruleContext, common, ccCompilationOutputs, fake))
+ .addProvider(LipoContextProvider.class, new LipoContextProvider(
+ cppCompilationContext, ImmutableMap.copyOf(scannableMap)))
+ .addProvider(CppLinkAction.Context.class, linkContext)
+ .build();
+ }
+
+ /**
+ * Creates an action to strip an executable.
+ */
+ private static void createStripAction(RuleContext context,
+ CppConfiguration cppConfiguration, Artifact input, Artifact output) {
+ context.registerAction(new SpawnAction.Builder()
+ .addInput(input)
+ .addTransitiveInputs(CppHelper.getToolchain(context).getStrip())
+ .addOutput(output)
+ .useDefaultShellEnvironment()
+ .setExecutable(cppConfiguration.getStripExecutable())
+ .addArguments("-S", "-p", "-o", output.getExecPathString())
+ .addArguments("-R", ".gnu.switches.text.quote_paths")
+ .addArguments("-R", ".gnu.switches.text.bracket_paths")
+ .addArguments("-R", ".gnu.switches.text.system_paths")
+ .addArguments("-R", ".gnu.switches.text.cpp_defines")
+ .addArguments("-R", ".gnu.switches.text.cpp_includes")
+ .addArguments("-R", ".gnu.switches.text.cl_args")
+ .addArguments("-R", ".gnu.switches.text.lipo_info")
+ .addArguments("-R", ".gnu.switches.text.annotation")
+ .addArguments(cppConfiguration.getStripOpts())
+ .addArgument(input.getExecPathString())
+ .setProgressMessage("Stripping " + output.prettyPrint() + " for " + context.getLabel())
+ .setMnemonic("CcStrip")
+ .build(context));
+ }
+
+ /**
+ * Given 'temps', traverse this target and its dependencies and collect up all
+ * the object files, libraries, linker options, linkstamps attributes and linker scripts.
+ */
+ private static CppLinkAction.Builder determineLinkerArguments(RuleContext context,
+ CcCommon common, CppConfiguration cppConfiguration, CcCompilationOutputs compilationOutputs,
+ ImmutableSet<Artifact> compilationPrerequisites,
+ boolean fake, PathFragment executableName) {
+ CppLinkAction.Builder builder = new CppLinkAction.Builder(context, executableName)
+ .setCrosstoolInputs(CppHelper.getToolchain(context).getLink())
+ .addNonLibraryInputs(compilationPrerequisites);
+
+ // Determine the object files to link in.
+ boolean usePic = CppHelper.usePic(context, !isLinkShared(context)) && !fake;
+ Iterable<Artifact> compiledObjectFiles = compilationOutputs.getObjectFiles(usePic);
+
+ if (fake) {
+ builder.addFakeNonLibraryInputs(compiledObjectFiles);
+ } else {
+ builder.addNonLibraryInputs(compiledObjectFiles);
+ }
+
+ builder.addNonLibraryInputs(common.getObjectFilesFromSrcs(usePic));
+ builder.addNonLibraryInputs(common.getLinkerScripts());
+
+ // Determine the libraries to link in.
+ // First libraries from srcs. Shared library artifacts here are substituted with mangled symlink
+ // artifacts generated by getDynamicLibraryLink(). This is done to minimize number of -rpath
+ // entries during linking process.
+ for (Artifact library : common.getLibrariesFromSrcs()) {
+ if (SHARED_LIBRARY_FILETYPES.matches(library.getFilename())) {
+ builder.addLibrary(common.getDynamicLibrarySymlink(library, true));
+ } else {
+ builder.addLibrary(LinkerInputs.opaqueLibraryToLink(library));
+ }
+ }
+
+ // Then libraries from the closure of deps.
+ List<String> linkopts = new ArrayList<>();
+ Map<Artifact, ImmutableList<Artifact>> linkstamps = new LinkedHashMap<>();
+
+ NestedSet<LibraryToLink> librariesInDepsClosure =
+ findLibrariesToLinkInDepsClosure(context, common, cppConfiguration, linkopts, linkstamps);
+ builder.addLinkopts(linkopts);
+ builder.addLinkstamps(linkstamps);
+
+ builder.addLibraries(librariesInDepsClosure);
+ return builder;
+ }
+
+ /**
+ * Explore the transitive closure of our deps to collect linking information.
+ */
+ private static NestedSet<LibraryToLink> findLibrariesToLinkInDepsClosure(
+ RuleContext context,
+ CcCommon common,
+ CppConfiguration cppConfiguration,
+ List<String> linkopts,
+ Map<Artifact,
+ ImmutableList<Artifact>> linkstamps) {
+ // This is true for both FULLY STATIC and MOSTLY STATIC linking.
+ boolean linkingStatically =
+ getLinkStaticness(context, common, cppConfiguration) != LinkStaticness.DYNAMIC;
+
+ CcLinkParams linkParams = collectCcLinkParams(
+ context, common, linkingStatically, isLinkShared(context));
+ linkopts.addAll(linkParams.flattenedLinkopts());
+ linkstamps.putAll(CppHelper.resolveLinkstamps(context, linkParams));
+ return linkParams.getLibraries();
+ }
+
+ /**
+ * Gets the linkopts to use for this binary. These options are NOT used when
+ * linking other binaries that depend on this binary.
+ *
+ * @return a new List instance that contains the linkopts for this binary
+ * target.
+ */
+ private static ImmutableList<String> getBinaryLinkopts(RuleContext context,
+ CcCommon common) {
+ List<String> linkopts = new ArrayList<>();
+ if (isLinkShared(context)) {
+ linkopts.add("-shared");
+ }
+ linkopts.addAll(common.getLinkopts());
+ return ImmutableList.copyOf(linkopts);
+ }
+
+ private static boolean linkstaticAttribute(RuleContext context) {
+ return context.attributes().get("linkstatic", Type.BOOLEAN);
+ }
+
+ /**
+ * Returns "true" if the {@code linkshared} attribute exists and is set.
+ */
+ private static final boolean isLinkShared(RuleContext context) {
+ return context.getRule().getRuleClassObject().hasAttr("linkshared", Type.BOOLEAN)
+ && context.attributes().get("linkshared", Type.BOOLEAN);
+ }
+
+ private static final boolean dashStaticInLinkopts(CcCommon common,
+ CppConfiguration cppConfiguration) {
+ return common.getLinkopts().contains("-static")
+ || cppConfiguration.getLinkOptions().contains("-static");
+ }
+
+ private static final LinkStaticness getLinkStaticness(RuleContext context,
+ CcCommon common, CppConfiguration cppConfiguration) {
+ if (cppConfiguration.getDynamicMode() == DynamicMode.FULLY) {
+ return LinkStaticness.DYNAMIC;
+ } else if (dashStaticInLinkopts(common, cppConfiguration)) {
+ return LinkStaticness.FULLY_STATIC;
+ } else if (cppConfiguration.getDynamicMode() == DynamicMode.OFF
+ || linkstaticAttribute(context)) {
+ return LinkStaticness.MOSTLY_STATIC;
+ } else {
+ return LinkStaticness.DYNAMIC;
+ }
+ }
+
+ /**
+ * Collects .dwo artifacts either transitively or directly, depending on the link type.
+ *
+ * <p>For a cc_binary, we only include the .dwo files corresponding to the .o files that are
+ * passed into the link. For static linking, this includes all transitive dependencies. But
+ * for dynamic linking, dependencies are separately linked into their own shared libraries,
+ * so we don't need them here.
+ */
+ private static DwoArtifactsCollector collectTransitiveDwoArtifacts(RuleContext context,
+ CcCommon common, CppConfiguration cppConfiguration, CcCompilationOutputs compilationOutputs) {
+ if (getLinkStaticness(context, common, cppConfiguration) == LinkStaticness.DYNAMIC) {
+ return DwoArtifactsCollector.directCollector(compilationOutputs);
+ } else {
+ return CcCommon.collectTransitiveDwoArtifacts(context, compilationOutputs);
+ }
+ }
+
+ @VisibleForTesting
+ public static Iterable<Artifact> getDwpInputs(
+ RuleContext context, NestedSet<Artifact> picDwoArtifacts, NestedSet<Artifact> dwoArtifacts) {
+ return CppHelper.usePic(context, !isLinkShared(context)) ? picDwoArtifacts : dwoArtifacts;
+ }
+
+ /**
+ * Creates the actions needed to generate this target's "debug info package"
+ * (i.e. its .dwp file).
+ */
+ private static void createDebugPackagerActions(RuleContext context,
+ CppConfiguration cppConfiguration, Artifact dwpOutput,
+ DwoArtifactsCollector dwoArtifactsCollector) {
+ Iterable<Artifact> allInputs = getDwpInputs(context,
+ dwoArtifactsCollector.getPicDwoArtifacts(),
+ dwoArtifactsCollector.getDwoArtifacts());
+
+ // No inputs? Just generate a trivially empty .dwp.
+ //
+ // Note this condition automatically triggers for any build where fission is disabled.
+ // Because rules referencing .dwp targets may be invoked with or without fission, we need
+ // to support .dwp generation even when fission is disabled. Since no actual functionality
+ // is expected then, an empty file is appropriate.
+ if (Iterables.isEmpty(allInputs)) {
+ context.registerAction(
+ new FileWriteAction(context.getActionOwner(), dwpOutput, "", false));
+ return;
+ }
+
+ // Get the tool inputs necessary to run the dwp command.
+ NestedSet<Artifact> dwpTools = CppHelper.getToolchain(context).getDwp();
+ Preconditions.checkState(!dwpTools.isEmpty());
+
+ // We apply a hierarchical action structure to limit the maximum number of inputs to any
+ // single action.
+ //
+ // While the dwp tools consumes .dwo files, it can also consume intermediate .dwp files,
+ // allowing us to split a large input set into smaller batches of arbitrary size and order.
+ // Aside from the parallelism performance benefits this offers, this also reduces input
+ // size requirements: if a.dwo, b.dwo, c.dwo, and e.dwo are each 1 KB files, we can apply
+ // two intermediate actions DWP(a.dwo, b.dwo) --> i1.dwp and DWP(c.dwo, e.dwo) --> i2.dwp.
+ // When we then apply the final action DWP(i1.dwp, i2.dwp) --> finalOutput.dwp, the inputs
+ // to this action will usually total far less than 4 KB.
+ //
+ // This list tracks every action we'll need to generate the output .dwp with batching.
+ List<SpawnAction.Builder> packagers = new ArrayList<>();
+
+ // Step 1: generate our batches. We currently break into arbitrary batches of fixed maximum
+ // input counts, but we can always apply more intelligent heuristics if the need arises.
+ SpawnAction.Builder currentPackager = newDwpAction(cppConfiguration, dwpTools);
+ int inputsForCurrentPackager = 0;
+
+ for (Artifact dwoInput : allInputs) {
+ if (inputsForCurrentPackager == MAX_INPUTS_PER_DWP_ACTION) {
+ packagers.add(currentPackager);
+ currentPackager = newDwpAction(cppConfiguration, dwpTools);
+ inputsForCurrentPackager = 0;
+ }
+ currentPackager.addInputArgument(dwoInput);
+ inputsForCurrentPackager++;
+ }
+ packagers.add(currentPackager);
+
+ // Step 2: given the batches, create the actions.
+ if (packagers.size() == 1) {
+ // If we only have one batch, make a single "original inputs --> final output" action.
+ context.registerAction(Iterables.getOnlyElement(packagers)
+ .addArgument("-o")
+ .addOutputArgument(dwpOutput)
+ .setMnemonic("CcGenerateDwp")
+ .build(context));
+ } else {
+ // If we have multiple batches, make them all intermediate actions, then pipe their outputs
+ // into an additional action that outputs the final artifact.
+ //
+ // Note this only creates a hierarchy one level deep (i.e. we don't check if the number of
+ // intermediate outputs exceeds the maximum batch size). This is okay for current needs,
+ // which shouldn't stress those limits.
+ List<Artifact> intermediateOutputs = new ArrayList<>();
+
+ int count = 1;
+ for (SpawnAction.Builder packager : packagers) {
+ Artifact intermediateOutput =
+ getIntermediateDwpFile(context.getAnalysisEnvironment(), dwpOutput, count++);
+ context.registerAction(packager
+ .addArgument("-o")
+ .addOutputArgument(intermediateOutput)
+ .setMnemonic("CcGenerateIntermediateDwp")
+ .build(context));
+ intermediateOutputs.add(intermediateOutput);
+ }
+
+ // Now create the final action.
+ context.registerAction(newDwpAction(cppConfiguration, dwpTools)
+ .addInputArguments(intermediateOutputs)
+ .addArgument("-o")
+ .addOutputArgument(dwpOutput)
+ .setMnemonic("CcGenerateDwp")
+ .build(context));
+ }
+ }
+
+ /**
+ * Returns a new SpawnAction builder for generating dwp files, pre-initialized with
+ * standard settings.
+ */
+ private static SpawnAction.Builder newDwpAction(CppConfiguration cppConfiguration,
+ NestedSet<Artifact> dwpTools) {
+ return new SpawnAction.Builder()
+ .addTransitiveInputs(dwpTools)
+ .setExecutable(cppConfiguration.getDwpExecutable())
+ .useParameterFile(ParameterFile.ParameterFileType.UNQUOTED);
+ }
+
+ /**
+ * Creates an intermediate dwp file keyed off the name and path of the final output.
+ */
+ private static Artifact getIntermediateDwpFile(AnalysisEnvironment env, Artifact dwpOutput,
+ int orderNumber) {
+ PathFragment outputPath = dwpOutput.getRootRelativePath();
+ PathFragment intermediatePath =
+ FileSystemUtils.appendWithoutExtension(outputPath, "-" + orderNumber);
+ return env.getDerivedArtifact(
+ outputPath.getParentDirectory().getRelative(
+ INTERMEDIATE_DWP_DIR + "/" + intermediatePath.getPathString()),
+ dwpOutput.getRoot());
+ }
+
+ /**
+ * Collect link parameters from the transitive closure.
+ */
+ private static CcLinkParams collectCcLinkParams(RuleContext context, CcCommon common,
+ boolean linkingStatically, boolean linkShared) {
+ CcLinkParams.Builder builder = CcLinkParams.builder(linkingStatically, linkShared);
+
+ if (isLinkShared(context)) {
+ // CcLinkingOutputs is empty because this target is not configured yet
+ builder.addCcLibrary(context, common, false, CcLinkingOutputs.EMPTY);
+ } else {
+ builder.addTransitiveTargets(
+ context.getPrerequisites("deps", Mode.TARGET),
+ CcLinkParamsProvider.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS);
+ builder.addTransitiveTarget(CppHelper.mallocForTarget(context));
+ builder.addLinkOpts(getBinaryLinkopts(context, common));
+ }
+ return builder.build();
+ }
+
+ private static ImmutableList<Artifact> createBaselineCoverageArtifacts(
+ RuleContext context, CcCommon common, CcCompilationOutputs compilationOutputs,
+ boolean fake) {
+ if (!TargetUtils.isTestRule(context.getRule()) && !fake) {
+ Iterable<Artifact> objectFiles = compilationOutputs.getObjectFiles(
+ CppHelper.usePic(context, !isLinkShared(context)));
+ return BaselineCoverageAction.getBaselineCoverageArtifacts(context,
+ common.getInstrumentedFilesProvider(objectFiles).getInstrumentedFiles());
+ } else {
+ return ImmutableList.of();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
new file mode 100644
index 0000000000..3f5ff76a0d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
@@ -0,0 +1,678 @@
+// Copyright 2014 Google Inc. 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.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.CompilationPrerequisitesProvider;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToCompileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TempsProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.LocalMetadataCollector;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProviderImpl;
+import com.google.devtools.build.lib.shell.ShellUtils;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Common parts of the implementation of cc rules.
+ */
+public final class CcCommon {
+
+ private static final String NO_COPTS_ATTRIBUTE = "nocopts";
+
+ private static final FileTypeSet SOURCE_TYPES = FileTypeSet.of(
+ CppFileTypes.CPP_SOURCE,
+ CppFileTypes.CPP_HEADER,
+ CppFileTypes.C_SOURCE,
+ CppFileTypes.ASSEMBLER_WITH_C_PREPROCESSOR);
+
+ /**
+ * Collects all metadata files generated by C++ compilation actions that output the .o files
+ * on the input.
+ */
+ private static final LocalMetadataCollector CC_METADATA_COLLECTOR =
+ new LocalMetadataCollector() {
+ @Override
+ public void collectMetadataArtifacts(Iterable<Artifact> objectFiles,
+ AnalysisEnvironment analysisEnvironment, NestedSetBuilder<Artifact> metadataFilesBuilder) {
+ for (Artifact artifact : objectFiles) {
+ Action action = analysisEnvironment.getLocalGeneratingAction(artifact);
+ if (action instanceof CppCompileAction) {
+ addOutputs(metadataFilesBuilder, action, CppFileTypes.COVERAGE_NOTES);
+ }
+ }
+ }
+ };
+
+ /** C++ configuration */
+ private final CppConfiguration cppConfiguration;
+
+ /** The Artifacts from srcs. */
+ private final ImmutableList<Artifact> sources;
+
+ private final ImmutableList<Pair<Artifact, Label>> cAndCppSources;
+
+ /** Expanded and tokenized copts attribute. Set by initCopts(). */
+ private final ImmutableList<String> copts;
+
+ /**
+ * The expanded linkopts for this rule.
+ */
+ private final ImmutableList<String> linkopts;
+
+ private final RuleContext ruleContext;
+
+ public CcCommon(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ this.cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
+ this.sources = hasAttribute("srcs", Type.LABEL_LIST)
+ ? ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list()
+ : ImmutableList.<Artifact>of();
+
+ this.cAndCppSources = collectCAndCppSources();
+ copts = initCopts();
+ linkopts = initLinkopts();
+ }
+
+ ImmutableList<Artifact> getTemps(CcCompilationOutputs compilationOutputs) {
+ return cppConfiguration.isLipoContextCollector()
+ ? ImmutableList.<Artifact>of()
+ : compilationOutputs.getTemps();
+ }
+
+ /**
+ * Returns our own linkopts from the rule attribute. This determines linker
+ * options to use when building this target and anything that depends on it.
+ */
+ public ImmutableList<String> getLinkopts() {
+ return linkopts;
+ }
+
+ public ImmutableList<String> getCopts() {
+ return copts;
+ }
+
+ private boolean hasAttribute(String name, Type<?> type) {
+ return ruleContext.getRule().getRuleClassObject().hasAttr(name, type);
+ }
+
+ private static NestedSet<Artifact> collectExecutionDynamicLibraryArtifacts(
+ RuleContext ruleContext,
+ List<LibraryToLink> executionDynamicLibraries) {
+ Iterable<Artifact> artifacts = LinkerInputs.toLibraryArtifacts(executionDynamicLibraries);
+ if (!Iterables.isEmpty(artifacts)) {
+ return NestedSetBuilder.wrap(Order.STABLE_ORDER, artifacts);
+ }
+
+ Iterable<CcExecutionDynamicLibrariesProvider> deps = ruleContext
+ .getPrerequisites("deps", Mode.TARGET, CcExecutionDynamicLibrariesProvider.class);
+
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (CcExecutionDynamicLibrariesProvider dep : deps) {
+ builder.addTransitive(dep.getExecutionDynamicLibraryArtifacts());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Collects all .dwo artifacts in this target's transitive closure.
+ */
+ public static DwoArtifactsCollector collectTransitiveDwoArtifacts(
+ RuleContext ruleContext,
+ CcCompilationOutputs compilationOutputs) {
+ ImmutableList.Builder<TransitiveInfoCollection> deps =
+ ImmutableList.<TransitiveInfoCollection>builder();
+
+ deps.addAll(ruleContext.getPrerequisites("deps", Mode.TARGET));
+
+ if (ruleContext.getRule().getRuleClassObject().hasAttr("malloc", Type.LABEL)) {
+ deps.add(CppHelper.mallocForTarget(ruleContext));
+ }
+ if (ruleContext.getRule().getRuleClassObject().hasAttr("implementation", Type.LABEL_LIST)) {
+ deps.addAll(ruleContext.getPrerequisites("implementation", Mode.TARGET));
+ }
+
+ return compilationOutputs == null // Possible in LIPO collection mode (see initializationHook).
+ ? DwoArtifactsCollector.emptyCollector()
+ : DwoArtifactsCollector.transitiveCollector(compilationOutputs, deps.build());
+ }
+
+ public TransitiveLipoInfoProvider collectTransitiveLipoLabels(CcCompilationOutputs outputs) {
+ if (cppConfiguration.getFdoSupport().getFdoRoot() == null
+ || !cppConfiguration.isLipoContextCollector()) {
+ return TransitiveLipoInfoProvider.EMPTY;
+ }
+
+ NestedSetBuilder<IncludeScannable> scannableBuilder = NestedSetBuilder.stableOrder();
+ CppHelper.addTransitiveLipoInfoForCommonAttributes(ruleContext, outputs, scannableBuilder);
+ if (hasAttribute("implementation", Type.LABEL_LIST)) {
+ for (TransitiveLipoInfoProvider impl : AnalysisUtils.getProviders(
+ ruleContext.getPrerequisites("implementation", Mode.TARGET),
+ TransitiveLipoInfoProvider.class)) {
+ scannableBuilder.addTransitive(impl.getTransitiveIncludeScannables());
+ }
+ }
+
+ return new TransitiveLipoInfoProvider(scannableBuilder.build());
+ }
+
+ private NestedSet<LinkerInput> collectTransitiveCcNativeLibraries(
+ RuleContext ruleContext,
+ List<? extends LinkerInput> dynamicLibraries) {
+ NestedSetBuilder<LinkerInput> builder = NestedSetBuilder.linkOrder();
+ builder.addAll(dynamicLibraries);
+ for (CcNativeLibraryProvider dep :
+ ruleContext.getPrerequisites("deps", Mode.TARGET, CcNativeLibraryProvider.class)) {
+ builder.addTransitive(dep.getTransitiveCcNativeLibraries());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns a list of ({@link Artifact}, {@link Label}) pairs. Each pair represents an input
+ * source file and the label of the rule that generates it (or the label of the source file
+ * itself if it is an input file)
+ */
+ ImmutableList<Pair<Artifact, Label>> getCAndCppSources() {
+ return cAndCppSources;
+ }
+
+ private boolean shouldProcessHeaders() {
+ boolean crosstoolSupportsHeaderParsing =
+ CppHelper.getToolchain(ruleContext).supportsHeaderParsing();
+ return crosstoolSupportsHeaderParsing && (
+ ruleContext.getFeatures().contains(CppRuleClasses.PREPROCESS_HEADERS)
+ || ruleContext.getFeatures().contains(CppRuleClasses.PARSE_HEADERS));
+ }
+
+ private ImmutableList<Pair<Artifact, Label>> collectCAndCppSources() {
+ Map<Artifact, Label> map = Maps.newLinkedHashMap();
+ if (!hasAttribute("srcs", Type.LABEL_LIST)) {
+ return ImmutableList.<Pair<Artifact, Label>>of();
+ }
+ Iterable<FileProvider> providers =
+ ruleContext.getPrerequisites("srcs", Mode.TARGET, FileProvider.class);
+ // TODO(bazel-team): Move header processing logic down in the stack (to CcLibraryHelper or
+ // such).
+ boolean processHeaders = shouldProcessHeaders();
+ if (processHeaders && hasAttribute("hdrs", Type.LABEL_LIST)) {
+ providers = Iterables.concat(providers,
+ ruleContext.getPrerequisites("hdrs", Mode.TARGET, FileProvider.class));
+ }
+ for (FileProvider provider : providers) {
+ for (Artifact artifact : FileType.filter(provider.getFilesToBuild(), SOURCE_TYPES)) {
+ boolean isHeader = CppFileTypes.CPP_HEADER.matches(artifact.getExecPath());
+ if ((isHeader && !processHeaders)
+ || CppFileTypes.CPP_TEXTUAL_INCLUDE.matches(artifact.getExecPath())) {
+ continue;
+ }
+ Label oldLabel = map.put(artifact, provider.getLabel());
+ // TODO(bazel-team): We currently do not warn for duplicate headers with
+ // different labels, as that would require cleaning up the code base
+ // without significant benefit; we should eventually make this
+ // consistent one way or the other.
+ if (!isHeader && oldLabel != null && !oldLabel.equals(provider.getLabel())) {
+ ruleContext.attributeError("srcs", String.format(
+ "Artifact '%s' is duplicated (through '%s' and '%s')",
+ artifact.getExecPathString(), oldLabel, provider.getLabel()));
+ }
+ }
+ }
+
+ ImmutableList.Builder<Pair<Artifact, Label>> result = ImmutableList.builder();
+ for (Map.Entry<Artifact, Label> entry : map.entrySet()) {
+ result.add(Pair.of(entry.getKey(), entry.getValue()));
+ }
+
+ return result.build();
+ }
+
+ Iterable<Artifact> getLibrariesFromSrcs() {
+ return FileType.filter(sources, CppFileTypes.ARCHIVE, CppFileTypes.PIC_ARCHIVE,
+ CppFileTypes.ALWAYS_LINK_LIBRARY, CppFileTypes.ALWAYS_LINK_PIC_LIBRARY,
+ CppFileTypes.SHARED_LIBRARY,
+ CppFileTypes.VERSIONED_SHARED_LIBRARY);
+ }
+
+ Iterable<Artifact> getSharedLibrariesFromSrcs() {
+ return getSharedLibrariesFrom(sources);
+ }
+
+ static Iterable<Artifact> getSharedLibrariesFrom(Iterable<Artifact> collection) {
+ return FileType.filter(collection, CppFileTypes.SHARED_LIBRARY,
+ CppFileTypes.VERSIONED_SHARED_LIBRARY);
+ }
+
+ Iterable<Artifact> getStaticLibrariesFromSrcs() {
+ return FileType.filter(sources, CppFileTypes.ARCHIVE, CppFileTypes.ALWAYS_LINK_LIBRARY);
+ }
+
+ Iterable<LibraryToLink> getPicStaticLibrariesFromSrcs() {
+ return LinkerInputs.opaqueLibrariesToLink(
+ FileType.filter(sources, CppFileTypes.PIC_ARCHIVE,
+ CppFileTypes.ALWAYS_LINK_PIC_LIBRARY));
+ }
+
+ Iterable<Artifact> getObjectFilesFromSrcs(final boolean usePic) {
+ if (usePic) {
+ return Iterables.filter(sources, new Predicate<Artifact>() {
+ @Override
+ public boolean apply(Artifact artifact) {
+ String filename = artifact.getExecPathString();
+
+ // For compatibility with existing BUILD files, any ".o" files listed
+ // in srcs are assumed to be position-independent code, or
+ // at least suitable for inclusion in shared libraries, unless they
+ // end with ".nopic.o". (The ".nopic.o" extension is an undocumented
+ // feature to give users at least some control over this.) Note that
+ // some target platforms do not require shared library code to be PIC.
+ return CppFileTypes.PIC_OBJECT_FILE.matches(filename) ||
+ (CppFileTypes.OBJECT_FILE.matches(filename) && !filename.endsWith(".nopic.o"));
+ }
+ });
+ } else {
+ return FileType.filter(sources, CppFileTypes.OBJECT_FILE);
+ }
+ }
+
+ /**
+ * Returns the files from headers and does some sanity checks. Note that this method reports
+ * warnings to the {@link RuleContext} as a side effect, and so should only be called once for any
+ * given rule.
+ */
+ public static List<Artifact> getHeaders(RuleContext ruleContext) {
+ List<Artifact> hdrs = new ArrayList<>();
+ for (TransitiveInfoCollection target :
+ ruleContext.getPrerequisitesIf("hdrs", Mode.TARGET, FileProvider.class)) {
+ FileProvider provider = target.getProvider(FileProvider.class);
+ for (Artifact artifact : provider.getFilesToBuild()) {
+ if (!CppRuleClasses.DISALLOWED_HDRS_FILES.matches(artifact.getFilename())) {
+ hdrs.add(artifact);
+ } else {
+ ruleContext.attributeWarning("hdrs", "file '" + artifact.getFilename()
+ + "' from target '" + target.getLabel() + "' is not allowed in hdrs");
+ }
+ }
+ }
+ return hdrs;
+ }
+
+ /**
+ * Uses {@link #getHeaders(RuleContext)} to get the {@code hdrs} on this target. This method will
+ * return an empty list if there is no {@code hdrs} attribute on this rule type.
+ */
+ List<Artifact> getHeaders() {
+ if (!hasAttribute("hdrs", Type.LABEL_LIST)) {
+ return ImmutableList.of();
+ }
+ return getHeaders(ruleContext);
+ }
+
+ HeadersCheckingMode determineHeadersCheckingMode() {
+ HeadersCheckingMode headersCheckingMode = cppConfiguration.getHeadersCheckingMode();
+
+ // Package default overrides command line option.
+ if (ruleContext.getRule().getPackage().isDefaultHdrsCheckSet()) {
+ String value =
+ ruleContext.getRule().getPackage().getDefaultHdrsCheck().toUpperCase(Locale.ENGLISH);
+ headersCheckingMode = HeadersCheckingMode.valueOf(value);
+ }
+
+ // 'hdrs_check' attribute overrides package default.
+ if (hasAttribute("hdrs_check", Type.STRING)
+ && ruleContext.getRule().isAttributeValueExplicitlySpecified("hdrs_check")) {
+ try {
+ String value = ruleContext.attributes().get("hdrs_check", Type.STRING)
+ .toUpperCase(Locale.ENGLISH);
+ headersCheckingMode = HeadersCheckingMode.valueOf(value);
+ } catch (IllegalArgumentException e) {
+ ruleContext.attributeError("hdrs_check", "must be one of: 'loose', 'warn' or 'strict'");
+ }
+ }
+
+ return headersCheckingMode;
+ }
+
+ /**
+ * Expand and tokenize the copts and nocopts attributes.
+ */
+ private ImmutableList<String> initCopts() {
+ if (!hasAttribute("copts", Type.STRING_LIST)) {
+ return ImmutableList.<String>of();
+ }
+ // TODO(bazel-team): getAttributeCopts should not tokenize the strings.
+ // Make a warning for now.
+ List<String> tokens = new ArrayList<>();
+ for (String str : ruleContext.attributes().get("copts", Type.STRING_LIST)) {
+ tokens.clear();
+ try {
+ ShellUtils.tokenize(tokens, str);
+ if (tokens.size() > 1) {
+ ruleContext.attributeWarning("copts",
+ "each item in the list should contain only one option");
+ }
+ } catch (ShellUtils.TokenizationException e) {
+ // ignore, the error is reported in the getAttributeCopts call
+ }
+ }
+
+ Pattern nocopts = getNoCopts(ruleContext);
+ if (nocopts != null && nocopts.matcher("-Wno-future-warnings").matches()) {
+ ruleContext.attributeWarning("nocopts",
+ "Regular expression '" + nocopts.pattern() + "' is too general; for example, it matches "
+ + "'-Wno-future-warnings'. Thus it might *re-enable* compiler warnings we wish to "
+ + "disable globally. To disable all compiler warnings, add '-w' to copts instead");
+ }
+
+ return ImmutableList.<String>builder()
+ .addAll(getPackageCopts(ruleContext))
+ .addAll(CppHelper.getAttributeCopts(ruleContext, "copts"))
+ .build();
+ }
+
+ private static ImmutableList<String> getPackageCopts(RuleContext ruleContext) {
+ List<String> unexpanded = ruleContext.getRule().getPackage().getDefaultCopts();
+ return ImmutableList.copyOf(CppHelper.expandMakeVariables(ruleContext, "copts", unexpanded));
+ }
+
+ Pattern getNoCopts() {
+ return getNoCopts(ruleContext);
+ }
+
+ /**
+ * Returns nocopts pattern built from the make variable expanded nocopts
+ * attribute.
+ */
+ private static Pattern getNoCopts(RuleContext ruleContext) {
+ Pattern nocopts = null;
+ if (ruleContext.getRule().isAttrDefined(NO_COPTS_ATTRIBUTE, Type.STRING)) {
+ String nocoptsAttr = ruleContext.expandMakeVariables(NO_COPTS_ATTRIBUTE,
+ ruleContext.attributes().get(NO_COPTS_ATTRIBUTE, Type.STRING));
+ try {
+ nocopts = Pattern.compile(nocoptsAttr);
+ } catch (PatternSyntaxException e) {
+ ruleContext.attributeError(NO_COPTS_ATTRIBUTE,
+ "invalid regular expression '" + nocoptsAttr + "': " + e.getMessage());
+ }
+ }
+ return nocopts;
+ }
+
+ // TODO(bazel-team): calculating nocopts every time is not very efficient,
+ // fix this after the rule migration. The problem is that in some cases we call this after
+ // the RCT is created (so RuleContext is not accessible), in some cases during the creation.
+ // It would probably make more sense to use TransitiveInfoProviders.
+ /**
+ * Returns true if the rule context has a nocopts regex that matches the given value, false
+ * otherwise.
+ */
+ static boolean noCoptsMatches(String option, RuleContext ruleContext) {
+ Pattern nocopts = getNoCopts(ruleContext);
+ return nocopts == null ? false : nocopts.matcher(option).matches();
+ }
+
+ private static final String DEFINES_ATTRIBUTE = "defines";
+
+ /**
+ * Returns a list of define tokens from "defines" attribute.
+ *
+ * <p>We tokenize the "defines" attribute, to ensure that the handling of
+ * quotes and backslash escapes is consistent Bazel's treatment of the "copts" attribute.
+ *
+ * <p>But we require that the "defines" attribute consists of a single token.
+ */
+ public List<String> getDefines() {
+ List<String> defines = new ArrayList<>();
+ for (String define :
+ ruleContext.attributes().get(DEFINES_ATTRIBUTE, Type.STRING_LIST)) {
+ List<String> tokens = new ArrayList<>();
+ try {
+ ShellUtils.tokenize(tokens, ruleContext.expandMakeVariables(DEFINES_ATTRIBUTE, define));
+ if (tokens.size() == 1) {
+ defines.add(tokens.get(0));
+ } else if (tokens.isEmpty()) {
+ ruleContext.attributeError(DEFINES_ATTRIBUTE, "empty definition not allowed");
+ } else {
+ ruleContext.attributeError(DEFINES_ATTRIBUTE,
+ "definition contains too many tokens (found " + tokens.size()
+ + ", expecting exactly one)");
+ }
+ } catch (ShellUtils.TokenizationException e) {
+ ruleContext.attributeError(DEFINES_ATTRIBUTE, e.getMessage());
+ }
+ }
+ return defines;
+ }
+
+ /**
+ * Collects our own linkopts from the rule attribute. This determines linker
+ * options to use when building this library and anything that depends on it.
+ */
+ private final ImmutableList<String> initLinkopts() {
+ if (!hasAttribute("linkopts", Type.STRING_LIST)) {
+ return ImmutableList.<String>of();
+ }
+ List<String> ourLinkopts = ruleContext.attributes().get("linkopts", Type.STRING_LIST);
+ List<String> result = new ArrayList<>();
+ if (ourLinkopts != null) {
+ boolean allowDashStatic = !cppConfiguration.forceIgnoreDashStatic()
+ && (cppConfiguration.getDynamicMode() != DynamicMode.FULLY);
+ for (String linkopt : ourLinkopts) {
+ if (linkopt.equals("-static") && !allowDashStatic) {
+ continue;
+ }
+ CppHelper.expandAttribute(ruleContext, result, "linkopts", linkopt, true);
+ }
+ }
+ return ImmutableList.copyOf(result);
+ }
+
+ /**
+ * Determines a list of loose include directories that are only allowed to be referenced when
+ * headers checking is {@link HeadersCheckingMode#LOOSE} or {@link HeadersCheckingMode#WARN}.
+ */
+ List<PathFragment> getLooseIncludeDirs() {
+ List<PathFragment> result = new ArrayList<>();
+ // The package directory of the rule contributes includes. Note that this also covers all
+ // non-subpackage sub-directories.
+ PathFragment rulePackage = ruleContext.getLabel().getPackageFragment();
+ result.add(rulePackage);
+
+ // Gather up all the dirs from the rule's srcs as well as any of the srcs outputs.
+ if (hasAttribute("srcs", Type.LABEL_LIST)) {
+ for (FileProvider src :
+ ruleContext.getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) {
+ PathFragment packageDir = src.getLabel().getPackageFragment();
+ for (Artifact a : src.getFilesToBuild()) {
+ result.add(packageDir);
+ // Attempt to gather subdirectories that might contain include files.
+ result.add(a.getRootRelativePath().getParentDirectory());
+ }
+ }
+ }
+
+ // Add in any 'includes' attribute values as relative path fragments
+ if (ruleContext.getRule().isAttributeValueExplicitlySpecified("includes")) {
+ PathFragment packageFragment = ruleContext.getLabel().getPackageFragment();
+ // For now, anything with an 'includes' needs a blanket declaration
+ result.add(packageFragment.getRelative("**"));
+ }
+ return result;
+ }
+
+ List<PathFragment> getSystemIncludeDirs() {
+ // Add in any 'includes' attribute values as relative path fragments
+ if (!ruleContext.getRule().isAttributeValueExplicitlySpecified("includes")
+ || !cppConfiguration.useIsystemForIncludes()) {
+ return ImmutableList.of();
+ }
+ return getIncludeDirsFromIncludesAttribute();
+ }
+
+ List<PathFragment> getIncludeDirs() {
+ if (!ruleContext.getRule().isAttributeValueExplicitlySpecified("includes")
+ || cppConfiguration.useIsystemForIncludes()) {
+ return ImmutableList.of();
+ }
+ return getIncludeDirsFromIncludesAttribute();
+ }
+
+ private List<PathFragment> getIncludeDirsFromIncludesAttribute() {
+ List<PathFragment> result = new ArrayList<>();
+ PathFragment packageFragment = ruleContext.getLabel().getPackageFragment();
+ for (String includesAttr : ruleContext.attributes().get("includes", Type.STRING_LIST)) {
+ includesAttr = ruleContext.expandMakeVariables("includes", includesAttr);
+ if (includesAttr.startsWith("/")) {
+ ruleContext.attributeWarning("includes",
+ "ignoring invalid absolute path '" + includesAttr + "'");
+ continue;
+ }
+ PathFragment includesPath = packageFragment.getRelative(includesAttr).normalize();
+ if (!includesPath.isNormalized()) {
+ ruleContext.attributeError("includes",
+ "Path references a path above the execution root.");
+ }
+ result.add(includesPath);
+ result.add(ruleContext.getConfiguration().getGenfilesFragment().getRelative(includesPath));
+ }
+ return result;
+ }
+
+ /**
+ * Collects compilation prerequisite artifacts.
+ */
+ static CompilationPrerequisitesProvider collectCompilationPrerequisites(
+ RuleContext ruleContext, CppCompilationContext context) {
+ // TODO(bazel-team): Use context.getCompilationPrerequisites() instead.
+ NestedSetBuilder<Artifact> prerequisites = NestedSetBuilder.stableOrder();
+ if (ruleContext.getRule().getRuleClassObject().hasAttr("srcs", Type.LABEL_LIST)) {
+ for (FileProvider provider : ruleContext
+ .getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) {
+ prerequisites.addAll(FileType.filter(provider.getFilesToBuild(), SOURCE_TYPES));
+ }
+ }
+ prerequisites.addTransitive(context.getDeclaredIncludeSrcs());
+ return new CompilationPrerequisitesProvider(prerequisites.build());
+ }
+
+ /**
+ * Replaces shared library artifact with mangled symlink and creates related
+ * symlink action. For artifacts that should retain filename (e.g. libraries
+ * with SONAME tag), link is created to the parent directory instead.
+ *
+ * This action is performed to minimize number of -rpath entries used during
+ * linking process (by essentially "collecting" as many shared libraries as
+ * possible in the single directory), since we will be paying quadratic price
+ * for each additional entry on the -rpath.
+ *
+ * @param library Shared library artifact that needs to be mangled
+ * @param preserveName true if filename should be preserved, false - mangled.
+ * @return mangled symlink artifact.
+ */
+ public LibraryToLink getDynamicLibrarySymlink(Artifact library, boolean preserveName) {
+ return SolibSymlinkAction.getDynamicLibrarySymlink(
+ ruleContext, library, preserveName, true, ruleContext.getConfiguration());
+ }
+
+ /**
+ * Returns any linker scripts found in the dependencies of the rule.
+ */
+ Iterable<Artifact> getLinkerScripts() {
+ return FileType.filter(ruleContext.getPrerequisiteArtifacts("deps", Mode.TARGET).list(),
+ CppFileTypes.LINKER_SCRIPT);
+ }
+
+ ImmutableList<Artifact> getFilesToCompile(CcCompilationOutputs compilationOutputs) {
+ return cppConfiguration.isLipoContextCollector()
+ ? ImmutableList.<Artifact>of()
+ : compilationOutputs.getObjectFiles(CppHelper.usePic(ruleContext, false));
+ }
+
+ InstrumentedFilesProvider getInstrumentedFilesProvider(Iterable<Artifact> files) {
+ return cppConfiguration.isLipoContextCollector()
+ ? InstrumentedFilesProviderImpl.EMPTY
+ : new InstrumentedFilesProviderImpl(new InstrumentedFilesCollector(
+ ruleContext, CppRuleClasses.INSTRUMENTATION_SPEC, CC_METADATA_COLLECTOR, files));
+ }
+
+ public static FeatureConfiguration configureFeatures(RuleContext ruleContext) {
+ CcToolchainProvider toolchain = CppHelper.getToolchain(ruleContext);
+ Set<String> requestedFeatures = ImmutableSet.of(CppRuleClasses.MODULE_MAP_HOME_CWD);
+ return toolchain.getFeatures().getFeatureConfiguration(requestedFeatures);
+ }
+
+ public void addTransitiveInfoProviders(RuleConfiguredTargetBuilder builder,
+ NestedSet<Artifact> filesToBuild,
+ CcCompilationOutputs ccCompilationOutputs,
+ CppCompilationContext cppCompilationContext,
+ CcLinkingOutputs linkingOutputs,
+ DwoArtifactsCollector dwoArtifacts,
+ TransitiveLipoInfoProvider transitiveLipoInfo) {
+ List<Artifact> instrumentedObjectFiles = new ArrayList<>();
+ instrumentedObjectFiles.addAll(ccCompilationOutputs.getObjectFiles(false));
+ instrumentedObjectFiles.addAll(ccCompilationOutputs.getObjectFiles(true));
+ builder
+ .setFilesToBuild(filesToBuild)
+ .add(CppCompilationContext.class, cppCompilationContext)
+ .add(TransitiveLipoInfoProvider.class, transitiveLipoInfo)
+ .add(CcExecutionDynamicLibrariesProvider.class,
+ new CcExecutionDynamicLibrariesProvider(collectExecutionDynamicLibraryArtifacts(
+ ruleContext, linkingOutputs.getExecutionDynamicLibraries())))
+ .add(CcNativeLibraryProvider.class, new CcNativeLibraryProvider(
+ collectTransitiveCcNativeLibraries(ruleContext, linkingOutputs.getDynamicLibraries())))
+ .add(InstrumentedFilesProvider.class, getInstrumentedFilesProvider(
+ instrumentedObjectFiles))
+ .add(FilesToCompileProvider.class, new FilesToCompileProvider(
+ getFilesToCompile(ccCompilationOutputs)))
+ .add(CompilationPrerequisitesProvider.class,
+ collectCompilationPrerequisites(ruleContext, cppCompilationContext))
+ .add(TempsProvider.class, new TempsProvider(getTemps(ccCompilationOutputs)))
+ .add(CppDebugFileProvider.class, new CppDebugFileProvider(
+ dwoArtifacts.getDwoArtifacts(),
+ dwoArtifacts.getPicDwoArtifacts()));
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationOutputs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationOutputs.java
new file mode 100644
index 0000000000..b9fa4e8f49
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationOutputs.java
@@ -0,0 +1,207 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A structured representation of the compilation outputs of a C++ rule.
+ */
+public class CcCompilationOutputs {
+ /**
+ * All .o files built by the target.
+ */
+ private final ImmutableList<Artifact> objectFiles;
+
+ /**
+ * All .pic.o files built by the target.
+ */
+ private final ImmutableList<Artifact> picObjectFiles;
+
+ /**
+ * All .dwo files built by the target, corresponding to .o outputs.
+ */
+ private final ImmutableList<Artifact> dwoFiles;
+
+ /**
+ * All .pic.dwo files built by the target, corresponding to .pic.o outputs.
+ */
+ private final ImmutableList<Artifact> picDwoFiles;
+
+ /**
+ * All artifacts that are created if "--save_temps" is true.
+ */
+ private final ImmutableList<Artifact> temps;
+
+ /**
+ * All token .h.processed files created when preprocessing or parsing headers.
+ */
+ private final ImmutableList<Artifact> headerTokenFiles;
+
+ private final List<IncludeScannable> lipoScannables;
+
+ private CcCompilationOutputs(ImmutableList<Artifact> objectFiles,
+ ImmutableList<Artifact> picObjectFiles, ImmutableList<Artifact> dwoFiles,
+ ImmutableList<Artifact> picDwoFiles, ImmutableList<Artifact> temps,
+ ImmutableList<Artifact> headerTokenFiles,
+ ImmutableList<IncludeScannable> lipoScannables) {
+ this.objectFiles = objectFiles;
+ this.picObjectFiles = picObjectFiles;
+ this.dwoFiles = dwoFiles;
+ this.picDwoFiles = picDwoFiles;
+ this.temps = temps;
+ this.headerTokenFiles = headerTokenFiles;
+ this.lipoScannables = lipoScannables;
+ }
+
+ /**
+ * Returns an unmodifiable view of the .o or .pic.o files set.
+ *
+ * @param usePic whether to return .pic.o files
+ */
+ public ImmutableList<Artifact> getObjectFiles(boolean usePic) {
+ return usePic ? picObjectFiles : objectFiles;
+ }
+
+ /**
+ * Returns an unmodifiable view of the .dwo files set.
+ */
+ public ImmutableList<Artifact> getDwoFiles() {
+ return dwoFiles;
+ }
+
+ /**
+ * Returns an unmodifiable view of the .pic.dwo files set.
+ */
+ public ImmutableList<Artifact> getPicDwoFiles() {
+ return picDwoFiles;
+ }
+
+ /**
+ * Returns an unmodifiable view of the temp files set.
+ */
+ public ImmutableList<Artifact> getTemps() {
+ return temps;
+ }
+
+ /**
+ * Returns an unmodifiable view of the .h.processed files.
+ */
+ public Iterable<Artifact> getHeaderTokenFiles() {
+ return headerTokenFiles;
+ }
+
+ /**
+ * Returns the {@link IncludeScannable} objects this C++ compile action contributes to a
+ * LIPO context collector.
+ */
+ public List<IncludeScannable> getLipoScannables() {
+ return lipoScannables;
+ }
+
+ public static final class Builder {
+ private final Set<Artifact> objectFiles = new LinkedHashSet<>();
+ private final Set<Artifact> picObjectFiles = new LinkedHashSet<>();
+ private final Set<Artifact> dwoFiles = new LinkedHashSet<>();
+ private final Set<Artifact> picDwoFiles = new LinkedHashSet<>();
+ private final Set<Artifact> temps = new LinkedHashSet<>();
+ private final Set<Artifact> headerTokenFiles = new LinkedHashSet<>();
+ private final List<IncludeScannable> lipoScannables = new ArrayList<>();
+
+ public CcCompilationOutputs build() {
+ return new CcCompilationOutputs(ImmutableList.copyOf(objectFiles),
+ ImmutableList.copyOf(picObjectFiles), ImmutableList.copyOf(dwoFiles),
+ ImmutableList.copyOf(picDwoFiles), ImmutableList.copyOf(temps),
+ ImmutableList.copyOf(headerTokenFiles),
+ ImmutableList.copyOf(lipoScannables));
+ }
+
+ public Builder merge(CcCompilationOutputs outputs) {
+ this.objectFiles.addAll(outputs.objectFiles);
+ this.picObjectFiles.addAll(outputs.picObjectFiles);
+ this.dwoFiles.addAll(outputs.dwoFiles);
+ this.picDwoFiles.addAll(outputs.picDwoFiles);
+ this.temps.addAll(outputs.temps);
+ this.headerTokenFiles.addAll(outputs.headerTokenFiles);
+ this.lipoScannables.addAll(outputs.lipoScannables);
+ return this;
+ }
+
+ /**
+ * Adds an .o file.
+ */
+ public Builder addObjectFile(Artifact artifact) {
+ objectFiles.add(artifact);
+ return this;
+ }
+
+ public Builder addObjectFiles(Iterable<Artifact> artifacts) {
+ Iterables.addAll(objectFiles, artifacts);
+ return this;
+ }
+
+ /**
+ * Adds a .pic.o file.
+ */
+ public Builder addPicObjectFile(Artifact artifact) {
+ picObjectFiles.add(artifact);
+ return this;
+ }
+
+ public Builder addPicObjectFiles(Iterable<Artifact> artifacts) {
+ Iterables.addAll(picObjectFiles, artifacts);
+ return this;
+ }
+
+ public Builder addDwoFile(Artifact artifact) {
+ dwoFiles.add(artifact);
+ return this;
+ }
+
+ public Builder addPicDwoFile(Artifact artifact) {
+ picDwoFiles.add(artifact);
+ return this;
+ }
+
+ /**
+ * Adds temp files.
+ */
+ public Builder addTemps(Iterable<Artifact> artifacts) {
+ Iterables.addAll(temps, artifacts);
+ return this;
+ }
+
+ public Builder addHeaderTokenFile(Artifact artifact) {
+ headerTokenFiles.add(artifact);
+ return this;
+ }
+
+ /**
+ * Adds an {@link IncludeScannable} that this compilation output object contributes to a
+ * LIPO context collector.
+ */
+ public Builder addLipoScannable(IncludeScannable scannable) {
+ lipoScannables.add(scannable);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcExecutionDynamicLibrariesProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcExecutionDynamicLibrariesProvider.java
new file mode 100644
index 0000000000..39ce942f60
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcExecutionDynamicLibrariesProvider.java
@@ -0,0 +1,49 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+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;
+
+/**
+ * A target that provides the execution-time dynamic libraries of a C++ rule.
+ */
+@Immutable
+public final class CcExecutionDynamicLibrariesProvider implements TransitiveInfoProvider {
+ public static final CcExecutionDynamicLibrariesProvider EMPTY =
+ new CcExecutionDynamicLibrariesProvider(
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER));
+
+ private final NestedSet<Artifact> ccExecutionDynamicLibraries;
+
+ public CcExecutionDynamicLibrariesProvider(NestedSet<Artifact> ccExecutionDynamicLibraries) {
+ this.ccExecutionDynamicLibraries = ccExecutionDynamicLibraries;
+ }
+
+ /**
+ * Returns the execution-time dynamic libraries.
+ *
+ * <p>This normally returns the dynamic library created by the rule itself. However, if the rule
+ * does not create any dynamic libraries, then it returns the combined results of calling
+ * getExecutionDynamicLibraryArtifacts on all the rule's deps. This behaviour is so that this
+ * method is useful for a cc_library with deps but no srcs.
+ */
+ public NestedSet<Artifact> getExecutionDynamicLibraryArtifacts() {
+ return ccExecutionDynamicLibraries;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java
new file mode 100644
index 0000000000..428eedb6e2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java
@@ -0,0 +1,395 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AlwaysBuiltArtifactsProvider;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.rules.test.BaselineCoverageAction;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A ConfiguredTarget for <code>cc_library</code> rules.
+ */
+public abstract class CcLibrary implements RuleConfiguredTargetFactory {
+
+ private final CppSemantics semantics;
+
+ protected CcLibrary(CppSemantics semantics) {
+ this.semantics = semantics;
+ }
+
+ // These file extensions don't generate object files.
+ private static final FileTypeSet NO_OBJECT_GENERATING_FILETYPES = FileTypeSet.of(
+ CppFileTypes.CPP_HEADER, CppFileTypes.ARCHIVE, CppFileTypes.PIC_ARCHIVE,
+ CppFileTypes.ALWAYS_LINK_LIBRARY, CppFileTypes.ALWAYS_LINK_PIC_LIBRARY,
+ CppFileTypes.SHARED_LIBRARY);
+
+ private static final Predicate<LibraryToLink> PIC_STATIC_FILTER = new Predicate<LibraryToLink>() {
+ @Override
+ public boolean apply(LibraryToLink input) {
+ String name = input.getArtifact().getExecPath().getBaseName();
+ return !name.endsWith(".nopic.a") && !name.endsWith(".nopic.lo");
+ }
+ };
+
+ private static Runfiles collectRunfiles(RuleContext context,
+ CcLinkingOutputs ccLinkingOutputs,
+ boolean neverLink, boolean addDynamicRuntimeInputArtifactsToRunfiles,
+ boolean linkingStatically) {
+ Runfiles.Builder builder = new Runfiles.Builder();
+
+ // neverlink= true creates a library that will never be linked into any binary that depends on
+ // it, but instead be loaded as an extension. So we need the dynamic library for this in the
+ // runfiles.
+ builder.addArtifacts(ccLinkingOutputs.getLibrariesForRunfiles(linkingStatically && !neverLink));
+ builder.add(context, CppRunfilesProvider.runfilesFunction(linkingStatically));
+ if (context.getRule().isAttrDefined("implements", Type.LABEL_LIST)) {
+ builder.addTargets(context.getPrerequisites("implements", Mode.TARGET),
+ RunfilesProvider.DEFAULT_RUNFILES);
+ builder.addTargets(context.getPrerequisites("implements", Mode.TARGET),
+ CppRunfilesProvider.runfilesFunction(linkingStatically));
+ }
+ if (context.getRule().isAttrDefined("implementation", Type.LABEL_LIST)) {
+ builder.addTargets(context.getPrerequisites("implementation", Mode.TARGET),
+ RunfilesProvider.DEFAULT_RUNFILES);
+ builder.addTargets(context.getPrerequisites("implementation", Mode.TARGET),
+ CppRunfilesProvider.runfilesFunction(linkingStatically));
+ }
+
+ builder.addDataDeps(context);
+
+ if (addDynamicRuntimeInputArtifactsToRunfiles) {
+ builder.addTransitiveArtifacts(CppHelper.getToolchain(context).getDynamicRuntimeLinkInputs());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public ConfiguredTarget create(RuleContext context) {
+ RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(context);
+ LinkTargetType linkType = getStaticLinkType(context);
+ boolean linkStatic = context.attributes().get("linkstatic", Type.BOOLEAN);
+ init(semantics, context, builder, linkType,
+ /*neverLink =*/ false,
+ linkStatic,
+ /*collectLinkstamp =*/ true,
+ /*addDynamicRuntimeInputArtifactsToRunfiles =*/ false);
+ return builder.build();
+ }
+
+ public static void init(CppSemantics semantics, RuleContext ruleContext,
+ RuleConfiguredTargetBuilder targetBuilder, LinkTargetType linkType,
+ boolean neverLink,
+ boolean linkStatic,
+ boolean collectLinkstamp,
+ boolean addDynamicRuntimeInputArtifactsToRunfiles) {
+ final CcCommon common = new CcCommon(ruleContext);
+
+ CcLibraryHelper helper = new CcLibraryHelper(ruleContext, semantics)
+ .setLinkType(linkType)
+ .enableCcNativeLibrariesProvider()
+ .enableInterfaceSharedObjects()
+ .enableCompileProviders()
+ .setNeverLink(neverLink)
+ .setHeadersCheckingMode(common.determineHeadersCheckingMode())
+ .addCopts(common.getCopts())
+ .setNoCopts(common.getNoCopts())
+ .addLinkopts(common.getLinkopts())
+ .addDefines(common.getDefines())
+ .addCompilationPrerequisites(common.getSharedLibrariesFromSrcs())
+ .addCompilationPrerequisites(common.getStaticLibrariesFromSrcs())
+ .addSources(common.getCAndCppSources())
+ .addPublicHeaders(common.getHeaders())
+ .addObjectFiles(common.getObjectFilesFromSrcs(false))
+ .addPicObjectFiles(common.getObjectFilesFromSrcs(true))
+ .addPicIndependentObjectFiles(common.getLinkerScripts())
+ .addDeps(ruleContext.getPrerequisites("deps", Mode.TARGET))
+ .setEnableLayeringCheck(ruleContext.getFeatures().contains(CppRuleClasses.LAYERING_CHECK))
+ .setCompileHeaderModules(ruleContext.getFeatures().contains(CppRuleClasses.HEADER_MODULES))
+ .addSystemIncludeDirs(common.getSystemIncludeDirs())
+ .addIncludeDirs(common.getIncludeDirs())
+ .addLooseIncludeDirs(common.getLooseIncludeDirs())
+ .setEmitHeaderTargetModuleMaps(
+ ruleContext.getRule().getRuleClass().equals("cc_public_library"));
+
+ if (collectLinkstamp) {
+ helper.addLinkstamps(ruleContext.getPrerequisites("linkstamp", Mode.TARGET));
+ }
+
+ if (ruleContext.getRule().isAttrDefined("implements", Type.LABEL_LIST)) {
+ helper.addDeps(ruleContext.getPrerequisites("implements", Mode.TARGET));
+ }
+
+ if (ruleContext.getRule().isAttrDefined("implementation", Type.LABEL_LIST)) {
+ helper.addDeps(ruleContext.getPrerequisites("implementation", Mode.TARGET));
+ }
+
+ PathFragment soImplFilename = null;
+ if (ruleContext.getRule().isAttrDefined("outs", Type.STRING_LIST)) {
+ List<String> outs = ruleContext.attributes().get("outs", Type.STRING_LIST);
+ if (outs.size() > 1) {
+ ruleContext.attributeError("outs", "must be a singleton list");
+ } else if (outs.size() == 1) {
+ soImplFilename = CppHelper.getLinkedFilename(ruleContext, LinkTargetType.DYNAMIC_LIBRARY);
+ soImplFilename = soImplFilename.replaceName(outs.get(0));
+ if (!soImplFilename.getPathString().endsWith(".so")) { // Sanity check.
+ ruleContext.attributeError("outs", "file name must end in '.so'");
+ }
+ }
+ }
+
+ if (ruleContext.getRule().isAttrDefined("srcs", Type.LABEL_LIST)) {
+ helper.addPrivateHeaders(FileType.filter(
+ ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list(),
+ CppFileTypes.CPP_HEADER));
+ ruleContext.checkSrcsSamePackage(true);
+ }
+
+ if (common.getLinkopts().contains("-static")) {
+ ruleContext.attributeWarning("linkopts", "Using '-static' here won't work. "
+ + "Did you mean to use 'linkstatic=1' instead?");
+ }
+
+ boolean createDynamicLibrary =
+ !linkStatic && !appearsToHaveNoObjectFiles(ruleContext.attributes());
+ helper.setCreateDynamicLibrary(createDynamicLibrary);
+ helper.setDynamicLibraryPath(soImplFilename);
+
+ /*
+ * Add the libraries from srcs, if any. For static/mostly static
+ * linking we setup the dynamic libraries if there are no static libraries
+ * to choose from. Path to the libraries will be mangled to avoid using
+ * absolute path names on the -rpath, but library filenames will be
+ * preserved (since some libraries might have SONAME tag) - symlink will
+ * be created to the parent directory instead.
+ *
+ * For compatibility with existing BUILD files, any ".a" or ".lo" files listed in
+ * srcs are assumed to be position-independent code, or at least suitable for
+ * inclusion in shared libraries, unless they end with ".nopic.a" or ".nopic.lo".
+ *
+ * Note that some target platforms do not require shared library code to be PIC.
+ */
+ Iterable<LibraryToLink> staticLibrariesFromSrcs =
+ LinkerInputs.opaqueLibrariesToLink(common.getStaticLibrariesFromSrcs());
+ helper.addStaticLibraries(staticLibrariesFromSrcs);
+ helper.addPicStaticLibraries(Iterables.filter(staticLibrariesFromSrcs, PIC_STATIC_FILTER));
+ helper.addPicStaticLibraries(common.getPicStaticLibrariesFromSrcs());
+ helper.addDynamicLibraries(Iterables.transform(common.getSharedLibrariesFromSrcs(),
+ new Function<Artifact, LibraryToLink>() {
+ @Override
+ public LibraryToLink apply(Artifact library) {
+ return common.getDynamicLibrarySymlink(library, true);
+ }
+ }));
+ CcLibraryHelper.Info info = helper.build();
+
+ /*
+ * We always generate a static library, even if there aren't any source files.
+ * This keeps things simpler by avoiding special cases when making use of the library.
+ * For example, this is needed to ensure that building a library with "bazel build"
+ * will also build all of the library's "deps".
+ * However, we only generate a dynamic library if there are source files.
+ */
+ // For now, we don't add the precompiled libraries to the files to build.
+ CcLinkingOutputs linkedLibraries = info.getCcLinkingOutputsExcludingPrecompiledLibraries();
+
+ NestedSet<Artifact> artifactsToForce =
+ collectArtifactsToForce(ruleContext, common, info.getCcCompilationOutputs());
+
+ NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder();
+ filesBuilder.addAll(LinkerInputs.toLibraryArtifacts(linkedLibraries.getStaticLibraries()));
+ filesBuilder.addAll(LinkerInputs.toLibraryArtifacts(linkedLibraries.getPicStaticLibraries()));
+ filesBuilder.addAll(LinkerInputs.toNonSolibArtifacts(linkedLibraries.getDynamicLibraries()));
+ filesBuilder.addAll(
+ LinkerInputs.toNonSolibArtifacts(linkedLibraries.getExecutionDynamicLibraries()));
+
+ CcLinkingOutputs linkingOutputs = info.getCcLinkingOutputs();
+ warnAboutEmptyLibraries(
+ ruleContext, info.getCcCompilationOutputs(), linkType, linkStatic);
+ NestedSet<Artifact> filesToBuild = filesBuilder.build();
+
+ Runfiles staticRunfiles = collectRunfiles(ruleContext,
+ linkingOutputs, neverLink, addDynamicRuntimeInputArtifactsToRunfiles, true);
+ Runfiles sharedRunfiles = collectRunfiles(ruleContext,
+ linkingOutputs, neverLink, addDynamicRuntimeInputArtifactsToRunfiles, false);
+
+ List<Artifact> instrumentedObjectFiles = new ArrayList<>();
+ instrumentedObjectFiles.addAll(info.getCcCompilationOutputs().getObjectFiles(false));
+ instrumentedObjectFiles.addAll(info.getCcCompilationOutputs().getObjectFiles(true));
+ InstrumentedFilesProvider instrumentedFilesProvider =
+ common.getInstrumentedFilesProvider(instrumentedObjectFiles);
+ targetBuilder
+ .setFilesToBuild(filesToBuild)
+ .addProviders(info.getProviders())
+ .add(InstrumentedFilesProvider.class, instrumentedFilesProvider)
+ .add(RunfilesProvider.class, RunfilesProvider.withData(staticRunfiles, sharedRunfiles))
+ // Remove this?
+ .add(CppRunfilesProvider.class, new CppRunfilesProvider(staticRunfiles, sharedRunfiles))
+ .setBaselineCoverageArtifacts(BaselineCoverageAction.getBaselineCoverageArtifacts(
+ ruleContext, instrumentedFilesProvider.getInstrumentedFiles()))
+ .add(ImplementedCcPublicLibrariesProvider.class,
+ new ImplementedCcPublicLibrariesProvider(getImplementedCcPublicLibraries(ruleContext)))
+ .add(AlwaysBuiltArtifactsProvider.class,
+ new AlwaysBuiltArtifactsProvider(artifactsToForce));
+ }
+
+ private static NestedSet<Artifact> collectArtifactsToForce(RuleContext ruleContext,
+ CcCommon common, CcCompilationOutputs ccCompilationOutputs) {
+ // Ensure that we build all the dependencies, otherwise users may get confused.
+ NestedSetBuilder<Artifact> artifactsToForceBuilder = NestedSetBuilder.stableOrder();
+ artifactsToForceBuilder.addTransitive(
+ NestedSetBuilder.wrap(Order.STABLE_ORDER, common.getFilesToCompile(ccCompilationOutputs)));
+ for (AlwaysBuiltArtifactsProvider dep :
+ ruleContext.getPrerequisites("deps", Mode.TARGET, AlwaysBuiltArtifactsProvider.class)) {
+ artifactsToForceBuilder.addTransitive(dep.getArtifactsToAlwaysBuild());
+ }
+ return artifactsToForceBuilder.build();
+ }
+
+ /**
+ * Returns the type of the generated static library.
+ */
+ private static LinkTargetType getStaticLinkType(RuleContext context) {
+ return context.attributes().get("alwayslink", Type.BOOLEAN)
+ ? LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY
+ : LinkTargetType.STATIC_LIBRARY;
+ }
+
+ private static void warnAboutEmptyLibraries(RuleContext ruleContext,
+ CcCompilationOutputs ccCompilationOutputs, LinkTargetType linkType,
+ boolean linkstaticAttribute) {
+ if (ruleContext.getFragment(CppConfiguration.class).isLipoContextCollector()) {
+ // Do not signal warnings in the lipo context collector configuration. These will be duly
+ // signaled in the target configuration, and there can be spurious warnings since targets in
+ // the LIPO context collector configuration do not compile anything.
+ return;
+ }
+ if (ccCompilationOutputs.getObjectFiles(false).isEmpty()
+ && ccCompilationOutputs.getObjectFiles(true).isEmpty()) {
+ if (linkType == LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY
+ || linkType == LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY) {
+ ruleContext.attributeWarning("alwayslink",
+ "'alwayslink' has no effect if there are no 'srcs'");
+ }
+ if (!linkstaticAttribute && !appearsToHaveNoObjectFiles(ruleContext.attributes())) {
+ ruleContext.attributeWarning("linkstatic",
+ "setting 'linkstatic=1' is recommended if there are no object files");
+ }
+ } else {
+ if (!linkstaticAttribute && appearsToHaveNoObjectFiles(ruleContext.attributes())) {
+ Artifact element = ccCompilationOutputs.getObjectFiles(false).isEmpty()
+ ? ccCompilationOutputs.getObjectFiles(true).get(0)
+ : ccCompilationOutputs.getObjectFiles(false).get(0);
+ ruleContext.attributeWarning("srcs",
+ "this library appears at first glance to have no object files, "
+ + "but on closer inspection it does have something to link, e.g. "
+ + element.prettyPrint() + ". "
+ + "(You may have used some very confusing rule names in srcs? "
+ + "Or the library consists entirely of a linker script?) "
+ + "Bazel assumed linkstatic=1, but this may be inappropriate. "
+ + "You may need to add an explicit '.cc' file to 'srcs'. "
+ + "Alternatively, add 'linkstatic=1' to suppress this warning");
+ }
+ }
+ }
+
+ private static ImmutableList<Label> getImplementedCcPublicLibraries(RuleContext context) {
+ if (context.getRule().getRuleClassObject().hasAttr("implements", Type.LABEL_LIST)) {
+ return ImmutableList.copyOf(context.attributes().get("implements", Type.LABEL_LIST));
+ } else {
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Returns true if the rule (which must be a cc_library rule)
+ * appears to have no object files. This only looks at the rule
+ * itself, not at any other rules (from this package or other
+ * packages) that it might reference.
+ *
+ * <p>
+ * In some cases, this may return "false" even
+ * though the rule actually has no object files.
+ * For example, it will return false for a rule such as
+ * <code>cc_library(name = 'foo', srcs = [':bar'])</code>
+ * because we can't tell what ':bar' is; it might
+ * be a genrule that generates a source file, or it might
+ * be a genrule that generates a header file.
+ *
+ * <p>
+ * In other cases, this may return "true" even
+ * though the rule actually does have object files.
+ * For example, it will return true for a rule such as
+ * <code>cc_library(name = 'foo', srcs = ['bar.h'])</code>
+ * but as in the other example above, we can't tell whether
+ * 'bar.h' is a file name or a rule name, and 'bar.h' could
+ * in fact be the name of a genrule that generates a source file.
+ */
+ public static boolean appearsToHaveNoObjectFiles(AttributeMap rule) {
+ // Temporary hack while configurable attributes is under development. This has no effect
+ // for any rule that doesn't use configurable attributes.
+ // TODO(bazel-team): remove this hack for a more principled solution.
+ try {
+ rule.get("srcs", Type.LABEL_LIST);
+ } catch (ClassCastException e) {
+ // "srcs" is actually a configurable selector. Assume object files are possible somewhere.
+ return false;
+ }
+
+ List<Label> srcs = rule.get("srcs", Type.LABEL_LIST);
+ if (srcs != null) {
+ for (Label srcfile : srcs) {
+ /*
+ * We cheat a little bit here by looking at the file extension
+ * of the Label treated as file name. In general that might
+ * not necessarily work, because of the possibility that the
+ * user might give a rule a funky name ending in one of these
+ * extensions, e.g.
+ * genrule(name = 'foo.h', outs = ['foo.cc'], ...) // Funky rule name!
+ * cc_library(name = 'bar', srcs = ['foo.h']) // This DOES have object files.
+ */
+ if (!NO_OBJECT_GENERATING_FILETYPES.matches(srcfile.getName())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java
new file mode 100644
index 0000000000..3812bf5001
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java
@@ -0,0 +1,905 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.CompilationPrerequisitesProvider;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToCompileProvider;
+import com.google.devtools.build.lib.analysis.LanguageDependentFragment;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TempsProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * A class to create C/C++ compile and link actions in a way that is consistent with cc_library.
+ * Rules that generate source files and emulate cc_library on top of that should use this class
+ * instead of the lower-level APIs in CppHelper and CppModel.
+ *
+ * <p>Rules that want to use this class are required to have implicit dependencies on the
+ * toolchain, the STL, the lipo context, and so on. Optionally, they can also have copts,
+ * and malloc attributes, but note that these require explicit calls to the corresponding setter
+ * methods.
+ */
+public final class CcLibraryHelper {
+ /** Function for extracting module maps from CppCompilationDependencies. */
+ public static final Function<TransitiveInfoCollection, CppModuleMap> CPP_DEPS_TO_MODULES =
+ new Function<TransitiveInfoCollection, CppModuleMap>() {
+ @Override
+ @Nullable
+ public CppModuleMap apply(TransitiveInfoCollection dep) {
+ CppCompilationContext context = dep.getProvider(CppCompilationContext.class);
+ return context == null ? null : context.getCppModuleMap();
+ }
+ };
+
+ /**
+ * Contains the providers as well as the compilation and linking outputs, and the compilation
+ * context.
+ */
+ public static final class Info {
+ private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers;
+ private final CcCompilationOutputs compilationOutputs;
+ private final CcLinkingOutputs linkingOutputs;
+ private final CcLinkingOutputs linkingOutputsExcludingPrecompiledLibraries;
+ private final CppCompilationContext context;
+
+ private Info(Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers,
+ CcCompilationOutputs compilationOutputs, CcLinkingOutputs linkingOutputs,
+ CcLinkingOutputs linkingOutputsExcludingPrecompiledLibraries,
+ CppCompilationContext context) {
+ this.providers = Collections.unmodifiableMap(providers);
+ this.compilationOutputs = compilationOutputs;
+ this.linkingOutputs = linkingOutputs;
+ this.linkingOutputsExcludingPrecompiledLibraries =
+ linkingOutputsExcludingPrecompiledLibraries;
+ this.context = context;
+ }
+
+ public Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> getProviders() {
+ return providers;
+ }
+
+ public CcCompilationOutputs getCcCompilationOutputs() {
+ return compilationOutputs;
+ }
+
+ public CcLinkingOutputs getCcLinkingOutputs() {
+ return linkingOutputs;
+ }
+
+ /**
+ * Returns the linking outputs before adding the pre-compiled libraries. Avoid using this -
+ * pre-compiled and locally compiled libraries should be treated identically. This method only
+ * exists for backwards compatibility.
+ */
+ public CcLinkingOutputs getCcLinkingOutputsExcludingPrecompiledLibraries() {
+ return linkingOutputsExcludingPrecompiledLibraries;
+ }
+
+ public CppCompilationContext getCppCompilationContext() {
+ return context;
+ }
+
+ /**
+ * Adds the static, pic-static, and dynamic (both compile-time and execution-time) libraries to
+ * the given builder.
+ */
+ public void addLinkingOutputsTo(NestedSetBuilder<Artifact> filesBuilder) {
+ filesBuilder.addAll(LinkerInputs.toLibraryArtifacts(linkingOutputs.getStaticLibraries()));
+ filesBuilder.addAll(LinkerInputs.toLibraryArtifacts(linkingOutputs.getPicStaticLibraries()));
+ filesBuilder.addAll(LinkerInputs.toNonSolibArtifacts(linkingOutputs.getDynamicLibraries()));
+ filesBuilder.addAll(
+ LinkerInputs.toNonSolibArtifacts(linkingOutputs.getExecutionDynamicLibraries()));
+ }
+ }
+
+ private final RuleContext ruleContext;
+ private final BuildConfiguration configuration;
+ private final CppSemantics semantics;
+
+ private final List<Artifact> publicHeaders = new ArrayList<>();
+ private final List<Artifact> privateHeaders = new ArrayList<>();
+ private final List<PathFragment> additionalExportedHeaders = new ArrayList<>();
+ private final List<Pair<Artifact, Label>> sources = new ArrayList<>();
+ private final List<Artifact> objectFiles = new ArrayList<>();
+ private final List<Artifact> picObjectFiles = new ArrayList<>();
+ private final List<String> copts = new ArrayList<>();
+ @Nullable private Pattern nocopts;
+ private final List<String> linkopts = new ArrayList<>();
+ private final Set<String> defines = new LinkedHashSet<>();
+ private final List<TransitiveInfoCollection> deps = new ArrayList<>();
+ private final List<Artifact> linkstamps = new ArrayList<>();
+ private final List<Artifact> prerequisites = new ArrayList<>();
+ private final List<PathFragment> looseIncludeDirs = new ArrayList<>();
+ private final List<PathFragment> systemIncludeDirs = new ArrayList<>();
+ private final List<PathFragment> includeDirs = new ArrayList<>();
+ @Nullable private PathFragment dynamicLibraryPath;
+ private LinkTargetType linkType = LinkTargetType.STATIC_LIBRARY;
+ private HeadersCheckingMode headersCheckingMode = HeadersCheckingMode.LOOSE;
+ private boolean neverlink;
+ private boolean fake;
+
+ private final List<LibraryToLink> staticLibraries = new ArrayList<>();
+ private final List<LibraryToLink> picStaticLibraries = new ArrayList<>();
+ private final List<LibraryToLink> dynamicLibraries = new ArrayList<>();
+
+ private boolean emitCppModuleMaps = true;
+ private boolean enableLayeringCheck;
+ private boolean compileHeaderModules;
+ private boolean emitCompileActionsIfEmpty = true;
+ private boolean emitCcNativeLibrariesProvider;
+ private boolean emitCcSpecificLinkParamsProvider;
+ private boolean emitInterfaceSharedObjects;
+ private boolean emitDynamicLibrary = true;
+ private boolean checkDepsGenerateCpp = true;
+ private boolean emitCompileProviders;
+ private boolean emitHeaderTargetModuleMaps = false;
+
+ public CcLibraryHelper(RuleContext ruleContext, CppSemantics semantics) {
+ this.ruleContext = Preconditions.checkNotNull(ruleContext);
+ this.configuration = ruleContext.getConfiguration();
+ this.semantics = Preconditions.checkNotNull(semantics);
+ }
+
+ /**
+ * Add the corresponding files as header files, i.e., these files will not be compiled, but are
+ * made visible as includes to dependent rules.
+ */
+ public CcLibraryHelper addPublicHeaders(Collection<Artifact> headers) {
+ this.publicHeaders.addAll(headers);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as public header files, i.e., these files will not be compiled, but
+ * are made visible as includes to dependent rules in module maps.
+ */
+ public CcLibraryHelper addPublicHeaders(Artifact... headers) {
+ return addPublicHeaders(Arrays.asList(headers));
+ }
+
+ /**
+ * Add the corresponding files as private header files, i.e., these files will not be compiled,
+ * but are not made visible as includes to dependent rules in module maps.
+ */
+ public CcLibraryHelper addPrivateHeaders(Iterable<Artifact> privateHeaders) {
+ Iterables.addAll(this.privateHeaders, privateHeaders);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as public header files, i.e., these files will not be compiled, but
+ * are made visible as includes to dependent rules in module maps.
+ */
+ public CcLibraryHelper addAdditionalExportedHeaders(
+ Iterable<PathFragment> additionalExportedHeaders) {
+ Iterables.addAll(this.additionalExportedHeaders, additionalExportedHeaders);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as source files. These may also be header files, in which case
+ * they will not be compiled, but also not made visible as includes to dependent rules.
+ */
+ // TODO(bazel-team): This is inconsistent with the documentation on CppModel.
+ public CcLibraryHelper addSources(Collection<Artifact> sources) {
+ for (Artifact source : sources) {
+ this.sources.add(Pair.of(source, ruleContext.getLabel()));
+ }
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as source files. These may also be header files, in which case
+ * they will not be compiled, but also not made visible as includes to dependent rules.
+ */
+ // TODO(bazel-team): This is inconsistent with the documentation on CppModel.
+ public CcLibraryHelper addSources(Iterable<Pair<Artifact, Label>> sources) {
+ Iterables.addAll(this.sources, sources);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as source files. These may also be header files, in which case
+ * they will not be compiled, but also not made visible as includes to dependent rules.
+ */
+ public CcLibraryHelper addSources(Artifact... sources) {
+ return addSources(Arrays.asList(sources));
+ }
+
+ /**
+ * Add the corresponding files as linker inputs for non-PIC links. If the corresponding files are
+ * compiled with PIC, the final link may or may not fail. Note that the final link may not happen
+ * here, if {@code --start_end_lib} is enabled, but instead at any binary that transitively
+ * depends on the current rule.
+ */
+ public CcLibraryHelper addObjectFiles(Iterable<Artifact> objectFiles) {
+ Iterables.addAll(this.objectFiles, objectFiles);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as linker inputs for PIC links. If the corresponding files are not
+ * compiled with PIC, the final link may or may not fail. Note that the final link may not happen
+ * here, if {@code --start_end_lib} is enabled, but instead at any binary that transitively
+ * depends on the current rule.
+ */
+ public CcLibraryHelper addPicObjectFiles(Iterable<Artifact> picObjectFiles) {
+ Iterables.addAll(this.picObjectFiles, picObjectFiles);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as linker inputs for both PIC and non-PIC links.
+ */
+ public CcLibraryHelper addPicIndependentObjectFiles(Iterable<Artifact> objectFiles) {
+ addPicObjectFiles(objectFiles);
+ return addObjectFiles(objectFiles);
+ }
+
+ /**
+ * Add the corresponding files as linker inputs for both PIC and non-PIC links.
+ */
+ public CcLibraryHelper addPicIndependentObjectFiles(Artifact... objectFiles) {
+ return addPicIndependentObjectFiles(Arrays.asList(objectFiles));
+ }
+
+ /**
+ * Add the corresponding files as static libraries into the linker outputs (i.e., after the linker
+ * action) - this makes them available for linking to binary rules that depend on this rule.
+ */
+ public CcLibraryHelper addStaticLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(staticLibraries, libraries);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as static libraries into the linker outputs (i.e., after the linker
+ * action) - this makes them available for linking to binary rules that depend on this rule.
+ */
+ public CcLibraryHelper addPicStaticLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(picStaticLibraries, libraries);
+ return this;
+ }
+
+ /**
+ * Add the corresponding files as dynamic libraries into the linker outputs (i.e., after the
+ * linker action) - this makes them available for linking to binary rules that depend on this
+ * rule.
+ */
+ public CcLibraryHelper addDynamicLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(dynamicLibraries, libraries);
+ return this;
+ }
+
+ /**
+ * Adds the copts to the compile command line.
+ */
+ public CcLibraryHelper addCopts(Iterable<String> copts) {
+ Iterables.addAll(this.copts, copts);
+ return this;
+ }
+
+ /**
+ * Sets a pattern that is used to filter copts; set to {@code null} for no filtering.
+ */
+ public CcLibraryHelper setNoCopts(@Nullable Pattern nocopts) {
+ this.nocopts = nocopts;
+ return this;
+ }
+
+ /**
+ * Adds the given options as linker options to the link command.
+ */
+ public CcLibraryHelper addLinkopts(Iterable<String> linkopts) {
+ Iterables.addAll(this.linkopts, linkopts);
+ return this;
+ }
+
+ /**
+ * Adds the given defines to the compiler command line.
+ */
+ public CcLibraryHelper addDefines(Iterable<String> defines) {
+ Iterables.addAll(this.defines, defines);
+ return this;
+ }
+
+ /**
+ * Adds the given targets as dependencies - this can include explicit dependencies on other
+ * rules (like from a "deps" attribute) and also implicit dependencies on runtime libraries.
+ */
+ public CcLibraryHelper addDeps(Iterable<? extends TransitiveInfoCollection> deps) {
+ for (TransitiveInfoCollection dep : deps) {
+ Preconditions.checkArgument(dep.getConfiguration() == null
+ || dep.getConfiguration().equals(configuration));
+ this.deps.add(dep);
+ }
+ return this;
+ }
+
+ /**
+ * Adds the given linkstamps. Note that linkstamps are usually not compiled at the library level,
+ * but only in the dependent binary rules.
+ */
+ public CcLibraryHelper addLinkstamps(Iterable<? extends TransitiveInfoCollection> linkstamps) {
+ for (TransitiveInfoCollection linkstamp : linkstamps) {
+ Iterables.addAll(this.linkstamps,
+ linkstamp.getProvider(FileProvider.class).getFilesToBuild());
+ }
+ return this;
+ }
+
+ /**
+ * Adds the given prerequisites as prerequisites for the generated compile actions. This ensures
+ * that the corresponding files exist - otherwise the action fails. Note that these dependencies
+ * add edges to the action graph, and can therefore increase the length of the critical path,
+ * i.e., make the build slower.
+ */
+ public CcLibraryHelper addCompilationPrerequisites(Iterable<Artifact> prerequisites) {
+ Iterables.addAll(this.prerequisites, prerequisites);
+ return this;
+ }
+
+ /**
+ * Adds the given directories to the loose include directories that are only allowed to be
+ * referenced when headers checking is {@link HeadersCheckingMode#LOOSE} or {@link
+ * HeadersCheckingMode#WARN}.
+ */
+ public CcLibraryHelper addLooseIncludeDirs(Iterable<PathFragment> looseIncludeDirs) {
+ Iterables.addAll(this.looseIncludeDirs, looseIncludeDirs);
+ return this;
+ }
+
+ /**
+ * Adds the given directories to the system include directories (they are passed with {@code
+ * "-isystem"} to the compiler); these are also passed to dependent rules.
+ */
+ public CcLibraryHelper addSystemIncludeDirs(Iterable<PathFragment> systemIncludeDirs) {
+ Iterables.addAll(this.systemIncludeDirs, systemIncludeDirs);
+ return this;
+ }
+
+ /**
+ * Adds the given directories to the quote include directories (they are passed with {@code
+ * "-iquote"} to the compiler); these are also passed to dependent rules.
+ */
+ public CcLibraryHelper addIncludeDirs(Iterable<PathFragment> includeDirs) {
+ Iterables.addAll(this.includeDirs, includeDirs);
+ return this;
+ }
+
+ /**
+ * Overrides the path for the generated dynamic library - this should only be called if the
+ * dynamic library is an implicit or explicit output of the rule, i.e., if it is accessible by
+ * name from other rules in the same package. Set to {@code null} to use the default computation.
+ */
+ public CcLibraryHelper setDynamicLibraryPath(@Nullable PathFragment dynamicLibraryPath) {
+ this.dynamicLibraryPath = dynamicLibraryPath;
+ return this;
+ }
+
+ /**
+ * Marks the output of this rule as alwayslink, i.e., the corresponding symbols will be retained
+ * by the linker even if they are not otherwise used. This is useful for libraries that register
+ * themselves somewhere during initialization.
+ *
+ * <p>This only sets the link type (see {@link #setLinkType}), either to a static library or to
+ * an alwayslink static library (blaze uses a different file extension to signal alwayslink to
+ * downstream code).
+ */
+ public CcLibraryHelper setAlwayslink(boolean alwayslink) {
+ linkType = alwayslink
+ ? LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY
+ : LinkTargetType.STATIC_LIBRARY;
+ return this;
+ }
+
+ /**
+ * Directly set the link type. This can be used instead of {@link #setAlwayslink}. Setting
+ * anything other than a static link causes this class to skip the link action creation.
+ */
+ public CcLibraryHelper setLinkType(LinkTargetType linkType) {
+ this.linkType = Preconditions.checkNotNull(linkType);
+ return this;
+ }
+
+ /**
+ * Marks the resulting code as neverlink, i.e., the code will not be linked into dependent
+ * libraries or binaries - the header files are still available.
+ */
+ public CcLibraryHelper setNeverLink(boolean neverlink) {
+ this.neverlink = neverlink;
+ return this;
+ }
+
+ /**
+ * Sets the given headers checking mode. The default is {@link HeadersCheckingMode#LOOSE}.
+ */
+ public CcLibraryHelper setHeadersCheckingMode(HeadersCheckingMode headersCheckingMode) {
+ this.headersCheckingMode = Preconditions.checkNotNull(headersCheckingMode);
+ return this;
+ }
+
+ /**
+ * Marks the resulting code as fake, i.e., the code will not actually be compiled or linked, but
+ * instead, the compile command is written to a file and added to the runfiles. This is currently
+ * used for non-compilation tests. Unfortunately, the design is problematic, so please don't add
+ * any further uses.
+ */
+ public CcLibraryHelper setFake(boolean fake) {
+ this.fake = fake;
+ return this;
+ }
+
+ /**
+ * This adds the {@link CcNativeLibraryProvider} to the providers created by this class.
+ */
+ public CcLibraryHelper enableCcNativeLibrariesProvider() {
+ this.emitCcNativeLibrariesProvider = true;
+ return this;
+ }
+
+ /**
+ * This adds the {@link CcSpecificLinkParamsProvider} to the providers created by this class.
+ * Otherwise the result will contain an instance of {@link CcLinkParamsProvider}.
+ */
+ public CcLibraryHelper enableCcSpecificLinkParamsProvider() {
+ this.emitCcSpecificLinkParamsProvider = true;
+ return this;
+ }
+
+ /**
+ * This disables C++ module map generation for the current rule. Don't call this unless you know
+ * what you are doing.
+ */
+ public CcLibraryHelper disableCppModuleMapGeneration() {
+ this.emitCppModuleMaps = false;
+ return this;
+ }
+
+ /**
+ * This enables or disables use of module maps during compilation, i.e., layering checks.
+ */
+ public CcLibraryHelper setEnableLayeringCheck(boolean enableLayeringCheck) {
+ this.enableLayeringCheck = enableLayeringCheck;
+ return this;
+ }
+
+ /**
+ * This enabled or disables compilation of C++ header modules.
+ * TODO(bazel-team): Add a cc_toolchain flag that allows fully disabling this feature and document
+ * this feature.
+ * See http://clang.llvm.org/docs/Modules.html.
+ */
+ public CcLibraryHelper setCompileHeaderModules(boolean compileHeaderModules) {
+ this.compileHeaderModules = compileHeaderModules;
+ return this;
+ }
+
+ /**
+ * Enables or disables generation of compile actions if there are no sources. Some rules declare a
+ * .a or .so implicit output, which requires that these files are created even if there are no
+ * source files, so be careful when calling this.
+ */
+ public CcLibraryHelper setGenerateCompileActionsIfEmpty(boolean emitCompileActionsIfEmpty) {
+ this.emitCompileActionsIfEmpty = emitCompileActionsIfEmpty;
+ return this;
+ }
+
+ /**
+ * Enables the optional generation of interface dynamic libraries - this is only used when the
+ * linker generates a dynamic library, and only if the crosstool supports it. The default is not
+ * to generate interface dynamic libraries.
+ */
+ public CcLibraryHelper enableInterfaceSharedObjects() {
+ this.emitInterfaceSharedObjects = true;
+ return this;
+ }
+
+ /**
+ * This enables or disables the generation of a dynamic library link action. The default is to
+ * generate a dynamic library. Note that the selection between dynamic or static linking is
+ * performed at the binary rule level.
+ */
+ public CcLibraryHelper setCreateDynamicLibrary(boolean emitDynamicLibrary) {
+ this.emitDynamicLibrary = emitDynamicLibrary;
+ return this;
+ }
+
+ /**
+ * Disables checking that the deps actually are C++ rules. By default, the {@link #build} method
+ * uses {@link LanguageDependentFragment.Checker#depSupportsLanguage} to check that all deps
+ * provide C++ providers.
+ */
+ public CcLibraryHelper setCheckDepsGenerateCpp(boolean checkDepsGenerateCpp) {
+ this.checkDepsGenerateCpp = checkDepsGenerateCpp;
+ return this;
+ }
+
+ /**
+ * Enables the output of {@link FilesToCompileProvider} and {@link
+ * CompilationPrerequisitesProvider}.
+ */
+ // TODO(bazel-team): We probably need to adjust this for the multi-language rules.
+ public CcLibraryHelper enableCompileProviders() {
+ this.emitCompileProviders = true;
+ return this;
+ }
+
+ /**
+ * Sets whether to emit the transitive module map references of a public library headers target.
+ */
+ public CcLibraryHelper setEmitHeaderTargetModuleMaps(boolean emitHeaderTargetModuleMaps) {
+ this.emitHeaderTargetModuleMaps = emitHeaderTargetModuleMaps;
+ return this;
+ }
+
+ /**
+ * Create the C++ compile and link actions, and the corresponding C++-related providers.
+ */
+ public Info build() {
+ // Fail early if there is no lipo context collector on the rule - otherwise we end up failing
+ // in lipo optimization.
+ Preconditions.checkState(
+ // 'cc_inc_library' rules do not compile, and thus are not affected by LIPO.
+ ruleContext.getRule().getRuleClass().equals("cc_inc_library")
+ || ruleContext.getRule().isAttrDefined(":lipo_context_collector", Type.LABEL));
+
+ if (checkDepsGenerateCpp) {
+ for (LanguageDependentFragment dep :
+ AnalysisUtils.getProviders(deps, LanguageDependentFragment.class)) {
+ LanguageDependentFragment.Checker.depSupportsLanguage(
+ ruleContext, dep, CppRuleClasses.LANGUAGE);
+ }
+ }
+
+ CcLinkingOutputs ccLinkingOutputs = CcLinkingOutputs.EMPTY;
+ CcCompilationOutputs ccOutputs = new CcCompilationOutputs.Builder().build();
+ FeatureConfiguration featureConfiguration = CcCommon.configureFeatures(ruleContext);
+
+ CppModel model = new CppModel(ruleContext, semantics)
+ .addSources(sources)
+ .addCopts(copts)
+ .setLinkTargetType(linkType)
+ .setNeverLink(neverlink)
+ .setFake(fake)
+ .setAllowInterfaceSharedObjects(emitInterfaceSharedObjects)
+ .setCreateDynamicLibrary(emitDynamicLibrary)
+ // Note: this doesn't actually save the temps, it just makes the CppModel use the
+ // configurations --save_temps setting to decide whether to actually save the temps.
+ .setSaveTemps(true)
+ .setEnableLayeringCheck(enableLayeringCheck)
+ .setCompileHeaderModules(compileHeaderModules)
+ .setNoCopts(nocopts)
+ .setDynamicLibraryPath(dynamicLibraryPath)
+ .addLinkopts(linkopts)
+ .setFeatureConfiguration(featureConfiguration);
+ CppCompilationContext cppCompilationContext =
+ initializeCppCompilationContext(model, featureConfiguration);
+ model.setContext(cppCompilationContext);
+ if (emitCompileActionsIfEmpty || !sources.isEmpty() || compileHeaderModules) {
+ Preconditions.checkState(
+ !compileHeaderModules || cppCompilationContext.getCppModuleMap() != null,
+ "All cc rules must support module maps.");
+ ccOutputs = model.createCcCompileActions();
+ if (!objectFiles.isEmpty() || !picObjectFiles.isEmpty()) {
+ // Merge the pre-compiled object files into the compiler outputs.
+ ccOutputs = new CcCompilationOutputs.Builder()
+ .merge(ccOutputs)
+ .addObjectFiles(objectFiles)
+ .addPicObjectFiles(picObjectFiles)
+ .build();
+ }
+ if (linkType.isStaticLibraryLink()) {
+ // TODO(bazel-team): This can't create the link action for a cc_binary yet.
+ ccLinkingOutputs = model.createCcLinkActions(ccOutputs);
+ }
+ }
+ CcLinkingOutputs originalLinkingOutputs = ccLinkingOutputs;
+ if (!(
+ staticLibraries.isEmpty() && picStaticLibraries.isEmpty() && dynamicLibraries.isEmpty())) {
+ // Merge the pre-compiled libraries (static & dynamic) into the linker outputs.
+ ccLinkingOutputs = new CcLinkingOutputs.Builder()
+ .merge(ccLinkingOutputs)
+ .addStaticLibraries(staticLibraries)
+ .addPicStaticLibraries(picStaticLibraries)
+ .addDynamicLibraries(dynamicLibraries)
+ .addExecutionDynamicLibraries(dynamicLibraries)
+ .build();
+ }
+
+ DwoArtifactsCollector dwoArtifacts = DwoArtifactsCollector.transitiveCollector(ccOutputs, deps);
+ Runfiles cppStaticRunfiles = collectCppRunfiles(ccLinkingOutputs, true);
+ Runfiles cppSharedRunfiles = collectCppRunfiles(ccLinkingOutputs, false);
+
+ // By very careful when adding new providers here - it can potentially affect a lot of rules.
+ // We should consider merging most of these providers into a single provider.
+ Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers =
+ new LinkedHashMap<>();
+ providers.put(CppRunfilesProvider.class,
+ new CppRunfilesProvider(cppStaticRunfiles, cppSharedRunfiles));
+ providers.put(CppCompilationContext.class, cppCompilationContext);
+ providers.put(CppDebugFileProvider.class, new CppDebugFileProvider(
+ dwoArtifacts.getDwoArtifacts(), dwoArtifacts.getPicDwoArtifacts()));
+ providers.put(TransitiveLipoInfoProvider.class, collectTransitiveLipoInfo(ccOutputs));
+ providers.put(TempsProvider.class, getTemps(ccOutputs));
+ if (emitCompileProviders) {
+ providers.put(FilesToCompileProvider.class, new FilesToCompileProvider(
+ getFilesToCompile(ccOutputs)));
+ providers.put(CompilationPrerequisitesProvider.class,
+ CcCommon.collectCompilationPrerequisites(ruleContext, cppCompilationContext));
+ }
+
+ // TODO(bazel-team): Maybe we can infer these from other data at the places where they are
+ // used.
+ if (emitCcNativeLibrariesProvider) {
+ providers.put(CcNativeLibraryProvider.class,
+ new CcNativeLibraryProvider(collectNativeCcLibraries(ccLinkingOutputs)));
+ }
+ providers.put(CcExecutionDynamicLibrariesProvider.class,
+ collectExecutionDynamicLibraryArtifacts(ccLinkingOutputs.getExecutionDynamicLibraries()));
+
+ boolean forcePic = ruleContext.getFragment(CppConfiguration.class).forcePic();
+ if (emitCcSpecificLinkParamsProvider) {
+ providers.put(CcSpecificLinkParamsProvider.class, new CcSpecificLinkParamsProvider(
+ createCcLinkParamsStore(ccLinkingOutputs, cppCompilationContext, forcePic)));
+ } else {
+ providers.put(CcLinkParamsProvider.class, new CcLinkParamsProvider(
+ createCcLinkParamsStore(ccLinkingOutputs, cppCompilationContext, forcePic)));
+ }
+ return new Info(providers, ccOutputs, ccLinkingOutputs, originalLinkingOutputs,
+ cppCompilationContext);
+ }
+
+ /**
+ * Create context for cc compile action from generated inputs.
+ */
+ private CppCompilationContext initializeCppCompilationContext(CppModel model,
+ FeatureConfiguration featureConfiguration) {
+ CppCompilationContext.Builder contextBuilder =
+ new CppCompilationContext.Builder(ruleContext);
+
+ // Setup the include path; local include directories come before those inherited from deps or
+ // from the toolchain; in case of aliasing (same include file found on different entries),
+ // prefer the local include rather than the inherited one.
+
+ // Add in the roots for well-formed include names for source files and
+ // generated files. It is important that the execRoot (EMPTY_FRAGMENT) comes
+ // before the genfilesFragment to preferably pick up source files. Otherwise
+ // we might pick up stale generated files.
+ contextBuilder.addQuoteIncludeDir(PathFragment.EMPTY_FRAGMENT);
+ contextBuilder.addQuoteIncludeDir(ruleContext.getConfiguration().getGenfilesFragment());
+
+ for (PathFragment systemIncludeDir : systemIncludeDirs) {
+ contextBuilder.addSystemIncludeDir(systemIncludeDir);
+ }
+ for (PathFragment includeDir : includeDirs) {
+ contextBuilder.addIncludeDir(includeDir);
+ }
+
+ contextBuilder.mergeDependentContexts(
+ AnalysisUtils.getProviders(deps, CppCompilationContext.class));
+ CppHelper.mergeToolchainDependentContext(ruleContext, contextBuilder);
+
+ // But defines come after those inherited from deps.
+ contextBuilder.addDefines(defines);
+
+ // There are no ordering constraints for declared include dirs/srcs, or the pregrepped headers.
+ contextBuilder.addDeclaredIncludeSrcs(publicHeaders);
+ contextBuilder.addDeclaredIncludeSrcs(privateHeaders);
+ contextBuilder.addPregreppedHeaderMap(
+ CppHelper.createExtractInclusions(ruleContext, publicHeaders));
+ contextBuilder.addPregreppedHeaderMap(
+ CppHelper.createExtractInclusions(ruleContext, privateHeaders));
+ contextBuilder.addCompilationPrerequisites(prerequisites);
+
+ // Add this package's dir to declaredIncludeDirs, & this rule's headers to declaredIncludeSrcs
+ // Note: no include dir for STRICT mode.
+ if (headersCheckingMode == HeadersCheckingMode.WARN) {
+ contextBuilder.addDeclaredIncludeWarnDir(ruleContext.getLabel().getPackageFragment());
+ for (PathFragment looseIncludeDir : looseIncludeDirs) {
+ contextBuilder.addDeclaredIncludeWarnDir(looseIncludeDir);
+ }
+ } else if (headersCheckingMode == HeadersCheckingMode.LOOSE) {
+ contextBuilder.addDeclaredIncludeDir(ruleContext.getLabel().getPackageFragment());
+ for (PathFragment looseIncludeDir : looseIncludeDirs) {
+ contextBuilder.addDeclaredIncludeDir(looseIncludeDir);
+ }
+ }
+
+ if (emitCppModuleMaps) {
+ CppModuleMap cppModuleMap = CppHelper.addCppModuleMapToContext(ruleContext, contextBuilder);
+ // TODO(bazel-team): addCppModuleMapToContext second-guesses whether module maps should
+ // actually be enabled, so we need to double-check here. Who would write code like this?
+ if (cppModuleMap != null) {
+ CppModuleMapAction action = new CppModuleMapAction(ruleContext.getActionOwner(),
+ cppModuleMap,
+ privateHeaders,
+ publicHeaders,
+ collectModuleMaps(),
+ additionalExportedHeaders,
+ compileHeaderModules,
+ featureConfiguration.isEnabled(CppRuleClasses.MODULE_MAP_HOME_CWD));
+ ruleContext.registerAction(action);
+ }
+ if (model.getGeneratesPicHeaderModule()) {
+ contextBuilder.setPicHeaderModule(model.getPicHeaderModule(cppModuleMap.getArtifact()));
+ }
+ if (model.getGeratesNoPicHeaderModule()) {
+ contextBuilder.setHeaderModule(model.getHeaderModule(cppModuleMap.getArtifact()));
+ }
+ }
+
+ semantics.setupCompilationContext(ruleContext, contextBuilder);
+ return contextBuilder.build();
+ }
+
+ private Iterable<CppModuleMap> collectModuleMaps() {
+ // Cpp module maps may be null for some rules. We filter the nulls out at the end.
+ List<CppModuleMap> result = new ArrayList<>();
+ Iterables.addAll(result, Iterables.transform(deps, CPP_DEPS_TO_MODULES));
+ CppCompilationContext stl =
+ ruleContext.getPrerequisite(":stl", Mode.TARGET, CppCompilationContext.class);
+ if (stl != null) {
+ result.add(stl.getCppModuleMap());
+ }
+
+ CcToolchainProvider toolchain = CppHelper.getToolchain(ruleContext);
+ if (toolchain != null) {
+ result.add(toolchain.getCppCompilationContext().getCppModuleMap());
+ }
+
+ if (emitHeaderTargetModuleMaps) {
+ for (HeaderTargetModuleMapProvider provider : AnalysisUtils.getProviders(
+ deps, HeaderTargetModuleMapProvider.class)) {
+ result.addAll(provider.getCppModuleMaps());
+ }
+ }
+
+ return Iterables.filter(result, Predicates.<CppModuleMap>notNull());
+ }
+
+ private TransitiveLipoInfoProvider collectTransitiveLipoInfo(CcCompilationOutputs outputs) {
+ if (ruleContext.getFragment(CppConfiguration.class).getFdoSupport().getFdoRoot() == null) {
+ return TransitiveLipoInfoProvider.EMPTY;
+ }
+ NestedSetBuilder<IncludeScannable> scannableBuilder = NestedSetBuilder.stableOrder();
+ // TODO(bazel-team): Only fetch the STL prerequisite in one place.
+ TransitiveInfoCollection stl = ruleContext.getPrerequisite(":stl", Mode.TARGET);
+ if (stl != null) {
+ TransitiveLipoInfoProvider provider = stl.getProvider(TransitiveLipoInfoProvider.class);
+ if (provider != null) {
+ scannableBuilder.addTransitive(provider.getTransitiveIncludeScannables());
+ }
+ }
+
+ for (TransitiveLipoInfoProvider dep :
+ AnalysisUtils.getProviders(deps, TransitiveLipoInfoProvider.class)) {
+ scannableBuilder.addTransitive(dep.getTransitiveIncludeScannables());
+ }
+
+ for (IncludeScannable scannable : outputs.getLipoScannables()) {
+ Preconditions.checkState(scannable.getIncludeScannerSources().size() == 1);
+ scannableBuilder.add(scannable);
+ }
+ return new TransitiveLipoInfoProvider(scannableBuilder.build());
+ }
+
+ private Runfiles collectCppRunfiles(
+ CcLinkingOutputs ccLinkingOutputs, boolean linkingStatically) {
+ Runfiles.Builder builder = new Runfiles.Builder();
+ builder.addTargets(deps, RunfilesProvider.DEFAULT_RUNFILES);
+ builder.addTargets(deps, CppRunfilesProvider.runfilesFunction(linkingStatically));
+ // Add the shared libraries to the runfiles.
+ builder.addArtifacts(ccLinkingOutputs.getLibrariesForRunfiles(linkingStatically));
+ return builder.build();
+ }
+
+ private CcLinkParamsStore createCcLinkParamsStore(
+ final CcLinkingOutputs ccLinkingOutputs, final CppCompilationContext cppCompilationContext,
+ final boolean forcePic) {
+ return new CcLinkParamsStore() {
+ @Override
+ protected void collect(CcLinkParams.Builder builder, boolean linkingStatically,
+ boolean linkShared) {
+ builder.addLinkstamps(linkstamps, cppCompilationContext);
+ builder.addTransitiveTargets(deps,
+ CcLinkParamsProvider.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS);
+ if (!neverlink) {
+ builder.addLibraries(ccLinkingOutputs.getPreferredLibraries(linkingStatically,
+ /*preferPic=*/linkShared || forcePic));
+ builder.addLinkOpts(linkopts);
+ }
+ }
+ };
+ }
+
+ private NestedSet<LinkerInput> collectNativeCcLibraries(CcLinkingOutputs ccLinkingOutputs) {
+ NestedSetBuilder<LinkerInput> result = NestedSetBuilder.linkOrder();
+ result.addAll(ccLinkingOutputs.getDynamicLibraries());
+ for (CcNativeLibraryProvider dep : AnalysisUtils.getProviders(
+ deps, CcNativeLibraryProvider.class)) {
+ result.addTransitive(dep.getTransitiveCcNativeLibraries());
+ }
+
+ return result.build();
+ }
+
+ private CcExecutionDynamicLibrariesProvider collectExecutionDynamicLibraryArtifacts(
+ List<LibraryToLink> executionDynamicLibraries) {
+ Iterable<Artifact> artifacts = LinkerInputs.toLibraryArtifacts(executionDynamicLibraries);
+ if (!Iterables.isEmpty(artifacts)) {
+ return new CcExecutionDynamicLibrariesProvider(
+ NestedSetBuilder.wrap(Order.STABLE_ORDER, artifacts));
+ }
+
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (CcExecutionDynamicLibrariesProvider dep :
+ AnalysisUtils.getProviders(deps, CcExecutionDynamicLibrariesProvider.class)) {
+ builder.addTransitive(dep.getExecutionDynamicLibraryArtifacts());
+ }
+ return builder.isEmpty()
+ ? CcExecutionDynamicLibrariesProvider.EMPTY
+ : new CcExecutionDynamicLibrariesProvider(builder.build());
+ }
+
+ private TempsProvider getTemps(CcCompilationOutputs compilationOutputs) {
+ return ruleContext.getFragment(CppConfiguration.class).isLipoContextCollector()
+ ? new TempsProvider(ImmutableList.<Artifact>of())
+ : new TempsProvider(compilationOutputs.getTemps());
+ }
+
+ private ImmutableList<Artifact> getFilesToCompile(CcCompilationOutputs compilationOutputs) {
+ return ruleContext.getFragment(CppConfiguration.class).isLipoContextCollector()
+ ? ImmutableList.<Artifact>of()
+ : compilationOutputs.getObjectFiles(CppHelper.usePic(ruleContext, false));
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java
new file mode 100644
index 0000000000..4e4804b177
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java
@@ -0,0 +1,357 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+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.rules.cpp.LinkerInputs.LibraryToLink;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Parameters to be passed to the linker.
+ *
+ * <p>The parameters concerned are the link options (strings) passed to the linker, linkstamps and a
+ * list of libraries to be linked in.
+ *
+ * <p>Items in the collections are stored in nested sets. Link options and libraries are stored in
+ * link order (preorder) and linkstamps are sorted.
+ */
+public final class CcLinkParams {
+ private final NestedSet<ImmutableList<String>> linkOpts;
+ private final NestedSet<Linkstamp> linkstamps;
+ private final NestedSet<LibraryToLink> libraries;
+
+ private CcLinkParams(NestedSet<ImmutableList<String>> linkOpts,
+ NestedSet<Linkstamp> linkstamps,
+ NestedSet<LibraryToLink> libraries) {
+ this.linkOpts = linkOpts;
+ this.linkstamps = linkstamps;
+ this.libraries = libraries;
+ }
+
+ /**
+ * @return the linkopts
+ */
+ public NestedSet<ImmutableList<String>> getLinkopts() {
+ return linkOpts;
+ }
+
+ public ImmutableList<String> flattenedLinkopts() {
+ return ImmutableList.copyOf(Iterables.concat(linkOpts));
+ }
+
+ /**
+ * @return the linkstamps
+ */
+ public NestedSet<Linkstamp> getLinkstamps() {
+ return linkstamps;
+ }
+
+ /**
+ * @return the libraries
+ */
+ public NestedSet<LibraryToLink> getLibraries() {
+ return libraries;
+ }
+
+ public static final Builder builder(boolean linkingStatically, boolean linkShared) {
+ return new Builder(linkingStatically, linkShared);
+ }
+
+ /**
+ * Builder for {@link CcLinkParams}.
+ *
+ *
+ */
+ public static final class Builder {
+
+ /**
+ * linkingStatically is true when we're linking this target in either FULLY STATIC mode
+ * (linkopts=["-static"]) or MOSTLY STATIC mode (linkstatic=1). When this is true, we want to
+ * use static versions of any libraries that this target depends on (except possibly system
+ * libraries, which are not handled by CcLinkParams). When this is false, we want to use dynamic
+ * versions of any libraries that this target depends on.
+ */
+ private final boolean linkingStatically;
+
+ /**
+ * linkShared is true when we're linking with "-shared" (linkshared=1).
+ */
+ private final boolean linkShared;
+
+ private ImmutableList.Builder<String> localLinkoptsBuilder = ImmutableList.builder();
+
+ private final NestedSetBuilder<ImmutableList<String>> linkOptsBuilder =
+ NestedSetBuilder.linkOrder();
+ private final NestedSetBuilder<Linkstamp> linkstampsBuilder =
+ NestedSetBuilder.compileOrder();
+ private final NestedSetBuilder<LibraryToLink> librariesBuilder =
+ NestedSetBuilder.linkOrder();
+
+ private boolean built = false;
+
+ private Builder(boolean linkingStatically, boolean linkShared) {
+ this.linkingStatically = linkingStatically;
+ this.linkShared = linkShared;
+ }
+
+ /**
+ * Build a {@link CcLinkParams} object.
+ */
+ public CcLinkParams build() {
+ Preconditions.checkState(!built);
+ // Not thread-safe, but builders should not be shared across threads.
+ built = true;
+ ImmutableList<String> localLinkopts = localLinkoptsBuilder.build();
+ if (!localLinkopts.isEmpty()) {
+ linkOptsBuilder.add(localLinkopts);
+ }
+ return new CcLinkParams(linkOptsBuilder.build(), linkstampsBuilder.build(),
+ librariesBuilder.build());
+ }
+
+ private boolean add(CcLinkParamsStore store) {
+ if (store != null) {
+ CcLinkParams args = store.get(linkingStatically, linkShared);
+ addTransitiveArgs(args);
+ }
+ return store != null;
+ }
+
+ /**
+ * Includes link parameters from a collection of dependency targets.
+ */
+ public Builder addTransitiveTargets(Iterable<? extends TransitiveInfoCollection> targets) {
+ for (TransitiveInfoCollection target : targets) {
+ addTransitiveTarget(target);
+ }
+ return this;
+ }
+
+ /**
+ * Includes link parameters from a dependency target.
+ *
+ * <p>The target should implement {@link CcLinkParamsProvider}. If it does not,
+ * the method does not do anything.
+ */
+ public Builder addTransitiveTarget(TransitiveInfoCollection target) {
+ return addTransitiveProvider(target.getProvider(CcLinkParamsProvider.class));
+ }
+
+ /**
+ * Includes link parameters from a dependency target. The target is checked for the given
+ * mappings in the order specified, and the first mapping that returns a non-null result is
+ * added.
+ */
+ @SafeVarargs
+ public final Builder addTransitiveTarget(TransitiveInfoCollection target,
+ Function<TransitiveInfoCollection, CcLinkParamsStore> firstMapping,
+ @SuppressWarnings("unchecked") // Java arrays don't preserve generic arguments.
+ Function<TransitiveInfoCollection, CcLinkParamsStore>... remainingMappings) {
+ if (add(firstMapping.apply(target))) {
+ return this;
+ }
+ for (Function<TransitiveInfoCollection, CcLinkParamsStore> mapping : remainingMappings) {
+ if (add(mapping.apply(target))) {
+ return this;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Includes link parameters from a CcLinkParamsProvider provider.
+ */
+ public Builder addTransitiveProvider(CcLinkParamsProvider provider) {
+ if (provider == null) {
+ return this;
+ }
+
+ CcLinkParams args = provider.getCcLinkParams(linkingStatically, linkShared);
+ addTransitiveArgs(args);
+ return this;
+ }
+
+ /**
+ * Includes link parameters from the given targets. Each target is checked for the given
+ * mappings in the order specified, and the first mapping that returns a non-null result is
+ * added.
+ */
+ @SafeVarargs
+ public final Builder addTransitiveTargets(
+ Iterable<? extends TransitiveInfoCollection> targets,
+ Function<TransitiveInfoCollection, CcLinkParamsStore> firstMapping,
+ @SuppressWarnings("unchecked") // Java arrays don't preserve generic arguments.
+ Function<TransitiveInfoCollection, CcLinkParamsStore>... remainingMappings) {
+ for (TransitiveInfoCollection target : targets) {
+ addTransitiveTarget(target, firstMapping, remainingMappings);
+ }
+ return this;
+ }
+
+ /**
+ * Includes link parameters from the given targets. Each target is checked for the given
+ * mappings in the order specified, and the first mapping that returns a non-null result is
+ * added.
+ *
+ * @deprecated don't add any new uses; all existing uses need to be audited and possibly merged
+ * into a single call - some of them may introduce semantic changes which need to be
+ * carefully vetted
+ */
+ @Deprecated
+ @SafeVarargs
+ public final Builder addTransitiveLangTargets(
+ Iterable<? extends TransitiveInfoCollection> targets,
+ Function<TransitiveInfoCollection, CcLinkParamsStore> firstMapping,
+ @SuppressWarnings("unchecked") // Java arrays don't preserve generic arguments.
+ Function<TransitiveInfoCollection, CcLinkParamsStore>... remainingMappings) {
+ return addTransitiveTargets(targets, firstMapping, remainingMappings);
+ }
+
+ /**
+ * Merges the other {@link CcLinkParams} object into this one.
+ */
+ public Builder addTransitiveArgs(CcLinkParams args) {
+ linkOptsBuilder.addTransitive(args.getLinkopts());
+ linkstampsBuilder.addTransitive(args.getLinkstamps());
+ librariesBuilder.addTransitive(args.getLibraries());
+ return this;
+ }
+
+ /**
+ * Adds a collection of link options.
+ */
+ public Builder addLinkOpts(Collection<String> linkOpts) {
+ localLinkoptsBuilder.addAll(linkOpts);
+ return this;
+ }
+
+ /**
+ * Adds a collection of linkstamps.
+ */
+ public Builder addLinkstamps(Iterable<Artifact> linkstamps, CppCompilationContext context) {
+ ImmutableList<Artifact> declaredIncludeSrcs =
+ ImmutableList.copyOf(context.getDeclaredIncludeSrcs());
+ for (Artifact linkstamp : linkstamps) {
+ linkstampsBuilder.add(new Linkstamp(linkstamp, declaredIncludeSrcs));
+ }
+ return this;
+ }
+
+ /**
+ * Adds a library artifact.
+ */
+ public Builder addLibrary(LibraryToLink library) {
+ librariesBuilder.add(library);
+ return this;
+ }
+
+ /**
+ * Adds a collection of library artifacts.
+ */
+ public Builder addLibraries(Iterable<LibraryToLink> libraries) {
+ librariesBuilder.addAll(libraries);
+ return this;
+ }
+
+ /**
+ * Processes typical dependencies a C/C++ library.
+ *
+ * <p>A helper method that processes getValues() and merges contents of
+ * getPreferredLibraries() and getLinkOpts() into the current link params
+ * object.
+ */
+ public Builder addCcLibrary(RuleContext context, CcCommon common, boolean neverlink,
+ CcLinkingOutputs linkingOutputs) {
+ addTransitiveTargets(
+ context.getPrerequisites("deps", Mode.TARGET),
+ CcLinkParamsProvider.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS);
+
+ if (!neverlink) {
+ addLibraries(linkingOutputs.getPreferredLibraries(linkingStatically,
+ linkShared || context.getFragment(CppConfiguration.class).forcePic()));
+ addLinkOpts(common.getLinkopts());
+ }
+ return this;
+ }
+ }
+
+ /**
+ * A linkstamp that also knows about its declared includes.
+ *
+ * <p>This object is required because linkstamp files may include other headers which
+ * will have to be provided during compilation.
+ */
+ public static final class Linkstamp {
+ private final Artifact artifact;
+ private final ImmutableList<Artifact> declaredIncludeSrcs;
+
+ private Linkstamp(Artifact artifact, ImmutableList<Artifact> declaredIncludeSrcs) {
+ this.artifact = Preconditions.checkNotNull(artifact);
+ this.declaredIncludeSrcs = Preconditions.checkNotNull(declaredIncludeSrcs);
+ }
+
+ /**
+ * Returns the linkstamp artifact.
+ */
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ /**
+ * Returns the declared includes.
+ */
+ public ImmutableList<Artifact> getDeclaredIncludeSrcs() {
+ return declaredIncludeSrcs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(artifact, declaredIncludeSrcs);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Linkstamp)) {
+ return false;
+ }
+ Linkstamp other = (Linkstamp) obj;
+ return artifact.equals(other.artifact)
+ && declaredIncludeSrcs.equals(other.declaredIncludeSrcs);
+ }
+ }
+
+ /**
+ * Empty CcLinkParams.
+ */
+ public static final CcLinkParams EMPTY = new CcLinkParams(
+ NestedSetBuilder.<ImmutableList<String>>emptySet(Order.LINK_ORDER),
+ NestedSetBuilder.<Linkstamp>emptySet(Order.COMPILE_ORDER),
+ NestedSetBuilder.<LibraryToLink>emptySet(Order.LINK_ORDER));
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsProvider.java
new file mode 100644
index 0000000000..11f6011f50
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsProvider.java
@@ -0,0 +1,50 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore.CcLinkParamsStoreImpl;
+
+/**
+ * A target that provides C linker parameters.
+ */
+@Immutable
+public final class CcLinkParamsProvider implements TransitiveInfoProvider {
+ public static final Function<TransitiveInfoCollection, CcLinkParamsStore> TO_LINK_PARAMS =
+ new Function<TransitiveInfoCollection, CcLinkParamsStore>() {
+ @Override
+ public CcLinkParamsStore apply(TransitiveInfoCollection input) {
+ CcLinkParamsProvider provider = input.getProvider(
+ CcLinkParamsProvider.class);
+ return provider == null ? null : provider.store;
+ }
+ };
+
+ private final CcLinkParamsStoreImpl store;
+
+ public CcLinkParamsProvider(CcLinkParamsStore store) {
+ this.store = new CcLinkParamsStoreImpl(store);
+ }
+
+ /**
+ * Returns link parameters given static / shared linking settings.
+ */
+ public CcLinkParams getCcLinkParams(boolean linkingStatically, boolean linkShared) {
+ return store.get(linkingStatically, linkShared);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsStore.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsStore.java
new file mode 100644
index 0000000000..a150488d84
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParamsStore.java
@@ -0,0 +1,136 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Builder;
+
+/**
+ * A cache of C link parameters.
+ *
+ * <p>The cache holds instances of {@link CcLinkParams} for combinations of
+ * linkingStatically and linkShared. If a requested value is not available in
+ * the cache, it is computed and then stored.
+ *
+ * <p>Typically this class is used on targets that may be linked in as C
+ * libraries as in the following example:
+ *
+ * <pre>
+ * class SomeTarget implements CcLinkParamsProvider {
+ * private final CcLinkParamsStore ccLinkParamsStore = new CcLinkParamsStore() {
+ * @Override
+ * protected void collect(CcLinkParams.Builder builder, boolean linkingStatically,
+ * boolean linkShared) {
+ * builder.add[...]
+ * }
+ * };
+ *
+ * @Override
+ * public CcLinkParams getCcLinkParams(boolean linkingStatically, boolean linkShared) {
+ * return ccLinkParamsStore.get(linkingStatically, linkShared);
+ * }
+ * }
+ * </pre>
+ */
+public abstract class CcLinkParamsStore {
+
+ private CcLinkParams staticSharedParams;
+ private CcLinkParams staticNoSharedParams;
+ private CcLinkParams noStaticSharedParams;
+ private CcLinkParams noStaticNoSharedParams;
+
+ private CcLinkParams compute(boolean linkingStatically, boolean linkShared) {
+ CcLinkParams.Builder builder = CcLinkParams.builder(linkingStatically, linkShared);
+ collect(builder, linkingStatically, linkShared);
+ return builder.build();
+ }
+
+ /**
+ * Returns {@link CcLinkParams} for a combination of parameters.
+ *
+ * <p>The {@link CcLinkParams} instance is computed lazily and cached.
+ */
+ public synchronized CcLinkParams get(boolean linkingStatically, boolean linkShared) {
+ CcLinkParams result = lookup(linkingStatically, linkShared);
+ if (result == null) {
+ result = compute(linkingStatically, linkShared);
+ put(linkingStatically, linkShared, result);
+ }
+ return result;
+ }
+
+ private CcLinkParams lookup(boolean linkingStatically, boolean linkShared) {
+ if (linkingStatically) {
+ return linkShared ? staticSharedParams : staticNoSharedParams;
+ } else {
+ return linkShared ? noStaticSharedParams : noStaticNoSharedParams;
+ }
+ }
+
+ private void put(boolean linkingStatically, boolean linkShared, CcLinkParams params) {
+ Preconditions.checkNotNull(params);
+ if (linkingStatically) {
+ if (linkShared) {
+ staticSharedParams = params;
+ } else {
+ staticNoSharedParams = params;
+ }
+ } else {
+ if (linkShared) {
+ noStaticSharedParams = params;
+ } else {
+ noStaticNoSharedParams = params;
+ }
+ }
+ }
+
+ /**
+ * Hook for building the actual link params.
+ *
+ * <p>Users should override this method and call methods of the builder to
+ * set up the actual CcLinkParams objects.
+ *
+ * <p>Implementations of this method must not fail or try to report errors on the
+ * configured target.
+ */
+ protected abstract void collect(CcLinkParams.Builder builder, boolean linkingStatically,
+ boolean linkShared);
+
+ /**
+ * An empty CcLinkParamStore.
+ */
+ public static final CcLinkParamsStore EMPTY = new CcLinkParamsStore() {
+
+ @Override
+ protected void collect(Builder builder, boolean linkingStatically, boolean linkShared) {}
+ };
+
+ /**
+ * An implementation class for the CcLinkParamsStore.
+ */
+ public static final class CcLinkParamsStoreImpl extends CcLinkParamsStore {
+
+ public CcLinkParamsStoreImpl(CcLinkParamsStore store) {
+ super.staticSharedParams = store.get(true, true);
+ super.staticNoSharedParams = store.get(true, false);
+ super.noStaticSharedParams = store.get(false, true);
+ super.noStaticNoSharedParams = store.get(false, false);
+ }
+
+ @Override
+ protected void collect(Builder builder, boolean linkingStatically, boolean linkShared) {}
+ }
+}
+
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingOutputs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingOutputs.java
new file mode 100644
index 0000000000..6b45c79645
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingOutputs.java
@@ -0,0 +1,243 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A structured representation of the link outputs of a C++ rule.
+ */
+public class CcLinkingOutputs {
+
+ public static final CcLinkingOutputs EMPTY = new Builder().build();
+
+ private final ImmutableList<LibraryToLink> staticLibraries;
+
+ private final ImmutableList<LibraryToLink> picStaticLibraries;
+
+ private final ImmutableList<LibraryToLink> dynamicLibraries;
+
+ private final ImmutableList<LibraryToLink> executionDynamicLibraries;
+
+ private CcLinkingOutputs(ImmutableList<LibraryToLink> staticLibraries,
+ ImmutableList<LibraryToLink> picStaticLibraries,
+ ImmutableList<LibraryToLink> dynamicLibraries,
+ ImmutableList<LibraryToLink> executionDynamicLibraries) {
+ this.staticLibraries = staticLibraries;
+ this.picStaticLibraries = picStaticLibraries;
+ this.dynamicLibraries = dynamicLibraries;
+ this.executionDynamicLibraries = executionDynamicLibraries;
+ }
+
+ public ImmutableList<LibraryToLink> getStaticLibraries() {
+ return staticLibraries;
+ }
+
+ public ImmutableList<LibraryToLink> getPicStaticLibraries() {
+ return picStaticLibraries;
+ }
+
+ public ImmutableList<LibraryToLink> getDynamicLibraries() {
+ return dynamicLibraries;
+ }
+
+ public ImmutableList<LibraryToLink> getExecutionDynamicLibraries() {
+ return executionDynamicLibraries;
+ }
+
+ /**
+ * Add the ".a", ".pic.a" and/or ".so" files in appropriate order of preference depending on the
+ * link preferences.
+ *
+ * <p>This method tries to simulate a search path for adding static and dynamic libraries,
+ * allowing either to be preferred over the other depending on the link {@link LinkStaticness}.
+ *
+ * TODO(bazel-team): (2009) we should preserve the relative ordering of first and second
+ * choice libraries. E.g. if srcs=['foo.a','bar.so','baz.a'] then we should link them in the
+ * same order. Currently we link entries from the first choice list before those from the
+ * second choice list, i.e. in the order {@code ['bar.so', 'foo.a', 'baz.a']}.
+ *
+ * @param linkingStatically whether to prefer static over dynamic libraries. Should be
+ * <code>true</code> for binaries that are linked in fully static or mostly static mode.
+ * @param preferPic whether to prefer pic over non pic libraries (usually used when linking
+ * shared)
+ */
+ public List<LibraryToLink> getPreferredLibraries(
+ boolean linkingStatically, boolean preferPic) {
+ return getPreferredLibraries(linkingStatically, preferPic, false);
+ }
+
+ /**
+ * Returns the shared libraries that are linked against and therefore also need to be in the
+ * runfiles.
+ */
+ public Iterable<Artifact> getLibrariesForRunfiles(boolean linkingStatically) {
+ List<LibraryToLink> libraries =
+ getPreferredLibraries(linkingStatically, /*preferPic*/false, true);
+ return CcCommon.getSharedLibrariesFrom(LinkerInputs.toLibraryArtifacts(libraries));
+ }
+
+ /**
+ * Add the ".a", ".pic.a" and/or ".so" files in appropriate order of
+ * preference depending on the link preferences.
+ */
+ private List<LibraryToLink> getPreferredLibraries(boolean linkingStatically, boolean preferPic,
+ boolean forRunfiles) {
+ List<LibraryToLink> candidates = new ArrayList<>();
+ // It's important that this code keeps the invariant that preferPic has no effect on the output
+ // of .so libraries. That is, the resulting list should contain the same .so files in the same
+ // order.
+ if (linkingStatically) { // Prefer the static libraries.
+ if (preferPic) {
+ // First choice is the PIC static libraries.
+ // Second choice is the other static libraries (may cause link error if they're not PIC,
+ // but I think this is preferable to linking dynamically when you asked for statically).
+ candidates.addAll(picStaticLibraries);
+ candidates.addAll(staticLibraries);
+ } else {
+ // First choice is the non-pic static libraries (best performance);
+ // second choice is the staticPicLibraries (at least they're static;
+ // we can live with the extra overhead of PIC).
+ candidates.addAll(staticLibraries);
+ candidates.addAll(picStaticLibraries);
+ }
+ candidates.addAll(forRunfiles ? executionDynamicLibraries : dynamicLibraries);
+ } else {
+ // First choice is the dynamicLibraries.
+ candidates.addAll(forRunfiles ? executionDynamicLibraries : dynamicLibraries);
+ if (preferPic) {
+ // Second choice is the staticPicLibraries (at least they're PIC, so we won't get a
+ // link error).
+ candidates.addAll(picStaticLibraries);
+ candidates.addAll(staticLibraries);
+ } else {
+ candidates.addAll(staticLibraries);
+ candidates.addAll(picStaticLibraries);
+ }
+ }
+ return filterCandidates(candidates);
+ }
+
+ /**
+ * Helper method to filter the candidates by removing equivalent library
+ * entries from the list of candidates.
+ *
+ * @param candidates the library candidates to filter
+ * @return the list of libraries with equivalent duplicate libraries removed.
+ */
+ private List<LibraryToLink> filterCandidates(List<LibraryToLink> candidates) {
+ List<LibraryToLink> libraries = new ArrayList<>();
+ Set<String> identifiers = new HashSet<>();
+ for (LibraryToLink library : candidates) {
+ if (identifiers.add(libraryIdentifierOf(library.getOriginalLibraryArtifact()))) {
+ libraries.add(library);
+ }
+ }
+ return libraries;
+ }
+
+ /**
+ * Returns the library identifier of an artifact: a string that is different for different
+ * libraries, but is the same for the shared, static and pic versions of the same library.
+ */
+ private static String libraryIdentifierOf(Artifact libraryArtifact) {
+ String name = libraryArtifact.getRootRelativePath().getPathString();
+ String basename = FileSystemUtils.removeExtension(name);
+ // Need to special-case file types with double extension.
+ return name.endsWith(".pic.a")
+ ? FileSystemUtils.removeExtension(basename)
+ : name.endsWith(".nopic.a")
+ ? FileSystemUtils.removeExtension(basename)
+ : name.endsWith(".pic.lo")
+ ? FileSystemUtils.removeExtension(basename)
+ : basename;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private final Set<LibraryToLink> staticLibraries = new LinkedHashSet<>();
+ private final Set<LibraryToLink> picStaticLibraries = new LinkedHashSet<>();
+ private final Set<LibraryToLink> dynamicLibraries = new LinkedHashSet<>();
+ private final Set<LibraryToLink> executionDynamicLibraries = new LinkedHashSet<>();
+
+ public CcLinkingOutputs build() {
+ return new CcLinkingOutputs(ImmutableList.copyOf(staticLibraries),
+ ImmutableList.copyOf(picStaticLibraries), ImmutableList.copyOf(dynamicLibraries),
+ ImmutableList.copyOf(executionDynamicLibraries));
+ }
+
+ public Builder merge(CcLinkingOutputs outputs) {
+ staticLibraries.addAll(outputs.getStaticLibraries());
+ picStaticLibraries.addAll(outputs.getPicStaticLibraries());
+ dynamicLibraries.addAll(outputs.getDynamicLibraries());
+ executionDynamicLibraries.addAll(outputs.getExecutionDynamicLibraries());
+ return this;
+ }
+
+ public Builder addStaticLibrary(LibraryToLink library) {
+ staticLibraries.add(library);
+ return this;
+ }
+
+ public Builder addStaticLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(staticLibraries, libraries);
+ return this;
+ }
+
+ public Builder addPicStaticLibrary(LibraryToLink library) {
+ picStaticLibraries.add(library);
+ return this;
+ }
+
+ public Builder addPicStaticLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(picStaticLibraries, libraries);
+ return this;
+ }
+
+ public Builder addDynamicLibrary(LibraryToLink library) {
+ dynamicLibraries.add(library);
+ return this;
+ }
+
+ public Builder addDynamicLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(dynamicLibraries, libraries);
+ return this;
+ }
+
+ public Builder addExecutionDynamicLibrary(LibraryToLink library) {
+ executionDynamicLibraries.add(library);
+ return this;
+ }
+
+ public Builder addExecutionDynamicLibraries(Iterable<LibraryToLink> libraries) {
+ Iterables.addAll(executionDynamicLibraries, libraries);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcNativeLibraryProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcNativeLibraryProvider.java
new file mode 100644
index 0000000000..5e96291520
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcNativeLibraryProvider.java
@@ -0,0 +1,43 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A target that provides native libraries in the transitive closure of its deps that are needed for
+ * executing C++ code.
+ */
+@Immutable
+public final class CcNativeLibraryProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<LinkerInput> transitiveCcNativeLibraries;
+
+ public CcNativeLibraryProvider(NestedSet<LinkerInput> transitiveCcNativeLibraries) {
+ this.transitiveCcNativeLibraries = transitiveCcNativeLibraries;
+ }
+
+ /**
+ * Collects native libraries in the transitive closure of its deps that are needed for executing
+ * C/C++ code.
+ *
+ * <p>In effect, returns all dynamic library (.so) artifacts provided by the transitive closure.
+ */
+ public NestedSet<LinkerInput> getTransitiveCcNativeLibraries() {
+ return transitiveCcNativeLibraries;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcSpecificLinkParamsProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcSpecificLinkParamsProvider.java
new file mode 100644
index 0000000000..dfcecc276c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcSpecificLinkParamsProvider.java
@@ -0,0 +1,48 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore.CcLinkParamsStoreImpl;
+
+/**
+ * A target that provides libraries to be only linked into other C++ targets (and not targets
+ * for other languages)
+ */
+@Immutable
+public final class CcSpecificLinkParamsProvider implements TransitiveInfoProvider {
+ private final CcLinkParamsStoreImpl store;
+
+ public CcSpecificLinkParamsProvider(CcLinkParamsStore store) {
+ this.store = new CcLinkParamsStoreImpl(store);
+ }
+
+ public CcLinkParamsStore getLinkParams() {
+ return store;
+ }
+
+ public static final Function<TransitiveInfoCollection, CcLinkParamsStore> TO_LINK_PARAMS =
+ new Function<TransitiveInfoCollection, CcLinkParamsStore>() {
+ @Override
+ public CcLinkParamsStore apply(TransitiveInfoCollection input) {
+ CcSpecificLinkParamsProvider provider = input.getProvider(
+ CcSpecificLinkParamsProvider.class);
+ return provider == null ? null : provider.getLinkParams();
+ }
+ };
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcTest.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcTest.java
new file mode 100644
index 0000000000..78271836b0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcTest.java
@@ -0,0 +1,36 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/**
+ * A configured target class for cc_test rules.
+ */
+public abstract class CcTest implements RuleConfiguredTargetFactory {
+
+ private final CppSemantics semantics;
+
+ protected CcTest(CppSemantics semantics) {
+ this.semantics = semantics;
+ }
+
+ @Override
+ public ConfiguredTarget create(RuleContext context) throws InterruptedException {
+ return CcBinary.init(semantics, context, /*fake =*/ false, /*useTestOnlyFlags =*/ true);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
new file mode 100644
index 0000000000..bd39d0f745
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -0,0 +1,249 @@
+// Copyright 2014 Google Inc. 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 static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Actions;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.CompilationHelper;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.LicensesProvider;
+import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense;
+import com.google.devtools.build.lib.analysis.MiddlemanProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.License;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.List;
+
+/**
+ * Implementation for the cc_toolchain rule.
+ */
+public class CcToolchain implements RuleConfiguredTargetFactory {
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) {
+ final Label label = ruleContext.getLabel();
+ final NestedSet<Artifact> crosstool = ruleContext.getPrerequisite("all_files", Mode.HOST)
+ .getProvider(FileProvider.class).getFilesToBuild();
+ final NestedSet<Artifact> crosstoolMiddleman = getFiles(ruleContext, "all_files");
+ final NestedSet<Artifact> compile = getFiles(ruleContext, "compiler_files");
+ final NestedSet<Artifact> strip = getFiles(ruleContext, "strip_files");
+ final NestedSet<Artifact> objcopy = getFiles(ruleContext, "objcopy_files");
+ final NestedSet<Artifact> link = getFiles(ruleContext, "linker_files");
+ final NestedSet<Artifact> dwp = getFiles(ruleContext, "dwp_files");
+ final NestedSet<Artifact> libcLink = inputsForLibcLink(ruleContext);
+ String purposePrefix = Actions.escapeLabel(label) + "_";
+ String runtimeSolibDirBase = "_solib_" + "_" + Actions.escapeLabel(label);
+ final PathFragment runtimeSolibDir = ruleContext.getConfiguration()
+ .getBinFragment().getRelative(runtimeSolibDirBase);
+
+ CppConfiguration cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
+ // Static runtime inputs.
+ TransitiveInfoCollection staticRuntimeLibDep = selectDep(ruleContext, "static_runtime_libs",
+ cppConfiguration.getStaticRuntimeLibsLabel());
+ final NestedSet<Artifact> staticRuntimeLinkInputs;
+ final Artifact staticRuntimeLinkMiddleman;
+ if (cppConfiguration.supportsEmbeddedRuntimes()) {
+ staticRuntimeLinkInputs = staticRuntimeLibDep
+ .getProvider(FileProvider.class)
+ .getFilesToBuild();
+ } else {
+ staticRuntimeLinkInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (!staticRuntimeLinkInputs.isEmpty()) {
+ NestedSet<Artifact> staticRuntimeLinkMiddlemanSet = CompilationHelper.getAggregatingMiddleman(
+ ruleContext,
+ purposePrefix + "static_runtime_link",
+ staticRuntimeLibDep);
+ staticRuntimeLinkMiddleman = staticRuntimeLinkMiddlemanSet.isEmpty()
+ ? null : Iterables.getOnlyElement(staticRuntimeLinkMiddlemanSet);
+ } else {
+ staticRuntimeLinkMiddleman = null;
+ }
+
+ Preconditions.checkState(
+ (staticRuntimeLinkMiddleman == null) == staticRuntimeLinkInputs.isEmpty());
+
+ // Dynamic runtime inputs.
+ TransitiveInfoCollection dynamicRuntimeLibDep = selectDep(ruleContext, "dynamic_runtime_libs",
+ cppConfiguration.getDynamicRuntimeLibsLabel());
+ final NestedSet<Artifact> dynamicRuntimeLinkInputs;
+ final Artifact dynamicRuntimeLinkMiddleman;
+ if (cppConfiguration.supportsEmbeddedRuntimes()) {
+ NestedSetBuilder<Artifact> dynamicRuntimeLinkInputsBuilder = NestedSetBuilder.stableOrder();
+ for (Artifact artifact : dynamicRuntimeLibDep
+ .getProvider(FileProvider.class).getFilesToBuild()) {
+ if (CppHelper.SHARED_LIBRARY_FILETYPES.matches(artifact.getFilename())) {
+ dynamicRuntimeLinkInputsBuilder.add(SolibSymlinkAction.getCppRuntimeSymlink(
+ ruleContext, artifact, runtimeSolibDirBase,
+ ruleContext.getConfiguration()).getArtifact());
+ } else {
+ dynamicRuntimeLinkInputsBuilder.add(artifact);
+ }
+ }
+ dynamicRuntimeLinkInputs = dynamicRuntimeLinkInputsBuilder.build();
+ } else {
+ dynamicRuntimeLinkInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (!dynamicRuntimeLinkInputs.isEmpty()) {
+ List<Artifact> dynamicRuntimeLinkMiddlemanSet =
+ CppHelper.getAggregatingMiddlemanForCppRuntimes(
+ ruleContext,
+ purposePrefix + "dynamic_runtime_link",
+ dynamicRuntimeLibDep,
+ runtimeSolibDirBase,
+ ruleContext.getConfiguration());
+ dynamicRuntimeLinkMiddleman = dynamicRuntimeLinkMiddlemanSet.isEmpty()
+ ? null : Iterables.getOnlyElement(dynamicRuntimeLinkMiddlemanSet);
+ } else {
+ dynamicRuntimeLinkMiddleman = null;
+ }
+
+ Preconditions.checkState(
+ (dynamicRuntimeLinkMiddleman == null) == dynamicRuntimeLinkInputs.isEmpty());
+
+ CppCompilationContext.Builder contextBuilder =
+ new CppCompilationContext.Builder(ruleContext);
+ CppModuleMap moduleMap = createCrosstoolModuleMap(ruleContext);
+ if (moduleMap != null) {
+ contextBuilder.setCppModuleMap(moduleMap);
+ }
+ final CppCompilationContext context = contextBuilder.build();
+ boolean supportsParamFiles = ruleContext.attributes().get("supports_param_files", BOOLEAN);
+ boolean supportsHeaderParsing =
+ ruleContext.attributes().get("supports_header_parsing", BOOLEAN);
+
+ CcToolchainProvider provider = new CcToolchainProvider(
+ Preconditions.checkNotNull(ruleContext.getFragment(CppConfiguration.class)),
+ crosstool,
+ fullInputsForCrosstool(ruleContext, crosstoolMiddleman),
+ compile,
+ strip,
+ objcopy,
+ fullInputsForLink(ruleContext, link),
+ dwp,
+ libcLink,
+ staticRuntimeLinkInputs,
+ staticRuntimeLinkMiddleman,
+ dynamicRuntimeLinkInputs,
+ dynamicRuntimeLinkMiddleman,
+ runtimeSolibDir,
+ context,
+ supportsParamFiles,
+ supportsHeaderParsing);
+ RuleConfiguredTargetBuilder builder =
+ new RuleConfiguredTargetBuilder(ruleContext)
+ .add(CcToolchainProvider.class, provider)
+ .setFilesToBuild(new NestedSetBuilder<Artifact>(Order.STABLE_ORDER).build())
+ .add(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY));
+
+ // If output_license is specified on the cc_toolchain rule, override the transitive licenses
+ // with that one. This is necessary because cc_toolchain is used in the target configuration,
+ // but it is sort-of-kind-of a tool, but various parts of it are linked into the output...
+ // ...so we trust the judgment of the author of the cc_toolchain rule to figure out what
+ // licenses should be propagated to C++ targets.
+ License outputLicense = ruleContext.getRule().getToolOutputLicense(ruleContext.attributes());
+ if (outputLicense != null && outputLicense != License.NO_LICENSE) {
+ final NestedSet<TargetLicense> license = NestedSetBuilder.create(Order.STABLE_ORDER,
+ new TargetLicense(ruleContext.getLabel(), outputLicense));
+ LicensesProvider licensesProvider = new LicensesProvider() {
+ @Override
+ public NestedSet<TargetLicense> getTransitiveLicenses() {
+ return license;
+ }
+ };
+
+ builder.add(LicensesProvider.class, licensesProvider);
+ }
+
+ return builder.build();
+ }
+
+ private NestedSet<Artifact> inputsForLibcLink(RuleContext ruleContext) {
+ TransitiveInfoCollection libcLink = ruleContext.getPrerequisite(":libc_link", Mode.HOST);
+ return libcLink != null
+ ? libcLink.getProvider(FileProvider.class).getFilesToBuild()
+ : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER);
+ }
+
+ private NestedSet<Artifact> fullInputsForCrosstool(RuleContext ruleContext,
+ NestedSet<Artifact> crosstoolMiddleman) {
+ return NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(crosstoolMiddleman)
+ // Use "libc_link" here, because it is functionally identical to the case
+ // below. If we introduce separate filegroups for compiling and linking, we
+ // need to fix that here.
+ .addTransitive(AnalysisUtils.getMiddlemanFor(ruleContext, ":libc_link"))
+ .build();
+ }
+
+ private NestedSet<Artifact> fullInputsForLink(RuleContext ruleContext, NestedSet<Artifact> link) {
+ return NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(link)
+ .addTransitive(AnalysisUtils.getMiddlemanFor(ruleContext, ":libc_link"))
+ .add(ruleContext.getAnalysisEnvironment().getEmbeddedToolArtifact(
+ CppRuleClasses.BUILD_INTERFACE_SO))
+ .build();
+ }
+
+ private CppModuleMap createCrosstoolModuleMap(RuleContext ruleContext) {
+ if (ruleContext.getPrerequisite("module_map", Mode.HOST) == null) {
+ return null;
+ }
+ Artifact moduleMapArtifact = ruleContext.getPrerequisiteArtifact("module_map", Mode.HOST);
+ if (moduleMapArtifact == null) {
+ return null;
+ }
+ return new CppModuleMap(moduleMapArtifact, "crosstool");
+ }
+
+ private TransitiveInfoCollection selectDep(
+ RuleContext ruleContext, String attribute, Label label) {
+ for (TransitiveInfoCollection dep : ruleContext.getPrerequisites(attribute, Mode.TARGET)) {
+ if (dep.getLabel().equals(label)) {
+ return dep;
+ }
+ }
+
+ return ruleContext.getPrerequisites(attribute, Mode.TARGET).get(0);
+ }
+
+ private NestedSet<Artifact> getFiles(RuleContext context, String attribute) {
+ TransitiveInfoCollection dep = context.getPrerequisite(attribute, Mode.HOST);
+ MiddlemanProvider middlemanProvider = dep.getProvider(MiddlemanProvider.class);
+ // We use the middleman if we can (if the dep is a filegroup), otherwise, just the regular
+ // filesToBuild (e.g. if it is a simple input file)
+ return middlemanProvider != null
+ ? middlemanProvider.getMiddlemanArtifact()
+ : dep.getProvider(FileProvider.class).getFilesToBuild();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java
new file mode 100644
index 0000000000..29ab45cf4a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeatures.java
@@ -0,0 +1,802 @@
+// Copyright 2015 Google Inc. 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.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Provides access to features supported by a specific toolchain.
+ *
+ * <p>This class can be generated from the CToolchain protocol buffer.
+ *
+ * <p>TODO(bazel-team): Implement support for specifying the toolchain configuration directly from
+ * the BUILD file.
+ *
+ * <p>TODO(bazel-team): Find a place to put the public-facing documentation and link to it from
+ * here.
+ *
+ * <p>TODO(bazel-team): Split out Feature as CcToolchainFeature, which will modularize the
+ * crosstool configuration into one part that is about handling a set of features (including feature
+ * selection) and one part that is about how to apply a single feature (parsing flags and expanding
+ * them from build variables).
+ */
+@Immutable
+public class CcToolchainFeatures implements Serializable {
+
+ /**
+ * Thrown when a flag value cannot be expanded under a set of build variables.
+ *
+ * <p>This happens for example when a flag references a variable that is not provided by the
+ * action, or when a flag group references multiple variables of sequence type.
+ */
+ public static class ExpansionException extends RuntimeException {
+ ExpansionException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * A piece of a single flag.
+ *
+ * <p>A single flag can contain a combination of text and variables (for example
+ * "-f %{var1}/%{var2}"). We split the flag into chunks, where each chunk represents either a
+ * text snippet, or a variable that is to be replaced.
+ */
+ interface FlagChunk {
+
+ /**
+ * Expands this chunk.
+ *
+ * @param variables variable names mapped to their values for a single flag expansion.
+ * @param flag the flag content to append to.
+ */
+ void expand(Map<String, String> variables, StringBuilder flag);
+ }
+
+ /**
+ * A plain text chunk of a flag.
+ */
+ @Immutable
+ private static class StringChunk implements FlagChunk, Serializable {
+ private final String text;
+
+ private StringChunk(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public void expand(Map<String, String> variables, StringBuilder flag) {
+ flag.append(text);
+ }
+ }
+
+ /**
+ * A chunk of a flag into which a variable should be expanded.
+ */
+ @Immutable
+ private static class VariableChunk implements FlagChunk, Serializable {
+ private final String variableName;
+
+ private VariableChunk(String variableName) {
+ this.variableName = variableName;
+ }
+
+ @Override
+ public void expand(Map<String, String> variables, StringBuilder flag) {
+ String value = variables.get(variableName);
+ if (value == null) {
+ // We check all variables in FlagGroup.expandCommandLine, so if we arrive here with a
+ // null value, the variable map originally handed to the feature selection must have
+ // contained an explicit null value.
+ throw new ExpansionException("Internal blaze error: build variable was set to 'null'.");
+ }
+ flag.append(variables.get(variableName));
+ }
+ }
+
+ /**
+ * Parser for toolchain flags.
+ *
+ * <p>A flag contains a snippet of text supporting variable expansion. For example, a flag value
+ * "-f %{var1}/%{var2}" will expand the values of the variables "var1" and "var2" in the
+ * corresponding places in the string.
+ *
+ * <p>The {@code FlagParser} takes a flag string and parses it into a list of {@code FlagChunk}
+ * objects, where each chunk represents either a snippet of text or a variable to be expanded. In
+ * the above example, the resulting chunks would be ["-f ", var1, "/", var2].
+ *
+ * <p>In addition to the list of chunks, the {@code FlagParser} also provides the set of variables
+ * necessary for the expansion of this flag via {@code getUsedVariables}.
+ *
+ * <p>To get a literal percent character, "%%" can be used in the flag text.
+ */
+ private static class FlagParser {
+
+ /**
+ * The given flag value.
+ */
+ private final String value;
+
+ /**
+ * The current position in {@value} during parsing.
+ */
+ private int current = 0;
+
+ private final ImmutableList.Builder<FlagChunk> chunks = ImmutableList.builder();
+ private final ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
+
+ private FlagParser(String value) throws InvalidConfigurationException {
+ this.value = value;
+ parse();
+ }
+
+ /**
+ * @return the parsed chunks for this flag.
+ */
+ private ImmutableList<FlagChunk> getChunks() {
+ return chunks.build();
+ }
+
+ /**
+ * @return all variable names needed to expand this flag.
+ */
+ private ImmutableSet<String> getUsedVariables() {
+ return usedVariables.build();
+ }
+
+ /**
+ * Parses the flag.
+ *
+ * @throws InvalidConfigurationException if there is a parsing error.
+ */
+ private void parse() throws InvalidConfigurationException {
+ while (current < value.length()) {
+ if (atVariableStart()) {
+ parseVariableChunk();
+ } else {
+ parseStringChunk();
+ }
+ }
+ }
+
+ /**
+ * @return whether the current position is the start of a variable.
+ */
+ private boolean atVariableStart() {
+ // We parse a variable when value starts with '%', but not '%%'.
+ return value.charAt(current) == '%'
+ && (current + 1 >= value.length() || value.charAt(current + 1) != '%');
+ }
+
+ /**
+ * Parses a chunk of text until the next '%', which indicates either an escaped literal '%'
+ * or a variable.
+ */
+ private void parseStringChunk() {
+ int start = current;
+ // We only parse string chunks starting with '%' if they also start with '%%'.
+ // In that case, we want to have a single '%' in the string, so we start at the second
+ // character.
+ // Note that for flags like "abc%%def" this will lead to two string chunks, the first
+ // referencing the subtring "abc", and a second referencing the substring "%def".
+ if (value.charAt(current) == '%') {
+ current = current + 1;
+ start = current;
+ }
+ current = value.indexOf('%', current + 1);
+ if (current == -1) {
+ current = value.length();
+ }
+ final String text = value.substring(start, current);
+ chunks.add(new StringChunk(text));
+ }
+
+ /**
+ * Parses a variable to be expanded.
+ *
+ * @throws InvalidConfigurationException if there is a parsing error.
+ */
+ private void parseVariableChunk() throws InvalidConfigurationException {
+ current = current + 1;
+ if (current >= value.length() || value.charAt(current) != '{') {
+ abort("expected '{'");
+ }
+ current = current + 1;
+ if (current >= value.length() || value.charAt(current) == '}') {
+ abort("expected variable name");
+ }
+ int end = value.indexOf('}', current);
+ final String name = value.substring(current, end);
+ usedVariables.add(name);
+ chunks.add(new VariableChunk(name));
+ current = end + 1;
+ }
+
+ /**
+ * @throws InvalidConfigurationException with the given error text, adding information about
+ * the current position in the flag.
+ */
+ private void abort(String error) throws InvalidConfigurationException {
+ throw new InvalidConfigurationException("Invalid toolchain configuration: " + error
+ + " at position " + current + " while parsing a flag containing '" + value + "'");
+ }
+ }
+
+ /**
+ * A single flag to be expanded under a set of variables.
+ *
+ * <p>TODO(bazel-team): Consider specializing Flag for the simple case that a flag is just a bit
+ * of text.
+ */
+ @Immutable
+ private static class Flag implements Serializable {
+ private final ImmutableList<FlagChunk> chunks;
+
+ private Flag(ImmutableList<FlagChunk> chunks) {
+ this.chunks = chunks;
+ }
+
+ /**
+ * Expand this flag into a single new entry in {@code commandLine}.
+ */
+ private void expandCommandLine(Map<String, String> variables, List<String> commandLine) {
+ StringBuilder flag = new StringBuilder();
+ for (FlagChunk chunk : chunks) {
+ chunk.expand(variables, flag);
+ }
+ commandLine.add(flag.toString());
+ }
+ }
+
+ /**
+ * A group of flags.
+ */
+ @Immutable
+ private static class FlagGroup implements Serializable {
+ private final ImmutableList<Flag> flags;
+ private final ImmutableSet<String> usedVariables;
+
+ private FlagGroup(CToolchain.FlagGroup flagGroup) throws InvalidConfigurationException {
+ ImmutableList.Builder<Flag> flags = ImmutableList.builder();
+ ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
+ for (String flag : flagGroup.getFlagList()) {
+ FlagParser parser = new FlagParser(flag);
+ flags.add(new Flag(parser.getChunks()));
+ usedVariables.addAll(parser.getUsedVariables());
+ }
+ this.flags = flags.build();
+ this.usedVariables = usedVariables.build();
+ }
+
+ /**
+ * Expands all flags in this group and adds them to {@code commandLine}.
+ *
+ * <p>The flags of the group will be expanded either:
+ * <ul>
+ * <li>once, if there is no variable of sequence type in any of the group's flags, or</li>
+ * <li>for each element in the sequence, if there is one variable of sequence type within
+ * the flags.</li>
+ * </ul>
+ *
+ * <p>Having more than a single variable of sequence type in a single flag group is not
+ * supported.
+ */
+ private void expandCommandLine(Multimap<String, String> variables, List<String> commandLine) {
+ Map<String, String> variableView = new HashMap<>();
+ String sequenceName = null;
+ for (String name : usedVariables) {
+ Collection<String> value = variables.get(name);
+ if (value.isEmpty()) {
+ throw new ExpansionException("Invalid toolchain configuration: unknown variable '" + name
+ + "' can not be expanded.");
+ } else if (value.size() > 1) {
+ if (sequenceName != null) {
+ throw new ExpansionException(
+ "Invalid toolchain configuration: trying to expand two variable list in one "
+ + "flag group: '" + sequenceName + "' and '" + name + "'");
+ }
+ sequenceName = name;
+ } else {
+ variableView.put(name, value.iterator().next());
+ }
+ }
+ if (sequenceName != null) {
+ for (String value : variables.get(sequenceName)) {
+ variableView.put(sequenceName, value);
+ expandOnce(variableView, commandLine);
+ }
+ } else {
+ expandOnce(variableView, commandLine);
+ }
+ }
+
+ /**
+ * Expanding all flags of this group into {@code commandLine}.
+ */
+ private void expandOnce(Map<String, String> variables, List<String> commandLine) {
+ for (Flag flag : flags) {
+ flag.expandCommandLine(variables, commandLine);
+ }
+ }
+ }
+
+ /**
+ * Groups a set of flags to apply for certain actions.
+ */
+ @Immutable
+ private static class FlagSet implements Serializable {
+ private final ImmutableSet<String> actions;
+ private final ImmutableList<FlagGroup> flagGroups;
+
+ private FlagSet(CToolchain.FlagSet flagSet) throws InvalidConfigurationException {
+ this.actions = ImmutableSet.copyOf(flagSet.getActionList());
+ ImmutableList.Builder<FlagGroup> builder = ImmutableList.builder();
+ for (CToolchain.FlagGroup flagGroup : flagSet.getFlagGroupList()) {
+ builder.add(new FlagGroup(flagGroup));
+ }
+ this.flagGroups = builder.build();
+ }
+
+ /**
+ * Adds the flags that apply to the given {@code action} to {@code commandLine}.
+ */
+ private void expandCommandLine(String action, Multimap<String, String> variables,
+ List<String> commandLine) {
+ if (!actions.contains(action)) {
+ return;
+ }
+ for (FlagGroup flagGroup : flagGroups) {
+ flagGroup.expandCommandLine(variables, commandLine);
+ }
+ }
+ }
+
+ /**
+ * Contains flags for a specific feature.
+ */
+ @Immutable
+ private static class Feature implements Serializable {
+ private final String name;
+ private final ImmutableList<FlagSet> flagSets;
+
+ private Feature(CToolchain.Feature feature) throws InvalidConfigurationException {
+ this.name = feature.getName();
+ ImmutableList.Builder<FlagSet> builder = ImmutableList.builder();
+ for (CToolchain.FlagSet flagSet : feature.getFlagSetList()) {
+ builder.add(new FlagSet(flagSet));
+ }
+ this.flagSets = builder.build();
+ }
+
+ /**
+ * @return the features's name.
+ */
+ private String getName() {
+ return name;
+ }
+
+ /**
+ * Adds the flags that apply to the given {@code action} to {@code commandLine}.
+ */
+ private void expandCommandLine(String action, Multimap<String, String> variables,
+ List<String> commandLine) {
+ for (FlagSet flagSet : flagSets) {
+ flagSet.expandCommandLine(action, variables, commandLine);
+ }
+ }
+ }
+
+ /**
+ * Captures the set of enabled features for a rule.
+ */
+ @Immutable
+ public static class FeatureConfiguration {
+ private final ImmutableSet<String> enabledFeatureNames;
+ private final ImmutableList<Feature> enabledFeatures;
+
+ public FeatureConfiguration() {
+ enabledFeatureNames = ImmutableSet.of();
+ enabledFeatures = ImmutableList.of();
+ }
+
+ private FeatureConfiguration(ImmutableList<Feature> enabledFeatures) {
+ this.enabledFeatures = enabledFeatures;
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (Feature feature : enabledFeatures) {
+ builder.add(feature.getName());
+ }
+ this.enabledFeatureNames = builder.build();
+ }
+
+ /**
+ * @return whether the given {@code feature} is enabled.
+ */
+ boolean isEnabled(String feature) {
+ return enabledFeatureNames.contains(feature);
+ }
+
+ /**
+ * @return the command line for the given {@code action}.
+ */
+ List<String> getCommandLine(String action, Multimap<String, String> variables) {
+ List<String> commandLine = new ArrayList<>();
+ for (Feature feature : enabledFeatures) {
+ feature.expandCommandLine(action, variables, commandLine);
+ }
+ return commandLine;
+ }
+ }
+
+ /**
+ * All features in the order in which they were specified in the configuration.
+ *
+ * <p>We guarantee the command line to be in the order in which the flags were specified in the
+ * configuration.
+ */
+ private final ImmutableList<Feature> features;
+
+ /**
+ * Maps from the feature's name to the feature.
+ */
+ private final ImmutableMap<String, Feature> featuresByName;
+
+ /**
+ * Maps from a feature to a set of all the features it has a direct 'implies' edge to.
+ */
+ private final ImmutableMultimap<Feature, Feature> implies;
+
+ /**
+ * Maps from a feature to all features that have an direct 'implies' edge to this feature.
+ */
+ private final ImmutableMultimap<Feature, Feature> impliedBy;
+
+ /**
+ * Maps from a feature to a set of feature sets, where:
+ * <ul>
+ * <li>a feature set satisfies the 'requires' condition, if all features in the feature set are
+ * enabled</li>
+ * <li>the 'requires' condition is satisfied, if at least one of the feature sets satisfies the
+ * 'requires' condition.</li>
+ * </ul>
+ */
+ private final ImmutableMultimap<Feature, ImmutableSet<Feature>> requires;
+
+ /**
+ * Maps from a feature to all features that have a requirement referencing it.
+ *
+ * <p>This will be used to determine which features need to be re-checked after a feature was
+ * disabled.
+ */
+ private final ImmutableMultimap<Feature, Feature> requiredBy;
+
+ /**
+ * A cache of feature selection results, so we do not recalculate the feature selection for
+ * all actions.
+ */
+ private transient LoadingCache<Collection<String>, FeatureConfiguration>
+ configurationCache = buildConfigurationCache();
+
+ /**
+ * Constructs the feature configuration from a {@code CToolchain} protocol buffer.
+ *
+ * @param toolchain the toolchain configuration as specified by the user.
+ * @throws InvalidConfigurationException if the configuration has logical errors.
+ */
+ CcToolchainFeatures(CToolchain toolchain) throws InvalidConfigurationException {
+ // Build up the feature graph.
+ // First, we build up the map of name -> features in one pass, so that earlier features can
+ // reference later features in their configuration.
+ ImmutableList.Builder<Feature> features = ImmutableList.builder();
+ HashMap<String, Feature> featuresByName = new HashMap<>();
+ for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) {
+ Feature feature = new Feature(toolchainFeature);
+ features.add(feature);
+ if (featuresByName.put(feature.getName(), feature) != null) {
+ throw new InvalidConfigurationException("Invalid toolchain configuration: feature '"
+ + feature.getName() + "' was specified multiple times.");
+ }
+ }
+ this.features = features.build();
+ this.featuresByName = ImmutableMap.copyOf(featuresByName);
+
+ // Next, we build up all forward references for 'implies' and 'requires' edges.
+ ImmutableMultimap.Builder<Feature, Feature> implies = ImmutableMultimap.builder();
+ ImmutableMultimap.Builder<Feature, ImmutableSet<Feature>> requires =
+ ImmutableMultimap.builder();
+ // We also store the reverse 'implied by' and 'required by' edges during this pass.
+ ImmutableMultimap.Builder<Feature, Feature> impliedBy = ImmutableMultimap.builder();
+ ImmutableMultimap.Builder<Feature, Feature> requiredBy = ImmutableMultimap.builder();
+ for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) {
+ String name = toolchainFeature.getName();
+ Feature feature = featuresByName.get(name);
+ for (CToolchain.FeatureSet requiredFeatures : toolchainFeature.getRequiresList()) {
+ ImmutableSet.Builder<Feature> allOf = ImmutableSet.builder();
+ for (String requiredName : requiredFeatures.getFeatureList()) {
+ Feature required = getFeatureOrFail(requiredName, name);
+ allOf.add(required);
+ requiredBy.put(required, feature);
+ }
+ requires.put(feature, allOf.build());
+ }
+ for (String impliedName : toolchainFeature.getImpliesList()) {
+ Feature implied = getFeatureOrFail(impliedName, name);
+ impliedBy.put(implied, feature);
+ implies.put(feature, implied);
+ }
+ }
+ this.implies = implies.build();
+ this.requires = requires.build();
+ this.impliedBy = impliedBy.build();
+ this.requiredBy = requiredBy.build();
+ }
+
+ /**
+ * Assign an empty cache after default-deserializing all non-transient members.
+ */
+ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
+ in.defaultReadObject();
+ this.configurationCache = buildConfigurationCache();
+ }
+
+ /**
+ * @return an empty {@code FeatureConfiguration} cache.
+ */
+ private LoadingCache<Collection<String>, FeatureConfiguration> buildConfigurationCache() {
+ return CacheBuilder.newBuilder()
+ // TODO(klimek): Benchmark and tweak once we support a larger configuration.
+ .maximumSize(10000)
+ .build(new CacheLoader<Collection<String>, FeatureConfiguration>() {
+ @Override
+ public FeatureConfiguration load(Collection<String> requestedFeatures) {
+ return computeFeatureConfiguration(requestedFeatures);
+ }
+ });
+ }
+
+ /**
+ * Given a list of {@code requestedFeatures}, returns all features that are enabled by the
+ * toolchain configuration.
+ *
+ * <p>A requested feature will not be enabled if the toolchain does not support it (which may
+ * depend on other requested features).
+ *
+ * <p>Additional features will be enabled if the toolchain supports them and they are implied by
+ * requested features.
+ */
+ FeatureConfiguration getFeatureConfiguration(Collection<String> requestedFeatures) {
+ return configurationCache.getUnchecked(requestedFeatures);
+ }
+
+ private FeatureConfiguration computeFeatureConfiguration(Collection<String> requestedFeatures) {
+ // Command line flags will be output in the order in which they are specified in the toolchain
+ // configuration.
+ return new FeatureSelection(requestedFeatures).run();
+ }
+
+ /**
+ * Convenience method taking a variadic string argument list for testing.
+ */
+ FeatureConfiguration getFeatureConfiguration(String... requestedFeatures) {
+ return getFeatureConfiguration(Arrays.asList(requestedFeatures));
+ }
+
+ /**
+ * @return the feature with the given {@code name}.
+ *
+ * @throws InvalidConfigurationException if no feature with the given name was configured.
+ */
+ private Feature getFeatureOrFail(String name, String reference)
+ throws InvalidConfigurationException {
+ if (!featuresByName.containsKey(name)) {
+ throw new InvalidConfigurationException("Invalid toolchain configuration: feature '" + name
+ + "', which is referenced from feature '" + reference + "', is not defined.");
+ }
+ return featuresByName.get(name);
+ }
+
+ @VisibleForTesting
+ Collection<String> getFeatureNames() {
+ Collection<String> featureNames = new HashSet<>();
+ for (Feature feature : features) {
+ featureNames.add(feature.getName());
+ }
+ return featureNames;
+ }
+
+ /**
+ * Implements the feature selection algorithm.
+ *
+ * <p>Feature selection is done by first enabling all features reachable by an 'implies' edge,
+ * and then iteratively pruning features that have unmet requirements.
+ */
+ private class FeatureSelection {
+
+ /**
+ * The features Bazel would like to enable; either because they are supported and generally
+ * useful, or because the user required them (for example through the command line).
+ */
+ private final ImmutableSet<Feature> requestedFeatures;
+
+ /**
+ * The currently enabled feature; during feature selection, we first put all features reachable
+ * via an 'implies' edge into the enabled feature set, and than prune that set from features
+ * that have unmet requirements.
+ */
+ private Set<Feature> enabled = new HashSet<>();
+
+ private FeatureSelection(Collection<String> requestedFeatures) {
+ ImmutableSet.Builder<Feature> builder = ImmutableSet.builder();
+ for (String name : requestedFeatures) {
+ if (featuresByName.containsKey(name)) {
+ builder.add(featuresByName.get(name));
+ }
+ }
+ this.requestedFeatures = builder.build();
+ }
+
+ /**
+ * @return all enabled features in the order in which they were specified in the configuration.
+ */
+ private FeatureConfiguration run() {
+ for (Feature feature : requestedFeatures) {
+ enableAllImpliedBy(feature);
+ }
+ disableUnsupportedFeatures();
+ ImmutableList.Builder<Feature> enabledFeaturesInOrder = ImmutableList.builder();
+ for (Feature feature : features) {
+ if (enabled.contains(feature)) {
+ enabledFeaturesInOrder.add(feature);
+ }
+ }
+ return new FeatureConfiguration(enabledFeaturesInOrder.build());
+ }
+
+ /**
+ * Transitively and unconditionally enable all features implied by the given feature and the
+ * feature itself to the enabled feature set.
+ */
+ private void enableAllImpliedBy(Feature feature) {
+ if (enabled.contains(feature)) {
+ return;
+ }
+ enabled.add(feature);
+ for (Feature implied : implies.get(feature)) {
+ enableAllImpliedBy(implied);
+ }
+ }
+
+ /**
+ * Remove all unsupported features from the enabled feature set.
+ */
+ private void disableUnsupportedFeatures() {
+ Queue<Feature> check = new ArrayDeque<>(enabled);
+ while (!check.isEmpty()) {
+ checkFeature(check.poll());
+ }
+ }
+
+ /**
+ * Check if the given feature is still satisfied within the set of currently enabled features.
+ *
+ * <p>If it is not, remove the feature from the set of enabled features, and re-check all
+ * features that may now also become disabled.
+ */
+ private void checkFeature(Feature feature) {
+ if (!enabled.contains(feature) || isSatisfied(feature)) {
+ return;
+ }
+ enabled.remove(feature);
+
+ // Once we disable a feature, we have to re-check all features that can be affected by
+ // that removal.
+ // 1. A feature that implied the current feature is now going to be disabled.
+ for (Feature impliesCurrent : impliedBy.get(feature)) {
+ checkFeature(impliesCurrent);
+ }
+ // 2. A feature that required the current feature may now be disabled, depending on whether
+ // the requirement was optional.
+ for (Feature requiresCurrent : requiredBy.get(feature)) {
+ checkFeature(requiresCurrent);
+ }
+ // 3. A feature that this feature implied may now be disabled if no other feature also implies
+ // it.
+ for (Feature implied : implies.get(feature)) {
+ checkFeature(implied);
+ }
+ }
+
+ /**
+ * @return whether all requirements of the feature are met in the set of currently enabled
+ * features.
+ */
+ private boolean isSatisfied(Feature feature) {
+ return (requestedFeatures.contains(feature) || isImpliedByEnabledFeature(feature))
+ && allImplicationsEnabled(feature) && allRequirementsMet(feature);
+ }
+
+ /**
+ * @return whether a currently enabled feature implies the given feature.
+ */
+ private boolean isImpliedByEnabledFeature(Feature feature) {
+ for (Feature implies : impliedBy.get(feature)) {
+ if (enabled.contains(implies)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether all implications of the given feature are enabled.
+ */
+ private boolean allImplicationsEnabled(Feature feature) {
+ for (Feature implied : implies.get(feature)) {
+ if (!enabled.contains(implied)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return whether all requirements are enabled.
+ *
+ * <p>This implies that for any of the feature sets all of the specified features are enabled.
+ */
+ private boolean allRequirementsMet(Feature feature) {
+ if (!requires.containsKey(feature)) {
+ return true;
+ }
+ for (ImmutableSet<Feature> requiresAllOf : requires.get(feature)) {
+ boolean requirementMet = true;
+ for (Feature required : requiresAllOf) {
+ if (!enabled.contains(required)) {
+ requirementMet = false;
+ break;
+ }
+ }
+ if (requirementMet) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
new file mode 100644
index 0000000000..e1940a5d1f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
@@ -0,0 +1,226 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.devtools.build.lib.actions.Artifact;
+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.vfs.PathFragment;
+
+import javax.annotation.Nullable;
+
+/**
+ * Information about a C++ compiler used by the <code>cc_*</code> rules.
+ */
+@Immutable
+public final class CcToolchainProvider implements TransitiveInfoProvider {
+ /**
+ * An empty toolchain to be returned in the error case (instead of null).
+ */
+ public static final CcToolchainProvider EMPTY_TOOLCHAIN_IS_ERROR = new CcToolchainProvider(
+ null,
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ null,
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ null,
+ PathFragment.EMPTY_FRAGMENT,
+ CppCompilationContext.EMPTY,
+ false,
+ false);
+
+ @Nullable private final CppConfiguration cppConfiguration;
+ private final NestedSet<Artifact> crosstool;
+ private final NestedSet<Artifact> crosstoolMiddleman;
+ private final NestedSet<Artifact> compile;
+ private final NestedSet<Artifact> strip;
+ private final NestedSet<Artifact> objCopy;
+ private final NestedSet<Artifact> link;
+ private final NestedSet<Artifact> dwp;
+ private final NestedSet<Artifact> libcLink;
+ private final NestedSet<Artifact> staticRuntimeLinkInputs;
+ @Nullable private final Artifact staticRuntimeLinkMiddleman;
+ private final NestedSet<Artifact> dynamicRuntimeLinkInputs;
+ @Nullable private final Artifact dynamicRuntimeLinkMiddleman;
+ private final PathFragment dynamicRuntimeSolibDir;
+ private final CppCompilationContext cppCompilationContext;
+ private final boolean supportsParamFiles;
+ private final boolean supportsHeaderParsing;
+
+ public CcToolchainProvider(
+ @Nullable CppConfiguration cppConfiguration,
+ NestedSet<Artifact> crosstool,
+ NestedSet<Artifact> crosstoolMiddleman,
+ NestedSet<Artifact> compile,
+ NestedSet<Artifact> strip,
+ NestedSet<Artifact> objCopy,
+ NestedSet<Artifact> link,
+ NestedSet<Artifact> dwp,
+ NestedSet<Artifact> libcLink,
+ NestedSet<Artifact> staticRuntimeLinkInputs,
+ @Nullable Artifact staticRuntimeLinkMiddleman,
+ NestedSet<Artifact> dynamicRuntimeLinkInputs,
+ @Nullable Artifact dynamicRuntimeLinkMiddleman,
+ PathFragment dynamicRuntimeSolibDir,
+ CppCompilationContext cppCompilationContext,
+ boolean supportsParamFiles,
+ boolean supportsHeaderParsing) {
+ this.cppConfiguration = cppConfiguration;
+ this.crosstool = Preconditions.checkNotNull(crosstool);
+ this.crosstoolMiddleman = Preconditions.checkNotNull(crosstoolMiddleman);
+ this.compile = Preconditions.checkNotNull(compile);
+ this.strip = Preconditions.checkNotNull(strip);
+ this.objCopy = Preconditions.checkNotNull(objCopy);
+ this.link = Preconditions.checkNotNull(link);
+ this.dwp = Preconditions.checkNotNull(dwp);
+ this.libcLink = Preconditions.checkNotNull(libcLink);
+ this.staticRuntimeLinkInputs = Preconditions.checkNotNull(staticRuntimeLinkInputs);
+ this.staticRuntimeLinkMiddleman = staticRuntimeLinkMiddleman;
+ this.dynamicRuntimeLinkInputs = Preconditions.checkNotNull(dynamicRuntimeLinkInputs);
+ this.dynamicRuntimeLinkMiddleman = dynamicRuntimeLinkMiddleman;
+ this.dynamicRuntimeSolibDir = Preconditions.checkNotNull(dynamicRuntimeSolibDir);
+ this.cppCompilationContext = Preconditions.checkNotNull(cppCompilationContext);
+ this.supportsParamFiles = supportsParamFiles;
+ this.supportsHeaderParsing = supportsHeaderParsing;
+ }
+
+ /**
+ * Returns all the files in Crosstool. Is not a middleman.
+ */
+ public NestedSet<Artifact> getCrosstool() {
+ return crosstool;
+ }
+
+ /**
+ * Returns a middleman for all the files in Crosstool.
+ */
+ public NestedSet<Artifact> getCrosstoolMiddleman() {
+ return crosstoolMiddleman;
+ }
+
+ /**
+ * Returns the files necessary for compilation.
+ */
+ public NestedSet<Artifact> getCompile() {
+ // If include scanning is disabled, we need the entire crosstool filegroup, including header
+ // files. If it is enabled, we use the filegroup without header files - they are found by
+ // include scanning. For go, we also don't need the header files.
+ return cppConfiguration != null && cppConfiguration.shouldScanIncludes() ? compile : crosstool;
+ }
+
+ /**
+ * Returns the files necessary for a 'strip' invocation.
+ */
+ public NestedSet<Artifact> getStrip() {
+ return strip;
+ }
+
+ /**
+ * Returns the files necessary for an 'objcopy' invocation.
+ */
+ public NestedSet<Artifact> getObjcopy() {
+ return objCopy;
+ }
+
+ /**
+ * Returns the files necessary for linking, including the files needed for libc.
+ */
+ public NestedSet<Artifact> getLink() {
+ return link;
+ }
+
+ public NestedSet<Artifact> getDwp() {
+ return dwp;
+ }
+
+ public NestedSet<Artifact> getLibcLink() {
+ return libcLink;
+ }
+
+ /**
+ * Returns the static runtime libraries.
+ */
+ public NestedSet<Artifact> getStaticRuntimeLinkInputs() {
+ return staticRuntimeLinkInputs;
+ }
+
+ /**
+ * Returns an aggregating middleman that represents the static runtime libraries.
+ */
+ @Nullable public Artifact getStaticRuntimeLinkMiddleman() {
+ return staticRuntimeLinkMiddleman;
+ }
+
+ /**
+ * Returns the dynamic runtime libraries.
+ */
+ public NestedSet<Artifact> getDynamicRuntimeLinkInputs() {
+ return dynamicRuntimeLinkInputs;
+ }
+
+ /**
+ * Returns an aggregating middleman that represents the dynamic runtime libraries.
+ */
+ @Nullable public Artifact getDynamicRuntimeLinkMiddleman() {
+ return dynamicRuntimeLinkMiddleman;
+ }
+
+ /**
+ * Returns the name of the directory where the solib symlinks for the dynamic runtime libraries
+ * live. The directory itself will be under the root of the host configuration in the 'bin'
+ * directory.
+ */
+ public PathFragment getDynamicRuntimeSolibDir() {
+ return dynamicRuntimeSolibDir;
+ }
+
+ /**
+ * Returns the C++ compilation context for the toolchain.
+ */
+ public CppCompilationContext getCppCompilationContext() {
+ return cppCompilationContext;
+ }
+
+ /**
+ * Whether the toolchains supports parameter files.
+ */
+ public boolean supportsParamFiles() {
+ return supportsParamFiles;
+ }
+
+ /**
+ * Whether the toolchains supports header parsing.
+ */
+ public boolean supportsHeaderParsing() {
+ return supportsHeaderParsing;
+ }
+
+ /**
+ * Returns the configured features of the toolchain.
+ */
+ public CcToolchainFeatures getFeatures() {
+ return cppConfiguration.getFeatures();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java
new file mode 100644
index 0000000000..6c68f00bab
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java
@@ -0,0 +1,71 @@
+// Copyright 2014 Google Inc. 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 static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
+import static com.google.devtools.build.lib.packages.Type.LICENSE;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.BlazeRule;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * Rule definition for compiler definition.
+ */
+@BlazeRule(name = "cc_toolchain",
+ ancestors = { BaseRuleClasses.BaseRule.class },
+ factoryClass = CcToolchain.class)
+public final class CcToolchainRule implements RuleDefinition {
+ private static final LateBoundLabel<BuildConfiguration> LIBC_LINK =
+ new LateBoundLabel<BuildConfiguration>() {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration.getFragment(CppConfiguration.class).getLibcLabel();
+ }
+ };
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .setUndocumented()
+ .add(attr("output_licenses", LICENSE))
+ .add(attr("cpu", STRING).mandatory())
+ .add(attr("all_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("compiler_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("strip_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("objcopy_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("linker_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("dwp_files", LABEL).legacyAllowAnyFileType().cfg(HOST).mandatory())
+ .add(attr("static_runtime_libs", LABEL_LIST).legacyAllowAnyFileType().mandatory())
+ .add(attr("dynamic_runtime_libs", LABEL_LIST).legacyAllowAnyFileType().mandatory())
+ .add(attr("module_map", LABEL).legacyAllowAnyFileType().cfg(HOST))
+ .add(attr("supports_param_files", BOOLEAN).value(true))
+ .add(attr("supports_header_parsing", BOOLEAN).value(false))
+ // TODO(bazel-team): Should be using the TARGET configuration.
+ .add(attr(":libc_link", LABEL).cfg(HOST).value(LIBC_LINK))
+ .build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppBuildInfo.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppBuildInfo.java
new file mode 100644
index 0000000000..78a5f89700
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppBuildInfo.java
@@ -0,0 +1,89 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * C++ build info creation - generates header files that contain the corresponding build-info data.
+ */
+public final class CppBuildInfo implements BuildInfoFactory {
+ public static final BuildInfoKey KEY = new BuildInfoKey("C++");
+
+ private static final PathFragment BUILD_INFO_NONVOLATILE_HEADER_NAME =
+ new PathFragment("build-info-nonvolatile.h");
+ private static final PathFragment BUILD_INFO_VOLATILE_HEADER_NAME =
+ new PathFragment("build-info-volatile.h");
+ // TODO(bazel-team): (2011) Get rid of the redacted build info. We should try to make
+ // the linkstamping process handle the case where those values are undefined.
+ private static final PathFragment BUILD_INFO_REDACTED_HEADER_NAME =
+ new PathFragment("build-info-redacted.h");
+
+ @Override
+ public BuildInfoCollection create(BuildInfoContext buildInfoContext, BuildConfiguration config,
+ Artifact buildInfo, Artifact buildChangelist) {
+ List<Action> actions = new ArrayList<>();
+ WriteBuildInfoHeaderAction redactedInfo = getHeader(buildInfoContext, config,
+ BUILD_INFO_REDACTED_HEADER_NAME,
+ Artifact.NO_ARTIFACTS, true, true);
+ WriteBuildInfoHeaderAction nonvolatileInfo = getHeader(buildInfoContext, config,
+ BUILD_INFO_NONVOLATILE_HEADER_NAME,
+ ImmutableList.of(buildInfo),
+ false, true);
+ WriteBuildInfoHeaderAction volatileInfo = getHeader(buildInfoContext, config,
+ BUILD_INFO_VOLATILE_HEADER_NAME,
+ ImmutableList.of(buildChangelist),
+ true, false);
+ actions.add(redactedInfo);
+ actions.add(nonvolatileInfo);
+ actions.add(volatileInfo);
+ return new BuildInfoCollection(actions,
+ ImmutableList.of(nonvolatileInfo.getPrimaryOutput(), volatileInfo.getPrimaryOutput()),
+ ImmutableList.of(redactedInfo.getPrimaryOutput()));
+ }
+
+ private WriteBuildInfoHeaderAction getHeader(BuildInfoContext buildInfoContext,
+ BuildConfiguration config, PathFragment headerName,
+ Collection<Artifact> inputs,
+ boolean writeVolatileInfo, boolean writeNonVolatileInfo) {
+ Root outputPath = config.getIncludeDirectory();
+ final Artifact header =
+ buildInfoContext.getBuildInfoArtifact(headerName, outputPath,
+ writeVolatileInfo && !inputs.isEmpty()
+ ? BuildInfoType.NO_REBUILD : BuildInfoType.FORCE_REBUILD_IF_CHANGED);
+ return new WriteBuildInfoHeaderAction(
+ inputs, header, writeVolatileInfo, writeNonVolatileInfo);
+ }
+
+ @Override
+ public BuildInfoKey getKey() {
+ return KEY;
+ }
+
+ @Override
+ public boolean isEnabled(BuildConfiguration config) {
+ return config.hasFragment(CppConfiguration.class);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java
new file mode 100644
index 0000000000..cf39ef57c5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java
@@ -0,0 +1,918 @@
+// Copyright 2014 Google Inc. 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.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+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.vfs.PathFragment;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 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 ImmutableList<DepsContext> depsContexts;
+ private final CppModuleMap cppModuleMap;
+ private final Artifact headerModule;
+ private final Artifact picHeaderModule;
+ private final ImmutableSet<Artifact> compilationPrerequisites;
+
+ private CppCompilationContext(CommandLineContext commandLineContext,
+ List<DepsContext> depsContexts, CppModuleMap cppModuleMap, Artifact headerModule,
+ Artifact picHeaderModule) {
+ Preconditions.checkNotNull(commandLineContext);
+ Preconditions.checkArgument(!depsContexts.isEmpty());
+ this.commandLineContext = commandLineContext;
+ this.depsContexts = ImmutableList.copyOf(depsContexts);
+ this.cppModuleMap = cppModuleMap;
+ this.headerModule = headerModule;
+ this.picHeaderModule = picHeaderModule;
+
+ if (depsContexts.size() == 1) {
+ // Only LIPO targets have more than one DepsContexts. This codepath avoids creating
+ // an ImmutableSet.Builder for the vast majority of the cases.
+ compilationPrerequisites = (depsContexts.get(0).compilationPrerequisiteStampFile != null)
+ ? ImmutableSet.<Artifact>of(depsContexts.get(0).compilationPrerequisiteStampFile)
+ : ImmutableSet.<Artifact>of();
+ } else {
+ ImmutableSet.Builder<Artifact> prerequisites = ImmutableSet.builder();
+ for (DepsContext depsContext : depsContexts) {
+ if (depsContext.compilationPrerequisiteStampFile != null) {
+ prerequisites.add(depsContext.compilationPrerequisiteStampFile);
+ }
+ }
+ compilationPrerequisites = prerequisites.build();
+ }
+ }
+
+ /**
+ * Returns the compilation prerequisites consolidated into middlemen
+ * prerequisites, or an empty set if there are no prerequisites.
+ *
+ * <p>For correct dependency tracking, and to reduce the overhead to establish
+ * dependencies on generated headers, 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> getCompilationPrerequisites() {
+ 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 BuildConfiguration#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 BuildConfiguration#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 BuildConfiguration#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() {
+ if (depsContexts.isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (depsContexts.size() == 1) {
+ return depsContexts.get(0).declaredIncludeDirs;
+ }
+
+ NestedSetBuilder<PathFragment> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.declaredIncludeDirs);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * 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() {
+ if (depsContexts.isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (depsContexts.size() == 1) {
+ return depsContexts.get(0).declaredIncludeWarnDirs;
+ }
+
+ NestedSetBuilder<PathFragment> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.declaredIncludeWarnDirs);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * 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() {
+ if (depsContexts.isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (depsContexts.size() == 1) {
+ return depsContexts.get(0).declaredIncludeSrcs;
+ }
+
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.declaredIncludeSrcs);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Returns the immutable pairs of (header file, pregrepped header file).
+ */
+ public NestedSet<Pair<Artifact, Artifact>> getPregreppedHeaders() {
+ if (depsContexts.isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (depsContexts.size() == 1) {
+ return depsContexts.get(0).pregreppedHdrs;
+ }
+
+ NestedSetBuilder<Pair<Artifact, Artifact>> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.pregreppedHdrs);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Returns the immutable set of additional transitive inputs needed for
+ * compilation, like C++ module map artifacts.
+ */
+ public NestedSet<Artifact> getAdditionalInputs() {
+ if (depsContexts.isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ if (depsContexts.size() == 1) {
+ return depsContexts.get(0).auxiliaryInputs;
+ }
+
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.auxiliaryInputs);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Returns optional inputs that are needed by any C++ compilations that use header modules.
+ *
+ * <p>For every target that the current target depends on transitively and that is built as header
+ * module, contains:
+ * <ul>
+ * <li>the pic/non-pic header module (pcm file)</li>
+ * <li>the transitive list of module maps.</li>
+ * </ul>
+ */
+ private NestedSet<Artifact> getTransitiveAuxiliaryInputs() {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.transitiveAuxiliaryInputs);
+ }
+ return builder.build();
+ }
+
+ /**
+ * @return all modules maps in the transitive closure.
+ */
+ private NestedSet<Artifact> getTransitiveModuleMaps() {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.transitiveModuleMaps);
+ }
+ return builder.build();
+ }
+
+ /**
+ * @return all headers whose transitive closure of includes needs to be
+ * available when compiling anything in the current target.
+ */
+ protected NestedSet<Artifact> getTransitiveHeaderModuleSrcs() {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.transitiveHeaderModuleSrcs);
+ }
+ return builder.build();
+ }
+
+ /**
+ * @return all declared headers of the current module if the current target
+ * is compiled as a module.
+ */
+ protected NestedSet<Artifact> getHeaderModuleSrcs() {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (DepsContext depsContext : depsContexts) {
+ builder.addTransitive(depsContext.headerModuleSrcs);
+ }
+ return builder.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;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof CppCompilationContext)) {
+ return false;
+ }
+ CppCompilationContext other = (CppCompilationContext) obj;
+ return Objects.equals(headerModule, other.headerModule)
+ && Objects.equals(picHeaderModule, other.picHeaderModule)
+ && commandLineContext.equals(other.commandLineContext)
+ && depsContexts.equals(other.depsContexts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(headerModule, picHeaderModule, commandLineContext, depsContexts);
+ }
+
+ /**
+ * 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) {
+ ImmutableList.Builder<DepsContext> builder = ImmutableList.builder();
+ for (DepsContext depsContext : context.depsContexts) {
+ builder.add(new DepsContext(
+ depsContext.compilationPrerequisiteStampFile,
+ NestedSetBuilder.<PathFragment>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<PathFragment>emptySet(Order.STABLE_ORDER),
+ depsContext.declaredIncludeSrcs,
+ depsContext.pregreppedHdrs,
+ depsContext.auxiliaryInputs,
+ depsContext.headerModuleSrcs,
+ depsContext.transitiveAuxiliaryInputs,
+ depsContext.transitiveHeaderModuleSrcs,
+ depsContext.transitiveModuleMaps));
+ }
+ return new CppCompilationContext(context.commandLineContext, builder.build(),
+ context.cppModuleMap, context.headerModule, context.picHeaderModule);
+ }
+
+ /**
+ * 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).
+ *
+ * <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) {
+ return new CppCompilationContext(libContext.commandLineContext,
+ ImmutableList.copyOf(Iterables.concat(ownerContext.depsContexts, libContext.depsContexts)),
+ libContext.cppModuleMap, libContext.headerModule, libContext.picHeaderModule);
+ }
+
+ /**
+ * @return the C++ module map of the owner.
+ */
+ public CppModuleMap getCppModuleMap() {
+ return cppModuleMap;
+ }
+
+ /**
+ * @return the non-pic C++ header module of the owner.
+ */
+ private Artifact getHeaderModule() {
+ return headerModule;
+ }
+
+ /**
+ * @return the pic C++ header module of the owner.
+ */
+ private Artifact getPicHeaderModule() {
+ return picHeaderModule;
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof CommandLineContext)) {
+ return false;
+ }
+ CommandLineContext other = (CommandLineContext) obj;
+ return Objects.equals(includeDirs, other.includeDirs)
+ && Objects.equals(quoteIncludeDirs, other.quoteIncludeDirs)
+ && Objects.equals(systemIncludeDirs, other.systemIncludeDirs)
+ && Objects.equals(defines, other.defines);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(includeDirs, quoteIncludeDirs, systemIncludeDirs, defines);
+ }
+ }
+
+ /**
+ * The parts of the compilation context that defined the dependencies of
+ * actions of scheduling and inclusion validity checking.
+ */
+ @Immutable
+ private static class DepsContext {
+ private final Artifact compilationPrerequisiteStampFile;
+ private final NestedSet<PathFragment> declaredIncludeDirs;
+ private final NestedSet<PathFragment> declaredIncludeWarnDirs;
+ private final NestedSet<Artifact> declaredIncludeSrcs;
+ private final NestedSet<Pair<Artifact, Artifact>> pregreppedHdrs;
+
+ /**
+ * Optional inputs that are used by some forms of compilation, containing:
+ * <ul>
+ * <li>module map of the current target</li>
+ * <li>module maps of all direct dependencies that are not compiled as header modules</li>
+ * <li>all transitiveAuxiliaryInputs.</li>
+ * </ul>
+ */
+ private final NestedSet<Artifact> auxiliaryInputs;
+
+ /**
+ * All declared headers of the current module, if compiled as a header module.
+ */
+ private final NestedSet<Artifact> headerModuleSrcs;
+
+ private final NestedSet<Artifact> transitiveAuxiliaryInputs;
+
+ /**
+ * Headers whose transitive closure of includes needs to be available when compiling the current
+ * target. For every target that the current target depends on transitively and that is built as
+ * header module, contains all headers that are part of its header module.
+ */
+ private final NestedSet<Artifact> transitiveHeaderModuleSrcs;
+
+ /**
+ * The module maps from all targets the current target depends on transitively.
+ */
+ private final NestedSet<Artifact> transitiveModuleMaps;
+
+ DepsContext(Artifact compilationPrerequisiteStampFile,
+ NestedSet<PathFragment> declaredIncludeDirs,
+ NestedSet<PathFragment> declaredIncludeWarnDirs,
+ NestedSet<Artifact> declaredIncludeSrcs,
+ NestedSet<Pair<Artifact, Artifact>> pregreppedHdrs,
+ NestedSet<Artifact> auxiliaryInputs,
+ NestedSet<Artifact> headerModuleSrcs,
+ NestedSet<Artifact> transitiveAuxiliaryInputs,
+ NestedSet<Artifact> transitiveHeaderModuleSrcs,
+ NestedSet<Artifact> transitiveModuleMaps) {
+ this.compilationPrerequisiteStampFile = compilationPrerequisiteStampFile;
+ this.declaredIncludeDirs = declaredIncludeDirs;
+ this.declaredIncludeWarnDirs = declaredIncludeWarnDirs;
+ this.declaredIncludeSrcs = declaredIncludeSrcs;
+ this.pregreppedHdrs = pregreppedHdrs;
+ this.auxiliaryInputs = auxiliaryInputs;
+ this.headerModuleSrcs = headerModuleSrcs;
+ this.transitiveAuxiliaryInputs = transitiveAuxiliaryInputs;
+ this.transitiveHeaderModuleSrcs = transitiveHeaderModuleSrcs;
+ this.transitiveModuleMaps = transitiveModuleMaps;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof DepsContext)) {
+ return false;
+ }
+ DepsContext other = (DepsContext) obj;
+ return Objects.equals(
+ compilationPrerequisiteStampFile, other.compilationPrerequisiteStampFile)
+ && Objects.equals(declaredIncludeDirs, other.declaredIncludeDirs)
+ && Objects.equals(declaredIncludeWarnDirs, other.declaredIncludeWarnDirs)
+ && Objects.equals(declaredIncludeSrcs, other.declaredIncludeSrcs)
+ && Objects.equals(auxiliaryInputs, other.auxiliaryInputs)
+ && Objects.equals(headerModuleSrcs, other.headerModuleSrcs)
+ // Due to the NestedSet equals being ==, and the code flow only setting them if at least
+ // auxiliaryInputs is set, these checks cannot be executed. We leave them in so the equals
+ // is still correct if that connection ever changes.R
+ && Objects.equals(transitiveAuxiliaryInputs, other.transitiveAuxiliaryInputs)
+ && Objects.equals(transitiveHeaderModuleSrcs, other.transitiveHeaderModuleSrcs)
+ && Objects.equals(transitiveModuleMaps, other.transitiveModuleMaps)
+ ;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(compilationPrerequisiteStampFile,
+ declaredIncludeDirs,
+ declaredIncludeWarnDirs,
+ declaredIncludeSrcs,
+ auxiliaryInputs,
+ headerModuleSrcs,
+ transitiveAuxiliaryInputs,
+ transitiveHeaderModuleSrcs,
+ transitiveModuleMaps);
+ }
+ }
+
+ /**
+ * Builder class for {@link CppCompilationContext}.
+ */
+ public static class Builder {
+ private String purpose = "cpp_compilation_prerequisites";
+ 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> auxiliaryInputs =
+ NestedSetBuilder.stableOrder();
+ private final NestedSetBuilder<Artifact> headerModuleSrcs =
+ NestedSetBuilder.stableOrder();
+ private final NestedSetBuilder<Artifact> transitiveAuxiliaryInputs =
+ NestedSetBuilder.stableOrder();
+ private final NestedSetBuilder<Artifact> transitiveHeaderModuleSrcs =
+ NestedSetBuilder.stableOrder();
+ private final NestedSetBuilder<Artifact> transitiveModuleMaps =
+ NestedSetBuilder.stableOrder();
+ private final Set<String> defines = new LinkedHashSet<>();
+ private CppModuleMap cppModuleMap;
+ private Artifact headerModule;
+ private Artifact picHeaderModule;
+
+ /** 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.getCompilationPrerequisites());
+ 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());
+
+ // Forward transitive information.
+ transitiveAuxiliaryInputs.addTransitive(otherContext.getTransitiveAuxiliaryInputs());
+ transitiveModuleMaps.addTransitive(otherContext.getTransitiveModuleMaps());
+ transitiveHeaderModuleSrcs.addTransitive(otherContext.getTransitiveHeaderModuleSrcs());
+
+ // All module maps of direct dependencies are inputs to the current compile independently of
+ // the build type.
+ if (otherContext.getCppModuleMap() != null) {
+ auxiliaryInputs.add(otherContext.getCppModuleMap().getArtifact());
+ }
+ if (otherContext.getHeaderModule() != null || otherContext.getPicHeaderModule() != null) {
+ // If we depend directly on a target that has a compiled header module, all targets
+ // transitively depending on us will need that header module, and all transitive module
+ // maps.
+ if (otherContext.getHeaderModule() != null) {
+ transitiveAuxiliaryInputs.add(otherContext.getHeaderModule());
+ }
+ if (otherContext.getPicHeaderModule() != null) {
+ transitiveAuxiliaryInputs.add(otherContext.getPicHeaderModule());
+ }
+ transitiveAuxiliaryInputs.addAll(otherContext.getTransitiveModuleMaps());
+
+ // All targets transitively depending on us will need to have the full transitive #include
+ // closure of the headers in that module available.
+ transitiveHeaderModuleSrcs.addAll(otherContext.getHeaderModuleSrcs());
+ }
+ // All compile actions in the current target will need the transitive inputs.
+ auxiliaryInputs.addAll(transitiveAuxiliaryInputs.build().toCollection());
+
+ 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.
+ */
+ public Builder addCompilationPrerequisites(Iterable<Artifact> prerequisites) {
+ // LIPO collector must not add compilation prerequisites in order to avoid
+ // the creation of a middleman action.
+ 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 BuildConfiguration#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
+ * BuildConfiguration#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
+ * BuildConfiguration#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
+ * BuildConfiguration#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.
+ */
+ public Builder addDeclaredIncludeSrc(Artifact header) {
+ declaredIncludeSrcs.add(header);
+ compilationPrerequisites.add(header);
+ headerModuleSrcs.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.
+ */
+ public Builder addDeclaredIncludeSrcs(Iterable<Artifact> declaredIncludeSrcs) {
+ this.declaredIncludeSrcs.addAll(declaredIncludeSrcs);
+ this.headerModuleSrcs.addAll(declaredIncludeSrcs);
+ return addCompilationPrerequisites(declaredIncludeSrcs);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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++ header module in non-pic mode.
+ */
+ public Builder setHeaderModule(Artifact headerModule) {
+ this.headerModule = headerModule;
+ return this;
+ }
+
+ /**
+ * Sets the C++ header module in pic mode.
+ */
+ public Builder setPicHeaderModule(Artifact picHeaderModule) {
+ this.picHeaderModule = 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) {
+ if (cppModuleMap != null) {
+ // .cppmap files should also be mandatory inputs for compile actions
+ auxiliaryInputs.add(cppModuleMap.getArtifact());
+ transitiveModuleMaps.add(cppModuleMap.getArtifact());
+ }
+
+ // 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)),
+ ImmutableList.of(new DepsContext(prerequisiteStampFile,
+ declaredIncludeDirs.build(),
+ declaredIncludeWarnDirs.build(),
+ declaredIncludeSrcs.build(),
+ pregreppedHdrs.build(),
+ auxiliaryInputs.build(),
+ headerModuleSrcs.build(),
+ transitiveAuxiliaryInputs.build(),
+ transitiveHeaderModuleSrcs.build(),
+ transitiveModuleMaps.build())),
+ cppModuleMap,
+ headerModule,
+ picHeaderModule);
+ }
+
+ /**
+ * 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.
+ return middlemanFactory.createErrorPropagatingMiddleman(
+ owner, ruleContext.getLabel().toString(), purpose,
+ ImmutableList.copyOf(compilationPrerequisites),
+ ruleContext.getConfiguration().getMiddlemanDirectory());
+ }
+
+ /**
+ * 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());
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
new file mode 100644
index 0000000000..e90f9f74ba
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -0,0 +1,1356 @@
+// Copyright 2014 Google Inc. 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.base.Predicate;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.MiddlemanExpander;
+import com.google.devtools.build.lib.actions.ArtifactResolver;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.extra.CppCompileInfo;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.DependencySet;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+/**
+ * Action that represents some kind of C++ compilation step.
+ */
+@ThreadCompatible
+public class CppCompileAction extends AbstractAction implements IncludeScannable {
+ /**
+ * Represents logic that determines which artifacts, if any, should be added to the actual inputs
+ * for each included file (in addition to the included file itself)
+ */
+ public interface IncludeResolver {
+ /**
+ * Returns the set of files to be added for an included file (as returned in the .d file)
+ */
+ Iterable<Artifact> getInputsForIncludedFile(
+ Artifact includedFile, ArtifactResolver artifactResolver);
+ }
+
+ public static final IncludeResolver VOID_INCLUDE_RESOLVER = new IncludeResolver() {
+ @Override
+ public Iterable<Artifact> getInputsForIncludedFile(Artifact includedFile,
+ ArtifactResolver artifactResolver) {
+ return ImmutableList.of();
+ }
+ };
+
+ private static final int VALIDATION_DEBUG = 0; // 0==none, 1==warns/errors, 2==all
+ private static final boolean VALIDATION_DEBUG_WARN = VALIDATION_DEBUG >= 1;
+
+ /**
+ * A string constant for the c compilation action.
+ */
+ public static final String C_COMPILE = "c-compile";
+
+ /**
+ * A string constant for the c++ compilation action.
+ */
+ public static final String CPP_COMPILE = "c++-compile";
+
+ /**
+ * A string constant for the c++ header parsing.
+ */
+ public static final String CPP_HEADER_PARSING = "c++-header-parsing";
+
+ /**
+ * A string constant for the c++ header preprocessing.
+ */
+ public static final String CPP_HEADER_PREPROCESSING = "c++-header-preprocessing";
+
+ /**
+ * A string constant for the c++ module compilation action.
+ * Note: currently we don't support C module compilation.
+ */
+ public static final String CPP_MODULE_COMPILE = "c++-module-compile";
+
+ /**
+ * A string constant for the preprocessing assembler action.
+ */
+ public static final String PREPROCESS_ASSEMBLE = "preprocess-assemble";
+
+
+ private final BuildConfiguration configuration;
+ protected final Artifact outputFile;
+ private final Label sourceLabel;
+ private final Artifact dwoFile;
+ private final Artifact optionalSourceFile;
+ private final NestedSet<Artifact> mandatoryInputs;
+ private final CppCompilationContext context;
+ private final Collection<PathFragment> extraSystemIncludePrefixes;
+ private final Iterable<IncludeScannable> lipoScannables;
+ private final CppCompileCommandLine cppCompileCommandLine;
+ private final boolean enableLayeringCheck;
+ private final boolean compileHeaderModules;
+
+ @VisibleForTesting
+ final CppConfiguration cppConfiguration;
+ private final Class<? extends CppCompileActionContext> actionContext;
+ private final IncludeResolver includeResolver;
+
+ /**
+ * Identifier for the actual execution time behavior of the action.
+ *
+ * <p>Required because the behavior of this class can be modified by injecting code in the
+ * constructor or by inheritance, and we want to have different cache keys for those.
+ */
+ private final UUID actionClassId;
+
+ private boolean inputsKnown = false;
+
+ /**
+ * Set when the action prepares for execution. Used to preserve state between preparation and
+ * execution.
+ */
+ private Collection<? extends ActionInput> additionalInputs = null;
+
+ /**
+ * Creates a new action to compile C/C++ source files.
+ *
+ * @param owner the owner of the action, usually the configured target that
+ * emitted it
+ * @param sourceFile the source file that should be compiled. {@code mandatoryInputs} must
+ * contain this file
+ * @param sourceLabel the label of the rule the source file is generated by
+ * @param mandatoryInputs any additional files that need to be present for the
+ * compilation to succeed, can be empty but not null, for example, extra sources for FDO.
+ * @param outputFile the object file that is written as result of the
+ * compilation, or the fake object for {@link FakeCppCompileAction}s
+ * @param dotdFile the .d file that is generated as a side-effect of
+ * compilation
+ * @param gcnoFile the coverage notes that are written in coverage mode, can
+ * be null
+ * @param dwoFile the .dwo output file where debug information is stored for Fission
+ * builds (null if Fission mode is disabled)
+ * @param optionalSourceFile an additional optional source file (null if unneeded)
+ * @param configuration the build configurations
+ * @param context the compilation context
+ * @param copts options for the compiler
+ * @param coptsFilter regular expression to remove options from {@code copts}
+ * @param compileHeaderModules whether to compile C++ header modules
+ */
+ protected CppCompileAction(ActionOwner owner,
+ // TODO(bazel-team): Eventually we will remove 'features'; all functionality in 'features'
+ // will be provided by 'featureConfiguration'.
+ ImmutableList<String> features,
+ FeatureConfiguration featureConfiguration,
+ Artifact sourceFile,
+ Label sourceLabel,
+ NestedSet<Artifact> mandatoryInputs,
+ Artifact outputFile,
+ DotdFile dotdFile,
+ @Nullable Artifact gcnoFile,
+ @Nullable Artifact dwoFile,
+ Artifact optionalSourceFile,
+ BuildConfiguration configuration,
+ CppConfiguration cppConfiguration,
+ CppCompilationContext context,
+ Class<? extends CppCompileActionContext> actionContext,
+ ImmutableList<String> copts,
+ ImmutableList<String> pluginOpts,
+ Predicate<String> coptsFilter,
+ ImmutableList<PathFragment> extraSystemIncludePrefixes,
+ boolean enableLayeringCheck,
+ @Nullable String fdoBuildStamp,
+ IncludeResolver includeResolver,
+ Iterable<IncludeScannable> lipoScannables,
+ UUID actionClassId,
+ boolean compileHeaderModules) {
+ // getInputs() method is overridden in this class so we pass a dummy empty
+ // list to the AbstractAction constructor in place of a real input collection.
+ super(owner,
+ Artifact.NO_ARTIFACTS,
+ CollectionUtils.asListWithoutNulls(outputFile, dotdFile.artifact(),
+ gcnoFile, dwoFile));
+ this.configuration = configuration;
+ this.sourceLabel = sourceLabel;
+ this.outputFile = Preconditions.checkNotNull(outputFile);
+ this.dwoFile = dwoFile;
+ this.optionalSourceFile = optionalSourceFile;
+ this.context = context;
+ this.extraSystemIncludePrefixes = extraSystemIncludePrefixes;
+ this.enableLayeringCheck = enableLayeringCheck;
+ this.includeResolver = includeResolver;
+ this.cppConfiguration = cppConfiguration;
+ if (cppConfiguration != null && !cppConfiguration.shouldScanIncludes()) {
+ inputsKnown = true;
+ }
+ this.cppCompileCommandLine = new CppCompileCommandLine(sourceFile, dotdFile,
+ context.getCppModuleMap(), copts, coptsFilter, pluginOpts,
+ (gcnoFile != null), features, featureConfiguration, fdoBuildStamp);
+ this.actionContext = actionContext;
+ this.lipoScannables = lipoScannables;
+ this.actionClassId = actionClassId;
+ this.compileHeaderModules = compileHeaderModules;
+
+ // We do not need to include the middleman artifact since it is a generated
+ // artifact and will definitely exist prior to this action execution.
+ this.mandatoryInputs = mandatoryInputs;
+ setInputs(createInputs(mandatoryInputs, context.getCompilationPrerequisites(),
+ optionalSourceFile));
+ }
+
+ private static NestedSet<Artifact> createInputs(
+ NestedSet<Artifact> mandatoryInputs,
+ Set<Artifact> prerequisites, Artifact optionalSourceFile) {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ if (optionalSourceFile != null) {
+ builder.add(optionalSourceFile);
+ }
+ builder.addAll(prerequisites);
+ builder.addTransitive(mandatoryInputs);
+ return builder.build();
+ }
+
+ public boolean shouldScanIncludes() {
+ return cppConfiguration.shouldScanIncludes();
+ }
+
+ @Override
+ public List<PathFragment> getBuiltInIncludeDirectories() {
+ return cppConfiguration.getBuiltInIncludeDirectories();
+ }
+
+ public String getHostSystemName() {
+ return cppConfiguration.getHostSystemName();
+ }
+
+ @Override
+ public NestedSet<Artifact> getMandatoryInputs() {
+ return mandatoryInputs;
+ }
+
+ @Override
+ public boolean inputsKnown() {
+ return inputsKnown;
+ }
+
+ /**
+ * Returns the list of additional inputs found by dependency discovery, during action preparation,
+ * and clears the stored list. {@link #prepare} must be called before this method is called, on
+ * each action execution.
+ */
+ public Collection<? extends ActionInput> getAdditionalInputs() {
+ Collection<? extends ActionInput> result = Preconditions.checkNotNull(additionalInputs);
+ additionalInputs = null;
+ return result;
+ }
+
+ @Override
+ public boolean discoversInputs() {
+ return true;
+ }
+
+ @Override
+ public void discoverInputs(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+ try {
+ this.additionalInputs = executor.getContext(CppCompileActionContext.class)
+ .findAdditionalInputs(this, actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException("Include scanning of rule '" + getOwner().getLabel() + "'",
+ executor.getVerboseFailures(), this);
+ }
+ }
+
+ @Override
+ public Artifact getPrimaryInput() {
+ return getSourceFile();
+ }
+
+ @Override
+ public Artifact getPrimaryOutput() {
+ return getOutputFile();
+ }
+
+ /**
+ * Returns the path of the c/cc source for gcc.
+ */
+ public final Artifact getSourceFile() {
+ return cppCompileCommandLine.sourceFile;
+ }
+
+ /**
+ * Returns the path where gcc should put its result.
+ */
+ public Artifact getOutputFile() {
+ return outputFile;
+ }
+
+ /**
+ * Returns the path of the debug info output file (when debug info is
+ * spliced out of the .o file via fission).
+ */
+ @Nullable
+ Artifact getDwoFile() {
+ return dwoFile;
+ }
+
+ protected PathFragment getInternalOutputFile() {
+ return outputFile.getExecPath();
+ }
+
+ @VisibleForTesting
+ public List<String> getPluginOpts() {
+ return cppCompileCommandLine.pluginOpts;
+ }
+
+ Collection<PathFragment> getExtraSystemIncludePrefixes() {
+ return extraSystemIncludePrefixes;
+ }
+
+ @Override
+ public Map<Artifact, Path> getLegalGeneratedScannerFileMap() {
+ Map<Artifact, Path> legalOuts = new HashMap<>();
+
+ for (Artifact a : context.getDeclaredIncludeSrcs()) {
+ if (!a.isSourceArtifact()) {
+ legalOuts.put(a, null);
+ }
+ }
+ for (Pair<Artifact, Artifact> pregreppedSrcs : context.getPregreppedHeaders()) {
+ Artifact hdr = pregreppedSrcs.getFirst();
+ Preconditions.checkState(!hdr.isSourceArtifact(), hdr);
+ legalOuts.put(hdr, pregreppedSrcs.getSecond().getPath());
+ }
+ return Collections.unmodifiableMap(legalOuts);
+ }
+
+ /**
+ * Returns the path where gcc should put the discovered dependency
+ * information.
+ */
+ public DotdFile getDotdFile() {
+ return cppCompileCommandLine.dotdFile;
+ }
+
+ protected boolean needsIncludeScanning(Executor executor) {
+ return executor.getContext(actionContext).needsIncludeScanning();
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return executor.getContext(actionContext).strategyLocality();
+ }
+
+ @VisibleForTesting
+ public CppCompilationContext getContext() {
+ return context;
+ }
+
+ @Override
+ public List<PathFragment> getQuoteIncludeDirs() {
+ return context.getQuoteIncludeDirs();
+ }
+
+ @Override
+ public List<PathFragment> getIncludeDirs() {
+ ImmutableList.Builder<PathFragment> result = ImmutableList.builder();
+ result.addAll(context.getIncludeDirs());
+ for (String opt : cppCompileCommandLine.copts) {
+ if (opt.startsWith("-I") && opt.length() > 2) {
+ // We insist on the combined form "-Idir".
+ result.add(new PathFragment(opt.substring(2)));
+ }
+ }
+ return result.build();
+ }
+
+ @Override
+ public List<PathFragment> getSystemIncludeDirs() {
+ ImmutableList.Builder<PathFragment> result = ImmutableList.builder();
+ result.addAll(context.getSystemIncludeDirs());
+ for (String opt : cppCompileCommandLine.copts) {
+ if (opt.startsWith("-isystem") && opt.length() > 8) {
+ // We insist on the combined form "-isystemdir".
+ result.add(new PathFragment(opt.substring(8)));
+ }
+ }
+ return result.build();
+ }
+
+ @Override
+ public List<String> getCmdlineIncludes() {
+ ImmutableList.Builder<String> cmdlineIncludes = ImmutableList.builder();
+ List<String> args = getArgv();
+ for (Iterator<String> argi = args.iterator(); argi.hasNext();) {
+ String arg = argi.next();
+ if (arg.equals("-include") && argi.hasNext()) {
+ cmdlineIncludes.add(argi.next());
+ }
+ }
+ return cmdlineIncludes.build();
+ }
+
+ @Override
+ public Collection<Artifact> getIncludeScannerSources() {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ // For every header module we use for the build we need the set of sources that it can
+ // reference.
+ builder.addAll(context.getTransitiveHeaderModuleSrcs());
+ if (CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath())) {
+ // If this is an action that compiles the header module itself, the source we build is the
+ // module map, and we need to include-scan all headers that are referenced in the module map.
+ // We need to do include scanning as long as we want to support building code bases that are
+ // not fully strict layering clean.
+ builder.addAll(context.getHeaderModuleSrcs());
+ } else {
+ builder.add(getSourceFile());
+ }
+ return builder.build().toCollection();
+ }
+
+ @Override
+ public Iterable<IncludeScannable> getAuxiliaryScannables() {
+ return lipoScannables;
+ }
+
+ /**
+ * Returns the list of "-D" arguments that should be used by this gcc
+ * invocation. Only used for testing.
+ */
+ @VisibleForTesting
+ public ImmutableCollection<String> getDefines() {
+ return context.getDefines();
+ }
+
+ /**
+ * Returns an (immutable) map of environment key, value pairs to be
+ * provided to the C++ compiler.
+ */
+ public ImmutableMap<String, String> getEnvironment() {
+ Map<String, String> environment =
+ new LinkedHashMap<>(configuration.getDefaultShellEnvironment());
+ if (configuration.isCodeCoverageEnabled()) {
+ environment.put("PWD", "/proc/self/cwd");
+ }
+ if (OS.getCurrent() == OS.WINDOWS) {
+ // TODO(bazel-team): Both GCC and clang rely on their execution directories being on
+ // PATH, otherwise they fail to find dependent DLLs (and they fail silently...). On
+ // the other hand, Windows documentation says that the directory of the executable
+ // is always searched for DLLs first. Not sure what to make of it.
+ // Other options are to forward the system path (brittle), or to add a PATH field to
+ // the crosstool file.
+ environment.put("PATH", cppConfiguration.getToolPathFragment(Tool.GCC).getParentDirectory()
+ .getPathString());
+ }
+ return ImmutableMap.copyOf(environment);
+ }
+
+ /**
+ * Returns a new, mutable list of command and arguments (argv) to be passed
+ * to the gcc subprocess.
+ */
+ public final List<String> getArgv() {
+ return getArgv(getInternalOutputFile());
+ }
+
+ protected final List<String> getArgv(PathFragment outputFile) {
+ return cppCompileCommandLine.getArgv(outputFile);
+ }
+
+ @Override
+ public ExtraActionInfo.Builder getExtraActionInfo() {
+ CppCompileInfo.Builder info = CppCompileInfo.newBuilder();
+ info.setTool(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString());
+ for (String option : getCompilerOptions()) {
+ info.addCompilerOption(option);
+ }
+ info.setOutputFile(outputFile.getExecPathString());
+ info.setSourceFile(getSourceFile().getExecPathString());
+ if (inputsKnown()) {
+ info.addAllSourcesAndHeaders(Artifact.toExecPaths(getInputs()));
+ } else {
+ info.addSourcesAndHeaders(getSourceFile().getExecPathString());
+ info.addAllSourcesAndHeaders(
+ Artifact.toExecPaths(context.getDeclaredIncludeSrcs()));
+ }
+
+ return super.getExtraActionInfo()
+ .setExtension(CppCompileInfo.cppCompileInfo, info.build());
+ }
+
+ /**
+ * Returns the compiler options.
+ */
+ @VisibleForTesting
+ public List<String> getCompilerOptions() {
+ return cppCompileCommandLine.getCompilerOptions();
+ }
+
+ /**
+ * Enforce that the includes actually visited during the compile were properly
+ * declared in the rules.
+ *
+ * <p>The technique is to walk through all of the reported includes that gcc
+ * emits into the .d file, and verify that they came from acceptable
+ * relative include directories. This is done in two steps:
+ *
+ * <p>First, each included file is stripped of any include path prefix from
+ * {@code quoteIncludeDirs} to produce an effective relative include dir+name.
+ *
+ * <p>Second, the remaining directory is looked up in {@code declaredIncludeDirs},
+ * a list of acceptable dirs. This list contains a set of dir fragments that
+ * have been calculated by the configured target to be allowable for inclusion
+ * by this source. If no match is found, an error is reported and an exception
+ * is thrown.
+ *
+ * @throws ActionExecutionException iff there was an undeclared dependency
+ */
+ @VisibleForTesting
+ public void validateInclusions(
+ MiddlemanExpander middlemanExpander, EventHandler eventHandler)
+ throws ActionExecutionException {
+ if (!cppConfiguration.shouldScanIncludes() || !inputsKnown()) {
+ return;
+ }
+
+ IncludeProblems errors = new IncludeProblems();
+ IncludeProblems warnings = new IncludeProblems();
+ Set<Artifact> allowedIncludes = new HashSet<>();
+ for (Artifact input : mandatoryInputs) {
+ if (input.isMiddlemanArtifact()) {
+ middlemanExpander.expand(input, allowedIncludes);
+ }
+ allowedIncludes.add(input);
+ }
+
+ if (optionalSourceFile != null) {
+ allowedIncludes.add(optionalSourceFile);
+ }
+ List<PathFragment> cxxSystemIncludeDirs =
+ cppConfiguration.getBuiltInIncludeDirectories();
+ Iterable<PathFragment> ignoreDirs = Iterables.concat(cxxSystemIncludeDirs,
+ extraSystemIncludePrefixes, context.getSystemIncludeDirs());
+
+ // Copy the sets to hash sets for fast contains checking.
+ // Avoid immutable sets here to limit memory churn.
+ Set<PathFragment> declaredIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeDirs());
+ Set<PathFragment> warnIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeWarnDirs());
+ Set<Artifact> declaredIncludeSrcs = Sets.newHashSet(context.getDeclaredIncludeSrcs());
+ for (Artifact input : getInputs()) {
+ if (context.getCompilationPrerequisites().contains(input)
+ || allowedIncludes.contains(input)) {
+ continue; // ignore our fixed source in mandatoryInput: we just want includes
+ }
+ // Ignore headers from built-in include directories.
+ if (FileSystemUtils.startsWithAny(input.getExecPath(), ignoreDirs)) {
+ continue;
+ }
+ if (!isDeclaredIn(input, declaredIncludeDirs, declaredIncludeSrcs)) {
+ // This call can never match the declared include sources (they would be matched above).
+ // There are no declared include sources we need to warn about, so use an empty set here.
+ if (isDeclaredIn(input, warnIncludeDirs, ImmutableSet.<Artifact>of())) {
+ warnings.add(input.getPath().toString());
+ } else {
+ errors.add(input.getPath().toString());
+ }
+ }
+ }
+ if (VALIDATION_DEBUG_WARN) {
+ synchronized (System.err) {
+ if (VALIDATION_DEBUG >= 2 || errors.hasProblems() || warnings.hasProblems()) {
+ if (errors.hasProblems()) {
+ System.err.println("ERROR: Include(s) were not in declared srcs:");
+ } else if (warnings.hasProblems()) {
+ System.err.println("WARN: Include(s) were not in declared srcs:");
+ } else {
+ System.err.println("INFO: Include(s) were OK for '" + getSourceFile()
+ + "', declared srcs:");
+ }
+ for (Artifact a : context.getDeclaredIncludeSrcs()) {
+ System.err.println(" '" + a.toDetailString() + "'");
+ }
+ System.err.println(" or under declared dirs:");
+ for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeDirs())) {
+ System.err.println(" '" + f + "'");
+ }
+ System.err.println(" or under declared warn dirs:");
+ for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeWarnDirs())) {
+ System.err.println(" '" + f + "'");
+ }
+ System.err.println(" with prefixes:");
+ for (PathFragment dirpath : context.getQuoteIncludeDirs()) {
+ System.err.println(" '" + dirpath + "'");
+ }
+ }
+ }
+ }
+
+ if (warnings.hasProblems()) {
+ eventHandler.handle(
+ new Event(EventKind.WARNING,
+ getOwner().getLocation(), warnings.getMessage(this, getSourceFile()),
+ Label.print(getOwner().getLabel())));
+ }
+ errors.assertProblemFree(this, getSourceFile());
+ }
+
+ /**
+ * Returns true if an included artifact is declared in a set of allowed
+ * include directories. The simple case is that the artifact's parent
+ * directory is contained in the set, or is empty.
+ *
+ * <p>This check also supports a wildcard suffix of '**' for the cases where the
+ * calculations are inexact.
+ *
+ * <p>It also handles unseen non-nested-package subdirs by walking up the path looking
+ * for matches.
+ */
+ private static boolean isDeclaredIn(Artifact input, Set<PathFragment> declaredIncludeDirs,
+ Set<Artifact> declaredIncludeSrcs) {
+ // First check if it's listed in "srcs". If so, then its declared & OK.
+ if (declaredIncludeSrcs.contains(input)) {
+ return true;
+ }
+ // If it's a derived artifact, then it MUST be listed in "srcs" as checked above.
+ // We define derived here as being not source and not under the include link tree.
+ if (!input.isSourceArtifact()
+ && !input.getRoot().getExecPath().getBaseName().equals("include")) {
+ return false;
+ }
+ // Need to do dir/package matching: first try a quick exact lookup.
+ PathFragment includeDir = input.getRootRelativePath().getParentDirectory();
+ if (includeDir.segmentCount() == 0 || declaredIncludeDirs.contains(includeDir)) {
+ return true; // OK: quick exact match.
+ }
+ // Not found in the quick lookup: try the wildcards.
+ for (PathFragment declared : declaredIncludeDirs) {
+ if (declared.getBaseName().equals("**")) {
+ if (includeDir.startsWith(declared.getParentDirectory())) {
+ return true; // OK: under a wildcard dir.
+ }
+ }
+ }
+ // Still not found: see if it is in a subdir of a declared package.
+ Path root = input.getRoot().getPath();
+ for (Path dir = input.getPath().getParentDirectory();;) {
+ if (dir.getRelative("BUILD").exists()) {
+ return false; // Bad: this is a sub-package, not a subdir of a declared package.
+ }
+ dir = dir.getParentDirectory();
+ if (dir.equals(root)) {
+ return false; // Bad: at the top, give up.
+ }
+ if (declaredIncludeDirs.contains(dir.relativeTo(root))) {
+ return true; // OK: found under a declared dir.
+ }
+ }
+ }
+
+ /**
+ * Recalculates this action's live input collection, including sources, middlemen.
+ *
+ * @throws ActionExecutionException iff any errors happen during update.
+ */
+ @VisibleForTesting
+ @ThreadCompatible
+ public final void updateActionInputs(Path execRoot,
+ ArtifactResolver artifactResolver, CppCompileActionContext.Reply reply)
+ throws ActionExecutionException {
+ if (!cppConfiguration.shouldScanIncludes()) {
+ return;
+ }
+ inputsKnown = false;
+ NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder();
+ Profiler.instance().startTask(ProfilerTask.ACTION_UPDATE, this);
+ try {
+ inputs.addTransitive(mandatoryInputs);
+ if (optionalSourceFile != null) {
+ inputs.add(optionalSourceFile);
+ }
+ inputs.addAll(context.getCompilationPrerequisites());
+ populateActionInputs(execRoot, artifactResolver, reply, inputs);
+ inputsKnown = true;
+ } finally {
+ Profiler.instance().completeTask(ProfilerTask.ACTION_UPDATE);
+ synchronized (this) {
+ setInputs(inputs.build());
+ }
+ }
+ }
+
+ private DependencySet processDepset(Path execRoot, CppCompileActionContext.Reply reply)
+ throws IOException {
+ DependencySet depSet = new DependencySet(execRoot);
+
+ // artifact() is null if we are not using in-memory .d files. We also want to prepare for the
+ // case where we expected an in-memory .d file, but we did not get an appropriate response.
+ // Perhaps we produced the file locally.
+ if (getDotdFile().artifact() != null || reply == null) {
+ return depSet.read(getDotdFile().getPath());
+ } else {
+ // This is an in-memory .d file.
+ return depSet.process(reply.getContents());
+ }
+ }
+
+ /**
+ * Populates the given ordered collection with additional input artifacts
+ * relevant to the specific action implementation.
+ *
+ * <p>The default implementation updates this Action's input set by reading
+ * dynamically-discovered dependency information out of the .d file.
+ *
+ * <p>Artifacts are considered inputs but not "mandatory" inputs.
+ *
+ *
+ * @param reply the reply from the compilation.
+ * @param inputs the ordered collection of inputs to append to
+ * @throws ActionExecutionException iff the .d is missing, malformed or has
+ * unresolvable included artifacts.
+ */
+ @ThreadCompatible
+ private void populateActionInputs(Path execRoot,
+ ArtifactResolver artifactResolver, CppCompileActionContext.Reply reply,
+ NestedSetBuilder<Artifact> inputs)
+ throws ActionExecutionException {
+ try {
+ // Read .d file.
+ DependencySet depSet = processDepset(execRoot, reply);
+
+ // Determine prefixes of allowed absolute inclusions.
+ CppConfiguration toolchain = cppConfiguration;
+ List<PathFragment> systemIncludePrefixes = new ArrayList<>();
+ for (PathFragment includePath : toolchain.getBuiltInIncludeDirectories()) {
+ if (includePath.isAbsolute()) {
+ systemIncludePrefixes.add(includePath);
+ }
+ }
+ systemIncludePrefixes.addAll(extraSystemIncludePrefixes);
+
+ // Check inclusions.
+ IncludeProblems problems = new IncludeProblems();
+ Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap();
+ for (PathFragment execPath : depSet.getDependencies()) {
+ if (execPath.isAbsolute()) {
+ // Absolute includes from system paths are ignored.
+ if (FileSystemUtils.startsWithAny(execPath, systemIncludePrefixes)) {
+ continue;
+ }
+ // Since gcc is given only relative paths on the command line,
+ // non-system include paths here should never be absolute. If they
+ // are, it's probably due to a non-hermetic #include, & we should stop
+ // the build with an error.
+ if (execPath.startsWith(execRoot.asFragment())) {
+ execPath = execPath.relativeTo(execRoot.asFragment()); // funky but tolerable path
+ } else {
+ problems.add(execPath.getPathString());
+ continue;
+ }
+ }
+ Artifact artifact = allowedDerivedInputsMap.get(execPath);
+ if (artifact == null) {
+ artifact = artifactResolver.resolveSourceArtifact(execPath);
+ }
+ if (artifact != null) {
+ inputs.add(artifact);
+ // In some cases, execution backends need extra files for each included file. Add them
+ // to the set of actual inputs.
+ inputs.addAll(includeResolver.getInputsForIncludedFile(artifact, artifactResolver));
+ } else {
+ // Abort if we see files that we can't resolve, likely caused by
+ // undeclared includes or illegal include constructs.
+ problems.add(execPath.getPathString());
+ }
+ }
+ problems.assertProblemFree(this, getSourceFile());
+ } catch (IOException e) {
+ // Some kind of IO or parse exception--wrap & rethrow it to stop the build.
+ throw new ActionExecutionException("error while parsing .d file", e, this, false);
+ }
+ }
+
+ @Override
+ public void updateInputsFromCache(
+ ArtifactResolver artifactResolver, Collection<PathFragment> inputPaths) {
+ // Note that this method may trigger a violation of the desirable invariant that getInputs()
+ // is a superset of getMandatoryInputs(). See bug about an "action not in canonical form"
+ // error message and the integration test test_crosstool_change_and_failure().
+
+ Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap();
+ List<Artifact> inputs = new ArrayList<>();
+ for (PathFragment execPath : inputPaths) {
+ // The artifact may be a derived artifact, and if it has been created already, then we still
+ // want to keep it to preserve incrementality.
+ Artifact artifact = allowedDerivedInputsMap.get(execPath);
+ if (artifact == null) {
+ artifact = artifactResolver.resolveSourceArtifact(execPath);
+ }
+ // If PathFragment cannot be resolved into the artifact - ignore it. This could happen if
+ // rule definition has changed and action no longer depends on, e.g., additional source file
+ // in the separate package and that package is no longer referenced anywhere else.
+ // It is safe to ignore such paths because dependency checker would identify change in inputs
+ // (ignored path was used before) and will force action execution.
+ if (artifact != null) {
+ inputs.add(artifact);
+ }
+ }
+ inputsKnown = true;
+ synchronized (this) {
+ setInputs(inputs);
+ }
+ }
+
+ private Map<PathFragment, Artifact> getAllowedDerivedInputsMap() {
+ Map<PathFragment, Artifact> allowedDerivedInputMap = new HashMap<>();
+ addToMap(allowedDerivedInputMap, mandatoryInputs);
+ addToMap(allowedDerivedInputMap, context.getDeclaredIncludeSrcs());
+ addToMap(allowedDerivedInputMap, context.getCompilationPrerequisites());
+ Artifact artifact = getSourceFile();
+ if (!artifact.isSourceArtifact()) {
+ allowedDerivedInputMap.put(artifact.getExecPath(), artifact);
+ }
+ return allowedDerivedInputMap;
+ }
+
+ private void addToMap(Map<PathFragment, Artifact> map, Iterable<Artifact> artifacts) {
+ for (Artifact artifact : artifacts) {
+ if (!artifact.isSourceArtifact()) {
+ map.put(artifact.getExecPath(), artifact);
+ }
+ }
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Compiling " + getSourceFile().prettyPrint();
+ }
+
+ /**
+ * Return the directories in which to look for headers (pertains to headers
+ * not specifically listed in {@code declaredIncludeSrcs}). The return value
+ * may contain duplicate elements.
+ */
+ public NestedSet<PathFragment> getDeclaredIncludeDirs() {
+ return context.getDeclaredIncludeDirs();
+ }
+
+ /**
+ * Return the directories in which to look for headers and issue a warning.
+ * (pertains to headers not specifically listed in {@code
+ * declaredIncludeSrcs}). The return value may contain duplicate elements.
+ */
+ public NestedSet<PathFragment> getDeclaredIncludeWarnDirs() {
+ return context.getDeclaredIncludeWarnDirs();
+ }
+
+ /**
+ * Return explicit header files (i.e., header files explicitly listed). The
+ * return value may contain duplicate elements.
+ */
+ public NestedSet<Artifact> getDeclaredIncludeSrcs() {
+ return context.getDeclaredIncludeSrcs();
+ }
+
+ /**
+ * Return explicit header files (i.e., header files explicitly listed) in an order
+ * that is stable between builds.
+ */
+ protected final List<PathFragment> getDeclaredIncludeSrcsInStableOrder() {
+ List<PathFragment> paths = new ArrayList<>();
+ for (Artifact declaredIncludeSrc : context.getDeclaredIncludeSrcs()) {
+ paths.add(declaredIncludeSrc.getExecPath());
+ }
+ Collections.sort(paths); // Order is not important, but stability is.
+ return paths;
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return executor.getContext(actionContext).estimateResourceConsumption(this);
+ }
+
+ @VisibleForTesting
+ public Class<? extends CppCompileActionContext> getActionContext() {
+ return actionContext;
+ }
+
+ /**
+ * Estimate resource consumption when this action is executed locally.
+ */
+ public ResourceSet estimateResourceConsumptionLocal() {
+ // We use a local compile, so much of the time is spent waiting for IO,
+ // but there is still significant CPU; hence we estimate 50% cpu usage.
+ return new ResourceSet(/*memoryMb=*/200, /*cpuUsage=*/0.5, /*ioUsage=*/0.0);
+ }
+
+ @Override
+ public String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addUUID(actionClassId);
+ f.addStrings(getArgv());
+
+ /*
+ * getArgv() above captures all changes which affect the compilation
+ * command and hence the contents of the object file. But we need to
+ * also make sure that we reexecute the action if any of the fields
+ * that affect whether validateIncludes() will report an error or warning
+ * have changed, otherwise we might miss some errors.
+ */
+ f.addPaths(context.getDeclaredIncludeDirs());
+ f.addPaths(context.getDeclaredIncludeWarnDirs());
+ f.addPaths(getDeclaredIncludeSrcsInStableOrder());
+ f.addPaths(getExtraSystemIncludePrefixes());
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ @ThreadCompatible
+ public void execute(
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+ CppCompileActionContext.Reply reply;
+ try {
+ reply = executor.getContext(actionContext).execWithReply(this, actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException("C++ compilation of rule '" + getOwner().getLabel() + "'",
+ executor.getVerboseFailures(), this);
+ }
+ ensureCoverageNotesFilesExist();
+ IncludeScanningContext scanningContext = executor.getContext(IncludeScanningContext.class);
+ updateActionInputs(executor.getExecRoot(), scanningContext.getArtifactResolver(), reply);
+ reply = null; // Clear in-memory .d files early.
+ validateInclusions(actionExecutionContext.getMiddlemanExpander(), executor.getEventHandler());
+ }
+
+ /**
+ * Gcc only creates ".gcno" files if the compilation unit is non-empty.
+ * To ensure that the set of outputs for a CppCompileAction remains consistent
+ * and doesn't vary dynamically depending on the _contents_ of the input files,
+ * we create empty ".gcno" files if gcc didn't create them.
+ */
+ private void ensureCoverageNotesFilesExist() throws ActionExecutionException {
+ for (Artifact output : getOutputs()) {
+ if (CppFileTypes.COVERAGE_NOTES.matches(output.getFilename()) // ".gcno"
+ && !output.getPath().exists()) {
+ try {
+ FileSystemUtils.createEmptyFile(output.getPath());
+ } catch (IOException e) {
+ throw new ActionExecutionException(
+ "Error creating file '" + output.getPath() + "': " + e.getMessage(), e, this, false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Provides list of include files needed for performing extra actions on this action when run
+ * remotely. The list of include files is created by performing a header scan on the known input
+ * files.
+ */
+ @Override
+ public Iterable<Artifact> getInputFilesForExtraAction(
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Collection<Artifact> scannedIncludes =
+ actionExecutionContext.getExecutor().getContext(actionContext)
+ .getScannedIncludeFiles(this, actionExecutionContext);
+ // Use a set to eliminate duplicates.
+ ImmutableSet.Builder<Artifact> result = ImmutableSet.builder();
+ return result.addAll(getInputs()).addAll(scannedIncludes).build();
+ }
+
+ @Override
+ public String getMnemonic() { return "CppCompile"; }
+
+ @Override
+ public String describeKey() {
+ StringBuilder message = new StringBuilder();
+ message.append(getProgressMessage());
+ message.append('\n');
+ message.append(" Command: ");
+ message.append(
+ ShellEscaper.escapeString(cppConfiguration.getLdExecutable().getPathString()));
+ message.append('\n');
+ // Outputting one argument per line makes it easier to diff the results.
+ for (String argument : ShellEscaper.escapeAll(getArgv())) {
+ message.append(" Argument: ");
+ message.append(argument);
+ message.append('\n');
+ }
+
+ for (PathFragment path : context.getDeclaredIncludeDirs()) {
+ message.append(" Declared include directory: ");
+ message.append(ShellEscaper.escapeString(path.getPathString()));
+ message.append('\n');
+ }
+
+ for (PathFragment path : getDeclaredIncludeSrcsInStableOrder()) {
+ message.append(" Declared include source: ");
+ message.append(ShellEscaper.escapeString(path.getPathString()));
+ message.append('\n');
+ }
+
+ for (PathFragment path : getExtraSystemIncludePrefixes()) {
+ message.append(" Extra system include prefix: ");
+ message.append(ShellEscaper.escapeString(path.getPathString()));
+ message.append('\n');
+ }
+ return message.toString();
+ }
+
+ /**
+ * The compile command line for the enclosing C++ compile action.
+ */
+ public final class CppCompileCommandLine {
+ private final Artifact sourceFile;
+ private final DotdFile dotdFile;
+ private final CppModuleMap cppModuleMap;
+ private final List<String> copts;
+ private final Predicate<String> coptsFilter;
+ private final List<String> pluginOpts;
+ private final boolean isInstrumented;
+ private final Collection<String> features;
+ private final FeatureConfiguration featureConfiguration;
+
+ // The value of the BUILD_FDO_TYPE macro to be defined on command line
+ @Nullable private final String fdoBuildStamp;
+
+ public CppCompileCommandLine(Artifact sourceFile, DotdFile dotdFile, CppModuleMap cppModuleMap,
+ ImmutableList<String> copts, Predicate<String> coptsFilter,
+ ImmutableList<String> pluginOpts, boolean isInstrumented,
+ Collection<String> features, FeatureConfiguration featureConfiguration,
+ @Nullable String fdoBuildStamp) {
+ this.sourceFile = Preconditions.checkNotNull(sourceFile);
+ this.dotdFile = Preconditions.checkNotNull(dotdFile);
+ this.cppModuleMap = cppModuleMap;
+ this.copts = Preconditions.checkNotNull(copts);
+ this.coptsFilter = coptsFilter;
+ this.pluginOpts = Preconditions.checkNotNull(pluginOpts);
+ this.isInstrumented = isInstrumented;
+ this.features = Preconditions.checkNotNull(features);
+ this.featureConfiguration = featureConfiguration;
+ this.fdoBuildStamp = fdoBuildStamp;
+ }
+
+ protected List<String> getArgv(PathFragment outputFile) {
+ List<String> commandLine = new ArrayList<>();
+
+ // first: The command name.
+ commandLine.add(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString());
+
+ // second: The compiler options.
+ commandLine.addAll(getCompilerOptions());
+
+ // third: The file to compile!
+ commandLine.add("-c");
+ commandLine.add(sourceFile.getExecPathString());
+
+ // finally: The output file. (Prefixed with -o).
+ commandLine.add("-o");
+ commandLine.add(outputFile.getPathString());
+
+ return commandLine;
+ }
+
+ private String getActionName() {
+ PathFragment sourcePath = sourceFile.getExecPath();
+ if (CppFileTypes.CPP_MODULE_MAP.matches(sourcePath)) {
+ return CPP_MODULE_COMPILE;
+ } else if (CppFileTypes.CPP_HEADER.matches(sourcePath)) {
+ // TODO(bazel-team): Handle C headers that probably don't work in C++ mode.
+ // TODO(bazel-team): Replace use of features.contains with featureConfiguration.isEnabled
+ // here (the other instances will need to stay with the current feature selection process
+ // until all crosstool configurations have been converted).
+ if (features.contains(CppRuleClasses.PARSE_HEADERS)) {
+ return CPP_HEADER_PARSING;
+ } else if (features.contains(CppRuleClasses.PREPROCESS_HEADERS)) {
+ return CPP_HEADER_PREPROCESSING;
+ } else {
+ // CcCommon.collectCAndCppSources() ensures we do not add headers to
+ // the compilation artifacts unless either 'parse_headers' or
+ // 'preprocess_headers' is set.
+ throw new IllegalStateException();
+ }
+ } else if (CppFileTypes.C_SOURCE.matches(sourcePath)) {
+ return C_COMPILE;
+ } else if (CppFileTypes.CPP_SOURCE.matches(sourcePath)) {
+ return CPP_COMPILE;
+ } else if (CppFileTypes.ASSEMBLER_WITH_C_PREPROCESSOR.matches(sourcePath)) {
+ return PREPROCESS_ASSEMBLE;
+ }
+ // CcLibraryHelper ensures CppCompileAction only gets instantiated for supported file types.
+ throw new IllegalStateException();
+ }
+
+ public List<String> getCompilerOptions() {
+ List<String> options = new ArrayList<>();
+
+ // TODO(bazel-team): Extract combinations of options into sections in the CROSSTOOL file.
+ if (CppFileTypes.CPP_MODULE_MAP.matches(sourceFile.getExecPath())) {
+ options.add("-x");
+ options.add("c++");
+ } else if (CppFileTypes.CPP_HEADER.matches(sourceFile.getExecPath())) {
+ // TODO(bazel-team): Read the compiler flag settings out of the CROSSTOOL file.
+ // TODO(bazel-team): Handle C headers that probably don't work in C++ mode.
+ if (features.contains(CppRuleClasses.PARSE_HEADERS)) {
+ options.add("-x");
+ options.add("c++-header");
+ // Specifying -x c++-header will make clang/gcc create precompiled
+ // headers, which we suppress with -fsyntax-only.
+ options.add("-fsyntax-only");
+ } else if (features.contains(CppRuleClasses.PREPROCESS_HEADERS)) {
+ options.add("-E");
+ options.add("-x");
+ options.add("c++");
+ } else {
+ // CcCommon.collectCAndCppSources() ensures we do not add headers to
+ // the compilation artifacts unless either 'parse_headers' or
+ // 'preprocess_headers' is set.
+ throw new IllegalStateException();
+ }
+ }
+
+ for (PathFragment quoteIncludePath : context.getQuoteIncludeDirs()) {
+ // "-iquote" is a gcc-specific option. For C compilers that don't support "-iquote",
+ // we should instead use "-I".
+ options.add("-iquote");
+ options.add(quoteIncludePath.getSafePathString());
+ }
+ for (PathFragment includePath : context.getIncludeDirs()) {
+ options.add("-I" + includePath.getSafePathString());
+ }
+ for (PathFragment systemIncludePath : context.getSystemIncludeDirs()) {
+ options.add("-isystem");
+ options.add(systemIncludePath.getSafePathString());
+ }
+
+ CppConfiguration toolchain = cppConfiguration;
+
+ // pluginOpts has to be added before defaultCopts because -fplugin must precede -plugin-arg.
+ options.addAll(pluginOpts);
+ addFilteredOptions(options, toolchain.getCompilerOptions(features));
+
+ // Enable instrumentation if requested.
+ if (isInstrumented) {
+ addFilteredOptions(options, ImmutableList.of("-fprofile-arcs", "-ftest-coverage"));
+ }
+
+ String sourceFilename = sourceFile.getExecPathString();
+ if (CppFileTypes.C_SOURCE.matches(sourceFilename)) {
+ addFilteredOptions(options, toolchain.getCOptions());
+ }
+ if (CppFileTypes.CPP_SOURCE.matches(sourceFilename)
+ || CppFileTypes.CPP_HEADER.matches(sourceFilename)
+ || CppFileTypes.CPP_MODULE_MAP.matches(sourceFilename)) {
+ addFilteredOptions(options, toolchain.getCxxOptions(features));
+ }
+
+ // Users don't expect the explicit copts to be filtered by coptsFilter, add them verbatim.
+ options.addAll(copts);
+
+ for (String warn : cppConfiguration.getCWarns()) {
+ options.add("-W" + warn);
+ }
+ for (String define : context.getDefines()) {
+ options.add("-D" + define);
+ }
+
+ // Stamp FDO builds with FDO subtype string
+ if (fdoBuildStamp != null) {
+ options.add("-D" + CppConfiguration.FDO_STAMP_MACRO + "=\"" + fdoBuildStamp + "\"");
+ }
+
+ options.addAll(toolchain.getUnfilteredCompilerOptions(features));
+
+ // GCC gives randomized names to symbols which are defined in
+ // an anonymous namespace but have external linkage. To make
+ // computation of these deterministic, we want to override the
+ // default seed for the random number generator. It's safe to use
+ // any value which differs for all translation units; we use the
+ // path to the object file.
+ options.add("-frandom-seed=" + outputFile.getExecPathString());
+
+ // Add the options of --per_file_copt, if the label or the base name of the source file
+ // matches the specified regular expression filter.
+ for (PerLabelOptions perLabelOptions : cppConfiguration.getPerFileCopts()) {
+ if ((sourceLabel != null && perLabelOptions.isIncluded(sourceLabel))
+ || perLabelOptions.isIncluded(sourceFile)) {
+ options.addAll(perLabelOptions.getOptions());
+ }
+ }
+
+ // Enable <object>.d file generation.
+ if (dotdFile != null) {
+ // Gcc options:
+ // -MD turns on .d file output as a side-effect (doesn't imply -E)
+ // -MM[D] enables user includes only, not system includes
+ // -MF <name> specifies the dotd file name
+ // Issues:
+ // -M[M] alone subverts actual .o output (implies -E)
+ // -M[M]D alone breaks some of the .d naming assumptions
+ // This combination gets user and system includes with specified name:
+ // -MD -MF <name>
+ options.add("-MD");
+ options.add("-MF");
+ options.add(dotdFile.getSafeExecPath().getPathString());
+ }
+
+ if (cppModuleMap != null && (compileHeaderModules || enableLayeringCheck)) {
+ options.add("-Xclang-only=-fmodule-maps");
+ options.add("-Xclang-only=-fmodule-name=" + cppModuleMap.getName());
+ options.add("-Xclang-only=-fmodule-map-file="
+ + cppModuleMap.getArtifact().getExecPathString());
+ options.add("-Xclang=-fno-modules-implicit-maps");
+
+ if (compileHeaderModules) {
+ options.add("-Xclang-only=-fmodules");
+ if (CppFileTypes.CPP_MODULE_MAP.matches(sourceFilename)) {
+ options.add("-Xclang=-emit-module");
+ options.add("-Xcrosstool-module-compilation");
+ }
+ // Select .pcm inputs to pass on the command line depending on whether
+ // we are in pic or non-pic mode.
+ // TODO(bazel-team): We want to add these to the compile even if the
+ // current target is not built as a module; currently that implies
+ // passing -fmodules to the compiler, which is experimental; thus, we
+ // do not use the header modules files for now if the current
+ // compilation is not modules enabled on its own.
+ boolean pic = copts.contains("-fPIC");
+ for (Artifact source : context.getAdditionalInputs()) {
+ if ((pic && source.getFilename().endsWith(".pic.pcm")) || (!pic
+ && !source.getFilename().endsWith(".pic.pcm")
+ && source.getFilename().endsWith(".pcm"))) {
+ options.add("-Xclang=-fmodule-file=" + source.getExecPathString());
+ }
+ }
+ }
+ if (enableLayeringCheck) {
+ options.add("-Xclang-only=-fmodules-strict-decluse");
+ }
+ }
+
+ if (FileType.contains(outputFile, CppFileTypes.ASSEMBLER, CppFileTypes.PIC_ASSEMBLER)) {
+ options.add("-S");
+ } else if (FileType.contains(outputFile, CppFileTypes.PREPROCESSED_C,
+ CppFileTypes.PREPROCESSED_CPP, CppFileTypes.PIC_PREPROCESSED_C,
+ CppFileTypes.PIC_PREPROCESSED_CPP)) {
+ options.add("-E");
+ }
+
+ if (cppConfiguration.useFission()) {
+ options.add("-gsplit-dwarf");
+ }
+
+ options.addAll(featureConfiguration.getCommandLine(getActionName(),
+ ImmutableMultimap.<String, String>of()));
+ return options;
+ }
+
+ // For each option in 'in', add it to 'out' unless it is matched by the 'coptsFilter' regexp.
+ private void addFilteredOptions(List<String> out, List<String> in) {
+ Iterables.addAll(out, Iterables.filter(in, coptsFilter));
+ }
+ }
+
+ /**
+ * A reference to a .d file. There are two modes:
+ * <ol>
+ * <li>an Artifact that represents a real on-disk file
+ * <li>just an execPath that refers to a virtual .d file that is not written to disk
+ * </ol>
+ */
+ public static class DotdFile {
+ private final Artifact artifact;
+ private final PathFragment execPath;
+
+ public DotdFile(Artifact artifact) {
+ this.artifact = artifact;
+ this.execPath = null;
+ }
+
+ public DotdFile(PathFragment execPath) {
+ this.artifact = null;
+ this.execPath = execPath;
+ }
+
+ /**
+ * @return the Artifact or null
+ */
+ public Artifact artifact() {
+ return artifact;
+ }
+
+ /**
+ * @return Gets the execPath regardless of whether this is a real Artifact
+ */
+ public PathFragment getSafeExecPath() {
+ return execPath == null ? artifact.getExecPath() : execPath;
+ }
+
+ /**
+ * @return the on-disk location of the .d file or null
+ */
+ public Path getPath() {
+ return artifact.getPath();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
new file mode 100644
index 0000000000..0d8da3e4a2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
@@ -0,0 +1,439 @@
+// Copyright 2014 Google Inc. 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.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+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.Root;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CppCompileAction.DotdFile;
+import com.google.devtools.build.lib.rules.cpp.CppCompileAction.IncludeResolver;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+/**
+ * Builder class to construct C++ compile actions.
+ */
+public class CppCompileActionBuilder {
+ public static final UUID GUID = UUID.fromString("cee5db0a-d2ad-4c69-9b81-97c936a29075");
+
+ private final ActionOwner owner;
+ private final List<String> features = new ArrayList<>();
+ private CcToolchainFeatures.FeatureConfiguration featureConfiguration;
+ private final Artifact sourceFile;
+ private final Label sourceLabel;
+ private final NestedSetBuilder<Artifact> mandatoryInputsBuilder;
+ private NestedSetBuilder<Artifact> pluginInputsBuilder;
+ private Artifact optionalSourceFile;
+ private Artifact outputFile;
+ private PathFragment tempOutputFile;
+ private DotdFile dotdFile;
+ private Artifact gcnoFile;
+ private final BuildConfiguration configuration;
+ private CppCompilationContext context = CppCompilationContext.EMPTY;
+ private final List<String> copts = new ArrayList<>();
+ private final List<String> pluginOpts = new ArrayList<>();
+ private final List<Pattern> nocopts = new ArrayList<>();
+ private AnalysisEnvironment analysisEnvironment;
+ private ImmutableList<PathFragment> extraSystemIncludePrefixes = ImmutableList.of();
+ private boolean enableLayeringCheck;
+ private boolean compileHeaderModules;
+ private String fdoBuildStamp;
+ private IncludeResolver includeResolver = CppCompileAction.VOID_INCLUDE_RESOLVER;
+ private UUID actionClassId = GUID;
+ private Class<? extends CppCompileActionContext> actionContext;
+ private CppConfiguration cppConfiguration;
+ private ImmutableMap<Artifact, IncludeScannable> lipoScannableMap;
+
+ /**
+ * Creates a builder from a rule. This also uses the configuration and
+ * artifact factory from the rule.
+ */
+ public CppCompileActionBuilder(RuleContext ruleContext, Artifact sourceFile, Label sourceLabel) {
+ this.owner = ruleContext.getActionOwner();
+ this.actionContext = CppCompileActionContext.class;
+ this.cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
+ this.analysisEnvironment = ruleContext.getAnalysisEnvironment();
+ this.sourceFile = sourceFile;
+ this.sourceLabel = sourceLabel;
+ this.configuration = ruleContext.getConfiguration();
+ this.mandatoryInputsBuilder = NestedSetBuilder.stableOrder();
+ this.pluginInputsBuilder = NestedSetBuilder.stableOrder();
+ this.lipoScannableMap = getLipoScannableMap(ruleContext);
+
+ features.addAll(ruleContext.getFeatures());
+ }
+
+ private static ImmutableMap<Artifact, IncludeScannable> getLipoScannableMap(
+ RuleContext ruleContext) {
+ if (!ruleContext.getFragment(CppConfiguration.class).isLipoOptimization()) {
+ return null;
+ }
+
+ LipoContextProvider provider = ruleContext.getPrerequisite(
+ ":lipo_context_collector", Mode.DONT_CHECK, LipoContextProvider.class);
+ return provider.getIncludeScannables();
+ }
+
+ /**
+ * Creates a builder for an owner that is not required to be rule.
+ */
+ public CppCompileActionBuilder(
+ ActionOwner owner, AnalysisEnvironment analysisEnvironment, Artifact sourceFile,
+ Label sourceLabel, BuildConfiguration configuration) {
+ this.owner = owner;
+ this.actionContext = CppCompileActionContext.class;
+ this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
+ this.analysisEnvironment = analysisEnvironment;
+ this.sourceFile = sourceFile;
+ this.sourceLabel = sourceLabel;
+ this.configuration = configuration;
+ this.mandatoryInputsBuilder = NestedSetBuilder.stableOrder();
+ this.pluginInputsBuilder = NestedSetBuilder.stableOrder();
+ this.lipoScannableMap = ImmutableMap.of();
+ }
+
+ /**
+ * Creates a builder that is a copy of another builder.
+ */
+ public CppCompileActionBuilder(CppCompileActionBuilder other) {
+ this.owner = other.owner;
+ this.features.addAll(other.features);
+ this.featureConfiguration = other.featureConfiguration;
+ this.sourceFile = other.sourceFile;
+ this.sourceLabel = other.sourceLabel;
+ this.mandatoryInputsBuilder = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(other.mandatoryInputsBuilder.build());
+ this.pluginInputsBuilder = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(other.pluginInputsBuilder.build());
+ this.optionalSourceFile = other.optionalSourceFile;
+ this.outputFile = other.outputFile;
+ this.tempOutputFile = other.tempOutputFile;
+ this.dotdFile = other.dotdFile;
+ this.gcnoFile = other.gcnoFile;
+ this.configuration = other.configuration;
+ this.context = other.context;
+ this.copts.addAll(other.copts);
+ this.pluginOpts.addAll(other.pluginOpts);
+ this.nocopts.addAll(other.nocopts);
+ this.analysisEnvironment = other.analysisEnvironment;
+ this.extraSystemIncludePrefixes = ImmutableList.copyOf(other.extraSystemIncludePrefixes);
+ this.enableLayeringCheck = other.enableLayeringCheck;
+ this.compileHeaderModules = other.compileHeaderModules;
+ this.includeResolver = other.includeResolver;
+ this.actionClassId = other.actionClassId;
+ this.actionContext = other.actionContext;
+ this.cppConfiguration = other.cppConfiguration;
+ this.lipoScannableMap = other.lipoScannableMap;
+ }
+
+ public PathFragment getTempOutputFile() {
+ return tempOutputFile;
+ }
+
+ public Artifact getSourceFile() {
+ return sourceFile;
+ }
+
+ public CppCompilationContext getContext() {
+ return context;
+ }
+
+ public NestedSet<Artifact> getMandatoryInputs() {
+ return mandatoryInputsBuilder.build();
+ }
+
+ /**
+ * Returns the .dwo output file that matches the specified .o output file. If Fission mode
+ * isn't enabled for this build, this is null (we don't produce .dwo files in that case).
+ */
+ private static Artifact getDwoFile(Artifact outputFile, AnalysisEnvironment artifactFactory,
+ CppConfiguration cppConfiguration) {
+
+ // Only create .dwo's for .o compilations (i.e. not .ii or .S).
+ boolean isObjectOutput = CppFileTypes.OBJECT_FILE.matches(outputFile.getExecPath())
+ || CppFileTypes.PIC_OBJECT_FILE.matches(outputFile.getExecPath());
+
+ // Note configurations can be null for tests.
+ if (cppConfiguration != null && cppConfiguration.useFission() && isObjectOutput) {
+ return artifactFactory.getDerivedArtifact(
+ FileSystemUtils.replaceExtension(outputFile.getRootRelativePath(), ".dwo"),
+ outputFile.getRoot());
+ } else {
+ return null;
+ }
+ }
+
+ private static Predicate<String> getNocoptPredicate(Collection<Pattern> patterns) {
+ final ImmutableList<Pattern> finalPatterns = ImmutableList.copyOf(patterns);
+ if (finalPatterns.isEmpty()) {
+ return Predicates.alwaysTrue();
+ } else {
+ return new Predicate<String>() {
+ @Override
+ public boolean apply(String option) {
+ for (Pattern pattern : finalPatterns) {
+ if (pattern.matcher(option).matches()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+ }
+
+ private Iterable<IncludeScannable> getLipoScannables(NestedSet<Artifact> realMandatoryInputs) {
+ return lipoScannableMap == null ? ImmutableList.<IncludeScannable>of() : Iterables.filter(
+ Iterables.transform(
+ Iterables.filter(
+ FileType.filter(
+ realMandatoryInputs,
+ CppFileTypes.C_SOURCE, CppFileTypes.CPP_SOURCE,
+ CppFileTypes.ASSEMBLER_WITH_C_PREPROCESSOR),
+ Predicates.not(Predicates.equalTo(getSourceFile()))),
+ Functions.forMap(lipoScannableMap, null)),
+ Predicates.notNull());
+ }
+
+ /**
+ * Builds the Action as configured and returns the to be generated Artifact.
+ *
+ * <p>This method may be called multiple times to create multiple compile
+ * actions (usually after calling some setters to modify the generated
+ * action).
+ */
+ public CppCompileAction build() {
+ // Configuration can be null in tests.
+ NestedSetBuilder<Artifact> realMandatoryInputsBuilder = NestedSetBuilder.compileOrder();
+ realMandatoryInputsBuilder.addTransitive(mandatoryInputsBuilder.build());
+ if (tempOutputFile == null && configuration != null
+ && !configuration.getFragment(CppConfiguration.class).shouldScanIncludes()) {
+ realMandatoryInputsBuilder.addTransitive(context.getDeclaredIncludeSrcs());
+ }
+ realMandatoryInputsBuilder.addTransitive(context.getAdditionalInputs());
+ realMandatoryInputsBuilder.addTransitive(pluginInputsBuilder.build());
+ realMandatoryInputsBuilder.add(sourceFile);
+ boolean fake = tempOutputFile != null;
+
+ // Copying the collections is needed to make the builder reusable.
+ if (fake) {
+ return new FakeCppCompileAction(owner, ImmutableList.copyOf(features), featureConfiguration,
+ sourceFile, sourceLabel, realMandatoryInputsBuilder.build(), outputFile, tempOutputFile,
+ dotdFile, configuration, cppConfiguration, context, ImmutableList.copyOf(copts),
+ ImmutableList.copyOf(pluginOpts), getNocoptPredicate(nocopts),
+ extraSystemIncludePrefixes, enableLayeringCheck, fdoBuildStamp);
+ } else {
+ NestedSet<Artifact> realMandatoryInputs = realMandatoryInputsBuilder.build();
+
+ return new CppCompileAction(owner, ImmutableList.copyOf(features), featureConfiguration,
+ sourceFile, sourceLabel, realMandatoryInputs, outputFile, dotdFile,
+ gcnoFile, getDwoFile(outputFile, analysisEnvironment, cppConfiguration),
+ optionalSourceFile, configuration, cppConfiguration, context,
+ actionContext, ImmutableList.copyOf(copts),
+ ImmutableList.copyOf(pluginOpts),
+ getNocoptPredicate(nocopts),
+ extraSystemIncludePrefixes, enableLayeringCheck, fdoBuildStamp,
+ includeResolver, getLipoScannables(realMandatoryInputs), actionClassId,
+ compileHeaderModules);
+ }
+ }
+
+ /**
+ * Sets the feature configuration to be used for the action.
+ */
+ public CppCompileActionBuilder setFeatureConfiguration(
+ FeatureConfiguration featureConfiguration) {
+ this.featureConfiguration = featureConfiguration;
+ return this;
+ }
+
+ public CppCompileActionBuilder setIncludeResolver(IncludeResolver includeResolver) {
+ this.includeResolver = includeResolver;
+ return this;
+ }
+
+ public CppCompileActionBuilder setCppConfiguration(CppConfiguration cppConfiguration) {
+ this.cppConfiguration = cppConfiguration;
+ return this;
+ }
+
+ public CppCompileActionBuilder setActionContext(
+ Class<? extends CppCompileActionContext> actionContext) {
+ this.actionContext = actionContext;
+ return this;
+ }
+
+ public CppCompileActionBuilder setActionClassId(UUID uuid) {
+ this.actionClassId = uuid;
+ return this;
+ }
+
+ public CppCompileActionBuilder setExtraSystemIncludePrefixes(
+ Collection<PathFragment> extraSystemIncludePrefixes) {
+ this.extraSystemIncludePrefixes = ImmutableList.copyOf(extraSystemIncludePrefixes);
+ return this;
+ }
+
+ public CppCompileActionBuilder addPluginInput(Artifact artifact) {
+ pluginInputsBuilder.add(artifact);
+ return this;
+ }
+
+ public CppCompileActionBuilder clearPluginInputs() {
+ pluginInputsBuilder = NestedSetBuilder.stableOrder();
+ return this;
+ }
+
+ /**
+ * Set an optional source file (usually with metadata of the main source file). The optional
+ * source file can only be set once, whether via this method or through the constructor
+ * {@link #CppCompileActionBuilder(CppCompileActionBuilder)}.
+ */
+ public CppCompileActionBuilder addOptionalSourceFile(Artifact artifact) {
+ Preconditions.checkState(optionalSourceFile == null, "%s %s", optionalSourceFile, artifact);
+ optionalSourceFile = artifact;
+ return this;
+ }
+
+ public CppCompileActionBuilder addMandatoryInputs(Iterable<Artifact> artifacts) {
+ mandatoryInputsBuilder.addAll(artifacts);
+ return this;
+ }
+
+ public CppCompileActionBuilder addTransitiveMandatoryInputs(NestedSet<Artifact> artifacts) {
+ mandatoryInputsBuilder.addTransitive(artifacts);
+ return this;
+ }
+
+ public CppCompileActionBuilder setOutputFile(Artifact outputFile) {
+ this.outputFile = outputFile;
+ return this;
+ }
+
+ /**
+ * The temp output file is not an artifact, since it does not appear in the outputs of the
+ * action.
+ *
+ * <p>This is theoretically a problem if that file already existed before, since then Blaze
+ * does not delete it before executing the rule, but 1. that only applies for local
+ * execution which does not happen very often and 2. it is only a problem if the compiler is
+ * affected by the presence of this file, which it should not be.
+ */
+ public CppCompileActionBuilder setTempOutputFile(PathFragment tempOutputFile) {
+ this.tempOutputFile = tempOutputFile;
+ return this;
+ }
+
+ @VisibleForTesting
+ public CppCompileActionBuilder setDotdFileForTesting(Artifact dotdFile) {
+ this.dotdFile = new DotdFile(dotdFile);
+ return this;
+ }
+
+ public CppCompileActionBuilder setDotdFile(PathFragment outputName, String extension,
+ RuleContext ruleContext) {
+ if (configuration.getFragment(CppConfiguration.class).getInmemoryDotdFiles()) {
+ // Just set the path, no artifact is constructed
+ PathFragment file = FileSystemUtils.replaceExtension(outputName, extension);
+ Root root = configuration.getBinDirectory();
+ dotdFile = new DotdFile(root.getExecPath().getRelative(file));
+ } else {
+ dotdFile = new DotdFile(ruleContext.getRelatedArtifact(outputName, extension));
+ }
+ return this;
+ }
+
+ public CppCompileActionBuilder setGcnoFile(Artifact gcnoFile) {
+ this.gcnoFile = gcnoFile;
+ return this;
+ }
+
+ public CppCompileActionBuilder addCopt(String copt) {
+ copts.add(copt);
+ return this;
+ }
+
+ public CppCompileActionBuilder addPluginOpt(String opt) {
+ pluginOpts.add(opt);
+ return this;
+ }
+
+ public CppCompileActionBuilder clearPluginOpts() {
+ pluginOpts.clear();
+ return this;
+ }
+
+ public CppCompileActionBuilder addCopts(Iterable<? extends String> copts) {
+ Iterables.addAll(this.copts, copts);
+ return this;
+ }
+
+ public CppCompileActionBuilder addCopts(int position, Iterable<? extends String> copts) {
+ this.copts.addAll(position, ImmutableList.copyOf(copts));
+ return this;
+ }
+
+ public CppCompileActionBuilder addNocopts(Pattern nocopts) {
+ this.nocopts.add(nocopts);
+ return this;
+ }
+
+ public CppCompileActionBuilder setContext(CppCompilationContext context) {
+ this.context = context;
+ return this;
+ }
+
+ public CppCompileActionBuilder setEnableLayeringCheck(boolean enableLayeringCheck) {
+ this.enableLayeringCheck = enableLayeringCheck;
+ return this;
+ }
+
+ /**
+ * Sets whether the CompileAction should use header modules for its compilation.
+ */
+ public CppCompileActionBuilder setCompileHeaderModules(boolean compileHeaderModules) {
+ this.compileHeaderModules = compileHeaderModules;
+ return this;
+ }
+
+ public CppCompileActionBuilder setFdoBuildStamp(String fdoBuildStamp) {
+ this.fdoBuildStamp = fdoBuildStamp;
+ return this;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionContext.java
new file mode 100644
index 0000000000..4bbfa44d61
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionContext.java
@@ -0,0 +1,84 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.ActionContextMarker;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.actions.ResourceSet;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+/**
+ * Context for compiling plain C++.
+ */
+@ActionContextMarker(name = "C++")
+public interface CppCompileActionContext extends ActionContext {
+ /**
+ * Reply for the execution of a C++ compilation.
+ */
+ public interface Reply {
+ /**
+ * Returns the contents of the .d file.
+ */
+ byte[] getContents() throws IOException;
+ }
+
+ /** Does include scanning to find the list of files needed to execute the action. */
+ public Collection<? extends ActionInput> findAdditionalInputs(CppCompileAction action,
+ ActionExecutionContext actionExecutionContext)
+ throws ExecException, InterruptedException, ActionExecutionException;
+
+ /**
+ * Executes the given action and return the reply of the executor.
+ */
+ Reply execWithReply(CppCompileAction action,
+ ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException;
+
+ /**
+ * Returns the executor reply from an exec exception, if available.
+ */
+ @Nullable Reply getReplyFromException(
+ ExecException e, CppCompileAction action);
+
+ /**
+ * Returns the estimated resource consumption of the action.
+ */
+ ResourceSet estimateResourceConsumption(CppCompileAction action);
+
+ /**
+ * Returns where the action actually runs.
+ */
+ String strategyLocality();
+
+ /**
+ * Returns whether include scanning needs to be run.
+ */
+ boolean needsIncludeScanning();
+
+ /**
+ * Returns the include files that should be shipped to the executor in addition the ones that
+ * were declared.
+ */
+ Collection<Artifact> getScannedIncludeFiles(
+ CppCompileAction action, ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
new file mode 100644
index 0000000000..c5cc9a57fc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -0,0 +1,1691 @@
+// Copyright 2014 Google Inc. 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.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.PackageRootResolver;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.rules.cpp.CppConfigurationLoader.CppConfigurationParameters;
+import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoException;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.syntax.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+import com.google.devtools.build.lib.util.IncludeScanningUtil;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LinkingModeFlags;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipException;
+
+/**
+ * This class represents the C/C++ parts of the {@link BuildConfiguration},
+ * including the host architecture, target architecture, compiler version, and
+ * a standard library version. It has information about the tools locations and
+ * the flags required for compiling.
+ */
+@SkylarkModule(name = "cpp", doc = "A configuration fragment for C++")
+@Immutable
+public class CppConfiguration extends BuildConfiguration.Fragment {
+ /**
+ * An enumeration of all the tools that comprise a toolchain.
+ */
+ public enum Tool {
+ AR("ar"),
+ CPP("cpp"),
+ GCC("gcc"),
+ GCOV("gcov"),
+ GCOVTOOL("gcov-tool"),
+ LD("ld"),
+ NM("nm"),
+ OBJCOPY("objcopy"),
+ OBJDUMP("objdump"),
+ STRIP("strip"),
+ DWP("dwp");
+
+ private final String namePart;
+
+ private Tool(String namePart) {
+ this.namePart = namePart;
+ }
+
+ public String getNamePart() {
+ return namePart;
+ }
+ }
+
+ /**
+ * Values for the --hdrs_check option.
+ */
+ public static enum HeadersCheckingMode {
+ /** Legacy behavior: Silently allow undeclared headers. */
+ LOOSE,
+ /** Warn about undeclared headers. */
+ WARN,
+ /** Disallow undeclared headers. */
+ STRICT
+ }
+
+ /**
+ * --dynamic_mode parses to DynamicModeFlag, but AUTO will be translated based on platform,
+ * resulting in a DynamicMode value.
+ */
+ public enum DynamicMode { OFF, DEFAULT, FULLY }
+
+ /**
+ * This enumeration is used for the --strip option.
+ */
+ public static enum StripMode {
+
+ ALWAYS("always"), // Always strip.
+ SOMETIMES("sometimes"), // Strip iff compilationMode == FASTBUILD.
+ NEVER("never"); // Never strip.
+
+ private final String mode;
+
+ private StripMode(String mode) {
+ this.mode = mode;
+ }
+
+ @Override
+ public String toString() {
+ return mode;
+ }
+ }
+
+ /** Storage for the libc label, if given. */
+ public static class LibcTop implements Serializable {
+ private final Label label;
+
+ LibcTop(Label label) {
+ Preconditions.checkArgument(label != null);
+ this.label = label;
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ public PathFragment getSysroot() {
+ return label.getPackageFragment();
+ }
+
+ @Override
+ public String toString() {
+ return label.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof LibcTop) {
+ return label.equals(((LibcTop) other).label);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return label.hashCode();
+ }
+ }
+
+ /**
+ * This macro will be passed as a command-line parameter (eg. -DBUILD_FDO_TYPE="LIPO").
+ * For possible values see {@code CppModel.getFdoBuildStamp()}.
+ */
+ public static final String FDO_STAMP_MACRO = "BUILD_FDO_TYPE";
+
+ /**
+ * Represents an optional flag that can be toggled using the package features mechanism.
+ */
+ @VisibleForTesting
+ static class OptionalFlag implements Serializable {
+ private final String name;
+ private final List<String> flags;
+
+ @VisibleForTesting
+ OptionalFlag(String name, List<String> flags) {
+ this.name = name;
+ this.flags = flags;
+ }
+
+ private List<String> getFlags() {
+ return flags;
+ }
+
+ private String getName() {
+ return name;
+ }
+ }
+
+ @VisibleForTesting
+ static class FlagList implements Serializable {
+ private List<String> prefixFlags;
+ private List<OptionalFlag> optionalFlags;
+ private List<String> suffixFlags;
+
+ @VisibleForTesting
+ FlagList(List<String> prefixFlags,
+ List<OptionalFlag> optionalFlags,
+ List<String> suffixFlags) {
+ this.prefixFlags = prefixFlags;
+ this.optionalFlags = optionalFlags;
+ this.suffixFlags = suffixFlags;
+ }
+
+ @VisibleForTesting
+ List<String> evaluate(Collection<String> features) {
+ ImmutableList.Builder<String> result = ImmutableList.builder();
+ result.addAll(prefixFlags);
+ for (OptionalFlag optionalFlag : optionalFlags) {
+ // The flag is added if the default is true and the flag is not specified,
+ // or if the default is false and the flag is specified.
+ if (features.contains(optionalFlag.getName())) {
+ result.addAll(optionalFlag.getFlags());
+ }
+ }
+
+ result.addAll(suffixFlags);
+ return result.build();
+ }
+ }
+
+ private final Label crosstoolTop;
+ private final String hostSystemName;
+ private final String compiler;
+ private final String targetCpu;
+ private final String targetSystemName;
+ private final String targetLibc;
+ private final LipoMode lipoMode;
+ private final PathFragment crosstoolTopPathFragment;
+
+ private final String abi;
+ private final String abiGlibcVersion;
+
+ private final String toolchainIdentifier;
+ private final String cacheKey;
+
+ private final CcToolchainFeatures toolchainFeatures;
+ private final boolean supportsGoldLinker;
+ private final boolean supportsThinArchives;
+ private final boolean supportsStartEndLib;
+ private final boolean supportsInterfaceSharedObjects;
+ private final boolean supportsEmbeddedRuntimes;
+ private final boolean supportsFission;
+
+ // We encode three states with two booleans:
+ // (1) (false false) -> no pic code
+ // (2) (true false) -> shared libraries as pic, but not binaries
+ // (3) (true true) -> both shared libraries and binaries as pic
+ private final boolean toolchainNeedsPic;
+ private final boolean usePicForBinaries;
+
+ private final FdoSupport fdoSupport;
+
+ // TODO(bazel-team): All these labels (except for ccCompilerRuleLabel) can be removed once the
+ // transition to the cc_compiler rule is complete.
+ private final Label libcLabel;
+ private final Label staticRuntimeLibsLabel;
+ private final Label dynamicRuntimeLibsLabel;
+ private final Label ccToolchainLabel;
+
+ private final PathFragment sysroot;
+ private final PathFragment runtimeSysroot;
+ private final List<PathFragment> builtInIncludeDirectories;
+
+ private final Map<String, PathFragment> toolPaths;
+ private final PathFragment ldExecutable;
+
+ // Only used during construction.
+ private final List<String> commonLinkOptions;
+ private final ListMultimap<CompilationMode, String> linkOptionsFromCompilationMode;
+ private final ListMultimap<LipoMode, String> linkOptionsFromLipoMode;
+ private final ListMultimap<LinkingMode, String> linkOptionsFromLinkingMode;
+
+ private final FlagList compilerFlags;
+ private final FlagList cxxFlags;
+ private final FlagList unfilteredCompilerFlags;
+ private final List<String> cOptions;
+
+ private FlagList fullyStaticLinkFlags;
+ private FlagList mostlyStaticLinkFlags;
+ private FlagList mostlyStaticSharedLinkFlags;
+ private FlagList dynamicLinkFlags;
+ private FlagList dynamicLibraryLinkFlags;
+ private final List<String> testOnlyLinkFlags;
+
+ private final List<String> linkOptions;
+
+ private final List<String> objcopyOptions;
+ private final List<String> ldOptions;
+ private final List<String> arOptions;
+ private final List<String> arThinArchivesOptions;
+
+ private final Map<String, String> additionalMakeVariables;
+
+ private final CppOptions cppOptions;
+
+ // The dynamic mode for linking.
+ private final DynamicMode dynamicMode;
+ private final boolean stripBinaries;
+ private final ImmutableMap<String, String> commandLineDefines;
+ private final String solibDirectory;
+ private final CompilationMode compilationMode;
+ private final Path execRoot;
+ /**
+ * If true, the ConfiguredTarget is only used to get the necessary cross-referenced
+ * CppCompilationContexts, but registering build actions is disabled.
+ */
+ private final boolean lipoContextCollector;
+ private final Root greppedIncludesDirectory;
+
+ protected CppConfiguration(CppConfigurationParameters params)
+ throws InvalidConfigurationException {
+ CrosstoolConfig.CToolchain toolchain = params.toolchain;
+ cppOptions = params.buildOptions.get(CppOptions.class);
+ this.hostSystemName = toolchain.getHostSystemName();
+ this.compiler = toolchain.getCompiler();
+ this.targetCpu = toolchain.getTargetCpu();
+ this.lipoMode = cppOptions.getLipoMode();
+ this.targetSystemName = toolchain.getTargetSystemName();
+ this.targetLibc = toolchain.getTargetLibc();
+ this.crosstoolTop = params.crosstoolTop;
+ this.ccToolchainLabel = params.ccToolchainLabel;
+ this.compilationMode =
+ params.buildOptions.get(BuildConfiguration.Options.class).compilationMode;
+ this.lipoContextCollector = cppOptions.lipoCollector;
+ this.execRoot = params.execRoot;
+
+ // Note that the grepped includes directory is not configuration-specific; the paths of the
+ // files within that directory, however, are configuration-specific.
+ this.greppedIncludesDirectory = Root.asDerivedRoot(execRoot,
+ execRoot.getRelative(IncludeScanningUtil.GREPPED_INCLUDES));
+
+ this.crosstoolTopPathFragment = crosstoolTop.getPackageFragment();
+
+ try {
+ this.staticRuntimeLibsLabel =
+ crosstoolTop.getRelative(toolchain.hasStaticRuntimesFilegroup() ?
+ toolchain.getStaticRuntimesFilegroup() : "static-runtime-libs-" + targetCpu);
+ this.dynamicRuntimeLibsLabel =
+ crosstoolTop.getRelative(toolchain.hasDynamicRuntimesFilegroup() ?
+ toolchain.getDynamicRuntimesFilegroup() : "dynamic-runtime-libs-" + targetCpu);
+ } catch (SyntaxException e) {
+ // All of the above label.getRelative() calls are valid labels, and the crosstool_top
+ // was already checked earlier in the process.
+ throw new AssertionError(e);
+ }
+
+ if (cppOptions.lipoMode == LipoMode.BINARY) {
+ // TODO(bazel-team): implement dynamic linking with LIPO
+ this.dynamicMode = DynamicMode.OFF;
+ } else {
+ switch (cppOptions.dynamicMode) {
+ case DEFAULT:
+ this.dynamicMode = DynamicMode.DEFAULT; break;
+ case OFF: this.dynamicMode = DynamicMode.OFF; break;
+ case FULLY: this.dynamicMode = DynamicMode.FULLY; break;
+ default: throw new IllegalStateException("Invalid dynamicMode.");
+ }
+ }
+
+ this.fdoSupport = new FdoSupport(
+ params.buildOptions.get(CppOptions.class).fdoInstrument, params.fdoZip,
+ cppOptions.lipoMode, execRoot);
+
+ this.stripBinaries = (cppOptions.stripBinaries == StripMode.ALWAYS ||
+ (cppOptions.stripBinaries == StripMode.SOMETIMES &&
+ compilationMode == CompilationMode.FASTBUILD));
+
+ CrosstoolConfigurationIdentifier crosstoolConfig =
+ CrosstoolConfigurationIdentifier.fromToolchain(toolchain);
+ Preconditions.checkState(crosstoolConfig.getCpu().equals(targetCpu));
+ Preconditions.checkState(crosstoolConfig.getCompiler().equals(compiler));
+ Preconditions.checkState(crosstoolConfig.getLibc().equals(targetLibc));
+
+ this.solibDirectory = "_solib_" + targetCpu;
+
+ this.toolchainIdentifier = toolchain.getToolchainIdentifier();
+ this.cacheKey = this + ":" + crosstoolTop + ":" + params.cacheKeySuffix + ":"
+ + lipoContextCollector;
+
+ this.toolchainFeatures = new CcToolchainFeatures(toolchain);
+ this.supportsGoldLinker = toolchain.getSupportsGoldLinker();
+ this.supportsThinArchives = toolchain.getSupportsThinArchives();
+ this.supportsStartEndLib = toolchain.getSupportsStartEndLib();
+ this.supportsInterfaceSharedObjects = toolchain.getSupportsInterfaceSharedObjects();
+ this.supportsEmbeddedRuntimes = toolchain.getSupportsEmbeddedRuntimes();
+ this.supportsFission = toolchain.getSupportsFission();
+ this.toolchainNeedsPic = toolchain.getNeedsPic();
+ this.usePicForBinaries =
+ toolchain.getNeedsPic() && compilationMode != CompilationMode.OPT;
+
+ this.toolPaths = Maps.newHashMap();
+ for (CrosstoolConfig.ToolPath tool : toolchain.getToolPathList()) {
+ PathFragment path = new PathFragment(tool.getPath());
+ if (!path.isNormalized()) {
+ throw new IllegalArgumentException("The include path '" + tool.getPath()
+ + "' is not normalized.");
+ }
+ toolPaths.put(tool.getName(), crosstoolTopPathFragment.getRelative(path));
+ }
+
+ if (toolPaths.isEmpty()) {
+ // If no paths are specified, we just use the names of the tools as the path.
+ for (Tool tool : Tool.values()) {
+ toolPaths.put(tool.getNamePart(),
+ crosstoolTopPathFragment.getRelative(tool.getNamePart()));
+ }
+ } else {
+ Iterable<Tool> neededTools = Iterables.filter(EnumSet.allOf(Tool.class),
+ new Predicate<Tool>() {
+ @Override
+ public boolean apply(Tool tool) {
+ if (tool == Tool.DWP) {
+ // When fission is unsupported, don't check for the dwp tool.
+ return supportsFission();
+ } else if (tool == Tool.GCOVTOOL) {
+ // gcov-tool is optional, don't check whether it's present
+ return false;
+ } else {
+ return true;
+ }
+ }
+ });
+ for (Tool tool : neededTools) {
+ if (!toolPaths.containsKey(tool.getNamePart())) {
+ throw new IllegalArgumentException("Tool path for '" + tool.getNamePart()
+ + "' is missing");
+ }
+ }
+ }
+
+ // We can't use an ImmutableMap.Builder here; we need the ability (at least
+ // in tests) to add entries with keys that are already in the map, and only
+ // HashMap supports this (by replacing the existing entry under the key).
+ Map<String, String> commandLineDefinesBuilder = new HashMap<>();
+ for (Map.Entry<String, String> define : cppOptions.commandLineDefinedVariables) {
+ commandLineDefinesBuilder.put(define.getKey(), define.getValue());
+ }
+ commandLineDefines = ImmutableMap.copyOf(commandLineDefinesBuilder);
+
+ ListMultimap<CompilationMode, String> cFlags = ArrayListMultimap.create();
+ ListMultimap<CompilationMode, String> cxxFlags = ArrayListMultimap.create();
+ linkOptionsFromCompilationMode = ArrayListMultimap.create();
+ for (CrosstoolConfig.CompilationModeFlags flags : toolchain.getCompilationModeFlagsList()) {
+ // Remove this when CROSSTOOL files no longer contain 'coverage'.
+ if (flags.getMode() == CrosstoolConfig.CompilationMode.COVERAGE) {
+ continue;
+ }
+ CompilationMode realmode = importCompilationMode(flags.getMode());
+ cFlags.putAll(realmode, flags.getCompilerFlagList());
+ cxxFlags.putAll(realmode, flags.getCxxFlagList());
+ linkOptionsFromCompilationMode.putAll(realmode, flags.getLinkerFlagList());
+ }
+
+ ListMultimap<LipoMode, String> lipoCFlags = ArrayListMultimap.create();
+ ListMultimap<LipoMode, String> lipoCxxFlags = ArrayListMultimap.create();
+ linkOptionsFromLipoMode = ArrayListMultimap.create();
+ for (CrosstoolConfig.LipoModeFlags flags : toolchain.getLipoModeFlagsList()) {
+ LipoMode realmode = flags.getMode();
+ lipoCFlags.putAll(realmode, flags.getCompilerFlagList());
+ lipoCxxFlags.putAll(realmode, flags.getCxxFlagList());
+ linkOptionsFromLipoMode.putAll(realmode, flags.getLinkerFlagList());
+ }
+
+ linkOptionsFromLinkingMode = ArrayListMultimap.create();
+ for (LinkingModeFlags flags : toolchain.getLinkingModeFlagsList()) {
+ LinkingMode realmode = importLinkingMode(flags.getMode());
+ linkOptionsFromLinkingMode.putAll(realmode, flags.getLinkerFlagList());
+ }
+
+ this.commonLinkOptions = ImmutableList.copyOf(toolchain.getLinkerFlagList());
+ dynamicLibraryLinkFlags = new FlagList(
+ ImmutableList.copyOf(toolchain.getDynamicLibraryLinkerFlagList()),
+ convertOptionalOptions(toolchain.getOptionalDynamicLibraryLinkerFlagList()),
+ Collections.<String>emptyList());
+ this.objcopyOptions = ImmutableList.copyOf(toolchain.getObjcopyEmbedFlagList());
+ this.ldOptions = ImmutableList.copyOf(toolchain.getLdEmbedFlagList());
+ this.arOptions = copyOrDefaultIfEmpty(toolchain.getArFlagList(), "rcsD");
+ this.arThinArchivesOptions = copyOrDefaultIfEmpty(
+ toolchain.getArThinArchivesFlagList(), "rcsDT");
+
+ this.abi = toolchain.getAbiVersion();
+ this.abiGlibcVersion = toolchain.getAbiLibcVersion();
+
+ // The default value for optional string attributes is the empty string.
+ PathFragment defaultSysroot = toolchain.getBuiltinSysroot().length() == 0
+ ? null
+ : new PathFragment(toolchain.getBuiltinSysroot());
+ if ((defaultSysroot != null) && !defaultSysroot.isNormalized()) {
+ throw new IllegalArgumentException("The built-in sysroot '" + defaultSysroot
+ + "' is not normalized.");
+ }
+
+ if ((cppOptions.libcTop != null) && (defaultSysroot == null)) {
+ throw new InvalidConfigurationException("The selected toolchain " + toolchainIdentifier
+ + " does not support setting --grte_top.");
+ }
+ LibcTop libcTop = cppOptions.libcTop;
+ if ((libcTop == null) && !toolchain.getDefaultGrteTop().isEmpty()) {
+ try {
+ libcTop = new CppOptions.LibcTopConverter().convert(toolchain.getDefaultGrteTop());
+ } catch (OptionsParsingException e) {
+ throw new InvalidConfigurationException(e.getMessage(), e);
+ }
+ }
+ if ((libcTop != null) && (libcTop.getLabel() != null)) {
+ libcLabel = libcTop.getLabel();
+ } else {
+ libcLabel = null;
+ }
+
+ ImmutableList.Builder<PathFragment> builtInIncludeDirectoriesBuilder
+ = ImmutableList.builder();
+ sysroot = libcTop == null ? defaultSysroot : libcTop.getSysroot();
+ for (String s : toolchain.getCxxBuiltinIncludeDirectoryList()) {
+ builtInIncludeDirectoriesBuilder.add(
+ resolveIncludeDir(s, sysroot, crosstoolTopPathFragment));
+ }
+ builtInIncludeDirectories = builtInIncludeDirectoriesBuilder.build();
+
+ // The runtime sysroot should really be set from --grte_top. However, currently libc has no
+ // way to set the sysroot. The CROSSTOOL file does set the runtime sysroot, in the
+ // builtin_sysroot field. This implies that you can not arbitrarily mix and match Crosstool
+ // and libc versions, you must always choose compatible ones.
+ runtimeSysroot = defaultSysroot;
+
+ String sysrootFlag;
+ if (sysroot != null && !sysroot.equals(defaultSysroot)) {
+ // Only specify the --sysroot option if it is different from the built-in one.
+ sysrootFlag = "--sysroot=" + sysroot;
+ } else {
+ sysrootFlag = null;
+ }
+
+ ImmutableList.Builder<String> unfilteredCoptsBuilder = ImmutableList.builder();
+ if (sysrootFlag != null) {
+ unfilteredCoptsBuilder.add(sysrootFlag);
+ }
+ unfilteredCoptsBuilder.addAll(toolchain.getUnfilteredCxxFlagList());
+ unfilteredCompilerFlags = new FlagList(
+ unfilteredCoptsBuilder.build(),
+ convertOptionalOptions(toolchain.getOptionalUnfilteredCxxFlagList()),
+ Collections.<String>emptyList());
+
+ ImmutableList.Builder<String> linkoptsBuilder = ImmutableList.builder();
+ linkoptsBuilder.addAll(cppOptions.linkoptList);
+ if (cppOptions.experimentalOmitfp) {
+ linkoptsBuilder.add("-Wl,--eh-frame-hdr");
+ }
+ if (sysrootFlag != null) {
+ linkoptsBuilder.add(sysrootFlag);
+ }
+ this.linkOptions = linkoptsBuilder.build();
+
+ ImmutableList.Builder<String> coptsBuilder = ImmutableList.<String>builder()
+ .addAll(toolchain.getCompilerFlagList())
+ .addAll(cFlags.get(compilationMode))
+ .addAll(lipoCFlags.get(cppOptions.getLipoMode()));
+ if (cppOptions.experimentalOmitfp) {
+ coptsBuilder.add("-fomit-frame-pointer");
+ coptsBuilder.add("-fasynchronous-unwind-tables");
+ coptsBuilder.add("-DNO_FRAME_POINTER");
+ }
+ this.compilerFlags = new FlagList(
+ coptsBuilder.build(),
+ convertOptionalOptions(toolchain.getOptionalCompilerFlagList()),
+ cppOptions.coptList);
+
+ this.cOptions = ImmutableList.copyOf(cppOptions.conlyoptList);
+
+ ImmutableList.Builder<String> cxxOptsBuilder = ImmutableList.<String>builder()
+ .addAll(toolchain.getCxxFlagList())
+ .addAll(cxxFlags.get(compilationMode))
+ .addAll(lipoCxxFlags.get(cppOptions.getLipoMode()));
+
+ this.cxxFlags = new FlagList(
+ cxxOptsBuilder.build(),
+ convertOptionalOptions(toolchain.getOptionalCxxFlagList()),
+ cppOptions.cxxoptList);
+
+ this.ldExecutable = getToolPathFragment(CppConfiguration.Tool.LD);
+
+ boolean stripBinaries = (cppOptions.stripBinaries == StripMode.ALWAYS) ||
+ ((cppOptions.stripBinaries == StripMode.SOMETIMES) &&
+ (compilationMode == CompilationMode.FASTBUILD));
+
+ fullyStaticLinkFlags = new FlagList(
+ configureLinkerOptions(compilationMode, lipoMode, LinkingMode.FULLY_STATIC,
+ ldExecutable, stripBinaries),
+ convertOptionalOptions(toolchain.getOptionalLinkerFlagList()),
+ Collections.<String>emptyList());
+ mostlyStaticLinkFlags = new FlagList(
+ configureLinkerOptions(compilationMode, lipoMode, LinkingMode.MOSTLY_STATIC,
+ ldExecutable, stripBinaries),
+ convertOptionalOptions(toolchain.getOptionalLinkerFlagList()),
+ Collections.<String>emptyList());
+ mostlyStaticSharedLinkFlags = new FlagList(
+ configureLinkerOptions(compilationMode, lipoMode,
+ LinkingMode.MOSTLY_STATIC_LIBRARIES, ldExecutable, stripBinaries),
+ convertOptionalOptions(toolchain.getOptionalLinkerFlagList()),
+ Collections.<String>emptyList());
+ dynamicLinkFlags = new FlagList(
+ configureLinkerOptions(compilationMode, lipoMode, LinkingMode.DYNAMIC,
+ ldExecutable, stripBinaries),
+ convertOptionalOptions(toolchain.getOptionalLinkerFlagList()),
+ Collections.<String>emptyList());
+ testOnlyLinkFlags = ImmutableList.copyOf(toolchain.getTestOnlyLinkerFlagList());
+
+ Map<String, String> makeVariablesBuilder = new HashMap<>();
+ // The following are to be used to allow some build rules to avoid the limits on stack frame
+ // sizes and variable-length arrays. Ensure that these are always set.
+ makeVariablesBuilder.put("STACK_FRAME_UNLIMITED", "");
+ makeVariablesBuilder.put("CC_FLAGS", "");
+ for (CrosstoolConfig.MakeVariable variable : toolchain.getMakeVariableList()) {
+ makeVariablesBuilder.put(variable.getName(), variable.getValue());
+ }
+ if (sysrootFlag != null) {
+ String ccFlags = makeVariablesBuilder.get("CC_FLAGS");
+ ccFlags = ccFlags.isEmpty() ? sysrootFlag : ccFlags + " " + sysrootFlag;
+ makeVariablesBuilder.put("CC_FLAGS", ccFlags);
+ }
+ this.additionalMakeVariables = ImmutableMap.copyOf(makeVariablesBuilder);
+ }
+
+ private List<OptionalFlag> convertOptionalOptions(
+ List<CrosstoolConfig.CToolchain.OptionalFlag> optionalFlagList)
+ throws IllegalArgumentException {
+ List<OptionalFlag> result = new ArrayList<>();
+
+ for (CrosstoolConfig.CToolchain.OptionalFlag crosstoolOptionalFlag : optionalFlagList) {
+ String name = crosstoolOptionalFlag.getDefaultSettingName();
+ result.add(new OptionalFlag(
+ name,
+ ImmutableList.copyOf(crosstoolOptionalFlag.getFlagList())));
+ }
+
+ return result;
+ }
+
+ private static ImmutableList<String> copyOrDefaultIfEmpty(List<String> list,
+ String defaultValue) {
+ return list.isEmpty() ? ImmutableList.of(defaultValue) : ImmutableList.copyOf(list);
+ }
+
+ @VisibleForTesting
+ static CompilationMode importCompilationMode(CrosstoolConfig.CompilationMode mode) {
+ return CompilationMode.valueOf(mode.name());
+ }
+
+ @VisibleForTesting
+ static LinkingMode importLinkingMode(CrosstoolConfig.LinkingMode mode) {
+ return LinkingMode.valueOf(mode.name());
+ }
+
+ private static final PathFragment SYSROOT_FRAGMENT = new PathFragment("%sysroot%");
+
+ /**
+ * Resolve the given include directory. If it is not absolute, it is
+ * interpreted relative to the crosstool top. If it starts with %sysroot%/,
+ * that part is replaced with the actual sysroot.
+ */
+ static PathFragment resolveIncludeDir(String s, PathFragment sysroot,
+ PathFragment crosstoolTopPathFragment) {
+ PathFragment path = new PathFragment(s);
+ if (!path.isNormalized()) {
+ throw new IllegalArgumentException("The include path '" + s + "' is not normalized.");
+ }
+ if (path.startsWith(SYSROOT_FRAGMENT)) {
+ if (sysroot == null) {
+ throw new IllegalArgumentException("A %sysroot% prefix is only allowed if the "
+ + "default_sysroot option is set");
+ }
+ return sysroot.getRelative(path.relativeTo(SYSROOT_FRAGMENT));
+ } else {
+ return crosstoolTopPathFragment.getRelative(path);
+ }
+ }
+
+ /**
+ * Returns the configuration-independent grepped-includes directory.
+ */
+ public Root getGreppedIncludesDirectory() {
+ return greppedIncludesDirectory;
+ }
+
+ @VisibleForTesting
+ List<String> configureLinkerOptions(
+ CompilationMode compilationMode, LipoMode lipoMode, LinkingMode linkingMode,
+ PathFragment ldExecutable, boolean stripBinaries) {
+ List<String> result = new ArrayList<>();
+ result.addAll(commonLinkOptions);
+
+ result.add("-B" + ldExecutable.getParentDirectory().getPathString());
+ if (stripBinaries) {
+ result.add("-Wl,-S");
+ }
+
+ result.addAll(linkOptionsFromCompilationMode.get(compilationMode));
+ result.addAll(linkOptionsFromLipoMode.get(lipoMode));
+ result.addAll(linkOptionsFromLinkingMode.get(linkingMode));
+ return ImmutableList.copyOf(result);
+ }
+
+ /**
+ * Returns the toolchain identifier, which uniquely identifies the compiler
+ * version, target libc version, target cpu, and LIPO linkage.
+ */
+ public String getToolchainIdentifier() {
+ return toolchainIdentifier;
+ }
+
+ /**
+ * Returns the system name which is required by the toolchain to run.
+ */
+ public String getHostSystemName() {
+ return hostSystemName;
+ }
+
+ @Override
+ public String toString() {
+ return toolchainIdentifier;
+ }
+
+ /**
+ * Returns the compiler version string (e.g. "gcc-4.1.1").
+ */
+ @SkylarkCallable(name = "compiler", structField = true, doc = "C++ compiler.")
+ public String getCompiler() {
+ return compiler;
+ }
+
+ /**
+ * Returns the libc version string (e.g. "glibc-2.2.2").
+ */
+ public String getTargetLibc() {
+ return targetLibc;
+ }
+
+ /**
+ * Returns the target architecture using blaze-specific constants (e.g. "piii").
+ */
+ @SkylarkCallable(name = "cpu", structField = true, doc = "Target CPU of the C++ toolchain.")
+ public String getTargetCpu() {
+ return targetCpu;
+ }
+
+ /**
+ * Returns the path fragment that is either absolute or relative to the
+ * execution root that can be used to execute the given tool.
+ *
+ * <p>Note that you must not use this method to get the linker location, but
+ * use {@link #getLdExecutable} instead!
+ */
+ public PathFragment getToolPathFragment(CppConfiguration.Tool tool) {
+ return toolPaths.get(tool.getNamePart());
+ }
+
+ /**
+ * Returns a label that forms a dependency to the files required for the
+ * sysroot that is used.
+ */
+ public Label getLibcLabel() {
+ return libcLabel;
+ }
+
+ /**
+ * Returns a label that references the library files needed to statically
+ * link the C++ runtime (i.e. libgcc.a, libgcc_eh.a, libstdc++.a) for the
+ * target architecture.
+ */
+ public Label getStaticRuntimeLibsLabel() {
+ return supportsEmbeddedRuntimes() ? staticRuntimeLibsLabel : null;
+ }
+
+ /**
+ * Returns a label that references the library files needed to dynamically
+ * link the C++ runtime (i.e. libgcc_s.so, libstdc++.so) for the target
+ * architecture.
+ */
+ public Label getDynamicRuntimeLibsLabel() {
+ return supportsEmbeddedRuntimes() ? dynamicRuntimeLibsLabel : null;
+ }
+
+ /**
+ * Returns the label of the <code>cc_compiler</code> rule for the C++ configuration.
+ */
+ public Label getCcToolchainRuleLabel() {
+ return ccToolchainLabel;
+ }
+
+ /**
+ * Returns the abi we're using, which is a gcc version. E.g.: "gcc-3.4".
+ * Note that in practice we might be using gcc-3.4 as ABI even when compiling
+ * with gcc-4.1.0, because ABIs are backwards compatible.
+ */
+ // TODO(bazel-team): The javadoc should clarify how this is used in Blaze.
+ public String getAbi() {
+ return abi;
+ }
+
+ /**
+ * Returns the glibc version used by the abi we're using. This is a
+ * glibc version number (e.g., "2.2.2"). Note that in practice we
+ * might be using glibc 2.2.2 as ABI even when compiling with
+ * gcc-4.2.2, gcc-4.3.1, or gcc-4.4.0 (which use glibc 2.3.6),
+ * because ABIs are backwards compatible.
+ */
+ // TODO(bazel-team): The javadoc should clarify how this is used in Blaze.
+ public String getAbiGlibcVersion() {
+ return abiGlibcVersion;
+ }
+
+ /**
+ * Returns the configured features of the toolchain. Rules should not call this directly, but
+ * instead use {@code CcToolchainProvider.getFeatures}.
+ */
+ public CcToolchainFeatures getFeatures() {
+ return toolchainFeatures;
+ }
+
+ /**
+ * Returns whether the toolchain supports the gold linker.
+ */
+ public boolean supportsGoldLinker() {
+ return supportsGoldLinker;
+ }
+
+ /**
+ * Returns whether the toolchain supports thin archives.
+ */
+ public boolean supportsThinArchives() {
+ return supportsThinArchives;
+ }
+
+ /**
+ * Returns whether the toolchain supports the --start-lib/--end-lib options.
+ */
+ public boolean supportsStartEndLib() {
+ return supportsStartEndLib;
+ }
+
+ /**
+ * Returns whether build_interface_so can build interface shared objects for this toolchain.
+ * Should be true if this toolchain generates ELF objects.
+ */
+ public boolean supportsInterfaceSharedObjects() {
+ return supportsInterfaceSharedObjects;
+ }
+
+ /**
+ * Returns whether the toolchain supports linking C/C++ runtime libraries
+ * supplied inside the toolchain distribution.
+ */
+ public boolean supportsEmbeddedRuntimes() {
+ return supportsEmbeddedRuntimes;
+ }
+
+ /**
+ * Returns whether the toolchain supports EXEC_ORIGIN libraries resolution.
+ */
+ public boolean supportsExecOrigin() {
+ // We're rolling out support for this in the same release that also supports embedded runtimes.
+ return supportsEmbeddedRuntimes;
+ }
+
+ /**
+ * Returns whether the toolchain supports "Fission" C++ builds, i.e. builds
+ * where compilation partitions object code and debug symbols into separate
+ * output files.
+ */
+ public boolean supportsFission() {
+ return supportsFission;
+ }
+
+ /**
+ * Returns whether shared libraries must be compiled with position
+ * independent code on this platform.
+ */
+ public boolean toolchainNeedsPic() {
+ return toolchainNeedsPic;
+ }
+
+ /**
+ * Returns whether binaries must be compiled with position independent code.
+ */
+ public boolean usePicForBinaries() {
+ return usePicForBinaries;
+ }
+
+ /**
+ * Returns the type of archives being used.
+ */
+ public Link.ArchiveType archiveType() {
+ if (useStartEndLib()) {
+ return Link.ArchiveType.START_END_LIB;
+ }
+ if (useThinArchives()) {
+ return Link.ArchiveType.THIN;
+ }
+ return Link.ArchiveType.FAT;
+ }
+
+ /**
+ * Returns the ar flags to be used.
+ */
+ public List<String> getArFlags(boolean thinArchives) {
+ return thinArchives ? arThinArchivesOptions : arOptions;
+ }
+
+ /**
+ * Returns a string that uniquely identifies the toolchain.
+ */
+ @Override
+ public String cacheKey() {
+ return cacheKey;
+ }
+
+ /**
+ * Returns the built-in list of system include paths for the toolchain
+ * compiler. All paths in this list should be relative to the exec directory.
+ * They may be absolute if they are also installed on the remote build nodes or
+ * for local compilation.
+ */
+ public List<PathFragment> getBuiltInIncludeDirectories() {
+ return builtInIncludeDirectories;
+ }
+
+ /**
+ * Returns the sysroot to be used. If the toolchain compiler does not support
+ * different sysroots, or the sysroot is the same as the default sysroot, then
+ * this method returns <code>null</code>.
+ */
+ public PathFragment getSysroot() {
+ return sysroot;
+ }
+
+ /**
+ * Returns the run time sysroot, which is where the dynamic linker
+ * and system libraries are found at runtime. This is usually an absolute path. If the
+ * toolchain compiler does not support sysroots, then this method returns <code>null</code>.
+ */
+ public PathFragment getRuntimeSysroot() {
+ return runtimeSysroot;
+ }
+
+ /**
+ * Returns the default options to use for compiling C, C++, and assembler.
+ * This is just the options that should be used for all three languages.
+ * There may be additional C-specific or C++-specific options that should be used,
+ * in addition to the ones returned by this method;
+ */
+ public List<String> getCompilerOptions(Collection<String> features) {
+ return compilerFlags.evaluate(features);
+ }
+
+ /**
+ * Returns the list of additional C-specific options to use for compiling
+ * C. These should be go on the command line after the common options
+ * returned by {@link #getCompilerOptions}.
+ */
+ public List<String> getCOptions() {
+ return cOptions;
+ }
+
+ /**
+ * Returns the list of additional C++-specific options to use for compiling
+ * C++. These should be go on the command line after the common options
+ * returned by {@link #getCompilerOptions}.
+ */
+ public List<String> getCxxOptions(Collection<String> features) {
+ return cxxFlags.evaluate(features);
+ }
+
+ /**
+ * Returns the default list of options which cannot be filtered by BUILD
+ * rules. These should be appended to the command line after filtering.
+ */
+ public List<String> getUnfilteredCompilerOptions(Collection<String> features) {
+ return unfilteredCompilerFlags.evaluate(features);
+ }
+
+ /**
+ * Returns the set of command-line linker options, including any flags
+ * inferred from the command-line options.
+ *
+ * @see Link
+ */
+ // TODO(bazel-team): Clean up the linker options computation!
+ public List<String> getLinkOptions() {
+ return linkOptions;
+ }
+
+ /**
+ * Returns the immutable list of linker options for fully statically linked
+ * outputs. Does not include command-line options passed via --linkopt or
+ * --linkopts.
+ *
+ * @param features default settings affecting this link
+ * @param sharedLib true if the output is a shared lib, false if it's an executable
+ */
+ public List<String> getFullyStaticLinkOptions(Collection<String> features,
+ boolean sharedLib) {
+ if (sharedLib) {
+ return getSharedLibraryLinkOptions(mostlyStaticLinkFlags, features);
+ } else {
+ return fullyStaticLinkFlags.evaluate(features);
+ }
+ }
+
+ /**
+ * Returns the immutable list of linker options for mostly statically linked
+ * outputs. Does not include command-line options passed via --linkopt or
+ * --linkopts.
+ *
+ * @param features default settings affecting this link
+ * @param sharedLib true if the output is a shared lib, false if it's an executable
+ */
+ public List<String> getMostlyStaticLinkOptions(Collection<String> features,
+ boolean sharedLib) {
+ if (sharedLib) {
+ return getSharedLibraryLinkOptions(
+ supportsEmbeddedRuntimes ? mostlyStaticSharedLinkFlags : dynamicLinkFlags,
+ features);
+ } else {
+ return mostlyStaticLinkFlags.evaluate(features);
+ }
+ }
+
+ /**
+ * Returns the immutable list of linker options for artifacts that are not
+ * fully or mostly statically linked. Does not include command-line options
+ * passed via --linkopt or --linkopts.
+ *
+ * @param features default settings affecting this link
+ * @param sharedLib true if the output is a shared lib, false if it's an executable
+ */
+ public List<String> getDynamicLinkOptions(Collection<String> features,
+ boolean sharedLib) {
+ if (sharedLib) {
+ return getSharedLibraryLinkOptions(dynamicLinkFlags, features);
+ } else {
+ return dynamicLinkFlags.evaluate(features);
+ }
+ }
+
+ /**
+ * Returns link options for the specified flag list, combined with universal options
+ * for all shared libraries (regardless of link staticness).
+ */
+ private List<String> getSharedLibraryLinkOptions(FlagList flags,
+ Collection<String> features) {
+ return ImmutableList.<String>builder()
+ .addAll(flags.evaluate(features))
+ .addAll(dynamicLibraryLinkFlags.evaluate(features))
+ .build();
+ }
+
+ /**
+ * Returns test-only link options such that certain test-specific features can be configured
+ * separately (e.g. lazy binding).
+ */
+ public List<String> getTestOnlyLinkOptions() {
+ return testOnlyLinkFlags;
+ }
+
+
+ /**
+ * Returns the list of options to be used with 'objcopy' when converting
+ * binary files to object files, or {@code null} if this operation is not
+ * supported.
+ */
+ public List<String> getObjCopyOptionsForEmbedding() {
+ return objcopyOptions;
+ }
+
+ /**
+ * Returns the list of options to be used with 'ld' when converting
+ * binary files to object files, or {@code null} if this operation is not
+ * supported.
+ */
+ public List<String> getLdOptionsForEmbedding() {
+ return ldOptions;
+ }
+
+ /**
+ * Returns a map of additional make variables for use by {@link
+ * BuildConfiguration}. These are to used to allow some build rules to
+ * avoid the limits on stack frame sizes and variable-length arrays.
+ *
+ * <p>The returned map must contain an entry for {@code STACK_FRAME_UNLIMITED},
+ * though the entry may be an empty string.
+ */
+ @VisibleForTesting
+ public Map<String, String> getAdditionalMakeVariables() {
+ return additionalMakeVariables;
+ }
+
+ /**
+ * Returns the execution path to the linker binary to use for this build.
+ * Relative paths are relative to the execution root.
+ */
+ public PathFragment getLdExecutable() {
+ return ldExecutable;
+ }
+
+ /**
+ * Returns the dynamic linking mode (full, off, or default).
+ */
+ public DynamicMode getDynamicMode() {
+ return dynamicMode;
+ }
+
+ /*
+ * If true then the directory name for non-LIPO targets will have a '-lipodata' suffix in
+ * AutoFDO mode.
+ */
+ public boolean getAutoFdoLipoData() {
+ return cppOptions.autoFdoLipoData;
+ }
+
+ /**
+ * Returns the STL label if given on the command line. {@code null}
+ * otherwise.
+ */
+ public Label getStl() {
+ return cppOptions.stl;
+ }
+
+ /*
+ * Returns the command-line "Make" variable overrides.
+ */
+ @Override
+ public ImmutableMap<String, String> getCommandLineDefines() {
+ return commandLineDefines;
+ }
+
+ /**
+ * Returns the command-line override value for the specified "Make" variable
+ * for this configuration, or null if none.
+ */
+ public String getMakeVariableOverride(String var) {
+ return commandLineDefines.get(var);
+ }
+
+ public boolean shouldScanIncludes() {
+ return cppOptions.scanIncludes;
+ }
+
+ /**
+ * Returns the currently active LIPO compilation mode.
+ */
+ public LipoMode getLipoMode() {
+ return cppOptions.lipoMode;
+ }
+
+ public boolean isFdo() {
+ return cppOptions.isFdo();
+ }
+
+ public boolean isLipoOptimization() {
+ // The LIPO optimization bits are set in the LIPO context collector configuration, too.
+ return cppOptions.isLipoOptimization() && !isLipoContextCollector();
+ }
+
+ public boolean isLipoOptimizationOrInstrumentation() {
+ return cppOptions.isLipoOptimizationOrInstrumentation();
+ }
+
+ /**
+ * Returns true if it is AutoFDO LIPO build.
+ */
+ public boolean isAutoFdoLipo() {
+ return cppOptions.fdoOptimize != null && FdoSupport.isAutoFdo(cppOptions.fdoOptimize)
+ && getLipoMode() != LipoMode.OFF;
+ }
+
+ /**
+ * Returns the default header check mode.
+ */
+ public HeadersCheckingMode getHeadersCheckingMode() {
+ return cppOptions.headersCheckingMode;
+ }
+
+ /**
+ * Returns whether or not to strip the binaries.
+ */
+ public boolean shouldStripBinaries() {
+ return stripBinaries;
+ }
+
+ /**
+ * Returns the additional options to pass to strip when generating a
+ * {@code <name>.stripped} binary by this build.
+ */
+ public List<String> getStripOpts() {
+ return cppOptions.stripoptList;
+ }
+
+ /**
+ * Returns whether temporary outputs from gcc will be saved.
+ */
+ public boolean getSaveTemps() {
+ return cppOptions.saveTemps;
+ }
+
+ /**
+ * Returns the {@link PerLabelOptions} to apply to the gcc command line, if
+ * the label of the compiled file matches the regular expression.
+ */
+ public List<PerLabelOptions> getPerFileCopts() {
+ return cppOptions.perFileCopts;
+ }
+
+ public Label getLipoContextLabel() {
+ return cppOptions.getLipoContextLabel();
+ }
+
+ /**
+ * Returns the custom malloc library label.
+ */
+ public Label customMalloc() {
+ return cppOptions.customMalloc;
+ }
+
+ /**
+ * Returns the extra warnings enabled for C compilation.
+ */
+ public List<String> getCWarns() {
+ return cppOptions.cWarns;
+ }
+
+ /**
+ * Returns true if mostly-static C++ binaries should be skipped.
+ */
+ public boolean skipStaticOutputs() {
+ return cppOptions.skipStaticOutputs;
+ }
+
+ /**
+ * Returns true if Fission is specified for this build and supported by the crosstool.
+ */
+ public boolean useFission() {
+ return cppOptions.fissionModes.contains(compilationMode) && supportsFission();
+ }
+
+ /**
+ * Returns true if all C++ compilations should produce position-independent code, links should
+ * produce position-independent executables, and dependencies with equivalent pre-built pic and
+ * nopic versions should apply the pic versions. Returns false if default settings should be
+ * applied (i.e. make no special provisions for pic code).
+ */
+ public boolean forcePic() {
+ return cppOptions.forcePic;
+ }
+
+ public boolean useStartEndLib() {
+ return cppOptions.useStartEndLib && supportsStartEndLib();
+ }
+
+ public boolean useThinArchives() {
+ return cppOptions.useThinArchives && supportsThinArchives();
+ }
+
+ /**
+ * Returns true if interface shared objects should be used.
+ */
+ public boolean useInterfaceSharedObjects() {
+ return supportsInterfaceSharedObjects() && cppOptions.useInterfaceSharedObjects;
+ }
+
+ public boolean forceIgnoreDashStatic() {
+ return cppOptions.forceIgnoreDashStatic;
+ }
+
+ /**
+ * Returns true iff this build configuration requires inclusion extraction
+ * (for include scanning) in the action graph.
+ */
+ public boolean needsIncludeScanning() {
+ return cppOptions.extractInclusions;
+ }
+
+ public boolean createCppModuleMaps() {
+ return cppOptions.cppModuleMaps;
+ }
+
+ /**
+ * Returns true if shared libraries must be compiled with position independent code
+ * on this platform or in this configuration.
+ */
+ public boolean needsPic() {
+ return forcePic() || toolchainNeedsPic();
+ }
+
+ /**
+ * Returns true iff we should use ".pic.o" files when linking executables.
+ */
+ public boolean usePicObjectsForBinaries() {
+ return forcePic() || usePicForBinaries();
+ }
+
+ public boolean legacyWholeArchive() {
+ return cppOptions.legacyWholeArchive;
+ }
+
+ public boolean getSymbolCounts() {
+ return cppOptions.symbolCounts;
+ }
+
+ public boolean getInmemoryDotdFiles() {
+ return cppOptions.inmemoryDotdFiles;
+ }
+
+ public boolean useIsystemForIncludes() {
+ return cppOptions.useIsystemForIncludes;
+ }
+
+ public LibcTop getLibcTop() {
+ return cppOptions.libcTop;
+ }
+
+ public boolean getUseInterfaceSharedObjects() {
+ return cppOptions.useInterfaceSharedObjects;
+ }
+
+ /**
+ * Returns the FDO support object.
+ */
+ public FdoSupport getFdoSupport() {
+ return fdoSupport;
+ }
+
+ /**
+ * Return the name of the directory (relative to the bin directory) that
+ * holds mangled links to shared libraries. This name is always set to
+ * the '{@code _solib_<cpu_archictecture_name>}.
+ */
+ public String getSolibDirectory() {
+ return solibDirectory;
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'objcopy' binary to use for this
+ * build. (Corresponds to $(OBJCOPY) in make-dbg.) Relative paths are
+ * relative to the execution root.
+ */
+ public PathFragment getObjCopyExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.OBJCOPY);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'gcc' binary that should be used
+ * by this build. This binary should support compilation of both C (*.c)
+ * and C++ (*.cc) files. Relative paths are relative to the execution root.
+ */
+ public PathFragment getCppExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.GCC);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'g++' binary that should be used
+ * by this build. This binary should support linking of both C (*.c)
+ * and C++ (*.cc) files. Relative paths are relative to the execution root.
+ */
+ public PathFragment getCppLinkExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.GCC);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'cpp' binary that should be used
+ * by this build. Relative paths are relative to the execution root.
+ */
+ public PathFragment getCpreprocessorExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.CPP);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'gcov' binary that should be used
+ * by this build to analyze C++ coverage data. Relative paths are relative to
+ * the execution root.
+ */
+ public PathFragment getGcovExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.GCOV);
+ }
+
+ /**
+ * Returns the path to the 'gcov-tool' executable that should be used
+ * by this build. Relative paths are relative to the execution root.
+ */
+ public PathFragment getGcovToolExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.GCOVTOOL);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'nm' executable that should be used
+ * by this build. Used only for testing. Relative paths are relative to the
+ * execution root.
+ */
+ public PathFragment getNmExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.NM);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'objdump' executable that should be
+ * used by this build. Used only for testing. Relative paths are relative to
+ * the execution root.
+ */
+ public PathFragment getObjdumpExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.OBJDUMP);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'ar' binary to use for this build.
+ * Relative paths are relative to the execution root.
+ */
+ public PathFragment getArExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.AR);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'strip' executable that should be used
+ * by this build. Relative paths are relative to the execution root.
+ */
+ public PathFragment getStripExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.STRIP);
+ }
+
+ /**
+ * Returns the path to the GNU binutils 'dwp' binary that should be used by this
+ * build to combine debug info output from individual C++ compilations (i.e. .dwo
+ * files) into aggregate target-level debug packages. Relative paths are relative to the
+ * execution root. See https://gcc.gnu.org/wiki/DebugFission .
+ */
+ public PathFragment getDwpExecutable() {
+ return getToolPathFragment(CppConfiguration.Tool.DWP);
+ }
+
+ /**
+ * Returns the GNU System Name
+ */
+ public String getTargetGnuSystemName() {
+ return targetSystemName;
+ }
+
+ /**
+ * Returns the architecture component of the GNU System Name
+ */
+ public String getGnuSystemArch() {
+ if (targetSystemName.indexOf('-') == -1) {
+ return targetSystemName;
+ }
+ return targetSystemName.substring(0, targetSystemName.indexOf('-'));
+ }
+
+ /**
+ * Returns whether the configuration's purpose is only to collect LIPO-related data.
+ */
+ public boolean isLipoContextCollector() {
+ return lipoContextCollector;
+ }
+
+ @Override
+ public String getName() {
+ return "cpp";
+ }
+
+ @Override
+ public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) {
+ CppOptions cppOptions = buildOptions.get(CppOptions.class);
+ if (stripBinaries) {
+ boolean warn = cppOptions.coptList.contains("-g");
+ for (PerLabelOptions opt : cppOptions.perFileCopts) {
+ warn |= opt.getOptions().contains("-g");
+ }
+ if (warn) {
+ reporter.handle(Event.warn("Stripping enabled, but '--copt=-g' (or --per_file_copt=...@-g) "
+ + "specified. Debug information will be generated and then stripped away. This is "
+ + "probably not what you want! Use '-c dbg' for debug mode, or use '--strip=never' "
+ + "to disable stripping"));
+ }
+ }
+
+ if (cppOptions.fdoInstrument != null && cppOptions.fdoOptimize != null) {
+ reporter.handle(Event.error("Cannot instrument and optimize for FDO at the same time. "
+ + "Remove one of the '--fdo_instrument' and '--fdo_optimize' options"));
+ }
+
+ if (cppOptions.lipoContext != null) {
+ if (cppOptions.lipoMode != LipoMode.BINARY || cppOptions.fdoOptimize == null) {
+ reporter.handle(Event.warn("The --lipo_context option can only be used together with "
+ + "--fdo_optimize=<profile zip> and --lipo=binary. LIPO context will be ignored."));
+ }
+ } else {
+ if (cppOptions.lipoMode == LipoMode.BINARY && cppOptions.fdoOptimize != null) {
+ reporter.handle(Event.error("The --lipo_context option must be specified when using "
+ + "--fdo_optimize=<profile zip> and --lipo=binary"));
+ }
+ }
+ if (cppOptions.lipoMode == LipoMode.BINARY &&
+ compilationMode != CompilationMode.OPT) {
+ reporter.handle(Event.error(
+ "'--lipo=binary' can only be used with '--compilation_mode=opt' (or '-c opt')"));
+ }
+
+ if (cppOptions.fissionModes.contains(compilationMode) && !supportsFission()) {
+ reporter.handle(
+ Event.warn("Fission is not supported by this crosstool. Please use a supporting " +
+ "crosstool to enable fission"));
+ }
+ }
+
+ @Override
+ public void addGlobalMakeVariables(Builder<String, String> globalMakeEnvBuilder) {
+ // hardcoded CC->gcc setting for unit tests
+ globalMakeEnvBuilder.put("CC", getCppExecutable().getPathString());
+
+ // Make variables provided by crosstool/gcc compiler suite.
+ globalMakeEnvBuilder.put("AR", getArExecutable().getPathString());
+ globalMakeEnvBuilder.put("NM", getNmExecutable().getPathString());
+ globalMakeEnvBuilder.put("OBJCOPY", getObjCopyExecutable().getPathString());
+ globalMakeEnvBuilder.put("STRIP", getStripExecutable().getPathString());
+
+ PathFragment gcovtool = getGcovToolExecutable();
+ if (gcovtool != null) {
+ // gcov-tool is optional in Crosstool
+ globalMakeEnvBuilder.put("GCOVTOOL", gcovtool.getPathString());
+ }
+
+ if (getTargetLibc().startsWith("glibc-")) {
+ globalMakeEnvBuilder.put("GLIBC_VERSION",
+ getTargetLibc().substring("glibc-".length()));
+ } else {
+ globalMakeEnvBuilder.put("GLIBC_VERSION", getTargetLibc());
+ }
+
+ globalMakeEnvBuilder.put("C_COMPILER", getCompiler());
+ globalMakeEnvBuilder.put("TARGET_CPU", getTargetCpu());
+
+ // Deprecated variables
+
+ // TODO(bazel-team): (2009) These variables are so rarely used we should try to eliminate
+ // them entirely. see: "cs -f=BUILD -noi GNU_TARGET" and "cs -f=build_defs -noi
+ // GNU_TARGET"
+ globalMakeEnvBuilder.put("CROSSTOOLTOP", crosstoolTopPathFragment.getPathString());
+ globalMakeEnvBuilder.put("GLIBC", getTargetLibc());
+ globalMakeEnvBuilder.put("GNU_TARGET", targetSystemName);
+
+ globalMakeEnvBuilder.putAll(getAdditionalMakeVariables());
+
+ globalMakeEnvBuilder.put("ABI_GLIBC_VERSION", getAbiGlibcVersion());
+ globalMakeEnvBuilder.put("ABI", abi);
+ }
+
+ @Override
+ public void addImplicitLabels(Multimap<String, Label> implicitLabels) {
+ if (getLibcLabel() != null) {
+ implicitLabels.put("crosstool", getLibcLabel());
+ }
+
+ implicitLabels.put("crosstool", crosstoolTop);
+ }
+
+ @Override
+ public void prepareHook(Path execRoot, ArtifactFactory artifactFactory, PathFragment genfilesPath,
+ PackageRootResolver resolver) throws ViewCreationFailedException {
+ try {
+ getFdoSupport().prepareToBuild(execRoot, genfilesPath, artifactFactory, resolver);
+ } catch (ZipException e) {
+ throw new ViewCreationFailedException("Error reading provided FDO zip file", e);
+ } catch (FdoException | IOException e) {
+ throw new ViewCreationFailedException("Error while initializing FDO support", e);
+ }
+ }
+
+ @Override
+ public void declareSkyframeDependencies(Environment env) {
+ getFdoSupport().declareSkyframeDependencies(env, execRoot);
+ }
+
+ @Override
+ public void addRoots(List<Root> roots) {
+ // Fdo root can only exist for the target configuration.
+ FdoSupport fdoSupport = getFdoSupport();
+ if (fdoSupport.getFdoRoot() != null) {
+ roots.add(fdoSupport.getFdoRoot());
+ }
+
+ // Grepped header includes; this root is not configuration specific.
+ roots.add(getGreppedIncludesDirectory());
+ }
+
+ @Override
+ public Map<String, String> getCoverageEnvironment() {
+ ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
+ env.put("COVERAGE_GCOV_PATH", getGcovExecutable().getPathString());
+ PathFragment fdoInstrument = getFdoSupport().getFdoInstrument();
+ if (fdoInstrument != null) {
+ env.put("FDO_DIR", fdoInstrument.getPathString());
+ }
+ return env.build();
+ }
+
+ @Override
+ public ImmutableList<Label> getCoverageLabels() {
+ // TODO(bazel-team): Using a gcov-specific crosstool filegroup here could reduce the number of
+ // inputs significantly. We'd also need to add logic in tools/coverage/collect_coverage.sh to
+ // drop crosstool dependency if metadataFiles does not contain *.gcno artifacts.
+ return ImmutableList.of(crosstoolTop);
+ }
+
+ @Override
+ public String getOutputDirectoryName() {
+ String lipoSuffix;
+ if (getLipoMode() != LipoMode.OFF && !isAutoFdoLipo()) {
+ lipoSuffix = "-lipo";
+ } else if (getAutoFdoLipoData()) {
+ lipoSuffix = "-lipodata";
+ } else {
+ lipoSuffix = "";
+ }
+ return toolchainIdentifier + lipoSuffix;
+ }
+
+ @Override
+ public String getConfigurationNameSuffix() {
+ return isLipoContextCollector() ? "collector" : null;
+ }
+
+ @Override
+ public String getPlatformName() {
+ return getToolchainIdentifier();
+ }
+
+ @Override
+ public boolean supportsIncrementalBuild() {
+ return !isLipoOptimization();
+ }
+
+ @Override
+ public boolean performsStaticLink() {
+ return getLinkOptions().contains("-static");
+ }
+
+ /**
+ * Returns true if we should share identical native libraries between different targets.
+ */
+ public boolean shareNativeDeps() {
+ return cppOptions.shareNativeDeps;
+ }
+
+ @Override
+ public void prepareForExecutionPhase() throws IOException {
+ // _fdo has a prefix of "_", but it should nevertheless be deleted. Detailed description
+ // of the structure of the symlinks / directories can be found at FdoSupport.extractFdoZip().
+ // We actually create a directory named "blaze-fdo" under the exec root, the previous version
+ // of which is deleted in FdoSupport.prepareToBuildExec(). We cannot do that just before the
+ // execution phase because that needs to happen before the analysis phase (in order to create
+ // the artifacts corresponding to the .gcda files).
+ Path tempPath = execRoot.getRelative("_fdo");
+ if (tempPath.exists()) {
+ FileSystemUtils.deleteTree(tempPath);
+ }
+ }
+
+ @Override
+ public Map<String, Object> lateBoundOptionDefaults() {
+ // --cpu defaults to null. With that default, the actual target cpu string gets picked up
+ // by the "default_target_cpu" crosstool parameter.
+ return ImmutableMap.<String, Object>of("cpu", getTargetCpu());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java
new file mode 100644
index 0000000000..de20283419
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java
@@ -0,0 +1,174 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.RedirectChaser;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
+
+import javax.annotation.Nullable;
+
+/**
+ * Loader for C++ configurations.
+ */
+public class CppConfigurationLoader implements ConfigurationFragmentFactory {
+ @Override
+ public Class<? extends Fragment> creates() {
+ return CppConfiguration.class;
+ }
+
+ private final Function<String, String> cpuTransformer;
+
+ /**
+ * Creates a new CrosstoolConfigurationLoader instance with the given
+ * configuration provider. The configuration provider is used to perform
+ * caller-specific configuration file lookup.
+ */
+ public CppConfigurationLoader(Function<String, String> cpuTransformer) {
+ this.cpuTransformer = cpuTransformer;
+ }
+
+ @Override
+ public CppConfiguration create(ConfigurationEnvironment env, BuildOptions options)
+ throws InvalidConfigurationException {
+ CppConfigurationParameters params = createParameters(env, options);
+ if (params == null) {
+ return null;
+ }
+ return new CppConfiguration(params);
+ }
+
+ /**
+ * Value class for all the data needed to create a {@link CppConfiguration}.
+ */
+ public static class CppConfigurationParameters {
+ protected final CrosstoolConfig.CToolchain toolchain;
+ protected final String cacheKeySuffix;
+ protected final BuildOptions buildOptions;
+ protected final Label crosstoolTop;
+ protected final Label ccToolchainLabel;
+ protected final Path fdoZip;
+ protected final Path execRoot;
+
+ CppConfigurationParameters(CrosstoolConfig.CToolchain toolchain,
+ String cacheKeySuffix,
+ BuildOptions buildOptions,
+ Path fdoZip,
+ Path execRoot,
+ Label crosstoolTop,
+ Label ccToolchainLabel) {
+ this.toolchain = toolchain;
+ this.cacheKeySuffix = cacheKeySuffix;
+ this.buildOptions = buildOptions;
+ this.fdoZip = fdoZip;
+ this.execRoot = execRoot;
+ this.crosstoolTop = crosstoolTop;
+ this.ccToolchainLabel = ccToolchainLabel;
+ }
+ }
+
+ @Nullable
+ protected CppConfigurationParameters createParameters(
+ ConfigurationEnvironment env, BuildOptions options) throws InvalidConfigurationException {
+ BlazeDirectories directories = env.getBlazeDirectories();
+ if (directories == null) {
+ return null;
+ }
+ Label crosstoolTop = RedirectChaser.followRedirects(env,
+ options.get(CppOptions.class).crosstoolTop, "crosstool_top");
+ if (crosstoolTop == null) {
+ return null;
+ }
+ CrosstoolConfigurationLoader.CrosstoolFile file =
+ CrosstoolConfigurationLoader.readCrosstool(env, crosstoolTop);
+ if (file == null) {
+ return null;
+ }
+ CrosstoolConfig.CToolchain toolchain =
+ CrosstoolConfigurationLoader.selectToolchain(file.getProto(), options, cpuTransformer);
+
+ // FDO
+ // TODO(bazel-team): move this to CppConfiguration.prepareHook
+ CppOptions cppOptions = options.get(CppOptions.class);
+ Path fdoZip;
+ if (cppOptions.fdoOptimize == null) {
+ fdoZip = null;
+ } else if (cppOptions.fdoOptimize.startsWith("//")) {
+ try {
+ Target target = env.getTarget(Label.parseAbsolute(cppOptions.fdoOptimize));
+ if (target == null) {
+ return null;
+ }
+ if (!(target instanceof InputFile)) {
+ throw new InvalidConfigurationException(
+ "--fdo_optimize cannot accept targets that do not refer to input files");
+ }
+ fdoZip = env.getPath(target.getPackage(), target.getName());
+ if (fdoZip == null) {
+ throw new InvalidConfigurationException(
+ "The --fdo_optimize parameter you specified resolves to a file that does not exist");
+ }
+ } catch (NoSuchPackageException | NoSuchTargetException | SyntaxException e) {
+ throw new InvalidConfigurationException(e);
+ }
+ } else {
+ fdoZip = directories.getWorkspace().getRelative(cppOptions.fdoOptimize);
+ }
+
+ Label ccToolchainLabel;
+ try {
+ ccToolchainLabel = crosstoolTop.getRelative("cc-compiler-" + toolchain.getTargetCpu());
+ } catch (Label.SyntaxException e) {
+ throw new InvalidConfigurationException(String.format(
+ "'%s' is not a valid CPU. It should only consist of characters valid in labels",
+ toolchain.getTargetCpu()));
+ }
+
+ Target ccToolchain;
+ try {
+ ccToolchain = env.getTarget(ccToolchainLabel);
+ if (ccToolchain == null) {
+ return null;
+ }
+ } catch (NoSuchThingException e) {
+ throw new InvalidConfigurationException(String.format(
+ "The toolchain rule '%s' does not exist", ccToolchainLabel));
+ }
+
+ if (!(ccToolchain instanceof Rule)
+ || !((Rule) ccToolchain).getRuleClass().equals("cc_toolchain")) {
+ throw new InvalidConfigurationException(String.format(
+ "The label '%s' is not a cc_toolchain rule", ccToolchainLabel));
+ }
+
+ return new CppConfigurationParameters(toolchain, file.getMd5(), options,
+ fdoZip, directories.getExecRoot(), crosstoolTop, ccToolchainLabel);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugFileProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugFileProvider.java
new file mode 100644
index 0000000000..c0fcb11330
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugFileProvider.java
@@ -0,0 +1,54 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A target that provides .dwo files which can be combined into a .dwp packaging step. See
+ * https://gcc.gnu.org/wiki/DebugFission for details.
+ */
+@Immutable
+public final class CppDebugFileProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> transitiveDwoFiles;
+ private final NestedSet<Artifact> transitivePicDwoFiles;
+
+ public CppDebugFileProvider(NestedSet<Artifact> transitiveDwoFiles,
+ NestedSet<Artifact> transitivePicDwoFiles) {
+ this.transitiveDwoFiles = transitiveDwoFiles;
+ this.transitivePicDwoFiles = transitivePicDwoFiles;
+ }
+
+ /**
+ * Returns the .dwo files that should be included in this target's .dwp packaging (if this
+ * target is linked) or passed through to a dependant's .dwp packaging (e.g. if this is a
+ * cc_library depended on by a statically linked cc_binary).
+ *
+ * Assumes the corresponding link consumes .o files (vs. .pic.o files).
+ */
+ public NestedSet<Artifact> getTransitiveDwoFiles() {
+ return transitiveDwoFiles;
+ }
+
+ /**
+ * Same as above, but assumes the corresponding link consumes pic.o files.
+ */
+ public NestedSet<Artifact> getTransitivePicDwoFiles() {
+ return transitivePicDwoFiles;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugPackageProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugPackageProvider.java
new file mode 100644
index 0000000000..864a4d5a11
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppDebugPackageProvider.java
@@ -0,0 +1,69 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import javax.annotation.Nullable;
+
+/**
+ * Provides the binary artifact and its associated .dwp files, if fission is enabled.
+ * If Fission ({@link https://gcc.gnu.org/wiki/DebugFission}) is not enabled, the
+ * dwp file will be null.
+ */
+@Immutable
+public final class CppDebugPackageProvider implements TransitiveInfoProvider {
+
+ private final Artifact strippedArtifact;
+ private final Artifact unstrippedArtifact;
+ @Nullable private final Artifact dwpArtifact;
+
+ public CppDebugPackageProvider(
+ Artifact strippedArtifact,
+ Artifact unstrippedArtifact,
+ @Nullable Artifact dwpArtifact) {
+ Preconditions.checkNotNull(strippedArtifact);
+ Preconditions.checkNotNull(unstrippedArtifact);
+ this.strippedArtifact = strippedArtifact;
+ this.unstrippedArtifact = unstrippedArtifact;
+ this.dwpArtifact = dwpArtifact;
+ }
+
+ /**
+ * Returns the stripped file (the explicit ".stripped" target).
+ */
+ public final Artifact getStrippedArtifact() {
+ return strippedArtifact;
+ }
+
+ /**
+ * Returns the unstripped file (the default executable target).
+ */
+ public final Artifact getUnstrippedArtifact() {
+ return unstrippedArtifact;
+ }
+
+ /**
+ * Returns the .dwp file (for fission builds) or null if --fission=no.
+ */
+ @Nullable
+ public final Artifact getDwpArtifact() {
+ return dwpArtifact;
+ }
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java
new file mode 100644
index 0000000000..d9bb7b6803
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java
@@ -0,0 +1,141 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.util.FileType;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * C++-related file type definitions.
+ */
+public final class CppFileTypes {
+ public static final FileType CPP_SOURCE = FileType.of(".cc", ".cpp", ".cxx", ".C");
+ public static final FileType C_SOURCE = FileType.of(".c");
+ public static final FileType CPP_HEADER = FileType.of(".h", ".hh", ".hpp", ".hxx", ".inc");
+ public static final FileType CPP_TEXTUAL_INCLUDE = FileType.of(".inc");
+
+ public static final FileType PIC_PREPROCESSED_C = FileType.of(".pic.i");
+ public static final FileType PREPROCESSED_C = new FileType() {
+ final String ext = ".i";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !PIC_PREPROCESSED_C.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+ public static final FileType PIC_PREPROCESSED_CPP = FileType.of(".pic.ii");
+ public static final FileType PREPROCESSED_CPP = new FileType() {
+ final String ext = ".ii";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !PIC_PREPROCESSED_CPP.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+
+ public static final FileType ASSEMBLER_WITH_C_PREPROCESSOR = FileType.of(".S");
+ public static final FileType PIC_ASSEMBLER = FileType.of(".pic.s");
+ public static final FileType ASSEMBLER = new FileType() {
+ final String ext = ".s";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !PIC_ASSEMBLER.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+
+ public static final FileType PIC_ARCHIVE = FileType.of(".pic.a");
+ public static final FileType ARCHIVE = new FileType() {
+ final String ext = ".a";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !PIC_ARCHIVE.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+
+ public static final FileType ALWAYS_LINK_PIC_LIBRARY = FileType.of(".pic.lo");
+ public static final FileType ALWAYS_LINK_LIBRARY = new FileType() {
+ final String ext = ".lo";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !ALWAYS_LINK_PIC_LIBRARY.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+
+ public static final FileType PIC_OBJECT_FILE = FileType.of(".pic.o");
+ public static final FileType OBJECT_FILE = new FileType() {
+ final String ext = ".o";
+ @Override
+ public boolean apply(String filename) {
+ return filename.endsWith(ext) && !PIC_OBJECT_FILE.matches(filename);
+ }
+ @Override
+ public List<String> getExtensions() {
+ return ImmutableList.of(ext);
+ }
+ };
+
+
+ public static final FileType SHARED_LIBRARY = FileType.of(".so");
+ public static final FileType INTERFACE_SHARED_LIBRARY = FileType.of(".ifso");
+ public static final FileType LINKER_SCRIPT = FileType.of(".lds");
+ // Matches shared libraries with version names in the extension, i.e.
+ // libmylib.so.2 or libmylib.so.2.10.
+ private static final Pattern VERSIONED_SHARED_LIBRARY_PATTERN =
+ Pattern.compile("^.+\\.so(\\.\\d+)+$");
+ public static final FileType VERSIONED_SHARED_LIBRARY = new FileType() {
+ @Override
+ public boolean apply(String filename) {
+ // Because regex matching can be slow, we first do a quick digit check on the final
+ // character before risking the full-on regex match. This should eliminate the performance
+ // hit on practically every non-qualifying file type.
+ if (Character.isDigit(filename.charAt(filename.length() - 1))) {
+ return VERSIONED_SHARED_LIBRARY_PATTERN.matcher(filename).matches();
+ } else {
+ return false;
+ }
+ }
+ };
+
+ public static final FileType COVERAGE_NOTES = FileType.of(".gcno");
+ public static final FileType COVERAGE_DATA = FileType.of(".gcda");
+ public static final FileType COVERAGE_DATA_IMPORTS = FileType.of(".gcda.imports");
+ public static final FileType GCC_AUTO_PROFILE = FileType.of(".afdo");
+
+ public static final FileType CPP_MODULE_MAP = FileType.of(".cppmap");
+ public static final FileType CPP_MODULE = FileType.of(".pcm");
+
+ // Output of the dwp tool
+ public static final FileType DEBUG_INFO_PACKAGE = FileType.of(".dwp");
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
new file mode 100644
index 0000000000..5bc1363072
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
@@ -0,0 +1,529 @@
+// Copyright 2014 Google Inc. 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.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.actions.Root;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.Util;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Linkstamp;
+import com.google.devtools.build.lib.rules.cpp.CppCompilationContext.Builder;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.shell.ShellUtils;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.IncludeScanningUtil;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Helper class for functionality shared by cpp related rules.
+ *
+ * <p>This class can be used only after the loading phase.
+ */
+public class CppHelper {
+ // TODO(bazel-team): should this use Link.SHARED_LIBRARY_FILETYPES?
+ public static final FileTypeSet SHARED_LIBRARY_FILETYPES = FileTypeSet.of(
+ CppFileTypes.SHARED_LIBRARY,
+ CppFileTypes.VERSIONED_SHARED_LIBRARY);
+
+ private static final FileTypeSet CPP_FILETYPES = FileTypeSet.of(
+ CppFileTypes.CPP_HEADER,
+ CppFileTypes.CPP_SOURCE);
+
+ private CppHelper() {
+ // prevents construction
+ }
+
+ /**
+ * Merges the STL and toolchain contexts into context builder. The STL is automatically determined
+ * using the ":stl" attribute.
+ */
+ public static void mergeToolchainDependentContext(RuleContext ruleContext,
+ Builder contextBuilder) {
+ TransitiveInfoCollection stl = ruleContext.getPrerequisite(":stl", Mode.TARGET);
+ if (stl != null) {
+ // TODO(bazel-team): Clean this up.
+ contextBuilder.addSystemIncludeDir(stl.getLabel().getPackageFragment().getRelative("gcc3"));
+ contextBuilder.mergeDependentContext(stl.getProvider(CppCompilationContext.class));
+ }
+ CcToolchainProvider toolchain = getToolchain(ruleContext);
+ if (toolchain != null) {
+ contextBuilder.mergeDependentContext(toolchain.getCppCompilationContext());
+ }
+ }
+
+ /**
+ * Returns the malloc implementation for the given target.
+ */
+ public static TransitiveInfoCollection mallocForTarget(RuleContext ruleContext) {
+ if (ruleContext.getFragment(CppConfiguration.class).customMalloc() != null) {
+ return ruleContext.getPrerequisite(":default_malloc", Mode.TARGET);
+ } else {
+ return ruleContext.getPrerequisite("malloc", Mode.TARGET);
+ }
+ }
+
+ /**
+ * Expands Make variables in a list of string and tokenizes the result. If the package feature
+ * no_copts_tokenization is set, tokenize only items consisting of a single make variable.
+ *
+ * @param ruleContext the ruleContext to be used as the context of Make variable expansion
+ * @param attributeName the name of the attribute to use in error reporting
+ * @param input the list of strings to expand
+ * @return a list of strings containing the expanded and tokenized values for the
+ * attribute
+ */
+ // TODO(bazel-team): Move to CcCommon; refactor CcPlugin to use either CcLibraryHelper or
+ // CcCommon.
+ static List<String> expandMakeVariables(
+ RuleContext ruleContext, String attributeName, List<String> input) {
+ boolean tokenization =
+ !ruleContext.getFeatures().contains("no_copts_tokenization");
+
+ List<String> tokens = new ArrayList<>();
+ for (String token : input) {
+ try {
+ // Legacy behavior: tokenize all items.
+ if (tokenization) {
+ ruleContext.tokenizeAndExpandMakeVars(tokens, attributeName, token);
+ } else {
+ String exp = ruleContext.expandSingleMakeVariable(attributeName, token);
+ if (exp != null) {
+ ShellUtils.tokenize(tokens, exp);
+ } else {
+ tokens.add(ruleContext.expandMakeVariables(attributeName, token));
+ }
+ }
+ } catch (ShellUtils.TokenizationException e) {
+ ruleContext.attributeError(attributeName, e.getMessage());
+ }
+ }
+ return ImmutableList.copyOf(tokens);
+ }
+
+ /**
+ * Appends the tokenized values of the copts attribute to copts.
+ */
+ public static ImmutableList<String> getAttributeCopts(RuleContext ruleContext, String attr) {
+ Preconditions.checkArgument(ruleContext.getRule().isAttrDefined(attr, Type.STRING_LIST));
+ List<String> unexpanded = ruleContext.attributes().get(attr, Type.STRING_LIST);
+
+ return ImmutableList.copyOf(expandMakeVariables(ruleContext, attr, unexpanded));
+ }
+
+ /**
+ * Expands attribute value either using label expansion
+ * (if attemptLabelExpansion == {@code true} and it does not look like make
+ * variable or flag) or tokenizes and expands make variables.
+ */
+ public static void expandAttribute(RuleContext ruleContext,
+ List<String> values, String attrName, String attrValue, boolean attemptLabelExpansion) {
+ if (attemptLabelExpansion && CppHelper.isLinkoptLabel(attrValue)) {
+ if (!CppHelper.expandLabel(ruleContext, values, attrValue)) {
+ ruleContext.attributeError(attrName, "could not resolve label '" + attrValue + "'");
+ }
+ } else {
+ ruleContext.tokenizeAndExpandMakeVars(values, attrName, attrValue);
+ }
+ }
+
+ /**
+ * Determines if a linkopt can be a label. Linkopts come in 2 varieties:
+ * literals -- flags like -Xl and makefile vars like $(LD) -- and labels,
+ * which we should expand into filenames.
+ *
+ * @param linkopt the link option to test.
+ * @return true if the linkopt is not a flag (starting with "-") or a makefile
+ * variable (starting with "$");
+ */
+ private static boolean isLinkoptLabel(String linkopt) {
+ return !linkopt.startsWith("$") && !linkopt.startsWith("-");
+ }
+
+ /**
+ * Expands a label against the target's deps, adding the expanded path strings
+ * to the linkopts.
+ *
+ * @param linkopts the linkopts to add the expanded label to
+ * @param labelName the name of the label to expand
+ * @return true if the label was expanded successfully, false otherwise
+ */
+ private static boolean expandLabel(RuleContext ruleContext, List<String> linkopts,
+ String labelName) {
+ try {
+ Label label = ruleContext.getLabel().getRelative(labelName);
+ for (FileProvider target : ruleContext
+ .getPrerequisites("deps", Mode.TARGET, FileProvider.class)) {
+ if (target.getLabel().equals(label)) {
+ for (Artifact artifact : target.getFilesToBuild()) {
+ linkopts.add(artifact.getExecPathString());
+ }
+ return true;
+ }
+ }
+ } catch (SyntaxException e) {
+ // Quietly ignore and fall through.
+ }
+ linkopts.add(labelName);
+ return false;
+ }
+
+ /**
+ * This almost trivial method looks up the :cc_toolchain attribute on the rule context, makes sure
+ * that it refers to a rule that has a {@link CcToolchainProvider} (gives an error otherwise), and
+ * returns a reference to that {@link CcToolchainProvider}. The method only returns {@code null}
+ * if there is no such attribute (this is currently not an error).
+ */
+ @Nullable public static CcToolchainProvider getToolchain(RuleContext ruleContext) {
+ if (ruleContext.attributes().getAttributeDefinition(":cc_toolchain") == null) {
+ // TODO(bazel-team): Report an error or throw an exception in this case.
+ return null;
+ }
+ TransitiveInfoCollection dep = ruleContext.getPrerequisite(":cc_toolchain", Mode.TARGET);
+ return getToolchain(ruleContext, dep);
+ }
+
+ /**
+ * This almost trivial method makes sure that the given info collection has a {@link
+ * CcToolchainProvider} (gives an error otherwise), and returns a reference to that {@link
+ * CcToolchainProvider}. The method never returns {@code null}, even if there is no toolchain.
+ */
+ public static CcToolchainProvider getToolchain(RuleContext ruleContext,
+ TransitiveInfoCollection dep) {
+ // TODO(bazel-team): Consider checking this generally at the attribute level.
+ if ((dep == null) || (dep.getProvider(CcToolchainProvider.class) == null)) {
+ ruleContext.ruleError("The selected C++ toolchain is not a cc_toolchain rule");
+ return CcToolchainProvider.EMPTY_TOOLCHAIN_IS_ERROR;
+ }
+ return dep.getProvider(CcToolchainProvider.class);
+ }
+
+ /**
+ * Returns the directory where object files are created.
+ */
+ public static PathFragment getObjDirectory(Label ruleLabel) {
+ return AnalysisUtils.getUniqueDirectory(ruleLabel, new PathFragment("_objs"));
+ }
+
+ /**
+ * Creates a grep-includes ExtractInclusions action for generated sources/headers in the
+ * needsIncludeScanning() BuildConfiguration case. Returns a map from original header
+ * Artifact to the output Artifact of grepping over it. The return value only includes
+ * entries for generated sources or headers when --extract_generated_inclusions is enabled.
+ *
+ * <p>Previously, incremental rebuilds redid all include scanning work
+ * for a given .cc source in serial. For high-latency file systems, this could cause
+ * performance problems if many headers are generated.
+ */
+ @Nullable
+ public static final Map<Artifact, Artifact> createExtractInclusions(RuleContext ruleContext,
+ Iterable<Artifact> prerequisites) {
+ Map<Artifact, Artifact> extractions = new HashMap<>();
+ for (Artifact prerequisite : prerequisites) {
+ Artifact scanned = createExtractInclusions(ruleContext, prerequisite);
+ if (scanned != null) {
+ extractions.put(prerequisite, scanned);
+ }
+ }
+ return extractions;
+ }
+
+ /**
+ * Creates a grep-includes ExtractInclusions action for generated sources/headers in the
+ * needsIncludeScanning() BuildConfiguration case.
+ *
+ * <p>Previously, incremental rebuilds redid all include scanning work for a given
+ * .cc source in serial. For high-latency file systems, this could cause
+ * performance problems if many headers are generated.
+ */
+ private static final Artifact createExtractInclusions(RuleContext ruleContext,
+ Artifact prerequisite) {
+ if (ruleContext != null &&
+ ruleContext.getFragment(CppConfiguration.class).needsIncludeScanning() &&
+ !prerequisite.isSourceArtifact() &&
+ CPP_FILETYPES.matches(prerequisite.getFilename())) {
+ Artifact scanned = getIncludesOutput(ruleContext, prerequisite);
+ ruleContext.registerAction(
+ new ExtractInclusionAction(ruleContext.getActionOwner(), prerequisite, scanned));
+ return scanned;
+ }
+ return null;
+ }
+
+ private static Artifact getIncludesOutput(RuleContext ruleContext, Artifact src) {
+ Root root = ruleContext.getFragment(CppConfiguration.class).getGreppedIncludesDirectory();
+ PathFragment relOut = IncludeScanningUtil.getRootRelativeOutputPath(src.getExecPath());
+ return ruleContext.getAnalysisEnvironment().getDerivedArtifact(relOut, root);
+ }
+
+ /**
+ * Returns the workspace-relative filename for the linked artifact.
+ */
+ public static PathFragment getLinkedFilename(RuleContext ruleContext,
+ LinkTargetType linkType) {
+ PathFragment relativePath = Util.getWorkspaceRelativePath(ruleContext.getTarget());
+ PathFragment linkedFileName = (linkType == LinkTargetType.EXECUTABLE) ?
+ relativePath :
+ relativePath.replaceName("lib" + relativePath.getBaseName() + linkType.getExtension());
+ return linkedFileName;
+ }
+
+ /**
+ * Resolves the linkstamp collection from the {@code CcLinkParams} into a map.
+ *
+ * <p>Emits a warning on the rule if there are identical linkstamp artifacts with different
+ * compilation contexts.
+ */
+ public static Map<Artifact, ImmutableList<Artifact>> resolveLinkstamps(RuleContext ruleContext,
+ CcLinkParams linkParams) {
+ Map<Artifact, ImmutableList<Artifact>> result = new LinkedHashMap<>();
+ for (Linkstamp pair : linkParams.getLinkstamps()) {
+ Artifact artifact = pair.getArtifact();
+ if (result.containsKey(artifact)) {
+ ruleContext.ruleWarning("rule inherits the '" + artifact.toDetailString()
+ + "' linkstamp file from more than one cc_library rule");
+ }
+ result.put(artifact, pair.getDeclaredIncludeSrcs());
+ }
+ return result;
+ }
+
+ public static void addTransitiveLipoInfoForCommonAttributes(
+ RuleContext ruleContext,
+ CcCompilationOutputs outputs,
+ NestedSetBuilder<IncludeScannable> scannableBuilder) {
+
+ TransitiveLipoInfoProvider stl = null;
+ if (ruleContext.getRule().getAttributeDefinition(":stl") != null &&
+ ruleContext.getPrerequisite(":stl", Mode.TARGET) != null) {
+ // If the attribute is defined, it is never null.
+ stl = ruleContext.getPrerequisite(":stl", Mode.TARGET)
+ .getProvider(TransitiveLipoInfoProvider.class);
+ }
+ if (stl != null) {
+ scannableBuilder.addTransitive(stl.getTransitiveIncludeScannables());
+ }
+
+ for (TransitiveLipoInfoProvider dep :
+ ruleContext.getPrerequisites("deps", Mode.TARGET, TransitiveLipoInfoProvider.class)) {
+ scannableBuilder.addTransitive(dep.getTransitiveIncludeScannables());
+ }
+
+ if (ruleContext.getRule().getRuleClassObject().hasAttr("malloc", Type.LABEL)) {
+ TransitiveInfoCollection malloc = mallocForTarget(ruleContext);
+ TransitiveLipoInfoProvider provider = malloc.getProvider(TransitiveLipoInfoProvider.class);
+ if (provider != null) {
+ scannableBuilder.addTransitive(provider.getTransitiveIncludeScannables());
+ }
+ }
+
+ for (IncludeScannable scannable : outputs.getLipoScannables()) {
+ Preconditions.checkState(scannable.getIncludeScannerSources().size() == 1);
+ scannableBuilder.add(scannable);
+ }
+ }
+
+ // TODO(bazel-team): figure out a way to merge these 2 methods. See the Todo in
+ // CcCommonConfiguredTarget.noCoptsMatches().
+ /**
+ * Determines if we should apply -fPIC for this rule's C++ compilations. This determination
+ * is generally made by the global C++ configuration settings "needsPic" and
+ * and "usePicForBinaries". However, an individual rule may override these settings by applying
+ * -fPIC" to its "nocopts" attribute. This allows incompatible rules to "opt out" of global PIC
+ * settings (see bug: "Provide a way to turn off -fPIC for targets that can't be built that way").
+ *
+ * @param ruleContext the context of the rule to check
+ * @param forBinary true if compiling for a binary, false if for a shared library
+ * @return true if this rule's compilations should apply -fPIC, false otherwise
+ */
+ public static boolean usePic(RuleContext ruleContext, boolean forBinary) {
+ if (CcCommon.noCoptsMatches("-fPIC", ruleContext)) {
+ return false;
+ }
+ CppConfiguration config = ruleContext.getFragment(CppConfiguration.class);
+ return forBinary ? config.usePicObjectsForBinaries() : config.needsPic();
+ }
+
+ /**
+ * Returns the LIPO context provider for configured target,
+ * or null if such a provider doesn't exist.
+ */
+ public static LipoContextProvider getLipoContextProvider(RuleContext ruleContext) {
+ if (ruleContext.getRule().getAttributeDefinition(":lipo_context_collector") == null) {
+ return null;
+ }
+
+ TransitiveInfoCollection dep =
+ ruleContext.getPrerequisite(":lipo_context_collector", Mode.DONT_CHECK);
+ return (dep != null) ? dep.getProvider(LipoContextProvider.class) : null;
+ }
+
+ // Creates CppModuleMap object, and adds it to C++ compilation context.
+ public static CppModuleMap addCppModuleMapToContext(RuleContext ruleContext,
+ CppCompilationContext.Builder contextBuilder) {
+ if (!ruleContext.getFragment(CppConfiguration.class).createCppModuleMaps()) {
+ return null;
+ }
+ if (getToolchain(ruleContext).getCppCompilationContext().getCppModuleMap() == null) {
+ return null;
+ }
+ // Create the module map artifact as a genfile.
+ PathFragment mapPath = FileSystemUtils.appendExtension(ruleContext.getLabel().toPathFragment(),
+ Iterables.getOnlyElement(CppFileTypes.CPP_MODULE_MAP.getExtensions()));
+ Artifact mapFile = ruleContext.getAnalysisEnvironment().getDerivedArtifact(mapPath,
+ ruleContext.getConfiguration().getGenfilesDirectory());
+ CppModuleMap moduleMap =
+ new CppModuleMap(mapFile, ruleContext.getLabel().toString());
+ contextBuilder.setCppModuleMap(moduleMap);
+ return moduleMap;
+ }
+
+ /**
+ * Returns a middleman for all files to build for the given configured target,
+ * substituting shared library artifacts with corresponding solib symlinks. If
+ * multiple calls are made, then it returns the same artifact for configurations
+ * with the same internal directory.
+ *
+ * <p>The resulting middleman only aggregates the inputs and must be expanded
+ * before populating the set of files necessary to execute an action.
+ */
+ static List<Artifact> getAggregatingMiddlemanForCppRuntimes(RuleContext ruleContext,
+ String purpose, TransitiveInfoCollection dep, String solibDirOverride,
+ BuildConfiguration configuration) {
+ return getMiddlemanInternal(
+ ruleContext.getAnalysisEnvironment(), ruleContext, ruleContext.getActionOwner(), purpose,
+ dep, true, true, solibDirOverride, configuration);
+ }
+
+ @VisibleForTesting
+ public static List<Artifact> getAggregatingMiddlemanForTesting(AnalysisEnvironment env,
+ RuleContext ruleContext, ActionOwner owner, String purpose, TransitiveInfoCollection dep,
+ boolean useSolibSymlinks, BuildConfiguration configuration) {
+ return getMiddlemanInternal(
+ env, ruleContext, owner, purpose, dep, useSolibSymlinks, false, null, configuration);
+ }
+
+ /**
+ * Internal implementation for getAggregatingMiddlemanForCppRuntimes.
+ */
+ private static List<Artifact> getMiddlemanInternal(AnalysisEnvironment env,
+ RuleContext ruleContext, ActionOwner actionOwner, String purpose,
+ TransitiveInfoCollection dep, boolean useSolibSymlinks, boolean isCppRuntime,
+ String solibDirOverride, BuildConfiguration configuration) {
+ if (dep == null) {
+ return ImmutableList.of();
+ }
+ MiddlemanFactory factory = env.getMiddlemanFactory();
+ Iterable<Artifact> artifacts = dep.getProvider(FileProvider.class).getFilesToBuild();
+ if (useSolibSymlinks) {
+ List<Artifact> symlinkedArtifacts = new ArrayList<>();
+ for (Artifact artifact : artifacts) {
+ symlinkedArtifacts.add(solibArtifactMaybe(
+ ruleContext, artifact, isCppRuntime, solibDirOverride, configuration));
+ }
+ artifacts = symlinkedArtifacts;
+ purpose += "_with_solib";
+ }
+ return ImmutableList.of(factory.createMiddlemanAllowMultiple(
+ env, actionOwner, purpose, artifacts, configuration.getMiddlemanDirectory()));
+ }
+
+ /**
+ * If the artifact is a shared library, returns the solib symlink artifact associated with it.
+ *
+ * @param ruleContext the context of the rule that creates the symlink
+ * @param artifact the library the solib symlink should point to
+ * @param isCppRuntime whether the library is a C++ runtime
+ * @param solibDirOverride if not null, forces the solib symlink to be in this directory
+ */
+ private static Artifact solibArtifactMaybe(RuleContext ruleContext, Artifact artifact,
+ boolean isCppRuntime, String solibDirOverride, BuildConfiguration configuration) {
+ if (SHARED_LIBRARY_FILETYPES.matches(artifact.getFilename())) {
+ return isCppRuntime
+ ? SolibSymlinkAction.getCppRuntimeSymlink(
+ ruleContext, artifact, solibDirOverride, configuration)
+ .getArtifact()
+ : SolibSymlinkAction.getDynamicLibrarySymlink(
+ ruleContext, artifact, false, true, configuration)
+ .getArtifact();
+ } else {
+ return artifact;
+ }
+ }
+
+ /**
+ * Returns the type of archives being used.
+ */
+ public static Link.ArchiveType archiveType(BuildConfiguration config) {
+ CppConfiguration cppConfig = config.getFragment(CppConfiguration.class);
+ return cppConfig.archiveType();
+ }
+
+ /**
+ * Returns the FDO build subtype.
+ */
+ public static String getFdoBuildStamp(CppConfiguration cppConfiguration) {
+ if (cppConfiguration.getFdoSupport().isAutoFdoEnabled()) {
+ return (cppConfiguration.getLipoMode() == LipoMode.BINARY) ? "ALIPO" : "AFDO";
+ }
+ if (cppConfiguration.isFdo()) {
+ return (cppConfiguration.getLipoMode() == LipoMode.BINARY) ? "LIPO" : "FDO";
+ }
+ return null;
+ }
+
+ /**
+ * Returns a relative path to the bin directory for data in AutoFDO LIPO mode.
+ */
+ public static PathFragment getLipoDataBinFragment(BuildConfiguration configuration) {
+ PathFragment parent = configuration.getBinFragment().getParentDirectory();
+ return parent.replaceName(parent.getBaseName() + "-lipodata")
+ .getChild(configuration.getBinFragment().getBaseName());
+ }
+
+ /**
+ * Returns a relative path to the genfiles directory for data in AutoFDO LIPO mode.
+ */
+ public static PathFragment getLipoDataGenfilesFragment(BuildConfiguration configuration) {
+ PathFragment parent = configuration.getGenfilesFragment().getParentDirectory();
+ return parent.replaceName(parent.getBaseName() + "-lipodata")
+ .getChild(configuration.getGenfilesFragment().getBaseName());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
new file mode 100644
index 0000000000..ecf3431b4f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
@@ -0,0 +1,1074 @@
+// Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.extra.CppLinkInfo;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.ImmutableIterable;
+import com.google.devtools.build.lib.collect.IterablesChain;
+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.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Action that represents an ELF linking step.
+ */
+@ThreadCompatible
+public final class CppLinkAction extends AbstractAction {
+ private static final String LINK_GUID = "58ec78bd-1176-4e36-8143-439f656b181d";
+ private static final String FAKE_LINK_GUID = "da36f819-5a15-43a9-8a45-e01b60e10c8b";
+
+ private final CppConfiguration cppConfiguration;
+ private final LibraryToLink outputLibrary;
+ private final LibraryToLink interfaceOutputLibrary;
+
+ private final LinkCommandLine linkCommandLine;
+
+ /** True for cc_fake_binary targets. */
+ private final boolean fake;
+
+ private final Iterable<Artifact> mandatoryInputs;
+
+ // Linking uses a lot of memory; estimate 1 MB per input file, min 1.5 Gib.
+ // It is vital to not underestimate too much here,
+ // because running too many concurrent links can
+ // thrash the machine to the point where it stops
+ // responding to keystrokes or mouse clicks.
+ // CPU and IO do not scale similarly and still use the static minimum estimate.
+ public static final ResourceSet LINK_RESOURCES_PER_INPUT = new ResourceSet(1, 0, 0);
+
+ // This defines the minimum of each resource that will be reserved.
+ public static final ResourceSet MIN_STATIC_LINK_RESOURCES = new ResourceSet(1536, 1, 0.3);
+
+ // Dynamic linking should be cheaper than static linking.
+ public static final ResourceSet MIN_DYNAMIC_LINK_RESOURCES = new ResourceSet(1024, 0.3, 0.2);
+
+ /**
+ * Use {@link Builder} to create instances of this class. Also see there for
+ * the documentation of all parameters.
+ *
+ * <p>This constructor is intentionally private and is only to be called from
+ * {@link Builder#build()}.
+ */
+ private CppLinkAction(ActionOwner owner,
+ Iterable<Artifact> inputs,
+ ImmutableList<Artifact> outputs,
+ CppConfiguration cppConfiguration,
+ LibraryToLink outputLibrary,
+ LibraryToLink interfaceOutputLibrary,
+ boolean fake,
+ LinkCommandLine linkCommandLine) {
+ super(owner, inputs, outputs);
+ this.mandatoryInputs = inputs;
+ this.cppConfiguration = cppConfiguration;
+ this.outputLibrary = outputLibrary;
+ this.interfaceOutputLibrary = interfaceOutputLibrary;
+ this.fake = fake;
+
+ this.linkCommandLine = linkCommandLine;
+ }
+
+ private static Iterable<LinkerInput> filterLinkerInputs(Iterable<LinkerInput> inputs) {
+ return Iterables.filter(inputs, new Predicate<LinkerInput>() {
+ @Override
+ public boolean apply(LinkerInput input) {
+ return Link.VALID_LINKER_INPUTS.matches(input.getArtifact().getFilename());
+ }
+ });
+ }
+
+ private static Iterable<Artifact> filterLinkerInputArtifacts(Iterable<Artifact> inputs) {
+ return Iterables.filter(inputs, new Predicate<Artifact>() {
+ @Override
+ public boolean apply(Artifact input) {
+ return Link.VALID_LINKER_INPUTS.matches(input.getFilename());
+ }
+ });
+ }
+
+ private CppConfiguration getCppConfiguration() {
+ return cppConfiguration;
+ }
+
+ @VisibleForTesting
+ public String getTargetCpu() {
+ return getCppConfiguration().getTargetCpu();
+ }
+
+ public String getHostSystemName() {
+ return getCppConfiguration().getHostSystemName();
+ }
+
+ /**
+ * Returns the link configuration; for correctness you should not call this method during
+ * execution - only the argv is part of the action cache key, and we therefore don't guarantee
+ * that the action will be re-executed if the contents change in a way that does not affect the
+ * argv.
+ */
+ @VisibleForTesting
+ public LinkCommandLine getLinkCommandLine() {
+ return linkCommandLine;
+ }
+
+ public LibraryToLink getOutputLibrary() {
+ return outputLibrary;
+ }
+
+ public LibraryToLink getInterfaceOutputLibrary() {
+ return interfaceOutputLibrary;
+ }
+
+ /**
+ * Returns the path to the output artifact produced by the linker.
+ */
+ public Path getOutputFile() {
+ return outputLibrary.getArtifact().getPath();
+ }
+
+ @VisibleForTesting
+ public List<String> getRawLinkArgv() {
+ return linkCommandLine.getRawLinkArgv();
+ }
+
+ @VisibleForTesting
+ public List<String> getArgv() {
+ return linkCommandLine.arguments();
+ }
+
+ /**
+ * Prepares and returns the command line specification for this link.
+ * Splits appropriate parts into a .params file and adds any required
+ * linkstamp compilation steps.
+ *
+ * @return a finalized command line suitable for execution
+ */
+ public final List<String> prepareCommandLine(Path execRoot, List<String> inputFiles)
+ throws ExecException {
+ List<String> commandlineArgs;
+ // Try to shorten the command line by use of a parameter file.
+ // This makes the output with --subcommands (et al) more readable.
+ if (linkCommandLine.canBeSplit()) {
+ PathFragment paramExecPath = ParameterFile.derivePath(
+ outputLibrary.getArtifact().getExecPath());
+ Pair<List<String>, List<String>> split = linkCommandLine.splitCommandline(paramExecPath);
+ commandlineArgs = split.first;
+ writeToParamFile(execRoot, paramExecPath, split.second);
+ if (inputFiles != null) {
+ inputFiles.add(paramExecPath.getPathString());
+ }
+ } else {
+ commandlineArgs = linkCommandLine.getRawLinkArgv();
+ }
+ return linkCommandLine.finalizeWithLinkstampCommands(commandlineArgs);
+ }
+
+ private static void writeToParamFile(Path workingDir, PathFragment paramExecPath,
+ List<String> paramFileArgs) throws ExecException {
+ // Create parameter file.
+ ParameterFile paramFile = new ParameterFile(workingDir, paramExecPath, ISO_8859_1,
+ ParameterFileType.UNQUOTED);
+ Path paramFilePath = paramFile.getPath();
+ try {
+ // writeContent() fails for existing files that are marked readonly.
+ paramFilePath.delete();
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("could not delete file '" + paramFilePath + "'", e);
+ }
+ paramFile.writeContent(paramFileArgs);
+
+ // Normally Blaze chmods all output files automatically (see
+ // SkyframeActionExecutor#setOutputsReadOnlyAndExecutable), but this params file is created
+ // out-of-band and is not declared as an output. By chmodding the file, other processes
+ // can observe this file being created.
+ try {
+ paramFilePath.setWritable(false);
+ paramFilePath.setExecutable(true); // for consistency with other action outputs
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("could not chmod param file '" + paramFilePath + "'", e);
+ }
+ }
+
+ @Override
+ @ThreadCompatible
+ public void execute(
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ if (fake) {
+ executeFake();
+ } else {
+ Executor executor = actionExecutionContext.getExecutor();
+
+ try {
+ executor.getContext(CppLinkActionContext.class).exec(
+ this, actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException("Linking of rule '" + getOwner().getLabel() + "'",
+ executor.getVerboseFailures(), this);
+ }
+ }
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return fake
+ ? "fake,local"
+ : executor.getContext(CppLinkActionContext.class).strategyLocality(this);
+ }
+
+ // Don't forget to update FAKE_LINK_GUID if you modify this method.
+ @ThreadCompatible
+ private void executeFake()
+ throws ActionExecutionException {
+ // The uses of getLinkConfiguration in this method may not be consistent with the computed key.
+ // I.e., this may be incrementally incorrect.
+ final Collection<Artifact> linkstampOutputs = getLinkCommandLine().getLinkstamps().values();
+
+ // Prefix all fake output files in the command line with $TEST_TMPDIR/.
+ final String outputPrefix = "$TEST_TMPDIR/";
+ List<String> escapedLinkArgv = escapeLinkArgv(linkCommandLine.getRawLinkArgv(),
+ linkstampOutputs, outputPrefix);
+ // Write the commands needed to build the real target to the fake target
+ // file.
+ StringBuilder s = new StringBuilder();
+ Joiner.on('\n').appendTo(s,
+ "# This is a fake target file, automatically generated.",
+ "# Do not edit by hand!",
+ "echo $0 is a fake target file and not meant to be executed.",
+ "exit 0",
+ "EOS",
+ "",
+ "makefile_dir=.",
+ "");
+
+ try {
+ // Concatenate all the (fake) .o files into the result.
+ for (LinkerInput linkerInput : getLinkCommandLine().getLinkerInputs()) {
+ Artifact objectFile = linkerInput.getArtifact();
+ if (CppFileTypes.OBJECT_FILE.matches(objectFile.getFilename())
+ && linkerInput.isFake()) {
+ s.append(FileSystemUtils.readContentAsLatin1(objectFile.getPath())); // (IOException)
+ }
+ }
+
+ s.append(getOutputFile().getBaseName()).append(": ");
+ for (Artifact linkstamp : linkstampOutputs) {
+ s.append("mkdir -p " + outputPrefix +
+ linkstamp.getExecPath().getParentDirectory() + " && ");
+ }
+ Joiner.on(' ').appendTo(s,
+ ShellEscaper.escapeAll(linkCommandLine.finalizeAlreadyEscapedWithLinkstampCommands(
+ escapedLinkArgv, outputPrefix)));
+ s.append('\n');
+ if (getOutputFile().exists()) {
+ getOutputFile().setWritable(true); // (IOException)
+ }
+ FileSystemUtils.writeContent(getOutputFile(), ISO_8859_1, s.toString());
+ getOutputFile().setExecutable(true); // (IOException)
+ for (Artifact linkstamp : linkstampOutputs) {
+ FileSystemUtils.touchFile(linkstamp.getPath());
+ }
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create fake link command for rule '" +
+ getOwner().getLabel() + ": " + e.getMessage(),
+ this, false);
+ }
+ }
+
+ /**
+ * Shell-escapes the raw link command line.
+ *
+ * @param rawLinkArgv raw link command line
+ * @param linkstampOutputs linkstamp artifacts
+ * @param outputPrefix to be prepended to any outputs
+ * @return escaped link command line
+ */
+ private List<String> escapeLinkArgv(List<String> rawLinkArgv,
+ final Collection<Artifact> linkstampOutputs, final String outputPrefix) {
+ final List<String> linkstampExecPaths = Artifact.asExecPaths(linkstampOutputs);
+ ImmutableList.Builder<String> escapedArgs = ImmutableList.builder();
+ for (String rawArg : rawLinkArgv) {
+ String escapedArg;
+ if (rawArg.equals(getPrimaryOutput().getExecPathString())
+ || linkstampExecPaths.contains(rawArg)) {
+ escapedArg = outputPrefix + ShellEscaper.escapeString(rawArg);
+ } else if (rawArg.startsWith(Link.FAKE_OBJECT_PREFIX)) {
+ escapedArg = outputPrefix + ShellEscaper.escapeString(
+ rawArg.substring(Link.FAKE_OBJECT_PREFIX.length()));
+ } else {
+ escapedArg = ShellEscaper.escapeString(rawArg);
+ }
+ escapedArgs.add(escapedArg);
+ }
+ return escapedArgs.build();
+ }
+
+ @Override
+ public ExtraActionInfo.Builder getExtraActionInfo() {
+ // The uses of getLinkConfiguration in this method may not be consistent with the computed key.
+ // I.e., this may be incrementally incorrect.
+ CppLinkInfo.Builder info = CppLinkInfo.newBuilder();
+ info.addAllInputFile(Artifact.toExecPaths(
+ LinkerInputs.toLibraryArtifacts(getLinkCommandLine().getLinkerInputs())));
+ info.addAllInputFile(Artifact.toExecPaths(
+ LinkerInputs.toLibraryArtifacts(getLinkCommandLine().getRuntimeInputs())));
+ info.setOutputFile(getPrimaryOutput().getExecPathString());
+ if (interfaceOutputLibrary != null) {
+ info.setInterfaceOutputFile(interfaceOutputLibrary.getArtifact().getExecPathString());
+ }
+ info.setLinkTargetType(getLinkCommandLine().getLinkTargetType().name());
+ info.setLinkStaticness(getLinkCommandLine().getLinkStaticness().name());
+ info.addAllLinkStamp(Artifact.toExecPaths(getLinkCommandLine().getLinkstamps().values()));
+ info.addAllBuildInfoHeaderArtifact(
+ Artifact.toExecPaths(getLinkCommandLine().getBuildInfoHeaderArtifacts()));
+ info.addAllLinkOpt(getLinkCommandLine().getLinkopts());
+
+ return super.getExtraActionInfo()
+ .setExtension(CppLinkInfo.cppLinkInfo, info.build());
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(fake ? FAKE_LINK_GUID : LINK_GUID);
+ f.addString(getCppConfiguration().getLdExecutable().getPathString());
+ f.addStrings(linkCommandLine.arguments());
+ // TODO(bazel-team): For correctness, we need to ensure the invariant that all values accessed
+ // during the execution phase are also covered by the key. Above, we add the argv to the key,
+ // which covers most cases. Unfortunately, the extra action and fake support methods above also
+ // sometimes directly access settings from the link configuration that may or may not affect the
+ // key. We either need to change the code to cover them in the key computation, or change the
+ // LinkConfiguration to disallow the combinations where the value of a setting does not affect
+ // the argv.
+ f.addBoolean(linkCommandLine.isNativeDeps());
+ f.addBoolean(linkCommandLine.useTestOnlyFlags());
+ if (linkCommandLine.getRuntimeSolibDir() != null) {
+ f.addPath(linkCommandLine.getRuntimeSolibDir());
+ }
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String describeKey() {
+ StringBuilder message = new StringBuilder();
+ if (fake) {
+ message.append("Fake ");
+ }
+ message.append(getProgressMessage());
+ message.append('\n');
+ message.append(" Command: ");
+ message.append(ShellEscaper.escapeString(
+ getCppConfiguration().getLdExecutable().getPathString()));
+ message.append('\n');
+ // Outputting one argument per line makes it easier to diff the results.
+ for (String argument : ShellEscaper.escapeAll(linkCommandLine.arguments())) {
+ message.append(" Argument: ");
+ message.append(argument);
+ message.append('\n');
+ }
+ return message.toString();
+ }
+
+ @Override
+ public String getMnemonic() { return "CppLink"; }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Linking " + outputLibrary.getArtifact().prettyPrint();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return executor.getContext(CppLinkActionContext.class).estimateResourceConsumption(this);
+ }
+
+ /**
+ * Estimate the resources consumed when this action is run locally.
+ */
+ public ResourceSet estimateResourceConsumptionLocal() {
+ // It's ok if this behaves differently even if the key is identical.
+ ResourceSet minLinkResources =
+ getLinkCommandLine().getLinkStaticness() == Link.LinkStaticness.DYNAMIC
+ ? MIN_DYNAMIC_LINK_RESOURCES
+ : MIN_STATIC_LINK_RESOURCES;
+
+ final int inputSize = Iterables.size(getLinkCommandLine().getLinkerInputs())
+ + Iterables.size(getLinkCommandLine().getRuntimeInputs());
+
+ return new ResourceSet(
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getMemoryMb(),
+ minLinkResources.getMemoryMb()),
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getCpuUsage(),
+ minLinkResources.getCpuUsage()),
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getIoUsage(),
+ minLinkResources.getIoUsage())
+ );
+ }
+
+ @Override
+ public Iterable<Artifact> getMandatoryInputs() {
+ return mandatoryInputs;
+ }
+
+ /**
+ * Determines whether or not this link should output a symbol counts file.
+ */
+ private static boolean enableSymbolsCounts(CppConfiguration cppConfiguration, boolean fake,
+ LinkTargetType linkType) {
+ return cppConfiguration.getSymbolCounts()
+ && cppConfiguration.supportsGoldLinker()
+ && linkType == LinkTargetType.EXECUTABLE
+ && !fake;
+ }
+
+ /**
+ * Builder class to construct {@link CppLinkAction}s.
+ */
+ public static class Builder {
+ // Builder-only
+ private final RuleContext ruleContext;
+ private final AnalysisEnvironment analysisEnvironment;
+ private final PathFragment outputPath;
+ private final CcToolchainProvider toolchain;
+ private PathFragment interfaceOutputPath;
+ private PathFragment runtimeSolibDir;
+ protected final BuildConfiguration configuration;
+ private final CppConfiguration cppConfiguration;
+
+ // Morally equivalent with {@link Context}, except these are mutable.
+ // Keep these in sync with {@link Context}.
+ private final Set<LinkerInput> nonLibraries = new LinkedHashSet<>();
+ private final NestedSetBuilder<LibraryToLink> libraries = NestedSetBuilder.linkOrder();
+ private NestedSet<Artifact> crosstoolInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ private Artifact runtimeMiddleman;
+ private NestedSet<Artifact> runtimeInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ private final NestedSetBuilder<Artifact> compilationInputs = NestedSetBuilder.stableOrder();
+ private final Set<Artifact> linkstamps = new LinkedHashSet<>();
+ private List<String> linkstampOptions = new ArrayList<>();
+ private final List<String> linkopts = new ArrayList<>();
+ private LinkTargetType linkType = LinkTargetType.STATIC_LIBRARY;
+ private LinkStaticness linkStaticness = LinkStaticness.FULLY_STATIC;
+ private boolean fake;
+ private boolean isNativeDeps;
+ private boolean useTestOnlyFlags;
+ private boolean wholeArchive;
+ private boolean supportsParamFiles = true;
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction} instances.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath) {
+ this(ruleContext, outputPath, ruleContext.getConfiguration(),
+ ruleContext.getAnalysisEnvironment(), CppHelper.getToolchain(ruleContext));
+ }
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction} instances.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath,
+ BuildConfiguration configuration, CcToolchainProvider toolchain) {
+ this(ruleContext, outputPath, configuration,
+ ruleContext.getAnalysisEnvironment(), toolchain);
+ }
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction}s.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ * @param configuration the configuration used to determine the tool chain
+ * and the default link options
+ */
+ private Builder(RuleContext ruleContext, PathFragment outputPath,
+ BuildConfiguration configuration, AnalysisEnvironment analysisEnvironment,
+ CcToolchainProvider toolchain) {
+ this.ruleContext = ruleContext;
+ this.analysisEnvironment = Preconditions.checkNotNull(analysisEnvironment);
+ this.outputPath = Preconditions.checkNotNull(outputPath);
+ this.configuration = Preconditions.checkNotNull(configuration);
+ this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
+ this.toolchain = toolchain;
+
+ // The toolchain != null is here for CppLinkAction.createTestBuilder(). Meh.
+ if (cppConfiguration.supportsEmbeddedRuntimes() && toolchain != null) {
+ runtimeSolibDir = toolchain.getDynamicRuntimeSolibDir();
+ }
+ if (toolchain != null) {
+ supportsParamFiles = toolchain.supportsParamFiles();
+ }
+ }
+
+ /**
+ * Given a Context, creates a Builder that builds {@link CppLinkAction}s.
+ * Note well: Keep the Builder->Context and Context->Builder transforms consistent!
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ * @param linkContext an immutable CppLinkAction.Context from the original builder
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath, Context linkContext,
+ BuildConfiguration configuration) {
+ // These Builder-only fields get set in the constructor:
+ // ruleContext, analysisEnvironment, outputPath, configuration, runtimeSolibDir
+ this(ruleContext, outputPath, configuration, ruleContext.getAnalysisEnvironment(),
+ CppHelper.getToolchain(ruleContext));
+ Preconditions.checkNotNull(linkContext);
+
+ // All linkContext fields should be transferred to this Builder.
+ this.nonLibraries.addAll(linkContext.nonLibraries);
+ this.libraries.addTransitive(linkContext.libraries);
+ this.crosstoolInputs = linkContext.crosstoolInputs;
+ this.runtimeMiddleman = linkContext.runtimeMiddleman;
+ this.runtimeInputs = linkContext.runtimeInputs;
+ this.compilationInputs.addTransitive(linkContext.compilationInputs);
+ this.linkstamps.addAll(linkContext.linkstamps);
+ this.linkopts.addAll(linkContext.linkopts);
+ this.linkType = linkContext.linkType;
+ this.linkStaticness = linkContext.linkStaticness;
+ this.fake = linkContext.fake;
+ this.isNativeDeps = linkContext.isNativeDeps;
+ this.useTestOnlyFlags = linkContext.useTestOnlyFlags;
+ }
+
+ /**
+ * Builds the Action as configured and returns it.
+ *
+ * <p>This method may only be called once.
+ */
+ public CppLinkAction build() {
+ if (interfaceOutputPath != null && (fake || linkType != LinkTargetType.DYNAMIC_LIBRARY)) {
+ throw new RuntimeException("Interface output can only be used "
+ + "with non-fake DYNAMIC_LIBRARY targets");
+ }
+
+ final Artifact output = createArtifact(outputPath);
+ final Artifact interfaceOutput = (interfaceOutputPath != null)
+ ? createArtifact(interfaceOutputPath)
+ : null;
+
+ final ImmutableList<Artifact> buildInfoHeaderArtifacts = !linkstamps.isEmpty()
+ ? ruleContext.getAnalysisEnvironment().getBuildInfo(ruleContext, CppBuildInfo.KEY)
+ : ImmutableList.<Artifact>of();
+
+ final Artifact symbolCountOutput = enableSymbolsCounts(cppConfiguration, fake, linkType)
+ ? createArtifact(output.getRootRelativePath().replaceName(
+ output.getExecPath().getBaseName() + ".sc"))
+ : null;
+
+ boolean needWholeArchive = wholeArchive || needWholeArchive(
+ linkStaticness, linkType, linkopts, isNativeDeps, cppConfiguration);
+
+ NestedSet<LibraryToLink> uniqueLibraries = libraries.build();
+ final Iterable<Artifact> filteredNonLibraryArtifacts = filterLinkerInputArtifacts(
+ LinkerInputs.toLibraryArtifacts(nonLibraries));
+ final Iterable<LinkerInput> linkerInputs = IterablesChain.<LinkerInput>builder()
+ .add(ImmutableList.copyOf(filterLinkerInputs(nonLibraries)))
+ .add(ImmutableIterable.from(Link.mergeInputsCmdLine(
+ uniqueLibraries, needWholeArchive, cppConfiguration.archiveType())))
+ .build();
+
+ // ruleContext can only be null during testing. This is kind of ugly.
+ final ImmutableSet<String> features = (ruleContext == null)
+ ? ImmutableSet.<String>of()
+ : ruleContext.getFeatures();
+
+ final LibraryToLink outputLibrary =
+ LinkerInputs.newInputLibrary(output, filteredNonLibraryArtifacts);
+ final LibraryToLink interfaceOutputLibrary = interfaceOutput == null ? null :
+ LinkerInputs.newInputLibrary(interfaceOutput, filteredNonLibraryArtifacts);
+
+ final ImmutableMap<Artifact, Artifact> linkstampMap =
+ mapLinkstampsToOutputs(linkstamps, ruleContext, output);
+
+ final ImmutableList<Artifact> actionOutputs = constructOutputs(
+ outputLibrary.getArtifact(),
+ linkstampMap.values(),
+ interfaceOutputLibrary == null ? null : interfaceOutputLibrary.getArtifact(),
+ symbolCountOutput);
+
+ LinkCommandLine linkCommandLine = new LinkCommandLine.Builder(configuration, getOwner())
+ .setOutput(outputLibrary.getArtifact())
+ .setInterfaceOutput(interfaceOutput)
+ .setSymbolCountsOutput(symbolCountOutput)
+ .setBuildInfoHeaderArtifacts(buildInfoHeaderArtifacts)
+ .setLinkerInputs(linkerInputs)
+ .setRuntimeInputs(ImmutableList.copyOf(LinkerInputs.simpleLinkerInputs(runtimeInputs)))
+ .setLinkTargetType(linkType)
+ .setLinkStaticness(linkStaticness)
+ .setLinkopts(ImmutableList.copyOf(linkopts))
+ .setFeatures(features)
+ .setLinkstamps(linkstampMap)
+ .addLinkstampCompileOptions(linkstampOptions)
+ .setRuntimeSolibDir(linkType.isStaticLibraryLink() ? null : runtimeSolibDir)
+ .setNativeDeps(isNativeDeps)
+ .setUseTestOnlyFlags(useTestOnlyFlags)
+ .setNeedWholeArchive(needWholeArchive)
+ .setInterfaceSoBuilder(getInterfaceSoBuilder())
+ .setSupportsParamFiles(supportsParamFiles)
+ .build();
+
+ // Compute the set of inputs - we only need stable order here.
+ NestedSetBuilder<Artifact> dependencyInputsBuilder = NestedSetBuilder.stableOrder();
+ dependencyInputsBuilder.addAll(buildInfoHeaderArtifacts);
+ dependencyInputsBuilder.addAll(linkstamps);
+ dependencyInputsBuilder.addTransitive(crosstoolInputs);
+ if (runtimeMiddleman != null) {
+ dependencyInputsBuilder.add(runtimeMiddleman);
+ }
+ dependencyInputsBuilder.addTransitive(compilationInputs.build());
+
+ Iterable<Artifact> expandedInputs =
+ LinkerInputs.toLibraryArtifacts(Link.mergeInputsDependencies(uniqueLibraries,
+ needWholeArchive, cppConfiguration.archiveType()));
+ // getPrimaryInput returns the first element, and that is a public interface - therefore the
+ // order here is important.
+ Iterable<Artifact> inputs = IterablesChain.<Artifact>builder()
+ .add(ImmutableList.copyOf(LinkerInputs.toLibraryArtifacts(nonLibraries)))
+ .add(dependencyInputsBuilder.build())
+ .add(ImmutableIterable.from(expandedInputs))
+ .deduplicate()
+ .build();
+
+ return new CppLinkAction(
+ getOwner(),
+ inputs,
+ actionOutputs,
+ cppConfiguration,
+ outputLibrary,
+ interfaceOutputLibrary,
+ fake,
+ linkCommandLine);
+ }
+
+ /**
+ * The default heuristic on whether we need to use whole-archive for the link.
+ */
+ private static boolean needWholeArchive(LinkStaticness staticness,
+ LinkTargetType type, Collection<String> linkopts, boolean isNativeDeps,
+ CppConfiguration cppConfig) {
+ boolean fullyStatic = (staticness == LinkStaticness.FULLY_STATIC);
+ boolean mostlyStatic = (staticness == LinkStaticness.MOSTLY_STATIC);
+ boolean sharedLinkopts = type == LinkTargetType.DYNAMIC_LIBRARY
+ || linkopts.contains("-shared")
+ || cppConfig.getLinkOptions().contains("-shared");
+ return (isNativeDeps || cppConfig.legacyWholeArchive())
+ && (fullyStatic || mostlyStatic)
+ && sharedLinkopts;
+ }
+
+ private static ImmutableList<Artifact> constructOutputs(Artifact primaryOutput,
+ Collection<Artifact> outputList, Artifact... outputs) {
+ return new ImmutableList.Builder<Artifact>()
+ .add(primaryOutput)
+ .addAll(outputList)
+ .addAll(CollectionUtils.asListWithoutNulls(outputs))
+ .build();
+ }
+
+ /**
+ * Translates a collection of linkstamp source files to an immutable
+ * mapping from source files to object files. In other words, given a
+ * set of source files, this method determines the output path to which
+ * each file should be compiled.
+ *
+ * @param linkstamps collection of linkstamp source files
+ * @param ruleContext the rule for which this link is being performed
+ * @param outputBinary the binary output path for this link
+ * @return an immutable map that pairs each source file with the
+ * corresponding object file that should be fed into the link
+ */
+ public static ImmutableMap<Artifact, Artifact> mapLinkstampsToOutputs(
+ Collection<Artifact> linkstamps, RuleContext ruleContext, Artifact outputBinary) {
+ ImmutableMap.Builder<Artifact, Artifact> mapBuilder = ImmutableMap.builder();
+
+ PathFragment outputBinaryPath = outputBinary.getRootRelativePath();
+ PathFragment stampOutputDirectory = outputBinaryPath.getParentDirectory().
+ getRelative("_objs").getRelative(outputBinaryPath.getBaseName());
+
+ for (Artifact linkstamp : linkstamps) {
+ PathFragment stampOutputPath = stampOutputDirectory.getRelative(
+ FileSystemUtils.replaceExtension(linkstamp.getRootRelativePath(), ".o"));
+ mapBuilder.put(linkstamp,
+ ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ stampOutputPath, outputBinary.getRoot()));
+ }
+ return mapBuilder.build();
+ }
+
+ protected ActionOwner getOwner() {
+ return ruleContext.getActionOwner();
+ }
+
+ protected Artifact createArtifact(PathFragment path) {
+ return analysisEnvironment.getDerivedArtifact(path, configuration.getBinDirectory());
+ }
+
+ protected Artifact getInterfaceSoBuilder() {
+ return analysisEnvironment.getEmbeddedToolArtifact(CppRuleClasses.BUILD_INTERFACE_SO);
+ }
+
+ /**
+ * Set the crosstool inputs required for the action.
+ */
+ public Builder setCrosstoolInputs(NestedSet<Artifact> inputs) {
+ this.crosstoolInputs = inputs;
+ return this;
+ }
+
+ /**
+ * Sets the C++ runtime library inputs for the action.
+ */
+ public Builder setRuntimeInputs(Artifact middleman, NestedSet<Artifact> inputs) {
+ Preconditions.checkArgument((middleman == null) == inputs.isEmpty());
+ this.runtimeMiddleman = middleman;
+ this.runtimeInputs = inputs;
+ return this;
+ }
+
+ /**
+ * Sets the interface output of the link. A non-null argument can
+ * only be provided if the link type is {@code DYNAMIC_LIBRARY}
+ * and fake is false.
+ */
+ public Builder setInterfaceOutputPath(PathFragment path) {
+ this.interfaceOutputPath = path;
+ return this;
+ }
+
+ /**
+ * Add additional inputs needed for the linkstamp compilation that is being done as part of the
+ * link.
+ */
+ public Builder addCompilationInputs(Iterable<Artifact> inputs) {
+ this.compilationInputs.addAll(inputs);
+ return this;
+ }
+
+ public Builder addTransitiveCompilationInputs(NestedSet<Artifact> inputs) {
+ this.compilationInputs.addTransitive(inputs);
+ return this;
+ }
+
+ private void addNonLibraryInput(LinkerInput input) {
+ String name = input.getArtifact().getFilename();
+ Preconditions.checkArgument(
+ !Link.ARCHIVE_LIBRARY_FILETYPES.matches(name)
+ && !Link.SHARED_LIBRARY_FILETYPES.matches(name),
+ "'%s' is a library file", input);
+ this.nonLibraries.add(input);
+ }
+ /**
+ * Adds a single artifact to the set of inputs (C++ source files, header files, etc). Artifacts
+ * that are not of recognized types will be used for dependency checking but will not be passed
+ * to the linker. The artifact must not be an archive or a shared library.
+ */
+ public Builder addNonLibraryInput(Artifact input) {
+ addNonLibraryInput(LinkerInputs.simpleLinkerInput(input));
+ return this;
+ }
+
+ /**
+ * Adds multiple artifacts to the set of inputs (C++ source files, header files, etc).
+ * Artifacts that are not of recognized types will be used for dependency checking but will
+ * not be passed to the linker. The artifacts must not be archives or shared libraries.
+ */
+ public Builder addNonLibraryInputs(Iterable<Artifact> inputs) {
+ for (Artifact input : inputs) {
+ addNonLibraryInput(LinkerInputs.simpleLinkerInput(input));
+ }
+ return this;
+ }
+
+ public Builder addFakeNonLibraryInputs(Iterable<Artifact> inputs) {
+ for (Artifact input : inputs) {
+ addNonLibraryInput(LinkerInputs.fakeLinkerInput(input));
+ }
+ return this;
+ }
+
+ private void checkLibrary(LibraryToLink input) {
+ String name = input.getArtifact().getFilename();
+ Preconditions.checkArgument(
+ Link.ARCHIVE_LIBRARY_FILETYPES.matches(name) ||
+ Link.SHARED_LIBRARY_FILETYPES.matches(name),
+ "'%s' is not a library file", input);
+ }
+
+ /**
+ * Adds a single artifact to the set of inputs. The artifact must be an archive or a shared
+ * library. Note that all directly added libraries are implicitly ordered before all nested
+ * sets added with {@link #addLibraries}, even if added in the opposite order.
+ */
+ public Builder addLibrary(LibraryToLink input) {
+ checkLibrary(input);
+ libraries.add(input);
+ return this;
+ }
+
+ /**
+ * Adds multiple artifact to the set of inputs. The artifacts must be archives or shared
+ * libraries.
+ */
+ public Builder addLibraries(NestedSet<LibraryToLink> inputs) {
+ for (LibraryToLink input : inputs) {
+ checkLibrary(input);
+ }
+ this.libraries.addTransitive(inputs);
+ return this;
+ }
+
+ /**
+ * Sets the type of ELF file to be created (.a, .so, .lo, executable). The
+ * default is {@link LinkTargetType#STATIC_LIBRARY}.
+ */
+ public Builder setLinkType(LinkTargetType linkType) {
+ this.linkType = linkType;
+ return this;
+ }
+
+ /**
+ * Sets the degree of "staticness" of the link: fully static (static binding
+ * of all symbols), mostly static (use dynamic binding only for symbols from
+ * glibc), dynamic (use dynamic binding wherever possible). The default is
+ * {@link LinkStaticness#FULLY_STATIC}.
+ */
+ public Builder setLinkStaticness(LinkStaticness linkStaticness) {
+ this.linkStaticness = linkStaticness;
+ return this;
+ }
+
+ /**
+ * Adds a C++ source file which will be compiled at link time. This is used
+ * to embed various values from the build system into binaries to identify
+ * their provenance.
+ *
+ * <p>Link stamps are also automatically added to the inputs.
+ */
+ public Builder addLinkstamps(Map<Artifact, ImmutableList<Artifact>> linkstamps) {
+ this.linkstamps.addAll(linkstamps.keySet());
+ // Add inputs for linkstamping.
+ if (!linkstamps.isEmpty()) {
+ // This will just be the compiler unless include scanning is disabled, in which case it will
+ // include all header files. Since we insist that linkstamps declare all their headers, all
+ // header files would be overkill, but that only happens when include scanning is disabled.
+ addTransitiveCompilationInputs(toolchain.getCompile());
+ for (Map.Entry<Artifact, ImmutableList<Artifact>> entry : linkstamps.entrySet()) {
+ addCompilationInputs(entry.getValue());
+ }
+ }
+ return this;
+ }
+
+ public Builder addLinkstampCompilerOptions(ImmutableList<String> linkstampOptions) {
+ this.linkstampOptions = linkstampOptions;
+ return this;
+ }
+
+ /**
+ * Adds an additional linker option.
+ */
+ public Builder addLinkopt(String linkopt) {
+ this.linkopts.add(linkopt);
+ return this;
+ }
+
+ /**
+ * Adds multiple linker options at once.
+ *
+ * @see #addLinkopt(String)
+ */
+ public Builder addLinkopts(Collection<String> linkopts) {
+ this.linkopts.addAll(linkopts);
+ return this;
+ }
+
+ /**
+ * Sets whether this link action will be used for a cc_fake_binary; false by
+ * default.
+ */
+ public Builder setFake(boolean fake) {
+ this.fake = fake;
+ return this;
+ }
+
+ /**
+ * Sets whether this link action is used for a native dependency library.
+ */
+ public Builder setNativeDeps(boolean isNativeDeps) {
+ this.isNativeDeps = isNativeDeps;
+ return this;
+ }
+
+ /**
+ * Setting this to true overrides the default whole-archive computation and force-enables
+ * whole archives for every archive in the link. This is only necessary for linking executable
+ * binaries that are supposed to export symbols.
+ *
+ * <p>Usually, the link action while use whole archives for dynamic libraries that are native
+ * deps (or the legacy whole archive flag is enabled), and that are not dynamically linked.
+ *
+ * <p>(Note that it is possible to build dynamic libraries with cc_binary rules by specifying
+ * linkshared = 1, and giving the rule a name that matches the pattern {@code
+ * lib&lt;name&gt;.so}.)
+ */
+ public Builder setWholeArchive(boolean wholeArchive) {
+ this.wholeArchive = wholeArchive;
+ return this;
+ }
+
+ /**
+ * Sets whether this link action should use test-specific flags (e.g. $EXEC_ORIGIN instead of
+ * $ORIGIN for the solib search path or lazy binding); false by default.
+ */
+ public Builder setUseTestOnlyFlags(boolean useTestOnlyFlags) {
+ this.useTestOnlyFlags = useTestOnlyFlags;
+ return this;
+ }
+
+ /**
+ * Sets the name of the directory where the solib symlinks for the dynamic runtime libraries
+ * live. This is usually automatically set from the cc_toolchain.
+ */
+ public Builder setRuntimeSolibDir(PathFragment runtimeSolibDir) {
+ this.runtimeSolibDir = runtimeSolibDir;
+ return this;
+ }
+
+ /**
+ * Creates a builder without the need for a {@link RuleContext}.
+ * This is to be used exclusively for testing purposes.
+ *
+ * <p>Link stamping is not supported if using this method.
+ */
+ @VisibleForTesting
+ public static Builder createTestBuilder(
+ final ActionOwner owner, final AnalysisEnvironment analysisEnvironment,
+ final PathFragment outputPath, BuildConfiguration config) {
+ return new Builder(null, outputPath, config, analysisEnvironment, null) {
+ @Override
+ protected Artifact createArtifact(PathFragment path) {
+ return new Artifact(configuration.getBinDirectory().getPath().getRelative(path),
+ configuration.getBinDirectory(), configuration.getBinFragment().getRelative(path),
+ analysisEnvironment.getOwner());
+ }
+ @Override
+ protected ActionOwner getOwner() {
+ return owner;
+ }
+ };
+ }
+ }
+
+ /**
+ * Immutable ELF linker context, suitable for serialization.
+ */
+ @Immutable @ThreadSafe
+ public static final class Context implements TransitiveInfoProvider {
+ // Morally equivalent with {@link Builder}, except these are immutable.
+ // Keep these in sync with {@link Builder}.
+ private final ImmutableSet<LinkerInput> nonLibraries;
+ private final NestedSet<LibraryToLink> libraries;
+ private final NestedSet<Artifact> crosstoolInputs;
+ private final Artifact runtimeMiddleman;
+ private final NestedSet<Artifact> runtimeInputs;
+ private final NestedSet<Artifact> compilationInputs;
+ private final ImmutableSet<Artifact> linkstamps;
+ private final ImmutableList<String> linkopts;
+ private final LinkTargetType linkType;
+ private final LinkStaticness linkStaticness;
+ private final boolean fake;
+ private final boolean isNativeDeps;
+ private final boolean useTestOnlyFlags;
+
+ /**
+ * Given a {@link Builder}, creates a {@code Context} to pass to another target.
+ * Note well: Keep the Builder->Context and Context->Builder transforms consistent!
+ * @param builder a mutable {@link CppLinkAction.Builder} to clone from
+ */
+ public Context(Builder builder) {
+ this.nonLibraries = ImmutableSet.copyOf(builder.nonLibraries);
+ this.libraries = NestedSetBuilder.<LibraryToLink>linkOrder()
+ .addTransitive(builder.libraries.build()).build();
+ this.crosstoolInputs =
+ NestedSetBuilder.<Artifact>stableOrder().addTransitive(builder.crosstoolInputs).build();
+ this.runtimeMiddleman = builder.runtimeMiddleman;
+ this.runtimeInputs =
+ NestedSetBuilder.<Artifact>stableOrder().addTransitive(builder.runtimeInputs).build();
+ this.compilationInputs = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(builder.compilationInputs.build()).build();
+ this.linkstamps = ImmutableSet.copyOf(builder.linkstamps);
+ this.linkopts = ImmutableList.copyOf(builder.linkopts);
+ this.linkType = builder.linkType;
+ this.linkStaticness = builder.linkStaticness;
+ this.fake = builder.fake;
+ this.isNativeDeps = builder.isNativeDeps;
+ this.useTestOnlyFlags = builder.useTestOnlyFlags;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionContext.java
new file mode 100644
index 0000000000..24a936b281
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionContext.java
@@ -0,0 +1,44 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.ActionContextMarker;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.actions.ResourceSet;
+
+/**
+ * Context for executing {@link CppLinkAction}s.
+ */
+@ActionContextMarker(name = "C++ link")
+public interface CppLinkActionContext extends ActionContext {
+ /**
+ * Returns where the action actually runs.
+ */
+ String strategyLocality(CppLinkAction action);
+
+ /**
+ * Returns the estimated resource consumption of the action.
+ */
+ ResourceSet estimateResourceConsumption(CppLinkAction action);
+
+ /**
+ * Executes the specified action.
+ */
+ void exec(CppLinkAction action,
+ ActionExecutionContext actionExecutionContext)
+ throws ExecException, ActionExecutionException, InterruptedException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java
new file mode 100644
index 0000000000..44258a5ab1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java
@@ -0,0 +1,707 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.cpp.CcCompilationOutputs.Builder;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.RegexFilter;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * Representation of a C/C++ compilation. Its purpose is to share the code that creates compilation
+ * actions between all classes that need to do so. It follows the builder pattern - load up the
+ * necessary settings and then call {@link #createCcCompileActions}.
+ *
+ * <p>This class is not thread-safe, and it should only be used once for each set of source files,
+ * i.e. calling {@link #createCcCompileActions} will throw an Exception if called twice.
+ */
+public final class CppModel {
+ private final CppSemantics semantics;
+ private final RuleContext ruleContext;
+ private final BuildConfiguration configuration;
+ private final CppConfiguration cppConfiguration;
+
+ // compile model
+ private CppCompilationContext context;
+ private final List<Pair<Artifact, Label>> sourceFiles = new ArrayList<>();
+ private final List<String> copts = new ArrayList<>();
+ private final List<PathFragment> additionalIncludes = new ArrayList<>();
+ @Nullable private Pattern nocopts;
+ private boolean fake;
+ private boolean maySaveTemps;
+ private boolean onlySingleOutput;
+ private CcCompilationOutputs compilationOutputs;
+ private boolean enableLayeringCheck;
+ private boolean compileHeaderModules;
+
+ // link model
+ private final List<String> linkopts = new ArrayList<>();
+ private LinkTargetType linkType = LinkTargetType.STATIC_LIBRARY;
+ private boolean neverLink;
+ private boolean allowInterfaceSharedObjects;
+ private boolean createDynamicLibrary = true;
+ private PathFragment soImplFilename;
+ private FeatureConfiguration featureConfiguration;
+
+ public CppModel(RuleContext ruleContext, CppSemantics semantics) {
+ this.ruleContext = ruleContext;
+ this.semantics = semantics;
+ configuration = ruleContext.getConfiguration();
+ cppConfiguration = configuration.getFragment(CppConfiguration.class);
+ }
+
+ /**
+ * If the cpp compilation is a fake, then it creates only a single compile action without PIC.
+ * Defaults to false.
+ */
+ public CppModel setFake(boolean fake) {
+ this.fake = fake;
+ return this;
+ }
+
+ /**
+ * If set, the CppModel only creates a single .o output that can be linked into a dynamic library,
+ * i.e., it never generates both PIC and non-PIC outputs. Otherwise it creates outputs that can be
+ * linked into both static binaries and dynamic libraries (if both require PIC or both require
+ * non-PIC, then it still only creates a single output). Defaults to false.
+ */
+ public CppModel setOnlySingleOutput(boolean onlySingleOutput) {
+ this.onlySingleOutput = onlySingleOutput;
+ return this;
+ }
+
+ /**
+ * If set, use compiler flags to enable compiler based layering checks.
+ */
+ public CppModel setEnableLayeringCheck(boolean enableLayeringCheck) {
+ this.enableLayeringCheck = enableLayeringCheck;
+ return this;
+ }
+
+ /**
+ * If set, add actions that compile header modules to the build.
+ * See http://clang.llvm.org/docs/Modules.html for more information.
+ */
+ public CppModel setCompileHeaderModules(boolean compileHeaderModules) {
+ this.compileHeaderModules = compileHeaderModules;
+ return this;
+ }
+
+ /**
+ * Whether to create actions for temps. This defaults to false.
+ */
+ public CppModel setSaveTemps(boolean maySaveTemps) {
+ this.maySaveTemps = maySaveTemps;
+ return this;
+ }
+
+ /**
+ * Sets the compilation context, i.e. include directories and allowed header files inclusions.
+ */
+ public CppModel setContext(CppCompilationContext context) {
+ this.context = context;
+ return this;
+ }
+
+ /**
+ * Adds a single source file to be compiled. Note that this should only be called for primary
+ * compilation units, not for header files or files that are otherwise included.
+ */
+ public CppModel addSources(Iterable<Artifact> sourceFiles, Label sourceLabel) {
+ for (Artifact sourceFile : sourceFiles) {
+ this.sourceFiles.add(Pair.of(sourceFile, sourceLabel));
+ }
+ return this;
+ }
+
+ /**
+ * Adds all the source files. Note that this should only be called for primary compilation units,
+ * not for header files or files that are otherwise included.
+ */
+ public CppModel addSources(Iterable<Pair<Artifact, Label>> sources) {
+ Iterables.addAll(this.sourceFiles, sources);
+ return this;
+ }
+
+ /**
+ * Adds the given copts.
+ */
+ public CppModel addCopts(Collection<String> copts) {
+ this.copts.addAll(copts);
+ return this;
+ }
+
+ /**
+ * Sets the nocopts pattern. This is used to filter out flags from the system defined set of
+ * flags. By default no filter is applied.
+ */
+ public CppModel setNoCopts(@Nullable Pattern nocopts) {
+ this.nocopts = nocopts;
+ return this;
+ }
+
+ /**
+ * This can be used to specify additional include directories, without modifying the compilation
+ * context.
+ */
+ public CppModel addAdditionalIncludes(Collection<PathFragment> additionalIncludes) {
+ // TODO(bazel-team): Maybe this could be handled by the compilation context instead?
+ this.additionalIncludes.addAll(additionalIncludes);
+ return this;
+ }
+
+ /**
+ * Adds the given linkopts to the optional dynamic library link command.
+ */
+ public CppModel addLinkopts(Collection<String> linkopts) {
+ this.linkopts.addAll(linkopts);
+ return this;
+ }
+
+ /**
+ * Sets the link type used for the link actions. Note that only static links are supported at this
+ * time.
+ */
+ public CppModel setLinkTargetType(LinkTargetType linkType) {
+ this.linkType = linkType;
+ return this;
+ }
+
+ public CppModel setNeverLink(boolean neverLink) {
+ this.neverLink = neverLink;
+ return this;
+ }
+
+ /**
+ * Whether to allow interface dynamic libraries. Note that setting this to true only has an effect
+ * if the configuration allows it. Defaults to false.
+ */
+ public CppModel setAllowInterfaceSharedObjects(boolean allowInterfaceSharedObjects) {
+ // TODO(bazel-team): Set the default to true, and require explicit action to disable it.
+ this.allowInterfaceSharedObjects = allowInterfaceSharedObjects;
+ return this;
+ }
+
+ public CppModel setCreateDynamicLibrary(boolean createDynamicLibrary) {
+ this.createDynamicLibrary = createDynamicLibrary;
+ return this;
+ }
+
+ public CppModel setDynamicLibraryPath(PathFragment soImplFilename) {
+ this.soImplFilename = soImplFilename;
+ return this;
+ }
+
+ /**
+ * Sets the feature configuration to be used for C/C++ actions.
+ */
+ public CppModel setFeatureConfiguration(FeatureConfiguration featureConfiguration) {
+ this.featureConfiguration = featureConfiguration;
+ return this;
+ }
+
+ /**
+ * @return the non-pic header module artifact for the current target.
+ */
+ public Artifact getHeaderModule(Artifact moduleMapArtifact) {
+ PathFragment objectDir = CppHelper.getObjDirectory(ruleContext.getLabel());
+ PathFragment outputName = objectDir.getRelative(
+ semantics.getEffectiveSourcePath(moduleMapArtifact));
+ return ruleContext.getRelatedArtifact(outputName, ".pcm");
+ }
+
+ /**
+ * @return the pic header module artifact for the current target.
+ */
+ public Artifact getPicHeaderModule(Artifact moduleMapArtifact) {
+ PathFragment objectDir = CppHelper.getObjDirectory(ruleContext.getLabel());
+ PathFragment outputName = objectDir.getRelative(
+ semantics.getEffectiveSourcePath(moduleMapArtifact));
+ return ruleContext.getRelatedArtifact(outputName, ".pic.pcm");
+ }
+
+ /**
+ * @return whether this target needs to generate pic actions.
+ */
+ public boolean getGeneratePicActions() {
+ return CppHelper.usePic(ruleContext, false);
+ }
+
+ /**
+ * @return whether this target needs to generate non-pic actions.
+ */
+ public boolean getGenerateNoPicActions() {
+ return
+ // If we always need pic for everything, then don't bother to create a no-pic action.
+ (!CppHelper.usePic(ruleContext, true) || !CppHelper.usePic(ruleContext, false))
+ // onlySingleOutput guarantees that the code is only ever linked into a dynamic library - so
+ // we don't need a no-pic action even if linking into a binary would require it.
+ && !((onlySingleOutput && getGeneratePicActions()));
+ }
+
+ /**
+ * @return whether this target needs to generate a pic header module.
+ */
+ public boolean getGeneratesPicHeaderModule() {
+ // TODO(bazel-team): Make sure cc_fake_binary works with header module support.
+ return compileHeaderModules && !fake && getGeneratePicActions();
+ }
+
+ /**
+ * @return whether this target needs to generate a non-pic header module.
+ */
+ public boolean getGeratesNoPicHeaderModule() {
+ return compileHeaderModules && !fake && getGenerateNoPicActions();
+ }
+
+ /**
+ * Returns a {@code CppCompileActionBuilder} with the common fields for a C++ compile action
+ * being initialized.
+ */
+ private CppCompileActionBuilder initializeCompileAction(Artifact sourceArtifact,
+ Label sourceLabel) {
+ CppCompileActionBuilder builder = createCompileActionBuilder(sourceArtifact, sourceLabel);
+ if (nocopts != null) {
+ builder.addNocopts(nocopts);
+ }
+
+ builder.setEnableLayeringCheck(enableLayeringCheck);
+ builder.setCompileHeaderModules(compileHeaderModules);
+ builder.setExtraSystemIncludePrefixes(additionalIncludes);
+ builder.setFdoBuildStamp(CppHelper.getFdoBuildStamp(cppConfiguration));
+ builder.setFeatureConfiguration(featureConfiguration);
+ return builder;
+ }
+
+ /**
+ * Constructs the C++ compiler actions. It generally creates one action for every specified source
+ * file. It takes into account LIPO, fake-ness, coverage, and PIC, in addition to using the
+ * settings specified on the current object. This method should only be called once.
+ */
+ public CcCompilationOutputs createCcCompileActions() {
+ CcCompilationOutputs.Builder result = new CcCompilationOutputs.Builder();
+ Preconditions.checkNotNull(context);
+ AnalysisEnvironment env = ruleContext.getAnalysisEnvironment();
+ PathFragment objectDir = CppHelper.getObjDirectory(ruleContext.getLabel());
+
+ if (compileHeaderModules) {
+ Artifact moduleMapArtifact = context.getCppModuleMap().getArtifact();
+ Label moduleMapLabel = Label.parseAbsoluteUnchecked(context.getCppModuleMap().getName());
+ PathFragment outputName = getObjectOutputPath(moduleMapArtifact, objectDir);
+ CppCompileActionBuilder builder = initializeCompileAction(moduleMapArtifact, moduleMapLabel);
+
+ // A header module compile action is just like a normal compile action, but:
+ // - the compiled source file is the module map
+ // - it creates a header module (.pcm file).
+ createSourceAction(outputName, result, env, moduleMapArtifact, builder, ".pcm");
+ }
+
+ for (Pair<Artifact, Label> source : sourceFiles) {
+ Artifact sourceArtifact = source.getFirst();
+ Label sourceLabel = source.getSecond();
+ PathFragment outputName = getObjectOutputPath(sourceArtifact, objectDir);
+ CppCompileActionBuilder builder = initializeCompileAction(sourceArtifact, sourceLabel);
+
+ if (CppFileTypes.CPP_HEADER.matches(source.first.getExecPath())) {
+ createHeaderAction(outputName, result, env, builder);
+ } else {
+ createSourceAction(outputName, result, env, sourceArtifact, builder, ".o");
+ }
+ }
+
+ compilationOutputs = result.build();
+ return compilationOutputs;
+ }
+
+ private void createHeaderAction(PathFragment outputName, Builder result, AnalysisEnvironment env,
+ CppCompileActionBuilder builder) {
+ builder.setOutputFile(ruleContext.getRelatedArtifact(outputName, ".h.processed")).setDotdFile(
+ outputName, ".h.d", ruleContext);
+ semantics.finalizeCompileActionBuilder(ruleContext, builder);
+ CppCompileAction compileAction = builder.build();
+ env.registerAction(compileAction);
+ Artifact tokenFile = compileAction.getOutputFile();
+ result.addHeaderTokenFile(tokenFile);
+ }
+
+ private void createSourceAction(PathFragment outputName,
+ CcCompilationOutputs.Builder result,
+ AnalysisEnvironment env,
+ Artifact sourceArtifact,
+ CppCompileActionBuilder builder,
+ String outputExtension) {
+ PathFragment ccRelativeName = semantics.getEffectiveSourcePath(sourceArtifact);
+ LipoContextProvider lipoProvider = null;
+ if (cppConfiguration.isLipoOptimization()) {
+ // TODO(bazel-team): we shouldn't be needing this, merging context with the binary
+ // is a superset of necessary information.
+ lipoProvider = Preconditions.checkNotNull(CppHelper.getLipoContextProvider(ruleContext),
+ outputName);
+ builder.setContext(CppCompilationContext.mergeForLipo(lipoProvider.getLipoContext(),
+ context));
+ }
+ if (fake) {
+ // For cc_fake_binary, we only create a single fake compile action. It's
+ // not necessary to use -fPIC for negative compilation tests, and using
+ // .pic.o files in cc_fake_binary would break existing uses of
+ // cc_fake_binary.
+ Artifact outputFile = ruleContext.getRelatedArtifact(outputName, outputExtension);
+ PathFragment tempOutputName =
+ FileSystemUtils.replaceExtension(outputFile.getExecPath(), ".temp" + outputExtension);
+ builder
+ .setOutputFile(outputFile)
+ .setDotdFile(outputName, ".d", ruleContext)
+ .setTempOutputFile(tempOutputName);
+ semantics.finalizeCompileActionBuilder(ruleContext, builder);
+ CppCompileAction action = builder.build();
+ env.registerAction(action);
+ result.addObjectFile(action.getOutputFile());
+ } else {
+ boolean generatePicAction = getGeneratePicActions();
+ // If we always need pic for everything, then don't bother to create a no-pic action.
+ boolean generateNoPicAction = getGenerateNoPicActions();
+ Preconditions.checkState(generatePicAction || generateNoPicAction);
+
+ // Create PIC compile actions (same as non-PIC, but use -fPIC and
+ // generate .pic.o, .pic.d, .pic.gcno instead of .o, .d, .gcno.)
+ if (generatePicAction) {
+ CppCompileActionBuilder picBuilder = copyAsPicBuilder(builder, outputName, outputExtension);
+ cppConfiguration.getFdoSupport().configureCompilation(picBuilder, ruleContext, env,
+ ruleContext.getLabel(), ccRelativeName, nocopts, /*usePic=*/true,
+ lipoProvider);
+
+ if (maySaveTemps) {
+ result.addTemps(
+ createTempsActions(sourceArtifact, outputName, picBuilder, /*usePic=*/true));
+ }
+
+ if (isCodeCoverageEnabled()) {
+ picBuilder.setGcnoFile(ruleContext.getRelatedArtifact(outputName, ".pic.gcno"));
+ }
+
+ semantics.finalizeCompileActionBuilder(ruleContext, picBuilder);
+ CppCompileAction picAction = picBuilder.build();
+ env.registerAction(picAction);
+ result.addPicObjectFile(picAction.getOutputFile());
+ if (picAction.getDwoFile() != null) {
+ // Host targets don't produce .dwo files.
+ result.addPicDwoFile(picAction.getDwoFile());
+ }
+ if (cppConfiguration.isLipoContextCollector() && !generateNoPicAction) {
+ result.addLipoScannable(picAction);
+ }
+ }
+
+ if (generateNoPicAction) {
+ builder
+ .setOutputFile(ruleContext.getRelatedArtifact(outputName, outputExtension))
+ .setDotdFile(outputName, ".d", ruleContext);
+ // Create non-PIC compile actions
+ cppConfiguration.getFdoSupport().configureCompilation(builder, ruleContext, env,
+ ruleContext.getLabel(), ccRelativeName, nocopts, /*usePic=*/false,
+ lipoProvider);
+
+ if (maySaveTemps) {
+ result.addTemps(
+ createTempsActions(sourceArtifact, outputName, builder, /*usePic=*/false));
+ }
+
+ if (!cppConfiguration.isLipoOptimization() && isCodeCoverageEnabled()) {
+ builder.setGcnoFile(ruleContext.getRelatedArtifact(outputName, ".gcno"));
+ }
+
+ semantics.finalizeCompileActionBuilder(ruleContext, builder);
+ CppCompileAction compileAction = builder.build();
+ env.registerAction(compileAction);
+ Artifact objectFile = compileAction.getOutputFile();
+ result.addObjectFile(objectFile);
+ if (compileAction.getDwoFile() != null) {
+ // Host targets don't produce .dwo files.
+ result.addDwoFile(compileAction.getDwoFile());
+ }
+ if (cppConfiguration.isLipoContextCollector()) {
+ result.addLipoScannable(compileAction);
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructs the C++ linker actions. It generally generates two actions, one for a static library
+ * and one for a dynamic library. If PIC is required for shared libraries, but not for binaries,
+ * it additionally creates a third action to generate a PIC static library.
+ *
+ * <p>For dynamic libraries, this method can additionally create an interface shared library that
+ * can be used for linking, but doesn't contain any executable code. This increases the number of
+ * cache hits for link actions. Call {@link #setAllowInterfaceSharedObjects(boolean)} to enable
+ * this behavior.
+ */
+ public CcLinkingOutputs createCcLinkActions(CcCompilationOutputs ccOutputs) {
+ // For now only handle static links. Note that the dynamic library link below ignores linkType.
+ // TODO(bazel-team): Either support non-static links or move this check to setLinkType().
+ Preconditions.checkState(linkType.isStaticLibraryLink(), "can only handle static links");
+
+ CcLinkingOutputs.Builder result = new CcLinkingOutputs.Builder();
+ if (cppConfiguration.isLipoContextCollector()) {
+ // Don't try to create LIPO link actions in collector mode,
+ // because it needs some data that's not available at this point.
+ return result.build();
+ }
+
+ AnalysisEnvironment env = ruleContext.getAnalysisEnvironment();
+ boolean usePicForBinaries = CppHelper.usePic(ruleContext, true);
+ boolean usePicForSharedLibs = CppHelper.usePic(ruleContext, false);
+
+ // Create static library (.a). The linkType only reflects whether the library is alwayslink or
+ // not. The PIC-ness is determined by whether we need to use PIC or not. There are three cases
+ // for (usePicForSharedLibs usePicForBinaries):
+ //
+ // (1) (false false) -> no pic code
+ // (2) (true false) -> shared libraries as pic, but not binaries
+ // (3) (true true) -> both shared libraries and binaries as pic
+ //
+ // In case (3), we always need PIC, so only create one static library containing the PIC object
+ // files. The name therefore does not match the content.
+ //
+ // Presumably, it is done this way because the .a file is an implicit output of every cc_library
+ // rule, so we can't use ".pic.a" that in the always-PIC case.
+ PathFragment linkedFileName = CppHelper.getLinkedFilename(ruleContext, linkType);
+ CppLinkAction maybePicAction = newLinkActionBuilder(linkedFileName)
+ .addNonLibraryInputs(ccOutputs.getObjectFiles(usePicForBinaries))
+ .addNonLibraryInputs(ccOutputs.getHeaderTokenFiles())
+ .setLinkType(linkType)
+ .setLinkStaticness(LinkStaticness.FULLY_STATIC)
+ .build();
+ env.registerAction(maybePicAction);
+ result.addStaticLibrary(maybePicAction.getOutputLibrary());
+
+ // Create a second static library (.pic.a). Only in case (2) do we need both PIC and non-PIC
+ // static libraries. In that case, the first static library contains the non-PIC code, and this
+ // one contains the PIC code, so the names match the content.
+ if (!usePicForBinaries && usePicForSharedLibs) {
+ LinkTargetType picLinkType = (linkType == LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY)
+ ? LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY
+ : LinkTargetType.PIC_STATIC_LIBRARY;
+
+ PathFragment picFileName = CppHelper.getLinkedFilename(ruleContext, picLinkType);
+ CppLinkAction picAction = newLinkActionBuilder(picFileName)
+ .addNonLibraryInputs(ccOutputs.getObjectFiles(true))
+ .addNonLibraryInputs(ccOutputs.getHeaderTokenFiles())
+ .setLinkType(picLinkType)
+ .setLinkStaticness(LinkStaticness.FULLY_STATIC)
+ .build();
+ env.registerAction(picAction);
+ result.addPicStaticLibrary(picAction.getOutputLibrary());
+ }
+
+ if (!createDynamicLibrary) {
+ return result.build();
+ }
+
+ // Create dynamic library.
+ if (soImplFilename == null) {
+ soImplFilename = CppHelper.getLinkedFilename(ruleContext, LinkTargetType.DYNAMIC_LIBRARY);
+ }
+ List<String> sonameLinkopts = ImmutableList.of();
+ PathFragment soInterfaceFilename = null;
+ if (cppConfiguration.useInterfaceSharedObjects() && allowInterfaceSharedObjects) {
+ soInterfaceFilename =
+ CppHelper.getLinkedFilename(ruleContext, LinkTargetType.INTERFACE_DYNAMIC_LIBRARY);
+ Artifact dynamicLibrary = env.getDerivedArtifact(
+ soImplFilename, configuration.getBinDirectory());
+ sonameLinkopts = ImmutableList.of("-Wl,-soname=" +
+ SolibSymlinkAction.getDynamicLibrarySoname(dynamicLibrary.getRootRelativePath(), false));
+ }
+
+ // Should we also link in any libraries that this library depends on?
+ // That is required on some systems...
+ CppLinkAction action = newLinkActionBuilder(soImplFilename)
+ .setInterfaceOutputPath(soInterfaceFilename)
+ .addNonLibraryInputs(ccOutputs.getObjectFiles(usePicForSharedLibs))
+ .addNonLibraryInputs(ccOutputs.getHeaderTokenFiles())
+ .setLinkType(LinkTargetType.DYNAMIC_LIBRARY)
+ .setLinkStaticness(LinkStaticness.DYNAMIC)
+ .addLinkopts(linkopts)
+ .addLinkopts(sonameLinkopts)
+ .setRuntimeInputs(
+ CppHelper.getToolchain(ruleContext).getDynamicRuntimeLinkMiddleman(),
+ CppHelper.getToolchain(ruleContext).getDynamicRuntimeLinkInputs())
+ .build();
+ env.registerAction(action);
+
+ LibraryToLink dynamicLibrary = action.getOutputLibrary();
+ LibraryToLink interfaceLibrary = action.getInterfaceOutputLibrary();
+ if (interfaceLibrary == null) {
+ interfaceLibrary = dynamicLibrary;
+ }
+
+ // If shared library has neverlink=1, then leave it untouched. Otherwise,
+ // create a mangled symlink for it and from now on reference it through
+ // mangled name only.
+ if (neverLink) {
+ result.addDynamicLibrary(interfaceLibrary);
+ result.addExecutionDynamicLibrary(dynamicLibrary);
+ } else {
+ LibraryToLink libraryLink = SolibSymlinkAction.getDynamicLibrarySymlink(
+ ruleContext, interfaceLibrary.getArtifact(), false, false,
+ ruleContext.getConfiguration());
+ result.addDynamicLibrary(libraryLink);
+ LibraryToLink implLibraryLink = SolibSymlinkAction.getDynamicLibrarySymlink(
+ ruleContext, dynamicLibrary.getArtifact(), false, false,
+ ruleContext.getConfiguration());
+ result.addExecutionDynamicLibrary(implLibraryLink);
+ }
+ return result.build();
+ }
+
+ private CppLinkAction.Builder newLinkActionBuilder(PathFragment outputPath) {
+ return new CppLinkAction.Builder(ruleContext, outputPath)
+ .setCrosstoolInputs(CppHelper.getToolchain(ruleContext).getLink())
+ .addNonLibraryInputs(context.getCompilationPrerequisites());
+ }
+
+ /**
+ * Returns the output artifact path relative to the object directory.
+ */
+ private PathFragment getObjectOutputPath(Artifact source, PathFragment objectDirectory) {
+ return objectDirectory.getRelative(semantics.getEffectiveSourcePath(source));
+ }
+
+ /**
+ * Creates a basic cpp compile action builder for source file. Configures options,
+ * crosstool inputs, output and dotd file names, compilation context and copts.
+ */
+ private CppCompileActionBuilder createCompileActionBuilder(
+ Artifact source, Label label) {
+ CppCompileActionBuilder builder = new CppCompileActionBuilder(
+ ruleContext, source, label);
+
+ builder
+ .setContext(context)
+ .addCopts(copts);
+ return builder;
+ }
+
+ /**
+ * Creates cpp PIC compile action builder from the given builder by adding necessary copt and
+ * changing output and dotd file names.
+ */
+ private CppCompileActionBuilder copyAsPicBuilder(CppCompileActionBuilder builder,
+ PathFragment outputName, String outputExtension) {
+ CppCompileActionBuilder picBuilder = new CppCompileActionBuilder(builder);
+ picBuilder.addCopt("-fPIC")
+ .setOutputFile(ruleContext.getRelatedArtifact(outputName, ".pic" + outputExtension))
+ .setDotdFile(outputName, ".pic.d", ruleContext);
+ return picBuilder;
+ }
+
+ /**
+ * Create the actions for "--save_temps".
+ */
+ private ImmutableList<Artifact> createTempsActions(Artifact source, PathFragment outputName,
+ CppCompileActionBuilder builder, boolean usePic) {
+ if (!cppConfiguration.getSaveTemps()) {
+ return ImmutableList.of();
+ }
+
+ String path = source.getFilename();
+ boolean isCFile = CppFileTypes.C_SOURCE.matches(path);
+ boolean isCppFile = CppFileTypes.CPP_SOURCE.matches(path);
+
+ if (!isCFile && !isCppFile) {
+ return ImmutableList.of();
+ }
+
+ String iExt = isCFile ? ".i" : ".ii";
+ String picExt = usePic ? ".pic" : "";
+ CppCompileActionBuilder dBuilder = new CppCompileActionBuilder(builder);
+ CppCompileActionBuilder sdBuilder = new CppCompileActionBuilder(builder);
+
+ dBuilder
+ .setOutputFile(ruleContext.getRelatedArtifact(outputName, picExt + iExt))
+ .setDotdFile(outputName, picExt + iExt + ".d", ruleContext);
+ semantics.finalizeCompileActionBuilder(ruleContext, dBuilder);
+ CppCompileAction dAction = dBuilder.build();
+ ruleContext.registerAction(dAction);
+
+ sdBuilder
+ .setOutputFile(ruleContext.getRelatedArtifact(outputName, picExt + ".s"))
+ .setDotdFile(outputName, picExt + ".s.d", ruleContext);
+ semantics.finalizeCompileActionBuilder(ruleContext, sdBuilder);
+ CppCompileAction sdAction = sdBuilder.build();
+ ruleContext.registerAction(sdAction);
+ return ImmutableList.of(
+ dAction.getOutputFile(),
+ sdAction.getOutputFile());
+ }
+
+ /**
+ * Returns true iff code coverage is enabled for the given target.
+ */
+ private boolean isCodeCoverageEnabled() {
+ if (configuration.isCodeCoverageEnabled()) {
+ final RegexFilter filter = configuration.getInstrumentationFilter();
+ // If rule is matched by the instrumentation filter, enable instrumentation
+ if (filter.isIncluded(ruleContext.getLabel().toString())) {
+ return true;
+ }
+ // At this point the rule itself is not matched by the instrumentation filter. However, we
+ // might still want to instrument C++ rules if one of the targets listed in "deps" is
+ // instrumented and, therefore, can supply header files that we would want to collect code
+ // coverage for. For example, think about cc_test rule that tests functionality defined in a
+ // header file that is supplied by the cc_library.
+ //
+ // Note that we only check direct prerequisites and not the transitive closure. This is done
+ // for two reasons:
+ // a) It is a good practice to declare libraries which you directly rely on. Including headers
+ // from a library hidden deep inside the transitive closure makes build dependencies less
+ // readable and can lead to unexpected breakage.
+ // b) Traversing the transitive closure for each C++ compile action would require more complex
+ // implementation (with caching results of this method) to avoid O(N^2) slowdown.
+ if (ruleContext.getRule().isAttrDefined("deps", Type.LABEL_LIST)) {
+ for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("deps", Mode.TARGET)) {
+ if (dep.getProvider(CppCompilationContext.class) != null
+ && filter.isIncluded(dep.getLabel().toString())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMap.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMap.java
new file mode 100644
index 0000000000..bb272098a4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMap.java
@@ -0,0 +1,44 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Structure for C++ module maps. Stores the name of the module and a .cppmap artifact.
+ */
+@Immutable
+public class CppModuleMap {
+ private final Artifact artifact;
+ private final String name;
+
+ public CppModuleMap(Artifact artifact, String name) {
+ this.artifact = artifact;
+ this.name = name;
+ }
+
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name + "@" + artifact;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java
new file mode 100644
index 0000000000..a350fc4618
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModuleMapAction.java
@@ -0,0 +1,185 @@
+// Copyright 2014 Google Inc. 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.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Creates C++ module map artifact genfiles. These are then passed to Clang to
+ * do dependency checking.
+ */
+public class CppModuleMapAction extends AbstractFileWriteAction {
+
+ private static final String GUID = "4f407081-1951-40c1-befc-d6b4daff5de3";
+
+ // C++ module map of the current target
+ private final CppModuleMap cppModuleMap;
+
+ /**
+ * If set, the paths in the module map are relative to the current working directory instead
+ * of relative to the module map file's location.
+ */
+ private final boolean moduleMapHomeIsCwd;
+
+ // Headers and dependencies list
+ private final ImmutableList<Artifact> privateHeaders;
+ private final ImmutableList<Artifact> publicHeaders;
+ private final ImmutableList<CppModuleMap> dependencies;
+ private final ImmutableList<PathFragment> additionalExportedHeaders;
+ private final boolean compiledModule;
+
+ public CppModuleMapAction(ActionOwner owner, CppModuleMap cppModuleMap,
+ Iterable<Artifact> privateHeaders, Iterable<Artifact> publicHeaders,
+ Iterable<CppModuleMap> dependencies, Iterable<PathFragment> additionalExportedHeaders,
+ boolean compiledModule, boolean moduleMapHomeIsCwd) {
+ super(owner, ImmutableList.<Artifact>of(), cppModuleMap.getArtifact(),
+ /*makeExecutable=*/false);
+ this.cppModuleMap = cppModuleMap;
+ this.moduleMapHomeIsCwd = moduleMapHomeIsCwd;
+ this.privateHeaders = ImmutableList.copyOf(privateHeaders);
+ this.publicHeaders = ImmutableList.copyOf(publicHeaders);
+ this.dependencies = ImmutableList.copyOf(dependencies);
+ this.additionalExportedHeaders = ImmutableList.copyOf(additionalExportedHeaders);
+ this.compiledModule = compiledModule;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ StringBuilder content = new StringBuilder();
+ PathFragment fragment = cppModuleMap.getArtifact().getExecPath();
+ int segmentsToExecPath = fragment.segmentCount() - 1;
+
+ // For details about the different header types, see:
+ // http://clang.llvm.org/docs/Modules.html#header-declaration
+ String leadingPeriods = moduleMapHomeIsCwd ? "" : Strings.repeat("../", segmentsToExecPath);
+ content.append("module \"").append(cppModuleMap.getName()).append("\" {\n");
+ content.append(" export *\n");
+ for (Artifact artifact : privateHeaders) {
+ appendHeader(content, "private", artifact.getExecPath(), leadingPeriods,
+ /*canCompile=*/true);
+ }
+ for (Artifact artifact : publicHeaders) {
+ appendHeader(content, "", artifact.getExecPath(), leadingPeriods, /*canCompile=*/true);
+ }
+ for (PathFragment additionalExportedHeader : additionalExportedHeaders) {
+ appendHeader(content, "", additionalExportedHeader, leadingPeriods, /*canCompile*/false);
+ }
+ for (CppModuleMap dep : dependencies) {
+ content.append(" use \"").append(dep.getName()).append("\"\n");
+ }
+ content.append("}");
+ for (CppModuleMap dep : dependencies) {
+ content.append("\nextern module \"")
+ .append(dep.getName())
+ .append("\" \"")
+ .append(leadingPeriods)
+ .append(dep.getArtifact().getExecPath())
+ .append("\"");
+ }
+ out.write(content.toString().getBytes(StandardCharsets.ISO_8859_1));
+ }
+ };
+ }
+
+ private void appendHeader(StringBuilder content, String visibilitySpecifier, PathFragment path,
+ String leadingPeriods, boolean canCompile) {
+ content.append(" ");
+ if (!visibilitySpecifier.isEmpty()) {
+ content.append(visibilitySpecifier).append(" ");
+ }
+ if (!canCompile || !shouldCompileHeader(path)) {
+ content.append("textual ");
+ }
+ content.append("header \"").append(leadingPeriods).append(path).append("\"\n");
+ }
+
+ private boolean shouldCompileHeader(PathFragment path) {
+ return compiledModule && !CppFileTypes.CPP_TEXTUAL_INCLUDE.matches(path);
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "CppModuleMap";
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addInt(privateHeaders.size());
+ for (Artifact artifact : privateHeaders) {
+ f.addPath(artifact.getRootRelativePath());
+ }
+ f.addInt(publicHeaders.size());
+ for (Artifact artifact : publicHeaders) {
+ f.addPath(artifact.getRootRelativePath());
+ }
+ f.addInt(dependencies.size());
+ for (CppModuleMap dep : dependencies) {
+ f.addPath(dep.getArtifact().getExecPath());
+ }
+ f.addPath(cppModuleMap.getArtifact().getExecPath());
+ f.addString(cppModuleMap.getName());
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumptionLocal() {
+ return new ResourceSet(/*memoryMb=*/0, /*cpuUsage=*/0, /*ioUsage=*/0.02);
+ }
+
+ @VisibleForTesting
+ public Collection<Artifact> getPublicHeaders() {
+ return publicHeaders;
+ }
+
+ @VisibleForTesting
+ public Collection<Artifact> getPrivateHeaders() {
+ return privateHeaders;
+ }
+
+ @VisibleForTesting
+ public ImmutableList<PathFragment> getAdditionalExportedHeaders() {
+ return additionalExportedHeaders;
+ }
+
+ @VisibleForTesting
+ public Collection<Artifact> getDependencyArtifacts() {
+ List<Artifact> artifacts = new ArrayList<>();
+ for (CppModuleMap map : dependencies) {
+ artifacts.add(map.getArtifact());
+ }
+ return artifacts;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
new file mode 100644
index 0000000000..b0f2e820a5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -0,0 +1,646 @@
+// Copyright 2014 Google Inc. 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.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.LabelConverter;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.LibcTop;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.StripMode;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.util.OptionsUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.Converters;
+import com.google.devtools.common.options.EnumConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Command-line options for C++.
+ */
+public class CppOptions extends FragmentOptions {
+ /**
+ * Label of a filegroup that contains all crosstool files for all configurations.
+ */
+ @VisibleForTesting
+ public static final String DEFAULT_CROSSTOOL_TARGET = "//tools/cpp:toolchain";
+
+
+ /**
+ * Converter for --cwarn flag
+ */
+ public static class GccWarnConverter implements Converter<String> {
+ @Override
+ public String convert(String input) throws OptionsParsingException {
+ if (input.startsWith("no-") || input.startsWith("-W")) {
+ throw new OptionsParsingException("Not a valid gcc warning to enable");
+ }
+ return input;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "A gcc warning to enable";
+ }
+ }
+
+ /**
+ * Converts a comma-separated list of compilation mode settings to a properly typed List.
+ */
+ public static class FissionOptionConverter implements Converter<List<CompilationMode>> {
+ @Override
+ public List<CompilationMode> convert(String input) throws OptionsParsingException {
+ ImmutableSet.Builder<CompilationMode> modes = ImmutableSet.builder();
+ if (input.equals("yes")) { // Special case: enable all modes.
+ modes.add(CompilationMode.values());
+ } else if (!input.equals("no")) { // "no" is another special case that disables all modes.
+ CompilationMode.Converter modeConverter = new CompilationMode.Converter();
+ for (String mode : Splitter.on(',').split(input)) {
+ modes.add(modeConverter.convert(mode));
+ }
+ }
+ return modes.build().asList();
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a set of compilation modes";
+ }
+ }
+
+ /**
+ * The same as DynamicMode, but on command-line we also allow AUTO.
+ */
+ public enum DynamicModeFlag { OFF, DEFAULT, FULLY, AUTO }
+
+ /**
+ * Converter for DynamicModeFlag
+ */
+ public static class DynamicModeConverter extends EnumConverter<DynamicModeFlag> {
+ public DynamicModeConverter() {
+ super(DynamicModeFlag.class, "dynamic mode");
+ }
+ }
+
+ /**
+ * Converter for the --strip option.
+ */
+ public static class StripModeConverter extends EnumConverter<StripMode> {
+ public StripModeConverter() {
+ super(StripMode.class, "strip mode");
+ }
+ }
+
+ private static final String LIBC_RELATIVE_LABEL = ":everything";
+
+ /**
+ * Converts a String, which is an absolute path or label into a LibcTop
+ * object.
+ */
+ public static class LibcTopConverter implements Converter<LibcTop> {
+ @Override
+ public LibcTop convert(String input) throws OptionsParsingException {
+ if (!input.startsWith("//")) {
+ throw new OptionsParsingException("Not a label");
+ }
+ try {
+ Label label = Label.parseAbsolute(input).getRelative(LIBC_RELATIVE_LABEL);
+ return new LibcTop(label);
+ } catch (SyntaxException e) {
+ throw new OptionsParsingException(e.getMessage());
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a label";
+ }
+ }
+
+ /**
+ * Converter for the --hdrs_check option.
+ */
+ public static class HdrsCheckConverter extends EnumConverter<HeadersCheckingMode> {
+ public HdrsCheckConverter() {
+ super(HeadersCheckingMode.class, "Headers check mode");
+ }
+ }
+
+ /**
+ * Checks whether a string is a valid regex pattern and compiles it.
+ */
+ public static class NullableRegexPatternConverter implements Converter<Pattern> {
+
+ @Override
+ public Pattern convert(String input) throws OptionsParsingException {
+ if (input.isEmpty()) {
+ return null;
+ }
+ try {
+ return Pattern.compile(input);
+ } catch (PatternSyntaxException e) {
+ throw new OptionsParsingException("Not a valid regular expression: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a valid Java regular expression";
+ }
+ }
+
+ /**
+ * Converter for the --lipo option.
+ */
+ public static class LipoModeConverter extends EnumConverter<LipoMode> {
+ public LipoModeConverter() {
+ super(LipoMode.class, "LIPO mode");
+ }
+ }
+
+ @Option(name = "lipo input collector",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Internal flag, only used to create configurations with the LIPO-collector flag set.")
+ public boolean lipoCollector;
+
+ @Option(name = "crosstool_top",
+ defaultValue = CppOptions.DEFAULT_CROSSTOOL_TARGET,
+ category = "version",
+ converter = LabelConverter.class,
+ help = "The label of the crosstool package to be used for compiling C++ code.")
+ public Label crosstoolTop;
+
+ @Option(name = "compiler",
+ defaultValue = "null",
+ category = "version",
+ help = "The C++ compiler to use for compiling the target.")
+ public String cppCompiler;
+
+ @Option(name = "glibc",
+ defaultValue = "null",
+ category = "version",
+ help = "The version of glibc the target should be linked against. "
+ + "By default, a suitable version is chosen based on --cpu.")
+ public String glibc;
+
+ @Option(name = "thin_archives",
+ defaultValue = "false",
+ category = "strategy", // but also adds edges to the action graph
+ help = "Pass the 'T' flag to ar if supported by the toolchain. " +
+ "All supported toolchains support this setting.")
+ public boolean useThinArchives;
+
+ // O intrepid reaper of unused options: Be warned that the [no]start_end_lib
+ // option, however tempting to remove, has a use case. Look in our telemetry data.
+ @Option(name = "start_end_lib",
+ defaultValue = "true",
+ category = "strategy", // but also adds edges to the action graph
+ help = "Use the --start-lib/--end-lib ld options if supported by the toolchain.")
+ public boolean useStartEndLib;
+
+ @Option(name = "interface_shared_objects",
+ defaultValue = "true",
+ category = "strategy", // but also adds edges to the action graph
+ help = "Use interface shared objects if supported by the toolchain. " +
+ "All ELF toolchains currently support this setting.")
+ public boolean useInterfaceSharedObjects;
+
+ @Option(name = "cc_include_scanning",
+ defaultValue = "true",
+ category = "strategy",
+ help = "Whether to perform include scanning. Without it, your build will most likely "
+ + "fail.")
+ public boolean scanIncludes;
+
+ @Option(name = "extract_generated_inclusions",
+ defaultValue = "true",
+ category = "undocumented",
+ help = "Run grep-includes actions (used for include scanning) over " +
+ "generated headers and sources.")
+ public boolean extractInclusions;
+
+ @Option(name = "fission",
+ defaultValue = "no",
+ converter = FissionOptionConverter.class,
+ category = "semantics",
+ help = "Specifies which compilation modes use fission for C++ compilations and links. "
+ + " May be any combination of {'fastbuild', 'dbg', 'opt'} or the special values 'yes' "
+ + " to enable all modes and 'no' to disable all modes.")
+ public List<CompilationMode> fissionModes;
+
+ @Option(name = "dynamic_mode",
+ defaultValue = "default",
+ converter = DynamicModeConverter.class,
+ category = "semantics",
+ help = "Determines whether C++ binaries will be linked dynamically. 'default' means "
+ + "blaze will choose whether to link dynamically. 'fully' means all libraries "
+ + "will be linked dynamically. 'off' means that all libraries will be linked "
+ + "in mostly static mode.")
+ public DynamicModeFlag dynamicMode;
+
+ @Option(name = "force_pic",
+ defaultValue = "false",
+ category = "semantics",
+ help = "If enabled, all C++ compilations produce position-independent code (\"-fPIC\"),"
+ + " links prefer PIC pre-built libraries over non-PIC libraries, and links produce"
+ + " position-independent executables (\"-pie\").")
+ public boolean forcePic;
+
+ @Option(name = "force_ignore_dash_static",
+ defaultValue = "false",
+ category = "semantics",
+ help = "If set, '-static' options in the linkopts of cc_* rules will be ignored.")
+ public boolean forceIgnoreDashStatic;
+
+ @Option(name = "experimental_skip_static_outputs",
+ defaultValue = "false",
+ category = "semantics",
+ help = "This flag is experimental and may go away at any time. "
+ + "If true, linker output for mostly-static C++ executables is a tiny amount of "
+ + "dummy dependency information, and NOT a usable binary. Kludge, but can reduce "
+ + "network and disk I/O load (and thus, continuous build cycle times) by a lot. "
+ + "NOTE: use of this flag REQUIRES --distinct_host_configuration.")
+ public boolean skipStaticOutputs;
+
+ @Option(name = "hdrs_check",
+ allowMultiple = false,
+ defaultValue = "loose",
+ converter = HdrsCheckConverter.class,
+ category = "semantics",
+ help = "Headers check mode for rules that don't specify it explicitly using a "
+ + "hdrs_check attribute. Allowed values: 'loose' allows undeclared headers, 'warn' "
+ + "warns about undeclared headers, and 'strict' disallows them.")
+ public HeadersCheckingMode headersCheckingMode;
+
+ @Option(name = "copt",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "Additional options to pass to gcc.")
+ public List<String> coptList;
+
+ @Option(name = "cwarn",
+ converter = GccWarnConverter.class,
+ defaultValue = "",
+ category = "flags",
+ allowMultiple = true,
+ help = "Additional warnings to enable when compiling C or C++ source files.")
+ public List<String> cWarns;
+
+ @Option(name = "cxxopt",
+ defaultValue = "",
+ category = "flags",
+ allowMultiple = true,
+ help = "Additional option to pass to gcc when compiling C++ source files.")
+ public List<String> cxxoptList;
+
+ @Option(name = "conlyopt",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "Additional option to pass to gcc when compiling C source files.")
+ public List<String> conlyoptList;
+
+ @Option(name = "linkopt",
+ defaultValue = "",
+ category = "flags",
+ allowMultiple = true,
+ help = "Additional option to pass to gcc when linking.")
+ public List<String> linkoptList;
+
+ @Option(name = "stripopt",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "Additional options to pass to strip when generating a '<name>.stripped' binary.")
+ public List<String> stripoptList;
+
+ @Option(name = "custom_malloc",
+ defaultValue = "null",
+ category = "semantics",
+ help = "Specifies a custom malloc implementation. This setting overrides malloc " +
+ "attributes in build rules.",
+ converter = LabelConverter.class)
+ public Label customMalloc;
+
+ @Option(name = "cpp_module_maps",
+ defaultValue = "true",
+ category = "flags",
+ help = "If true then C++ targets create a module map based on BUILD files, and "
+ + "pass them to the compiler.")
+ public boolean cppModuleMaps;
+
+ @Option(name = "legacy_whole_archive",
+ defaultValue = "true",
+ category = "semantics",
+ help = "When on, use --whole-archive for cc_binary rules that have "
+ + "linkshared=1 and either linkstatic=1 or '-static' in linkopts. "
+ + "This is for backwards compatibility only. "
+ + "A better alternative is to use alwayslink=1 where required.")
+ public boolean legacyWholeArchive;
+
+ @Option(name = "strip",
+ defaultValue = "sometimes",
+ category = "flags",
+ help = "Specifies whether to strip binaries and shared libraries "
+ + " (using \"-Wl,--strip-debug\"). The default value of 'sometimes'"
+ + " means strip iff --compilation_mode=fastbuild.",
+ converter = StripModeConverter.class)
+ public StripMode stripBinaries;
+
+ @Option(name = "fdo_instrument",
+ defaultValue = "null",
+ converter = OptionsUtils.PathFragmentConverter.class,
+ category = "flags",
+ implicitRequirements = {"--copt=-Wno-error"},
+ help = "Generate binaries with FDO instrumentation. Specify the relative " +
+ "directory name for the .gcda files at runtime.")
+ public PathFragment fdoInstrument;
+
+ @Option(name = "fdo_optimize",
+ defaultValue = "null",
+ category = "flags",
+ help = "Use FDO profile information to optimize compilation. Specify the name " +
+ "of the zip file containing the .gcda file tree or an afdo file containing " +
+ "an auto profile. This flag also accepts files specified as labels, for " +
+ "example //foo/bar:file.afdo. Such labels must refer to input files; you may " +
+ "need to add an exports_files directive to the corresponding package to make " +
+ "the file visible to Blaze.")
+ public String fdoOptimize;
+
+ @Option(name = "autofdo_lipo_data",
+ defaultValue = "false",
+ category = "flags",
+ help = "If true then the directory name for non-LIPO targets will have a " +
+ "'-lipodata' suffix in AutoFDO mode.")
+ public boolean autoFdoLipoData;
+
+ @Option(name = "lipo",
+ defaultValue = "off",
+ converter = LipoModeConverter.class,
+ category = "flags",
+ help = "Enable LIPO optimization (lightweight inter-procedural optimization, The allowed "
+ + "values for this option are 'off' and 'binary', which enables LIPO. This option only "
+ + "has an effect when FDO is also enabled. Currently LIPO is only supported when "
+ + "building a single cc_binary rule.")
+ public LipoMode lipoMode;
+
+ @Option(name = "lipo_context",
+ defaultValue = "null",
+ category = "flags",
+ converter = LabelConverter.class,
+ implicitRequirements = {"--linkopt=-Wl,--warn-unresolved-symbols"},
+ help = "Specifies the binary from which the LIPO profile information comes.")
+ public Label lipoContext;
+
+ @Option(name = "experimental_stl",
+ converter = LabelConverter.class,
+ defaultValue = "null",
+ category = "version",
+ help = "If set, use this label instead of the default STL implementation. "
+ + "This option is EXPERIMENTAL and may go away in a future release.")
+ public Label stl;
+
+ @Option(name = "save_temps",
+ defaultValue = "false",
+ category = "what",
+ help = "If set, temporary outputs from gcc will be saved. "
+ + "These include .s files (assembler code), .i files (preprocessed C) and "
+ + ".ii files (preprocessed C++).")
+ public boolean saveTemps;
+
+ @Option(name = "per_file_copt",
+ allowMultiple = true,
+ converter = PerLabelOptions.PerLabelOptionsConverter.class,
+ defaultValue = "",
+ category = "semantics",
+ help = "Additional options to selectively pass to gcc when compiling certain files. "
+ + "This option can be passed multiple times. "
+ + "Syntax: regex_filter@option_1,option_2,...,option_n. Where regex_filter stands "
+ + "for a list of include and exclude regular expression patterns (Also see "
+ + "--instrumentation_filter). option_1 to option_n stand for "
+ + "arbitrary command line options. If an option contains a comma it has to be "
+ + "quoted with a backslash. Options can contain @. Only the first @ is used to "
+ + "split the string. Example: "
+ + "--per_file_copt=//foo/.*\\.cc,-//foo/bar\\.cc@-O0 adds the -O0 "
+ + "command line option to the gcc command line of all cc files in //foo/ "
+ + "except bar.cc.")
+ public List<PerLabelOptions> perFileCopts;
+
+ @Option(name = "host_crosstool_top",
+ defaultValue = "null",
+ converter = LabelConverter.class,
+ category = "semantics",
+ help = "By default, the --crosstool_top, --glibc, and --compiler options are also used " +
+ "for the host configuration. If this flag is provided, Blaze uses the default glibc " +
+ "and compiler for the given crosstool_top.")
+ public Label hostCrosstoolTop;
+
+ @Option(name = "host_copt",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "Additional options to pass to gcc for host tools.")
+ public List<String> hostCoptList;
+
+ @Option(name = "define",
+ converter = Converters.AssignmentConverter.class,
+ defaultValue = "",
+ category = "semantics",
+ allowMultiple = true,
+ help = "Each --define option specifies an assignment for a build variable.")
+ public List<Map.Entry<String, String>> commandLineDefinedVariables;
+
+ @Option(name = "grte_top",
+ defaultValue = "null", // The default value is chosen by the toolchain.
+ category = "version",
+ converter = LibcTopConverter.class,
+ help = "A label to a checked-in libc library. The default value is selected by the crosstool "
+ + "toolchain, and you almost never need to override it.")
+ public LibcTop libcTop;
+
+ @Option(name = "host_grte_top",
+ defaultValue = "null", // The default value is chosen by the toolchain.
+ category = "version",
+ converter = LibcTopConverter.class,
+ help = "If specified, this setting overrides the libc top-level directory (--grte_top) "
+ + "for the host configuration.")
+ public LibcTop hostLibcTop;
+
+ @Option(name = "output_symbol_counts",
+ defaultValue = "false",
+ category = "flags",
+ help = "If enabled, every C++ binary linked with gold will store the number of used "
+ + "symbols per object file in a .sc file.")
+ public boolean symbolCounts;
+
+ @Option(name = "experimental_inmemory_dotd_files",
+ defaultValue = "false",
+ category = "experimental",
+ help = "If enabled, C++ .d files will be passed through in memory directly from the remote "
+ + "build nodes instead of being written to disk.")
+ public boolean inmemoryDotdFiles;
+
+ @Option(name = "use_isystem_for_includes",
+ defaultValue = "true",
+ category = "undocumented",
+ help = "Instruct C and C++ compilations to treat 'includes' paths as system header " +
+ "paths, by translating it into -isystem instead of -I.")
+ public boolean useIsystemForIncludes;
+
+ @Option(name = "experimental_omitfp",
+ defaultValue = "false",
+ category = "semantics",
+ help = "If true, use libunwind for stack unwinding, and compile with " +
+ "-fomit-frame-pointer and -fasynchronous-unwind-tables.")
+ public boolean experimentalOmitfp;
+
+ @Option(name = "share_native_deps",
+ defaultValue = "true",
+ category = "strategy",
+ help = "If true, native libraries that contain identical functionality "
+ + "will be shared among different targets")
+ public boolean shareNativeDeps;
+
+ @Override
+ public FragmentOptions getHost(boolean fallback) {
+ CppOptions host = (CppOptions) getDefault();
+
+ host.commandLineDefinedVariables = commandLineDefinedVariables;
+
+ // The crosstool options are partially copied from the target configuration.
+ if (!fallback) {
+ if (hostCrosstoolTop == null) {
+ host.cppCompiler = cppCompiler;
+ host.crosstoolTop = crosstoolTop;
+ host.glibc = glibc;
+ } else {
+ host.crosstoolTop = hostCrosstoolTop;
+ }
+ }
+
+ if (hostLibcTop != null) {
+ host.libcTop = hostLibcTop;
+ } else if (hostCrosstoolTop == null) {
+ // Track libc in the host configuration if no host crosstool is set.
+ host.libcTop = libcTop;
+ }
+
+ // -g0 is the default, but allowMultiple options cannot have default values so we just pass
+ // -g0 first and let the user options override it.
+ host.coptList = ImmutableList.<String>builder().add("-g0").addAll(hostCoptList).build();
+
+ host.useThinArchives = useThinArchives;
+ host.useStartEndLib = useStartEndLib;
+ host.extractInclusions = extractInclusions;
+ host.stripBinaries = StripMode.ALWAYS;
+ host.fdoOptimize = null;
+ host.lipoMode = LipoMode.OFF;
+ host.scanIncludes = scanIncludes;
+ host.inmemoryDotdFiles = inmemoryDotdFiles;
+ host.cppModuleMaps = cppModuleMaps;
+
+ return host;
+ }
+
+ @Override
+ public void addAllLabels(Multimap<String, Label> labelMap) {
+ labelMap.put("crosstool", crosstoolTop);
+ if (hostCrosstoolTop != null) {
+ labelMap.put("crosstool", hostCrosstoolTop);
+ }
+
+ if (libcTop != null) {
+ Label libcLabel = libcTop.getLabel();
+ if (libcLabel != null) {
+ labelMap.put("crosstool", libcLabel);
+ }
+ }
+ addOptionalLabel(labelMap, "fdo", fdoOptimize);
+
+ if (stl != null) {
+ labelMap.put("STL", stl);
+ }
+
+ if (customMalloc != null) {
+ labelMap.put("custom_malloc", customMalloc);
+ }
+
+ if (getLipoContextLabel() != null) {
+ labelMap.put("lipo", getLipoContextLabel());
+ }
+ }
+
+ @Override
+ public Map<String, Set<Label>> getDefaultsLabels(BuildConfiguration.Options commonOptions) {
+ Set<Label> crosstoolLabels = new LinkedHashSet<>();
+ crosstoolLabels.add(crosstoolTop);
+ if (hostCrosstoolTop != null) {
+ crosstoolLabels.add(hostCrosstoolTop);
+ }
+
+ if (libcTop != null) {
+ Label libcLabel = libcTop.getLabel();
+ if (libcLabel != null) {
+ crosstoolLabels.add(libcLabel);
+ }
+ }
+
+ return ImmutableMap.of(
+ "CROSSTOOL", crosstoolLabels,
+ "COVERAGE", ImmutableSet.<Label>of());
+ }
+
+ public boolean isFdo() {
+ return fdoOptimize != null || fdoInstrument != null;
+ }
+
+ public boolean isLipoOptimization() {
+ return lipoMode == LipoMode.BINARY && fdoOptimize != null && lipoContext != null;
+ }
+
+ public boolean isLipoOptimizationOrInstrumentation() {
+ return lipoMode == LipoMode.BINARY &&
+ ((fdoOptimize != null && lipoContext != null) || fdoInstrument != null);
+ }
+
+ public Label getLipoContextLabel() {
+ return (lipoMode == LipoMode.BINARY && fdoOptimize != null)
+ ? lipoContext : null;
+ }
+
+ public LipoMode getLipoMode() {
+ return lipoMode;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
new file mode 100644
index 0000000000..de7c95d4a8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
@@ -0,0 +1,104 @@
+// Copyright 2014 Google Inc. 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 static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.ALWAYS_LINK_LIBRARY;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.ALWAYS_LINK_PIC_LIBRARY;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.ARCHIVE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.ASSEMBLER_WITH_C_PREPROCESSOR;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.CPP_HEADER;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.CPP_SOURCE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.C_SOURCE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.OBJECT_FILE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.PIC_ARCHIVE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.PIC_OBJECT_FILE;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.SHARED_LIBRARY;
+import static com.google.devtools.build.lib.rules.cpp.CppFileTypes.VERSIONED_SHARED_LIBRARY;
+
+import com.google.devtools.build.lib.analysis.LanguageDependentFragment.LibraryLanguage;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+/**
+ * Rule class definitions for C++ rules.
+ */
+public class CppRuleClasses {
+ // Artifacts of these types are discarded from the 'hdrs' attribute in cc rules
+ static final FileTypeSet DISALLOWED_HDRS_FILES = FileTypeSet.of(
+ ARCHIVE,
+ PIC_ARCHIVE,
+ ALWAYS_LINK_LIBRARY,
+ ALWAYS_LINK_PIC_LIBRARY,
+ SHARED_LIBRARY,
+ VERSIONED_SHARED_LIBRARY,
+ OBJECT_FILE,
+ PIC_OBJECT_FILE);
+
+ /**
+ * The set of instrumented source file types; keep this in sync with the list above. Note that
+ * extension-less header files cannot currently be declared, so we cannot collect coverage for
+ * those.
+ */
+ static final InstrumentationSpec INSTRUMENTATION_SPEC = new InstrumentationSpec(
+ FileTypeSet.of(CPP_SOURCE, C_SOURCE, CPP_HEADER, ASSEMBLER_WITH_C_PREPROCESSOR),
+ "srcs", "deps", "data", "hdrs", "implements", "implementation");
+
+ public static final LibraryLanguage LANGUAGE = new LibraryLanguage("C++");
+
+ /**
+ * Implicit outputs for cc_binary rules.
+ */
+ public static final SafeImplicitOutputsFunction CC_BINARY_STRIPPED =
+ fromTemplates("%{name}.stripped");
+
+
+ // Used for requesting dwp "debug packages".
+ public static final SafeImplicitOutputsFunction CC_BINARY_DEBUG_PACKAGE =
+ fromTemplates("%{name}.dwp");
+
+
+ /**
+ * Path of the build_interface_so script in the Blaze binary.
+ */
+ public static final String BUILD_INTERFACE_SO = "build_interface_so";
+
+ /**
+ * A string constant for the layering_check feature.
+ */
+ public static final String LAYERING_CHECK = "layering_check";
+
+ /**
+ * A string constant for the parse_headers feature.
+ */
+ public static final String PARSE_HEADERS = "parse_headers";
+
+ /**
+ * A string constant for the preprocess_headers feature.
+ */
+ public static final String PREPROCESS_HEADERS = "preprocess_headers";
+
+ /**
+ * A string constant for the header_modules feature.
+ */
+ public static final String HEADER_MODULES = "header_modules";
+
+ /**
+ * A string constant for the module_map_home_cwd feature.
+ */
+ public static final String MODULE_MAP_HOME_CWD = "module_map_home_cwd";
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRunfilesProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRunfilesProvider.java
new file mode 100644
index 0000000000..f4aa38cb1f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRunfilesProvider.java
@@ -0,0 +1,85 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Runfiles provider for C++ targets.
+ *
+ * <p>Contains two {@link Runfiles} objects: one for the eventual statically linked binary and
+ * one for the one that uses shared libraries. Data dependencies are present in both.
+ */
+@Immutable
+public final class CppRunfilesProvider implements TransitiveInfoProvider {
+ private final Runfiles staticRunfiles;
+ private final Runfiles sharedRunfiles;
+
+ public CppRunfilesProvider(Runfiles staticRunfiles, Runfiles sharedRunfiles) {
+ this.staticRunfiles = staticRunfiles;
+ this.sharedRunfiles = sharedRunfiles;
+ }
+
+ public Runfiles getStaticRunfiles() {
+ return staticRunfiles;
+ }
+
+ public Runfiles getSharedRunfiles() {
+ return sharedRunfiles;
+ }
+
+ /**
+ * Returns a function that gets the static C++ runfiles from a {@link TransitiveInfoCollection}
+ * or the empty runfiles instance if it does not contain that provider.
+ */
+ public static final Function<TransitiveInfoCollection, Runfiles> STATIC_RUNFILES =
+ new Function<TransitiveInfoCollection, Runfiles>() {
+ @Override
+ public Runfiles apply(TransitiveInfoCollection input) {
+ CppRunfilesProvider provider = input.getProvider(CppRunfilesProvider.class);
+ return provider == null
+ ? Runfiles.EMPTY
+ : provider.getStaticRunfiles();
+ }
+ };
+
+ /**
+ * Returns a function that gets the shared C++ runfiles from a {@link TransitiveInfoCollection}
+ * or the empty runfiles instance if it does not contain that provider.
+ */
+ public static final Function<TransitiveInfoCollection, Runfiles> SHARED_RUNFILES =
+ new Function<TransitiveInfoCollection, Runfiles>() {
+ @Override
+ public Runfiles apply(TransitiveInfoCollection input) {
+ CppRunfilesProvider provider = input.getProvider(CppRunfilesProvider.class);
+ return provider == null
+ ? Runfiles.EMPTY
+ : provider.getSharedRunfiles();
+ }
+ };
+
+ /**
+ * Returns a function that gets the C++ runfiles from a {@link TransitiveInfoCollection} or
+ * the empty runfiles instance if it does not contain that provider.
+ */
+ public static final Function<TransitiveInfoCollection, Runfiles> runfilesFunction(
+ boolean linkingStatically) {
+ return linkingStatically ? STATIC_RUNFILES : SHARED_RUNFILES;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppSemantics.java
new file mode 100644
index 0000000000..600b2fa342
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppSemantics.java
@@ -0,0 +1,49 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Pluggable C++ compilation semantics.
+ */
+public interface CppSemantics {
+ /**
+ * Returns the "effective source path" of a source file.
+ *
+ * <p>It is used, among other things, for computing the output path.
+ */
+ PathFragment getEffectiveSourcePath(Artifact source);
+
+ /**
+ * Called before a C++ compile action is built.
+ *
+ * <p>Gives the semantics implementation the opportunity to change compile actions at the last
+ * minute.
+ */
+ void finalizeCompileActionBuilder(
+ RuleContext ruleContext, CppCompileActionBuilder actionBuilder);
+
+ /**
+ * Called before {@link CppCompilationContext}s are finalized.
+ *
+ * <p>Gives the semantics implementation the opportunity to change what the C++ rule propagates
+ * to dependent rules.
+ */
+ void setupCompilationContext(
+ RuleContext ruleContext, CppCompilationContext.Builder contextBuilder);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationIdentifier.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationIdentifier.java
new file mode 100644
index 0000000000..1111189101
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationIdentifier.java
@@ -0,0 +1,132 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
+
+import java.util.Objects;
+
+/**
+ * Contains parameters which uniquely describe a crosstool configuration
+ * and methods for comparing two crosstools against each other.
+ *
+ * <p>Two crosstools which contain equivalent values of these parameters are
+ * considered equal.
+ */
+public final class CrosstoolConfigurationIdentifier implements CrosstoolConfigurationOptions {
+
+ /** The CPU associated with this crosstool configuration. */
+ private final String cpu;
+
+ /** The compiler (e.g. gcc) associated with this crosstool configuration. */
+ private final String compiler;
+
+ /** The version of libc (e.g. glibc-2.11) associated with this crosstool configuration. */
+ private final String libc;
+
+ private CrosstoolConfigurationIdentifier(String cpu, String compiler, String libc) {
+ this.cpu = cpu;
+ this.compiler = compiler;
+ this.libc = libc;
+ }
+
+ /**
+ * Creates a new crosstool configuration from the given crosstool release and
+ * configuration options.
+ */
+ public static CrosstoolConfigurationIdentifier fromReleaseAndCrosstoolConfiguration(
+ CrosstoolConfig.CrosstoolRelease release, BuildOptions buildOptions) {
+ String cpu = buildOptions.get(BuildConfiguration.Options.class).getCpu();
+ if (cpu == null) {
+ cpu = release.getDefaultTargetCpu();
+ }
+ CppOptions cppOptions = buildOptions.get(CppOptions.class);
+ return new CrosstoolConfigurationIdentifier(cpu, cppOptions.cppCompiler, cppOptions.glibc);
+ }
+
+ public static CrosstoolConfigurationIdentifier fromToolchain(CToolchain toolchain) {
+ return new CrosstoolConfigurationIdentifier(
+ toolchain.getTargetCpu(), toolchain.getCompiler(), toolchain.getTargetLibc());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof CrosstoolConfigurationIdentifier)) {
+ return false;
+ }
+ CrosstoolConfigurationIdentifier otherCrosstool = (CrosstoolConfigurationIdentifier) other;
+ return Objects.equals(cpu, otherCrosstool.cpu)
+ && Objects.equals(compiler, otherCrosstool.compiler)
+ && Objects.equals(libc, otherCrosstool.libc);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cpu, compiler, libc);
+ }
+
+
+ /**
+ * Returns a series of command line flags which specify the configuration options.
+ * Any of these options may be null, in which case its flag is omitted.
+ *
+ * <p>The appended string will be along the lines of
+ * " --cpu='cpu' --compiler='compiler' --glibc='libc'".
+ */
+ public String describeFlags() {
+ StringBuilder message = new StringBuilder();
+ if (getCpu() != null) {
+ message.append(" --cpu='").append(getCpu()).append("'");
+ }
+ if (getCompiler() != null) {
+ message.append(" --compiler='").append(getCompiler()).append("'");
+ }
+ if (getLibc() != null) {
+ message.append(" --glibc='").append(getLibc()).append("'");
+ }
+ return message.toString();
+ }
+
+ /** Returns true if the specified toolchain is a candidate for use with this crosstool. */
+ public boolean isCandidateToolchain(CToolchain toolchain) {
+ return (toolchain.getTargetCpu().equals(getCpu())
+ && (getLibc() == null || toolchain.getTargetLibc().equals(getLibc()))
+ && (getCompiler() == null || toolchain.getCompiler().equals(
+ getCompiler())));
+ }
+
+ @Override
+ public String toString() {
+ return describeFlags();
+ }
+
+ @Override
+ public String getCpu() {
+ return cpu;
+ }
+
+ @Override
+ public String getCompiler() {
+ return compiler;
+ }
+
+ @Override
+ public String getLibc() {
+ return libc;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java
new file mode 100644
index 0000000000..a113f5f126
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java
@@ -0,0 +1,327 @@
+// Copyright 2014 Google Inc. 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.Function;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.io.BaseEncoding;
+import com.google.devtools.build.lib.analysis.RedirectChaser;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
+import com.google.protobuf.UninitializedMessageException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Nullable;
+
+/**
+ * A loader that reads Crosstool configuration files and creates CToolchain
+ * instances from them.
+ */
+public class CrosstoolConfigurationLoader {
+ private static final String CROSSTOOL_CONFIGURATION_FILENAME = "CROSSTOOL";
+
+ /**
+ * Cache for storing result of toReleaseConfiguration function based on path and md5 sum of
+ * input file. We can use md5 because result of this function depends only on the file content.
+ */
+ private static final LoadingCache<Pair<Path, String>, CrosstoolConfig.CrosstoolRelease>
+ crosstoolReleaseCache = CacheBuilder.newBuilder().concurrencyLevel(4).maximumSize(100).build(
+ new CacheLoader<Pair<Path, String>, CrosstoolConfig.CrosstoolRelease>() {
+ @Override
+ public CrosstoolConfig.CrosstoolRelease load(Pair<Path, String> key) throws IOException {
+ char[] data = FileSystemUtils.readContentAsLatin1(key.first);
+ return toReleaseConfiguration(key.first.getPathString(), new String(data));
+ }
+ });
+
+ /**
+ * A class that holds the results of reading a CROSSTOOL file.
+ */
+ public static class CrosstoolFile {
+ private final Label crosstoolTop;
+ private Path crosstoolPath;
+ private CrosstoolConfig.CrosstoolRelease crosstool;
+ private String md5;
+
+ CrosstoolFile(Label crosstoolTop) {
+ this.crosstoolTop = crosstoolTop;
+ }
+
+ void setCrosstoolPath(Path crosstoolPath) {
+ this.crosstoolPath = crosstoolPath;
+ }
+
+ void setCrosstool(CrosstoolConfig.CrosstoolRelease crosstool) {
+ this.crosstool = crosstool;
+ }
+
+ void setMd5(String md5) {
+ this.md5 = md5;
+ }
+
+ /**
+ * Returns the crosstool top as resolved.
+ */
+ public Label getCrosstoolTop() {
+ return crosstoolTop;
+ }
+
+ /**
+ * Returns the absolute path from which the CROSSTOOL file was read.
+ */
+ public Path getCrosstoolPath() {
+ return crosstoolPath;
+ }
+
+ /**
+ * Returns the parsed contents of the CROSSTOOL file.
+ */
+ public CrosstoolConfig.CrosstoolRelease getProto() {
+ return crosstool;
+ }
+
+ /**
+ * Returns an MD5 hash of the CROSSTOOL file contents.
+ */
+ public String getMd5() {
+ return md5;
+ }
+ }
+
+ private CrosstoolConfigurationLoader() {
+ }
+
+ /**
+ * Reads the given <code>data</code> String, which must be in ascii format,
+ * into a protocol buffer. It uses the <code>name</code> parameter for error
+ * messages.
+ *
+ * @throws IOException if the parsing failed
+ */
+ @VisibleForTesting
+ static CrosstoolConfig.CrosstoolRelease toReleaseConfiguration(String name, String data)
+ throws IOException {
+ CrosstoolConfig.CrosstoolRelease.Builder builder =
+ CrosstoolConfig.CrosstoolRelease.newBuilder();
+ try {
+ TextFormat.merge(data, builder);
+ return builder.build();
+ } catch (ParseException e) {
+ throw new IOException("Could not read the crosstool configuration file '" + name + "', "
+ + "because of a parser error (" + e.getMessage() + ")");
+ } catch (UninitializedMessageException e) {
+ throw new IOException("Could not read the crosstool configuration file '" + name + "', "
+ + "because of an incomplete protocol buffer (" + e.getMessage() + ")");
+ }
+ }
+
+ private static boolean findCrosstoolConfiguration(
+ ConfigurationEnvironment env,
+ CrosstoolConfigurationLoader.CrosstoolFile file)
+ throws IOException, InvalidConfigurationException {
+ Label crosstoolTop = file.getCrosstoolTop();
+ Path path = null;
+ try {
+ Package containingPackage = env.getTarget(crosstoolTop.getLocalTargetLabel("BUILD"))
+ .getPackage();
+ if (containingPackage == null) {
+ return false;
+ }
+ path = env.getPath(containingPackage, CROSSTOOL_CONFIGURATION_FILENAME);
+ } catch (SyntaxException e) {
+ throw new InvalidConfigurationException(e);
+ } catch (NoSuchThingException e) {
+ // Handled later
+ }
+
+ // If we can't find a file, fall back to the provided alternative.
+ if (path == null || !path.exists()) {
+ throw new InvalidConfigurationException("The crosstool_top you specified was resolved to '" +
+ crosstoolTop + "', which does not contain a CROSSTOOL file. " +
+ "You can use a crosstool from the depot by specifying its label.");
+ } else {
+ // Do this before we read the data, so if it changes, we get a different MD5 the next time.
+ // Alternatively, we could calculate the MD5 of the contents, which we also read, but this
+ // is faster if the file comes from a file system with md5 support.
+ file.setCrosstoolPath(path);
+ String md5 = BaseEncoding.base16().lowerCase().encode(path.getMD5Digest());
+ CrosstoolConfig.CrosstoolRelease release;
+ try {
+ release = crosstoolReleaseCache.get(new Pair<Path, String>(path, md5));
+ file.setCrosstool(release);
+ file.setMd5(md5);
+ } catch (ExecutionException e) {
+ throw new InvalidConfigurationException(e);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reads a crosstool file.
+ */
+ @Nullable
+ public static CrosstoolConfigurationLoader.CrosstoolFile readCrosstool(
+ ConfigurationEnvironment env, Label crosstoolTop) throws InvalidConfigurationException {
+ crosstoolTop = RedirectChaser.followRedirects(env, crosstoolTop, "crosstool_top");
+ if (crosstoolTop == null) {
+ return null;
+ }
+ CrosstoolConfigurationLoader.CrosstoolFile file =
+ new CrosstoolConfigurationLoader.CrosstoolFile(crosstoolTop);
+ try {
+ boolean allDependenciesPresent = findCrosstoolConfiguration(env, file);
+ return allDependenciesPresent ? file : null;
+ } catch (IOException e) {
+ throw new InvalidConfigurationException(e);
+ }
+ }
+
+ /**
+ * Selects a crosstool toolchain corresponding to the given crosstool
+ * configuration options. If all of these options are null, it returns the default
+ * toolchain specified in the crosstool release. If only cpu is non-null, it
+ * returns the default toolchain for that cpu, as specified in the crosstool
+ * release. Otherwise, all values must be non-null, and this method
+ * returns the toolchain which matches all of the values.
+ *
+ * @throws NullPointerException if {@code release} is null
+ * @throws InvalidConfigurationException if no matching toolchain can be found, or
+ * if the input parameters do not obey the constraints described above
+ */
+ public static CrosstoolConfig.CToolchain selectToolchain(
+ CrosstoolConfig.CrosstoolRelease release, BuildOptions options,
+ Function<String, String> cpuTransformer)
+ throws InvalidConfigurationException {
+ CrosstoolConfigurationIdentifier config =
+ CrosstoolConfigurationIdentifier.fromReleaseAndCrosstoolConfiguration(release, options);
+ if ((config.getCompiler() != null) || (config.getLibc() != null)) {
+ ArrayList<CrosstoolConfig.CToolchain> candidateToolchains = new ArrayList<>();
+ for (CrosstoolConfig.CToolchain toolchain : release.getToolchainList()) {
+ if (config.isCandidateToolchain(toolchain)) {
+ candidateToolchains.add(toolchain);
+ }
+ }
+ switch (candidateToolchains.size()) {
+ case 0: {
+ StringBuilder message = new StringBuilder();
+ message.append("No toolchain found for");
+ message.append(config.describeFlags());
+ message.append(". Valid toolchains are: ");
+ describeToolchainList(message, release.getToolchainList());
+ throw new InvalidConfigurationException(message.toString());
+ }
+ case 1:
+ return candidateToolchains.get(0);
+ default: {
+ StringBuilder message = new StringBuilder();
+ message.append("Multiple toolchains found for");
+ message.append(config.describeFlags());
+ message.append(": ");
+ describeToolchainList(message, candidateToolchains);
+ throw new InvalidConfigurationException(message.toString());
+ }
+ }
+ }
+ String selectedIdentifier = null;
+ // We use fake CPU values to allow cross-platform builds for other languages that use the
+ // C++ toolchain. Translate to the actual target architecture.
+ String desiredCpu = cpuTransformer.apply(config.getCpu());
+ for (CrosstoolConfig.DefaultCpuToolchain selector : release.getDefaultToolchainList()) {
+ if (selector.getCpu().equals(desiredCpu)) {
+ selectedIdentifier = selector.getToolchainIdentifier();
+ break;
+ }
+ }
+ checkToolChain(selectedIdentifier, desiredCpu);
+ for (CrosstoolConfig.CToolchain toolchain : release.getToolchainList()) {
+ if (toolchain.getToolchainIdentifier().equals(selectedIdentifier)) {
+ return toolchain;
+ }
+ }
+ throw new InvalidConfigurationException("Inconsistent crosstool configuration; no toolchain "
+ + "corresponding to '" + selectedIdentifier + "' found for cpu '" + config.getCpu() + "'");
+ }
+
+ private static String describeToolchainFlags(CrosstoolConfig.CToolchain toolchain) {
+ return CrosstoolConfigurationIdentifier.fromToolchain(toolchain).describeFlags();
+ }
+
+ /**
+ * Appends a series of toolchain descriptions (as the blaze command line flags
+ * that would specify that toolchain) to 'message'.
+ */
+ private static void describeToolchainList(StringBuilder message,
+ Collection<CrosstoolConfig.CToolchain> toolchains) {
+ message.append("[");
+ for (CrosstoolConfig.CToolchain toolchain : toolchains) {
+ message.append(describeToolchainFlags(toolchain));
+ message.append(",");
+ }
+ message.append("]");
+ }
+
+ /**
+ * Makes sure that {@code selectedIdentifier} is a valid identifier for a toolchain,
+ * i.e. it starts with a letter or an underscore and continues with only dots, dashes,
+ * spaces, letters, digits or underscores (i.e. matches the following regular expression:
+ * "[a-zA-Z_][\.\- \w]*").
+ *
+ * @throws InvalidConfigurationException if selectedIdentifier is null or does not match the
+ * aforementioned regular expression.
+ */
+ private static void checkToolChain(String selectedIdentifier, String cpu)
+ throws InvalidConfigurationException {
+ if (selectedIdentifier == null) {
+ throw new InvalidConfigurationException("No toolchain found for cpu '" + cpu + "'");
+ }
+ // If you update this regex, please do so in the javadoc comment too, and also in the
+ // crosstool_config.proto file.
+ String rx = "[a-zA-Z_][\\.\\- \\w]*";
+ if (!selectedIdentifier.matches(rx)) {
+ throw new InvalidConfigurationException("Toolchain identifier for cpu '" + cpu + "' " +
+ "is illegal (does not match '" + rx + "')");
+ }
+ }
+
+ public static CrosstoolConfig.CrosstoolRelease getCrosstoolReleaseProto(
+ ConfigurationEnvironment env, BuildOptions options,
+ Label crosstoolTop, Function<String, String> cpuTransformer)
+ throws InvalidConfigurationException {
+ CrosstoolConfigurationLoader.CrosstoolFile file =
+ readCrosstool(env, crosstoolTop);
+ // Make sure that we have the requested toolchain in the result. Throw an exception if not.
+ selectToolchain(file.getProto(), options, cpuTransformer);
+ return file.getProto();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationOptions.java
new file mode 100644
index 0000000000..e311ab6163
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationOptions.java
@@ -0,0 +1,29 @@
+// Copyright 2014 Google Inc. 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;
+
+/**
+ * A container object which provides crosstool configuration options to the build.
+ */
+public interface CrosstoolConfigurationOptions {
+ /** Returns the CPU associated with this crosstool configuration. */
+ public String getCpu();
+
+ /** Returns the compiler associated with this crosstool configuration. */
+ public String getCompiler();
+
+ /** Returns the libc version associated with this crosstool configuration. */
+ public String getLibc();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/DiscoveredSourceInputsHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/DiscoveredSourceInputsHelper.java
new file mode 100644
index 0000000000..a446125e5c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/DiscoveredSourceInputsHelper.java
@@ -0,0 +1,139 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactResolver;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper for actions that do include scanning. Currently only deals with source files, so is only
+ * appropriate for actions that do not discover generated files. Currently does not do .d file
+ * parsing, so the set of artifacts returned may be an overapproximation to the ones actually used
+ * during execution.
+ */
+public class DiscoveredSourceInputsHelper {
+
+ private DiscoveredSourceInputsHelper() {
+ }
+
+ /**
+ * Converts PathFragments into source Artifacts using an ArtifactResolver, ignoring any that are
+ * already in mandatoryInputs. Silently drops any PathFragments that cannot be resolved into
+ * Artifacts.
+ */
+ public static ImmutableList<Artifact> getDiscoveredInputsFromPaths(
+ Iterable<Artifact> mandatoryInputs, ArtifactResolver artifactResolver,
+ Collection<PathFragment> inputPaths) {
+ Set<PathFragment> knownPathFragments = new HashSet<>();
+ for (Artifact input : mandatoryInputs) {
+ knownPathFragments.add(input.getExecPath());
+ }
+ ImmutableList.Builder<Artifact> foundInputs = ImmutableList.builder();
+ for (PathFragment execPath : inputPaths) {
+ if (!knownPathFragments.add(execPath)) {
+ // Don't add any inputs that we already added, or original inputs, which we probably
+ // couldn't convert into artifacts anyway.
+ continue;
+ }
+ Artifact artifact = artifactResolver.resolveSourceArtifact(execPath);
+ // It is unlikely that this artifact is null, but tolerate the situation just in case.
+ // It is safe to ignore such paths because dependency checker would identify change in inputs
+ // (ignored path was used before) and will force action execution.
+ if (artifact != null) {
+ foundInputs.add(artifact);
+ }
+ }
+ return foundInputs.build();
+ }
+
+ /**
+ * Converts ActionInputs discovered as inputs during execution into source Artifacts, ignoring any
+ * that are already in mandatoryInputs or that live in builtInIncludeDirectories. If any
+ * ActionInputs cannot be resolved, an ActionExecutionException will be thrown.
+ *
+ * <p>This method duplicates the functionality of CppCompileAction#populateActionInputs, though it
+ * is simpler because it need not deal with derived artifacts and doesn't parse the .d file.
+ */
+ public static ImmutableList<Artifact> getDiscoveredInputsFromActionInputs(
+ Iterable<Artifact> mandatoryInputs,
+ ArtifactResolver artifactResolver,
+ Iterable<? extends ActionInput> discoveredInputs,
+ Iterable<PathFragment> builtInIncludeDirectories,
+ Action action,
+ Artifact primaryInput) throws ActionExecutionException {
+ List<PathFragment> systemIncludePrefixes = new ArrayList<>();
+ for (PathFragment includePath : builtInIncludeDirectories) {
+ if (includePath.isAbsolute()) {
+ systemIncludePrefixes.add(includePath);
+ }
+ }
+
+ // Avoid duplicates by keeping track of the ones we've seen so far, even though duplicates are
+ // unlikely, since they would have to be inputs to this (non-CppCompile) action and also
+ // #included by a C++ source file.
+ Set<Artifact> knownInputs = new HashSet<>();
+ Iterables.addAll(knownInputs, mandatoryInputs);
+ ImmutableList.Builder<Artifact> foundInputs = ImmutableList.builder();
+ // Check inclusions.
+ IncludeProblems problems = new IncludeProblems();
+ for (ActionInput input : discoveredInputs) {
+ if (input instanceof Artifact) {
+ Artifact artifact = (Artifact) input;
+ if (knownInputs.add(artifact)) {
+ foundInputs.add(artifact);
+ }
+ continue;
+ }
+ PathFragment execPath = new PathFragment(input.getExecPathString());
+ if (execPath.isAbsolute()) {
+ // Absolute includes from system paths are ignored.
+ if (FileSystemUtils.startsWithAny(execPath, systemIncludePrefixes)) {
+ continue;
+ }
+ // Theoretically, the more sophisticated logic of CppCompileAction#populateActioInputs could
+ // be used here, to allow absolute includes that started with the execRoot. However, since
+ // we don't hit this codepath for local execution, that should be unnecessary. If and when
+ // we examine the results of local execution for scanned includes, that case may need to be
+ // dealt with.
+ problems.add(execPath.getPathString());
+ }
+ Artifact artifact = artifactResolver.resolveSourceArtifact(execPath);
+ if (artifact != null) {
+ if (knownInputs.add(artifact)) {
+ foundInputs.add(artifact);
+ }
+ } else {
+ // Abort if we see files that we can't resolve, likely caused by
+ // undeclared includes or illegal include constructs.
+ problems.add(execPath.getPathString());
+ }
+ }
+ problems.assertProblemFree(action, primaryInput);
+ return foundInputs.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/DwoArtifactsCollector.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/DwoArtifactsCollector.java
new file mode 100644
index 0000000000..142a67a98b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/DwoArtifactsCollector.java
@@ -0,0 +1,120 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+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;
+
+/**
+ * Provides generic functionality for collecting the .dwo artifacts produced by any target
+ * that compiles C++ files. Supports both transitive and "only direct outputs" collection.
+ * Provides accessors for both PIC and non-PIC compilation modes.
+ */
+public class DwoArtifactsCollector {
+
+ /**
+ * The .dwo files collected by this target in non-PIC compilation mode (i.e. myobject.dwo).
+ */
+ private final NestedSet<Artifact> dwoArtifacts;
+
+ /**
+ * The .dwo files collected by this target in PIC compilation mode (i.e. myobject.pic.dwo).
+ */
+ private final NestedSet<Artifact> picDwoArtifacts;
+
+ /**
+ * Instantiates a "real" collector on meaningful data.
+ */
+ private DwoArtifactsCollector(CcCompilationOutputs compilationOutputs,
+ Iterable<TransitiveInfoCollection> deps) {
+
+ Preconditions.checkNotNull(compilationOutputs);
+ Preconditions.checkNotNull(deps);
+
+ // Note: .dwo collection works fine with any order, but tests may assume a
+ // specific order for readability / simplicity purposes. See
+ // DebugInfoPackagingTest for details.
+ NestedSetBuilder<Artifact> dwoBuilder = NestedSetBuilder.compileOrder();
+ NestedSetBuilder<Artifact> picDwoBuilder = NestedSetBuilder.compileOrder();
+
+ dwoBuilder.addAll(compilationOutputs.getDwoFiles());
+ picDwoBuilder.addAll(compilationOutputs.getPicDwoFiles());
+
+ for (TransitiveInfoCollection info : deps) {
+ CppDebugFileProvider provider = info.getProvider(CppDebugFileProvider.class);
+ if (provider != null) {
+ dwoBuilder.addTransitive(provider.getTransitiveDwoFiles());
+ picDwoBuilder.addTransitive(provider.getTransitivePicDwoFiles());
+ }
+ }
+
+ dwoArtifacts = dwoBuilder.build();
+ picDwoArtifacts = picDwoBuilder.build();
+ }
+
+ /**
+ * Instantiates an empty collector.
+ */
+ private DwoArtifactsCollector() {
+ dwoArtifacts = NestedSetBuilder.<Artifact>emptySet(Order.COMPILE_ORDER);
+ picDwoArtifacts = NestedSetBuilder.<Artifact>emptySet(Order.COMPILE_ORDER);
+ }
+
+ /**
+ * Returns a new instance that collects direct outputs and transitive dependencies.
+ *
+ * @param compilationOutputs the output compilation context for the owning target
+ * @param deps which of the target's transitive info collections should be visited
+ */
+ public static DwoArtifactsCollector transitiveCollector(CcCompilationOutputs compilationOutputs,
+ Iterable<TransitiveInfoCollection> deps) {
+ return new DwoArtifactsCollector(compilationOutputs, deps);
+ }
+
+ /**
+ * Returns a new instance that collects direct outputs only.
+ *
+ * @param compilationOutputs the output compilation context for the owning target
+ */
+ public static DwoArtifactsCollector directCollector(CcCompilationOutputs compilationOutputs) {
+ return new DwoArtifactsCollector(
+ compilationOutputs, ImmutableList.<TransitiveInfoCollection>of());
+ }
+
+ /**
+ * Returns a new instance that doesn't collect anything (its artifact sets are empty).
+ */
+ public static DwoArtifactsCollector emptyCollector() {
+ return new DwoArtifactsCollector();
+ }
+
+ /**
+ * Returns the .dwo files applicable to non-PIC compilation mode (i.e. myobject.dwo).
+ */
+ public NestedSet<Artifact> getDwoArtifacts() {
+ return dwoArtifacts;
+ }
+
+ /**
+ * Returns the .dwo files applicable to PIC compilation mode (i.e. myobject.pic.dwo).
+ */
+ public NestedSet<Artifact> getPicDwoArtifacts() {
+ return picDwoArtifacts;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtractInclusionAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtractInclusionAction.java
new file mode 100644
index 0000000000..15d7010f7c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtractInclusionAction.java
@@ -0,0 +1,85 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+
+import java.io.IOException;
+
+/**
+ * An action which greps for includes over a given .cc or .h file.
+ * This is a part of the work required for C++ include scanning.
+ *
+ * <p>Note that this may run grep-includes over-optimistically, where we previously
+ * had not. For example, consider a cc_library of generated headers. If another
+ * library depends on it, and only references one of the headers, the other
+ * grep-includes will have been wasted.
+ */
+final class ExtractInclusionAction extends AbstractAction {
+
+ private static final String GUID = "45b43e5a-4734-43bb-a05e-012313808142";
+
+ /**
+ * Constructs a new action.
+ */
+ public ExtractInclusionAction(ActionOwner owner, Artifact input, Artifact output) {
+ super(owner, ImmutableList.of(input), ImmutableList.of(output));
+ }
+
+ @Override
+ protected String computeKey() {
+ return GUID;
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return executor.getContext(CppCompileActionContext.class).strategyLocality();
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "GrepIncludes";
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Extracting include lines from " + getPrimaryInput().prettyPrint();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return ResourceSet.ZERO;
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+ IncludeScanningContext context = executor.getContext(IncludeScanningContext.class);
+ try {
+ context.extractIncludes(actionExecutionContext, this, getPrimaryInput(),
+ getPrimaryOutput());
+ } catch (IOException e) {
+ throw new ActionExecutionException(e, this, false);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
new file mode 100644
index 0000000000..bd15455491
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
@@ -0,0 +1,212 @@
+// Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * Action that represents a fake C++ compilation step.
+ */
+@ThreadCompatible
+public class FakeCppCompileAction extends CppCompileAction {
+
+ private static final Logger LOG = Logger.getLogger(FakeCppCompileAction.class.getName());
+
+ public static final UUID GUID = UUID.fromString("b2d95c91-1434-47ae-a786-816017de8494");
+
+ private final PathFragment tempOutputFile;
+
+ FakeCppCompileAction(ActionOwner owner,
+ ImmutableList<String> features,
+ FeatureConfiguration featureConfiguration,
+ Artifact sourceFile,
+ Label sourceLabel,
+ NestedSet<Artifact> mandatoryInputs,
+ Artifact outputFile,
+ PathFragment tempOutputFile,
+ DotdFile dotdFile,
+ BuildConfiguration configuration,
+ CppConfiguration cppConfiguration,
+ CppCompilationContext context,
+ ImmutableList<String> copts,
+ ImmutableList<String> pluginOpts,
+ Predicate<String> nocopts,
+ ImmutableList<PathFragment> extraSystemIncludePrefixes,
+ boolean enableLayeringCheck,
+ @Nullable String fdoBuildStamp) {
+ super(owner, features, featureConfiguration, sourceFile, sourceLabel, mandatoryInputs,
+ outputFile, dotdFile, null, null, null,
+ configuration, cppConfiguration,
+ // We only allow inclusion of header files explicitly declared in
+ // "srcs", so we only use declaredIncludeSrcs, not declaredIncludeDirs.
+ // (Disallowing use of undeclared headers for cc_fake_binary is needed
+ // because the header files get included in the runfiles for the
+ // cc_fake_binary and for the negative compilation tests that depend on
+ // the cc_fake_binary, and the runfiles must be determined at analysis
+ // time, so they can't depend on the contents of the ".d" file.)
+ CppCompilationContext.disallowUndeclaredHeaders(context), null, copts, pluginOpts, nocopts,
+ extraSystemIncludePrefixes, enableLayeringCheck, fdoBuildStamp, VOID_INCLUDE_RESOLVER,
+ ImmutableList.<IncludeScannable>of(),
+ GUID, /*compileHeaderModules=*/false);
+ this.tempOutputFile = Preconditions.checkNotNull(tempOutputFile);
+ }
+
+ @Override
+ @ThreadCompatible
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+
+ // First, do an normal compilation, to generate the ".d" file. The generated
+ // object file is built to a temporary location (tempOutputFile) and ignored
+ // afterwards.
+ LOG.info("Generating " + getDotdFile());
+ CppCompileActionContext context = executor.getContext(CppCompileActionContext.class);
+ CppCompileActionContext.Reply reply = null;
+ try {
+ // We delegate stdout/stderr to nowhere, i.e. same as redirecting to /dev/null.
+ reply = context.execWithReply(
+ this, actionExecutionContext.withFileOutErr(new FileOutErr()));
+ } catch (ExecException e) {
+ // We ignore failures here (other than capturing the Distributor reply).
+ // The compilation may well fail (that's the whole point of negative compilation tests).
+ // We execute it here just for the side effect of generating the ".d" file.
+ reply = context.getReplyFromException(e, this);
+ if (reply == null) {
+ // This can only happen if the ExecException does not come from remote execution.
+ throw e.toActionExecutionException("", executor.getVerboseFailures(), this);
+ }
+ }
+ IncludeScanningContext scanningContext = executor.getContext(IncludeScanningContext.class);
+ updateActionInputs(executor.getExecRoot(), scanningContext.getArtifactResolver(), reply);
+
+ // Even cc_fake_binary rules need to properly declare their dependencies...
+ // In fact, they need to declare their dependencies even more than cc_binary rules do.
+ // CcCommonConfiguredTarget passes in an empty set of declaredIncludeDirs,
+ // so this check below will only allow inclusion of header files that are explicitly
+ // listed in the "srcs" of the cc_fake_binary or in the "srcs" of a cc_library that it
+ // depends on.
+ try {
+ validateInclusions(actionExecutionContext.getMiddlemanExpander(), executor.getEventHandler());
+ } catch (ActionExecutionException e) {
+ // TODO(bazel-team): (2009) make this into an error, once most of the current warnings
+ // are fixed.
+ executor.getEventHandler().handle(Event.warn(
+ getOwner().getLocation(),
+ e.getMessage() + ";\n this warning may eventually become an error"));
+ }
+
+ // Generate a fake ".o" file containing the command line needed to generate
+ // the real object file.
+ LOG.info("Generating " + outputFile);
+
+ // A cc_fake_binary rule generates fake .o files and a fake target file,
+ // which merely contain instructions on building the real target. We need to
+ // be careful to use a new set of output file names in the instructions, as
+ // to not overwrite the fake output files when someone tries to follow the
+ // instructions. As the real compilation is executed by the test from its
+ // runfiles directory (where writing is forbidden), we patch the command
+ // line to write to $TEST_TMPDIR instead.
+ final String outputPrefix = "$TEST_TMPDIR/";
+ String argv = Joiner.on(' ').join(
+ Iterables.transform(getArgv(outputFile.getExecPath()), new Function<String, String>() {
+ @Override
+ public String apply(String input) {
+ String result = ShellEscaper.escapeString(input);
+ if (input.equals(outputFile.getExecPathString())
+ || input.equals(getDotdFile().getSafeExecPath().getPathString())) {
+ result = outputPrefix + result;
+ }
+ return result;
+ }
+ }));
+
+ // Write the command needed to build the real .o file to the fake .o file.
+ // Generate a command to ensure that the output directory exists; otherwise
+ // the compilation would fail.
+ try {
+ // Ensure that the .d file and .o file are siblings, so that the "mkdir" below works for
+ // both.
+ Preconditions.checkState(outputFile.getExecPath().getParentDirectory().equals(
+ getDotdFile().getSafeExecPath().getParentDirectory()));
+ FileSystemUtils.writeContent(outputFile.getPath(), ISO_8859_1,
+ outputFile.getPath().getBaseName() + ": "
+ + "mkdir -p " + outputPrefix + "$(dirname " + outputFile.getExecPath() + ")"
+ + " && " + argv + "\n");
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create fake compile command for rule '" +
+ getOwner().getLabel() + ": " + e.getMessage(),
+ this, false);
+ }
+ }
+
+ @Override
+ protected PathFragment getInternalOutputFile() {
+ return tempOutputFile;
+ }
+
+ @Override
+ public String getMnemonic() { return "FakeCppCompile"; }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "fake";
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumptionLocal() {
+ return new ResourceSet(/*memoryMb=*/1, /*cpuUsage=*/0.1, /*ioUsage=*/0.0);
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return executor.getContext(CppCompileActionContext.class).estimateResourceConsumption(this);
+ }
+
+ @Override
+ protected boolean needsIncludeScanning(Executor executor) {
+ return false;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoStubAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoStubAction.java
new file mode 100644
index 0000000000..f50a1ae5c2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoStubAction.java
@@ -0,0 +1,70 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.vfs.Path;
+
+/**
+ * Stub action to be used as the generating action for FDO files that are extracted from the
+ * FDO zip.
+ *
+ * <p>This is needed because the extraction is currently not a bona fide action, therefore, Blaze
+ * would complain that these files have no generating action if we did not set it to an instance of
+ * this class.
+ */
+public class FdoStubAction extends AbstractAction {
+ public FdoStubAction(ActionOwner owner, Artifact output) {
+ // TODO(bazel-team): Make extracting the zip file a honest-to-God action so that we can do away
+ // with this ugliness.
+ super(owner, ImmutableList.<Artifact>of(), ImmutableList.of(output));
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "";
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext) {
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "FdoStubAction";
+ }
+
+ @Override
+ protected String computeKey() {
+ return "fdoStubAction";
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return ResourceSet.ZERO;
+ }
+
+ @Override
+ public void prepare(Path execRoot) {
+ // The superclass would delete the output files here. We can't let that happen, since this
+ // action does not in fact create those files; it is only a placeholder and the actual files
+ // are created *before* the execution phase in FdoSupport.extractFdoZip()
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java
new file mode 100644
index 0000000000..911d888952
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java
@@ -0,0 +1,679 @@
+// Copyright 2014 Google Inc. 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.base.Predicate;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.PackageRootResolver;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.skyframe.FileValue;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.lib.vfs.ZipFileSystem;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
+import com.google.devtools.build.skyframe.SkyFunction;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.zip.ZipException;
+
+/**
+ * Support class for FDO (feedback directed optimization) and LIPO (lightweight inter-procedural
+ * optimization).
+ *
+ * <p>There is a 1:1 relationship between {@link CppConfiguration} objects and {@code FdoSupport}
+ * objects. The FDO support of a build configuration can be retrieved using {@link
+ * CppConfiguration#getFdoSupport()}.
+ *
+ * <p>With respect to thread-safety, the {@link #prepareToBuild} method is not thread-safe, and must
+ * not be called concurrently with other methods on this class.
+ *
+ * <p>Here follows a quick run-down of how FDO/LIPO builds work (for non-FDO/LIPO builds, none
+ * of this applies):
+ *
+ * <p>{@link CppConfiguration#prepareHook} is called before the analysis phase, which calls
+ * {@link #prepareToBuild}, which extracts the FDO .zip (in case we work with an explicitly
+ * generated FDO profile file) or analyzes the .afdo.imports file next to the .afdo file (if
+ * AutoFDO is in effect).
+ *
+ * <p>.afdo.imports files contain one import a line. A line is two paths separated by a colon,
+ * with functions in the second path being referenced by functions in the first path. These are
+ * then put into the imports map. If we do AutoFDO, we don't handle individual .gcda files, so
+ * gcdaFiles will be empty.
+ *
+ * <p>Regular .fdo zip files contain .gcda files (which are added to gcdaFiles) and
+ * .gcda.imports files. There is one .gcda.imports file for every source file and it contains one
+ * path in every line, which can either be a path to a source file that contains a function
+ * referenced by the original source file or the .gcda file for such a referenced file. They
+ * both are added to the imports map.
+ *
+ * <p>If we do LIPO, we create an extra configuration that is called the "LIPO context collector",
+ * whose job it is to collect information that every configured target compiled with LIPO needs.
+ * The top-level target of this configuration is the LIPO context (always a cc_binary) and is an
+ * implicit dependency of every cc_* rule through their :lipo_context_collector attribute. The
+ * collected information is encapsulated in {@link LipoContextProvider}.
+ *
+ * <p>For each C++ compile action in the target configuration, {@link #configureCompilation} is
+ * called, which adds command line options and input files required for the build. There are
+ * three cases:
+ *
+ * <ul>
+ * <li>If we do AutoFDO, the .afdo file and the source files containing the functions imported
+ * by the original source file (as determined from the inputs map) are added.
+ * <li>If we do FDO, the .gcda file corresponding to the source file is added.
+ * <li>If we do LIPO, in addition to the .gcda file corresponding to the source file
+ * (like for FDO) the source files that contain the functions referenced by the source file and
+ * their .gcda files are added, too.
+ * </ul>
+ *
+ * <p>If we do LIPO, the actual C++ compilation context for LIPO compilation actions is pieced
+ * together from the CppCompileContext in LipoContextProvider and that of the rule being compiled.
+ * (see {@link CppCompilationContext#mergeForLipo}) This is so that the include files for the
+ * extra LIPO sources are found and is, strictly speaking, incorrect, since it also changes the
+ * declared include directories of the main source file, which in theory can result in the
+ * compilation passing even though it should fail with undeclared inclusion errors.
+ *
+ * <p>During the actual execution of the C++ compile action, the extra sources also need to be
+ * include scanned, which is the reason why they are {@link IncludeScannable} objects and not
+ * simple artifacts. We currently create these {@link IncludeScannable} objects by creating actual
+ * C++ compile actions in the LIPO context collector configuration which are then never executed.
+ * In fact, these C++ compile actions are never even registered with Skyframe. For this we
+ * propagate a bit from {@code BuildConfiguration.isActionsEnabled} to
+ * {@code CachingAnalysisEnvironment.allowRegisteringActions}, which causes actions to be silently
+ * discarded after configured targets are created.
+ */
+public class FdoSupport implements Serializable {
+
+ /**
+ * Path within profile data .zip files that is considered the root of the
+ * profile information directory tree.
+ */
+ private static final PathFragment ZIP_ROOT = new PathFragment("/");
+
+ /**
+ * Returns true if the give fdoFile represents an AutoFdo profile.
+ */
+ public static final boolean isAutoFdo(String fdoFile) {
+ return CppFileTypes.GCC_AUTO_PROFILE.matches(fdoFile);
+ }
+
+ /**
+ * Coverage information output directory passed to {@code --fdo_instrument},
+ * or {@code null} if FDO instrumentation is disabled.
+ */
+ private final PathFragment fdoInstrument;
+
+ /**
+ * Path of the profile file passed to {@code --fdo_optimize}, or
+ * {@code null} if FDO optimization is disabled. The profile file
+ * can be a coverage ZIP or an AutoFDO feedback file.
+ */
+ private final Path fdoProfile;
+
+ /**
+ * Temporary directory to which the coverage ZIP file is extracted to
+ * (relative to the exec root), or {@code null} if FDO optimization is
+ * disabled. This is used to create artifacts for the extracted files.
+ *
+ * <p>Note that this root is intentionally not registered with the artifact
+ * factory.
+ */
+ private final Root fdoRoot;
+
+ /**
+ * The relative path of the FDO root to the exec root.
+ */
+ private final PathFragment fdoRootExecPath;
+
+ /**
+ * Path of FDO files under the FDO root.
+ */
+ private final PathFragment fdoPath;
+
+ /**
+ * LIPO mode passed to {@code --lipo}. This is only used if
+ * {@code fdoProfile != null}.
+ */
+ private final LipoMode lipoMode;
+
+ /**
+ * Flag indicating whether to use AutoFDO (as opposed to
+ * instrumentation-based FDO).
+ */
+ private final boolean useAutoFdo;
+
+ /**
+ * The {@code .gcda} files that have been extracted from the ZIP file,
+ * relative to the root of the ZIP file.
+ *
+ * <p>Set only in {@link #prepareToBuild}.
+ */
+ private ImmutableSet<PathFragment> gcdaFiles = ImmutableSet.of();
+
+ /**
+ * Multimap from .gcda file base names to auxiliary input files.
+ *
+ * <p>The keys of the multimap are the exec root relative paths of .gcda files
+ * with the extension removed. The values are the lines from the accompanying
+ * .gcda.imports file.
+ *
+ * <p>The contents of the multimap are copied verbatim from the .gcda.imports
+ * files and not yet checked for validity.
+ *
+ * <p>Set only in {@link #prepareToBuild}.
+ */
+ private ImmutableMultimap<PathFragment, Artifact> imports;
+
+ /**
+ * Creates an FDO support object.
+ *
+ * @param fdoInstrument value of the --fdo_instrument option
+ * @param fdoProfile path to the profile file passed to --fdo_optimize option
+ * @param lipoMode value of the --lipo_mode option
+ */
+ public FdoSupport(PathFragment fdoInstrument, Path fdoProfile, LipoMode lipoMode, Path execRoot) {
+ this.fdoInstrument = fdoInstrument;
+ this.fdoProfile = fdoProfile;
+ this.fdoRoot = (fdoProfile == null)
+ ? null
+ : Root.asDerivedRoot(execRoot, execRoot.getRelative("blaze-fdo"));
+ this.fdoRootExecPath = fdoProfile == null
+ ? null
+ : fdoRoot.getExecPath().getRelative(new PathFragment("_fdo").getChild(
+ FileSystemUtils.removeExtension(fdoProfile.getBaseName())));
+ this.fdoPath = fdoProfile == null
+ ? null
+ : new PathFragment("_fdo").getChild(
+ FileSystemUtils.removeExtension(fdoProfile.getBaseName()));
+ this.lipoMode = lipoMode;
+ this.useAutoFdo = fdoProfile != null && isAutoFdo(fdoProfile.getBaseName());
+ }
+
+ public Root getFdoRoot() {
+ return fdoRoot;
+ }
+
+ public void declareSkyframeDependencies(SkyFunction.Environment env, Path execRoot) {
+ if (fdoProfile != null) {
+ if (isLipoEnabled()) {
+ // Incrementality is not supported for LIPO builds, see FdoSupport#scannables.
+ // Ensure that the Skyframe value containing the configuration will not be reused to avoid
+ // incrementality issues.
+ PrecomputedValue.dependOnBuildId(env);
+ return;
+ }
+
+ // IMPORTANT: Keep the following in sync with #prepareToBuild.
+ Path path;
+ if (useAutoFdo) {
+ path = fdoProfile.getParentDirectory().getRelative(
+ fdoProfile.getBaseName() + ".imports");
+ } else {
+ path = fdoProfile;
+ }
+ env.getValue(FileValue.key(RootedPath.toRootedPathMaybeUnderRoot(path,
+ ImmutableList.of(execRoot))));
+ }
+ }
+
+ /**
+ * Prepares the FDO support for building.
+ *
+ * <p>When an {@code --fdo_optimize} compile is requested, unpacks the given
+ * FDO gcda zip file into a clean working directory under execRoot.
+ *
+ * @throws FdoException if the FDO ZIP contains a file of unknown type
+ */
+ @ThreadHostile // must be called before starting the build
+ public void prepareToBuild(Path execRoot, PathFragment genfilesPath,
+ ArtifactFactory artifactDeserializer, PackageRootResolver resolver)
+ throws IOException, FdoException {
+ // The execRoot != null case is only there for testing. We cannot provide a real ZIP file in
+ // tests because ZipFileSystem does not work with a ZIP on an in-memory file system.
+ // IMPORTANT: Keep in sync with #declareSkyframeDependencies to avoid incrementality issues.
+ if (fdoProfile != null && execRoot != null) {
+ Path fdoDirPath = execRoot.getRelative(fdoRootExecPath);
+
+ FileSystemUtils.deleteTreesBelow(fdoDirPath);
+ FileSystemUtils.createDirectoryAndParents(fdoDirPath);
+
+ if (useAutoFdo) {
+ Path fdoImports = fdoProfile.getParentDirectory().getRelative(
+ fdoProfile.getBaseName() + ".imports");
+ if (isLipoEnabled()) {
+ imports = readAutoFdoImports(artifactDeserializer, fdoImports, genfilesPath, resolver);
+ }
+ FileSystemUtils.ensureSymbolicLink(
+ execRoot.getRelative(getAutoProfilePath()), fdoProfile);
+ } else {
+ Path zipFilePath = new ZipFileSystem(fdoProfile).getRootDirectory();
+ if (!zipFilePath.getRelative("blaze-out").isDirectory()) {
+ throw new ZipException("FDO zip files must be zipped directly above 'blaze-out' " +
+ "for the compiler to find the profile");
+ }
+ ImmutableSet.Builder<PathFragment> gcdaFilesBuilder = ImmutableSet.builder();
+ ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder =
+ ImmutableMultimap.builder();
+ extractFdoZip(artifactDeserializer, zipFilePath, fdoDirPath,
+ gcdaFilesBuilder, importsBuilder, resolver);
+ gcdaFiles = gcdaFilesBuilder.build();
+ imports = importsBuilder.build();
+ }
+ }
+ }
+
+ /**
+ * Recursively extracts a directory from the GCDA ZIP file into a target
+ * directory.
+ *
+ * <p>Imports files are not written to disk. Their content is directly added
+ * to an internal data structure.
+ *
+ * <p>The files are written at $EXECROOT/blaze-fdo/_fdo/(base name of profile zip), and the
+ * {@code _fdo} directory there is symlinked to from the exec root, so that the file are also
+ * available at $EXECROOT/_fdo/..., which is their exec path. We need to jump through these
+ * hoops because the FDO root 1. needs to be a source root, thus the exec path of its root is
+ * ".", 2. it must not be equal to the exec root so that the artifact factory does not get
+ * confused, 3. the files under it must be reachable by their exec path from the exec root.
+ *
+ * @throws IOException if any of the I/O operations failed
+ * @throws FdoException if the FDO ZIP contains a file of unknown type
+ */
+ private void extractFdoZip(ArtifactFactory artifactFactory, Path sourceDir,
+ Path targetDir, ImmutableSet.Builder<PathFragment> gcdaFilesBuilder,
+ ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder,
+ PackageRootResolver resolver) throws IOException, FdoException {
+ for (Path sourceFile : sourceDir.getDirectoryEntries()) {
+ Path targetFile = targetDir.getRelative(sourceFile.getBaseName());
+ if (sourceFile.isDirectory()) {
+ targetFile.createDirectory();
+ extractFdoZip(artifactFactory, sourceFile, targetFile, gcdaFilesBuilder, importsBuilder,
+ resolver);
+ } else {
+ if (CppFileTypes.COVERAGE_DATA.matches(sourceFile)) {
+ FileSystemUtils.copyFile(sourceFile, targetFile);
+ gcdaFilesBuilder.add(
+ sourceFile.relativeTo(sourceFile.getFileSystem().getRootDirectory()));
+ } else if (CppFileTypes.COVERAGE_DATA_IMPORTS.matches(sourceFile)) {
+ readCoverageImports(artifactFactory, sourceFile, importsBuilder, resolver);
+ } else {
+ throw new FdoException("FDO ZIP file contained a file of unknown type: "
+ + sourceFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads a .gcda.imports file and stores the imports information.
+ *
+ * @throws FdoException if an auxiliary LIPO input was not found
+ */
+ private void readCoverageImports(ArtifactFactory artifactFactory, Path importsFile,
+ ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder,
+ PackageRootResolver resolver) throws IOException, FdoException {
+ PathFragment key = importsFile.asFragment().relativeTo(ZIP_ROOT);
+ String baseName = key.getBaseName();
+ String ext = Iterables.getOnlyElement(CppFileTypes.COVERAGE_DATA_IMPORTS.getExtensions());
+ key = key.replaceName(baseName.substring(0, baseName.length() - ext.length()));
+
+ for (String line : FileSystemUtils.iterateLinesAsLatin1(importsFile)) {
+ if (!line.isEmpty()) {
+ // We can't yet fully check the validity of a line. this is done later
+ // when we actually parse the contained paths.
+ PathFragment execPath = new PathFragment(line);
+ if (execPath.isAbsolute()) {
+ throw new FdoException("Absolute paths not allowed in gcda imports file " + importsFile
+ + ": " + execPath);
+ }
+ Artifact artifact = artifactFactory.deserializeArtifact(new PathFragment(line), resolver);
+ if (artifact == null) {
+ throw new FdoException("Auxiliary LIPO input not found: " + line);
+ }
+
+ importsBuilder.put(key, artifact);
+ }
+ }
+ }
+
+ /**
+ * Reads a .afdo.imports file and stores the imports information.
+ */
+ private ImmutableMultimap<PathFragment, Artifact> readAutoFdoImports(
+ ArtifactFactory artifactFactory, Path importsFile, PathFragment genFilePath,
+ PackageRootResolver resolver)
+ throws IOException, FdoException {
+ ImmutableMultimap.Builder<PathFragment, Artifact> importBuilder = ImmutableMultimap.builder();
+ for (String line : FileSystemUtils.iterateLinesAsLatin1(importsFile)) {
+ if (!line.isEmpty()) {
+ PathFragment key = new PathFragment(line.substring(0, line.indexOf(':')));
+ if (key.startsWith(genFilePath)) {
+ key = key.relativeTo(genFilePath);
+ }
+ if (key.isAbsolute()) {
+ throw new FdoException("Absolute paths not allowed in afdo imports file " + importsFile
+ + ": " + key);
+ }
+ key = FileSystemUtils.replaceSegments(key, "PROTECTED", "_protected", true);
+ for (String auxFile : line.substring(line.indexOf(':') + 1).split(" ")) {
+ if (auxFile.length() == 0) {
+ continue;
+ }
+ Artifact artifact = artifactFactory.deserializeArtifact(new PathFragment(auxFile),
+ resolver);
+ if (artifact == null) {
+ throw new FdoException("Auxiliary LIPO input not found: " + auxFile);
+ }
+ importBuilder.put(key, artifact);
+ }
+ }
+ }
+ return importBuilder.build();
+ }
+
+ /**
+ * Returns the imports from the .afdo.imports file of a source file.
+ *
+ * @param sourceName the source file
+ */
+ private Collection<Artifact> getAutoFdoImports(PathFragment sourceName) {
+ Preconditions.checkState(isLipoEnabled());
+ ImmutableCollection<Artifact> afdoImports = imports.get(sourceName);
+ Preconditions.checkState(afdoImports != null,
+ "AutoFDO import data missing for %s", sourceName);
+ return afdoImports;
+ }
+
+ /**
+ * Returns the imports from the .gcda.imports file of an object file.
+ *
+ * @param objDirectory the object directory of the object file's target
+ * @param objectName the object file
+ */
+ private Iterable<Artifact> getImports(PathFragment objDirectory, PathFragment objectName) {
+ Preconditions.checkState(isLipoEnabled());
+ Preconditions.checkState(imports != null,
+ "Tried to look up imports of uninitialized FDOSupport");
+ PathFragment key = objDirectory.getRelative(FileSystemUtils.removeExtension(objectName));
+ ImmutableCollection<Artifact> importsForObject = imports.get(key);
+ Preconditions.checkState(importsForObject != null, "Import data missing for %s", key);
+ return importsForObject;
+ }
+
+ /**
+ * Configures a compile action builder by adding command line options and
+ * auxiliary inputs according to the FDO configuration. This method does
+ * nothing If FDO is disabled.
+ */
+ @ThreadSafe
+ public void configureCompilation(CppCompileActionBuilder builder, RuleContext ruleContext,
+ AnalysisEnvironment env, Label lipoLabel, PathFragment sourceName, final Pattern nocopts,
+ boolean usePic, LipoContextProvider lipoInputProvider) {
+ // It is a bug if this method is called with useLipo if lipo is disabled. However, it is legal
+ // if is is called with !useLipo, even though lipo is enabled.
+ Preconditions.checkArgument(lipoInputProvider == null || isLipoEnabled());
+
+ // FDO is disabled -> do nothing.
+ if ((fdoInstrument == null) && (fdoRoot == null)) {
+ return;
+ }
+
+ List<String> fdoCopts = new ArrayList<>();
+ // Instrumentation phase
+ if (fdoInstrument != null) {
+ fdoCopts.add("-fprofile-generate=" + fdoInstrument.getPathString());
+ if (lipoMode != LipoMode.OFF) {
+ fdoCopts.add("-fripa");
+ }
+ }
+
+ // Optimization phase
+ if (fdoRoot != null) {
+ // Declare dependency on contents of zip file.
+ if (env.getSkyframeEnv().valuesMissing()) {
+ return;
+ }
+ Iterable<Artifact> auxiliaryInputs = getAuxiliaryInputs(
+ ruleContext, env, lipoLabel, sourceName, usePic, lipoInputProvider);
+ builder.addMandatoryInputs(auxiliaryInputs);
+ if (!Iterables.isEmpty(auxiliaryInputs)) {
+ if (useAutoFdo) {
+ fdoCopts.add("-fauto-profile=" + getAutoProfilePath().getPathString());
+ } else {
+ fdoCopts.add("-fprofile-use=" + fdoRootExecPath);
+ }
+ fdoCopts.add("-fprofile-correction");
+ if (lipoInputProvider != null) {
+ fdoCopts.add("-fripa");
+ }
+ }
+ }
+ Iterable<String> filteredCopts = fdoCopts;
+ if (nocopts != null) {
+ // Filter fdoCopts with nocopts if they exist.
+ filteredCopts = Iterables.filter(fdoCopts, new Predicate<String>() {
+ @Override
+ public boolean apply(String copt) {
+ return !nocopts.matcher(copt).matches();
+ }
+ });
+ }
+ builder.addCopts(0, filteredCopts);
+ }
+
+ /**
+ * Returns the auxiliary files that need to be added to the {@link CppCompileAction}.
+ */
+ private Iterable<Artifact> getAuxiliaryInputs(
+ RuleContext ruleContext, AnalysisEnvironment env, Label lipoLabel, PathFragment sourceName,
+ boolean usePic, LipoContextProvider lipoContextProvider) {
+ // If --fdo_optimize was not specified, we don't have any additional inputs.
+ if (fdoProfile == null) {
+ return ImmutableSet.of();
+ } else if (useAutoFdo) {
+ ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
+
+ Artifact artifact = env.getDerivedArtifact(
+ fdoPath.getRelative(getAutoProfileRootRelativePath()), fdoRoot);
+ env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
+ auxiliaryInputs.add(artifact);
+ if (lipoContextProvider != null) {
+ auxiliaryInputs.addAll(getAutoFdoImports(sourceName));
+ }
+ return auxiliaryInputs.build();
+ } else {
+ ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
+
+ PathFragment objectName =
+ FileSystemUtils.replaceExtension(sourceName, usePic ? ".pic.o" : ".o");
+
+ auxiliaryInputs.addAll(
+ getGcdaArtifactsForObjectFileName(ruleContext, env, objectName, lipoLabel));
+
+ if (lipoContextProvider != null) {
+ for (Artifact importedFile : getImports(
+ getNonLipoObjDir(ruleContext, lipoLabel), objectName)) {
+ if (CppFileTypes.COVERAGE_DATA.matches(importedFile.getFilename())) {
+ Artifact gcdaArtifact = getGcdaArtifactsForGcdaPath(
+ ruleContext, env, importedFile.getExecPath());
+ if (gcdaArtifact == null) {
+ ruleContext.ruleError(String.format(
+ ".gcda file %s is not in the FDO zip (referenced by source file %s)",
+ importedFile.getExecPath(), sourceName));
+ } else {
+ auxiliaryInputs.add(gcdaArtifact);
+ }
+ } else {
+ auxiliaryInputs.add(importedFile);
+ }
+ }
+ }
+
+ return auxiliaryInputs.build();
+ }
+ }
+
+ /**
+ * Returns the .gcda file artifacts for a .gcda path from the .gcda.imports file or null if the
+ * referenced .gcda file is not in the FDO zip.
+ */
+ private Artifact getGcdaArtifactsForGcdaPath(RuleContext ruleContext,
+ AnalysisEnvironment env, PathFragment gcdaPath) {
+ if (!gcdaFiles.contains(gcdaPath)) {
+ return null;
+ }
+
+ Artifact artifact = env.getDerivedArtifact(fdoPath.getRelative(gcdaPath), fdoRoot);
+ env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
+ return artifact;
+ }
+
+ private PathFragment getNonLipoObjDir(RuleContext ruleContext, Label label) {
+ return ruleContext.getConfiguration().getBinFragment()
+ .getRelative(CppHelper.getObjDirectory(label));
+ }
+
+ /**
+ * Returns a list of .gcda file artifacts for an object file path.
+ *
+ * <p>The resulting set is either empty (because no .gcda file exists for the
+ * given object file) or contains one or two artifacts (the file itself and a
+ * symlink to it).
+ */
+ private ImmutableList<Artifact> getGcdaArtifactsForObjectFileName(RuleContext ruleContext,
+ AnalysisEnvironment env, PathFragment objectFileName, Label lipoLabel) {
+ // We put the .gcda files relative to the location of the .o file in the instrumentation run.
+ String gcdaExt = Iterables.getOnlyElement(CppFileTypes.COVERAGE_DATA.getExtensions());
+ PathFragment baseName = FileSystemUtils.replaceExtension(objectFileName, gcdaExt);
+ PathFragment gcdaFile = getNonLipoObjDir(ruleContext, lipoLabel).getRelative(baseName);
+
+ if (!gcdaFiles.contains(gcdaFile)) {
+ // If the object is a .pic.o file and .pic.gcda is not found, we should try finding .gcda too
+ String picoExt = Iterables.getOnlyElement(CppFileTypes.PIC_OBJECT_FILE.getExtensions());
+ baseName = FileSystemUtils.replaceExtension(objectFileName, gcdaExt, picoExt);
+ if (baseName == null) {
+ // Object file is not .pic.o
+ return ImmutableList.of();
+ }
+ gcdaFile = getNonLipoObjDir(ruleContext, lipoLabel).getRelative(baseName);
+ if (!gcdaFiles.contains(gcdaFile)) {
+ // .gcda file not found
+ return ImmutableList.of();
+ }
+ }
+
+ final Artifact artifact = env.getDerivedArtifact(fdoPath.getRelative(gcdaFile), fdoRoot);
+ env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
+
+ return ImmutableList.of(artifact);
+ }
+
+
+ private PathFragment getAutoProfilePath() {
+ return fdoRootExecPath.getRelative(getAutoProfileRootRelativePath());
+ }
+
+ private PathFragment getAutoProfileRootRelativePath() {
+ return new PathFragment(fdoProfile.getBaseName());
+ }
+
+ /**
+ * Returns whether LIPO is enabled.
+ */
+ @ThreadSafe
+ public boolean isLipoEnabled() {
+ return fdoProfile != null && lipoMode != LipoMode.OFF;
+ }
+
+ /**
+ * Returns whether AutoFDO is enabled.
+ */
+ @ThreadSafe
+ public boolean isAutoFdoEnabled() {
+ return useAutoFdo;
+ }
+
+ /**
+ * Returns an immutable list of command line arguments to add to the linker
+ * command line. If FDO is disabled, and empty list is returned.
+ */
+ @ThreadSafe
+ public ImmutableList<String> getLinkOptions() {
+ return fdoInstrument != null
+ ? ImmutableList.of("-fprofile-generate=" + fdoInstrument.getPathString())
+ : ImmutableList.<String>of();
+ }
+
+ /**
+ * Returns the path of the FDO output tree (relative to the execution root)
+ * containing the .gcda profile files, or null if FDO is not enabled.
+ */
+ @VisibleForTesting
+ public PathFragment getFdoOptimizeDir() {
+ return fdoRootExecPath;
+ }
+
+ /**
+ * Returns the path of the FDO zip containing the .gcda profile files, or null
+ * if FDO is not enabled.
+ */
+ @VisibleForTesting
+ public Path getFdoOptimizeProfile() {
+ return fdoProfile;
+ }
+
+ /**
+ * Returns the path fragment of the instrumentation output dir for gcc when
+ * FDO is enabled, or null if FDO is not enabled.
+ */
+ @ThreadSafe
+ public PathFragment getFdoInstrument() {
+ return fdoInstrument;
+ }
+
+ @VisibleForTesting
+ public void setGcdaFilesForTesting(ImmutableSet<PathFragment> gcdaFiles) {
+ this.gcdaFiles = gcdaFiles;
+ }
+
+ /**
+ * An exception indicating an issue with FDO coverage files.
+ */
+ public static final class FdoException extends Exception {
+ FdoException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/HeaderTargetModuleMapProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/HeaderTargetModuleMapProvider.java
new file mode 100644
index 0000000000..17e2e5ce11
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/HeaderTargetModuleMapProvider.java
@@ -0,0 +1,42 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.List;
+
+/**
+ * A provider for cc_public_library rules to be able to convey the information about the
+ * header target's module map references to the public library target.
+ */
+@Immutable
+public final class HeaderTargetModuleMapProvider implements TransitiveInfoProvider {
+
+ private final ImmutableList<CppModuleMap> cppModuleMaps;
+
+ public HeaderTargetModuleMapProvider(Iterable<CppModuleMap> cppModuleMaps) {
+ this.cppModuleMaps = ImmutableList.copyOf(cppModuleMaps);
+ }
+
+ /**
+ * Returns the module maps referenced by cc_public_library's headers target.
+ */
+ public List<CppModuleMap> getCppModuleMaps() {
+ return cppModuleMaps;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/ImplementedCcPublicLibrariesProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/ImplementedCcPublicLibrariesProvider.java
new file mode 100644
index 0000000000..4f2a5854d9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/ImplementedCcPublicLibrariesProvider.java
@@ -0,0 +1,42 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * A provider for cc_library rules to be able to convey the information about which
+ * cc_public_library rules they implement to dependent targets.
+ */
+@Immutable
+public final class ImplementedCcPublicLibrariesProvider implements TransitiveInfoProvider {
+
+ private final ImmutableList<Label> implementedCcPublicLibraries;
+
+ public ImplementedCcPublicLibrariesProvider(ImmutableList<Label> implementedCcPublicLibraries) {
+ this.implementedCcPublicLibraries = implementedCcPublicLibraries;
+ }
+
+ /**
+ * Returns the labels for the "$headers" target that are implemented by the target which
+ * implements this interface.
+ */
+ public ImmutableList<Label> getImplementedCcPublicLibraries() {
+ return implementedCcPublicLibraries;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeParser.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeParser.java
new file mode 100644
index 0000000000..0b60b453ae
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeParser.java
@@ -0,0 +1,711 @@
+// Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.rules.cpp.IncludeParser.Inclusion.Kind;
+import com.google.devtools.build.lib.rules.cpp.RemoteIncludeExtractor.RemoteParseData;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.annotation.Nullable;
+
+/**
+ * Scans a source file and extracts the literal inclusions it specifies. Does not store results --
+ * repeated requests to the same file will result in repeated scans. Clients should implement a
+ * caching layer in order to avoid unnecessary disk access when requesting an already scanned file.
+ */
+public class IncludeParser implements SkyValue {
+ private static final Logger LOG = Logger.getLogger(IncludeParser.class.getName());
+ private static final boolean LOG_FINE = LOG.isLoggable(Level.FINE);
+ private static final boolean LOG_FINER = LOG.isLoggable(Level.FINER);
+
+ /**
+ * Immutable object representation of the four columns making up a single Rule
+ * in a Hints set. See {@link Hints} for more details.
+ */
+ private static class Rule {
+ private enum Type { PATH, FILE, INCLUDE_QUOTE, INCLUDE_ANGLE; }
+ final Type type;
+ final Pattern pattern;
+ final String findRoot;
+ final Pattern findFilter;
+
+ private Rule(String type, String pattern, String findRoot, Pattern findFilter) {
+ this.type = Type.valueOf(type.trim().toUpperCase());
+ this.pattern = Pattern.compile("^" + pattern + "$");
+ this.findRoot = findRoot;
+ this.findFilter = findFilter;
+ }
+
+ /**
+ * @throws PatternSyntaxException, IllegalArgumentException if bad values
+ * are provided
+ */
+ public Rule(String type, String pattern, String findRoot, String findFilter) {
+ this(type, pattern, findRoot.replace("\\", "$"), Pattern.compile(findFilter));
+ Preconditions.checkArgument((this.type == Type.PATH) || (this.type == Type.FILE));
+ }
+
+ public Rule(String type, String pattern, String findRoot) {
+ this(type, pattern, findRoot, (Pattern) null);
+ Preconditions.checkArgument((this.type == Type.INCLUDE_QUOTE)
+ || (this.type == Type.INCLUDE_ANGLE));
+ }
+
+ @Override public String toString() {
+ return "" + type + " " + pattern + " " + findRoot + " " + findFilter;
+ }
+ }
+
+ /**
+ * This class is a representation of the INCLUDE_HINTS file maintained and
+ * delivered with the remote client. The hints file contains regexp-based rules
+ * to help this simple include scanner cope with computed includes, which
+ * would otherwise require a full preprocessor with symbol support. Instead of
+ * actually processing symbols to evaluate the computed includes, we instead
+ * apply rules to gather inclusions for matching paths.
+ * <p>
+ * The hints file is read, line by line, into a list of rules each of which
+ * encapsulates a line of four columns. Each non-blank, non-comment line has
+ * the format:
+ *
+ * <pre>
+ * &quot;file&quot;|&quot;path&quot; match-pattern find-root find-filter
+ * </pre>
+ *
+ * <p>
+ * The first column specifies whether the line is a rule based on matching
+ * source <em>files</em> (passed directly to gcc as inputs, or transitively
+ * #included by other inputs) or include <em>paths</em> (passed to gcc as
+ * -I, -iquote, or -isystem flags).
+ * <p>
+ * The second column is a regexp for files or paths. Whenever a compiler
+ * argument of the specified type matches that regexp, the rule is taken. (All
+ * matching rules for every path and file on a compiler command line are
+ * followed, and the results are combined.)
+ * <p>
+ * The third column is a point in the local filesystem from which to extract a
+ * recursive listing. (This follows symlinks) Backrefs may be used to refer to
+ * the regexp or its capturing groups. (This is mostly necessary because
+ * --package_path can cause input paths to carry arbitrary prefixes.)
+ * <p>
+ * The fourth column is a regexp applied to each file found by the recursive
+ * listing. All matching files are treated as dependencies.
+ */
+ public static class Hints implements SkyValue {
+
+ private static final Pattern WS_PAT = Pattern.compile("\\s+");
+
+ private final Path workingDir;
+ private final List<Rule> rules = new ArrayList<>();
+ private final ArtifactFactory artifactFactory;
+
+ private final LoadingCache<Artifact, Collection<Artifact>> fileLevelHintsCache =
+ CacheBuilder.newBuilder().build(
+ new CacheLoader<Artifact, Collection<Artifact>>() {
+ @Override
+ public Collection<Artifact> load(Artifact path) {
+ return getHintedInclusions(Rule.Type.FILE, path.getPath(), path.getRoot());
+ }
+ });
+
+ private final LoadingCache<Path, Collection<Artifact>> pathLevelHintsCache =
+ CacheBuilder.newBuilder().build(
+ new CacheLoader<Path, Collection<Artifact>>() {
+ @Override
+ public Collection<Artifact> load(Path path) {
+ return getHintedInclusions(Rule.Type.PATH, path, null);
+ }
+ });
+
+ /**
+ * Constructs a hint set for a given working/exec directory and INCLUDE_HINTS file to read.
+ *
+ * @param workingDir the working/exec directory that processed paths are relative to
+ * @param hintsFile the hints file to read
+ * @throws IOException if the hints file can't be read or parsed
+ */
+ public Hints(Path workingDir, Path hintsFile, ArtifactFactory artifactFactory)
+ throws IOException {
+ this.workingDir = workingDir;
+ this.artifactFactory = artifactFactory;
+ try (InputStream is = hintsFile.getInputStream()) {
+ for (String line : CharStreams.readLines(new InputStreamReader(is, "UTF-8"))) {
+ line = line.trim();
+ if (line.length() == 0 || line.startsWith("#")) {
+ continue;
+ }
+ String[] tokens = WS_PAT.split(line);
+ try {
+ if (tokens.length == 3) {
+ rules.add(new Rule(tokens[0], tokens[1], tokens[2]));
+ } else if (tokens.length == 4) {
+ rules.add(new Rule(tokens[0], tokens[1], tokens[2], tokens[3]));
+ } else {
+ throw new IOException("Malformed hint line: " + line);
+ }
+ } catch (PatternSyntaxException e) {
+ throw new IOException("Malformed hint regex on: " + line + "\n " + e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw new IOException("Invalid type on: " + line + "\n " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the "file" type hinted inclusions for a given path, caching results by path.
+ */
+ public Collection<Artifact> getFileLevelHintedInclusions(Artifact path) {
+ return fileLevelHintsCache.getUnchecked(path);
+ }
+
+ public Collection<Artifact> getPathLevelHintedInclusions(Path path) {
+ return pathLevelHintsCache.getUnchecked(path);
+ }
+
+ /**
+ * Performs the work of matching a given file/path of a specified file/path type against the
+ * hints and returns the expanded paths.
+ */
+ private Collection<Artifact> getHintedInclusions(Rule.Type type, Path path,
+ @Nullable Root sourceRoot) {
+ String pathString = path.getPathString();
+ // Delay creation until we know we need one. Use a TreeSet to make sure that the results are
+ // sorted with a stable order and unique.
+ Set<Path> hints = null;
+ for (final Rule rule : rules) {
+ if (type != rule.type) {
+ continue;
+ }
+ Matcher m = rule.pattern.matcher(pathString);
+ if (!m.matches()) {
+ continue;
+ }
+ if (hints == null) { hints = Sets.newTreeSet(); }
+ Path root = workingDir.getRelative(m.replaceFirst(rule.findRoot));
+ if (LOG_FINE) {
+ LOG.fine("hint for " + rule.type + " " + pathString + " root: " + root);
+ }
+ try {
+ // The assumption is made here that all files specified by this hint are under the same
+ // package path as the original file -- this filesystem tree traversal is completely
+ // ignorant of package paths. This could be violated if there were a hint that resolved to
+ // foo/**/*.h, there was a package foo/bar, and the packages foo and foo/bar were in
+ // different package paths. In that case, this traversal would fail to pick up
+ // foo/bar/**/*.h. No examples of this currently exist in the INCLUDE_HINTS
+ // file.
+ FileSystemUtils.traverseTree(hints, root, new Predicate<Path>() {
+ @Override
+ public boolean apply(Path p) {
+ boolean take = p.isFile() && rule.findFilter.matcher(p.getPathString()).matches();
+ if (LOG_FINER && take) {
+ LOG.finer("hinted include: " + p);
+ }
+ return take;
+ }
+ });
+ } catch (IOException e) {
+ LOG.warning("Error in hint expansion: " + e);
+ }
+ }
+ if (hints != null && !hints.isEmpty()) {
+ // Transform paths into source artifacts (all hints must be to source artifacts).
+ List<Artifact> result = new ArrayList<>(hints.size());
+ for (Path hint : hints) {
+ if (hint.startsWith(workingDir)) {
+ // Paths that are under the execRoot can be resolved as source artifacts as usual. All
+ // include directories are specified relative to the execRoot, and so fall here.
+ result.add(Preconditions.checkNotNull(
+ artifactFactory.resolveSourceArtifact(hint.relativeTo(workingDir)), hint));
+ } else {
+ // The file passed in might not have been under the execRoot, for instance
+ // <workspace>/foo/foo.cc.
+ Preconditions.checkNotNull(sourceRoot, "%s %s", path, hint);
+ Path sourcePath = sourceRoot.getPath();
+ Preconditions.checkState(hint.startsWith(sourcePath),
+ "%s %s %s", hint, path, sourceRoot);
+ result.add(Preconditions.checkNotNull(
+ artifactFactory.getSourceArtifact(hint.relativeTo(sourcePath), sourceRoot)));
+ }
+ }
+ return result;
+ } else {
+ return ImmutableList.of();
+ }
+ }
+
+ private Collection<Inclusion> getHintedInclusions(Artifact path) {
+ String pathString = path.getPath().getPathString();
+ // Delay creation until we know we need one. Use a LinkedHashSet to make sure that the results
+ // are sorted with a stable order and unique.
+ Set<Inclusion> hints = null;
+ for (final Rule rule : rules) {
+ if ((rule.type != Rule.Type.INCLUDE_ANGLE) && (rule.type != Rule.Type.INCLUDE_QUOTE)) {
+ continue;
+ }
+ Matcher m = rule.pattern.matcher(pathString);
+ if (!m.matches()) {
+ continue;
+ }
+ if (hints == null) { hints = Sets.newLinkedHashSet(); }
+ Inclusion inclusion = new Inclusion(rule.findRoot, rule.type == Rule.Type.INCLUDE_QUOTE
+ ? Kind.QUOTE : Kind.ANGLE);
+ hints.add(inclusion);
+ if (LOG_FINE) {
+ LOG.fine("hint for " + rule.type + " " + pathString + " root: " + inclusion);
+ }
+ }
+ if (hints != null && !hints.isEmpty()) {
+ return ImmutableList.copyOf(hints);
+ } else {
+ return ImmutableList.of();
+ }
+ }
+ }
+
+ public Hints getHints() {
+ return hints;
+ }
+
+ /**
+ * An immutable inclusion tuple. This models an {@code #include} or {@code
+ * #include_next} line in a file without the context how this file got
+ * included.
+ */
+ public static class Inclusion {
+ /** The format of the #include in the source file -- quoted, angle bracket, etc. */
+ public enum Kind {
+ /** Quote includes: {@code #include "name"}. */
+ QUOTE,
+
+ /** Angle bracket includes: {@code #include <name>}. */
+ ANGLE,
+
+ /** Quote next includes: {@code #include_next "name"}. */
+ NEXT_QUOTE,
+
+ /** Angle next includes: {@code #include_next <name>}. */
+ NEXT_ANGLE,
+
+ /** Computed or other unhandlable includes: {@code #include HEADER}. */
+ OTHER;
+
+ /**
+ * Returns true if this is an {@code #include_next} inclusion,
+ */
+ public boolean isNext() {
+ return this == NEXT_ANGLE || this == NEXT_QUOTE;
+ }
+ }
+
+ /** The kind of inclusion. */
+ public final Kind kind;
+ /** The relative path of the inclusion. */
+ public final PathFragment pathFragment;
+
+ public Inclusion(String includeTarget, Kind kind) {
+ this.kind = kind;
+ this.pathFragment = new PathFragment(includeTarget);
+ }
+
+ public Inclusion(PathFragment pathFragment, Kind kind) {
+ this.kind = kind;
+ this.pathFragment = Preconditions.checkNotNull(pathFragment);
+ }
+
+ public String getPathString() {
+ return pathFragment.getPathString();
+ }
+
+ @Override
+ public String toString() {
+ return kind.toString() + ":" + pathFragment.getPathString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Inclusion)) {
+ return false;
+ }
+ Inclusion that = (Inclusion) o;
+ return kind == that.kind && pathFragment.equals(that.pathFragment);
+ }
+
+ @Override
+ public int hashCode() {
+ return pathFragment.hashCode() * 37 + kind.hashCode();
+ }
+ }
+
+ /**
+ * The externally-scoped immutable hints helper that is shared by all scanners.
+ */
+ private final Hints hints;
+
+ /**
+ * A scanner that extracts includes from an individual files remotely, used when scanning files
+ * generated remotely.
+ */
+ private final Supplier<? extends RemoteIncludeExtractor> remoteExtractor;
+
+ /**
+ * Constructs a new FileParser.
+ * @param remoteExtractor a processor that extracts includes from an individual file remotely.
+ * @param hints regexps for converting computed includes into simple strings
+ */
+ public IncludeParser(@Nullable RemoteIncludeExtractor remoteExtractor, Hints hints) {
+ this.hints = hints;
+ this.remoteExtractor = Suppliers.ofInstance(remoteExtractor);
+ }
+
+ /**
+ * Constructs a new FileParser.
+ * @param remoteExtractorSupplier a supplier of a processor that extracts includes from an
+ * individual file remotely.
+ * @param hints regexps for converting computed includes into simple strings
+ */
+ public IncludeParser(Supplier<? extends RemoteIncludeExtractor> remoteExtractorSupplier,
+ Hints hints) {
+ this.hints = hints;
+ this.remoteExtractor = remoteExtractorSupplier;
+ }
+
+ /**
+ * Skips whitespace, \+NL pairs, and block-style / * * / comments. Assumes
+ * line comments are handled outside. Does not handle digraphs, trigraphs or
+ * decahexagraphs.
+ *
+ * @param chars characters to scan
+ * @param pos the starting position
+ * @return the resulting position after skipping whitespace and comments.
+ */
+ protected static int skipWhitespace(char[] chars, int pos, int end) {
+ while (pos < end) {
+ if (Character.isWhitespace(chars[pos])) {
+ pos++;
+ } else if (chars[pos] == '\\' && pos + 1 < end && chars[pos + 1] == '\n') {
+ pos++;
+ } else if (chars[pos] == '/' && pos + 1 < end && chars[pos + 1] == '*') {
+ pos += 2;
+ while (pos < end - 1) {
+ if (chars[pos++] == '*') {
+ if (chars[pos] == '/') {
+ pos++;
+ break; // proper comment end
+ }
+ }
+ }
+ } else { // not whitespace
+ return pos;
+ }
+ }
+ return pos; // pos == len, meaning we fell off the end.
+ }
+
+ /**
+ * Checks for and skips a given token.
+ *
+ * @param chars characters to scan
+ * @param pos the starting position
+ * @param expected the expected token
+ * @return the resulting position if found, otherwise -1
+ */
+ protected static int expect(char[] chars, int pos, int end, String expected) {
+ int si = 0;
+ int expectedLen = expected.length();
+ while (pos < end) {
+ if (si == expectedLen) {
+ return pos;
+ }
+ if (chars[pos++] != expected.charAt(si++)) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the index of a given character token from a starting pos.
+ *
+ * @param chars characters to scan
+ * @param pos the starting position
+ * @param echar the character to find
+ * @return the resulting position of echar if found, otherwise -1
+ */
+ private static int indexOf(char[] chars, int pos, int end, char echar) {
+ while (pos < end) {
+ if (chars[pos] == echar) {
+ return pos;
+ }
+ pos++;
+ }
+ return -1;
+ }
+
+ private static final Pattern BS_NL_PAT = Pattern.compile("\\\\" + "\n");
+
+ // Keep this in sync with the auxiliary binary's scanning output format.
+ private static final ImmutableMap<Character, Kind> KIND_MAP = ImmutableMap.of(
+ '"', Kind.QUOTE,
+ '<', Kind.ANGLE,
+ 'q', Kind.NEXT_QUOTE,
+ 'a', Kind.NEXT_ANGLE);
+
+ /**
+ * Processes the output generated by an auxiliary include-scanning binary. Closes the stream upon
+ * completion.
+ *
+ * <p>If a source file has the following include statements:
+ * <pre>
+ * #include &lt;string&gt;
+ * #include "directory/header.h"
+ * </pre>
+ *
+ * <p>Then the output file has the following contents:
+ * <pre>
+ * "directory/header.h
+ * &lt;string
+ * </pre>
+ * <p>Each line of the output is translated into an Inclusion object.
+ */
+ public static List<Inclusion> processIncludes(Object streamName, InputStream is)
+ throws IOException {
+ List<Inclusion> inclusions = new ArrayList<>();
+ InputStreamReader reader = new InputStreamReader(is, ISO_8859_1);
+ try {
+ for (String line : CharStreams.readLines(reader)) {
+ char qchar = line.charAt(0);
+ String name = line.substring(1);
+ Inclusion.Kind kind = KIND_MAP.get(qchar);
+ if (kind == null) {
+ throw new IOException("Illegal inclusion kind '" + qchar + "'");
+ }
+ inclusions.add(new Inclusion(name, kind));
+ }
+ } catch (IOException e) {
+ throw new IOException("Error reading include file " + streamName + ": " + e.getMessage());
+ } finally {
+ reader.close();
+ }
+ return inclusions;
+ }
+
+ @VisibleForTesting
+ Inclusion extractInclusion(String line) {
+ return extractInclusion(line.toCharArray(), 0, line.length());
+ }
+
+ /**
+ * Extracts a new, unresolved an Inclusion from a line of source.
+ *
+ * @param chars the char array containing the line chars to parse
+ * @param lineBegin the position of the first character in the line
+ * @param lineEnd the position of the character after the last
+ * @return the inclusion object if possible, null if none
+ */
+ private Inclusion extractInclusion(char[] chars, int lineBegin, int lineEnd) {
+ // expect WS#WS(include|include_next)WS("name"|<name>|junk)
+ int pos = expectIncludeKeyword(chars, lineBegin, lineEnd);
+ if (pos == -1 || pos == lineEnd) {
+ return null;
+ }
+ boolean isNext = false;
+ int npos = expect(chars, pos, lineEnd, "_next");
+ if (npos >= 0) {
+ isNext = true;
+ pos = npos;
+ }
+ if ((pos = skipWhitespace(chars, pos, lineEnd)) == lineEnd) {
+ return null;
+ }
+ if (chars[pos] == '"' || chars[pos] == '<') {
+ char qchar = chars[pos++];
+ int spos = pos;
+ pos = indexOf(chars, pos + 1, lineEnd, qchar == '<' ? '>' : '"');
+ if (pos < 0) {
+ return null;
+ }
+ if (chars[spos] == '/') {
+ return null; // disallow absolute paths
+ }
+ String name = new String(chars, spos, pos - spos);
+ if (name.contains("\n")) { // strip any \+NL pairs within name
+ name = BS_NL_PAT.matcher(name).replaceAll("");
+ }
+ if (isNext) {
+ return new Inclusion(name, qchar == '"' ? Kind.NEXT_QUOTE : Kind.NEXT_ANGLE);
+ } else {
+ return new Inclusion(name, qchar == '"' ? Kind.QUOTE : Kind.ANGLE);
+ }
+ } else {
+ return createOtherInclusion(new String(chars, pos, lineEnd - pos));
+ }
+ }
+
+ /**
+ * Extracts all inclusions from characters of a file.
+ *
+ * @param chars the file contents to parse & extract inclusions from
+ * @return a new set of inclusions, normalized to the cache
+ */
+ @VisibleForTesting
+ List<Inclusion> extractInclusions(char[] chars) {
+ List<Inclusion> inclusions = new ArrayList<>();
+ int lineBegin = 0; // the first char of each line
+ int end = chars.length; // the file end
+ while (lineBegin < end) {
+ int lineEnd = lineBegin; // the char after the last non-\n in each line
+ // skip to the next \n or after end of buffer, ignoring continuations
+ while (lineEnd < end) {
+ if (chars[lineEnd] == '\n') {
+ break;
+ } else if (chars[lineEnd] == '\\') {
+ lineEnd++;
+ if (chars[lineEnd] == '\n') {
+ lineEnd++;
+ }
+ } else {
+ lineEnd++;
+ }
+ }
+
+ // TODO(bazel-team) handle multiline block comments /* */ for the cases:
+ // /* blah blah blah
+ // lalala */ #include "foo.h"
+ // and:
+ // /* blah
+ // #include "foo.h"
+ // */
+
+ // extract the inclusion, and save only the kind we care about.
+ Inclusion inclusion = extractInclusion(chars, lineBegin, lineEnd);
+ if (inclusion != null) {
+ if (isValidInclusionKind(inclusion.kind)) {
+ inclusions.add(inclusion);
+ } else {
+ //System.err.println("Funky include " + inclusion + " in " + file);
+ }
+ }
+ lineBegin = lineEnd + 1; // next line starts after the previous line
+ }
+ return inclusions;
+ }
+
+ /**
+ * Extracts all inclusions from a given source file.
+ *
+ * @param file the file to parse & extract inclusions from
+ * @param greppedFile if non-null, this file has the already-grepped include lines of file.
+ * @param actionExecutionContext Services in the scope of the action, like the stream to which
+ * scanning messages are printed
+ * @return a new set of inclusions, normalized to the cache
+ */
+ public Collection<Inclusion> extractInclusions(Artifact file, @Nullable Path greppedFile,
+ ActionExecutionContext actionExecutionContext)
+ throws IOException, InterruptedException {
+ Collection<Inclusion> inclusions;
+ if (greppedFile != null) {
+ inclusions = processIncludes(greppedFile, greppedFile.getInputStream());
+ } else {
+ RemoteParseData remoteParseData = remoteExtractor.get() == null
+ ? null
+ : remoteExtractor.get().shouldParseRemotely(file.getPath());
+ if (remoteParseData != null && remoteParseData.shouldParseRemotely()) {
+ inclusions =
+ remoteExtractor.get().extractInclusions(file, actionExecutionContext,
+ remoteParseData);
+ } else {
+ inclusions = extractInclusions(FileSystemUtils.readContentAsLatin1(file.getPath()));
+ }
+ }
+ if (hints != null) {
+ inclusions.addAll(hints.getHintedInclusions(file));
+ }
+ return ImmutableList.copyOf(inclusions);
+ }
+
+ /**
+ * Parses include keyword in the provided char array and returns position
+ * immediately after include keyword or -1 if keyword was not found. Can be
+ * overridden by subclasses.
+ */
+ protected int expectIncludeKeyword(char[] chars, int position, int end) {
+ int pos = expect(chars, skipWhitespace(chars, position, end), end, "#");
+ if (pos > 0) {
+ int npos = skipWhitespace(chars, pos, end);
+ if ((pos = expect(chars, npos, end, "include")) > 0) {
+ return pos;
+ } else if ((pos = expect(chars, npos, end, "import")) > 0) {
+ if (expect(chars, pos, end, "_") == -1) { // Needed to avoid #import_next.
+ return pos;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns true if we interested in the given inclusion kind. Can be
+ * overridden by the subclass.
+ */
+ protected boolean isValidInclusionKind(Kind kind) {
+ return kind != Kind.OTHER;
+ }
+
+ /**
+ * Returns inclusion object for non-standard inclusion cases or null if
+ * inclusion should be ignored.
+ */
+ protected Inclusion createOtherInclusion(String inclusionContent) {
+ return new Inclusion(inclusionContent, Kind.OTHER);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java
new file mode 100644
index 0000000000..f6be87747e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java
@@ -0,0 +1,51 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.Artifact;
+
+/**
+ * Accumulator for problems encountered while reading or validating inclusion
+ * results.
+ */
+class IncludeProblems {
+
+ private StringBuilder message; // null when no problems
+
+ void add(String included) {
+ if (message == null) { message = new StringBuilder(); }
+ message.append("\n '" + included + "'");
+ }
+
+ boolean hasProblems() { return message != null; }
+
+ String getMessage(Action action, Artifact sourceFile) {
+ if (message != null) {
+ return "undeclared inclusion(s) in rule '" + action.getOwner().getLabel() + "':\n"
+ + "this rule is missing dependency declarations for the following files "
+ + "included by '" + sourceFile.prettyPrint() + "':"
+ + message;
+ }
+ return null;
+ }
+
+ void assertProblemFree(Action action, Artifact sourceFile) throws ActionExecutionException {
+ if (hasProblems()) {
+ throw new ActionExecutionException(getMessage(action, sourceFile), action, false);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScannable.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScannable.java
new file mode 100644
index 0000000000..9c70090ac0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScannable.java
@@ -0,0 +1,90 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * To be implemented by actions (such as C++ compilation steps) whose inputs
+ * can be scanned to discover other implicit inputs (such as C++ header files).
+ *
+ * <p>This is useful for remote execution strategies to be able to compute the
+ * complete set of files that must be distributed in order to execute such an action.
+ */
+public interface IncludeScannable {
+
+ /**
+ * Returns the built-in list of system include paths for the toolchain compiler. All paths in this
+ * list should be relative to the exec directory. They may be absolute if they are also installed
+ * on the remote build nodes or for local compilation.
+ */
+ List<PathFragment> getBuiltInIncludeDirectories();
+
+ /**
+ * Returns an immutable list of "-iquote" include paths that should be used by
+ * the IncludeScanner for this action. GCC searches these paths first, but
+ * only for {@code #include "foo"}, not for {@code #include &lt;foo&gt;}.
+ */
+ List<PathFragment> getQuoteIncludeDirs();
+
+ /**
+ * Returns an immutable list of "-I" include paths that should be used by the
+ * IncludeScanner for this action. GCC searches these paths ahead of the
+ * system include paths, but after "-iquote" include paths.
+ */
+ List<PathFragment> getIncludeDirs();
+
+ /**
+ * Returns an immutable list of "-isystem" include paths that should be used
+ * by the IncludeScanner for this action. GCC searches these paths ahead of
+ * the built-in system include paths, but after all other paths. "-isystem"
+ * paths are treated the same as normal system directories.
+ */
+ List<PathFragment> getSystemIncludeDirs();
+
+ /**
+ * Returns an immutable list of "-include" inclusions specified explicitly on
+ * the command line of this action. GCC will imagine that these files have
+ * been quote-included at the beginning of each source file.
+ */
+ List<String> getCmdlineIncludes();
+
+ /**
+ * Returns an immutable list of sources that the IncludeScanner should scan
+ * for this action.
+ */
+ Collection<Artifact> getIncludeScannerSources();
+
+ /**
+ * Returns additional scannables that need also be scanned when scanning this
+ * scannable. May be empty but not null. This is not evaluated recursively.
+ */
+ Iterable<IncludeScannable> getAuxiliaryScannables();
+
+ /**
+ * Returns a map of generated files:files grepped for headers which may be reached during include
+ * scanning. Generated files which are reached, but not in the key set, must be ignored.
+ *
+ * <p>If grepping of output files is not enabled via --extract_generated_inclusions, keys
+ * should just map to null.
+ */
+ Map<Artifact, Path> getLegalGeneratedScannerFileMap();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java
new file mode 100644
index 0000000000..9c00efd64a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java
@@ -0,0 +1,177 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Scans source files to determine the bounding set of transitively referenced include files.
+ *
+ * <p>Note that include scanning is performance-critical code.
+ */
+public interface IncludeScanner {
+ /**
+ * Processes a source file and a list of includes extracted from command line
+ * flags. Adds all found files to the provided set {@code includes}. This
+ * method takes into account the path- and file-level hints that are part of
+ * this include scanner.
+ */
+ public void process(Artifact source, Map<Artifact, Path> legalOutputPaths,
+ List<String> cmdlineIncludes, Set<Artifact> includes,
+ ActionExecutionContext actionExecutionContext)
+ throws IOException, ExecException, InterruptedException;
+
+ /** Supplies IncludeScanners upon request. */
+ interface IncludeScannerSupplier {
+ /** Returns the possibly shared scanner to be used for a given pair of include paths. */
+ IncludeScanner scannerFor(List<Path> quoteIncludePaths, List<Path> includePaths);
+ }
+
+ /**
+ * Helper class that exists just to provide a static method that prepares the arguments with which
+ * to call an IncludeScanner.
+ */
+ class IncludeScanningPreparer {
+ private IncludeScanningPreparer() {}
+
+ /**
+ * Returns the files transitively included by the source files of the given IncludeScannable.
+ *
+ * @param action IncludeScannable whose sources' transitive includes will be returned.
+ * @param includeScannerSupplier supplies IncludeScanners to actually do the transitive
+ * scanning (and caching results) for a given source file.
+ * @param actionExecutionContext the context for {@code action}.
+ * @param profilerTaskName what the {@link Profiler} should record this call for.
+ */
+ public static Collection<Artifact> scanForIncludedInputs(IncludeScannable action,
+ IncludeScannerSupplier includeScannerSupplier,
+ ActionExecutionContext actionExecutionContext,
+ String profilerTaskName)
+ throws ExecException, InterruptedException, ActionExecutionException {
+
+ Set<Artifact> includes = Sets.newConcurrentHashSet();
+
+ Executor executor = actionExecutionContext.getExecutor();
+ Path execRoot = executor.getExecRoot();
+
+ final List<Path> absoluteBuiltInIncludeDirs = new ArrayList<>();
+
+ Profiler profiler = Profiler.instance();
+ try {
+ profiler.startTask(ProfilerTask.SCANNER, profilerTaskName);
+
+ // We need to scan the action itself, but also the auxiliary scannables
+ // (for LIPO). There is no need to call getAuxiliaryScannables
+ // recursively.
+ for (IncludeScannable scannable :
+ Iterables.concat(ImmutableList.of(action), action.getAuxiliaryScannables())) {
+
+ Map<Artifact, Path> legalOutputPaths = scannable.getLegalGeneratedScannerFileMap();
+ List<PathFragment> includeDirs = new ArrayList<>(scannable.getIncludeDirs());
+ List<PathFragment> quoteIncludeDirs = scannable.getQuoteIncludeDirs();
+ List<String> cmdlineIncludes = scannable.getCmdlineIncludes();
+
+ for (PathFragment pathFragment : scannable.getSystemIncludeDirs()) {
+ includeDirs.add(pathFragment);
+ }
+
+ // Add the system include paths to the list of include paths.
+ for (PathFragment pathFragment : action.getBuiltInIncludeDirectories()) {
+ if (pathFragment.isAbsolute()) {
+ absoluteBuiltInIncludeDirs.add(execRoot.getRelative(pathFragment));
+ }
+ includeDirs.add(pathFragment);
+ }
+
+ IncludeScanner scanner = includeScannerSupplier.scannerFor(
+ relativeTo(execRoot, quoteIncludeDirs),
+ relativeTo(execRoot, includeDirs));
+
+ for (Artifact source : scannable.getIncludeScannerSources()) {
+ // Add all include scanning entry points to the inputs; this is necessary
+ // when we have more than one source to scan from, for example when building
+ // C++ modules.
+ // In that case we have one of two cases:
+ // 1. We compile a header module - there, the .cppmap file is the main source file
+ // (which we do not include-scan, as that would require an extra parser), and
+ // thus already in the input; all headers in the .cppmap file are our entry points
+ // for include scanning, but are not yet in the inputs - they get added here.
+ // 2. We compile an object file that uses a header module; currently using a header
+ // module requires all headers it can reference to be available for the compilation.
+ // The header module can reference headers that are not in the transitive include
+ // closure of the current translation unit. Therefore, {@code CppCompileAction}
+ // adds all headers specified transitively for compiled header modules as include
+ // scanning entry points, and we need to add the entry points to the inputs here.
+ includes.add(source);
+ scanner.process(source, legalOutputPaths, cmdlineIncludes, includes,
+ actionExecutionContext);
+ }
+ }
+ } catch (IOException e) {
+ throw new EnvironmentalExecException(e.getMessage());
+ } finally {
+ profiler.completeTask(ProfilerTask.SCANNER);
+ }
+
+ // Collect inputs and output
+ List<Artifact> inputs = new ArrayList<>();
+ IncludeProblems includeProblems = new IncludeProblems();
+ for (Artifact included : includes) {
+ if (FileSystemUtils.startsWithAny(included.getPath(), absoluteBuiltInIncludeDirs)) {
+ // Skip include files found in absolute include directories. This currently only applies
+ // to grte.
+ continue;
+ }
+ if (included.getRoot().getPath().getParentDirectory() == null) {
+ throw new UserExecException(
+ "illegal absolute path to include file: " + included.getPath());
+ }
+ inputs.add(included);
+ }
+ return inputs;
+ }
+
+ private static List<Path> relativeTo(
+ Path path, Collection<PathFragment> fragments) {
+ List<Path> result = Lists.newArrayListWithCapacity(fragments.size());
+ for (PathFragment fragment : fragments) {
+ result.add(path.getRelative(fragment));
+ }
+ return result;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanningContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanningContext.java
new file mode 100644
index 0000000000..69cd26b6bd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanningContext.java
@@ -0,0 +1,44 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactResolver;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+
+import java.io.IOException;
+
+/**
+ * Context for actions that do include scanning.
+ */
+public interface IncludeScanningContext extends ActionContext {
+ /**
+ * Extracts the set of include files from a source file.
+ *
+ * @param actionExecutionContext the execution context
+ * @param resourceOwner the resource owner
+ * @param primaryInput the source file to be include scanned
+ * @param primaryOutput the output file where the results should be put
+ */
+ void extractIncludes(ActionExecutionContext actionExecutionContext,
+ ActionMetadata resourceOwner, Artifact primaryInput, Artifact primaryOutput)
+ throws IOException, InterruptedException;
+
+ /**
+ * Returns the artifact resolver.
+ */
+ ArtifactResolver getArtifactResolver();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/Link.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/Link.java
new file mode 100644
index 0000000000..26175ebb95
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/Link.java
@@ -0,0 +1,274 @@
+// Copyright 2014 Google Inc. 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.collect.AbstractIterator;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+import java.util.Iterator;
+
+/**
+ * Utility types and methods for generating command lines for the linker, given
+ * a CppLinkAction or LinkConfiguration.
+ *
+ * <p>The linker commands, e.g. "ar", may not be functional, i.e.
+ * they may mutate the output file rather than overwriting it.
+ * To avoid this, we need to delete the output file before invoking the
+ * command. But that is not done by this class; deleting the output
+ * file is the responsibility of the classes derived from LinkStrategy.
+ */
+public abstract class Link {
+
+ private Link() {} // uninstantiable
+
+ /** The set of valid linker input files. */
+ public static final FileTypeSet VALID_LINKER_INPUTS = FileTypeSet.of(
+ CppFileTypes.ARCHIVE, CppFileTypes.PIC_ARCHIVE,
+ CppFileTypes.ALWAYS_LINK_LIBRARY, CppFileTypes.ALWAYS_LINK_PIC_LIBRARY,
+ CppFileTypes.OBJECT_FILE, CppFileTypes.PIC_OBJECT_FILE,
+ CppFileTypes.SHARED_LIBRARY, CppFileTypes.VERSIONED_SHARED_LIBRARY,
+ CppFileTypes.INTERFACE_SHARED_LIBRARY);
+
+ /**
+ * These file are supposed to be added using {@code addLibrary()} calls to {@link CppLinkAction}
+ * but will never be expanded to their constituent {@code .o} files. {@link CppLinkAction} checks
+ * that these files are never added as non-libraries.
+ */
+ public static final FileTypeSet SHARED_LIBRARY_FILETYPES = FileTypeSet.of(
+ CppFileTypes.SHARED_LIBRARY,
+ CppFileTypes.VERSIONED_SHARED_LIBRARY,
+ CppFileTypes.INTERFACE_SHARED_LIBRARY);
+
+ /**
+ * These need special handling when --thin_archive is true. {@link CppLinkAction} checks that
+ * these files are never added as non-libraries.
+ */
+ public static final FileTypeSet ARCHIVE_LIBRARY_FILETYPES = FileTypeSet.of(
+ CppFileTypes.ARCHIVE,
+ CppFileTypes.PIC_ARCHIVE,
+ CppFileTypes.ALWAYS_LINK_LIBRARY,
+ CppFileTypes.ALWAYS_LINK_PIC_LIBRARY);
+
+ public static final FileTypeSet ARCHIVE_FILETYPES = FileTypeSet.of(
+ CppFileTypes.ARCHIVE,
+ CppFileTypes.PIC_ARCHIVE);
+
+ public static final FileTypeSet LINK_LIBRARY_FILETYPES = FileTypeSet.of(
+ CppFileTypes.ALWAYS_LINK_LIBRARY,
+ CppFileTypes.ALWAYS_LINK_PIC_LIBRARY);
+
+
+ /** The set of object files */
+ public static final FileTypeSet OBJECT_FILETYPES = FileTypeSet.of(
+ CppFileTypes.OBJECT_FILE,
+ CppFileTypes.PIC_OBJECT_FILE);
+
+ /**
+ * Prefix that is prepended to command line entries that refer to the output
+ * of cc_fake_binary compile actions. This is a bad hack to signal to the code
+ * in {@code CppLinkAction#executeFake(Executor, FileOutErr)} that it needs
+ * special handling.
+ */
+ public static final String FAKE_OBJECT_PREFIX = "fake:";
+
+ /**
+ * Types of ELF files that can be created by the linker (.a, .so, .lo,
+ * executable).
+ */
+ public enum LinkTargetType {
+ /** A normal static archive. */
+ STATIC_LIBRARY(".a", true),
+
+ /** A static archive with .pic.o object files (compiled with -fPIC). */
+ PIC_STATIC_LIBRARY(".pic.a", true),
+
+ /** An interface dynamic library. */
+ INTERFACE_DYNAMIC_LIBRARY(".ifso", false),
+
+ /** A dynamic library. */
+ DYNAMIC_LIBRARY(".so", false),
+
+ /** A static archive without removal of unused object files. */
+ ALWAYS_LINK_STATIC_LIBRARY(".lo", true),
+
+ /** A PIC static archive without removal of unused object files. */
+ ALWAYS_LINK_PIC_STATIC_LIBRARY(".pic.lo", true),
+
+ /** An executable binary. */
+ EXECUTABLE("", false);
+
+ private final String extension;
+ private final boolean staticLibraryLink;
+
+ private LinkTargetType(String extension, boolean staticLibraryLink) {
+ this.extension = extension;
+ this.staticLibraryLink = staticLibraryLink;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public boolean isStaticLibraryLink() {
+ return staticLibraryLink;
+ }
+ }
+
+ /**
+ * The degree of "staticness" of symbol resolution during linking.
+ */
+ public enum LinkStaticness {
+ FULLY_STATIC, // Static binding of all symbols.
+ MOSTLY_STATIC, // Use dynamic binding only for symbols from glibc.
+ DYNAMIC, // Use dynamic binding wherever possible.
+ }
+
+ /**
+ * Types of archive.
+ */
+ public enum ArchiveType {
+ FAT, // Regular archive that includes its members.
+ THIN, // Thin archive that just points to its members.
+ START_END_LIB // A --start-lib ... --end-lib group in the command line.
+ }
+
+ static boolean useStartEndLib(LinkerInput linkerInput, ArchiveType archiveType) {
+ // TODO(bazel-team): Figure out if PicArchives are actually used. For it to be used, both
+ // linkingStatically and linkShared must me true, we must be in opt mode and cpu has to be k8.
+ return archiveType == ArchiveType.START_END_LIB
+ && ARCHIVE_FILETYPES.matches(linkerInput.getArtifact().getFilename())
+ && linkerInput.containsObjectFiles();
+ }
+
+ /**
+ * Replace always used archives with its members. This is used to build the linker cmd line.
+ */
+ public static Iterable<LinkerInput> mergeInputsCmdLine(NestedSet<LibraryToLink> inputs,
+ boolean globalNeedWholeArchive, ArchiveType archiveType) {
+ return new FilterMembersForLinkIterable(inputs, globalNeedWholeArchive, archiveType, false);
+ }
+
+ /**
+ * Add in any object files which are implicitly named as inputs by the linker.
+ */
+ public static Iterable<LinkerInput> mergeInputsDependencies(NestedSet<LibraryToLink> inputs,
+ boolean globalNeedWholeArchive, ArchiveType archiveType) {
+ return new FilterMembersForLinkIterable(inputs, globalNeedWholeArchive, archiveType, true);
+ }
+
+ /**
+ * On the fly implementation to filter the members.
+ */
+ private static final class FilterMembersForLinkIterable implements Iterable<LinkerInput> {
+ private final boolean globalNeedWholeArchive;
+ private final ArchiveType archiveType;
+ private final boolean deps;
+
+ private final Iterable<LibraryToLink> inputs;
+
+ private FilterMembersForLinkIterable(Iterable<LibraryToLink> inputs,
+ boolean globalNeedWholeArchive, ArchiveType archiveType, boolean deps) {
+ this.globalNeedWholeArchive = globalNeedWholeArchive;
+ this.archiveType = archiveType;
+ this.deps = deps;
+ this.inputs = CollectionUtils.makeImmutable(inputs);
+ }
+
+ @Override
+ public Iterator<LinkerInput> iterator() {
+ return new FilterMembersForLinkIterator(inputs.iterator(), globalNeedWholeArchive,
+ archiveType, deps);
+ }
+ }
+
+ /**
+ * On the fly implementation to filter the members.
+ */
+ private static final class FilterMembersForLinkIterator extends AbstractIterator<LinkerInput> {
+ private final boolean globalNeedWholeArchive;
+ private final ArchiveType archiveType;
+ private final boolean deps;
+
+ private final Iterator<LibraryToLink> inputs;
+ private Iterator<LinkerInput> delayList = ImmutableList.<LinkerInput>of().iterator();
+
+ private FilterMembersForLinkIterator(Iterator<LibraryToLink> inputs,
+ boolean globalNeedWholeArchive, ArchiveType archiveType, boolean deps) {
+ this.globalNeedWholeArchive = globalNeedWholeArchive;
+ this.archiveType = archiveType;
+ this.deps = deps;
+ this.inputs = inputs;
+ }
+
+ @Override
+ protected LinkerInput computeNext() {
+ if (delayList.hasNext()) {
+ return delayList.next();
+ }
+
+ while (inputs.hasNext()) {
+ LibraryToLink inputLibrary = inputs.next();
+ Artifact input = inputLibrary.getArtifact();
+ String name = input.getFilename();
+
+ // True if the linker might use the members of this file, i.e., if the file is a thin or
+ // start_end_lib archive (aka static library). Also check if the library contains object
+ // files - otherwise getObjectFiles returns null, which would lead to an NPE in
+ // simpleLinkerInputs.
+ boolean needMembersForLink = archiveType != ArchiveType.FAT
+ && ARCHIVE_LIBRARY_FILETYPES.matches(name) && inputLibrary.containsObjectFiles();
+
+ // True if we will pass the members instead of the original archive.
+ boolean passMembersToLinkCmd = needMembersForLink
+ && (globalNeedWholeArchive || LINK_LIBRARY_FILETYPES.matches(name));
+
+ // If deps is false (when computing the inputs to be passed on the command line), then it's
+ // an if-then-else, i.e., the passMembersToLinkCmd flag decides whether to pass the object
+ // files or the archive itself. This flag in turn is based on whether the archives are fat
+ // or not (thin archives or start_end_lib) - we never expand fat archives, but we do expand
+ // non-fat archives if we need whole-archives for the entire link, or for the specific
+ // library (i.e., if alwayslink=1).
+ //
+ // If deps is true (when computing the inputs to be passed to the action as inputs), then it
+ // becomes more complicated. We always need to pass the members for thin and start_end_lib
+ // archives (needMembersForLink). And we _also_ need to pass the archive file itself unless
+ // it's a start_end_lib archive (unless it's an alwayslink library).
+
+ // A note about ordering: the order in which the object files and the library are returned
+ // does not currently matter - this code results in the library returned first, and the
+ // object files returned after, but only if both are returned, which can only happen if
+ // deps is true, in which case this code only computes the list of inputs for the link
+ // action (so the order isn't critical).
+ if (passMembersToLinkCmd || (deps && needMembersForLink)) {
+ delayList = LinkerInputs.simpleLinkerInputs(inputLibrary.getObjectFiles()).iterator();
+ }
+
+ if (!(passMembersToLinkCmd || (deps && useStartEndLib(inputLibrary, archiveType)))) {
+ return inputLibrary;
+ }
+
+ if (delayList.hasNext()) {
+ return delayList.next();
+ }
+ }
+ return endOfData();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java
new file mode 100644
index 0000000000..1dccafa8cc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java
@@ -0,0 +1,1121 @@
+// Copyright 2014 Google Inc. 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * Represents the command line of a linker invocation. It supports executables and dynamic
+ * libraries as well as static libraries.
+ */
+@Immutable
+public final class LinkCommandLine extends CommandLine {
+ private final BuildConfiguration configuration;
+ private final CppConfiguration cppConfiguration;
+ private final ActionOwner owner;
+ private final Artifact output;
+ @Nullable private final Artifact interfaceOutput;
+ @Nullable private final Artifact symbolCountsOutput;
+ private final ImmutableList<Artifact> buildInfoHeaderArtifacts;
+ private final Iterable<? extends LinkerInput> linkerInputs;
+ private final Iterable<? extends LinkerInput> runtimeInputs;
+ private final LinkTargetType linkTargetType;
+ private final LinkStaticness linkStaticness;
+ private final ImmutableList<String> linkopts;
+ private final ImmutableSet<String> features;
+ private final ImmutableMap<Artifact, Artifact> linkstamps;
+ private final ImmutableList<String> linkstampCompileOptions;
+ @Nullable private final PathFragment runtimeSolibDir;
+ private final boolean nativeDeps;
+ private final boolean useTestOnlyFlags;
+ private final boolean needWholeArchive;
+ private final boolean supportsParamFiles;
+ @Nullable private final Artifact interfaceSoBuilder;
+
+ private LinkCommandLine(
+ BuildConfiguration configuration,
+ ActionOwner owner,
+ Artifact output,
+ @Nullable Artifact interfaceOutput,
+ @Nullable Artifact symbolCountsOutput,
+ ImmutableList<Artifact> buildInfoHeaderArtifacts,
+ Iterable<? extends LinkerInput> linkerInputs,
+ Iterable<? extends LinkerInput> runtimeInputs,
+ LinkTargetType linkTargetType,
+ LinkStaticness linkStaticness,
+ ImmutableList<String> linkopts,
+ ImmutableSet<String> features,
+ ImmutableMap<Artifact, Artifact> linkstamps,
+ ImmutableList<String> linkstampCompileOptions,
+ @Nullable PathFragment runtimeSolibDir,
+ boolean nativeDeps,
+ boolean useTestOnlyFlags,
+ boolean needWholeArchive,
+ boolean supportsParamFiles,
+ Artifact interfaceSoBuilder) {
+ Preconditions.checkArgument(linkTargetType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY,
+ "you can't link an interface dynamic library directly");
+ if (linkTargetType != LinkTargetType.DYNAMIC_LIBRARY) {
+ Preconditions.checkArgument(interfaceOutput == null,
+ "interface output may only be non-null for dynamic library links");
+ }
+ if (linkTargetType.isStaticLibraryLink()) {
+ Preconditions.checkArgument(linkstamps.isEmpty(),
+ "linkstamps may only be present on dynamic library or executable links");
+ Preconditions.checkArgument(linkStaticness == LinkStaticness.FULLY_STATIC,
+ "static library link must be static");
+ Preconditions.checkArgument(buildInfoHeaderArtifacts.isEmpty(),
+ "build info headers may only be present on dynamic library or executable links");
+ Preconditions.checkArgument(symbolCountsOutput == null,
+ "the symbol counts output must be null for static links");
+ Preconditions.checkArgument(runtimeSolibDir == null,
+ "the runtime solib directory must be null for static links");
+ Preconditions.checkArgument(!nativeDeps,
+ "the native deps flag must be false for static links");
+ Preconditions.checkArgument(!needWholeArchive,
+ "the need whole archive flag must be false for static links");
+ }
+
+ this.configuration = Preconditions.checkNotNull(configuration);
+ this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
+ this.owner = Preconditions.checkNotNull(owner);
+ this.output = Preconditions.checkNotNull(output);
+ this.interfaceOutput = interfaceOutput;
+ this.symbolCountsOutput = symbolCountsOutput;
+ this.buildInfoHeaderArtifacts = Preconditions.checkNotNull(buildInfoHeaderArtifacts);
+ this.linkerInputs = Preconditions.checkNotNull(linkerInputs);
+ this.runtimeInputs = Preconditions.checkNotNull(runtimeInputs);
+ this.linkTargetType = Preconditions.checkNotNull(linkTargetType);
+ this.linkStaticness = Preconditions.checkNotNull(linkStaticness);
+ // For now, silently ignore linkopts if this is a static library link.
+ this.linkopts = linkTargetType.isStaticLibraryLink()
+ ? ImmutableList.<String>of()
+ : Preconditions.checkNotNull(linkopts);
+ this.features = Preconditions.checkNotNull(features);
+ this.linkstamps = Preconditions.checkNotNull(linkstamps);
+ this.linkstampCompileOptions = linkstampCompileOptions;
+ this.runtimeSolibDir = runtimeSolibDir;
+ this.nativeDeps = nativeDeps;
+ this.useTestOnlyFlags = useTestOnlyFlags;
+ this.needWholeArchive = needWholeArchive;
+ this.supportsParamFiles = supportsParamFiles;
+ // For now, silently ignore interfaceSoBuilder if we don't build an interface dynamic library.
+ this.interfaceSoBuilder =
+ ((linkTargetType == LinkTargetType.DYNAMIC_LIBRARY) && (interfaceOutput != null))
+ ? Preconditions.checkNotNull(interfaceSoBuilder,
+ "cannot build interface dynamic library without builder")
+ : null;
+ }
+
+ /**
+ * Returns an interface shared object output artifact produced during linking. This only returns
+ * non-null if {@link #getLinkTargetType} is {@code DYNAMIC_LIBRARY} and an interface shared
+ * object was requested.
+ */
+ @Nullable public Artifact getInterfaceOutput() {
+ return interfaceOutput;
+ }
+
+ /**
+ * Returns an artifact containing the number of symbols used per object file passed to the linker.
+ * This is currently a gold only feature, and is only produced for executables. If another target
+ * is being linked, or if symbol counts output is disabled, this will be null.
+ */
+ @Nullable public Artifact getSymbolCountsOutput() {
+ return symbolCountsOutput;
+ }
+
+ /**
+ * Returns the (ordered, immutable) list of header files that contain build info.
+ */
+ public ImmutableList<Artifact> getBuildInfoHeaderArtifacts() {
+ return buildInfoHeaderArtifacts;
+ }
+
+ /**
+ * Returns the (ordered, immutable) list of paths to the linker's input files.
+ */
+ public Iterable<? extends LinkerInput> getLinkerInputs() {
+ return linkerInputs;
+ }
+
+ /**
+ * Returns the runtime inputs to the linker.
+ */
+ public Iterable<? extends LinkerInput> getRuntimeInputs() {
+ return runtimeInputs;
+ }
+
+ /**
+ * Returns the current type of link target set.
+ */
+ public LinkTargetType getLinkTargetType() {
+ return linkTargetType;
+ }
+
+ /**
+ * Returns the "staticness" of the link.
+ */
+ public LinkStaticness getLinkStaticness() {
+ return linkStaticness;
+ }
+
+ /**
+ * Returns the additional linker options for this link.
+ */
+ public ImmutableList<String> getLinkopts() {
+ return linkopts;
+ }
+
+ /**
+ * Returns a (possibly empty) mapping of (C++ source file, .o output file) pairs for source files
+ * that need to be compiled at link time.
+ *
+ * <p>This is used to embed various values from the build system into binaries to identify their
+ * provenance.
+ */
+ public ImmutableMap<Artifact, Artifact> getLinkstamps() {
+ return linkstamps;
+ }
+
+ /**
+ * Returns the location of the C++ runtime solib symlinks. If null, the C++ dynamic runtime
+ * libraries either do not exist (because they do not come from the depot) or they are in the
+ * regular solib directory.
+ */
+ @Nullable public PathFragment getRuntimeSolibDir() {
+ return runtimeSolibDir;
+ }
+
+ /**
+ * Returns true for libraries linked as native dependencies for other languages.
+ */
+ public boolean isNativeDeps() {
+ return nativeDeps;
+ }
+
+ /**
+ * Returns true if this link should use test-specific flags (e.g. $EXEC_ORIGIN as the root for
+ * finding shared libraries or lazy binding); false by default. See bug "Please use
+ * $EXEC_ORIGIN instead of $ORIGIN when linking cc_tests" for further context.
+ */
+ public boolean useTestOnlyFlags() {
+ return useTestOnlyFlags;
+ }
+
+ /**
+ * Splits the link command-line into a part to be written to a parameter file, and the remaining
+ * actual command line to be executed (which references the parameter file). Call {@link
+ * #canBeSplit} first to check if the command-line can be split.
+ *
+ * @throws IllegalStateException if the command-line cannot be split
+ */
+ @VisibleForTesting
+ final Pair<List<String>, List<String>> splitCommandline(PathFragment paramExecPath) {
+ Preconditions.checkState(canBeSplit());
+ List<String> args = getRawLinkArgv();
+ if (linkTargetType.isStaticLibraryLink()) {
+ // Ar link commands can also generate huge command lines.
+ List<String> paramFileArgs = args.subList(1, args.size());
+ List<String> commandlineArgs = new ArrayList<>();
+ commandlineArgs.add(args.get(0));
+
+ commandlineArgs.add("@" + paramExecPath.getPathString());
+ return Pair.of(commandlineArgs, paramFileArgs);
+ } else {
+ // Gcc link commands tend to generate humongous commandlines for some targets, which may
+ // not fit on some remote execution machines. To work around this we will employ the help of
+ // a parameter file and pass any linker options through it.
+ List<String> paramFileArgs = new ArrayList<>();
+ List<String> commandlineArgs = new ArrayList<>();
+ extractArgumentsForParamFile(args, commandlineArgs, paramFileArgs);
+
+ commandlineArgs.add("-Wl,@" + paramExecPath.getPathString());
+ return Pair.of(commandlineArgs, paramFileArgs);
+ }
+ }
+
+ boolean canBeSplit() {
+ if (!supportsParamFiles) {
+ return false;
+ }
+ switch (linkTargetType) {
+ // We currently can't split dynamic library links if they have interface outputs. That was
+ // probably an unintended side effect of the change that introduced interface outputs.
+ case DYNAMIC_LIBRARY:
+ return interfaceOutput == null;
+ case EXECUTABLE:
+ case STATIC_LIBRARY:
+ case PIC_STATIC_LIBRARY:
+ case ALWAYS_LINK_STATIC_LIBRARY:
+ case ALWAYS_LINK_PIC_STATIC_LIBRARY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static void extractArgumentsForParamFile(List<String> args, List<String> commandlineArgs,
+ List<String> paramFileArgs) {
+ // Note, that it is not important that all linker arguments are extracted so that
+ // they can be moved into a parameter file, but the vast majority should.
+ commandlineArgs.add(args.get(0)); // gcc command, must not be moved!
+ int argsSize = args.size();
+ for (int i = 1; i < argsSize; i++) {
+ String arg = args.get(i);
+ if (arg.equals("-Wl,-no-whole-archive")) {
+ paramFileArgs.add("-no-whole-archive");
+ } else if (arg.equals("-Wl,-whole-archive")) {
+ paramFileArgs.add("-whole-archive");
+ } else if (arg.equals("-Wl,--start-group")) {
+ paramFileArgs.add("--start-group");
+ } else if (arg.equals("-Wl,--end-group")) {
+ paramFileArgs.add("--end-group");
+ } else if (arg.equals("-Wl,--start-lib")) {
+ paramFileArgs.add("--start-lib");
+ } else if (arg.equals("-Wl,--end-lib")) {
+ paramFileArgs.add("--end-lib");
+ } else if (arg.equals("--incremental-unchanged")) {
+ paramFileArgs.add(arg);
+ } else if (arg.equals("--incremental-changed")) {
+ paramFileArgs.add(arg);
+ } else if (arg.charAt(0) == '-') {
+ if (arg.startsWith("-l")) {
+ paramFileArgs.add(arg);
+ } else {
+ // Anything else starting with a '-' can stay on the commandline.
+ commandlineArgs.add(arg);
+ if (arg.equals("-o")) {
+ // Special case for '-o': add the following argument as well - it is the output file!
+ commandlineArgs.add(args.get(++i));
+ }
+ }
+ } else if (arg.endsWith(".a") || arg.endsWith(".lo") || arg.endsWith(".so")
+ || arg.endsWith(".ifso") || arg.endsWith(".o")
+ || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(arg)) {
+ // All objects of any kind go into the linker parameters.
+ paramFileArgs.add(arg);
+ } else {
+ // Everything that's left stays conservatively on the commandline.
+ commandlineArgs.add(arg);
+ }
+ }
+ }
+
+ /**
+ * Returns a raw link command for the given link invocation, including both command and
+ * arguments (argv). After any further usage-specific processing, this can be passed to
+ * {@link #finalizeWithLinkstampCommands} to give the final command line.
+ *
+ * @return raw link command line.
+ */
+ public List<String> getRawLinkArgv() {
+ List<String> argv = new ArrayList<>();
+ switch (linkTargetType) {
+ case EXECUTABLE:
+ addCppArgv(argv);
+ break;
+
+ case DYNAMIC_LIBRARY:
+ if (interfaceOutput != null) {
+ argv.add(configuration.getShExecutable().getPathString());
+ argv.add("-c");
+ argv.add("build_iface_so=\"$0\"; impl=\"$1\"; iface=\"$2\"; cmd=\"$3\"; shift 3; "
+ + "\"$cmd\" \"$@\" && \"$build_iface_so\" \"$impl\" \"$iface\"");
+ argv.add(interfaceSoBuilder.getExecPathString());
+ argv.add(output.getExecPathString());
+ argv.add(interfaceOutput.getExecPathString());
+ }
+ addCppArgv(argv);
+ // -pie is not compatible with -shared and should be
+ // removed when the latter is part of the link command. Should we need to further
+ // distinguish between shared libraries and executables, we could add additional
+ // command line / CROSSTOOL flags that distinguish them. But as long as this is
+ // the only relevant use case we're just special-casing it here.
+ Iterables.removeIf(argv, Predicates.equalTo("-pie"));
+ break;
+
+ case STATIC_LIBRARY:
+ case PIC_STATIC_LIBRARY:
+ case ALWAYS_LINK_STATIC_LIBRARY:
+ case ALWAYS_LINK_PIC_STATIC_LIBRARY:
+ // The static library link command follows this template:
+ // ar <cmd> <output_archive> <input_files...>
+ argv.add(cppConfiguration.getArExecutable().getPathString());
+ argv.addAll(
+ cppConfiguration.getArFlags(cppConfiguration.archiveType() == Link.ArchiveType.THIN));
+ argv.add(output.getExecPathString());
+ addInputFileLinkOptions(argv, /*needWholeArchive=*/false,
+ /*includeLinkopts=*/false);
+ break;
+
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ // Fission mode: debug info is in .dwo files instead of .o files. Inform the linker of this.
+ if (!linkTargetType.isStaticLibraryLink() && cppConfiguration.useFission()) {
+ argv.add("-Wl,--gdb-index");
+ }
+
+ return argv;
+ }
+
+ @Override
+ public List<String> arguments() {
+ return finalizeWithLinkstampCommands(getRawLinkArgv());
+ }
+
+ /**
+ * Takes a raw link command line and gives the final link command that will
+ * also first compile any linkstamps necessary. Elements of rawLinkArgv are
+ * shell-escaped.
+ *
+ * @param rawLinkArgv raw link command line
+ *
+ * @return final link command line suitable for execution
+ */
+ public List<String> finalizeWithLinkstampCommands(List<String> rawLinkArgv) {
+ return addLinkstampingToCommand(getLinkstampCompileCommands(""), rawLinkArgv, true);
+ }
+
+ /**
+ * Takes a raw link command line and gives the final link command that will also first compile any
+ * linkstamps necessary. Elements of rawLinkArgv are not shell-escaped.
+ *
+ * @param rawLinkArgv raw link command line
+ * @param outputPrefix prefix to add before the linkstamp outputs' exec paths
+ *
+ * @return final link command line suitable for execution
+ */
+ public List<String> finalizeAlreadyEscapedWithLinkstampCommands(
+ List<String> rawLinkArgv, String outputPrefix) {
+ return addLinkstampingToCommand(getLinkstampCompileCommands(outputPrefix), rawLinkArgv, false);
+ }
+
+ /**
+ * Adds linkstamp compilation to the (otherwise) fully specified link
+ * command if {@link #getLinkstamps} is non-empty.
+ *
+ * <p>Linkstamps were historically compiled implicitly as part of the link
+ * command, but implicit compilation doesn't guarantee consistent outputs.
+ * For example, the command "gcc input.o input.o foo/linkstamp.cc -o myapp"
+ * causes gcc to implicitly run "gcc foo/linkstamp.cc -o /tmp/ccEtJHDB.o",
+ * for some internally decided output path /tmp/ccEtJHDB.o, then add that path
+ * to the linker's command line options. The name of this path can change
+ * even between equivalently specified gcc invocations.
+ *
+ * <p>So now we explicitly compile these files in their own command
+ * invocations before running the link command, thus giving us direct
+ * control over the naming of their outputs. This method adds those extra
+ * steps as necessary.
+ * @param linkstampCommands individual linkstamp compilation commands
+ * @param linkCommand the complete list of link command arguments (after
+ * .params file compacting) for an invocation
+ * @param escapeArgs if true, linkCommand arguments are shell escaped. if
+ * false, arguments are returned as-is
+ *
+ * @return The original argument list if no linkstamps compilation commands
+ * are given, otherwise an expanded list that adds the linkstamp
+ * compilation commands and funnels their outputs into the link step.
+ * Note that these outputs only need to persist for the duration of
+ * the link step.
+ */
+ private static List<String> addLinkstampingToCommand(
+ List<String> linkstampCommands,
+ List<String> linkCommand,
+ boolean escapeArgs) {
+ if (linkstampCommands.isEmpty()) {
+ return linkCommand;
+ } else {
+ List<String> batchCommand = Lists.newArrayListWithCapacity(3);
+ batchCommand.add("/bin/bash");
+ batchCommand.add("-c");
+ batchCommand.add(
+ Joiner.on(" && ").join(linkstampCommands) + " && "
+ + (escapeArgs
+ ? ShellEscaper.escapeJoinAll(linkCommand)
+ : Joiner.on(" ").join(linkCommand)));
+ return ImmutableList.copyOf(batchCommand);
+ }
+ }
+
+ /**
+ * Computes, for each C++ source file in
+ * {@link #getLinkstamps}, the command necessary to compile
+ * that file such that the output is correctly fed into the link command.
+ *
+ * <p>As these options (as well as all others) are taken into account when
+ * computing the action key, they do not directly contain volatile build
+ * information to avoid unnecessary relinking. Instead this information is
+ * passed as an additional header generated by
+ * {@link com.google.devtools.build.lib.rules.cpp.WriteBuildInfoHeaderAction}.
+ *
+ * @param outputPrefix prefix to add before the linkstamp outputs' exec paths
+ * @return a list of shell-escaped compiler commmands, one for each entry
+ * in {@link #getLinkstamps}
+ */
+ public List<String> getLinkstampCompileCommands(String outputPrefix) {
+ if (linkstamps.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ String compilerCommand = cppConfiguration.getCppExecutable().getPathString();
+ List<String> commands = Lists.newArrayListWithCapacity(linkstamps.size());
+
+ for (Map.Entry<Artifact, Artifact> linkstamp : linkstamps.entrySet()) {
+ List<String> optionList = new ArrayList<>();
+
+ // Defines related to the build info are read from generated headers.
+ for (Artifact header : buildInfoHeaderArtifacts) {
+ optionList.add("-include");
+ optionList.add(header.getExecPathString());
+ }
+
+ String labelReplacement = Matcher.quoteReplacement(
+ isSharedNativeLibrary() ? output.getExecPathString() : Label.print(owner.getLabel()));
+ String outputPathReplacement = Matcher.quoteReplacement(
+ output.getExecPathString());
+ for (String option : linkstampCompileOptions) {
+ optionList.add(option
+ .replaceAll(Pattern.quote("${LABEL}"), labelReplacement)
+ .replaceAll(Pattern.quote("${OUTPUT_PATH}"), outputPathReplacement));
+ }
+
+ optionList.add("-DGPLATFORM=\"" + cppConfiguration + "\"");
+
+ // Needed to find headers included from linkstamps.
+ optionList.add("-I.");
+
+ // Add sysroot.
+ PathFragment sysroot = cppConfiguration.getSysroot();
+ if (sysroot != null) {
+ optionList.add("--sysroot=" + sysroot.getPathString());
+ }
+
+ // Add toolchain compiler options.
+ optionList.addAll(cppConfiguration.getCompilerOptions(features));
+ optionList.addAll(cppConfiguration.getCOptions());
+ optionList.addAll(cppConfiguration.getUnfilteredCompilerOptions(features));
+
+ // For dynamic libraries, produce position independent code.
+ if (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY
+ && cppConfiguration.toolchainNeedsPic()) {
+ optionList.add("-fPIC");
+ }
+
+ // Stamp FDO builds with FDO subtype string
+ String fdoBuildStamp = CppHelper.getFdoBuildStamp(cppConfiguration);
+ if (fdoBuildStamp != null) {
+ optionList.add("-D" + CppConfiguration.FDO_STAMP_MACRO + "=\"" + fdoBuildStamp + "\"");
+ }
+
+ // Add the compilation target.
+ optionList.add("-c");
+ optionList.add(linkstamp.getKey().getExecPathString());
+
+ // Assemble the final command, exempting outputPrefix from shell escaping.
+ commands.add(compilerCommand + " "
+ + ShellEscaper.escapeJoinAll(optionList)
+ + " -o "
+ + outputPrefix
+ + ShellEscaper.escapeString(linkstamp.getValue().getExecPathString()));
+ }
+
+ return commands;
+ }
+
+ /**
+ * Determine the arguments to pass to the C++ compiler when linking.
+ * Add them to the {@code argv} parameter.
+ */
+ private void addCppArgv(List<String> argv) {
+ argv.add(cppConfiguration.getCppExecutable().getPathString());
+
+ // When using gold to link an executable, output the number of used and unused symbols.
+ if (symbolCountsOutput != null) {
+ argv.add("-Wl,--print-symbol-counts=" + symbolCountsOutput.getExecPathString());
+ }
+
+ if (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY) {
+ argv.add("-shared");
+ }
+
+ // Add the outputs of any associated linkstamp compilations.
+ for (Artifact linkstampOutput : linkstamps.values()) {
+ argv.add(linkstampOutput.getExecPathString());
+ }
+
+ boolean fullyStatic = (linkStaticness == LinkStaticness.FULLY_STATIC);
+ boolean mostlyStatic = (linkStaticness == LinkStaticness.MOSTLY_STATIC);
+ boolean sharedLinkopts =
+ linkTargetType == LinkTargetType.DYNAMIC_LIBRARY
+ || linkopts.contains("-shared")
+ || cppConfiguration.getLinkOptions().contains("-shared");
+
+ if (output != null) {
+ argv.add("-o");
+ String execpath = output.getExecPathString();
+ if (mostlyStatic
+ && linkTargetType == LinkTargetType.EXECUTABLE
+ && cppConfiguration.skipStaticOutputs()) {
+ // Linked binary goes to /dev/null; bogus dependency info in its place.
+ Collections.addAll(argv, "/dev/null", "-MMD", "-MF", execpath); // thanks Ambrose
+ } else {
+ argv.add(execpath);
+ }
+ }
+
+ addInputFileLinkOptions(argv, needWholeArchive, /*includeLinkopts=*/true);
+
+ // Extra toolchain link options based on the output's link staticness.
+ if (fullyStatic) {
+ argv.addAll(cppConfiguration.getFullyStaticLinkOptions(features, sharedLinkopts));
+ } else if (mostlyStatic) {
+ argv.addAll(cppConfiguration.getMostlyStaticLinkOptions(features, sharedLinkopts));
+ } else {
+ argv.addAll(cppConfiguration.getDynamicLinkOptions(features, sharedLinkopts));
+ }
+
+ // Extra test-specific link options.
+ if (useTestOnlyFlags) {
+ argv.addAll(cppConfiguration.getTestOnlyLinkOptions());
+ }
+
+ if (configuration.isCodeCoverageEnabled()) {
+ argv.add("-lgcov");
+ }
+
+ if (linkTargetType == LinkTargetType.EXECUTABLE && cppConfiguration.forcePic()) {
+ argv.add("-pie");
+ }
+
+ argv.addAll(cppConfiguration.getLinkOptions());
+ argv.addAll(cppConfiguration.getFdoSupport().getLinkOptions());
+ }
+
+ private static boolean isDynamicLibrary(LinkerInput linkInput) {
+ Artifact libraryArtifact = linkInput.getArtifact();
+ String name = libraryArtifact.getFilename();
+ return Link.SHARED_LIBRARY_FILETYPES.matches(name) && name.startsWith("lib");
+ }
+
+ private boolean isSharedNativeLibrary() {
+ return nativeDeps && cppConfiguration.shareNativeDeps();
+ }
+
+ /**
+ * When linking a shared library fully or mostly static then we need to link in
+ * *all* dependent files, not just what the shared library needs for its own
+ * code. This is done by wrapping all objects/libraries with
+ * -Wl,-whole-archive and -Wl,-no-whole-archive. For this case the
+ * globalNeedWholeArchive parameter must be set to true. Otherwise only
+ * library objects (.lo) need to be wrapped with -Wl,-whole-archive and
+ * -Wl,-no-whole-archive.
+ */
+ private void addInputFileLinkOptions(List<String> argv, boolean globalNeedWholeArchive,
+ boolean includeLinkopts) {
+ // The Apple ld doesn't support -whole-archive/-no-whole-archive. It
+ // does have -all_load/-noall_load, but -all_load is a global setting
+ // that affects all subsequent files, and -noall_load is simply ignored.
+ // TODO(bazel-team): Not sure what the implications of this are, other than
+ // bloated binaries.
+ boolean macosx = cppConfiguration.getTargetLibc().equals("macosx");
+ if (globalNeedWholeArchive) {
+ argv.add(macosx ? "-Wl,-all_load" : "-Wl,-whole-archive");
+ }
+
+ // Used to collect -L and -Wl,-rpath options, ensuring that each used only once.
+ Set<String> libOpts = new LinkedHashSet<>();
+
+ // List of command line parameters to link input files (either directly or using -l).
+ List<String> linkerInputs = new ArrayList<>();
+
+ // List of command line parameters that need to be placed *outside* of
+ // --whole-archive ... --no-whole-archive.
+ List<String> noWholeArchiveInputs = new ArrayList<>();
+
+ PathFragment solibDir = configuration.getBinDirectory().getExecPath()
+ .getRelative(cppConfiguration.getSolibDirectory());
+ String runtimeSolibName = runtimeSolibDir != null ? runtimeSolibDir.getBaseName() : null;
+ boolean runtimeRpath = runtimeSolibDir != null
+ && (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY
+ || (linkTargetType == LinkTargetType.EXECUTABLE
+ && linkStaticness == LinkStaticness.DYNAMIC));
+
+ String rpathRoot = null;
+ List<String> runtimeRpathEntries = new ArrayList<>();
+
+ if (output != null) {
+ String origin =
+ useTestOnlyFlags && cppConfiguration.supportsExecOrigin() ? "$EXEC_ORIGIN/" : "$ORIGIN/";
+ if (runtimeRpath) {
+ runtimeRpathEntries.add("-Wl,-rpath," + origin + runtimeSolibName + "/");
+ }
+
+ // Calculate the correct relative value for the "-rpath" link option (which sets
+ // the search path for finding shared libraries).
+ if (isSharedNativeLibrary()) {
+ // For shared native libraries, special symlinking is applied to ensure C++
+ // runtimes are available under $ORIGIN/_solib_[arch]. So we set the RPATH to find
+ // them.
+ //
+ // Note that we have to do this because $ORIGIN points to different paths for
+ // different targets. In other words, blaze-bin/d1/d2/d3/a_shareddeps.so and
+ // blaze-bin/d4/b_shareddeps.so have different path depths. The first could
+ // reference a standard blaze-bin/_solib_[arch] via $ORIGIN/../../../_solib[arch],
+ // and the second could use $ORIGIN/../_solib_[arch]. But since this is a shared
+ // artifact, both are symlinks to the same place, so
+ // there's no *one* RPATH setting that fits all targets involved in the sharing.
+ rpathRoot = "-Wl,-rpath," + origin + ":"
+ + origin + cppConfiguration.getSolibDirectory() + "/";
+ if (runtimeRpath) {
+ runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/");
+ }
+ } else {
+ // For all other links, calculate the relative path from the output file to _solib_[arch]
+ // (the directory where all shared libraries are stored, which resides under the blaze-bin
+ // directory. In other words, given blaze-bin/my/package/binary, rpathRoot would be
+ // "../../_solib_[arch]".
+ if (runtimeRpath) {
+ runtimeRpathEntries.add("-Wl,-rpath," + origin
+ + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1)
+ + runtimeSolibName + "/");
+ }
+
+ rpathRoot = "-Wl,-rpath,"
+ + origin + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1)
+ + cppConfiguration.getSolibDirectory() + "/";
+
+ if (nativeDeps) {
+ // We also retain the $ORIGIN/ path to solibs that are in _solib_<arch>, as opposed to
+ // the package directory)
+ if (runtimeRpath) {
+ runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/");
+ }
+ rpathRoot += ":" + origin;
+ }
+ }
+ }
+
+ boolean includeSolibDir = false;
+
+ for (LinkerInput input : getLinkerInputs()) {
+ if (isDynamicLibrary(input)) {
+ PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory();
+ Preconditions.checkState(
+ libDir.startsWith(solibDir),
+ "Artifact '%s' is not under directory '%s'.", input.getArtifact(), solibDir);
+ if (libDir.equals(solibDir)) {
+ includeSolibDir = true;
+ }
+ addDynamicInputLinkOptions(input, linkerInputs, libOpts, solibDir, rpathRoot);
+ } else {
+ addStaticInputLinkOptions(input, linkerInputs);
+ }
+ }
+
+ boolean includeRuntimeSolibDir = false;
+
+ for (LinkerInput input : runtimeInputs) {
+ List<String> optionsList = globalNeedWholeArchive
+ ? noWholeArchiveInputs
+ : linkerInputs;
+
+ if (isDynamicLibrary(input)) {
+ PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory();
+ Preconditions.checkState(runtimeSolibDir != null && libDir.equals(runtimeSolibDir),
+ "Artifact '%s' is not under directory '%s'.", input.getArtifact(), solibDir);
+ includeRuntimeSolibDir = true;
+ addDynamicInputLinkOptions(input, optionsList, libOpts, solibDir, rpathRoot);
+ } else {
+ addStaticInputLinkOptions(input, optionsList);
+ }
+ }
+
+ // rpath ordering matters for performance; first add the one where most libraries are found.
+ if (includeSolibDir && rpathRoot != null) {
+ argv.add(rpathRoot);
+ }
+ if (includeRuntimeSolibDir) {
+ argv.addAll(runtimeRpathEntries);
+ }
+ argv.addAll(libOpts);
+
+ // Need to wrap static libraries with whole-archive option
+ for (String option : linkerInputs) {
+ if (!globalNeedWholeArchive && Link.LINK_LIBRARY_FILETYPES.matches(option)) {
+ argv.add(macosx ? "-Wl,-all_load" : "-Wl,-whole-archive");
+ argv.add(option);
+ argv.add(macosx ? "-Wl,-noall_load" : "-Wl,-no-whole-archive");
+ } else {
+ argv.add(option);
+ }
+ }
+
+ if (globalNeedWholeArchive) {
+ argv.add(macosx ? "-Wl,-noall_load" : "-Wl,-no-whole-archive");
+ argv.addAll(noWholeArchiveInputs);
+ }
+
+ if (includeLinkopts) {
+ /*
+ * For backwards compatibility, linkopts come _after_ inputFiles.
+ * This is needed to allow linkopts to contain libraries and
+ * positional library-related options such as
+ * -Wl,--begin-group -lfoo -lbar -Wl,--end-group
+ * or
+ * -Wl,--as-needed -lfoo -Wl,--no-as-needed
+ *
+ * As for the relative order of the three different flavours of linkopts
+ * (global defaults, per-target linkopts, and command-line linkopts),
+ * we have no idea what the right order should be, or if anyone cares.
+ */
+ argv.addAll(linkopts);
+ }
+ }
+
+ /**
+ * Adds command-line options for a dynamic library input file into
+ * options and libOpts.
+ */
+ private void addDynamicInputLinkOptions(LinkerInput input, List<String> options,
+ Set<String> libOpts, PathFragment solibDir, String rpathRoot) {
+ Preconditions.checkState(isDynamicLibrary(input));
+ Preconditions.checkState(
+ !Link.useStartEndLib(input, cppConfiguration.archiveType()));
+
+ Artifact inputArtifact = input.getArtifact();
+ PathFragment libDir = inputArtifact.getExecPath().getParentDirectory();
+ if (rpathRoot != null
+ && !libDir.equals(solibDir)
+ && (runtimeSolibDir == null || !runtimeSolibDir.equals(libDir))) {
+ String dotdots = "";
+ PathFragment commonParent = solibDir;
+ while (!libDir.startsWith(commonParent)) {
+ dotdots += "../";
+ commonParent = commonParent.getParentDirectory();
+ }
+
+ libOpts.add(rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString());
+ }
+
+ libOpts.add("-L" + inputArtifact.getExecPath().getParentDirectory().getPathString());
+
+ String name = inputArtifact.getFilename();
+ if (CppFileTypes.SHARED_LIBRARY.matches(name)) {
+ String libName = name.replaceAll("(^lib|\\.so$)", "");
+ options.add("-l" + libName);
+ } else {
+ // Interface shared objects have a non-standard extension
+ // that the linker won't be able to find. So use the
+ // filename directly rather than a -l option. Since the
+ // library has an SONAME attribute, this will work fine.
+ options.add(inputArtifact.getExecPathString());
+ }
+ }
+
+ /**
+ * Adds command-line options for a static library or non-library input
+ * into options.
+ */
+ private void addStaticInputLinkOptions(LinkerInput input, List<String> options) {
+ Preconditions.checkState(!isDynamicLibrary(input));
+
+ // start-lib/end-lib library: adds its input object files.
+ if (Link.useStartEndLib(input, cppConfiguration.archiveType())) {
+ Iterable<Artifact> archiveMembers = input.getObjectFiles();
+ if (!Iterables.isEmpty(archiveMembers)) {
+ options.add("-Wl,--start-lib");
+ for (Artifact member : archiveMembers) {
+ options.add(member.getExecPathString());
+ }
+ options.add("-Wl,--end-lib");
+ }
+ // For anything else, add the input directly.
+ } else {
+ Artifact inputArtifact = input.getArtifact();
+ if (input.isFake()) {
+ options.add(Link.FAKE_OBJECT_PREFIX + inputArtifact.getExecPathString());
+ } else {
+ options.add(inputArtifact.getExecPathString());
+ }
+ }
+ }
+
+ /**
+ * A builder for a {@link LinkCommandLine}.
+ */
+ public static final class Builder {
+ // TODO(bazel-team): Pass this in instead of having it here. Maybe move to cc_toolchain.
+ private static final ImmutableList<String> DEFAULT_LINKSTAMP_OPTIONS = ImmutableList.of(
+ // G3_VERSION_INFO and G3_TARGET_NAME are C string literals that normally
+ // contain the label of the target being linked. However, they are set
+ // differently when using shared native deps. In that case, a single .so file
+ // is shared by multiple targets, and its contents cannot depend on which
+ // target(s) were specified on the command line. So in that case we have
+ // to use the (obscure) name of the .so file instead, or more precisely
+ // the path of the .so file relative to the workspace root.
+ "-DG3_VERSION_INFO=\"${LABEL}\"",
+ "-DG3_TARGET_NAME=\"${LABEL}\"",
+
+ // G3_BUILD_TARGET is a C string literal containing the output of this
+ // link. (An undocumented and untested invariant is that G3_BUILD_TARGET is the location of
+ // the executable, either absolutely, or relative to the directory part of BUILD_INFO.)
+ "-DG3_BUILD_TARGET=\"${OUTPUT_PATH}\"");
+
+ private final BuildConfiguration configuration;
+ private final ActionOwner owner;
+
+ @Nullable private Artifact output;
+ @Nullable private Artifact interfaceOutput;
+ @Nullable private Artifact symbolCountsOutput;
+ private ImmutableList<Artifact> buildInfoHeaderArtifacts = ImmutableList.of();
+ private Iterable<? extends LinkerInput> linkerInputs = ImmutableList.of();
+ private Iterable<? extends LinkerInput> runtimeInputs = ImmutableList.of();
+ @Nullable private LinkTargetType linkTargetType;
+ private LinkStaticness linkStaticness = LinkStaticness.FULLY_STATIC;
+ private ImmutableList<String> linkopts = ImmutableList.of();
+ private ImmutableSet<String> features = ImmutableSet.of();
+ private ImmutableMap<Artifact, Artifact> linkstamps = ImmutableMap.of();
+ private List<String> linkstampCompileOptions = new ArrayList<>();
+ @Nullable private PathFragment runtimeSolibDir;
+ private boolean nativeDeps;
+ private boolean useTestOnlyFlags;
+ private boolean needWholeArchive;
+ private boolean supportsParamFiles;
+ @Nullable private Artifact interfaceSoBuilder;
+
+ public Builder(BuildConfiguration configuration, ActionOwner owner) {
+ this.configuration = configuration;
+ this.owner = owner;
+ }
+
+ public Builder(RuleContext ruleContext) {
+ this(ruleContext.getConfiguration(), ruleContext.getActionOwner());
+ }
+
+ public LinkCommandLine build() {
+ ImmutableList<String> actualLinkstampCompileOptions;
+ if (linkstampCompileOptions.isEmpty()) {
+ actualLinkstampCompileOptions = DEFAULT_LINKSTAMP_OPTIONS;
+ } else {
+ actualLinkstampCompileOptions = ImmutableList.copyOf(
+ Iterables.concat(DEFAULT_LINKSTAMP_OPTIONS, linkstampCompileOptions));
+ }
+ return new LinkCommandLine(configuration, owner, output, interfaceOutput,
+ symbolCountsOutput, buildInfoHeaderArtifacts, linkerInputs, runtimeInputs, linkTargetType,
+ linkStaticness, linkopts, features, linkstamps, actualLinkstampCompileOptions,
+ runtimeSolibDir, nativeDeps, useTestOnlyFlags, needWholeArchive, supportsParamFiles,
+ interfaceSoBuilder);
+ }
+
+ /**
+ * Sets the type of the link. It is an error to try to set this to {@link
+ * LinkTargetType#INTERFACE_DYNAMIC_LIBRARY}. Note that all the static target types (see {@link
+ * LinkTargetType#isStaticLibraryLink}) are equivalent, and there is no check that the output
+ * artifact matches the target type extension.
+ */
+ public Builder setLinkTargetType(LinkTargetType linkTargetType) {
+ Preconditions.checkArgument(linkTargetType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY);
+ this.linkTargetType = linkTargetType;
+ return this;
+ }
+
+ /**
+ * Sets the primary output artifact. This must be called before calling {@link #build}.
+ */
+ public Builder setOutput(Artifact output) {
+ this.output = output;
+ return this;
+ }
+
+ /**
+ * Sets a list of linker inputs. These get turned into linker options depending on the
+ * staticness and the target type. This call makes an immutable copy of the inputs, if the
+ * provided Iterable isn't already immutable (see {@link CollectionUtils#makeImmutable}).
+ */
+ public Builder setLinkerInputs(Iterable<LinkerInput> linkerInputs) {
+ this.linkerInputs = CollectionUtils.makeImmutable(linkerInputs);
+ return this;
+ }
+
+ public Builder setRuntimeInputs(ImmutableList<LinkerInput> runtimeInputs) {
+ this.runtimeInputs = runtimeInputs;
+ return this;
+ }
+
+ /**
+ * Sets the additional interface output artifact, which is only used for dynamic libraries. The
+ * {@link #build} method throws an exception if the target type is not {@link
+ * LinkTargetType#DYNAMIC_LIBRARY}.
+ */
+ public Builder setInterfaceOutput(Artifact interfaceOutput) {
+ this.interfaceOutput = interfaceOutput;
+ return this;
+ }
+
+ /**
+ * Sets an additional output artifact that contains symbol counts. The {@link #build} method
+ * throws an exception if this is non-null for a static link (see
+ * {@link LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setSymbolCountsOutput(Artifact symbolCountsOutput) {
+ this.symbolCountsOutput = symbolCountsOutput;
+ return this;
+ }
+
+ /**
+ * Sets the linker options. These are passed to the linker in addition to the other linker
+ * options like linker inputs, symbol count options, etc. The {@link #build} method
+ * throws an exception if the linker options are non-empty for a static link (see {@link
+ * LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setLinkopts(ImmutableList<String> linkopts) {
+ this.linkopts = linkopts;
+ return this;
+ }
+
+ /**
+ * Sets how static the link is supposed to be. For static target types (see {@link
+ * LinkTargetType#isStaticLibraryLink}), the {@link #build} method throws an exception if this
+ * is not {@link LinkStaticness#FULLY_STATIC}. The default setting is {@link
+ * LinkStaticness#FULLY_STATIC}.
+ */
+ public Builder setLinkStaticness(LinkStaticness linkStaticness) {
+ this.linkStaticness = linkStaticness;
+ return this;
+ }
+
+ /**
+ * Sets the binary that should be used to create the interface output for a dynamic library.
+ * This is ignored unless the target type is {@link LinkTargetType#DYNAMIC_LIBRARY} and an
+ * interface output artifact is specified.
+ */
+ public Builder setInterfaceSoBuilder(Artifact interfaceSoBuilder) {
+ this.interfaceSoBuilder = interfaceSoBuilder;
+ return this;
+ }
+
+ /**
+ * Sets the linkstamps. Linkstamps are additional C++ source files that are compiled as part of
+ * the link command. The {@link #build} method throws an exception if the linkstamps are
+ * non-empty for a static link (see {@link LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setLinkstamps(ImmutableMap<Artifact, Artifact> linkstamps) {
+ this.linkstamps = linkstamps;
+ return this;
+ }
+
+ /**
+ * Adds the given C++ compiler options to the list of options passed to the linkstamp
+ * compilation.
+ */
+ public Builder addLinkstampCompileOptions(List<String> linkstampCompileOptions) {
+ this.linkstampCompileOptions.addAll(linkstampCompileOptions);
+ return this;
+ }
+
+ /**
+ * The build info header artifacts are generated header files that are used for link stamping.
+ * The {@link #build} method throws an exception if the build info header artifacts are
+ * non-empty for a static link (see {@link LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setBuildInfoHeaderArtifacts(ImmutableList<Artifact> buildInfoHeaderArtifacts) {
+ this.buildInfoHeaderArtifacts = buildInfoHeaderArtifacts;
+ return this;
+ }
+
+ /**
+ * Sets the features enabled for the rule.
+ */
+ public Builder setFeatures(ImmutableSet<String> features) {
+ this.features = features;
+ return this;
+ }
+
+ /**
+ * Sets the directory of the dynamic runtime libraries, which is added to the rpath. The {@link
+ * #build} method throws an exception if the runtime dir is non-null for a static link (see
+ * {@link LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setRuntimeSolibDir(PathFragment runtimeSolibDir) {
+ this.runtimeSolibDir = runtimeSolibDir;
+ return this;
+ }
+
+ /**
+ * Whether the resulting library is intended to be used as a native library from another
+ * programming language. This influences the rpath. The {@link #build} method throws an
+ * exception if this is true for a static link (see {@link LinkTargetType#isStaticLibraryLink}).
+ */
+ public Builder setNativeDeps(boolean nativeDeps) {
+ this.nativeDeps = nativeDeps;
+ return this;
+ }
+
+ /**
+ * Sets whether to use test-specific linker flags, e.g. {@code $EXEC_ORIGIN} instead of
+ * {@code $ORIGIN} in the rpath or lazy binding.
+ */
+ public Builder setUseTestOnlyFlags(boolean useTestOnlyFlags) {
+ this.useTestOnlyFlags = useTestOnlyFlags;
+ return this;
+ }
+
+ public Builder setNeedWholeArchive(boolean needWholeArchive) {
+ this.needWholeArchive = needWholeArchive;
+ return this;
+ }
+
+ public Builder setSupportsParamFiles(boolean supportsParamFiles) {
+ this.supportsParamFiles = supportsParamFiles;
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkStrategy.java
new file mode 100644
index 0000000000..4f7673ecb2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkStrategy.java
@@ -0,0 +1,35 @@
+// Copyright 2014 Google Inc. 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;
+
+/**
+ * A strategy for executing {@link CppLinkAction}s.
+ *
+ * <p>The linker commands, e.g. "ar", are not necessary functional, i.e.
+ * they may mutate the output file rather than overwriting it.
+ * To avoid this, we need to delete the output file before invoking the
+ * command. That must be done by the classes that extend this class.
+ */
+public abstract class LinkStrategy implements CppLinkActionContext {
+ public LinkStrategy() {
+ }
+
+ /** The strategy name, preferably suitable for passing to --link_strategy. */
+ public abstract String linkStrategyName();
+
+ @Override
+ public String strategyLocality(CppLinkAction execOwner) {
+ return linkStrategyName();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInput.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInput.java
new file mode 100644
index 0000000000..15a8b90c7f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInput.java
@@ -0,0 +1,51 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.Artifact;
+
+/**
+ * Something that appears on the command line of the linker. Since we sometimes expand archive
+ * files to their constituent object files, we need to keep information whether a certain file
+ * contains embedded objects and if so, the list of the object files themselves.
+ */
+public interface LinkerInput {
+ /**
+ * Returns the artifact that is the input of the linker.
+ */
+ Artifact getArtifact();
+
+ /**
+ * Returns the original library to link. If this library is a solib symlink, returns the
+ * artifact the symlink points to, otherwise, the library itself.
+ */
+ Artifact getOriginalLibraryArtifact();
+
+ /**
+ * Whether the input artifact contains object files or is opaque.
+ */
+ boolean containsObjectFiles();
+
+ /**
+ * Returns whether the input artifact is a fake object file or not.
+ */
+ boolean isFake();
+
+ /**
+ * Return the list of object files included in the input artifact, if there are any. It is
+ * legal to call this only when {@link #containsObjectFiles()} returns true.
+ */
+ Iterable<Artifact> getObjectFiles();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java
new file mode 100644
index 0000000000..24120ce44f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java
@@ -0,0 +1,353 @@
+// Copyright 2014 Google Inc. 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.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.concurrent.ThreadSafety;
+
+/**
+ * Factory for creating new {@link LinkerInput} objects.
+ */
+public abstract class LinkerInputs {
+ /**
+ * An opaque linker input that is not a library, for example a linker script or an individual
+ * object file.
+ */
+ @ThreadSafety.Immutable
+ public static class SimpleLinkerInput implements LinkerInput {
+ private final Artifact artifact;
+
+ public SimpleLinkerInput(Artifact artifact) {
+ this.artifact = Preconditions.checkNotNull(artifact);
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public Artifact getOriginalLibraryArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public boolean containsObjectFiles() {
+ return false;
+ }
+
+ @Override
+ public boolean isFake() {
+ return false;
+ }
+
+ @Override
+ public Iterable<Artifact> getObjectFiles() {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+
+ if (!(that instanceof SimpleLinkerInput)) {
+ return false;
+ }
+
+ SimpleLinkerInput other = (SimpleLinkerInput) that;
+ return artifact.equals(other.artifact) && isFake() == other.isFake();
+ }
+
+ @Override
+ public int hashCode() {
+ return artifact.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "SimpleLinkerInput(" + artifact.toString() + ")";
+ }
+ }
+
+ /**
+ * A linker input that is a fake object file generated by cc_fake_binary. The contained
+ * artifact must be an object file.
+ */
+ @ThreadSafety.Immutable
+ private static class FakeLinkerInput extends SimpleLinkerInput {
+ private FakeLinkerInput(Artifact artifact) {
+ super(artifact);
+ Preconditions.checkState(Link.OBJECT_FILETYPES.matches(artifact.getFilename()));
+ }
+
+ @Override
+ public boolean isFake() {
+ return true;
+ }
+ }
+
+ /**
+ * A library the user can link to. This is different from a simple linker input in that it also
+ * has a library identifier.
+ */
+ public interface LibraryToLink extends LinkerInput {
+ /**
+ * Returns whether the library is a solib symlink.
+ */
+ boolean isSolibSymlink();
+ }
+
+ /**
+ * This class represents a solib library symlink. Its library identifier is inherited from
+ * the library that it links to.
+ */
+ @ThreadSafety.Immutable
+ public static class SolibLibraryToLink implements LibraryToLink {
+ private final Artifact solibSymlinkArtifact;
+ private final Artifact libraryArtifact;
+
+ private SolibLibraryToLink(Artifact solibSymlinkArtifact, Artifact libraryArtifact) {
+ this.solibSymlinkArtifact = Preconditions.checkNotNull(solibSymlinkArtifact);
+ this.libraryArtifact = libraryArtifact;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SolibLibraryToLink(%s -> %s",
+ solibSymlinkArtifact.toString(), libraryArtifact.toString());
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return solibSymlinkArtifact;
+ }
+
+ @Override
+ public boolean containsObjectFiles() {
+ return false;
+ }
+
+ @Override
+ public boolean isFake() {
+ return false;
+ }
+
+ @Override
+ public Iterable<Artifact> getObjectFiles() {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public Artifact getOriginalLibraryArtifact() {
+ return libraryArtifact;
+ }
+
+ @Override
+ public boolean isSolibSymlink() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+
+ if (!(that instanceof SolibLibraryToLink)) {
+ return false;
+ }
+
+ SolibLibraryToLink thatSolib = (SolibLibraryToLink) that;
+ return
+ solibSymlinkArtifact.equals(thatSolib.solibSymlinkArtifact) &&
+ libraryArtifact.equals(thatSolib.libraryArtifact);
+ }
+
+ @Override
+ public int hashCode() {
+ return solibSymlinkArtifact.hashCode();
+ }
+ }
+
+ /**
+ * This class represents a library that may contain object files.
+ */
+ @ThreadSafety.Immutable
+ private static class CompoundLibraryToLink implements LibraryToLink {
+ private final Artifact libraryArtifact;
+ private final Iterable<Artifact> objectFiles;
+
+ private CompoundLibraryToLink(Artifact libraryArtifact, Iterable<Artifact> objectFiles) {
+ this.libraryArtifact = Preconditions.checkNotNull(libraryArtifact);
+ this.objectFiles = objectFiles == null ? null : CollectionUtils.makeImmutable(objectFiles);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("CompoundLibraryToLink(%s)", libraryArtifact.toString());
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return libraryArtifact;
+ }
+
+ @Override
+ public Artifact getOriginalLibraryArtifact() {
+ return libraryArtifact;
+ }
+
+ @Override
+ public boolean containsObjectFiles() {
+ return objectFiles != null;
+ }
+
+ @Override
+ public boolean isFake() {
+ return false;
+ }
+
+ @Override
+ public Iterable<Artifact> getObjectFiles() {
+ Preconditions.checkNotNull(objectFiles);
+ return objectFiles;
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+
+ if (!(that instanceof CompoundLibraryToLink)) {
+ return false;
+ }
+
+ return libraryArtifact.equals(((CompoundLibraryToLink) that).libraryArtifact);
+ }
+
+ @Override
+ public int hashCode() {
+ return libraryArtifact.hashCode();
+ }
+
+ @Override
+ public boolean isSolibSymlink() {
+ return false;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // Public factory constructors:
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Creates linker input objects for non-library files.
+ */
+ public static Iterable<LinkerInput> simpleLinkerInputs(Iterable<Artifact> input) {
+ return Iterables.transform(input, new Function<Artifact, LinkerInput>() {
+ @Override
+ public LinkerInput apply(Artifact artifact) {
+ return simpleLinkerInput(artifact);
+ }
+ });
+ }
+
+ /**
+ * Creates a linker input for which we do not know what objects files it consists of.
+ */
+ public static LinkerInput simpleLinkerInput(Artifact artifact) {
+ // This precondition check was in place and *most* of the tests passed with them; the only
+ // exception is when you mention a generated .a file in the srcs of a cc_* rule.
+ // Preconditions.checkArgument(!ARCHIVE_LIBRARY_FILETYPES.contains(artifact.getFileType()));
+ return new SimpleLinkerInput(artifact);
+ }
+
+ /**
+ * Creates a fake linker input. The artifact must be an object file.
+ */
+ public static LinkerInput fakeLinkerInput(Artifact artifact) {
+ return new FakeLinkerInput(artifact);
+ }
+
+ /**
+ * Creates input libraries for which we do not know what objects files it consists of.
+ */
+ public static Iterable<LibraryToLink> opaqueLibrariesToLink(Iterable<Artifact> input) {
+ return Iterables.transform(input, new Function<Artifact, LibraryToLink>() {
+ @Override
+ public LibraryToLink apply(Artifact artifact) {
+ return opaqueLibraryToLink(artifact);
+ }
+ });
+ }
+
+ /**
+ * Creates a solib library symlink from the given artifact.
+ */
+ public static LibraryToLink solibLibraryToLink(Artifact solibSymlink, Artifact original) {
+ return new SolibLibraryToLink(solibSymlink, original);
+ }
+
+ /**
+ * Creates an input library for which we do not know what objects files it consists of.
+ */
+ public static LibraryToLink opaqueLibraryToLink(Artifact artifact) {
+ // This precondition check was in place and *most* of the tests passed with them; the only
+ // exception is when you mention a generated .a file in the srcs of a cc_* rule.
+ // It was very useful for proving that this actually works, though.
+ // Preconditions.checkArgument(
+ // !(artifact.getGeneratingAction() instanceof CppLinkAction) ||
+ // !Link.ARCHIVE_LIBRARY_FILETYPES.contains(artifact.getFileType()));
+ return new CompoundLibraryToLink(artifact, null);
+ }
+
+ /**
+ * Creates a library to link with the specified object files.
+ */
+ public static LibraryToLink newInputLibrary(Artifact library, Iterable<Artifact> objectFiles) {
+ return new CompoundLibraryToLink(library, objectFiles);
+ }
+
+ private static final Function<LibraryToLink, Artifact> LIBRARY_TO_NON_SOLIB =
+ new Function<LibraryToLink, Artifact>() {
+ @Override
+ public Artifact apply(LibraryToLink input) {
+ return input.getOriginalLibraryArtifact();
+ }
+ };
+
+ public static Iterable<Artifact> toNonSolibArtifacts(Iterable<LibraryToLink> libraries) {
+ return Iterables.transform(libraries, LIBRARY_TO_NON_SOLIB);
+ }
+
+ /**
+ * Returns the linker input artifacts from a collection of {@link LinkerInput} objects.
+ */
+ public static Iterable<Artifact> toLibraryArtifacts(Iterable<? extends LinkerInput> artifacts) {
+ return Iterables.transform(artifacts, new Function<LinkerInput, Artifact>() {
+ @Override
+ public Artifact apply(LinkerInput input) {
+ return input.getArtifact();
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkingMode.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkingMode.java
new file mode 100644
index 0000000000..8018108bea
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkingMode.java
@@ -0,0 +1,46 @@
+// Copyright 2014 Google Inc. 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;
+
+/**
+ * This class represents the different linking modes.
+ */
+public enum LinkingMode {
+
+ /**
+ * Everything is linked statically; e.g. {@code gcc -static x.o libfoo.a
+ * libbar.a -lm}. Specified by {@code -static} in linkopts.
+ */
+ FULLY_STATIC,
+
+ /**
+ * Link binaries statically except for system libraries
+ * e.g. {@code gcc x.o libfoo.a libbar.a -lm}. Specified by {@code linkstatic=1}.
+ *
+ * <p>This mode applies to executables.
+ */
+ MOSTLY_STATIC,
+
+ /**
+ * Same as MOSTLY_STATIC, but for shared libraries.
+ */
+ MOSTLY_STATIC_LIBRARIES,
+
+ /**
+ * All libraries are linked dynamically (if a dynamic version is available),
+ * e.g. {@code gcc x.o libfoo.so libbar.so -lm}. Specified by {@code
+ * linkstatic=0}.
+ */
+ DYNAMIC;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LipoContextProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LipoContextProvider.java
new file mode 100644
index 0000000000..a9ffea8f6b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LipoContextProvider.java
@@ -0,0 +1,58 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.Map;
+
+/**
+ * Provides LIPO context information to the LIPO-enabled target configuration.
+ *
+ * <p>This is a rollup of the data collected in the LIPO context collector configuration.
+ * Each target in the LIPO context collector configuration has a {@link TransitiveLipoInfoProvider}
+ * which is used to transitively collect the data, then the {@code cc_binary} that is referred to
+ * in {@code --lipo_context} puts the collected data into {@link LipoContextProvider}, of which
+ * there is only one in any given build.
+ */
+@Immutable
+public final class LipoContextProvider implements TransitiveInfoProvider {
+
+ private final CppCompilationContext cppCompilationContext;
+
+ private final ImmutableMap<Artifact, IncludeScannable> includeScannables;
+ public LipoContextProvider(CppCompilationContext cppCompilationContext,
+ Map<Artifact, IncludeScannable> scannables) {
+ this.cppCompilationContext = cppCompilationContext;
+ this.includeScannables = ImmutableMap.copyOf(scannables);
+ }
+
+ /**
+ * Returns merged compilation context for the whole LIPO subtree.
+ */
+ public CppCompilationContext getLipoContext() {
+ return cppCompilationContext;
+ }
+
+ /**
+ * Returns the map from source artifact to the include scannable object representing
+ * the corresponding FDO source input file.
+ */
+ public ImmutableMap<Artifact, IncludeScannable> getIncludeScannables() {
+ return includeScannables;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalGccStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalGccStrategy.java
new file mode 100644
index 0000000000..80ee23d70b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalGccStrategy.java
@@ -0,0 +1,96 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.ExecutionStrategy;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.common.options.OptionsClassProvider;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Run gcc locally by delegating to spawn.
+ */
+@ExecutionStrategy(name = { "local" },
+ contextType = CppCompileActionContext.class)
+public class LocalGccStrategy implements CppCompileActionContext {
+ private static final Reply CANNED_REPLY = new Reply() {
+ @Override
+ public byte[] getContents() {
+ throw new IllegalStateException("Remotely computed data requested for local action");
+ }
+ };
+
+ public LocalGccStrategy(OptionsClassProvider options) {
+ }
+
+ @Override
+ public String strategyLocality() {
+ return "local";
+ }
+
+ public static void updateEnv(CppCompileAction action, Map<String, String> env) {
+ // We cannot locally execute an action that does not expect to output a .d file, since we would
+ // have no way to tell what files that it included were used during compilation.
+ env.put("INTERCEPT_LOCALLY_EXECUTABLE", action.getDotdFile().artifact() == null ? "0" : "1");
+ }
+
+ @Override
+ public boolean needsIncludeScanning() {
+ return false;
+ }
+
+ @Override
+ public Collection<? extends ActionInput> findAdditionalInputs(CppCompileAction action,
+ ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public CppCompileActionContext.Reply execWithReply(
+ CppCompileAction action, ActionExecutionContext actionExecutionContext)
+ throws ExecException, InterruptedException {
+ Map<String, String> env = new HashMap<>();
+ env.putAll(action.getEnvironment());
+ updateEnv(action, env);
+ actionExecutionContext.getExecutor().getSpawnActionContext(action.getMnemonic())
+ .exec(new BaseSpawn.Local(action.getArgv(), env, action),
+ actionExecutionContext);
+ return null;
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(CppCompileAction action) {
+ return action.estimateResourceConsumptionLocal();
+ }
+
+ @Override
+ public Collection<Artifact> getScannedIncludeFiles(
+ CppCompileAction action, ActionExecutionContext actionExecutionContext) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public Reply getReplyFromException(ExecException e, CppCompileAction action) {
+ return CANNED_REPLY;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalLinkStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalLinkStrategy.java
new file mode 100644
index 0000000000..3e7c863f42
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LocalLinkStrategy.java
@@ -0,0 +1,62 @@
+// Copyright 2014 Google Inc. 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.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.ExecutionStrategy;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+
+import java.util.List;
+
+/**
+ * A link strategy that runs the linking step on the local host.
+ *
+ * <p>The set of input files necessary to successfully complete the link is the middleman-expanded
+ * set of the action's dependency inputs (which includes crosstool and libc dependencies, as
+ * defined by {@link com.google.devtools.build.lib.rules.cpp.CppHelper#getCrosstoolInputsForLink
+ * CppHelper.getCrosstoolInputsForLink}).
+ */
+@ExecutionStrategy(contextType = CppLinkActionContext.class, name = { "local" })
+public final class LocalLinkStrategy extends LinkStrategy {
+
+ public LocalLinkStrategy() {
+ }
+
+ @Override
+ public void exec(CppLinkAction action, ActionExecutionContext actionExecutionContext)
+ throws ExecException, ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+ List<String> argv =
+ action.prepareCommandLine(executor.getExecRoot(), null);
+ executor.getSpawnActionContext(action.getMnemonic()).exec(
+ new BaseSpawn.Local(argv, ImmutableMap.<String, String>of(), action),
+ actionExecutionContext);
+ }
+
+ @Override
+ public String linkStrategyName() {
+ return "local";
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(CppLinkAction action) {
+ return action.estimateResourceConsumptionLocal();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/RemoteIncludeExtractor.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/RemoteIncludeExtractor.java
new file mode 100644
index 0000000000..87a07123c6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/RemoteIncludeExtractor.java
@@ -0,0 +1,52 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.rules.cpp.IncludeParser.Inclusion;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/** Parses a single file for its (direct) includes, possibly using a remote service. */
+public interface RemoteIncludeExtractor extends ActionContext {
+ /** Result of checking if this object should be used to parse a given file. */
+ interface RemoteParseData {
+ boolean shouldParseRemotely();
+ }
+
+ /**
+ * Returns whether to use this object to parse the given file for includes. The returned data
+ * should be passed to {@link #extractInclusions} to direct its behavior.
+ */
+ RemoteParseData shouldParseRemotely(Path file);
+
+ /**
+ * Extracts all inclusions from a given source file, possibly using a remote service.
+ *
+ * @param file the file from which to parse and extract inclusions.
+ * @param actionExecutionContext services in the scope of the action. Like the Err/Out stream
+ * outputs.
+ * @param remoteParseData the returned value of {@link #shouldParseRemotely}.
+ * @return a collection of inclusions, normalized to the cache
+ */
+ public Collection<Inclusion> extractInclusions(Artifact file,
+ ActionExecutionContext actionExecutionContext, RemoteParseData remoteParseData)
+ throws IOException, InterruptedException;
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java
new file mode 100644
index 0000000000..120ba86a34
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java
@@ -0,0 +1,234 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Actions;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+
+/**
+ * Creates mangled symlinks in the solib directory for all shared libraries.
+ * Libraries that have a potential to contain SONAME field rely on the mangled
+ * symlink to the parent directory instead.
+ *
+ * Such symlinks are used by the linker to ensure that all rpath entries can be
+ * specified relative to the $ORIGIN.
+ */
+public final class SolibSymlinkAction extends AbstractAction {
+
+ private final Artifact library;
+ private final Path target;
+ private final Artifact symlink;
+
+ private SolibSymlinkAction(ActionOwner owner, Artifact library, Artifact symlink) {
+ super(owner, ImmutableList.of(library), ImmutableList.of(symlink));
+
+ Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
+ this.library = Preconditions.checkNotNull(library);
+ this.symlink = Preconditions.checkNotNull(symlink);
+ this.target = library.getPath();
+ }
+
+ @Override
+ protected void deleteOutputs(Path execRoot) throws IOException {
+ // Do not delete outputs if action does not intend to do anything.
+ if (target != null) {
+ super.deleteOutputs(execRoot);
+ }
+ }
+
+ @Override
+ public void execute(
+ ActionExecutionContext actionExecutionContext) throws ActionExecutionException {
+ Path mangledPath = symlink.getPath();
+ try {
+ FileSystemUtils.createDirectoryAndParents(mangledPath.getParentDirectory());
+ mangledPath.createSymbolicLink(target);
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create _solib symbolic link '"
+ + symlink.prettyPrint() + "' to target '" + target + "'", e, this, false);
+ }
+ }
+
+ @Override
+ public Artifact getPrimaryInput() {
+ return library;
+ }
+
+ @Override
+ public Artifact getPrimaryOutput() {
+ return symlink;
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return new ResourceSet(/*memoryMb=*/0, /*cpuUsage=*/0, /*ioUsage=*/0.0);
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addPath(symlink.getPath());
+ if (target != null) {
+ f.addPath(target);
+ }
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String getMnemonic() { return "SolibSymlink"; }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "local";
+ }
+
+ @Override
+ protected String getRawProgressMessage() { return null; }
+
+ /**
+ * Replaces shared library artifact with mangled symlink and creates related
+ * symlink action. For artifacts that should retain filename (e.g. libraries
+ * with SONAME tag), link is created to the parent directory instead.
+ *
+ * This action is performed to minimize number of -rpath entries used during
+ * linking process (by essentially "collecting" as many shared libraries as
+ * possible in the single directory), since we will be paying quadratic price
+ * for each additional entry on the -rpath.
+ *
+ * @param ruleContext rule context, that requested symlink.
+ * @param library Shared library artifact that needs to be mangled.
+ * @param preserveName whether to preserve the name of the library
+ * @param prefixConsumer whether to prefix the output artifact name with the label of the
+ * consumer
+ * @return mangled symlink artifact.
+ */
+ public static LibraryToLink getDynamicLibrarySymlink(final RuleContext ruleContext,
+ final Artifact library,
+ boolean preserveName,
+ boolean prefixConsumer,
+ BuildConfiguration configuration) {
+ PathFragment mangledName = getMangledName(
+ ruleContext, library.getRootRelativePath(), preserveName, prefixConsumer,
+ configuration.getFragment(CppConfiguration.class));
+ return getDynamicLibrarySymlinkInternal(ruleContext, library, mangledName, configuration);
+ }
+
+ /**
+ * Version of {@link #getDynamicLibrarySymlink} for the special case of C++ runtime libraries.
+ * These are handled differently than other libraries: neither their names nor directories are
+ * mangled, i.e. libstdc++.so.6 is symlinked from _solib_[arch]/libstdc++.so.6
+ */
+ public static LibraryToLink getCppRuntimeSymlink(RuleContext ruleContext, Artifact library,
+ String solibDirOverride, BuildConfiguration configuration) {
+ PathFragment solibDir = new PathFragment(solibDirOverride != null
+ ? solibDirOverride
+ : configuration.getFragment(CppConfiguration.class).getSolibDirectory());
+ PathFragment symlinkName = solibDir.getRelative(library.getRootRelativePath().getBaseName());
+ return getDynamicLibrarySymlinkInternal(ruleContext, library, symlinkName, configuration);
+ }
+
+ /**
+ * Internal implementation that takes a pre-determined symlink name; supports both the
+ * generic {@link #getDynamicLibrarySymlink} and the specialized {@link #getCppRuntimeSymlink}.
+ */
+ private static LibraryToLink getDynamicLibrarySymlinkInternal(RuleContext ruleContext,
+ Artifact library, PathFragment symlinkName, BuildConfiguration configuration) {
+ Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
+ Preconditions.checkArgument(!library.getRootRelativePath().getSegment(0).startsWith("_solib_"));
+
+ // Ignore libraries that are already represented by the symlinks.
+ Root root = configuration.getBinDirectory();
+ Artifact symlink = ruleContext.getAnalysisEnvironment().getDerivedArtifact(symlinkName, root);
+ ruleContext.registerAction(
+ new SolibSymlinkAction(ruleContext.getActionOwner(), library, symlink));
+ return LinkerInputs.solibLibraryToLink(symlink, library);
+ }
+
+ /**
+ * Returns the name of the symlink that will be created for a library, given
+ * its name.
+ *
+ * @param ruleContext rule context that requests symlink
+ * @param libraryPath the root-relative path of the library
+ * @param preserveName true if filename should be preserved
+ * @param prefixConsumer true if the result should be prefixed with the label of the consumer
+ * @returns root relative path name
+ */
+ public static PathFragment getMangledName(RuleContext ruleContext,
+ PathFragment libraryPath,
+ boolean preserveName,
+ boolean prefixConsumer,
+ CppConfiguration cppConfiguration) {
+ String escapedRulePath = Actions.escapedPath(
+ "_" + ruleContext.getLabel());
+ String soname = getDynamicLibrarySoname(libraryPath, preserveName);
+ PathFragment solibDir = new PathFragment(cppConfiguration.getSolibDirectory());
+ if (preserveName) {
+ String escapedLibraryPath =
+ Actions.escapedPath("_" + libraryPath.getParentDirectory().getPathString());
+ PathFragment mangledDir = solibDir.getRelative(prefixConsumer
+ ? escapedRulePath + "__" + escapedLibraryPath
+ : escapedLibraryPath);
+ return mangledDir.getRelative(soname);
+ } else {
+ return solibDir.getRelative(prefixConsumer
+ ? escapedRulePath + "__" + soname
+ : soname);
+ }
+ }
+
+ /**
+ * Compute the SONAME to use for a dynamic library. This name is basically the
+ * name of the shared library in its final symlinked location.
+ *
+ * @param libraryPath name of the shared library that needs to be mangled
+ * @param preserveName true if filename should be preserved, false - mangled
+ * @return soname to embed in the dynamic library
+ */
+ public static String getDynamicLibrarySoname(PathFragment libraryPath,
+ boolean preserveName) {
+ String mangledName;
+ if (preserveName) {
+ mangledName = libraryPath.getBaseName();
+ } else {
+ mangledName = "lib" + Actions.escapedPath(libraryPath.getPathString());
+ }
+ return mangledName;
+ }
+
+ @Override
+ public boolean shouldReportPathPrefixConflict(Action action) {
+ return false; // Always ignore path prefix conflict for the SolibSymlinkAction.
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/TransitiveLipoInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/TransitiveLipoInfoProvider.java
new file mode 100644
index 0000000000..40941249a2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/TransitiveLipoInfoProvider.java
@@ -0,0 +1,51 @@
+// Copyright 2014 Google Inc. 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.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;
+
+/**
+ * A target that can contribute profiling information to LIPO C++ compilations.
+ *
+ * <p>This is used in the LIPO context collector tree to collect data from the transitive
+ * closure of the :lipo_context_collector target. It is eventually passed to the configured
+ * targets in the target configuration through {@link LipoContextProvider}.
+ */
+@Immutable
+public final class TransitiveLipoInfoProvider implements TransitiveInfoProvider {
+ public static final TransitiveLipoInfoProvider EMPTY =
+ new TransitiveLipoInfoProvider(
+ NestedSetBuilder.<IncludeScannable>emptySet(Order.STABLE_ORDER));
+
+ private final NestedSet<IncludeScannable> includeScannables;
+
+ public TransitiveLipoInfoProvider(NestedSet<IncludeScannable> includeScannables) {
+ this.includeScannables = includeScannables;
+ }
+
+ /**
+ * Returns the include scannables in the transitive closure.
+ *
+ * <p>This is used for constructing the path fragment -> include scannable map in the
+ * LIPO-enabled target configuration.
+ */
+ public NestedSet<IncludeScannable> getTransitiveIncludeScannables() {
+ return includeScannables;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/WriteBuildInfoHeaderAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/WriteBuildInfoHeaderAction.java
new file mode 100644
index 0000000000..58b33309b6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/WriteBuildInfoHeaderAction.java
@@ -0,0 +1,194 @@
+// Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.analysis.BuildInfoHelper;
+import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An action that creates a C++ header containing the build information in the
+ * form of #define directives.
+ */
+public final class WriteBuildInfoHeaderAction extends AbstractFileWriteAction {
+ private static final String GUID = "b0798174-1352-4a54-854a-9785aaea491b";
+
+ private final ImmutableList<Artifact> valueArtifacts;
+
+ private final boolean writeVolatileInfo;
+ private final boolean writeStableInfo;
+
+ /**
+ * Creates an action that writes a C++ header with the build information.
+ *
+ * <p>It reads the set of build info keys from an action context that is usually contributed
+ * to Bazel by the workspace status module, and the value associated with said keys from the
+ * workspace status files (stable and volatile) written by the workspace status action.
+ *
+ * <p>Without input artifacts this action uses redacted build information.
+ * @param inputs Artifacts that contain build information, or an empty
+ * collection to use redacted build information
+ * @param output the C++ header Artifact created by this action
+ * @param writeVolatileInfo whether to write the volatile part of the build
+ * information to the generated header
+ * @param writeStableInfo whether to write the non-volatile part of the
+ * build information to the generated header
+ */
+ public WriteBuildInfoHeaderAction(Collection<Artifact> inputs,
+ Artifact output, boolean writeVolatileInfo, boolean writeStableInfo) {
+ super(BuildInfoHelper.BUILD_INFO_ACTION_OWNER,
+ inputs, output, /*makeExecutable=*/false);
+ valueArtifacts = ImmutableList.copyOf(inputs);
+ if (!inputs.isEmpty()) {
+ // With non-empty inputs we should not generate both volatile and non-volatile data
+ // in the same header file.
+ Preconditions.checkState(writeVolatileInfo ^ writeStableInfo);
+ }
+ Preconditions.checkState(
+ output.isConstantMetadata() == (writeVolatileInfo && !inputs.isEmpty()));
+
+ this.writeVolatileInfo = writeVolatileInfo;
+ this.writeStableInfo = writeStableInfo;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor)
+ throws IOException {
+ WorkspaceStatusAction.Context context =
+ executor.getContext(WorkspaceStatusAction.Context.class);
+
+ final Map<String, WorkspaceStatusAction.Key> keys = new LinkedHashMap<>();
+ if (writeVolatileInfo) {
+ keys.putAll(context.getVolatileKeys());
+ }
+
+ if (writeStableInfo) {
+ keys.putAll(context.getStableKeys());
+ }
+
+ final Map<String, String> values = new LinkedHashMap<>();
+ for (Artifact valueFile : valueArtifacts) {
+ values.putAll(WorkspaceStatusAction.parseValues(valueFile.getPath()));
+ }
+
+ final boolean redacted = valueArtifacts.isEmpty();
+
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ Writer writer = new OutputStreamWriter(out, UTF_8);
+
+ for (Map.Entry<String, WorkspaceStatusAction.Key> key : keys.entrySet()) {
+ if (!key.getValue().isInLanguage("C++")) {
+ continue;
+ }
+
+ String value = redacted ? key.getValue().getRedactedValue()
+ : values.containsKey(key.getKey()) ? values.get(key.getKey())
+ : key.getValue().getDefaultValue();
+
+ switch (key.getValue().getType()) {
+ case VERBATIM:
+ case INTEGER:
+ break;
+
+ case STRING:
+ value = quote(value);
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ define(writer, key.getKey(), value);
+
+ }
+ writer.flush();
+ }
+ };
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addBoolean(writeStableInfo);
+ f.addBoolean(writeVolatileInfo);
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public boolean executeUnconditionally() {
+ // Note: isVolatile must return true if executeUnconditionally can ever return true
+ // for this instance.
+ return isUnconditional();
+ }
+
+ @Override
+ public boolean isVolatile() {
+ return isUnconditional();
+ }
+
+ private boolean isUnconditional() {
+ // Because of special handling in the MetadataHandler, changed volatile build
+ // information does not trigger relinking of all libraries that have
+ // linkstamps. But we do want to regenerate the header in case libraries are
+ // relinked because of other reasons.
+ // Without inputs the contents of the header do not change, so there is no
+ // point in executing the action again in that case.
+ return writeVolatileInfo && !Iterables.isEmpty(getInputs());
+ }
+
+ /**
+ * Quote a string with double quotes.
+ */
+ private String quote(String string) {
+ // TODO(bazel-team): This is doesn't really work if the string contains quotes. Or a newline.
+ // Or a backslash. Or anything unusual, really.
+ return "\"" + string + "\"";
+ }
+
+ /**
+ * Write a preprocessor define directive to a Writer.
+ */
+ private void define(Writer writer, String name, String value) throws IOException {
+ writer.write("#define ");
+ writer.write(name);
+ writer.write(' ');
+ writer.write(value);
+ writer.write('\n');
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return null;
+ }
+}