2323import com .google .devtools .build .lib .analysis .NoBuildRequestFinishedEvent ;
2424import com .google .devtools .build .lib .bazel .bzlmod .BazelFetchAllValue ;
2525import com .google .devtools .build .lib .bazel .commands .RepositoryFetcher .RepositoryFetcherException ;
26+ import com .google .devtools .build .lib .bazel .commands .TargetFetcher .TargetFetcherException ;
2627import com .google .devtools .build .lib .bazel .repository .RepositoryOptions ;
28+ import com .google .devtools .build .lib .buildtool .BuildResult ;
2729import com .google .devtools .build .lib .cmdline .LabelConstants ;
2830import com .google .devtools .build .lib .cmdline .RepositoryName ;
2931import com .google .devtools .build .lib .events .Event ;
3840import com .google .devtools .build .lib .runtime .CommandEnvironment ;
3941import com .google .devtools .build .lib .runtime .KeepGoingOption ;
4042import com .google .devtools .build .lib .runtime .LoadingPhaseThreadsOption ;
43+ import com .google .devtools .build .lib .runtime .commands .TestCommand ;
4144import com .google .devtools .build .lib .server .FailureDetails ;
4245import com .google .devtools .build .lib .server .FailureDetails .FailureDetail ;
4346import com .google .devtools .build .lib .server .FailureDetails .FetchCommand .Code ;
47+ import com .google .devtools .build .lib .skyframe .ConfiguredTargetKey ;
4448import com .google .devtools .build .lib .skyframe .PrecomputedValue ;
4549import com .google .devtools .build .lib .skyframe .RepositoryMappingValue .RepositoryMappingResolutionException ;
46- import com .google .devtools .build .lib .util .AbruptExitException ;
4750import com .google .devtools .build .lib .util .DetailedExitCode ;
4851import com .google .devtools .build .lib .util .InterruptedFailureDetails ;
4952import com .google .devtools .build .lib .vfs .FileSystemUtils ;
5255import com .google .devtools .build .lib .vfs .Symlinks ;
5356import com .google .devtools .build .skyframe .EvaluationContext ;
5457import com .google .devtools .build .skyframe .EvaluationResult ;
58+ import com .google .devtools .build .skyframe .InMemoryGraph ;
59+ import com .google .devtools .build .skyframe .NodeEntry ;
60+ import com .google .devtools .build .skyframe .QueryableGraph .Reason ;
5561import com .google .devtools .build .skyframe .SkyKey ;
5662import com .google .devtools .build .skyframe .SkyValue ;
63+ import com .google .devtools .common .options .OptionsParser ;
5764import com .google .devtools .common .options .OptionsParsingResult ;
5865import java .io .IOException ;
66+ import java .util .ArrayDeque ;
5967import java .util .ArrayList ;
68+ import java .util .HashSet ;
6069import java .util .List ;
6170import java .util .Map .Entry ;
6271import java .util .Objects ;
72+ import java .util .Queue ;
73+ import java .util .Set ;
6374import javax .annotation .Nullable ;
6475
6576/** Fetches external repositories into a specified directory. */
6677@ Command (
6778 name = VendorCommand .NAME ,
79+ builds = true ,
80+ inherits = {TestCommand .class },
6881 options = {
6982 VendorOptions .class ,
7083 PackageOptions .class ,
7184 KeepGoingOption .class ,
7285 LoadingPhaseThreadsOption .class
7386 },
87+ allowResidue = true ,
88+ usesConfigurationOptions = true ,
7489 help = "resource:vendor.txt" ,
7590 shortDescription =
7691 "Fetches external repositories into a specific folder specified by the flag "
@@ -81,6 +96,14 @@ public final class VendorCommand implements BlazeCommand {
8196 // TODO(salmasamy) decide on name and format
8297 private static final String VENDOR_IGNORE = ".vendorignore" ;
8398
99+ @ Override
100+ public void editOptions (OptionsParser optionsParser ) {
101+ // We only need to inject these options with fetch target (when there is a residue)
102+ if (!optionsParser .getResidue ().isEmpty ()) {
103+ TargetFetcher .injectNoBuildOption (optionsParser );
104+ }
105+ }
106+
84107 @ Override
85108 public BlazeCommandResult exec (CommandEnvironment env , OptionsParsingResult options ) {
86109 BlazeCommandResult invalidResult = validateOptions (env , options );
@@ -109,15 +132,13 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
109132 PathFragment vendorDirectory = options .getOptions (RepositoryOptions .class ).vendorDirectory ;
110133 LoadingPhaseThreadsOption threadsOption = options .getOptions (LoadingPhaseThreadsOption .class );
111134 try {
112- env .syncPackageLoading (options );
113- if (!vendorOptions .repos .isEmpty ()) {
135+ if (!options .getResidue ().isEmpty ()) {
136+ result = vendorTargets (env , options , options .getResidue (), vendorDirectory );
137+ } else if (!vendorOptions .repos .isEmpty ()) {
114138 result = vendorRepos (env , threadsOption , vendorOptions .repos , vendorDirectory );
115139 } else {
116140 result = vendorAll (env , threadsOption , vendorDirectory );
117141 }
118- } catch (AbruptExitException e ) {
119- return createFailedBlazeCommandResult (
120- env .getReporter (), e .getMessage (), e .getDetailedExitCode ());
121142 } catch (InterruptedException e ) {
122143 return createFailedBlazeCommandResult (
123144 env .getReporter (), "Vendor interrupted: " + e .getMessage ());
@@ -165,15 +186,16 @@ private BlazeCommandResult vendorAll(
165186 SkyKey fetchKey = BazelFetchAllValue .key (/* configureEnabled= */ false );
166187 EvaluationResult <SkyValue > evaluationResult =
167188 env .getSkyframeExecutor ().prepareAndGet (ImmutableSet .of (fetchKey ), evaluationContext );
168- if (evaluationResult .hasError ()) {
169- Exception e = evaluationResult .getError ().getException ();
170- return createFailedBlazeCommandResult (
171- env .getReporter (),
172- e != null ? e .getMessage () : "Unexpected error during fetching all external deps." );
173- }
189+ if (evaluationResult .hasError ()) {
190+ Exception e = evaluationResult .getError ().getException ();
191+ return createFailedBlazeCommandResult (
192+ env .getReporter (),
193+ e != null ? e .getMessage () : "Unexpected error during fetching all external deps." );
194+ }
174195
175196 BazelFetchAllValue fetchAllValue = (BazelFetchAllValue ) evaluationResult .get (fetchKey );
176197 vendor (env , vendorDirectory , fetchAllValue .getReposToVendor ());
198+ env .getReporter ().handle (Event .info ("All external dependencies vendored successfully." ));
177199 return BlazeCommandResult .success ();
178200 }
179201
@@ -212,9 +234,69 @@ private BlazeCommandResult vendorRepos(
212234 return createFailedBlazeCommandResult (
213235 env .getReporter (), "Vendoring some repos failed with errors: " + notFoundRepoErrors );
214236 }
237+ env .getReporter ().handle (Event .info ("All requested repos vendored successfully." ));
215238 return BlazeCommandResult .success ();
216239 }
217240
241+ private BlazeCommandResult vendorTargets (
242+ CommandEnvironment env ,
243+ OptionsParsingResult options ,
244+ List <String > targets ,
245+ PathFragment vendorDirectory )
246+ throws InterruptedException , IOException {
247+ // Call fetch which runs build to have the targets graph and configuration set
248+ BuildResult buildResult ;
249+ try {
250+ buildResult = TargetFetcher .fetchTargets (env , options , targets );
251+ } catch (TargetFetcherException e ) {
252+ return createFailedBlazeCommandResult (
253+ env .getReporter (), Code .QUERY_EVALUATION_ERROR , e .getMessage ());
254+ }
255+
256+ // Traverse the graph created from build to collect repos and vendor them
257+ ImmutableList <SkyKey > targetKeys =
258+ buildResult .getActualTargets ().stream ()
259+ .map (
260+ target ->
261+ ConfiguredTargetKey .builder ()
262+ .setConfigurationKey (target .getConfigurationKey ())
263+ .setLabel (target .getLabel ())
264+ .build ())
265+ .collect (toImmutableList ());
266+ InMemoryGraph inMemoryGraph = env .getSkyframeExecutor ().getEvaluator ().getInMemoryGraph ();
267+ ImmutableSet <RepositoryName > reposToVendor = collectReposFromTargets (inMemoryGraph , targetKeys );
268+
269+ vendor (env , vendorDirectory , reposToVendor .asList ());
270+ env .getReporter ()
271+ .handle (
272+ Event .info (
273+ "All External dependencies for the requested targets vendored successfully." ));
274+ return BlazeCommandResult .success ();
275+ }
276+
277+ private ImmutableSet <RepositoryName > collectReposFromTargets (
278+ InMemoryGraph inMemoryGraph , ImmutableList <SkyKey > targetKeys ) throws InterruptedException {
279+ ImmutableSet .Builder <RepositoryName > repos = ImmutableSet .builder ();
280+ Queue <SkyKey > nodes = new ArrayDeque <>(targetKeys );
281+ Set <SkyKey > visited = new HashSet <>();
282+ while (!nodes .isEmpty ()) {
283+ SkyKey key = nodes .remove ();
284+ visited .add (key );
285+ NodeEntry nodeEntry = inMemoryGraph .get (null , Reason .VENDOR_EXTERNAL_REPOS , key );
286+ if (nodeEntry .getValue () instanceof RepositoryDirectoryValue repoDirValue
287+ && repoDirValue .repositoryExists ()
288+ && !repoDirValue .excludeFromVendoring ()) {
289+ repos .add ((RepositoryName ) key .argument ());
290+ }
291+ for (SkyKey depKey : nodeEntry .getDirectDeps ()) {
292+ if (!visited .contains (depKey )) {
293+ nodes .add (depKey );
294+ }
295+ }
296+ }
297+ return repos .build ();
298+ }
299+
218300 /**
219301 * Copies the fetched repos from the external cache into the vendor directory, unless the repo is
220302 * ignored or was already vendored and up-to-date
@@ -250,6 +332,8 @@ private void vendor(
250332 FileSystemUtils .createEmptyFile (vendorIgnore );
251333 }
252334
335+ env .getReporter ().handle (Event .info ("Vendoring ..." ));
336+
253337 // Update "out-of-date" repos under the vendor directory
254338 for (RepositoryName repo : reposToVendor ) {
255339 if (!isRepoUpToDate (repo .getName (), vendorPath , externalPath )) {
0 commit comments