// Copyright 2016 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.Preconditions; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.ActionExecutionException; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ArtifactResolver; import com.google.devtools.build.lib.cmdline.RepositoryName; 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.rules.cpp.CppCompileAction.DotdFile; import com.google.devtools.build.lib.rules.cpp.CppCompileAction.SpecialInputsHandler; import com.google.devtools.build.lib.util.DependencySet; 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.util.List; import java.util.Map; /** Manages the process of obtaining inputs used in a compilation from .d files. */ public class HeaderDiscovery { /** Indicates if a compile should perform dotd pruning. */ public static enum DotdPruningMode { USE, DO_NOT_USE } private final Action action; private final Artifact sourceFile; private final DotdFile dotdFile; private final SpecialInputsHandler specialInputsHandler; private final boolean shouldValidateInclusions; private final DependencySet depSet; private final List permittedSystemIncludePrefixes; private final Map allowedDerivedInputsMap; /** * Creates a HeaderDiscover instance * * @param action the action instance requiring header discovery * @param sourceFile the source file for the compile * @param dotdFile the .d file used for header discovery * @param specialInputsHandler the SpecialInputsHandler for the build * @param shouldValidateInclusions true if include validation should be performed */ public HeaderDiscovery( Action action, Artifact sourceFile, DotdFile dotdFile, SpecialInputsHandler specialInputsHandler, boolean shouldValidateInclusions, DependencySet depSet, List permittedSystemIncludePrefixes, Map allowedDerivedInputsMap) { this.action = Preconditions.checkNotNull(action); this.sourceFile = Preconditions.checkNotNull(sourceFile); this.dotdFile = Preconditions.checkNotNull(dotdFile); this.specialInputsHandler = specialInputsHandler; this.shouldValidateInclusions = shouldValidateInclusions; this.depSet = depSet; this.permittedSystemIncludePrefixes = permittedSystemIncludePrefixes; this.allowedDerivedInputsMap = allowedDerivedInputsMap; } /** * 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. * *

Artifacts are considered inputs but not "mandatory" inputs. * * @throws ActionExecutionException iff the .d is missing (when required), malformed, or has * unresolvable included artifacts. */ @VisibleForTesting @ThreadCompatible public NestedSet discoverInputsFromDotdFiles( Path execRoot, ArtifactResolver artifactResolver) throws ActionExecutionException { NestedSetBuilder inputs = NestedSetBuilder.stableOrder(); if (dotdFile == null) { return inputs.build(); } List systemIncludePrefixes = permittedSystemIncludePrefixes; // Check inclusions. IncludeProblems problems = new IncludeProblems(); for (Path execPath : depSet.getDependencies()) { 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. if (specialInputsHandler != null) { 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()); } } if (shouldValidateInclusions) { problems.assertProblemFree(action, sourceFile); } return inputs.build(); } /** A Builder for HeaderDiscovery */ public static class Builder { private Action action; private Artifact sourceFile; private DotdFile dotdFile; private SpecialInputsHandler specialInputsHandler; private boolean shouldValidateInclusions = false; private DependencySet depSet; private List permittedSystemIncludePrefixes; private Map allowedDerivedInputsMap; /** Sets the action for which to discover inputs. */ public Builder setAction(Action action) { this.action = action; return this; } /** Sets the source file for which to discover inputs. */ public Builder setSourceFile(Artifact sourceFile) { this.sourceFile = sourceFile; return this; } /** Sets the dotd file to be used to discover inputs. */ public Builder setDotdFile(DotdFile dotdFile) { this.dotdFile = dotdFile; return this; } /** Sets the SpecialInputsHandler for inputs to this build. */ public Builder setSpecialInputsHandler(SpecialInputsHandler specialInputsHandler) { this.specialInputsHandler = specialInputsHandler; return this; } /** Sets that this compile should validate inclusions against the dotd file. */ public Builder shouldValidateInclusions() { this.shouldValidateInclusions = true; return this; } /** Sets the DependencySet capturing used headers by this compile. */ public Builder setDependencySet(DependencySet depSet) { this.depSet = depSet; return this; } /** Sets prefixes of allowed absolute inclusions */ public Builder setPermittedSystemIncludePrefixes(List systemIncludePrefixes) { this.permittedSystemIncludePrefixes = systemIncludePrefixes; return this; } /** Sets permitted inputs to the build */ public Builder setAllowedDerivedinputsMap(Map allowedDerivedInputsMap) { this.allowedDerivedInputsMap = allowedDerivedInputsMap; return this; } /** Creates a CppHeaderDiscovery instance. */ public HeaderDiscovery build() { return new HeaderDiscovery( action, sourceFile, dotdFile, specialInputsHandler, shouldValidateInclusions, depSet, permittedSystemIncludePrefixes, allowedDerivedInputsMap); } } }