Skip to content

Commit 6b7508f

Browse files
authored
[9.0.0] Centralize more logic in SymlinkTreeHelper. (bazelbuild#27677)
Also make sure that the output manifest is always created last, as it attests to the symlink tree having been updated. PiperOrigin-RevId: 831838700 Change-Id: Iddd95f170dd27f3a4254fbfccf5ef649c424d469
1 parent 97e96f4 commit 6b7508f

File tree

3 files changed

+41
-76
lines changed

3 files changed

+41
-76
lines changed

src/main/java/com/google/devtools/build/lib/exec/RunfilesTreeUpdater.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,12 @@ private void updateRunfilesTree(RunfilesTree tree) throws IOException, ExecExcep
145145
SymlinkTreeHelper helper =
146146
new SymlinkTreeHelper(inputManifest, outputManifest, runfilesDir, tree.getWorkspaceName());
147147

148-
if (tree.getSymlinksMode() == RunfileSymlinksMode.CREATE) {
149-
helper.createRunfilesSymlinks(tree.getMapping());
150-
outputManifest.createSymbolicLink(inputManifest);
151-
} else {
152-
helper.clearRunfilesDirectory();
148+
switch (tree.getSymlinksMode()) {
149+
case CREATE -> {
150+
helper.createRunfilesSymlinks(tree.getMapping());
151+
helper.linkManifest();
152+
}
153+
case SKIP -> helper.createMinimalRunfilesDirectory();
153154
}
154155
}
155156

src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,13 @@ interface TargetPathFunction<T> {
7979
}
8080

8181
/** Creates a symlink tree for a fileset by making VFS calls. */
82-
public void createFilesetSymlinks(Map<PathFragment, PathFragment> symlinkMap) throws IOException {
82+
public void createFilesetSymlinks(Map<PathFragment, PathFragment> symlinkMap)
83+
throws ExecException {
8384
createSymlinks(symlinkMap, (path) -> path);
8485
}
8586

8687
/** Creates a symlink tree for a runfiles by making VFS calls. */
87-
public void createRunfilesSymlinks(Map<PathFragment, Artifact> symlinks) throws IOException {
88+
public void createRunfilesSymlinks(Map<PathFragment, Artifact> symlinks) throws ExecException {
8889
createSymlinks(
8990
symlinks,
9091
(artifact) ->
@@ -96,7 +97,7 @@ public void createRunfilesSymlinks(Map<PathFragment, Artifact> symlinks) throws
9697

9798
/** Creates a symlink tree. */
9899
private <T> void createSymlinks(
99-
Map<PathFragment, T> symlinkMap, TargetPathFunction<T> targetPathFn) throws IOException {
100+
Map<PathFragment, T> symlinkMap, TargetPathFunction<T> targetPathFn) throws ExecException {
100101
// Our strategy is to minimize mutating file system operations as much as possible. Ideally, if
101102
// there is an existing symlink tree with the expected contents, we don't make any changes. Our
102103
// algorithm goes as follows:
@@ -139,25 +140,23 @@ private <T> void createSymlinks(
139140
}
140141
root.syncTreeRecursively(symlinkTreeRoot, targetPathFn);
141142
createWorkspaceSubdirectory();
143+
} catch (IOException e) {
144+
throw new EnvironmentalExecException(e, Code.SYMLINK_TREE_CREATION_IO_EXCEPTION);
142145
}
143146
}
144147

145148
/**
146-
* Ensures that the runfiles directory is empty except for the symlinked MANIFEST and the
147-
* workspace subdirectory. This is the expected state with --noenable_runfiles.
149+
* Creates a minimal runfiles directory containing only the output manifest and the workspace
150+
* subdirectory. This is the expected state with --noenable_runfiles or --nobuild_runfile_links.
148151
*/
149-
public void clearRunfilesDirectory() throws ExecException {
150-
deleteRunfilesDirectory();
152+
public void createMinimalRunfilesDirectory() throws ExecException {
153+
clearRunfilesDirectory();
151154
linkManifest();
152-
try {
153-
createWorkspaceSubdirectory();
154-
} catch (IOException e) {
155-
throw new EnvironmentalExecException(e, Code.SYMLINK_TREE_CREATION_IO_EXCEPTION);
156-
}
155+
createWorkspaceSubdirectory();
157156
}
158157

159158
/** Deletes the contents of the runfiles directory. */
160-
private void deleteRunfilesDirectory() throws ExecException {
159+
private void clearRunfilesDirectory() throws ExecException {
161160
try (SilentCloseable c = Profiler.instance().profile("Clear symlink tree")) {
162161
symlinkTreeRoot.deleteTreesBelow();
163162
} catch (IOException e) {
@@ -166,23 +165,26 @@ private void deleteRunfilesDirectory() throws ExecException {
166165
}
167166

168167
/** Links the output manifest to the input manifest. */
169-
private void linkManifest() throws ExecException {
170-
// Pretend we created the runfiles tree by symlinking the output manifest to the input manifest.
168+
public void linkManifest() throws ExecException {
169+
// TODO(b/443703909): This should probably be a copy, not a symlink.
171170
try {
172-
symlinkTreeRoot.createDirectoryAndParents();
173171
outputManifest.delete();
174172
outputManifest.createSymbolicLink(inputManifest);
175173
} catch (IOException e) {
176174
throw new EnvironmentalExecException(e, Code.SYMLINK_TREE_MANIFEST_LINK_IO_EXCEPTION);
177175
}
178176
}
179177

180-
private void createWorkspaceSubdirectory() throws IOException {
178+
public void createWorkspaceSubdirectory() throws ExecException {
181179
// Always create the subdirectory corresponding to the workspace (i.e., the main repository).
182180
// This is required by tests as their working directory, even with --noenable_runfiles. But if
183181
// the test action creates the directory and then proceeds to execute the test spawn, this logic
184182
// would remove it. For the sake of consistency, always create the directory instead.
185-
symlinkTreeRoot.getRelative(workspaceName).createDirectory();
183+
try {
184+
symlinkTreeRoot.getRelative(workspaceName).createDirectory();
185+
} catch (IOException e) {
186+
throw new EnvironmentalExecException(e, Code.SYMLINK_TREE_CREATION_IO_EXCEPTION);
187+
}
186188
}
187189

188190
/**

src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import com.google.devtools.build.lib.actions.ActionExecutionContext;
2222
import com.google.devtools.build.lib.actions.ActionExecutionException;
2323
import com.google.devtools.build.lib.actions.Artifact;
24-
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
2524
import com.google.devtools.build.lib.actions.ExecException;
2625
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
2726
import com.google.devtools.build.lib.actions.RunningActionEvent;
@@ -30,13 +29,8 @@
3029
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue.RunfileSymlinksMode;
3130
import com.google.devtools.build.lib.profiler.AutoProfiler;
3231
import com.google.devtools.build.lib.profiler.GoogleAutoProfilerUtils;
33-
import com.google.devtools.build.lib.server.FailureDetails.Execution;
34-
import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
35-
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
3632
import com.google.devtools.build.lib.vfs.OutputService;
37-
import com.google.devtools.build.lib.vfs.Path;
3833
import com.google.devtools.build.lib.vfs.PathFragment;
39-
import java.io.IOException;
4034
import java.time.Duration;
4135
import java.util.Map;
4236

@@ -66,11 +60,13 @@ public void createSymlinks(
6660
actionExecutionContext.getEventHandler().post(new RunningActionEvent(action, "local"));
6761
try (AutoProfiler p =
6862
GoogleAutoProfilerUtils.logged("running " + action.prettyPrint(), MIN_LOGGING)) {
63+
SymlinkTreeHelper helper = createSymlinkTreeHelper(action, actionExecutionContext);
6964
// TODO(tjgq): Respect RunfileSymlinksMode.SKIP even in the presence of an OutputService.
7065
try {
66+
// Note that the output manifest must always be created last, as its presence ascertains
67+
// that the runfiles tree has been updated (only the output manifest is an action output,
68+
// so Skyframe cannot invalidate the symlink tree).
7169
if (outputService.canCreateSymlinkTree()) {
72-
Path inputManifest = actionExecutionContext.getInputPath(action.getInputManifest());
73-
7470
Map<PathFragment, PathFragment> symlinks;
7571
if (action.isFilesetTree()) {
7672
symlinks = getFilesetMap(action, actionExecutionContext);
@@ -79,31 +75,22 @@ public void createSymlinks(
7975
// created textually.
8076
symlinks = Maps.transformValues(getRunfilesMap(action), TO_PATH);
8177
}
82-
8378
outputService.createSymlinkTree(
8479
symlinks, action.getOutputManifest().getExecPath().getParentDirectory());
85-
86-
createOutput(action, actionExecutionContext, inputManifest);
80+
helper.linkManifest();
8781
} else if (action.getRunfileSymlinksMode() == RunfileSymlinksMode.SKIP) {
88-
// Delete symlinks possibly left over by a previous invocation with a different mode.
89-
// This is required because only the output manifest is considered an action output, so
90-
// Skyframe does not clear the directory for us.
91-
createSymlinkTreeHelper(action, actionExecutionContext).clearRunfilesDirectory();
82+
// Clear the runfiles directory, then create just the output manifest and the workspace
83+
// subdirectory. This is required because only the output manifest is considered an action
84+
// output, so if the previous invocation created a symlink tree, Skyframe will not clear
85+
// it for us.
86+
helper.createMinimalRunfilesDirectory();
9287
} else {
93-
try {
94-
SymlinkTreeHelper helper = createSymlinkTreeHelper(action, actionExecutionContext);
95-
if (action.isFilesetTree()) {
96-
helper.createFilesetSymlinks(getFilesetMap(action, actionExecutionContext));
97-
} else {
98-
helper.createRunfilesSymlinks(getRunfilesMap(action));
99-
}
100-
} catch (IOException e) {
101-
throw ActionExecutionException.fromExecException(
102-
new EnvironmentalExecException(e, Code.SYMLINK_TREE_CREATION_IO_EXCEPTION), action);
88+
if (action.isFilesetTree()) {
89+
helper.createFilesetSymlinks(getFilesetMap(action, actionExecutionContext));
90+
} else {
91+
helper.createRunfilesSymlinks(getRunfilesMap(action));
10392
}
104-
105-
Path inputManifest = actionExecutionContext.getInputPath(action.getInputManifest());
106-
createOutput(action, actionExecutionContext, inputManifest);
93+
helper.linkManifest();
10794
}
10895
} catch (ExecException e) {
10996
throw ActionExecutionException.fromExecException(e, action);
@@ -127,20 +114,6 @@ private static Map<PathFragment, Artifact> getRunfilesMap(SymlinkTreeAction acti
127114
return action.getRunfiles().getRunfilesInputs(action.getRepoMappingManifest());
128115
}
129116

130-
private static void createOutput(
131-
SymlinkTreeAction action, ActionExecutionContext actionExecutionContext, Path inputManifest)
132-
throws EnvironmentalExecException {
133-
Path outputManifest = actionExecutionContext.getInputPath(action.getOutputManifest());
134-
// Link output manifest on success. We avoid a file copy as these manifests may be
135-
// large. Note that this step has to come last because the OutputService may delete any
136-
// pre-existing symlink tree before creating a new one.
137-
try {
138-
outputManifest.createSymbolicLink(inputManifest);
139-
} catch (IOException e) {
140-
throw createLinkFailureException(outputManifest, e);
141-
}
142-
}
143-
144117
private SymlinkTreeHelper createSymlinkTreeHelper(
145118
SymlinkTreeAction action, ActionExecutionContext actionExecutionContext) {
146119
return new SymlinkTreeHelper(
@@ -149,15 +122,4 @@ private SymlinkTreeHelper createSymlinkTreeHelper(
149122
actionExecutionContext.getInputPath(action.getOutputManifest()).getParentDirectory(),
150123
workspaceName);
151124
}
152-
153-
private static EnvironmentalExecException createLinkFailureException(
154-
Path outputManifest, IOException e) {
155-
return new EnvironmentalExecException(
156-
e,
157-
FailureDetail.newBuilder()
158-
.setMessage("Failed to link output manifest '" + outputManifest.getPathString() + "'")
159-
.setExecution(
160-
Execution.newBuilder().setCode(Code.SYMLINK_TREE_MANIFEST_LINK_IO_EXCEPTION))
161-
.build());
162-
}
163125
}

0 commit comments

Comments
 (0)