Skip to content

Commit 292d8d4

Browse files
lfpinoaranguyen
authored andcommitted
Add a flag to force Bazel to download certain artifacts when using --remote_download_minimal
When using --remote_download_minimal Bazel only downloads to the client the minimum number of artifacts necessary for the build to succeed. This can be sometimes problematic when local development (i.e. IDE) requires some artifacts to do local work (e.g. syntax completion). We add a flag --experimental_remote_download_regex that allow specifying a regular expression that will force certain artifacts to be forced to downloaded. Tested locally that syntax errors disappear in IntelliJ when compiling Bazel itself with --remote_download_minimal and --experimental_remote_download_regex=".*jar$", also included an integration test with several cases. Signed-off-by: Luis Fernando Pino Duque <[email protected]> Closes bazelbuild#15638. PiperOrigin-RevId: 460672954 Change-Id: I3a4f18d046d2d91603a276f16173ad2a253480dd
1 parent 32674d2 commit 292d8d4

File tree

3 files changed

+145
-17
lines changed

3 files changed

+145
-17
lines changed

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

+76-17
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@
147147
import java.util.concurrent.Executor;
148148
import java.util.concurrent.Phaser;
149149
import java.util.concurrent.atomic.AtomicBoolean;
150+
import java.util.function.Predicate;
151+
import java.util.regex.Matcher;
152+
import java.util.regex.Pattern;
150153
import javax.annotation.Nullable;
151154

152155
/**
@@ -174,6 +177,8 @@ public class RemoteExecutionService {
174177

175178
private final AtomicBoolean shutdown = new AtomicBoolean(false);
176179
private final AtomicBoolean buildInterrupted = new AtomicBoolean(false);
180+
private final boolean shouldForceDownloads;
181+
private final Predicate<String> shouldForceDownloadPredicate;
177182

178183
public RemoteExecutionService(
179184
Executor executor,
@@ -215,6 +220,20 @@ public RemoteExecutionService(
215220
this.captureCorruptedOutputsDir = captureCorruptedOutputsDir;
216221

217222
this.scheduler = Schedulers.from(executor, /*interruptibleWorker=*/ true);
223+
224+
String regex = remoteOptions.remoteDownloadRegex;
225+
// TODO(bazel-team): Consider adding a warning or more validation if the remoteDownloadRegex is
226+
// used without RemoteOutputsMode.MINIMAL.
227+
this.shouldForceDownloads =
228+
!regex.isEmpty()
229+
&& (remoteOptions.remoteOutputsMode == RemoteOutputsMode.MINIMAL
230+
|| remoteOptions.remoteOutputsMode == RemoteOutputsMode.TOPLEVEL);
231+
Pattern pattern = Pattern.compile(regex);
232+
this.shouldForceDownloadPredicate =
233+
path -> {
234+
Matcher m = pattern.matcher(path);
235+
return m.matches();
236+
};
218237
}
219238

220239
static Command buildCommand(
@@ -1021,24 +1040,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10211040
/* exitCode = */ result.getExitCode(),
10221041
hasFilesToDownload(action.getSpawn().getOutputFiles(), filesToDownload));
10231042

1024-
if (downloadOutputs) {
1025-
HashSet<PathFragment> queuedFilePaths = new HashSet<>();
1026-
1027-
for (FileMetadata file : metadata.files()) {
1028-
PathFragment filePath = file.path().asFragment();
1029-
if (queuedFilePaths.add(filePath)) {
1030-
downloadsBuilder.add(downloadFile(action, file));
1031-
}
1032-
}
1043+
ImmutableList<ListenableFuture<FileMetadata>> forcedDownloads = ImmutableList.of();
10331044

1034-
for (Map.Entry<Path, DirectoryMetadata> entry : metadata.directories()) {
1035-
for (FileMetadata file : entry.getValue().files()) {
1036-
PathFragment filePath = file.path().asFragment();
1037-
if (queuedFilePaths.add(filePath)) {
1038-
downloadsBuilder.add(downloadFile(action, file));
1039-
}
1040-
}
1041-
}
1045+
if (downloadOutputs) {
1046+
downloadsBuilder.addAll(buildFilesToDownload(metadata, action));
10421047
} else {
10431048
checkState(
10441049
result.getExitCode() == 0,
@@ -1049,6 +1054,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10491054
"Symlinks in action outputs are not yet supported by "
10501055
+ "--experimental_remote_download_outputs=minimal");
10511056
}
1057+
if (shouldForceDownloads) {
1058+
forcedDownloads =
1059+
buildFilesToDownloadWithPredicate(metadata, action, shouldForceDownloadPredicate);
1060+
}
10521061
}
10531062

10541063
FileOutErr tmpOutErr = outErr.childOutErr();
@@ -1063,6 +1072,19 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10631072
try (SilentCloseable c = Profiler.instance().profile("Remote.download")) {
10641073
waitForBulkTransfer(downloads, /* cancelRemainingOnInterrupt= */ true);
10651074
} catch (Exception e) {
1075+
// TODO(bazel-team): Consider adding better case-by-case exception handling instead of just
1076+
// rethrowing
1077+
captureCorruptedOutputs(e);
1078+
deletePartialDownloadedOutputs(result, tmpOutErr, e);
1079+
throw e;
1080+
}
1081+
1082+
// TODO(bazel-team): Unify this block with the equivalent block above.
1083+
try (SilentCloseable c = Profiler.instance().profile("Remote.forcedDownload")) {
1084+
waitForBulkTransfer(forcedDownloads, /* cancelRemainingOnInterrupt= */ true);
1085+
} catch (Exception e) {
1086+
// TODO(bazel-team): Consider adding better case-by-case exception handling instead of just
1087+
// rethrowing
10661088
captureCorruptedOutputs(e);
10671089
deletePartialDownloadedOutputs(result, tmpOutErr, e);
10681090
throw e;
@@ -1096,6 +1118,13 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10961118
// might not be supported on all platforms
10971119
createSymlinks(symlinks);
10981120
} else {
1121+
// TODO(bazel-team): We should unify this if-block to rely on downloadOutputs above but, as of
1122+
// 2022-07-05, downloadOuputs' semantics isn't exactly the same as build-without-the-bytes
1123+
// which is necessary for using remoteDownloadRegex.
1124+
if (!forcedDownloads.isEmpty()) {
1125+
moveOutputsToFinalLocation(forcedDownloads);
1126+
}
1127+
10991128
ActionInput inMemoryOutput = null;
11001129
Digest inMemoryOutputDigest = null;
11011130
PathFragment inMemoryOutputPath = getInMemoryOutputPath(action.getSpawn());
@@ -1133,6 +1162,36 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
11331162
return null;
11341163
}
11351164

1165+
private ImmutableList<ListenableFuture<FileMetadata>> buildFilesToDownload(
1166+
ActionResultMetadata metadata, RemoteAction action) {
1167+
Predicate<String> alwaysTrue = unused -> true;
1168+
return buildFilesToDownloadWithPredicate(metadata, action, alwaysTrue);
1169+
}
1170+
1171+
private ImmutableList<ListenableFuture<FileMetadata>> buildFilesToDownloadWithPredicate(
1172+
ActionResultMetadata metadata, RemoteAction action, Predicate<String> predicate) {
1173+
HashSet<PathFragment> queuedFilePaths = new HashSet<>();
1174+
ImmutableList.Builder<ListenableFuture<FileMetadata>> builder = new ImmutableList.Builder<>();
1175+
1176+
for (FileMetadata file : metadata.files()) {
1177+
PathFragment filePath = file.path().asFragment();
1178+
if (queuedFilePaths.add(filePath) && predicate.test(file.path.toString())) {
1179+
builder.add(downloadFile(action, file));
1180+
}
1181+
}
1182+
1183+
for (Map.Entry<Path, DirectoryMetadata> entry : metadata.directories()) {
1184+
for (FileMetadata file : entry.getValue().files()) {
1185+
PathFragment filePath = file.path().asFragment();
1186+
if (queuedFilePaths.add(filePath) && predicate.test(file.path.toString())) {
1187+
builder.add(downloadFile(action, file));
1188+
}
1189+
}
1190+
}
1191+
1192+
return builder.build();
1193+
}
1194+
11361195
private static String prettyPrint(ActionInput actionInput) {
11371196
if (actionInput instanceof Artifact) {
11381197
return ((Artifact) actionInput).prettyPrint();

src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java

+12
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,18 @@ public RemoteOutputsStrategyConverter() {
587587
+ "`all` to print always.")
588588
public ExecutionMessagePrintMode remotePrintExecutionMessages;
589589

590+
@Option(
591+
name = "experimental_remote_download_regex",
592+
defaultValue = "",
593+
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
594+
effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
595+
help =
596+
"Force Bazel to download the artifacts that match the given regexp. To be used in"
597+
+ " conjunction with --remote_download_minimal or --remote_download_toplevel to allow"
598+
+ " the client to request certain artifacts that might be needed locally (e.g. IDE"
599+
+ " support)")
600+
public String remoteDownloadRegex;
601+
590602
// The below options are not configurable by users, only tests.
591603
// This is part of the effort to reduce the overall number of flags.
592604

src/test/shell/bazel/remote/build_without_the_bytes_test.sh

+57
Original file line numberDiff line numberDiff line change
@@ -1354,4 +1354,61 @@ EOF
13541354
expect_log "START.*: \[.*\] Executing genrule //a:dep"
13551355
}
13561356

1357+
function test_remote_download_regex() {
1358+
mkdir -p a
1359+
1360+
cat > a/BUILD <<'EOF'
1361+
java_library(
1362+
name = "lib",
1363+
srcs = ["Library.java"],
1364+
)
1365+
java_test(
1366+
name = "test",
1367+
srcs = ["JavaTest.java"],
1368+
test_class = "JavaTest",
1369+
deps = [":lib"],
1370+
)
1371+
EOF
1372+
1373+
cat > a/Library.java <<'EOF'
1374+
public class Library {
1375+
public static boolean TEST = true;
1376+
}
1377+
EOF
1378+
1379+
cat > a/JavaTest.java <<'EOF'
1380+
import org.junit.Assert;
1381+
import org.junit.Test;
1382+
public class JavaTest {
1383+
@Test
1384+
public void test() { Assert.assertTrue(Library.TEST); }
1385+
}
1386+
EOF
1387+
bazel test \
1388+
--remote_executor=grpc://localhost:${worker_port} \
1389+
--remote_download_minimal \
1390+
//a:test >& $TEST_log || fail "Failed to build"
1391+
1392+
[[ ! -e "bazel-bin/a/liblib.jar" ]] || fail "bazel-bin/a/liblib.jar shouldn't exist"
1393+
[[ ! -e "bazel-bin/a/liblib.jdeps" ]] || fail "bazel-bin/a/liblib.jdeps shouldn't exist"
1394+
1395+
bazel clean && bazel test \
1396+
--remote_executor=grpc://localhost:${worker_port} \
1397+
--remote_download_minimal \
1398+
--experimental_remote_download_regex=".*" \
1399+
//a:test >& $TEST_log || fail "Failed to build"
1400+
1401+
[[ -e "bazel-bin/a/liblib.jar" ]] || fail "bazel-bin/a/liblib.jar file does not exist!"
1402+
[[ -e "bazel-bin/a/liblib.jdeps" ]] || fail "bazel-bin/a/liblib.jdeps file does not exist!"
1403+
1404+
bazel clean && bazel test \
1405+
--remote_executor=grpc://localhost:${worker_port} \
1406+
--remote_download_minimal \
1407+
--experimental_remote_download_regex=".*jar$" \
1408+
//a:test >& $TEST_log || fail "Failed to build"
1409+
1410+
[[ -e "bazel-bin/a/liblib.jar" ]] || fail "bazel-bin/a/liblib.jar file does not exist!"
1411+
[[ ! -e "bazel-bin/a/liblib.jdeps" ]] || fail "bazel-bin/a/liblib.jdeps shouldn't exist"
1412+
}
1413+
13571414
run_suite "Build without the Bytes tests"

0 commit comments

Comments
 (0)