| // 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.annotations.VisibleForTesting; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.AbstractAction; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
| import com.google.devtools.build.lib.actions.ArtifactResolver; |
| import com.google.devtools.build.lib.actions.CommandAction; |
| import com.google.devtools.build.lib.actions.ExecException; |
| import com.google.devtools.build.lib.actions.Executor; |
| import com.google.devtools.build.lib.actions.PackageRootResolutionException; |
| import com.google.devtools.build.lib.actions.PackageRootResolver; |
| import com.google.devtools.build.lib.actions.ResourceSet; |
| import com.google.devtools.build.lib.actions.extra.CppCompileInfo; |
| import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.actions.ExecutionInfoSpecifier; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.config.PerLabelOptions; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.collect.CollectionUtils; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.profiler.Profiler; |
| import com.google.devtools.build.lib.profiler.ProfilerTask; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; |
| import com.google.devtools.build.lib.rules.cpp.CppCompileActionContext.Reply; |
| import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool; |
| import com.google.devtools.build.lib.util.DependencySet; |
| import com.google.devtools.build.lib.util.FileType; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.util.OS; |
| import com.google.devtools.build.lib.util.Pair; |
| import com.google.devtools.build.lib.util.Preconditions; |
| import com.google.devtools.build.lib.util.ShellEscaper; |
| 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 java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import javax.annotation.Nullable; |
| import javax.annotation.concurrent.GuardedBy; |
| |
| /** Action that represents some kind of C++ compilation step. */ |
| @ThreadCompatible |
| public class CppCompileAction extends AbstractAction |
| implements IncludeScannable, ExecutionInfoSpecifier, CommandAction { |
| /** |
| * Represents logic that determines if an artifact is a special input, meaning that it may require |
| * additional inputs when it is compiled or may not be available to other actions. |
| */ |
| public interface SpecialInputsHandler { |
| /** Returns if {@code includedFile} is special, so may not be available to other actions. */ |
| boolean isSpecialFile(Artifact includedFile); |
| |
| /** Returns the set of files to be added for an included file (as returned in the .d file). */ |
| Collection<Artifact> getInputsForIncludedFile( |
| Artifact includedFile, ArtifactResolver artifactResolver); |
| } |
| |
| static final SpecialInputsHandler VOID_SPECIAL_INPUTS_HANDLER = |
| new SpecialInputsHandler() { |
| @Override |
| public boolean isSpecialFile(Artifact includedFile) { |
| return false; |
| } |
| |
| @Override |
| public Collection<Artifact> getInputsForIncludedFile( |
| Artifact includedFile, ArtifactResolver artifactResolver) { |
| return ImmutableList.of(); |
| } |
| }; |
| |
| private static final int VALIDATION_DEBUG = 0; // 0==none, 1==warns/errors, 2==all |
| private static final boolean VALIDATION_DEBUG_WARN = VALIDATION_DEBUG >= 1; |
| |
| /** |
| * A string constant for the c compilation action. |
| */ |
| public static final String C_COMPILE = "c-compile"; |
| |
| /** |
| * A string constant for the c++ compilation action. |
| */ |
| public static final String CPP_COMPILE = "c++-compile"; |
| |
| /** |
| * A string constant for the objc compilation action. |
| */ |
| public static final String OBJC_COMPILE = "objc-compile"; |
| |
| /** |
| * A string constant for the objc++ compile action. |
| */ |
| public static final String OBJCPP_COMPILE = "objc++-compile"; |
| |
| /** |
| * A string constant for the c++ header parsing. |
| */ |
| public static final String CPP_HEADER_PARSING = "c++-header-parsing"; |
| |
| /** |
| * A string constant for the c++ header preprocessing. |
| */ |
| public static final String CPP_HEADER_PREPROCESSING = "c++-header-preprocessing"; |
| |
| /** |
| * A string constant for the c++ module compilation action. |
| * Note: currently we don't support C module compilation. |
| */ |
| public static final String CPP_MODULE_COMPILE = "c++-module-compile"; |
| |
| /** |
| * A string constant for the assembler actions. |
| */ |
| public static final String ASSEMBLE = "assemble"; |
| public static final String PREPROCESS_ASSEMBLE = "preprocess-assemble"; |
| |
| /** |
| * A string constant for the clif actions. Bazel enables different features of the toolchain based |
| * on the name of the action. This name enables the clif_matcher feature, which switches the |
| * "compiler" to the clif_matcher and adds some additional arguments as described in the CROSSTOOL |
| * file. |
| */ |
| public static final String CLIF_MATCH = "clif-match"; |
| |
| // TODO(ulfjack): this is only used to get the local shell environment and to check if coverage is |
| // enabled. Move those two things to local fields and drop this. Accessing anything other than |
| // these fields can impact correctness! |
| private final BuildConfiguration configuration; |
| protected final Artifact outputFile; |
| private final Label sourceLabel; |
| private final Artifact optionalSourceFile; |
| private final NestedSet<Artifact> mandatoryInputs; |
| private final boolean shouldScanIncludes; |
| private final boolean shouldPruneModules; |
| private final boolean usePic; |
| private final CppCompilationContext context; |
| private final Iterable<IncludeScannable> lipoScannables; |
| private final ImmutableList<Artifact> builtinIncludeFiles; |
| // A list of files to include scan that are not source files, pcm files, lipo scannables, or |
| // included via a command-line "-include file.h". Actions that use non C++ files as source |
| // files--such as Clif--may use this mechanism. |
| private final ImmutableList<Artifact> additionalIncludeScannables; |
| @VisibleForTesting public final CppCompileCommandLine cppCompileCommandLine; |
| private final ImmutableSet<String> executionRequirements; |
| private final ImmutableMap<String, String> environment; |
| |
| @VisibleForTesting final CppConfiguration cppConfiguration; |
| private final FeatureConfiguration featureConfiguration; |
| protected final Class<? extends CppCompileActionContext> actionContext; |
| private final SpecialInputsHandler specialInputsHandler; |
| private final CppSemantics semantics; |
| |
| /** |
| * Identifier for the actual execution time behavior of the action. |
| * |
| * <p>Required because the behavior of this class can be modified by injecting code in the |
| * constructor or by inheritance, and we want to have different cache keys for those. |
| */ |
| private final UUID actionClassId; |
| |
| // This can be read/written from multiple threads, and so accesses should be synchronized. |
| @GuardedBy("this") |
| private boolean inputsKnown = false; |
| |
| /** |
| * Set when the action prepares for execution. Used to preserve state between preparation and |
| * execution. |
| */ |
| private Collection<Artifact> additionalInputs = null; |
| |
| private CcToolchainFeatures.Variables overwrittenVariables = null; |
| |
| private ImmutableList<Artifact> resolvedInputs = ImmutableList.<Artifact>of(); |
| |
| /** |
| * Creates a new action to compile C/C++ source files. |
| * |
| * @param owner the owner of the action, usually the configured target that emitted it |
| * @param features TODO(bazel-team): Add parameter description. |
| * @param featureConfiguration TODO(bazel-team): Add parameter description. |
| * @param variables TODO(bazel-team): Add parameter description. |
| * @param sourceFile the source file that should be compiled. {@code mandatoryInputs} must contain |
| * this file |
| * @param shouldScanIncludes a boolean indicating whether scanning of {@code sourceFile} is to be |
| * performed looking for inclusions. |
| * @param usePic TODO(bazel-team): Add parameter description. |
| * @param sourceLabel the label of the rule the source file is generated by |
| * @param mandatoryInputs any additional files that need to be present for the compilation to |
| * succeed, can be empty but not null, for example, extra sources for FDO. |
| * @param outputFile the object file that is written as result of the compilation, or the fake |
| * object for {@link FakeCppCompileAction}s |
| * @param dotdFile the .d file that is generated as a side-effect of compilation |
| * @param gcnoFile the coverage notes that are written in coverage mode, can be null |
| * @param dwoFile the .dwo output file where debug information is stored for Fission builds (null |
| * if Fission mode is disabled) |
| * @param optionalSourceFile an additional optional source file (null if unneeded) |
| * @param configuration the build configurations |
| * @param cppConfiguration TODO(bazel-team): Add parameter description. |
| * @param context the compilation context |
| * @param actionContext TODO(bazel-team): Add parameter description. |
| * @param copts options for the compiler |
| * @param coptsFilter regular expression to remove options from {@code copts} |
| * @param specialInputsHandler TODO(bazel-team): Add parameter description. |
| * @param lipoScannables List of artifacts to include-scan when this action is a lipo action |
| * @param additionalIncludeScannables list of additional artifacts to include-scan |
| * @param actionClassId TODO(bazel-team): Add parameter description |
| * @param executionRequirements out-of-band hints to be passed to the execution backend to signal |
| * platform requirements |
| * @param environment TODO(bazel-team): Add parameter description |
| * @param actionName a string giving the name of this action for the purpose of toolchain |
| * evaluation |
| * @param ruleContext The rule-context that produced this action |
| */ |
| protected CppCompileAction( |
| ActionOwner owner, |
| // TODO(bazel-team): Eventually we will remove 'features'; all functionality in 'features' |
| // will be provided by 'featureConfiguration'. |
| ImmutableList<String> features, |
| FeatureConfiguration featureConfiguration, |
| CcToolchainFeatures.Variables variables, |
| Artifact sourceFile, |
| boolean shouldScanIncludes, |
| boolean shouldPruneModules, |
| boolean usePic, |
| Label sourceLabel, |
| NestedSet<Artifact> mandatoryInputs, |
| Artifact outputFile, |
| DotdFile dotdFile, |
| @Nullable Artifact gcnoFile, |
| @Nullable Artifact dwoFile, |
| Artifact optionalSourceFile, |
| BuildConfiguration configuration, |
| CppConfiguration cppConfiguration, |
| CppCompilationContext context, |
| Class<? extends CppCompileActionContext> actionContext, |
| ImmutableList<String> copts, |
| Predicate<String> coptsFilter, |
| SpecialInputsHandler specialInputsHandler, |
| Iterable<IncludeScannable> lipoScannables, |
| ImmutableList<Artifact> additionalIncludeScannables, |
| UUID actionClassId, |
| ImmutableSet<String> executionRequirements, |
| ImmutableMap<String, String> environment, |
| String actionName, |
| RuleContext ruleContext, |
| CppSemantics semantics) { |
| super( |
| owner, |
| createInputs( |
| ruleContext, |
| mandatoryInputs, |
| context.getTransitiveCompilationPrerequisites(), |
| optionalSourceFile, |
| lipoScannables), |
| CollectionUtils.asListWithoutNulls( |
| outputFile, (dotdFile == null ? null : dotdFile.artifact()), gcnoFile, dwoFile)); |
| this.configuration = configuration; |
| this.sourceLabel = sourceLabel; |
| this.outputFile = Preconditions.checkNotNull(outputFile); |
| this.optionalSourceFile = optionalSourceFile; |
| this.context = context; |
| this.specialInputsHandler = specialInputsHandler; |
| this.cppConfiguration = cppConfiguration; |
| this.featureConfiguration = featureConfiguration; |
| // inputsKnown begins as the logical negation of shouldScanIncludes. |
| // When scanning includes, the inputs begin as not known, and become |
| // known after inclusion scanning. When *not* scanning includes, |
| // the inputs are as declared, hence known, and remain so. |
| this.shouldScanIncludes = shouldScanIncludes; |
| this.shouldPruneModules = shouldPruneModules; |
| this.usePic = usePic; |
| this.inputsKnown = !shouldScanIncludes; |
| this.cppCompileCommandLine = |
| new CppCompileCommandLine( |
| sourceFile, dotdFile, copts, coptsFilter, features, variables, actionName); |
| this.actionContext = actionContext; |
| this.lipoScannables = lipoScannables; |
| this.actionClassId = actionClassId; |
| this.executionRequirements = executionRequirements; |
| this.environment = environment; |
| |
| // We do not need to include the middleman artifact since it is a generated |
| // artifact and will definitely exist prior to this action execution. |
| this.mandatoryInputs = mandatoryInputs; |
| this.builtinIncludeFiles = CppHelper.getToolchain(ruleContext).getBuiltinIncludeFiles(); |
| this.semantics = semantics; |
| if (semantics.needsIncludeValidation()) { |
| verifyIncludePaths(ruleContext); |
| } |
| this.additionalIncludeScannables = ImmutableList.copyOf(additionalIncludeScannables); |
| } |
| |
| /** |
| * Verifies that the include paths of this action are within the limits of the execution root. |
| */ |
| private void verifyIncludePaths(RuleContext ruleContext) { |
| if (ruleContext == null) { |
| return; |
| } |
| |
| Iterable<PathFragment> ignoredDirs = getValidationIgnoredDirs(); |
| |
| // We currently do not check the output of: |
| // - getQuoteIncludeDirs(): those only come from includes attributes, and are checked in |
| // CcCommon.getIncludeDirsFromIncludesAttribute(). |
| // - getBuiltinIncludeDirs(): while in practice this doesn't happen, bazel can be configured |
| // to use an absolute system root, in which case the builtin include dirs might be absolute. |
| for (PathFragment include : Iterables.concat(getIncludeDirs(), getSystemIncludeDirs())) { |
| |
| // Ignore headers from built-in include directories. |
| if (FileSystemUtils.startsWithAny(include, ignoredDirs)) { |
| continue; |
| } |
| |
| if (include.isAbsolute() |
| || !PathFragment.EMPTY_FRAGMENT.getRelative(include).normalize().isNormalized()) { |
| ruleContext.ruleError( |
| "The include path '" + include + "' references a path outside of the execution root."); |
| } |
| } |
| } |
| |
| private static NestedSet<Artifact> createInputs( |
| RuleContext ruleContext, |
| NestedSet<Artifact> mandatoryInputs, |
| Set<Artifact> prerequisites, |
| Artifact optionalSourceFile, |
| Iterable<IncludeScannable> lipoScannables) { |
| NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder(); |
| if (optionalSourceFile != null) { |
| builder.add(optionalSourceFile); |
| } |
| builder.addAll(prerequisites); |
| builder.addAll(CppHelper.getToolchain(ruleContext).getBuiltinIncludeFiles()); |
| builder.addTransitive(mandatoryInputs); |
| if (lipoScannables != null && lipoScannables.iterator().hasNext()) { |
| // We need to add "legal generated scanner files" coming through LIPO scannables here. These |
| // usually contain pre-grepped source files, i.e. files just containing the #include lines |
| // extracted from generated files. With LIPO, some of these files can be accessed, even though |
| // there is no direct dependency on them. Adding the artifacts as inputs to this compile |
| // action ensures that the action generating them is actually executed. |
| for (IncludeScannable lipoScannable : lipoScannables) { |
| for (Artifact value : lipoScannable.getLegalGeneratedScannerFileMap().values()) { |
| if (value != null) { |
| builder.add(value); |
| } |
| } |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Whether we should do "include scanning". Note that this does *not* mean whether we should parse |
| * the .d files to determine which include files were used during compilation. Instead, this means |
| * whether we should a) run the pre-execution include scanner (see {@code IncludeScanningContext}) |
| * if one exists and b) whether the action inputs should be modified to match the results of that |
| * pre-execution scanning and (if enabled) again after execution to match the results of the .d |
| * file parsing. |
| * |
| * <p>This does *not* have anything to do with "hdrs_check". |
| */ |
| public boolean shouldScanIncludes() { |
| return shouldScanIncludes; |
| } |
| |
| @Override |
| public List<PathFragment> getBuiltInIncludeDirectories() { |
| return cppConfiguration.getBuiltInIncludeDirectories(); |
| } |
| |
| @Nullable |
| @Override |
| public List<Artifact> getBuiltInIncludeFiles() { |
| return builtinIncludeFiles; |
| } |
| |
| public List<Artifact> getadditionalIncludeScannables() { |
| return additionalIncludeScannables; |
| } |
| |
| public String getHostSystemName() { |
| return cppConfiguration.getHostSystemName(); |
| } |
| |
| @Override |
| public NestedSet<Artifact> getMandatoryInputs() { |
| return mandatoryInputs; |
| } |
| |
| @Override |
| public synchronized boolean inputsKnown() { |
| return inputsKnown; |
| } |
| |
| /** |
| * Returns the list of additional inputs found by dependency discovery, during action preparation, |
| * and clears the stored list. {@link #prepare} must be called before this method is called, on |
| * each action execution. |
| */ |
| public Collection<? extends ActionInput> getAdditionalInputs() { |
| Collection<? extends ActionInput> result = Preconditions.checkNotNull(additionalInputs); |
| additionalInputs = null; |
| return result; |
| } |
| |
| @VisibleForTesting |
| public void setResolvedInputsForTesting(ImmutableList<Artifact> resolvedInputs) { |
| this.resolvedInputs = resolvedInputs; |
| } |
| |
| @Override |
| public boolean discoversInputs() { |
| return true; |
| } |
| |
| @Nullable |
| @Override |
| public Iterable<Artifact> discoverInputs(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException, InterruptedException { |
| Executor executor = actionExecutionContext.getExecutor(); |
| Collection<Artifact> initialResult; |
| if (!shouldScanIncludes) { |
| return null; |
| } |
| try { |
| initialResult = executor.getContext(actionContext) |
| .findAdditionalInputs(this, actionExecutionContext); |
| } catch (ExecException e) { |
| throw e.toActionExecutionException("Include scanning of rule '" + getOwner().getLabel() + "'", |
| executor.getVerboseFailures(), this); |
| } |
| if (initialResult == null) { |
| // We will find inputs during execution. Store an empty list to show we did try to discover |
| // inputs and return null to inform the caller that inputs will be discovered later. |
| this.additionalInputs = ImmutableList.of(); |
| return null; |
| } |
| |
| if (shouldPruneModules) { |
| Set<Artifact> initialResultSet = Sets.newLinkedHashSet(initialResult); |
| List<String> usedModulePaths = Lists.newArrayList(); |
| for (Artifact usedModule : context.getUsedModules(usePic, initialResultSet)) { |
| initialResultSet.add(usedModule); |
| usedModulePaths.add(usedModule.getExecPathString()); |
| } |
| CcToolchainFeatures.Variables.Builder variableBuilder = |
| new CcToolchainFeatures.Variables.Builder(); |
| variableBuilder.addSequenceVariable("module_files", usedModulePaths); |
| this.overwrittenVariables = variableBuilder.build(); |
| initialResult = initialResultSet; |
| } |
| |
| this.additionalInputs = initialResult; |
| // In some cases, execution backends need extra files for each included file. Add them |
| // to the set of inputs the caller may need to be aware of. |
| Collection<Artifact> result = new HashSet<>(); |
| ArtifactResolver artifactResolver = |
| executor.getContext(IncludeScanningContext.class).getArtifactResolver(); |
| for (Artifact artifact : initialResult) { |
| result.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver)); |
| } |
| for (Artifact artifact : getInputs()) { |
| result.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver)); |
| } |
| // TODO(ulfjack): This only works if include scanning is enabled; the cleanup is in progress, |
| // and this needs to be fixed before we can even consider disabling it. |
| resolvedInputs = ImmutableList.copyOf(result); |
| if (result.isEmpty()) { |
| result = initialResult; |
| } else { |
| result.addAll(initialResult); |
| } |
| return result; |
| } |
| |
| @Override |
| public Artifact getPrimaryInput() { |
| return getSourceFile(); |
| } |
| |
| @Override |
| public Artifact getPrimaryOutput() { |
| return getOutputFile(); |
| } |
| |
| /** |
| * Returns the path of the c/cc source for gcc. |
| */ |
| public final Artifact getSourceFile() { |
| return cppCompileCommandLine.sourceFile; |
| } |
| |
| /** |
| * Returns the path where gcc should put its result. |
| */ |
| public Artifact getOutputFile() { |
| return outputFile; |
| } |
| |
| protected PathFragment getInternalOutputFile() { |
| return outputFile.getExecPath(); |
| } |
| |
| @Override |
| public Map<Artifact, Artifact> getLegalGeneratedScannerFileMap() { |
| Map<Artifact, Artifact> legalOuts = new HashMap<>(); |
| |
| for (Artifact a : context.getDeclaredIncludeSrcs()) { |
| if (!a.isSourceArtifact()) { |
| legalOuts.put(a, null); |
| } |
| } |
| for (Pair<Artifact, Artifact> pregreppedSrcs : context.getPregreppedHeaders()) { |
| Artifact hdr = pregreppedSrcs.getFirst(); |
| Preconditions.checkState(!hdr.isSourceArtifact(), hdr); |
| legalOuts.put(hdr, pregreppedSrcs.getSecond()); |
| } |
| return Collections.unmodifiableMap(legalOuts); |
| } |
| |
| /** |
| * Returns the path where gcc should put the discovered dependency |
| * information. |
| */ |
| public DotdFile getDotdFile() { |
| return cppCompileCommandLine.dotdFile; |
| } |
| |
| @VisibleForTesting |
| public CppCompilationContext getContext() { |
| return context; |
| } |
| |
| @Override |
| public List<PathFragment> getQuoteIncludeDirs() { |
| return context.getQuoteIncludeDirs(); |
| } |
| |
| @Override |
| public List<PathFragment> getIncludeDirs() { |
| ImmutableList.Builder<PathFragment> result = ImmutableList.builder(); |
| result.addAll(context.getIncludeDirs()); |
| for (String opt : cppCompileCommandLine.copts) { |
| if (opt.startsWith("-I") && opt.length() > 2) { |
| // We insist on the combined form "-Idir". |
| result.add(new PathFragment(opt.substring(2))); |
| } |
| } |
| return result.build(); |
| } |
| |
| @Override |
| public List<PathFragment> getSystemIncludeDirs() { |
| // TODO(bazel-team): parsing the command line flags here couples us to gcc-style compiler |
| // command lines; use a different way to specify system includes (for example through a |
| // system_includes attribute in cc_toolchain); note that that would disallow users from |
| // specifying system include paths via the copts attribute. |
| // Currently, this works together with the include_paths features because getCommandLine() will |
| // get the system include paths from the CppCompilationContext instead. |
| ImmutableList.Builder<PathFragment> result = ImmutableList.builder(); |
| List<String> compilerOptions = getCompilerOptions(); |
| for (int i = 0; i < compilerOptions.size(); i++) { |
| String opt = compilerOptions.get(i); |
| if (opt.startsWith("-isystem")) { |
| if (opt.length() > 8) { |
| result.add(new PathFragment(opt.substring(8).trim())); |
| } else if (i + 1 < compilerOptions.size()) { |
| i++; |
| result.add(new PathFragment(compilerOptions.get(i))); |
| } else { |
| System.err.println("WARNING: dangling -isystem flag in options for " + prettyPrint()); |
| } |
| } |
| } |
| return result.build(); |
| } |
| |
| @Override |
| public List<String> getCmdlineIncludes() { |
| ImmutableList.Builder<String> cmdlineIncludes = ImmutableList.builder(); |
| List<String> args = getArgv(); |
| for (Iterator<String> argi = args.iterator(); argi.hasNext();) { |
| String arg = argi.next(); |
| if (arg.equals("-include") && argi.hasNext()) { |
| cmdlineIncludes.add(argi.next()); |
| } |
| } |
| return cmdlineIncludes.build(); |
| } |
| |
| @Override |
| public Artifact getMainIncludeScannerSource() { |
| return CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath()) |
| ? Iterables.getFirst(context.getHeaderModuleSrcs(), null) |
| : getSourceFile(); |
| } |
| |
| @Override |
| public Collection<Artifact> getIncludeScannerSources() { |
| NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder(); |
| if (CppFileTypes.CPP_MODULE_MAP.matches(getSourceFile().getPath())) { |
| // If this is an action that compiles the header module itself, the source we build is the |
| // module map, and we need to include-scan all headers that are referenced in the module map. |
| // We need to do include scanning as long as we want to support building code bases that are |
| // not fully strict layering clean. |
| builder.addAll(context.getHeaderModuleSrcs()); |
| } else { |
| builder.add(getSourceFile()); |
| builder.addAll(additionalIncludeScannables); |
| } |
| return builder.build().toCollection(); |
| } |
| |
| @Override |
| public Iterable<IncludeScannable> getAuxiliaryScannables() { |
| return lipoScannables; |
| } |
| |
| /** |
| * Returns the list of "-D" arguments that should be used by this gcc |
| * invocation. Only used for testing. |
| */ |
| @VisibleForTesting |
| public ImmutableCollection<String> getDefines() { |
| return context.getDefines(); |
| } |
| |
| @Override |
| public ImmutableMap<String, String> getEnvironment() { |
| Map<String, String> environment = new LinkedHashMap<>(configuration.getLocalShellEnvironment()); |
| if (configuration.isCodeCoverageEnabled()) { |
| environment.put("PWD", "/proc/self/cwd"); |
| } |
| |
| environment.putAll(this.environment); |
| environment.putAll(cppCompileCommandLine.getEnvironment()); |
| |
| // TODO(bazel-team): Check (crosstool) host system name instead of using OS.getCurrent. |
| if (OS.getCurrent() == OS.WINDOWS) { |
| // TODO(bazel-team): Both GCC and clang rely on their execution directories being on |
| // PATH, otherwise they fail to find dependent DLLs (and they fail silently...). On |
| // the other hand, Windows documentation says that the directory of the executable |
| // is always searched for DLLs first. Not sure what to make of it. |
| // Other options are to forward the system path (brittle), or to add a PATH field to |
| // the crosstool file. |
| // |
| // @see com.google.devtools.build.lib.rules.cpp.CppLinkAction#getEnvironment |
| environment.put("PATH", cppConfiguration.getToolPathFragment(Tool.GCC).getParentDirectory() |
| .getPathString()); |
| } |
| return ImmutableMap.copyOf(environment); |
| } |
| |
| /** |
| * Returns a new, mutable list of command and arguments (argv) to be passed |
| * to the gcc subprocess. |
| */ |
| public final List<String> getArgv() { |
| return getArgv(getInternalOutputFile()); |
| } |
| |
| @Override |
| public List<String> getArguments() { |
| return getArgv(); |
| } |
| |
| protected final List<String> getArgv(PathFragment outputFile) { |
| return cppCompileCommandLine.getArgv(outputFile, overwrittenVariables); |
| } |
| |
| @Override |
| public boolean extraActionCanAttach() { |
| return cppConfiguration.alwaysAttachExtraActions() |
| || !specialInputsHandler.isSpecialFile(getPrimaryInput()); |
| } |
| |
| @Override |
| public ExtraActionInfo.Builder getExtraActionInfo() { |
| CppCompileInfo.Builder info = CppCompileInfo.newBuilder(); |
| info.setTool(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString()); |
| for (String option : getCompilerOptions()) { |
| info.addCompilerOption(option); |
| } |
| info.setOutputFile(outputFile.getExecPathString()); |
| info.setSourceFile(getSourceFile().getExecPathString()); |
| if (inputsKnown()) { |
| info.addAllSourcesAndHeaders(Artifact.toExecPaths(getInputs())); |
| } else { |
| info.addSourcesAndHeaders(getSourceFile().getExecPathString()); |
| info.addAllSourcesAndHeaders( |
| Artifact.toExecPaths(context.getDeclaredIncludeSrcs())); |
| } |
| |
| return super.getExtraActionInfo() |
| .setExtension(CppCompileInfo.cppCompileInfo, info.build()); |
| } |
| |
| /** |
| * Returns the compiler options. |
| */ |
| @VisibleForTesting |
| public List<String> getCompilerOptions() { |
| return cppCompileCommandLine.getCompilerOptions(/*updatedVariables=*/null); |
| } |
| |
| @Override |
| public Map<String, String> getExecutionInfo() { |
| ImmutableMap.Builder<String, String> result = ImmutableMap.<String, String>builder(); |
| for (String requirement : executionRequirements) { |
| result.put(requirement, ""); |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Enforce that the includes actually visited during the compile were properly |
| * declared in the rules. |
| * |
| * <p>The technique is to walk through all of the reported includes that gcc |
| * emits into the .d file, and verify that they came from acceptable |
| * relative include directories. This is done in two steps: |
| * |
| * <p>First, each included file is stripped of any include path prefix from |
| * {@code quoteIncludeDirs} to produce an effective relative include dir+name. |
| * |
| * <p>Second, the remaining directory is looked up in {@code declaredIncludeDirs}, |
| * a list of acceptable dirs. This list contains a set of dir fragments that |
| * have been calculated by the configured target to be allowable for inclusion |
| * by this source. If no match is found, an error is reported and an exception |
| * is thrown. |
| * |
| * @throws ActionExecutionException iff there was an undeclared dependency |
| */ |
| @VisibleForTesting |
| public void validateInclusions( |
| Iterable<Artifact> inputsForValidation, |
| ArtifactExpander artifactExpander, |
| EventHandler eventHandler) |
| throws ActionExecutionException { |
| IncludeProblems errors = new IncludeProblems(); |
| IncludeProblems warnings = new IncludeProblems(); |
| Set<Artifact> allowedIncludes = new HashSet<>(); |
| for (Artifact input : mandatoryInputs) { |
| if (input.isMiddlemanArtifact() || input.isTreeArtifact()) { |
| artifactExpander.expand(input, allowedIncludes); |
| } |
| allowedIncludes.add(input); |
| } |
| allowedIncludes.addAll(resolvedInputs); |
| |
| if (optionalSourceFile != null) { |
| allowedIncludes.add(optionalSourceFile); |
| } |
| Iterable<PathFragment> ignoreDirs = getValidationIgnoredDirs(); |
| |
| // Copy the sets to hash sets for fast contains checking. |
| // Avoid immutable sets here to limit memory churn. |
| Set<PathFragment> declaredIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeDirs()); |
| Set<PathFragment> warnIncludeDirs = Sets.newHashSet(context.getDeclaredIncludeWarnDirs()); |
| Set<Artifact> declaredIncludeSrcs = Sets.newHashSet(getDeclaredIncludeSrcs()); |
| for (Artifact input : inputsForValidation) { |
| if (context.getTransitiveCompilationPrerequisites().contains(input) |
| || allowedIncludes.contains(input)) { |
| continue; // ignore our fixed source in mandatoryInput: we just want includes |
| } |
| // Ignore headers from built-in include directories. |
| if (FileSystemUtils.startsWithAny(input.getExecPath(), ignoreDirs)) { |
| continue; |
| } |
| if (!isDeclaredIn(input, declaredIncludeDirs, declaredIncludeSrcs)) { |
| // This call can never match the declared include sources (they would be matched above). |
| // There are no declared include sources we need to warn about, so use an empty set here. |
| if (isDeclaredIn(input, warnIncludeDirs, ImmutableSet.<Artifact>of())) { |
| warnings.add(input.getPath().toString()); |
| } else { |
| errors.add(input.getPath().toString()); |
| } |
| } |
| } |
| if (VALIDATION_DEBUG_WARN) { |
| synchronized (System.err) { |
| if (VALIDATION_DEBUG >= 2 || errors.hasProblems() || warnings.hasProblems()) { |
| if (errors.hasProblems()) { |
| System.err.println("ERROR: Include(s) were not in declared srcs:"); |
| } else if (warnings.hasProblems()) { |
| System.err.println("WARN: Include(s) were not in declared srcs:"); |
| } else { |
| System.err.println("INFO: Include(s) were OK for '" + getSourceFile() |
| + "', declared srcs:"); |
| } |
| for (Artifact a : context.getDeclaredIncludeSrcs()) { |
| System.err.println(" '" + a.toDetailString() + "'"); |
| } |
| System.err.println(" or under declared dirs:"); |
| for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeDirs())) { |
| System.err.println(" '" + f + "'"); |
| } |
| System.err.println(" or under declared warn dirs:"); |
| for (PathFragment f : Sets.newTreeSet(context.getDeclaredIncludeWarnDirs())) { |
| System.err.println(" '" + f + "'"); |
| } |
| System.err.println(" with prefixes:"); |
| for (PathFragment dirpath : context.getQuoteIncludeDirs()) { |
| System.err.println(" '" + dirpath + "'"); |
| } |
| } |
| } |
| } |
| |
| if (warnings.hasProblems()) { |
| eventHandler.handle( |
| Event.warn( |
| getOwner().getLocation(), |
| warnings.getMessage(this, getSourceFile())) |
| .withTag(Label.print(getOwner().getLabel()))); |
| } |
| errors.assertProblemFree(this, getSourceFile()); |
| } |
| |
| private Iterable<PathFragment> getValidationIgnoredDirs() { |
| List<PathFragment> cxxSystemIncludeDirs = cppConfiguration.getBuiltInIncludeDirectories(); |
| return Iterables.concat( |
| cxxSystemIncludeDirs, context.getSystemIncludeDirs()); |
| } |
| |
| /** |
| * Returns true if an included artifact is declared in a set of allowed |
| * include directories. The simple case is that the artifact's parent |
| * directory is contained in the set, or is empty. |
| * |
| * <p>This check also supports a wildcard suffix of '**' for the cases where the |
| * calculations are inexact. |
| * |
| * <p>It also handles unseen non-nested-package subdirs by walking up the path looking |
| * for matches. |
| */ |
| private static boolean isDeclaredIn( |
| Artifact input, Set<PathFragment> declaredIncludeDirs, Set<Artifact> declaredIncludeSrcs) { |
| // First check if it's listed in "srcs". If so, then its declared & OK. |
| if (declaredIncludeSrcs.contains(input)) { |
| return true; |
| } |
| // If it's a derived artifact, then it MUST be listed in "srcs" as checked above. |
| // We define derived here as being not source and not under the include link tree. |
| if (!input.isSourceArtifact() |
| && !input.getRoot().getExecPath().getBaseName().equals("include")) { |
| return false; |
| } |
| // Need to do dir/package matching: first try a quick exact lookup. |
| PathFragment includeDir = input.getRootRelativePath().getParentDirectory(); |
| if (includeDir.segmentCount() == 0 || declaredIncludeDirs.contains(includeDir)) { |
| return true; // OK: quick exact match. |
| } |
| // Not found in the quick lookup: try the wildcards. |
| for (PathFragment declared : declaredIncludeDirs) { |
| if (declared.getBaseName().equals("**")) { |
| if (includeDir.startsWith(declared.getParentDirectory())) { |
| return true; // OK: under a wildcard dir. |
| } |
| } |
| } |
| // Still not found: see if it is in a subdir of a declared package. |
| Path root = input.getRoot().getPath(); |
| for (Path dir = input.getPath().getParentDirectory();;) { |
| if (dir.getRelative("BUILD").exists()) { |
| return false; // Bad: this is a sub-package, not a subdir of a declared package. |
| } |
| dir = dir.getParentDirectory(); |
| if (dir.equals(root)) { |
| return false; // Bad: at the top, give up. |
| } |
| if (declaredIncludeDirs.contains(dir.relativeTo(root))) { |
| return true; // OK: found under a declared dir. |
| } |
| } |
| } |
| |
| /** |
| * Recalculates this action's live input collection, including sources, middlemen. |
| * |
| * @throws ActionExecutionException iff any errors happen during update. |
| */ |
| @VisibleForTesting |
| @ThreadCompatible |
| public final synchronized void updateActionInputs(NestedSet<Artifact> discoveredInputs) |
| throws ActionExecutionException { |
| inputsKnown = false; |
| NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder(); |
| Profiler.instance().startTask(ProfilerTask.ACTION_UPDATE, this); |
| try { |
| inputs.addTransitive(mandatoryInputs); |
| if (optionalSourceFile != null) { |
| inputs.add(optionalSourceFile); |
| } |
| inputs.addAll(context.getTransitiveCompilationPrerequisites()); |
| inputs.addTransitive(discoveredInputs); |
| inputsKnown = true; |
| } finally { |
| Profiler.instance().completeTask(ProfilerTask.ACTION_UPDATE); |
| synchronized (this) { |
| setInputs(inputs.build()); |
| } |
| } |
| } |
| |
| private DependencySet processDepset(Path execRoot, CppCompileActionContext.Reply reply) |
| throws IOException { |
| DotdFile dotdFile = getDotdFile(); |
| Preconditions.checkNotNull(dotdFile); |
| DependencySet depSet = new DependencySet(execRoot); |
| // artifact() is null if we are using in-memory .d files. We also want to prepare for the |
| // case where we expected an in-memory .d file, but we did not get an appropriate response. |
| // Perhaps we produced the file locally. |
| if (dotdFile.artifact() != null || reply == null) { |
| return depSet.read(dotdFile.getPath()); |
| } else { |
| // This is an in-memory .d file. |
| return depSet.process(reply.getContents()); |
| } |
| } |
| |
| /** |
| * Returns a collection with additional input artifacts relevant to the action by reading the |
| * dynamically-discovered dependency information from the .d file after the action has run. |
| * |
| * <p>Artifacts are considered inputs but not "mandatory" inputs. |
| * |
| * @param reply the reply from the compilation. |
| * @throws ActionExecutionException iff the .d is missing (when required), malformed, or has |
| * unresolvable included artifacts. |
| */ |
| @VisibleForTesting |
| @ThreadCompatible |
| public NestedSet<Artifact> discoverInputsFromDotdFiles( |
| Path execRoot, ArtifactResolver artifactResolver, Reply reply) |
| throws ActionExecutionException { |
| NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder(); |
| if (getDotdFile() == null) { |
| return inputs.build(); |
| } |
| try { |
| // Read .d file. |
| DependencySet depSet = processDepset(execRoot, reply); |
| |
| // Determine prefixes of allowed absolute inclusions. |
| CppConfiguration toolchain = cppConfiguration; |
| List<Path> systemIncludePrefixes = new ArrayList<>(); |
| for (PathFragment includePath : toolchain.getBuiltInIncludeDirectories()) { |
| if (includePath.isAbsolute()) { |
| systemIncludePrefixes.add(execRoot.getFileSystem().getPath(includePath)); |
| } |
| } |
| |
| // Check inclusions. |
| IncludeProblems problems = new IncludeProblems(); |
| Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap(); |
| for (Path execPath : depSet.getDependencies()) { |
| // Module .pcm files are generated and thus aren't declared inputs. |
| if (execPath.getBaseName().endsWith(".pcm")) { |
| continue; |
| } |
| PathFragment execPathFragment = execPath.asFragment(); |
| if (execPathFragment.isAbsolute()) { |
| // Absolute includes from system paths are ignored. |
| if (FileSystemUtils.startsWithAny(execPath, systemIncludePrefixes)) { |
| continue; |
| } |
| // Since gcc is given only relative paths on the command line, |
| // non-system include paths here should never be absolute. If they |
| // are, it's probably due to a non-hermetic #include, & we should stop |
| // the build with an error. |
| if (execPath.startsWith(execRoot)) { |
| execPathFragment = execPath.relativeTo(execRoot); // funky but tolerable path |
| } else { |
| problems.add(execPathFragment.getPathString()); |
| continue; |
| } |
| } |
| Artifact artifact = allowedDerivedInputsMap.get(execPathFragment); |
| if (artifact == null) { |
| artifact = artifactResolver.resolveSourceArtifact(execPathFragment, RepositoryName.MAIN); |
| } |
| if (artifact != null) { |
| inputs.add(artifact); |
| // In some cases, execution backends need extra files for each included file. Add them |
| // to the set of actual inputs. |
| inputs.addAll(specialInputsHandler.getInputsForIncludedFile(artifact, artifactResolver)); |
| } else { |
| // Abort if we see files that we can't resolve, likely caused by |
| // undeclared includes or illegal include constructs. |
| problems.add(execPathFragment.getPathString()); |
| } |
| } |
| //TODO(b/22551695): Remove in favor of seperate implementations. |
| if (semantics == null || semantics.needsIncludeValidation()) { |
| problems.assertProblemFree(this, getSourceFile()); |
| } |
| } catch (IOException e) { |
| // Some kind of IO or parse exception--wrap & rethrow it to stop the build. |
| throw new ActionExecutionException("error while parsing .d file", e, this, false); |
| } |
| return inputs.build(); |
| } |
| |
| @Override |
| public Iterable<Artifact> resolveInputsFromCache( |
| ArtifactResolver artifactResolver, |
| PackageRootResolver resolver, |
| Collection<PathFragment> inputPaths) |
| throws PackageRootResolutionException, InterruptedException { |
| // Note that this method may trigger a violation of the desirable invariant that getInputs() |
| // is a superset of getMandatoryInputs(). See bug about an "action not in canonical form" |
| // error message and the integration test test_crosstool_change_and_failure(). |
| Map<PathFragment, Artifact> allowedDerivedInputsMap = getAllowedDerivedInputsMap(); |
| List<Artifact> inputs = new ArrayList<>(); |
| List<PathFragment> unresolvedPaths = new ArrayList<>(); |
| for (PathFragment execPath : inputPaths) { |
| Artifact artifact = allowedDerivedInputsMap.get(execPath); |
| if (artifact != null) { |
| inputs.add(artifact); |
| } else { |
| // Remember this execPath, we will try to resolve it as a source artifact. |
| unresolvedPaths.add(execPath); |
| } |
| } |
| |
| Map<PathFragment, Artifact> resolvedArtifacts = |
| artifactResolver.resolveSourceArtifacts(unresolvedPaths, resolver); |
| if (resolvedArtifacts == null) { |
| // We are missing some dependencies. We need to rerun this update later. |
| return null; |
| } |
| |
| for (PathFragment execPath : unresolvedPaths) { |
| Artifact artifact = resolvedArtifacts.get(execPath); |
| // If PathFragment cannot be resolved into the artifact - ignore it. This could happen if |
| // rule definition has changed and action no longer depends on, e.g., additional source file |
| // in the separate package and that package is no longer referenced anywhere else. |
| // It is safe to ignore such paths because dependency checker would identify change in inputs |
| // (ignored path was used before) and will force action execution. |
| if (artifact != null) { |
| inputs.add(artifact); |
| } |
| } |
| return inputs; |
| } |
| |
| @Override |
| public synchronized void updateInputs(Iterable<Artifact> inputs) { |
| inputsKnown = true; |
| synchronized (this) { |
| setInputs(inputs); |
| } |
| } |
| |
| private Map<PathFragment, Artifact> getAllowedDerivedInputsMap() { |
| Map<PathFragment, Artifact> allowedDerivedInputMap = new HashMap<>(); |
| addToMap(allowedDerivedInputMap, mandatoryInputs); |
| addToMap(allowedDerivedInputMap, getDeclaredIncludeSrcs()); |
| addToMap(allowedDerivedInputMap, context.getTransitiveCompilationPrerequisites()); |
| Artifact artifact = getSourceFile(); |
| if (!artifact.isSourceArtifact()) { |
| allowedDerivedInputMap.put(artifact.getExecPath(), artifact); |
| } |
| return allowedDerivedInputMap; |
| } |
| |
| private void addToMap(Map<PathFragment, Artifact> map, Iterable<Artifact> artifacts) { |
| for (Artifact artifact : artifacts) { |
| if (!artifact.isSourceArtifact()) { |
| map.put(artifact.getExecPath(), artifact); |
| } |
| } |
| } |
| |
| @Override |
| protected String getRawProgressMessage() { |
| return "Compiling " + getSourceFile().prettyPrint(); |
| } |
| |
| /** |
| * Return the directories in which to look for headers (pertains to headers |
| * not specifically listed in {@code declaredIncludeSrcs}). The return value |
| * may contain duplicate elements. |
| */ |
| public NestedSet<PathFragment> getDeclaredIncludeDirs() { |
| return context.getDeclaredIncludeDirs(); |
| } |
| |
| /** |
| * Return the directories in which to look for headers and issue a warning. |
| * (pertains to headers not specifically listed in {@code |
| * declaredIncludeSrcs}). The return value may contain duplicate elements. |
| */ |
| public NestedSet<PathFragment> getDeclaredIncludeWarnDirs() { |
| return context.getDeclaredIncludeWarnDirs(); |
| } |
| |
| /** |
| * Return explicit header files (i.e., header files explicitly listed). The |
| * return value may contain duplicate elements. |
| */ |
| @Override |
| public NestedSet<Artifact> getDeclaredIncludeSrcs() { |
| if (lipoScannables != null && lipoScannables.iterator().hasNext()) { |
| NestedSetBuilder<Artifact> srcs = NestedSetBuilder.stableOrder(); |
| srcs.addTransitive(context.getDeclaredIncludeSrcs()); |
| for (IncludeScannable lipoScannable : lipoScannables) { |
| srcs.addTransitive(lipoScannable.getDeclaredIncludeSrcs()); |
| } |
| return srcs.build(); |
| } |
| return context.getDeclaredIncludeSrcs(); |
| } |
| |
| @Override |
| public ResourceSet estimateResourceConsumption(Executor executor) { |
| return executor.getContext(actionContext).estimateResourceConsumption(this); |
| } |
| |
| @VisibleForTesting |
| public Class<? extends CppCompileActionContext> getActionContext() { |
| return actionContext; |
| } |
| |
| /** |
| * Estimate resource consumption when this action is executed locally. |
| */ |
| public ResourceSet estimateResourceConsumptionLocal() { |
| // We use a local compile, so much of the time is spent waiting for IO, |
| // but there is still significant CPU; hence we estimate 50% cpu usage. |
| return ResourceSet.createWithRamCpuIo(/*memoryMb=*/200, /*cpuUsage=*/0.5, /*ioUsage=*/0.0); |
| } |
| |
| @Override |
| public String computeKey() { |
| Fingerprint f = new Fingerprint(); |
| f.addUUID(actionClassId); |
| f.addStringMap(getEnvironment()); |
| f.addStrings(getArgv()); |
| f.addStrings(executionRequirements); |
| |
| /* |
| * getArgv() above captures all changes which affect the compilation |
| * command and hence the contents of the object file. But we need to |
| * also make sure that we reexecute the action if any of the fields |
| * that affect whether validateIncludes() will report an error or warning |
| * have changed, otherwise we might miss some errors. |
| */ |
| f.addPaths(context.getDeclaredIncludeDirs()); |
| f.addPaths(context.getDeclaredIncludeWarnDirs()); |
| for (Artifact declaredIncludeSrc : context.getDeclaredIncludeSrcs()) { |
| f.addPath(declaredIncludeSrc.getExecPath()); |
| } |
| f.addInt(0); // mark the boundary between input types |
| for (Artifact input : getMandatoryInputs()) { |
| f.addPath(input.getExecPath()); |
| } |
| return f.hexDigestAndReset(); |
| } |
| |
| @Override |
| @ThreadCompatible |
| public void execute( |
| ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException, InterruptedException { |
| Executor executor = actionExecutionContext.getExecutor(); |
| CppCompileActionContext.Reply reply; |
| try { |
| reply = executor.getContext(actionContext).execWithReply(this, actionExecutionContext); |
| } catch (ExecException e) { |
| throw e.toActionExecutionException("C++ compilation of rule '" + getOwner().getLabel() + "'", |
| executor.getVerboseFailures(), this); |
| } |
| ensureCoverageNotesFilesExist(); |
| |
| // This is the .d file scanning part. |
| IncludeScanningContext scanningContext = executor.getContext(IncludeScanningContext.class); |
| NestedSet<Artifact> discoveredInputs = |
| discoverInputsFromDotdFiles( |
| executor.getExecRoot(), scanningContext.getArtifactResolver(), reply); |
| reply = null; // Clear in-memory .d files early. |
| |
| // Post-execute "include scanning", which modifies the action inputs to match what the compile |
| // action actually used by incorporating the results of .d file parsing. |
| // |
| // We enable this when "include scanning" itself is enabled, or when hdrs_check is set to loose |
| // or warn, as otherwise the action might be missing inputs that the compiler used and rebuilds |
| // become incorrect. |
| // |
| // Note that this effectively disables post-execute "include scanning" in Bazel, because |
| // hdrs_check is forced to "strict" and "include scanning" is forced to off. |
| boolean usesStrictHdrsChecks = context.getDeclaredIncludeDirs().isEmpty() |
| && context.getDeclaredIncludeWarnDirs().isEmpty(); |
| if (shouldScanIncludes() || !usesStrictHdrsChecks) { |
| updateActionInputs(discoveredInputs); |
| } |
| |
| // hdrs_check: Turning this off opens the door to incorrect builds. However, we allow it |
| // to accommodate the current behavior in the objc rules. |
| if (semantics == null || semantics.needsIncludeValidation()) { |
| validateInclusions( |
| discoveredInputs, |
| actionExecutionContext.getArtifactExpander(), |
| executor.getEventHandler()); |
| } |
| } |
| |
| /** |
| * Gcc only creates ".gcno" files if the compilation unit is non-empty. |
| * To ensure that the set of outputs for a CppCompileAction remains consistent |
| * and doesn't vary dynamically depending on the _contents_ of the input files, |
| * we create empty ".gcno" files if gcc didn't create them. |
| */ |
| private void ensureCoverageNotesFilesExist() throws ActionExecutionException { |
| for (Artifact output : getOutputs()) { |
| if (CppFileTypes.COVERAGE_NOTES.matches(output.getFilename()) // ".gcno" |
| && !output.getPath().exists()) { |
| try { |
| FileSystemUtils.createEmptyFile(output.getPath()); |
| } catch (IOException e) { |
| throw new ActionExecutionException( |
| "Error creating file '" + output.getPath() + "': " + e.getMessage(), e, this, false); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Provides list of include files needed for performing extra actions on this action when run |
| * remotely. The list of include files is created by performing a header scan on the known input |
| * files. |
| */ |
| @Override |
| public Iterable<Artifact> getInputFilesForExtraAction( |
| ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException, InterruptedException { |
| Collection<Artifact> scannedIncludes = |
| actionExecutionContext.getExecutor().getContext(actionContext) |
| .getScannedIncludeFiles(this, actionExecutionContext); |
| // Use a set to eliminate duplicates. |
| ImmutableSet.Builder<Artifact> result = ImmutableSet.builder(); |
| return result.addAll(getInputs()).addAll(scannedIncludes).build(); |
| } |
| |
| @Override |
| public String getMnemonic() { return "CppCompile"; } |
| |
| @Override |
| public String describeKey() { |
| StringBuilder message = new StringBuilder(); |
| message.append(getProgressMessage()); |
| message.append('\n'); |
| // Outputting one argument per line makes it easier to diff the results. |
| // The first element in getArgv() is actually the command to execute. |
| String legend = " Command: "; |
| for (String argument : ShellEscaper.escapeAll(getArgv())) { |
| message.append(legend); |
| message.append(argument); |
| message.append('\n'); |
| legend = " Argument: "; |
| } |
| |
| for (PathFragment path : context.getDeclaredIncludeDirs()) { |
| message.append(" Declared include directory: "); |
| message.append(ShellEscaper.escapeString(path.getPathString())); |
| message.append('\n'); |
| } |
| |
| for (Artifact src : getDeclaredIncludeSrcs()) { |
| message.append(" Declared include source: "); |
| message.append(ShellEscaper.escapeString(src.getExecPathString())); |
| message.append('\n'); |
| } |
| |
| return message.toString(); |
| } |
| |
| /** |
| * The compile command line for the enclosing C++ compile action. |
| */ |
| public final class CppCompileCommandLine { |
| private final Artifact sourceFile; |
| private final DotdFile dotdFile; |
| private final List<String> copts; |
| private final Predicate<String> coptsFilter; |
| private final Collection<String> features; |
| @VisibleForTesting public final CcToolchainFeatures.Variables variables; |
| private final String actionName; |
| |
| public CppCompileCommandLine( |
| Artifact sourceFile, |
| DotdFile dotdFile, |
| ImmutableList<String> copts, |
| Predicate<String> coptsFilter, |
| Collection<String> features, |
| CcToolchainFeatures.Variables variables, |
| String actionName) { |
| this.sourceFile = Preconditions.checkNotNull(sourceFile); |
| this.dotdFile = CppFileTypes.mustProduceDotdFile(sourceFile.getPath().toString()) |
| ? Preconditions.checkNotNull(dotdFile) : null; |
| this.copts = Preconditions.checkNotNull(copts); |
| this.coptsFilter = coptsFilter; |
| this.features = Preconditions.checkNotNull(features); |
| this.variables = variables; |
| this.actionName = actionName; |
| } |
| |
| /** |
| * Returns the environment variables that should be set for C++ compile actions. |
| */ |
| protected Map<String, String> getEnvironment() { |
| return featureConfiguration.getEnvironmentVariables(actionName, variables); |
| } |
| |
| protected List<String> getArgv( |
| PathFragment outputFile, CcToolchainFeatures.Variables overwrittenVariables) { |
| List<String> commandLine = new ArrayList<>(); |
| |
| // first: The command name. |
| if (!featureConfiguration.actionIsConfigured(actionName)) { |
| commandLine.add(cppConfiguration.getToolPathFragment(Tool.GCC).getPathString()); |
| } else { |
| commandLine.add( |
| featureConfiguration |
| .getToolForAction(actionName) |
| .getToolPath(cppConfiguration.getCrosstoolTopPathFragment()) |
| .getPathString()); |
| } |
| |
| // second: The compiler options. |
| commandLine.addAll(getCompilerOptions(overwrittenVariables)); |
| |
| if (!featureConfiguration.isEnabled("compile_action_flags_in_flag_set")) { |
| // third: The file to compile! |
| commandLine.add("-c"); |
| commandLine.add(sourceFile.getExecPathString()); |
| |
| // finally: The output file. (Prefixed with -o). |
| commandLine.add("-o"); |
| commandLine.add(outputFile.getPathString()); |
| } |
| |
| return commandLine; |
| } |
| |
| public List<String> getCompilerOptions( |
| @Nullable CcToolchainFeatures.Variables overwrittenVariables) { |
| List<String> options = new ArrayList<>(); |
| CppConfiguration toolchain = cppConfiguration; |
| |
| addFilteredOptions(options, toolchain.getCompilerOptions(features)); |
| |
| String sourceFilename = sourceFile.getExecPathString(); |
| if (CppFileTypes.C_SOURCE.matches(sourceFilename)) { |
| addFilteredOptions(options, toolchain.getCOptions()); |
| } |
| if (CppFileTypes.CPP_SOURCE.matches(sourceFilename) |
| || CppFileTypes.CPP_HEADER.matches(sourceFilename) |
| || CppFileTypes.CPP_MODULE_MAP.matches(sourceFilename) |
| || CppFileTypes.CLIF_INPUT_PROTO.matches(sourceFilename)) { |
| addFilteredOptions(options, toolchain.getCxxOptions(features)); |
| } |
| |
| // TODO(bazel-team): This needs to be before adding getUnfilteredCompilerOptions() and after |
| // adding the warning flags until all toolchains are migrated; currently toolchains use the |
| // unfiltered compiler options to inject include paths, which is superseded by the feature |
| // configuration; on the other hand toolchains switch off warnings for the layering check |
| // that will be re-added by the feature flags. |
| CcToolchainFeatures.Variables updatedVariables = variables; |
| if (overwrittenVariables != null) { |
| CcToolchainFeatures.Variables.Builder variablesBuilder = |
| new CcToolchainFeatures.Variables.Builder(); |
| variablesBuilder.addAll(variables); |
| variablesBuilder.addAll(overwrittenVariables); |
| updatedVariables = variablesBuilder.build(); |
| } |
| addFilteredOptions( |
| options, featureConfiguration.getCommandLine(actionName, updatedVariables)); |
| |
| // Users don't expect the explicit copts to be filtered by coptsFilter, add them verbatim. |
| // Make sure these are added after the options from the feature configuration, so that |
| // those options can be overriden. |
| options.addAll(copts); |
| |
| // Unfiltered compiler options contain system include paths. These must be added after |
| // the user provided options, otherwise users adding include paths will not pick up their |
| // own include paths first. |
| options.addAll(toolchain.getUnfilteredCompilerOptions(features)); |
| |
| // Add the options of --per_file_copt, if the label or the base name of the source file |
| // matches the specified regular expression filter. |
| for (PerLabelOptions perLabelOptions : cppConfiguration.getPerFileCopts()) { |
| if ((sourceLabel != null && perLabelOptions.isIncluded(sourceLabel)) |
| || perLabelOptions.isIncluded(sourceFile)) { |
| options.addAll(perLabelOptions.getOptions()); |
| } |
| } |
| |
| if (!featureConfiguration.isEnabled("compile_action_flags_in_flag_set")) { |
| if (FileType.contains(outputFile, CppFileTypes.ASSEMBLER, CppFileTypes.PIC_ASSEMBLER)) { |
| options.add("-S"); |
| } else if (FileType.contains(outputFile, CppFileTypes.PREPROCESSED_C, |
| CppFileTypes.PREPROCESSED_CPP, CppFileTypes.PIC_PREPROCESSED_C, |
| CppFileTypes.PIC_PREPROCESSED_CPP)) { |
| options.add("-E"); |
| } |
| } |
| |
| return options; |
| } |
| |
| // For each option in 'in', add it to 'out' unless it is matched by the 'coptsFilter' regexp. |
| private void addFilteredOptions(List<String> out, List<String> in) { |
| Iterables.addAll(out, Iterables.filter(in, coptsFilter)); |
| } |
| } |
| |
| /** |
| * A reference to a .d file. There are two modes: |
| * <ol> |
| * <li>an Artifact that represents a real on-disk file |
| * <li>just an execPath that refers to a virtual .d file that is not written to disk |
| * </ol> |
| */ |
| public static class DotdFile { |
| private final Artifact artifact; |
| private final PathFragment execPath; |
| |
| public DotdFile(Artifact artifact) { |
| this.artifact = artifact; |
| this.execPath = null; |
| } |
| |
| public DotdFile(PathFragment execPath) { |
| this.artifact = null; |
| this.execPath = execPath; |
| } |
| |
| /** |
| * @return the Artifact or null |
| */ |
| public Artifact artifact() { |
| return artifact; |
| } |
| |
| /** |
| * @return Gets the execPath regardless of whether this is a real Artifact |
| */ |
| public PathFragment getSafeExecPath() { |
| return execPath == null ? artifact.getExecPath() : execPath; |
| } |
| |
| /** |
| * @return the on-disk location of the .d file or null |
| */ |
| public Path getPath() { |
| return artifact.getPath(); |
| } |
| } |
| } |