// 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 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.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.AnalysisEnvironment; 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.cmdline.Label; 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.packages.BuildType; import com.google.devtools.build.lib.rules.apple.Platform; import com.google.devtools.build.lib.rules.cpp.CcLibraryHelper.SourceCategory; 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.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.Type; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.List; 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"; /** * 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) { ActionAnalysisMetadata action = analysisEnvironment.getLocalGeneratingAction(artifact); if (action instanceof CppCompileAction) { addOutputs(metadataFilesBuilder, action, CppFileTypes.COVERAGE_NOTES); } } } }; /** Features we request to enable unless a rule explicitly doesn't support them. */ private static final ImmutableSet<String> DEFAULT_FEATURES = ImmutableSet.of( CppRuleClasses.DEPENDENCY_FILE, CppRuleClasses.COMPILE_ACTION_FLAGS_IN_FLAG_SET, CppRuleClasses.RANDOM_SEED, CppRuleClasses.MODULE_MAPS, CppRuleClasses.MODULE_MAP_HOME_CWD, CppRuleClasses.HEADER_MODULE_COMPILE, CppRuleClasses.INCLUDE_PATHS, CppRuleClasses.PIC, CppRuleClasses.PER_OBJECT_DEBUG_INFO, CppRuleClasses.PREPROCESSOR_DEFINES); /** C++ configuration */ private final CppConfiguration cppConfiguration; private final RuleContext ruleContext; private final CcToolchainProvider ccToolchain; private final FdoSupportProvider fdoSupport; public CcCommon(RuleContext ruleContext) { this.ruleContext = ruleContext; this.cppConfiguration = ruleContext.getFragment(CppConfiguration.class); this.ccToolchain = Preconditions.checkNotNull(CppHelper.getToolchain(ruleContext, ":cc_toolchain")); this.fdoSupport = Preconditions.checkNotNull(CppHelper.getFdoSupport(ruleContext, ":cc_toolchain")); } /** * 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() { Preconditions.checkState(hasAttribute("linkopts", Type.STRING_LIST)); 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); } } if (Platform.isApplePlatform(cppConfiguration.getTargetCpu()) && result.contains("-static")) { ruleContext.attributeError( "linkopts", "Apple builds do not support statically linked binaries"); } return ImmutableList.copyOf(result); } public ImmutableList<String> getCopts() { Preconditions.checkState(hasAttribute("copts", Type.STRING_LIST)); // 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 boolean hasAttribute(String name, Type<?> type) { return ruleContext.attributes().has(name, type); } /** Collects all .dwo artifacts in this target's transitive closure. */ public static DwoArtifactsCollector collectTransitiveDwoArtifacts( RuleContext ruleContext, CcCompilationOutputs compilationOutputs, boolean generateDwo, boolean ltoBackendArtifactsUsePic, Iterable<LTOBackendArtifacts> ltoBackendArtifacts) { ImmutableList.Builder<TransitiveInfoCollection> deps = ImmutableList.<TransitiveInfoCollection>builder(); deps.addAll(ruleContext.getPrerequisites("deps", Mode.TARGET)); if (ruleContext.attributes().has("malloc", BuildType.LABEL)) { deps.add(CppHelper.mallocForTarget(ruleContext)); } return compilationOutputs == null // Possible in LIPO collection mode (see initializationHook). ? DwoArtifactsCollector.emptyCollector() : DwoArtifactsCollector.transitiveCollector( ruleContext, compilationOutputs, deps.build(), generateDwo, ltoBackendArtifactsUsePic, ltoBackendArtifacts); } public TransitiveLipoInfoProvider collectTransitiveLipoLabels(CcCompilationOutputs outputs) { if (fdoSupport.getFdoSupport().getFdoRoot() == null || !cppConfiguration.isLipoContextCollector()) { return TransitiveLipoInfoProvider.EMPTY; } NestedSetBuilder<IncludeScannable> scannableBuilder = NestedSetBuilder.stableOrder(); CppHelper.addTransitiveLipoInfoForCommonAttributes(ruleContext, outputs, scannableBuilder); return new TransitiveLipoInfoProvider(scannableBuilder.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). */ List<Pair<Artifact, Label>> getSources() { Map<Artifact, Label> map = Maps.newLinkedHashMap(); Iterable<? extends TransitiveInfoCollection> providers = ruleContext.getPrerequisitesIf("srcs", Mode.TARGET, FileProvider.class); for (TransitiveInfoCollection provider : providers) { for (Artifact artifact : provider.getProvider(FileProvider.class).getFilesToBuild()) { // TODO(bazel-team): We currently do not produce an error for duplicate headers and other // non-source artifacts 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. Label oldLabel = map.put(artifact, provider.getLabel()); boolean isHeader = CppFileTypes.CPP_HEADER.matches(artifact.getExecPath()); if (!isHeader && SourceCategory.CC_AND_OBJC.getSourceTypes().matches(artifact.getExecPathString()) && 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(); } /** * 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<Pair<Artifact, Label>> getHeaders(RuleContext ruleContext) { Map<Artifact, Label> map = Maps.newLinkedHashMap(); 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())) { ruleContext.attributeWarning("hdrs", "file '" + artifact.getFilename() + "' from target '" + target.getLabel() + "' is not allowed in hdrs"); continue; } Label oldLabel = map.put(artifact, target.getLabel()); if (oldLabel != null && !oldLabel.equals(target.getLabel())) { ruleContext.attributeWarning( "hdrs", String.format( "Artifact '%s' is duplicated (through '%s' and '%s')", artifact.getExecPathString(), oldLabel, target.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(); } /** * Returns the C++ toolchain provider. */ public CcToolchainProvider getToolchain() { return ccToolchain; } /** * Returns the C++ FDO optimization support provider. */ public FdoSupportProvider getFdoSupport() { return fdoSupport; } /** * 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 List<Pair<Artifact, Label>> getHeaders() { return getHeaders(ruleContext); } 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; } /** * 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().getPackageIdentifier() .getPathUnderExecRoot(); result.add(rulePackage); // Gather up all the dirs from the rule's srcs as well as any of the srcs outputs. if (hasAttribute("srcs", BuildType.LABEL_LIST)) { for (TransitiveInfoCollection src : ruleContext.getPrerequisitesIf("srcs", Mode.TARGET, FileProvider.class)) { PathFragment packageDir = src.getLabel().getPackageIdentifier().getPathUnderExecRoot(); for (Artifact a : src.getProvider(FileProvider.class).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().getPackageIdentifier() .getPathUnderExecRoot(); // For now, anything with an 'includes' needs a blanket declaration result.add(packageFragment.getRelative("**")); } return result; } List<PathFragment> getSystemIncludeDirs() { List<PathFragment> result = new ArrayList<>(); PackageIdentifier packageIdentifier = ruleContext.getLabel().getPackageIdentifier(); PathFragment packageFragment = packageIdentifier.getPathUnderExecRoot(); 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."); } if (includesPath.segmentCount() == 0) { ruleContext.attributeError( "includes", "'" + includesAttr + "' resolves to the workspace root, which would allow this rule and all of its " + "transitive dependents to include any file in your workspace. Please include only" + " what you need"); } else if (!includesPath.startsWith(packageFragment)) { ruleContext.attributeWarning( "includes", "'" + includesAttr + "' resolves to '" + includesPath + "' not below the relative path of its package '" + packageFragment + "'. This will be an error in the future"); } result.add(includesPath); result.add(ruleContext.getConfiguration().getGenfilesFragment().getRelative(includesPath)); } return result; } /** * Collects compilation prerequisite artifacts. */ static NestedSet<Artifact> collectCompilationPrerequisites( RuleContext ruleContext, CppCompilationContext context) { // TODO(bazel-team): Use context.getCompilationPrerequisites() instead; note that this will // need cleaning up the prerequisites, as the compilation context currently collects them // transitively (to get transitive headers), but source files are not transitive compilation // prerequisites. NestedSetBuilder<Artifact> prerequisites = NestedSetBuilder.stableOrder(); if (ruleContext.attributes().has("srcs", BuildType.LABEL_LIST)) { for (FileProvider provider : ruleContext.getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) { prerequisites.addAll( FileType.filter( provider.getFilesToBuild(), SourceCategory.CC_AND_OBJC.getSourceTypes())); } } prerequisites.addTransitive(context.getDeclaredIncludeSrcs()); prerequisites.addTransitive(context.getAdditionalInputs()); prerequisites.addTransitive(context.getTransitiveModules(true)); prerequisites.addTransitive(context.getTransitiveModules(false)); return 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 Artifact 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); } /** * Provides support for instrumentation. */ public InstrumentedFilesProvider getInstrumentedFilesProvider(Iterable<Artifact> files, boolean withBaselineCoverage) { return cppConfiguration.isLipoContextCollector() ? InstrumentedFilesProviderImpl.EMPTY : InstrumentedFilesCollector.collect( ruleContext, CppRuleClasses.INSTRUMENTATION_SPEC, CC_METADATA_COLLECTOR, files, CppHelper.getGcovFilesIfNeeded(ruleContext, ccToolchain), CppHelper.getCoverageEnvironmentIfNeeded(ruleContext, ccToolchain), withBaselineCoverage); } private static String getHostOrNonHostFeature(RuleContext ruleContext) { if (ruleContext.getConfiguration().isHostConfiguration()) { return "host"; } else { return "nonhost"; } } /** * Creates the feature configuration for a given rule. * * @param ruleContext the context of the rule we want the feature configuration for. * @param ruleSpecificRequestedFeatures features that will be requested, and thus be always * enabled if the toolchain supports them. * @param ruleSpecificUnsupportedFeatures features that are not supported in the current context. * @param sourceCategory the source category for this build. * @return the feature configuration for the given {@code ruleContext}. */ public static FeatureConfiguration configureFeatures( RuleContext ruleContext, Set<String> ruleSpecificRequestedFeatures, Set<String> ruleSpecificUnsupportedFeatures, SourceCategory sourceCategory, CcToolchainProvider toolchain) { ImmutableSet.Builder<String> unsupportedFeaturesBuilder = ImmutableSet.builder(); unsupportedFeaturesBuilder.addAll(ruleSpecificUnsupportedFeatures); if (!toolchain.supportsHeaderParsing()) { // TODO(bazel-team): Remove once supports_header_parsing has been removed from the // cc_toolchain rule. unsupportedFeaturesBuilder.add(CppRuleClasses.PARSE_HEADERS); unsupportedFeaturesBuilder.add(CppRuleClasses.PREPROCESS_HEADERS); } if (toolchain.getCppCompilationContext().getCppModuleMap() == null) { unsupportedFeaturesBuilder.add(CppRuleClasses.MODULE_MAPS); } Set<String> unsupportedFeatures = unsupportedFeaturesBuilder.build(); ImmutableSet.Builder<String> requestedFeatures = ImmutableSet.builder(); for (String feature : Iterables.concat( ImmutableSet.of( toolchain.getCompilationMode().toString(), getHostOrNonHostFeature(ruleContext)), DEFAULT_FEATURES, toolchain.getFeatures().getDefaultFeatures(), ruleContext.getFeatures())) { if (!unsupportedFeatures.contains(feature)) { requestedFeatures.add(feature); } } requestedFeatures.addAll(ruleSpecificRequestedFeatures); requestedFeatures.addAll(sourceCategory.getActionConfigSet()); FeatureConfiguration configuration = toolchain.getFeatures().getFeatureConfiguration(requestedFeatures.build()); for (String feature : unsupportedFeatures) { if (configuration.isEnabled(feature)) { ruleContext.ruleError("The C++ toolchain '" + ruleContext.getPrerequisite(":cc_toolchain", Mode.TARGET).getLabel() + "' unconditionally implies feature '" + feature + "', which is unsupported by this rule. " + "This is most likely a misconfiguration in the C++ toolchain."); } } return configuration; } /** * Creates a feature configuration for a given rule. * * @param ruleContext the context of the rule we want the feature configuration for. * @param toolchain the toolchain we want the feature configuration for. * @param sourceCategory the category of sources to be used in this build. * @return the feature configuration for the given {@code ruleContext}. */ public static FeatureConfiguration configureFeatures( RuleContext ruleContext, CcToolchainProvider toolchain, SourceCategory sourceCategory) { return configureFeatures( ruleContext, ImmutableSet.<String>of(), ImmutableSet.<String>of(), sourceCategory, toolchain); } /** * Creates a feature configuration for a given rule. Assumes strictly cc sources. * * @param ruleContext the context of the rule we want the feature configuration for. * @param toolchain C++ toolchain provider. * @return the feature configuration for the given {@code ruleContext}. */ public static FeatureConfiguration configureFeatures( RuleContext ruleContext, CcToolchainProvider toolchain) { return configureFeatures(ruleContext, toolchain, SourceCategory.CC); } }