1414package com .google .devtools .build .lib .bazel .commands ;
1515
1616import static com .google .common .primitives .Booleans .countTrue ;
17+ import static java .util .stream .Collectors .joining ;
1718
1819import com .google .common .base .Joiner ;
1920import com .google .common .collect .ImmutableList ;
2021import com .google .common .collect .ImmutableSet ;
2122import com .google .devtools .build .lib .analysis .NoBuildEvent ;
2223import com .google .devtools .build .lib .analysis .NoBuildRequestFinishedEvent ;
2324import com .google .devtools .build .lib .bazel .bzlmod .BazelFetchAllValue ;
25+ import com .google .devtools .build .lib .cmdline .LabelSyntaxException ;
2426import com .google .devtools .build .lib .cmdline .RepositoryMapping ;
2527import com .google .devtools .build .lib .cmdline .RepositoryName ;
2628import com .google .devtools .build .lib .cmdline .TargetPattern ;
3840import com .google .devtools .build .lib .query2 .engine .QueryExpression ;
3941import com .google .devtools .build .lib .query2 .engine .QuerySyntaxException ;
4042import com .google .devtools .build .lib .query2 .engine .ThreadSafeOutputFormatterCallback ;
43+ import com .google .devtools .build .lib .rules .repository .RepositoryDirectoryValue ;
4144import com .google .devtools .build .lib .runtime .BlazeCommand ;
4245import com .google .devtools .build .lib .runtime .BlazeCommandResult ;
4346import com .google .devtools .build .lib .runtime .Command ;
5659import com .google .devtools .build .lib .util .InterruptedFailureDetails ;
5760import com .google .devtools .build .skyframe .EvaluationContext ;
5861import com .google .devtools .build .skyframe .EvaluationResult ;
62+ import com .google .devtools .build .skyframe .SkyKey ;
5963import com .google .devtools .build .skyframe .SkyValue ;
6064import com .google .devtools .common .options .OptionsParsingResult ;
6165import java .io .IOException ;
6266import java .util .EnumSet ;
67+ import java .util .List ;
68+ import net .starlark .java .eval .EvalException ;
6369
6470/** Fetches external repositories. Which is so fetch. */
6571@ Command (
@@ -90,10 +96,13 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
9096 return createFailedBlazeCommandResult (Code .OPTIONS_INVALID , errorMessage );
9197 }
9298 FetchOptions fetchOptions = options .getOptions (FetchOptions .class );
93- // Validate only one option is provided for fetch
94- boolean moreThanOneOption =
95- countTrue (fetchOptions .all , fetchOptions .configure , !options .getResidue ().isEmpty ()) > 1 ;
96- if (moreThanOneOption ) {
99+ int optionsCount =
100+ countTrue (
101+ fetchOptions .all ,
102+ fetchOptions .configure ,
103+ !fetchOptions .repos .isEmpty (),
104+ !options .getResidue ().isEmpty ());
105+ if (optionsCount > 1 ) {
97106 String errorMessage = "Only one fetch option should be provided for fetch command." ;
98107 env .getReporter ().handle (Event .error (null , errorMessage ));
99108 return createFailedBlazeCommandResult (Code .OPTIONS_INVALID , errorMessage );
@@ -109,9 +118,10 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
109118 /* showProgress= */ true ,
110119 /* id= */ null ));
111120 BlazeCommandResult result ;
112-
113121 if (fetchOptions .all || fetchOptions .configure ) {
114- return fetchAll (env , options , fetchOptions .configure , threadsOption );
122+ result = fetchAll (env , options , threadsOption , fetchOptions .configure );
123+ } else if (!fetchOptions .repos .isEmpty ()) {
124+ result = fetchRepo (env , options , threadsOption , fetchOptions .repos );
115125 } else {
116126 result = fetchTarget (env , options , threadsOption );
117127 }
@@ -125,8 +135,8 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
125135 private BlazeCommandResult fetchAll (
126136 CommandEnvironment env ,
127137 OptionsParsingResult options ,
128- boolean configureEnabled ,
129- LoadingPhaseThreadsOption threadsOption ) {
138+ LoadingPhaseThreadsOption threadsOption ,
139+ boolean configureEnabled ) {
130140 if (!options .getOptions (BuildLanguageOptions .class ).enableBzlmod ) {
131141 String errorMessage =
132142 "Bzlmod has to be enabled for fetch --all to work, run with --enable_bzlmod" ;
@@ -168,6 +178,68 @@ private BlazeCommandResult fetchAll(
168178 }
169179 }
170180
181+ private BlazeCommandResult fetchRepo (
182+ CommandEnvironment env ,
183+ OptionsParsingResult options ,
184+ LoadingPhaseThreadsOption threadsOption ,
185+ List <String > repos ) {
186+ SkyframeExecutor skyframeExecutor = env .getSkyframeExecutor ();
187+ EvaluationContext evaluationContext =
188+ EvaluationContext .newBuilder ()
189+ .setParallelism (threadsOption .threads )
190+ .setEventHandler (env .getReporter ())
191+ .build ();
192+ try {
193+ env .syncPackageLoading (options );
194+ ImmutableSet .Builder <SkyKey > repoDelegatorKeys = ImmutableSet .builder ();
195+ for (String repo : repos ) {
196+ RepositoryName repoName = getRepositoryName (env , threadsOption , repo );
197+ repoDelegatorKeys .add (RepositoryDirectoryValue .key (repoName ));
198+ }
199+ EvaluationResult <SkyValue > evaluationResult =
200+ skyframeExecutor .prepareAndGet (repoDelegatorKeys .build (), evaluationContext );
201+ if (evaluationResult .hasError ()) {
202+ Exception e = evaluationResult .getError ().getException ();
203+ String errorMessage =
204+ e != null ? e .getMessage () : "Unexpected error during repository fetching." ;
205+ env .getReporter ().handle (Event .error (errorMessage ));
206+ return BlazeCommandResult .detailedExitCode (
207+ InterruptedFailureDetails .detailedExitCode (errorMessage ));
208+ }
209+ String notFoundRepos =
210+ repoDelegatorKeys .build ().stream ()
211+ .filter (
212+ key -> !((RepositoryDirectoryValue ) evaluationResult .get (key )).repositoryExists ())
213+ .map (key -> ((RepositoryDirectoryValue ) evaluationResult .get (key )).getErrorMsg ())
214+ .collect (joining ("; " ));
215+ if (!notFoundRepos .isEmpty ()) {
216+ String errorMessage = "Fetching repos failed with errors: " + notFoundRepos ;
217+ env .getReporter ().handle (Event .error (errorMessage ));
218+ return BlazeCommandResult .detailedExitCode (
219+ InterruptedFailureDetails .detailedExitCode (errorMessage ));
220+ }
221+
222+ // Everything has been fetched successfully!
223+ return BlazeCommandResult .success ();
224+ } catch (AbruptExitException e ) {
225+ env .getReporter ().handle (Event .error (null , "Unknown error: " + e .getMessage ()));
226+ return BlazeCommandResult .detailedExitCode (e .getDetailedExitCode ());
227+ } catch (InterruptedException e ) {
228+ String errorMessage = "Fetch interrupted: " + e .getMessage ();
229+ env .getReporter ().handle (Event .error (errorMessage ));
230+ return BlazeCommandResult .detailedExitCode (
231+ InterruptedFailureDetails .detailedExitCode (errorMessage ));
232+ } catch (LabelSyntaxException | EvalException | IllegalArgumentException e ) {
233+ String errorMessage = "Invalid repo name: " + e .getMessage ();
234+ env .getReporter ().handle (Event .error (null , errorMessage ));
235+ return BlazeCommandResult .detailedExitCode (
236+ InterruptedFailureDetails .detailedExitCode (errorMessage ));
237+ } catch (RepositoryMappingResolutionException e ) {
238+ env .getReporter ().handle (Event .error (e .getMessage ()));
239+ return BlazeCommandResult .detailedExitCode (e .getDetailedExitCode ());
240+ }
241+ }
242+
171243 private BlazeCommandResult fetchTarget (
172244 CommandEnvironment env ,
173245 OptionsParsingResult options ,
@@ -188,6 +260,7 @@ private BlazeCommandResult fetchTarget(
188260 RepositoryMapping repoMapping =
189261 env .getSkyframeExecutor ()
190262 .getMainRepoMapping (keepGoing , threadsOption .threads , env .getReporter ());
263+
191264 mainRepoTargetParser =
192265 new Parser (env .getRelativeWorkingDirectory (), RepositoryName .MAIN , repoMapping );
193266 } catch (RepositoryMappingResolutionException e ) {
@@ -294,6 +367,29 @@ public void processOutput(Iterable<Target> partialResult) {
294367 expr ));
295368 }
296369
370+ private RepositoryName getRepositoryName (
371+ CommandEnvironment env , LoadingPhaseThreadsOption threadsOption , String repoName )
372+ throws EvalException ,
373+ LabelSyntaxException ,
374+ RepositoryMappingResolutionException ,
375+ InterruptedException {
376+ if (repoName .startsWith ("@@" )) { // canonical RepoName
377+ return RepositoryName .create (repoName .substring (2 ));
378+ } else if (repoName .startsWith ("@" )) { // apparent RepoName
379+ RepositoryName .validateUserProvidedRepoName (repoName .substring (1 ));
380+ RepositoryMapping repoMapping =
381+ env .getSkyframeExecutor ()
382+ .getMainRepoMapping (
383+ env .getOptions ().getOptions (KeepGoingOption .class ).keepGoing ,
384+ threadsOption .threads ,
385+ env .getReporter ());
386+ return repoMapping .get (repoName .substring (1 ));
387+ } else {
388+ throw new IllegalArgumentException (
389+ "The repo value has to be either apparent '@repo' or canonical '@@repo' repo name" );
390+ }
391+ }
392+
297393 private static BlazeCommandResult createFailedBlazeCommandResult (
298394 Code fetchCommandCode , String message ) {
299395 return BlazeCommandResult .detailedExitCode (
0 commit comments