// 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.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.RuleErrorConsumer;
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.Type;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.util.IncludeScanningUtil;
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.
*
*
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 expandMakeVariables(
RuleContext ruleContext, String attributeName, List input) {
boolean tokenization =
!ruleContext.getFeatures().contains("no_copts_tokenization");
List 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 getAttributeCopts(RuleContext ruleContext, String attr) {
Preconditions.checkArgument(ruleContext.getRule().isAttrDefined(attr, Type.STRING_LIST));
List 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 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 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 (LabelSyntaxException 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.
*
* 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 createExtractInclusions(RuleContext ruleContext,
Iterable prerequisites) {
Map extractions = new HashMap<>();
for (Artifact prerequisite : prerequisites) {
if (extractions.containsKey(prerequisite)) {
// Don't create duplicate actions just because user specified same header file twice.
continue;
}
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.
*
* 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.getShareableArtifact(relOut, root);
}
/**
* Returns the linked artifact.
*/
public static Artifact getLinkedArtifact(RuleContext ruleContext, LinkTargetType linkType) {
PathFragment name = new PathFragment(ruleContext.getLabel().getName());
if (linkType != LinkTargetType.EXECUTABLE) {
name = name.replaceName("lib" + name.getBaseName() + linkType.getExtension());
}
return ruleContext.getPackageRelativeArtifact(
name, ruleContext.getConfiguration().getBinDirectory());
}
/**
* Resolves the linkstamp collection from the {@code CcLinkParams} into a map.
*
*
Emits a warning on the rule if there are identical linkstamp artifacts with different
* compilation contexts.
*/
public static Map> resolveLinkstamps(RuleErrorConsumer listener,
CcLinkParams linkParams) {
Map> result = new LinkedHashMap<>();
for (Linkstamp pair : linkParams.getLinkstamps()) {
Artifact artifact = pair.getArtifact();
if (result.containsKey(artifact)) {
listener.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 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.attributes().has("malloc", BuildType.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) {
// Create the module map artifact as a genfile.
Artifact mapFile = ruleContext.getPackageRelativeArtifact(
ruleContext.getLabel().getName()
+ Iterables.getOnlyElement(CppFileTypes.CPP_MODULE_MAP.getExtensions()),
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.
*
* The resulting middleman only aggregates the inputs and must be expanded
* before populating the set of files necessary to execute an action.
*/
static List 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 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 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 artifacts = dep.getProvider(FileProvider.class).getFilesToBuild();
if (useSolibSymlinks) {
List 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, ruleContext.getPackageDirectory(),
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());
}
/**
* Creates an action to strip an executable.
*/
public 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));
}
}