blob: a0a04a3eea12723585c93d7e426007b6160bcd00 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.exec;
15
ulfjack1a80bb82019-11-26 07:52:35 -080016
Ulf Adams015aad92016-07-13 16:49:40 +000017import com.google.common.annotations.VisibleForTesting;
Damien Martin-Guillereze5b7c592016-01-18 11:03:59 +000018import com.google.common.base.Preconditions;
fellya0e7b672020-01-29 11:31:07 -080019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.ImmutableMap;
cparsons83581482018-04-16 11:49:24 -070021import com.google.common.collect.Lists;
ulfjack112d41f2019-11-28 00:33:14 -080022import com.google.devtools.build.lib.actions.Artifact;
ulfjack34328d52019-11-19 06:13:48 -080023import com.google.devtools.build.lib.actions.EnvironmentalExecException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.actions.ExecException;
fellya0e7b672020-01-29 11:31:07 -080025import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
mschaller1eabf522020-06-10 22:03:13 -070026import com.google.devtools.build.lib.server.FailureDetails.Execution;
mschaller45576672020-06-10 19:15:07 -070027import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
mschaller1eabf522020-06-10 22:03:13 -070028import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
Benjamin Petersonc63e4fe2018-10-11 10:18:04 -070029import com.google.devtools.build.lib.shell.Command;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.shell.CommandException;
31import com.google.devtools.build.lib.util.CommandBuilder;
pcloudy0ec9d782018-08-28 08:56:16 -070032import com.google.devtools.build.lib.util.CommandUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.util.OsUtils;
Benjamin Petersonc63e4fe2018-10-11 10:18:04 -070034import com.google.devtools.build.lib.util.io.OutErr;
ulfjack80e703a2020-01-22 06:51:30 -080035import com.google.devtools.build.lib.vfs.Dirent;
36import com.google.devtools.build.lib.vfs.FileStatus;
Lukacs Berki31b059f2016-08-04 11:55:20 +000037import com.google.devtools.build.lib.vfs.FileSystemUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.lib.vfs.Path;
ulfjack1a80bb82019-11-26 07:52:35 -080039import com.google.devtools.build.lib.vfs.PathFragment;
ulfjack80e703a2020-01-22 06:51:30 -080040import com.google.devtools.build.lib.vfs.Symlinks;
Lukacs Berki31b059f2016-08-04 11:55:20 +000041import java.io.IOException;
ulfjack1a80bb82019-11-26 07:52:35 -080042import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.util.List;
ulfjack06760b72019-11-18 05:08:20 -080044import java.util.Map;
ulfjack80e703a2020-01-22 06:51:30 -080045import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046
47/**
ulfjack4d7f8f72017-11-29 03:37:04 -080048 * Helper class responsible for the symlink tree creation. Used to generate runfiles and fileset
49 * symlink farms.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050 */
51public final class SymlinkTreeHelper {
Ulf Adams015aad92016-07-13 16:49:40 +000052 @VisibleForTesting
53 public static final String BUILD_RUNFILES = "build-runfiles" + OsUtils.executableExtension();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
Lukacs Berki31b059f2016-08-04 11:55:20 +000055 private final Path inputManifest;
56 private final Path symlinkTreeRoot;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010057 private final boolean filesetTree;
58
59 /**
ulfjack4d7f8f72017-11-29 03:37:04 -080060 * Creates SymlinkTreeHelper instance. Can be used independently of SymlinkTreeAction.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061 *
62 * @param inputManifest exec path to the input runfiles manifest
Lukacs Berki31b059f2016-08-04 11:55:20 +000063 * @param symlinkTreeRoot the root of the symlink tree to be created
Googlere854c862018-05-18 05:15:04 -070064 * @param filesetTree true if this is fileset symlink tree, false if this is a runfiles symlink
65 * tree.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010066 */
ulfjack4d7f8f72017-11-29 03:37:04 -080067 public SymlinkTreeHelper(Path inputManifest, Path symlinkTreeRoot, boolean filesetTree) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068 this.inputManifest = inputManifest;
69 this.symlinkTreeRoot = symlinkTreeRoot;
70 this.filesetTree = filesetTree;
71 }
72
ulfjack80e703a2020-01-22 06:51:30 -080073 private Path getOutputManifest() {
74 return symlinkTreeRoot.getChild("MANIFEST");
Lukacs Berki31b059f2016-08-04 11:55:20 +000075 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076
ulfjack80e703a2020-01-22 06:51:30 -080077 /** Creates a symlink tree by making VFS calls. */
ulfjack112d41f2019-11-28 00:33:14 -080078 public void createSymlinksDirectly(Path symlinkTreeRoot, Map<PathFragment, Artifact> symlinks)
79 throws IOException {
80 Preconditions.checkState(!filesetTree);
ulfjack80e703a2020-01-22 06:51:30 -080081 // Our strategy is to minimize mutating file system operations as much as possible. Ideally, if
82 // there is an existing symlink tree with the expected contents, we don't make any changes. Our
83 // algorithm goes as follows:
84 //
85 // 1. Create a tree structure that represents the entire set of paths that we want to exist. The
86 // tree structure contains a node for every intermediate directory. For example, this is the
87 // tree structure corresponding to the symlinks {"a/b/c": "foobar", "a/d/e": null}:
88 //
89 // / b - c (symlink to "foobar")
90 // a
91 // \ d - e (empty file)
92 //
93 // Note that we need to distinguish directories, symlinks, and empty files. In the Directory
94 // class below, we use two maps for that purpose: one for directories, and one for symlinks
95 // and empty files. This avoids having to create additional classes / objects to distinguish
96 // them.
97 //
98 // 2. Perform a depth-first traversal over the on-disk file system tree and make each directory
99 // match our expected directory layout. To that end, call readdir, and compare the result
100 // with the contents of the corresponding node in the in-memory tree.
101 //
102 // For each Dirent entry in the readdir result:
103 // - If the entry is not in the current node, if the entry has an incompatible type, or if it
104 // is a symlink that points to the wrong location, delete the entry on disk (recursively).
105 // - Otherwise:
106 // - If the entry is a directory, recurse into that directory
107 // - In all cases, delete the entry in the current in-memory node.
108 //
109 // 3. For every remaining entry in the node, create the corresponding file, symlink, or
110 // directory on disk. If it is a directory, recurse into that directory.
111 Directory root = new Directory();
112 for (Map.Entry<PathFragment, Artifact> entry : symlinks.entrySet()) {
113 // This creates intermediate directory nodes as a side effect.
114 Directory parentDir = root.walk(entry.getKey().getParentDirectory());
115 parentDir.addSymlink(entry.getKey().getBaseName(), entry.getValue());
ulfjack112d41f2019-11-28 00:33:14 -0800116 }
ulfjack80e703a2020-01-22 06:51:30 -0800117 root.syncTreeRecursively(symlinkTreeRoot);
ulfjack112d41f2019-11-28 00:33:14 -0800118 }
119
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 /**
buchgr1c85fa02019-08-29 02:31:38 -0700121 * Creates symlink tree and output manifest using the {@code build-runfiles.cc} tool.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122 *
buchgr1c85fa02019-08-29 02:31:38 -0700123 * @param enableRunfiles If {@code false} only the output manifest is created.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100124 */
Googlere854c862018-05-18 05:15:04 -0700125 public void createSymlinks(
buchgr1c85fa02019-08-29 02:31:38 -0700126 Path execRoot,
127 OutErr outErr,
Dmitry Lomovdfe2f102016-02-12 14:41:05 +0000128 BinTools binTools,
ulfjack06760b72019-11-18 05:08:20 -0800129 Map<String, String> shellEnvironment,
Dmitry Lomova148b4c2016-06-21 12:04:34 +0000130 boolean enableRunfiles)
janakrca6d7ac2020-08-18 07:42:05 -0700131 throws ExecException, InterruptedException {
Lukacs Berki31b059f2016-08-04 11:55:20 +0000132 if (enableRunfiles) {
ulfjack34328d52019-11-19 06:13:48 -0800133 createSymlinksUsingCommand(execRoot, binTools, shellEnvironment, outErr);
Lukacs Berki31b059f2016-08-04 11:55:20 +0000134 } else {
ulfjack34328d52019-11-19 06:13:48 -0800135 copyManifest();
136 }
137 }
138
139 /** Copies the input manifest to the output manifest. */
140 public void copyManifest() throws ExecException {
141 // Pretend we created the runfiles tree by copying the manifest
142 try {
143 symlinkTreeRoot.createDirectoryAndParents();
ulfjack80e703a2020-01-22 06:51:30 -0800144 FileSystemUtils.copyFile(inputManifest, getOutputManifest());
ulfjack34328d52019-11-19 06:13:48 -0800145 } catch (IOException e) {
mschaller45576672020-06-10 19:15:07 -0700146 throw new EnvironmentalExecException(e, Code.SYMLINK_TREE_MANIFEST_COPY_IO_EXCEPTION);
ulfjack34328d52019-11-19 06:13:48 -0800147 }
148 }
149
150 /**
151 * Creates a symlink tree using a CommandBuilder. This means that the symlink tree will always be
152 * present on the developer's workstation. Useful when running commands locally.
153 *
154 * <p>Warning: this method REALLY executes the command on the box Bazel is running on, without any
155 * kind of synchronization, locking, or anything else.
156 */
157 public void createSymlinksUsingCommand(
158 Path execRoot, BinTools binTools, Map<String, String> shellEnvironment, OutErr outErr)
janakrca6d7ac2020-08-18 07:42:05 -0700159 throws EnvironmentalExecException, InterruptedException {
ulfjack34328d52019-11-19 06:13:48 -0800160 Command command = createCommand(execRoot, binTools, shellEnvironment);
161 try {
162 if (outErr != null) {
163 command.execute(outErr.getOutputStream(), outErr.getErrorStream());
164 } else {
165 command.execute();
Lukacs Berki31b059f2016-08-04 11:55:20 +0000166 }
ulfjack34328d52019-11-19 06:13:48 -0800167 } catch (CommandException e) {
mschaller1eabf522020-06-10 22:03:13 -0700168 throw new EnvironmentalExecException(
169 e,
170 FailureDetail.newBuilder()
171 .setMessage(CommandUtils.describeCommandFailure(true, e))
172 .setExecution(
173 Execution.newBuilder().setCode(Code.SYMLINK_TREE_CREATION_COMMAND_EXCEPTION))
174 .build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100175 }
176 }
177
ulfjack4d7f8f72017-11-29 03:37:04 -0800178 @VisibleForTesting
ulfjack06760b72019-11-18 05:08:20 -0800179 Command createCommand(Path execRoot, BinTools binTools, Map<String, String> shellEnvironment) {
Benjamin Peterson693e96e2019-04-04 02:42:14 -0700180 Preconditions.checkNotNull(shellEnvironment);
cparsons83581482018-04-16 11:49:24 -0700181 List<String> args = Lists.newArrayList();
Benjamin Peterson693e96e2019-04-04 02:42:14 -0700182 args.add(binTools.getEmbeddedPath(BUILD_RUNFILES).asFragment().getPathString());
Fabian Meumertzheimd8349052022-09-16 04:29:33 -0700183 args.add("--allow_relative");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100184 if (filesetTree) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 args.add("--use_metadata");
186 }
Dmitry Lomove36a66c2017-02-17 14:48:48 +0000187 args.add(inputManifest.relativeTo(execRoot).getPathString());
188 args.add(symlinkTreeRoot.relativeTo(execRoot).getPathString());
Benjamin Peterson693e96e2019-04-04 02:42:14 -0700189 return new CommandBuilder()
190 .addArgs(args)
191 .setWorkingDir(execRoot)
192 .setEnv(shellEnvironment)
193 .build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 }
ulfjack1a80bb82019-11-26 07:52:35 -0800195
fellya0e7b672020-01-29 11:31:07 -0800196 static ImmutableMap<PathFragment, PathFragment> processFilesetLinks(
197 ImmutableList<FilesetOutputSymlink> links, PathFragment root, PathFragment execRoot) {
198 Map<PathFragment, PathFragment> symlinks = new HashMap<>();
199 for (FilesetOutputSymlink symlink : links) {
200 symlinks.put(root.getRelative(symlink.getName()), symlink.reconstituteTargetPath(execRoot));
ulfjack1a80bb82019-11-26 07:52:35 -0800201 }
fellya0e7b672020-01-29 11:31:07 -0800202 return ImmutableMap.copyOf(symlinks);
ulfjack1a80bb82019-11-26 07:52:35 -0800203 }
ulfjack80e703a2020-01-22 06:51:30 -0800204
205 private static final class Directory {
206 private final Map<String, Artifact> symlinks = new HashMap<>();
207 private final Map<String, Directory> directories = new HashMap<>();
208
209 void addSymlink(String basename, @Nullable Artifact artifact) {
210 symlinks.put(basename, artifact);
211 }
212
213 Directory walk(PathFragment dir) {
214 Directory result = this;
jhorvitz60347952021-02-18 10:05:05 -0800215 for (String segment : dir.segments()) {
216 result = result.directories.computeIfAbsent(segment, unused -> new Directory());
ulfjack80e703a2020-01-22 06:51:30 -0800217 }
218 return result;
219 }
220
221 void syncTreeRecursively(Path at) throws IOException {
222 // This is a reimplementation of the C++ code in build-runfiles.cc. This avoids having to ship
223 // a separate native tool to create a few runfiles.
224 // TODO(ulfjack): Measure performance.
225 FileStatus stat = at.statNullable(Symlinks.FOLLOW);
226 if (stat == null) {
227 at.createDirectoryAndParents();
228 } else if (!stat.isDirectory()) {
229 at.deleteTree();
230 at.createDirectoryAndParents();
231 }
232 // TODO(ulfjack): provide the mode bits from FileStatus and use that to construct the correct
233 // chmod call here. Note that we do not have any tests for this right now. Something like
234 // this:
235 // if (!stat.isExecutable() || !stat.isReadable()) {
236 // at.chmod(stat.getMods() | 0700);
237 // }
238 for (Dirent dirent : at.readdir(Symlinks.FOLLOW)) {
239 String basename = dirent.getName();
240 Path next = at.getChild(basename);
241 if (symlinks.containsKey(basename)) {
242 Artifact value = symlinks.remove(basename);
243 if (value == null) {
244 if (dirent.getType() != Dirent.Type.FILE) {
245 next.deleteTree();
246 FileSystemUtils.createEmptyFile(next);
247 }
248 // For consistency with build-runfiles.cc, we don't truncate the file if one exists.
249 } else {
250 // TODO(ulfjack): On Windows, this call makes a copy rather than creating a symlink.
251 FileSystemUtils.ensureSymbolicLink(next, value.getPath().asFragment());
252 }
253 } else if (directories.containsKey(basename)) {
254 Directory nextDir = directories.remove(basename);
255 if (dirent.getType() != Dirent.Type.DIRECTORY) {
256 next.deleteTree();
257 }
258 nextDir.syncTreeRecursively(at.getChild(basename));
259 } else {
260 at.getChild(basename).deleteTree();
261 }
262 }
263
264 for (Map.Entry<String, Artifact> entry : symlinks.entrySet()) {
265 Path next = at.getChild(entry.getKey());
266 if (entry.getValue() == null) {
267 FileSystemUtils.createEmptyFile(next);
268 } else {
269 FileSystemUtils.ensureSymbolicLink(next, entry.getValue().getPath().asFragment());
270 }
271 }
272 for (Map.Entry<String, Directory> entry : directories.entrySet()) {
273 entry.getValue().syncTreeRecursively(at.getChild(entry.getKey()));
274 }
275 }
276 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100277}