145
145
import java .util .concurrent .Executor ;
146
146
import java .util .concurrent .Phaser ;
147
147
import java .util .concurrent .atomic .AtomicBoolean ;
148
+ import java .util .function .Predicate ;
149
+ import java .util .regex .Matcher ;
150
+ import java .util .regex .Pattern ;
148
151
import javax .annotation .Nullable ;
149
152
150
153
/**
@@ -172,6 +175,8 @@ public class RemoteExecutionService {
172
175
173
176
private final AtomicBoolean shutdown = new AtomicBoolean (false );
174
177
private final AtomicBoolean buildInterrupted = new AtomicBoolean (false );
178
+ private final boolean shouldForceDownloads ;
179
+ private final Predicate <String > shouldForceDownloadPredicate ;
175
180
176
181
public RemoteExecutionService (
177
182
Executor executor ,
@@ -213,6 +218,20 @@ public RemoteExecutionService(
213
218
this .captureCorruptedOutputsDir = captureCorruptedOutputsDir ;
214
219
215
220
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
+ };
216
235
}
217
236
218
237
static Command buildCommand (
@@ -1011,24 +1030,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1011
1030
/* exitCode = */ result .getExitCode (),
1012
1031
hasFilesToDownload (action .getSpawn ().getOutputFiles (), filesToDownload ));
1013
1032
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 ();
1023
1034
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 ));
1032
1037
} else {
1033
1038
checkState (
1034
1039
result .getExitCode () == 0 ,
@@ -1039,6 +1044,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1039
1044
"Symlinks in action outputs are not yet supported by "
1040
1045
+ "--experimental_remote_download_outputs=minimal" );
1041
1046
}
1047
+ if (shouldForceDownloads ) {
1048
+ forcedDownloads =
1049
+ buildFilesToDownloadWithPredicate (metadata , action , shouldForceDownloadPredicate );
1050
+ }
1042
1051
}
1043
1052
1044
1053
FileOutErr tmpOutErr = outErr .childOutErr ();
@@ -1053,6 +1062,19 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1053
1062
try (SilentCloseable c = Profiler .instance ().profile ("Remote.download" )) {
1054
1063
waitForBulkTransfer (downloads , /* cancelRemainingOnInterrupt= */ true );
1055
1064
} 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
1056
1078
captureCorruptedOutputs (e );
1057
1079
deletePartialDownloadedOutputs (result , tmpOutErr , e );
1058
1080
throw e ;
@@ -1083,6 +1105,13 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1083
1105
// might not be supported on all platforms
1084
1106
createSymlinks (symlinks );
1085
1107
} 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
+
1086
1115
ActionInput inMemoryOutput = null ;
1087
1116
Digest inMemoryOutputDigest = null ;
1088
1117
PathFragment inMemoryOutputPath = getInMemoryOutputPath (action .getSpawn ());
@@ -1120,6 +1149,36 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1120
1149
return null ;
1121
1150
}
1122
1151
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
+
1123
1182
private static String prettyPrint (ActionInput actionInput ) {
1124
1183
if (actionInput instanceof Artifact ) {
1125
1184
return ((Artifact ) actionInput ).prettyPrint ();
0 commit comments