| // Copyright 2017 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.exec; | |
| import static com.google.devtools.build.lib.vfs.PathFragment.HIERARCHICAL_COMPARATOR; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Preconditions; | |
| import com.google.devtools.build.lib.actions.ActionInput; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact; | |
| import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; | |
| import com.google.devtools.build.lib.actions.FilesetOutputSymlink; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTree; | |
| import com.google.devtools.build.lib.actions.InputMetadataProvider; | |
| import com.google.devtools.build.lib.actions.PathMapper; | |
| import com.google.devtools.build.lib.actions.RunfilesTree; | |
| import com.google.devtools.build.lib.actions.Spawn; | |
| import com.google.devtools.build.lib.actions.SpawnInputs; | |
| import com.google.devtools.build.lib.actions.VirtualActionInput; | |
| import com.google.devtools.build.lib.skyframe.TreeArtifactValue; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.SortedMap; | |
| import java.util.TreeMap; | |
| /** | |
| * A helper class for spawn strategies to turn runfiles suppliers into input mappings. This class | |
| * performs no I/O operations, but only rearranges the files according to how the runfiles should be | |
| * laid out. | |
| */ | |
| public final class SpawnInputExpander { | |
| private final boolean expandArchivedTreeArtifacts; | |
| public SpawnInputExpander() { | |
| this(/* expandArchivedTreeArtifacts= */ true); | |
| } | |
| public SpawnInputExpander(boolean expandArchivedTreeArtifacts) { | |
| this.expandArchivedTreeArtifacts = expandArchivedTreeArtifacts; | |
| } | |
| private static void addMapping( | |
| Map<PathFragment, ActionInput> inputMap, PathFragment targetLocation, ActionInput input) { | |
| Preconditions.checkArgument(!targetLocation.isAbsolute(), targetLocation); | |
| inputMap.put(targetLocation, input); | |
| } | |
| @VisibleForTesting | |
| void addSingleRunfilesTreeToInputs( | |
| RunfilesTree runfilesTree, | |
| Map<PathFragment, ActionInput> inputMap, | |
| InputMetadataProvider inputMetadataProvider, | |
| PathMapper pathMapper) { | |
| addSingleRunfilesTreeToInputs( | |
| inputMap, | |
| runfilesTree.getExecPath(), | |
| runfilesTree.getMapping(), | |
| inputMetadataProvider, | |
| pathMapper); | |
| } | |
| /** | |
| * Gathers the mapping for a single runfiles tree into {@code inputMap}. | |
| * | |
| * <p>This should not be a public interface, it's only there to support legacy code until we | |
| * figure out how not to call this method (or else how to make this method more palatable) | |
| */ | |
| public void addSingleRunfilesTreeToInputs( | |
| Map<PathFragment, ActionInput> inputMap, | |
| PathFragment root, | |
| Map<PathFragment, Artifact> mappings, | |
| InputMetadataProvider inputMetadataProvider, | |
| PathMapper pathMapper) { | |
| Preconditions.checkArgument(!root.isAbsolute(), root); | |
| for (Map.Entry<PathFragment, Artifact> mapping : mappings.entrySet()) { | |
| PathFragment location = root.getRelative(mapping.getKey()); | |
| Artifact artifact = mapping.getValue(); | |
| if (artifact == null) { | |
| addMapping( | |
| inputMap, mapForRunfiles(pathMapper, root, location), VirtualActionInput.EMPTY_MARKER); | |
| continue; | |
| } | |
| Preconditions.checkArgument(!artifact.isRunfilesTree(), artifact); | |
| if (artifact.isTreeArtifact()) { | |
| TreeArtifactValue treeArtifactValue = inputMetadataProvider.getTreeMetadata(artifact); | |
| ArchivedTreeArtifact archivedTreeArtifact = | |
| expandArchivedTreeArtifacts ? null : treeArtifactValue.getArchivedArtifact(); | |
| if (archivedTreeArtifact != null) { | |
| addMapping(inputMap, mapForRunfiles(pathMapper, root, location), archivedTreeArtifact); | |
| } else if (treeArtifactValue.getChildren().isEmpty()) { | |
| addMapping(inputMap, mapForRunfiles(pathMapper, root, location), artifact); | |
| } else { | |
| for (TreeFileArtifact input : treeArtifactValue.getChildren()) { | |
| addMapping( | |
| inputMap, | |
| mapForRunfiles(pathMapper, root, location) | |
| .getRelative(input.getParentRelativePath()), | |
| input); | |
| } | |
| } | |
| } else if (artifact.isFileset()) { | |
| // TODO(bazel-team): Add path mapping support for filesets. | |
| FilesetOutputTree filesetOutput = inputMetadataProvider.getFileset(artifact); | |
| addFilesetManifest(location, artifact, filesetOutput, inputMap); | |
| } else { | |
| addMapping(inputMap, mapForRunfiles(pathMapper, root, location), artifact); | |
| } | |
| } | |
| } | |
| @VisibleForTesting | |
| static void addFilesetManifests( | |
| Map<Artifact, FilesetOutputTree> filesetMappings, Map<PathFragment, ActionInput> inputMap) { | |
| for (Map.Entry<Artifact, FilesetOutputTree> entry : filesetMappings.entrySet()) { | |
| Artifact fileset = entry.getKey(); | |
| addFilesetManifest(fileset.getExecPath(), fileset, entry.getValue(), inputMap); | |
| } | |
| } | |
| private static void addFilesetManifest( | |
| PathFragment location, | |
| Artifact filesetArtifact, | |
| FilesetOutputTree filesetOutput, | |
| Map<PathFragment, ActionInput> inputMap) { | |
| Preconditions.checkArgument(filesetArtifact.isFileset(), filesetArtifact); | |
| for (FilesetOutputSymlink link : filesetOutput.symlinks()) { | |
| addMapping(inputMap, location.getRelative(link.name()), link.target()); | |
| } | |
| } | |
| private void addInputs( | |
| Map<PathFragment, ActionInput> inputMap, | |
| SpawnInputs inputFiles, | |
| InputMetadataProvider inputMetadataProvider, | |
| PathMapper pathMapper) { | |
| // Actions that accept TreeArtifacts as inputs generally expect the directory corresponding | |
| // to the artifact to be created, even if it is empty. We explicitly keep empty TreeArtifacts | |
| // here to signal consumers that they should create the directory. | |
| List<ActionInput> inputs = | |
| InputMetadataProvider.expandArtifacts( | |
| inputMetadataProvider, | |
| inputFiles.flatten(), | |
| /* keepEmptyTreeArtifacts= */ true, | |
| /* keepRunfilesTrees= */ true); | |
| for (ActionInput input : inputs) { | |
| switch (input) { | |
| case TreeFileArtifact child -> { | |
| Artifact parent = child.getParent(); | |
| PathFragment parentPath = pathMapper.map(parent.getExecPath()); | |
| addMapping( | |
| inputMap, | |
| // If the PathMapper was no-op for the parent, we can use the child's exec path and | |
| // avoid path concatenation. | |
| parentPath.equals(parent.getExecPath()) | |
| ? child.getExecPath() | |
| : parentPath.getRelative(child.getParentRelativePath()), | |
| input); | |
| } | |
| case Artifact runfilesTreeArtifact when runfilesTreeArtifact.isRunfilesTree() -> | |
| addSingleRunfilesTreeToInputs( | |
| inputMetadataProvider.getRunfilesMetadata(runfilesTreeArtifact).getRunfilesTree(), | |
| inputMap, | |
| inputMetadataProvider, | |
| pathMapper); | |
| case Artifact fileset when fileset.isFileset() -> | |
| addFilesetManifest( | |
| fileset.getExecPath(), | |
| fileset, | |
| inputMetadataProvider.getFileset(fileset), | |
| inputMap); | |
| default -> addMapping(inputMap, pathMapper.map(input.getExecPath()), input); | |
| } | |
| } | |
| } | |
| /** | |
| * Convert the inputs and runfiles of the given spawn to a map from exec-root relative paths to | |
| * {@link ActionInput}s. The returned map does not contain non-empty tree artifacts as they are | |
| * expanded to file artifacts. Tree artifacts that would expand to the empty set under the | |
| * provided {@link InputMetadataProvider} are left untouched so that their corresponding empty | |
| * directories can be created. | |
| * | |
| * <p>The returned map never contains {@code null} values. | |
| * | |
| * <p>The returned map contains all runfiles, but not the {@code MANIFEST}. | |
| * | |
| * <p>The returned map is sorted by {@link PathFragment#HIERARCHICAL_COMPARATOR}, which orders the | |
| * paths nested under a directory directly after it. | |
| */ | |
| public SortedMap<PathFragment, ActionInput> getInputMapping( | |
| Spawn spawn, InputMetadataProvider inputMetadataProvider) { | |
| TreeMap<PathFragment, ActionInput> inputMap = new TreeMap<>(HIERARCHICAL_COMPARATOR); | |
| addInputs(inputMap, spawn.getInputFiles(), inputMetadataProvider, spawn.getPathMapper()); | |
| return inputMap; | |
| } | |
| private static PathFragment mapForRunfiles( | |
| PathMapper pathMapper, PathFragment runfilesDir, PathFragment execPath) { | |
| if (pathMapper.isNoop()) { | |
| return execPath; | |
| } | |
| String runfilesDirName = runfilesDir.getBaseName(); | |
| Preconditions.checkArgument(runfilesDirName.endsWith(".runfiles")); | |
| // Derive the path of the executable, apply the path mapping to it and then rederive the path | |
| // of the runfiles dir. | |
| PathFragment executable = | |
| runfilesDir.replaceName( | |
| runfilesDirName.substring(0, runfilesDirName.length() - ".runfiles".length())); | |
| return pathMapper | |
| .map(executable) | |
| .replaceName(runfilesDirName) | |
| .getRelative(execPath.relativeTo(runfilesDir)); | |
| } | |
| } |