// 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.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.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.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.config.CompilationMode; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.cmdline.Label; 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.MakeVariableProvider; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.cpp.FdoSupport.FdoException; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.Preconditions; 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.SkyFunction; import com.google.devtools.build.skyframe.SkyKey; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Implementation for the cc_toolchain rule. */ public class CcToolchain implements RuleConfiguredTargetFactory { /** * 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"); private static String getLLVMProfileFileName(Path fdoProfile) { if (CppFileTypes.LLVM_PROFILE.matches(fdoProfile)) { return fdoProfile.getBaseName(); } else { return FileSystemUtils.removeExtension(fdoProfile.getBaseName()) + CppFileTypes.LLVM_PROFILE.getExtensions().get(0); } } /* * This function checks the format of the input profile data and converts it to * the indexed format (.profdata) if necessary. */ private Artifact convertLLVMRawProfileToIndexed( Path fdoProfile, CppConfiguration cppConfiguration, RuleContext ruleContext) throws InterruptedException { Artifact profileArtifact = ruleContext.getUniqueDirectoryArtifact( "fdo", getLLVMProfileFileName(fdoProfile), 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(), PathFragment.create(fdoProfile.getPathString()), profileArtifact, "Symlinking LLVM Profile " + fdoProfile.getPathString())); return profileArtifact; } Artifact rawProfileArtifact = ruleContext.getUniqueDirectoryArtifact( "fdo", fdoProfile.getBaseName(), ruleContext.getBinOrGenfilesDirectory()); ruleContext.registerAction( new SymlinkAction( ruleContext.getActionOwner(), PathFragment.create(fdoProfile.getPathString()), rawProfileArtifact, "Symlinking LLVM Profile " + fdoProfile.getPathString())); if (cppConfiguration.getLLVMProfDataExecutable() == 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(cppConfiguration.getLLVMProfDataExecutable()) .addArguments("merge", "-o", profileArtifact.getExecPathString()) .addArgument(rawProfileArtifact.getExecPathString()) .setProgressMessage("LLVMProfDataAction: Generating " + profileArtifact.prettyPrint()) .setMnemonic("LLVMProfDataAction") .build(ruleContext)); return profileArtifact; } @Override public ConfiguredTarget create(RuleContext ruleContext) throws RuleErrorException, InterruptedException { TransitiveInfoCollection lipoContextCollector = ruleContext.getPrerequisite(":lipo_context_collector", Mode.DONT_CHECK); if (lipoContextCollector != null && lipoContextCollector.getProvider(LipoContextProvider.class) == null) { ruleContext.ruleError("--lipo_context must point to a cc_binary or a cc_test rule"); return null; } CppConfiguration cppConfiguration = Preconditions.checkNotNull(ruleContext.getFragment(CppConfiguration.class)); Path fdoZip = ruleContext.getConfiguration().getCompilationMode() == CompilationMode.OPT ? cppConfiguration.getFdoZip() : null; SkyKey fdoKey = FdoSupportValue.key( cppConfiguration.getLipoMode(), fdoZip, cppConfiguration.getFdoInstrument()); 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; } // This tries to convert LLVM profiles to the indexed format if necessary. Artifact profileArtifact = null; if (cppConfiguration.isLLVMOptimizedFdo()) { profileArtifact = convertLLVMRawProfileToIndexed(fdoZip, cppConfiguration, ruleContext); if (ruleContext.hasErrors()) { return null; } } 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 = inputsForLibc(ruleContext); String purposePrefix = Actions.escapeLabel(label) + "_"; String runtimeSolibDirBase = "_solib_" + "_" + Actions.escapeLabel(label); final PathFragment runtimeSolibDir = ruleContext.getConfiguration() .getBinFragment().getRelative(runtimeSolibDirBase); // 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()); NestedSet<Artifact> dynamicRuntimeLinkSymlinks; List<Artifact> dynamicRuntimeLinkInputs = new ArrayList<>(); Artifact dynamicRuntimeLinkMiddleman; if (cppConfiguration.supportsEmbeddedRuntimes()) { NestedSetBuilder<Artifact> dynamicRuntimeLinkSymlinksBuilder = NestedSetBuilder.stableOrder(); for (Artifact artifact : dynamicRuntimeLibDep .getProvider(FileProvider.class).getFilesToBuild()) { if (CppHelper.SHARED_LIBRARY_FILETYPES.matches(artifact.getFilename())) { dynamicRuntimeLinkInputs.add(artifact); dynamicRuntimeLinkSymlinksBuilder.add(SolibSymlinkAction.getCppRuntimeSymlink( ruleContext, artifact, runtimeSolibDirBase, ruleContext.getConfiguration())); } } dynamicRuntimeLinkSymlinks = dynamicRuntimeLinkSymlinksBuilder.build(); } else { dynamicRuntimeLinkSymlinks = NestedSetBuilder.emptySet(Order.STABLE_ORDER); } if (!dynamicRuntimeLinkInputs.isEmpty()) { List<Artifact> dynamicRuntimeLinkMiddlemanSet = CppHelper.getAggregatingMiddlemanForCppRuntimes( ruleContext, purposePrefix + "dynamic_runtime_link", dynamicRuntimeLinkInputs, runtimeSolibDirBase, ruleContext.getConfiguration()); dynamicRuntimeLinkMiddleman = dynamicRuntimeLinkMiddlemanSet.isEmpty() ? null : Iterables.getOnlyElement(dynamicRuntimeLinkMiddlemanSet); } else { dynamicRuntimeLinkMiddleman = null; } Preconditions.checkState( (dynamicRuntimeLinkMiddleman == null) == dynamicRuntimeLinkSymlinks.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); NestedSetBuilder<Pair<String, String>> coverageEnvironment = NestedSetBuilder.compileOrder(); coverageEnvironment.add(Pair.of( "COVERAGE_GCOV_PATH", cppConfiguration.getGcovExecutable().getPathString())); if (cppConfiguration.getFdoInstrument() != null) { coverageEnvironment.add(Pair.of( "FDO_DIR", cppConfiguration.getFdoInstrument().getPathString())); } NestedSet<Artifact> coverage = getOptionalFiles(ruleContext, "coverage_files"); if (coverage.isEmpty()) { coverage = crosstool; } PathFragment sysroot = calculateSysroot(ruleContext); ImmutableList<PathFragment> builtInIncludeDirectories = null; try { builtInIncludeDirectories = cppConfiguration.getBuiltInIncludeDirectories(sysroot); } catch (InvalidConfigurationException e) { ruleContext.ruleError(e.getMessage()); } CcToolchainProvider ccProvider = new CcToolchainProvider( cppConfiguration, crosstool, fullInputsForCrosstool(ruleContext, crosstoolMiddleman), compile, strip, objcopy, fullInputsForLink(ruleContext, link), ruleContext.getPrerequisiteArtifact("$interface_library_builder", Mode.HOST), dwp, coverage, libcLink, staticRuntimeLinkInputs, staticRuntimeLinkMiddleman, dynamicRuntimeLinkSymlinks, dynamicRuntimeLinkMiddleman, runtimeSolibDir, context, supportsParamFiles, supportsHeaderParsing, getBuildVariables(ruleContext), getBuiltinIncludes(ruleContext), coverageEnvironment.build(), ruleContext.getPrerequisiteArtifact("$link_dynamic_library_tool", Mode.HOST), getEnvironment(ruleContext), builtInIncludeDirectories, sysroot); MakeVariableProvider makeVariableProvider = createMakeVariableProvider(cppConfiguration, sysroot); RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext) .addProvider(ccProvider) .addNativeDeclaredProvider(ccProvider) .addProvider(makeVariableProvider) .addNativeDeclaredProvider(makeVariableProvider) .addProvider( fdoSupport.getFdoSupport().createFdoSupportProvider(ruleContext, profileArtifact)) .setFilesToBuild(new NestedSetBuilder<Artifact>(Order.STABLE_ORDER).build()) .addProvider(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. // TODO(elenairina): Remove this and use Attribute.Builder.useOutputLicenses() on the // :cc_toolchain attribute instead. final License outputLicense = ruleContext.getRule().getToolOutputLicense(ruleContext.attributes()); if (outputLicense != null && !outputLicense.equals(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; } @Override public TargetLicense getOutputLicenses() { return new TargetLicense(label, outputLicense); } @Override public boolean hasOutputLicenses() { return true; } }; builder.add(LicensesProvider.class, licensesProvider); } return builder.build(); } private ImmutableList<Artifact> getBuiltinIncludes(RuleContext ruleContext) { ImmutableList.Builder<Artifact> result = ImmutableList.builder(); for (Artifact artifact : inputsForLibc(ruleContext)) { if (artifact.getExecPath().endsWith(BUILTIN_INCLUDE_FILE_SUFFIX)) { result.add(artifact); } } return result.build(); } private NestedSet<Artifact> inputsForLibc(RuleContext ruleContext) { TransitiveInfoCollection libc = ruleContext.getPrerequisite(":libc_top", Mode.TARGET); return libc != null ? libc.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) .addTransitive(AnalysisUtils.getMiddlemanFor(ruleContext, ":libc_top", Mode.TARGET)) .build(); } /** * Returns the crosstool-derived link action inputs for a given rule. Adds the given set of * artifacts as extra inputs. */ protected NestedSet<Artifact> fullInputsForLink( RuleContext ruleContext, NestedSet<Artifact> link) { return NestedSetBuilder.<Artifact>stableOrder() .addTransitive(link) .addTransitive(AnalysisUtils.getMiddlemanFor(ruleContext, ":libc_top", Mode.TARGET)) .add(ruleContext.getPrerequisiteArtifact("$interface_library_builder", Mode.HOST)) .add(ruleContext.getPrerequisiteArtifact("$link_dynamic_library_tool", Mode.HOST)) .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(); } private NestedSet<Artifact> getOptionalFiles(RuleContext context, String attribute) { TransitiveInfoCollection dep = context.getPrerequisite(attribute, Mode.HOST); return dep != null ? getFiles(context, attribute) : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER); } private MakeVariableProvider createMakeVariableProvider( CppConfiguration cppConfiguration, PathFragment sysroot) { HashMap<String, String> makeVariables = new HashMap<>(cppConfiguration.getAdditionalMakeVariables()); // Overwrite the CC_FLAGS variable to include sysroot, if it's available. if (sysroot != null) { String sysrootFlag = "--sysroot=" + sysroot; String ccFlags = makeVariables.get("CC_FLAGS"); ccFlags = ccFlags.isEmpty() ? sysrootFlag : ccFlags + " " + sysrootFlag; makeVariables.put("CC_FLAGS", ccFlags); } return new MakeVariableProvider(ImmutableMap.copyOf(makeVariables)); } /** * Returns a map that should be templated into the crosstool as build variables. Is meant to * be overridden by subclasses of CcToolchain. * * @param ruleContext the rule context * @throws RuleErrorException if there are configuration errors making it impossible to resolve * certain build variables of this toolchain */ protected Map<String, String> getBuildVariables(RuleContext ruleContext) throws RuleErrorException { return ImmutableMap.<String, String>of(); } /** * Returns a map of environment variables to be added to the compile actions created for this * toolchain. Ideally, this will get replaced by features, which also allow setting env variables. * * @param ruleContext the rule context */ protected ImmutableMap<String, String> getEnvironment(RuleContext ruleContext) { return ImmutableMap.<String, String>of(); } private PathFragment calculateSysroot(RuleContext ruleContext) { TransitiveInfoCollection sysrootTarget = ruleContext.getPrerequisite(":libc_top", Mode.TARGET); if (sysrootTarget == null) { CppConfiguration cppConfiguration = Preconditions.checkNotNull(ruleContext.getFragment(CppConfiguration.class)); return cppConfiguration.getDefaultSysroot(); } return sysrootTarget.getLabel().getPackageFragment(); } }