147
147
import java .util .concurrent .Executor ;
148
148
import java .util .concurrent .Phaser ;
149
149
import java .util .concurrent .atomic .AtomicBoolean ;
150
+ import java .util .function .Predicate ;
151
+ import java .util .regex .Matcher ;
152
+ import java .util .regex .Pattern ;
150
153
import javax .annotation .Nullable ;
151
154
152
155
/**
@@ -174,6 +177,8 @@ public class RemoteExecutionService {
174
177
175
178
private final AtomicBoolean shutdown = new AtomicBoolean (false );
176
179
private final AtomicBoolean buildInterrupted = new AtomicBoolean (false );
180
+ private final boolean shouldForceDownloads ;
181
+ private final Predicate <String > shouldForceDownloadPredicate ;
177
182
178
183
public RemoteExecutionService (
179
184
Executor executor ,
@@ -215,6 +220,20 @@ public RemoteExecutionService(
215
220
this .captureCorruptedOutputsDir = captureCorruptedOutputsDir ;
216
221
217
222
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
+ };
218
237
}
219
238
220
239
static Command buildCommand (
@@ -1021,24 +1040,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1021
1040
/* exitCode = */ result .getExitCode (),
1022
1041
hasFilesToDownload (action .getSpawn ().getOutputFiles (), filesToDownload ));
1023
1042
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 ();
1033
1044
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 ));
1042
1047
} else {
1043
1048
checkState (
1044
1049
result .getExitCode () == 0 ,
@@ -1049,6 +1054,10 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1049
1054
"Symlinks in action outputs are not yet supported by "
1050
1055
+ "--experimental_remote_download_outputs=minimal" );
1051
1056
}
1057
+ if (shouldForceDownloads ) {
1058
+ forcedDownloads =
1059
+ buildFilesToDownloadWithPredicate (metadata , action , shouldForceDownloadPredicate );
1060
+ }
1052
1061
}
1053
1062
1054
1063
FileOutErr tmpOutErr = outErr .childOutErr ();
@@ -1063,6 +1072,19 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1063
1072
try (SilentCloseable c = Profiler .instance ().profile ("Remote.download" )) {
1064
1073
waitForBulkTransfer (downloads , /* cancelRemainingOnInterrupt= */ true );
1065
1074
} 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
1066
1088
captureCorruptedOutputs (e );
1067
1089
deletePartialDownloadedOutputs (result , tmpOutErr , e );
1068
1090
throw e ;
@@ -1096,6 +1118,13 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1096
1118
// might not be supported on all platforms
1097
1119
createSymlinks (symlinks );
1098
1120
} 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
+
1099
1128
ActionInput inMemoryOutput = null ;
1100
1129
Digest inMemoryOutputDigest = null ;
1101
1130
PathFragment inMemoryOutputPath = getInMemoryOutputPath (action .getSpawn ());
@@ -1133,6 +1162,36 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
1133
1162
return null ;
1134
1163
}
1135
1164
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
+
1136
1195
private static String prettyPrint (ActionInput actionInput ) {
1137
1196
if (actionInput instanceof Artifact ) {
1138
1197
return ((Artifact ) actionInput ).prettyPrint ();
0 commit comments