// 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.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; 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.SpecialInputsHandler; 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.Collection; import java.util.List; import java.util.Map; /** * Manages the process of obtaining inputs used in a compilation from a dependency set parsed from * either .d files or /showIncludes output. */ 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 SpecialInputsHandler specialInputsHandler; private final boolean shouldValidateInclusions; private final Collection<Path> dependencies; private final List<Path> permittedSystemIncludePrefixes; private final Map<PathFragment, Artifact> allowedDerivedInputsMap; /** * Creates a HeaderDiscover instance * * @param action the action instance requiring header discovery * @param sourceFile the source file for the compile * @param specialInputsHandler the SpecialInputsHandler for the build * @param shouldValidateInclusions true if include validation should be performed */ public HeaderDiscovery( Action action, Artifact sourceFile, SpecialInputsHandler specialInputsHandler, boolean shouldValidateInclusions, Collection<Path> dependencies, List<Path> permittedSystemIncludePrefixes, Map<PathFragment, Artifact> allowedDerivedInputsMap) { this.action = Preconditions.checkNotNull(action); this.sourceFile = Preconditions.checkNotNull(sourceFile); this.specialInputsHandler = specialInputsHandler; this.shouldValidateInclusions = shouldValidateInclusions; this.dependencies = dependencies; 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 parsed dependency set after the action * has run. * * <p>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<Artifact> discoverInputsFromDependencies( Path execRoot, ArtifactResolver artifactResolver) throws ActionExecutionException { NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder(); if (dependencies == null) { return inputs.build(); } List<Path> systemIncludePrefixes = permittedSystemIncludePrefixes; // Check inclusions. IncludeProblems problems = new IncludeProblems(); for (Path execPath : dependencies) { 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) { try { RepositoryName repository = PackageIdentifier.discoverFromExecPath(execPathFragment, false).getRepository(); artifact = artifactResolver.resolveSourceArtifact(execPathFragment, repository); } catch (LabelSyntaxException e) { throw new ActionExecutionException( String.format("Could not find the external repository for %s", execPathFragment), e, action, false); } } 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 SpecialInputsHandler specialInputsHandler; private boolean shouldValidateInclusions = false; private Collection<Path> dependencies; private List<Path> permittedSystemIncludePrefixes; private Map<PathFragment, Artifact> 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 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 dependencies capturing used headers by this compile. */ public Builder setDependencies(Collection<Path> dependencies) { this.dependencies = dependencies; return this; } /** Sets prefixes of allowed absolute inclusions */ public Builder setPermittedSystemIncludePrefixes(List<Path> systemIncludePrefixes) { this.permittedSystemIncludePrefixes = systemIncludePrefixes; return this; } /** Sets permitted inputs to the build */ public Builder setAllowedDerivedinputsMap(Map<PathFragment, Artifact> allowedDerivedInputsMap) { this.allowedDerivedInputsMap = allowedDerivedInputsMap; return this; } /** Creates a CppHeaderDiscovery instance. */ public HeaderDiscovery build() { return new HeaderDiscovery( action, sourceFile, specialInputsHandler, shouldValidateInclusions, dependencies, permittedSystemIncludePrefixes, allowedDerivedInputsMap); } } }