// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.cpp; import static com.google.devtools.build.lib.syntax.Type.BOOLEAN; import com.google.common.base.Preconditions; 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.Actions; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; 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.LicensesProviderImpl; import com.google.devtools.build.lib.analysis.MiddlemanProvider; import com.google.devtools.build.lib.analysis.PlatformConfiguration; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; 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.TemplateVariableInfo; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; 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.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; 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.events.Location; import com.google.devtools.build.lib.packages.License; import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool; import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoException; import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoMode; import com.google.devtools.build.lib.syntax.Type; 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.FileSystemUtils; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyKey; import com.google.protobuf.TextFormat; import com.google.protobuf.TextFormat.ParseException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.annotation.Nullable; /** * Implementation for the cc_toolchain rule. */ public class CcToolchain implements RuleConfiguredTargetFactory { /** Default attribute name where rules store the reference to cc_toolchain */ public static final String CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME = ":cc_toolchain"; /** Default attribute name for the c++ toolchain type */ public static final String CC_TOOLCHAIN_TYPE_ATTRIBUTE_NAME = "$cc_toolchain_type"; /** * This file (found under the sysroot) may be unconditionally included in every C/C++ compilation. */ private static final PathFragment BUILTIN_INCLUDE_FILE_SUFFIX = PathFragment.create("include/stdc-predef.h"); /* * Returns the profile name with the same file name as fdoProfile and an * extension that matches {@link FileType}. */ private static String getLLVMProfileFileName(PathFragment fdoProfile, FileType type) { if (type.matches(fdoProfile)) { return fdoProfile.getBaseName(); } else { return FileSystemUtils.removeExtension(fdoProfile.getBaseName()) + type.getExtensions().get(0); } } private static final String SYSROOT_START = "%sysroot%/"; private static final String WORKSPACE_START = "%workspace%/"; private static final String CROSSTOOL_START = "%crosstool_top%/"; private static final String PACKAGE_START = "%package("; private static final String PACKAGE_END = ")%"; /** * Resolve the given include directory. * *
If it starts with %sysroot%/, that part is replaced with the actual sysroot. * *
If it starts with %workspace%/, that part is replaced with the empty string (essentially * making it relative to the build directory). * *
If it starts with %crosstool_top%/ or is any relative path, it is interpreted relative to * the crosstool top. The use of assumed-crosstool-relative specifications is considered * deprecated, and all such uses should eventually be replaced by "%crosstool_top%/". * *
If it is of the form %package(@repository//my/package)%/folder, then it is interpreted as * the named folder in the appropriate package. All of the normal package syntax is supported. The * /folder part is optional. * *
It is illegal if it starts with a % and does not match any of the above forms to avoid * accidentally silently ignoring misspelled prefixes. * *
If it is absolute, it remains unchanged.
*/
static PathFragment resolveIncludeDir(
String s, PathFragment sysroot, PathFragment crosstoolTopPathFragment)
throws InvalidConfigurationException {
PathFragment pathPrefix;
String pathString;
int packageEndIndex = s.indexOf(PACKAGE_END);
if (packageEndIndex != -1 && s.startsWith(PACKAGE_START)) {
String packageString = s.substring(PACKAGE_START.length(), packageEndIndex);
try {
pathPrefix = PackageIdentifier.parse(packageString).getSourceRoot();
} catch (LabelSyntaxException e) {
throw new InvalidConfigurationException("The package '" + packageString + "' is not valid");
}
int pathStartIndex = packageEndIndex + PACKAGE_END.length();
if (pathStartIndex + 1 < s.length()) {
if (s.charAt(pathStartIndex) != '/') {
throw new InvalidConfigurationException(
"The path in the package for '" + s + "' is not valid");
}
pathString = s.substring(pathStartIndex + 1, s.length());
} else {
pathString = "";
}
} else if (s.startsWith(SYSROOT_START)) {
if (sysroot == null) {
throw new InvalidConfigurationException(
"A %sysroot% prefix is only allowed if the " + "default_sysroot option is set");
}
pathPrefix = sysroot;
pathString = s.substring(SYSROOT_START.length(), s.length());
} else if (s.startsWith(WORKSPACE_START)) {
pathPrefix = PathFragment.EMPTY_FRAGMENT;
pathString = s.substring(WORKSPACE_START.length(), s.length());
} else {
pathPrefix = crosstoolTopPathFragment;
if (s.startsWith(CROSSTOOL_START)) {
pathString = s.substring(CROSSTOOL_START.length(), s.length());
} else if (s.startsWith("%")) {
throw new InvalidConfigurationException(
"The include path '" + s + "' has an " + "unrecognized %prefix%");
} else {
pathString = s;
}
}
if (!PathFragment.isNormalized(pathString)) {
throw new InvalidConfigurationException("The include path '" + s + "' is not normalized.");
}
PathFragment path = PathFragment.create(pathString);
return pathPrefix.getRelative(path);
}
private Artifact getPrefetchHintsArtifact(
FdoInputFile prefetchHintsFile, RuleContext ruleContext) {
Artifact prefetchHintsArtifact = null;
if (prefetchHintsFile != null) {
prefetchHintsArtifact = prefetchHintsFile.getArtifact();
if (prefetchHintsArtifact == null) {
prefetchHintsArtifact =
ruleContext.getUniqueDirectoryArtifact(
"fdo",
prefetchHintsFile.getAbsolutePath().getBaseName(),
ruleContext.getBinOrGenfilesDirectory());
ruleContext.registerAction(
new SymlinkAction(
ruleContext.getActionOwner(),
PathFragment.create(prefetchHintsFile.getAbsolutePath().getPathString()),
prefetchHintsArtifact,
"Symlinking LLVM Cache Prefetch Hints Profile "
+ prefetchHintsFile.getAbsolutePath().getPathString()));
}
}
return prefetchHintsArtifact;
}
/*
* This function checks the format of the input profile data and converts it to
* the indexed format (.profdata) if necessary.
*/
private Artifact convertLLVMRawProfileToIndexed(
PathFragment fdoProfile, CppToolchainInfo toolchainInfo, RuleContext ruleContext)
throws InterruptedException {
Artifact profileArtifact =
ruleContext.getUniqueDirectoryArtifact(
"fdo",
getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE),
ruleContext.getBinOrGenfilesDirectory());
// If the profile file is already in the desired format, symlink to it and return.
if (CppFileTypes.LLVM_PROFILE.matches(fdoProfile)) {
ruleContext.registerAction(
new SymlinkAction(
ruleContext.getActionOwner(),
fdoProfile,
profileArtifact,
"Symlinking LLVM Profile " + fdoProfile.getPathString()));
return profileArtifact;
}
Artifact rawProfileArtifact;
if (fdoProfile.getBaseName().endsWith(".zip")) {
// Get the zipper binary for unzipping the profile.
Artifact zipperBinaryArtifact = ruleContext.getPrerequisiteArtifact(":zipper", Mode.HOST);
if (zipperBinaryArtifact == null) {
ruleContext.ruleError("Cannot find zipper binary to unzip the profile");
return null;
}
// TODO(zhayu): find a way to avoid hard-coding cpu architecture here (b/65582760)
String rawProfileFileName = "fdocontrolz_profile.profraw";
String cpu = toolchainInfo.getTargetCpu();
if (!"k8".equals(cpu)) {
rawProfileFileName = "fdocontrolz_profile-" + cpu + ".profraw";
}
rawProfileArtifact =
ruleContext.getUniqueDirectoryArtifact(
"fdo", rawProfileFileName, ruleContext.getBinOrGenfilesDirectory());
// Symlink to the zipped profile file to extract the contents.
Artifact zipProfileArtifact =
ruleContext.getUniqueDirectoryArtifact(
"fdo", fdoProfile.getBaseName(), ruleContext.getBinOrGenfilesDirectory());
ruleContext.registerAction(
new SymlinkAction(
ruleContext.getActionOwner(),
PathFragment.create(fdoProfile.getPathString()),
zipProfileArtifact,
"Symlinking LLVM ZIP Profile " + fdoProfile.getPathString()));
// Unzip the profile.
ruleContext.registerAction(
new SpawnAction.Builder()
.addInput(zipProfileArtifact)
.addInput(zipperBinaryArtifact)
.addOutput(rawProfileArtifact)
.useDefaultShellEnvironment()
.setExecutable(zipperBinaryArtifact)
.setProgressMessage(
"LLVMUnzipProfileAction: Generating %s", rawProfileArtifact.prettyPrint())
.setMnemonic("LLVMUnzipProfileAction")
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("xf", zipProfileArtifact)
.add(
"-d",
rawProfileArtifact.getExecPath().getParentDirectory().getSafePathString())
.build())
.build(ruleContext));
} else {
rawProfileArtifact =
ruleContext.getUniqueDirectoryArtifact(
"fdo",
getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE_RAW),
ruleContext.getBinOrGenfilesDirectory());
ruleContext.registerAction(
new SymlinkAction(
ruleContext.getActionOwner(),
PathFragment.create(fdoProfile.getPathString()),
rawProfileArtifact,
"Symlinking LLVM Raw Profile " + fdoProfile.getPathString()));
}
if (toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA) == null) {
ruleContext.ruleError(
"llvm-profdata not available with this crosstool, needed for profile conversion");
return null;
}
// Convert LLVM raw profile to indexed format.
ruleContext.registerAction(
new SpawnAction.Builder()
.addInput(rawProfileArtifact)
.addTransitiveInputs(getFiles(ruleContext, "all_files"))
.addOutput(profileArtifact)
.useDefaultShellEnvironment()
.setExecutable(toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA))
.setProgressMessage("LLVMProfDataAction: Generating %s", profileArtifact.prettyPrint())
.setMnemonic("LLVMProfDataAction")
.addCommandLine(
CustomCommandLine.builder()
.add("merge")
.add("-o")
.addExecPath(profileArtifact)
.addExecPath(rawProfileArtifact)
.build())
.build(ruleContext));
return profileArtifact;
}
@Override
public ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException, ActionConflictException {
BuildConfiguration configuration = Preconditions.checkNotNull(ruleContext.getConfiguration());
CppConfiguration cppConfiguration =
Preconditions.checkNotNull(configuration.getFragment(CppConfiguration.class));
CppToolchainInfo toolchainInfo = getCppToolchainInfo(ruleContext, cppConfiguration);
PathFragment fdoZip = null;
FdoInputFile prefetchHints = null;
if (configuration.getCompilationMode() == CompilationMode.OPT) {
if (cppConfiguration.getFdoPrefetchHintsLabel() != null) {
FdoPrefetchHintsProvider provider =
ruleContext.getPrerequisite(
":fdo_prefetch_hints", Mode.TARGET, FdoPrefetchHintsProvider.PROVIDER);
prefetchHints = provider.getInputFile();
}
if (cppConfiguration.getFdoPath() != null) {
fdoZip = cppConfiguration.getFdoPath();
} else if (cppConfiguration.getFdoOptimizeLabel() != null) {
Artifact fdoArtifact =
ruleContext.getPrerequisiteArtifact(CcToolchainRule.FDO_OPTIMIZE_ATTR, Mode.TARGET);
if (!fdoArtifact.isSourceArtifact()) {
ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
return null;
}
Label fdoLabel =
ruleContext.getPrerequisite(CcToolchainRule.FDO_OPTIMIZE_ATTR, Mode.TARGET).getLabel();
if (!fdoLabel
.getPackageIdentifier()
.getPathUnderExecRoot()
.getRelative(fdoLabel.getName())
.equals(fdoArtifact.getExecPath())) {
ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
return null;
}
fdoZip = fdoArtifact.getPath().asFragment();
} else if (cppConfiguration.getFdoProfileLabel() != null) {
FdoProfileProvider fdoProvider =
ruleContext.getPrerequisite(
CcToolchainRule.FDO_PROFILE_ATTR, Mode.TARGET, FdoProfileProvider.PROVIDER);
FdoInputFile inputFile = fdoProvider.getInputFile();
fdoZip =
inputFile.getAbsolutePath() != null
? inputFile.getAbsolutePath()
: inputFile.getArtifact().getPath().asFragment();
}
}
FileTypeSet validExtensions =
FileTypeSet.of(
CppFileTypes.GCC_AUTO_PROFILE,
CppFileTypes.XBINARY_PROFILE,
CppFileTypes.LLVM_PROFILE,
CppFileTypes.LLVM_PROFILE_RAW,
FileType.of(".zip"));
if (fdoZip != null && !validExtensions.matches(fdoZip.getPathString())) {
ruleContext.ruleError("invalid extension for FDO profile file.");
return null;
}
FdoMode fdoMode;
if (fdoZip == null) {
fdoMode = FdoMode.OFF;
} else if (CppFileTypes.GCC_AUTO_PROFILE.matches(fdoZip.getBaseName())) {
fdoMode = FdoMode.AUTO_FDO;
} else if (isLLVMOptimizedFdo(toolchainInfo.isLLVMCompiler(), fdoZip)) {
fdoMode = FdoMode.LLVM_FDO;
} else if (CppFileTypes.XBINARY_PROFILE.matches(fdoZip.getBaseName())) {
fdoMode = FdoMode.XBINARY_FDO;
} else {
fdoMode = FdoMode.VANILLA;
}
SkyKey fdoKey =
FdoSupportValue.key(
fdoZip,
prefetchHints,
cppConfiguration.getFdoInstrument(),
fdoMode);
SkyFunction.Environment skyframeEnv = ruleContext.getAnalysisEnvironment().getSkyframeEnv();
FdoSupportValue fdoSupport;
try {
fdoSupport = (FdoSupportValue) skyframeEnv.getValueOrThrow(
fdoKey, FdoException.class, IOException.class);
} catch (FdoException | IOException e) {
ruleContext.ruleError("cannot initialize FDO: " + e.getMessage());
return null;
}
if (skyframeEnv.valuesMissing()) {
return null;
}
final Label label = ruleContext.getLabel();
final NestedSet This method is meant to be overridden by subclasses of CcToolchain.
*/
protected void addBuildVariables(RuleContext ruleContext, CcToolchainVariables.Builder variables)
throws RuleErrorException {
// To be overridden in subclasses.
}
private PathFragment calculateSysroot(RuleContext ruleContext, PathFragment defaultSysroot) {
TransitiveInfoCollection sysrootTarget =
ruleContext.getPrerequisite(CcToolchainRule.LIBC_TOP_ATTR, Mode.TARGET);
if (sysrootTarget == null) {
return defaultSysroot;
}
return sysrootTarget.getLabel().getPackageFragment();
}
}