Skip to content

Commit 8818a57

Browse files
coeuvreShreeM01
andauthored
[6.0.0] Only inject metadata for outputs that cannot be reconstructed by skyf… (bazelbuild#16879)
* Update RemoteActionFileSystem to apply permission changes to local files even if remote file exists. Previously, it only applies permission changes to local files when the remote one doesn't exist. It is fine because the call sites only use these method when remote file are missing. However, this isn't true with future changes. PiperOrigin-RevId: 490872822 Change-Id: I7a19d99cd828294cbafa7b5f3fdc368d64e556ec * Fix permission operations for the case of only remote directory. PiperOrigin-RevId: 491280334 Change-Id: I30afef9f069eca8aee4d983664f42b3961e95adf * Only inject metadata for outputs that cannot be reconstructed by skyframe later Currently, they are symlinks. For other outputs, let skyframe read action file system to construct the metadata. Before this change, we inject metadata of symlink outputs, tree outputs and file outputs inside `RemoteActionFileSystem#flush()` if these outputs are stored inside the in-memory fs. If the outputs are somehow generated in the local fs, skyframe will read the fs later to construct metadata for them. However, for tree outputs, skyframe always create an empty directory before action execution in the in-memory fs. So inside the `flush`, we always inject metadata for it. It means local tree outputs are ignored. We could update the code to also read local file system when reading tree files, but then the problem is how to construct metadata from file status which is well done by skyframe. So instead of injecting metadata by traversal the filesystem inside `flush`, we just let skyframe to do the job. Fixes bazelbuild#16789. Closes bazelbuild#16812. PiperOrigin-RevId: 491622005 Change-Id: I10434e6856a1b2a207f39e07122a9b646edf518c Co-authored-by: kshyanashree <[email protected]>
1 parent b471bbd commit 8818a57

File tree

6 files changed

+640
-64
lines changed

6 files changed

+640
-64
lines changed

src/main/java/com/google/devtools/build/lib/actions/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ java_library(
5757
"ResourceManager.java",
5858
"ResourceSet.java",
5959
"ResourceSetOrBuilder.java",
60+
"RemoteFileStatus.java",
6061
"PackageRootResolver.java",
6162
"PackageRoots.java",
6263
"ThreadStateReceiver.java",
@@ -263,6 +264,7 @@ java_library(
263264
"FileStateType.java",
264265
"FileStateValue.java",
265266
"FileValue.java",
267+
"RemoteFileStatus.java",
266268
"cache/MetadataDigestUtils.java",
267269
],
268270
deps = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
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.
14+
package com.google.devtools.build.lib.actions;
15+
16+
import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
17+
import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
18+
19+
/**
20+
* A FileStatus that exists remotely and provides remote metadata.
21+
*
22+
* <p>Filesystem may return implementation of this interface if the files are stored remotely. When
23+
* checking outputs of actions, Skyframe will inject the metadata returned by {@link
24+
* #getRemoteMetadata()} if the output file has {@link RemoteFileStatus}.
25+
*/
26+
public interface RemoteFileStatus extends FileStatusWithDigest {
27+
RemoteFileArtifactValue getRemoteMetadata();
28+
}

src/main/java/com/google/devtools/build/lib/remote/RemoteActionFileSystem.java

+64-60
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
3131
import com.google.devtools.build.lib.actions.FileArtifactValue;
3232
import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
33+
import com.google.devtools.build.lib.actions.RemoteFileStatus;
3334
import com.google.devtools.build.lib.actions.cache.MetadataInjector;
3435
import com.google.devtools.build.lib.clock.Clock;
3536
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
@@ -128,41 +129,7 @@ void flush() throws IOException {
128129
PathFragment path = execRoot.getRelative(entry.getKey());
129130
Artifact output = entry.getValue();
130131

131-
if (maybeInjectMetadataForSymlink(path, output)) {
132-
continue;
133-
}
134-
135-
if (output.isTreeArtifact()) {
136-
if (remoteOutputTree.exists(path)) {
137-
SpecialArtifact parent = (SpecialArtifact) output;
138-
TreeArtifactValue.Builder tree = TreeArtifactValue.newBuilder(parent);
139-
140-
// TODO: Check directory content on the local fs to support mixed tree.
141-
TreeArtifactValue.visitTree(
142-
remoteOutputTree.getPath(path),
143-
(parentRelativePath, type) -> {
144-
if (type == Dirent.Type.DIRECTORY) {
145-
return;
146-
}
147-
RemoteFileInfo remoteFile =
148-
remoteOutputTree.getRemoteFileInfo(
149-
path.getRelative(parentRelativePath), /* followSymlinks= */ true);
150-
if (remoteFile != null) {
151-
TreeFileArtifact child =
152-
TreeFileArtifact.createTreeOutput(parent, parentRelativePath);
153-
tree.putChild(child, createRemoteMetadata(remoteFile));
154-
}
155-
});
156-
157-
metadataInjector.injectTree(parent, tree.build());
158-
}
159-
} else {
160-
RemoteFileInfo remoteFile =
161-
remoteOutputTree.getRemoteFileInfo(path, /* followSymlinks= */ true);
162-
if (remoteFile != null) {
163-
metadataInjector.injectFile(output, createRemoteMetadata(remoteFile));
164-
}
165-
}
132+
maybeInjectMetadataForSymlink(path, output);
166133
}
167134
}
168135

@@ -183,26 +150,24 @@ void flush() throws IOException {
183150
* the output should be materialized as a symlink to the original location, which avoids
184151
* fetching multiple copies when multiple symlinks to the same artifact are created in the
185152
* same build.
186-
*
187-
* @return Whether the metadata was injected.
188153
*/
189-
private boolean maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact output)
154+
private void maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact output)
190155
throws IOException {
191156
if (output.isSymlink()) {
192-
return false;
157+
return;
193158
}
194159

195160
Path outputTreePath = remoteOutputTree.getPath(linkPath);
196161

197162
if (!outputTreePath.exists(Symlinks.NOFOLLOW)) {
198-
return false;
163+
return;
199164
}
200165

201166
PathFragment targetPath;
202167
try {
203168
targetPath = outputTreePath.readSymbolicLink();
204169
} catch (NotASymlinkException e) {
205-
return false;
170+
return;
206171
}
207172

208173
checkState(
@@ -212,7 +177,7 @@ private boolean maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact ou
212177
if (output.isTreeArtifact()) {
213178
TreeArtifactValue metadata = getRemoteTreeMetadata(targetPath);
214179
if (metadata == null) {
215-
return false;
180+
return;
216181
}
217182

218183
SpecialArtifact parent = (SpecialArtifact) output;
@@ -233,7 +198,7 @@ private boolean maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact ou
233198
} else {
234199
RemoteFileArtifactValue metadata = getRemoteMetadata(targetPath);
235200
if (metadata == null) {
236-
return false;
201+
return;
237202
}
238203

239204
RemoteFileArtifactValue injectedMetadata =
@@ -247,8 +212,6 @@ private boolean maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact ou
247212

248213
metadataInjector.injectFile(output, injectedMetadata);
249214
}
250-
251-
return true;
252215
}
253216

254217
private RemoteFileArtifactValue createRemoteMetadata(RemoteFileInfo remoteFile) {
@@ -313,54 +276,85 @@ protected byte[] getDigest(PathFragment path) throws IOException {
313276
}
314277

315278
// -------------------- File Permissions --------------------
279+
// Remote files are always readable, writable and executable since we can't control their
280+
// permissions.
281+
282+
private boolean existsInMemory(PathFragment path) {
283+
return remoteOutputTree.getPath(path).isDirectory() || getRemoteMetadata(path) != null;
284+
}
316285

317286
@Override
318287
protected boolean isReadable(PathFragment path) throws IOException {
319-
FileArtifactValue m = getRemoteMetadata(path);
320-
return m != null || super.isReadable(path);
288+
return existsInMemory(path) || super.isReadable(path);
321289
}
322290

323291
@Override
324292
protected boolean isWritable(PathFragment path) throws IOException {
325-
FileArtifactValue m = getRemoteMetadata(path);
326-
return m != null || super.isWritable(path);
293+
if (existsInMemory(path)) {
294+
// If path exists locally, also check whether it's writable. We need this check for the case
295+
// where the action need to delete their local outputs but the parent directory is not
296+
// writable.
297+
try {
298+
return super.isWritable(path);
299+
} catch (FileNotFoundException e) {
300+
// Intentionally ignored
301+
return true;
302+
}
303+
}
304+
305+
return super.isWritable(path);
327306
}
328307

329308
@Override
330309
protected boolean isExecutable(PathFragment path) throws IOException {
331-
FileArtifactValue m = getRemoteMetadata(path);
332-
return m != null || super.isExecutable(path);
310+
return existsInMemory(path) || super.isExecutable(path);
333311
}
334312

335313
@Override
336314
protected void setReadable(PathFragment path, boolean readable) throws IOException {
337-
RemoteFileArtifactValue m = getRemoteMetadata(path);
338-
if (m == null) {
315+
try {
339316
super.setReadable(path, readable);
317+
} catch (FileNotFoundException e) {
318+
// in case of missing in-memory path, re-throw the error.
319+
if (!existsInMemory(path)) {
320+
throw e;
321+
}
340322
}
341323
}
342324

343325
@Override
344326
public void setWritable(PathFragment path, boolean writable) throws IOException {
345-
RemoteFileArtifactValue m = getRemoteMetadata(path);
346-
if (m == null) {
327+
try {
347328
super.setWritable(path, writable);
329+
} catch (FileNotFoundException e) {
330+
// in case of missing in-memory path, re-throw the error.
331+
if (!existsInMemory(path)) {
332+
throw e;
333+
}
348334
}
349335
}
350336

351337
@Override
352338
protected void setExecutable(PathFragment path, boolean executable) throws IOException {
353-
RemoteFileArtifactValue m = getRemoteMetadata(path);
354-
if (m == null) {
339+
try {
355340
super.setExecutable(path, executable);
341+
} catch (FileNotFoundException e) {
342+
// in case of missing in-memory path, re-throw the error.
343+
if (!existsInMemory(path)) {
344+
throw e;
345+
}
356346
}
357347
}
358348

359349
@Override
360350
protected void chmod(PathFragment path, int mode) throws IOException {
361-
RemoteFileArtifactValue m = getRemoteMetadata(path);
362-
if (m == null) {
351+
try {
363352
super.chmod(path, mode);
353+
} catch (FileNotFoundException e) {
354+
// in case of missing in-memory path, re-throw the error.
355+
if (!existsInMemory(path)) {
356+
throw e;
357+
}
364358
}
365359
}
366360

@@ -467,7 +461,12 @@ protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOEx
467461
}
468462

469463
private static FileStatus statFromRemoteMetadata(RemoteFileArtifactValue m) {
470-
return new FileStatus() {
464+
return new RemoteFileStatus() {
465+
@Override
466+
public byte[] getDigest() {
467+
return m.getDigest();
468+
}
469+
471470
@Override
472471
public boolean isFile() {
473472
return m.getType().isFile();
@@ -507,6 +506,11 @@ public long getLastChangeTime() {
507506
public long getNodeId() {
508507
throw new UnsupportedOperationException("Cannot get node id for " + m);
509508
}
509+
510+
@Override
511+
public RemoteFileArtifactValue getRemoteMetadata() {
512+
return m;
513+
}
510514
};
511515
}
512516

src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java

+11-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.google.devtools.build.lib.actions.FilesetManifest;
3939
import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehaviorWithoutError;
4040
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
41+
import com.google.devtools.build.lib.actions.RemoteFileStatus;
4142
import com.google.devtools.build.lib.actions.cache.MetadataHandler;
4243
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
4344
import com.google.devtools.build.lib.vfs.DigestUtils;
@@ -656,14 +657,20 @@ private static FileArtifactValue fileArtifactValueFromStat(
656657
return FileArtifactValue.MISSING_FILE_MARKER;
657658
}
658659

660+
if (stat.isDirectory()) {
661+
return FileArtifactValue.createForDirectoryWithMtime(stat.getLastModifiedTime());
662+
}
663+
664+
if (stat instanceof RemoteFileStatus) {
665+
return ((RemoteFileStatus) stat).getRemoteMetadata();
666+
}
667+
659668
FileStateValue fileStateValue =
660669
FileStateValue.createWithStatNoFollow(
661670
rootedPath, stat, digestWillBeInjected, xattrProvider, tsgm);
662671

663-
return stat.isDirectory()
664-
? FileArtifactValue.createForDirectoryWithMtime(stat.getLastModifiedTime())
665-
: FileArtifactValue.createForNormalFile(
666-
fileStateValue.getDigest(), fileStateValue.getContentsProxy(), stat.getSize());
672+
return FileArtifactValue.createForNormalFile(
673+
fileStateValue.getDigest(), fileStateValue.getContentsProxy(), stat.getSize());
667674
}
668675

669676
private static void setPathReadOnlyAndExecutableIfFile(Path path) throws IOException {

src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java

+37
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,42 @@ public void downloadToplevel_treeArtifacts() throws Exception {
378378
assertValidOutputFile("foo/file-3", "file-3\n");
379379
}
380380

381+
@Test
382+
public void treeOutputsFromLocalFileSystem_works() throws Exception {
383+
// Disable on Windows since it fails for unknown reasons.
384+
// TODO(chiwang): Enable it on windows.
385+
if (OS.getCurrent() == OS.WINDOWS) {
386+
return;
387+
}
388+
389+
// Test that tree artifact generated locally can be consumed by other actions.
390+
// See https://github.com/bazelbuild/bazel/issues/16789
391+
392+
// Disable remote execution so tree outputs are generated locally
393+
addOptions("--modify_execution_info=OutputDir=+no-remote-exec");
394+
setDownloadToplevel();
395+
writeOutputDirRule();
396+
write(
397+
"BUILD",
398+
"load(':output_dir.bzl', 'output_dir')",
399+
"output_dir(",
400+
" name = 'foo',",
401+
" manifest = ':manifest',",
402+
")",
403+
"genrule(",
404+
" name = 'foobar',",
405+
" srcs = [':foo'],",
406+
" outs = ['out/foobar.txt'],",
407+
" cmd = 'cat $(location :foo)/file-1 > $@ && echo bar >> $@',",
408+
")");
409+
write("manifest", "file-1");
410+
411+
buildTarget("//:foobar");
412+
waitDownloads();
413+
414+
assertValidOutputFile("out/foobar.txt", "file-1\nbar\n");
415+
}
416+
381417
@Test
382418
public void incrementalBuild_deleteOutputsInUnwritableParentDirectory() throws Exception {
383419
write(
@@ -513,6 +549,7 @@ protected void writeOutputDirRule() throws IOException {
513549
"def _output_dir_impl(ctx):",
514550
" output_dir = ctx.actions.declare_directory(ctx.attr.name)",
515551
" ctx.actions.run_shell(",
552+
" mnemonic = 'OutputDir',",
516553
" inputs = [ctx.file.manifest],",
517554
" outputs = [output_dir],",
518555
" arguments = [ctx.file.manifest.path, output_dir.path],",

0 commit comments

Comments
 (0)