145145import java .util .concurrent .Executor ;
146146import java .util .concurrent .Phaser ;
147147import java .util .concurrent .atomic .AtomicBoolean ;
148+ import java .util .function .Predicate ;
149+ import java .util .regex .Matcher ;
150+ import java .util .regex .Pattern ;
148151import javax .annotation .Nullable ;
149152
150153/**
@@ -172,6 +175,8 @@ public class RemoteExecutionService {
172175
173176 private final AtomicBoolean shutdown = new AtomicBoolean (false );
174177 private final AtomicBoolean buildInterrupted = new AtomicBoolean (false );
178+ private final boolean shouldForceDownloads ;
179+ private final Predicate <String > shouldForceDownloadPredicate ;
175180
176181 public RemoteExecutionService (
177182 Executor executor ,
@@ -213,6 +218,20 @@ public RemoteExecutionService(
213218 this .captureCorruptedOutputsDir = captureCorruptedOutputsDir ;
214219
215220 this .scheduler = Schedulers .from (executor , /*interruptibleWorker=*/ true );
221+
222+ String regex = remoteOptions .remoteDownloadRegex ;
223+ // TODO(bazel-team): Consider adding a warning or more validation if the remoteDownloadRegex is
224+ // used without RemoteOutputsMode.MINIMAL.
225+ this .shouldForceDownloads =
226+ !regex .isEmpty ()
227+ && (remoteOptions .remoteOutputsMode == RemoteOutputsMode .MINIMAL
228+ || remoteOptions .remoteOutputsMode == RemoteOutputsMode .TOPLEVEL );
229+ Pattern pattern = Pattern .compile (regex );
230+ this .shouldForceDownloadPredicate =
231+ path -> {
232+ Matcher m = pattern .matcher (path );
233+ return m .matches ();
234+ };
216235 }
217236
218237 static Command buildCommand (
@@ -1011,24 +1030,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10111030 /* exitCode = */ result .getExitCode (),
10121031 hasFilesToDownload (action .getSpawn ().getOutputFiles (), filesToDownload ));
10131032
1014- if (downloadOutputs ) {
1015- HashSet <PathFragment > queuedFilePaths = new HashSet <>();
1016-
1017- for (FileMetadata file : metadata .files ()) {
1018- PathFragment filePath = file .path ().asFragment ();
1019- if (queuedFilePaths .add (filePath )) {
1020- downloadsBuilder .add (downloadFile (action , file ));
1021- }
1022- }
1033+ ImmutableList <ListenableFuture <FileMetadata >> forcedDownloads = ImmutableList .of ();
10231034
1024- for (Map .Entry <Path , DirectoryMetadata > entry : metadata .directories ()) {
1025- for (FileMetadata file : entry .getValue ().files ()) {
1026- PathFragment filePath = file .path ().asFragment ();
1027- if (queuedFilePaths .add (filePath )) {
1028- downloadsBuilder .add (downloadFile (action , file ));
1029- }
1030- }
1031- }
1035+ if (downloadOutputs ) {
1036+ downloadsBuilder .addAll (buildFilesToDownload (metadata , action ));
10321037 } else {
10331038 checkState (
10341039 result .getExitCode () == 0 ,
@@ -1039,6 +1044,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10391044 "Symlinks in action outputs are not yet supported by "
10401045 + "--experimental_remote_download_outputs=minimal" );
10411046 }
1047+ if (shouldForceDownloads ) {
1048+ forcedDownloads =
1049+ buildFilesToDownloadWithPredicate (metadata , action , shouldForceDownloadPredicate );
1050+ }
10421051 }
10431052
10441053 FileOutErr tmpOutErr = outErr .childOutErr ();
@@ -1053,6 +1062,19 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10531062 try (SilentCloseable c = Profiler .instance ().profile ("Remote.download" )) {
10541063 waitForBulkTransfer (downloads , /* cancelRemainingOnInterrupt= */ true );
10551064 } catch (Exception e ) {
1065+ // TODO(bazel-team): Consider adding better case-by-case exception handling instead of just
1066+ // rethrowing
1067+ captureCorruptedOutputs (e );
1068+ deletePartialDownloadedOutputs (result , tmpOutErr , e );
1069+ throw e ;
1070+ }
1071+
1072+ // TODO(bazel-team): Unify this block with the equivalent block above.
1073+ try (SilentCloseable c = Profiler .instance ().profile ("Remote.forcedDownload" )) {
1074+ waitForBulkTransfer (forcedDownloads , /* cancelRemainingOnInterrupt= */ true );
1075+ } catch (Exception e ) {
1076+ // TODO(bazel-team): Consider adding better case-by-case exception handling instead of just
1077+ // rethrowing
10561078 captureCorruptedOutputs (e );
10571079 deletePartialDownloadedOutputs (result , tmpOutErr , e );
10581080 throw e ;
@@ -1083,6 +1105,13 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
10831105 // might not be supported on all platforms
10841106 createSymlinks (symlinks );
10851107 } else {
1108+ // TODO(bazel-team): We should unify this if-block to rely on downloadOutputs above but, as of
1109+ // 2022-07-05, downloadOuputs' semantics isn't exactly the same as build-without-the-bytes
1110+ // which is necessary for using remoteDownloadRegex.
1111+ if (!forcedDownloads .isEmpty ()) {
1112+ moveOutputsToFinalLocation (forcedDownloads );
1113+ }
1114+
10861115 ActionInput inMemoryOutput = null ;
10871116 Digest inMemoryOutputDigest = null ;
10881117 PathFragment inMemoryOutputPath = getInMemoryOutputPath (action .getSpawn ());
@@ -1120,6 +1149,36 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
11201149 return null ;
11211150 }
11221151
1152+ private ImmutableList <ListenableFuture <FileMetadata >> buildFilesToDownload (
1153+ ActionResultMetadata metadata , RemoteAction action ) {
1154+ Predicate <String > alwaysTrue = unused -> true ;
1155+ return buildFilesToDownloadWithPredicate (metadata , action , alwaysTrue );
1156+ }
1157+
1158+ private ImmutableList <ListenableFuture <FileMetadata >> buildFilesToDownloadWithPredicate (
1159+ ActionResultMetadata metadata , RemoteAction action , Predicate <String > predicate ) {
1160+ HashSet <PathFragment > queuedFilePaths = new HashSet <>();
1161+ ImmutableList .Builder <ListenableFuture <FileMetadata >> builder = new ImmutableList .Builder <>();
1162+
1163+ for (FileMetadata file : metadata .files ()) {
1164+ PathFragment filePath = file .path ().asFragment ();
1165+ if (queuedFilePaths .add (filePath ) && predicate .test (file .path .toString ())) {
1166+ builder .add (downloadFile (action , file ));
1167+ }
1168+ }
1169+
1170+ for (Map .Entry <Path , DirectoryMetadata > entry : metadata .directories ()) {
1171+ for (FileMetadata file : entry .getValue ().files ()) {
1172+ PathFragment filePath = file .path ().asFragment ();
1173+ if (queuedFilePaths .add (filePath ) && predicate .test (file .path .toString ())) {
1174+ builder .add (downloadFile (action , file ));
1175+ }
1176+ }
1177+ }
1178+
1179+ return builder .build ();
1180+ }
1181+
11231182 private static String prettyPrint (ActionInput actionInput ) {
11241183 if (actionInput instanceof Artifact ) {
11251184 return ((Artifact ) actionInput ).prettyPrint ();
0 commit comments