diff options
author | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
---|---|---|
committer | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
commit | d08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch) | |
tree | 5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java |
Update from Google.
--
MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java | 529 |
1 files changed, 529 insertions, 0 deletions
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()); + } +} |