16
16
import com .google .common .base .Verify ;
17
17
import com .google .common .collect .ImmutableList ;
18
18
import com .google .common .collect .ImmutableMap ;
19
+ import com .google .common .collect .Streams ;
19
20
import com .google .devtools .build .lib .cmdline .Label ;
20
21
import com .google .devtools .build .lib .cmdline .LabelSyntaxException ;
21
22
import com .google .devtools .build .lib .cmdline .PackageIdentifier ;
24
25
import com .google .devtools .build .lib .skyframe .serialization .autocodec .AutoCodec .VisibleForSerialization ;
25
26
import com .google .devtools .build .lib .skyframe .serialization .autocodec .SerializationConstant ;
26
27
import com .google .devtools .build .lib .vfs .PathFragment ;
27
- import java .util .LinkedHashMap ;
28
28
import java .util .stream .Stream ;
29
29
import javax .annotation .Nullable ;
30
30
import net .starlark .java .eval .EvalException ;
@@ -56,13 +56,41 @@ public abstract class PackageSpecification {
56
56
protected abstract boolean containsPackage (PackageIdentifier packageName );
57
57
58
58
/**
59
- * Returns a {@link String} representation of the {@link PackageSpecification} of the same format
60
- * accepted by {@link #fromString}.
59
+ * Returns a string representation of this package spec.
61
60
*
62
- * <p>The returned {@link String} is insensitive to the {@link RepositoryName} associated with the
63
- * {@link PackageSpecification}.
61
+ * <p>The repository is included, unless it is the main repository, in which case there will be no
62
+ * leading {@literal @}. For instance, {@code "@somerepo//pkg/subpkg"} and {@code
63
+ * "//otherpkg/..."} are both valid outputs.
64
+ *
65
+ * <p>Note that since {@link #fromString} does not accept label strings with repositories, this
66
+ * representation is not guaranteed to be round-trippable.
67
+ *
68
+ * <p>If {@code includeDoubleSlash} is false, then in the case of the main repository, the leading
69
+ * {@code //} will also be omitted, so that the output looks like {@code otherpkg/...}. This form
70
+ * is deprecated.
71
+ */
72
+ // TODO(b/77598306): Remove the parameter after switching all callers to pass true.
73
+ protected abstract String asString (boolean includeDoubleSlash );
74
+
75
+ /**
76
+ * Returns a string representation of this package spec without the repository, and which is
77
+ * round-trippable through {@link #fromString}.
78
+ *
79
+ * <p>For instance, {@code @somerepo//pkg/subpkg/...} turns into {@code "//pkg/subpkg/..."}.
80
+ *
81
+ * <p>Omitting the repository means that the returned strings are ambiguous in the absence of
82
+ * additional context. But, for instance, if interpreted with respect to a {@code package_group}'s
83
+ * {@code packages} attribute, the strings always have the same repository as the package group.
64
84
*/
65
- protected abstract String toStringWithoutRepository ();
85
+ // TODO(brandjon): This method's main benefit is that it's round-trippable. We could eliminate
86
+ // it in favor of asString() if we provided a public variant of fromString() that tolerates
87
+ // repositories.
88
+ protected abstract String asStringWithoutRepository ();
89
+
90
+ @ Override
91
+ public String toString () {
92
+ return asString (/*includeDoubleSlash=*/ false );
93
+ }
66
94
67
95
/**
68
96
* Parses the provided {@link String} into a {@link PackageSpecification}.
@@ -196,13 +224,19 @@ protected boolean containsPackage(PackageIdentifier packageName) {
196
224
}
197
225
198
226
@ Override
199
- protected String toStringWithoutRepository () {
200
- return "//" + singlePackageName .getPackageFragment ().getPathString ();
227
+ protected String asString (boolean includeDoubleSlash ) {
228
+ if (includeDoubleSlash ) {
229
+ return singlePackageName .getCanonicalForm ();
230
+ } else {
231
+ // PackageIdentifier#toString implements the legacy behavior of omitting the double slash
232
+ // for the main repo.
233
+ return singlePackageName .toString ();
234
+ }
201
235
}
202
236
203
237
@ Override
204
- public String toString () {
205
- return singlePackageName .toString ();
238
+ protected String asStringWithoutRepository () {
239
+ return "//" + singlePackageName .getPackageFragment (). getPathString ();
206
240
}
207
241
208
242
@ Override
@@ -237,16 +271,27 @@ protected boolean containsPackage(PackageIdentifier packageName) {
237
271
}
238
272
239
273
@ Override
240
- protected String toStringWithoutRepository () {
241
- return "//" + prefix .getPackageFragment ().getPathString () + ALL_BENEATH_SUFFIX ;
274
+ protected String asString (boolean includeDoubleSlash ) {
275
+ if (prefix .getPackageFragment ().equals (PathFragment .EMPTY_FRAGMENT )) {
276
+ // Special case: Emit "//..." rather than suffixing "/...", which would yield "/...".
277
+ // Make sure not to strip the repo in the case of "@repo//...".
278
+ //
279
+ // Note that "//..." is the desired result, not "...", even under the legacy behavior of
280
+ // includeDoubleSlash=false.
281
+ return prefix .getCanonicalForm () + "..." ;
282
+ }
283
+ if (includeDoubleSlash ) {
284
+ return prefix .getCanonicalForm () + ALL_BENEATH_SUFFIX ;
285
+ } else {
286
+ // PackageIdentifier#toString implements the legacy behavior of omitting the double slash
287
+ // for the main repo.
288
+ return prefix .toString () + ALL_BENEATH_SUFFIX ;
289
+ }
242
290
}
243
291
244
292
@ Override
245
- public String toString () {
246
- if (prefix .getPackageFragment ().equals (PathFragment .EMPTY_FRAGMENT )) {
247
- return "//..." ;
248
- }
249
- return prefix + "/..." ;
293
+ protected String asStringWithoutRepository () {
294
+ return "//" + prefix .getPackageFragment ().getPathString () + ALL_BENEATH_SUFFIX ;
250
295
}
251
296
252
297
@ Override
@@ -281,8 +326,13 @@ protected boolean containsPackage(PackageIdentifier packageName) {
281
326
}
282
327
283
328
@ Override
284
- protected String toStringWithoutRepository () {
285
- return "-" + delegate .toStringWithoutRepository ();
329
+ protected String asString (boolean includeDoubleSlash ) {
330
+ return "-" + delegate .asString (includeDoubleSlash );
331
+ }
332
+
333
+ @ Override
334
+ protected String asStringWithoutRepository () {
335
+ return "-" + delegate .asStringWithoutRepository ();
286
336
}
287
337
288
338
@ Override
@@ -298,11 +348,6 @@ public boolean equals(Object obj) {
298
348
return obj instanceof NegativePackageSpecification
299
349
&& delegate .equals (((NegativePackageSpecification ) obj ).delegate );
300
350
}
301
-
302
- @ Override
303
- public String toString () {
304
- return "-" + delegate ;
305
- }
306
351
}
307
352
308
353
@ VisibleForSerialization
@@ -316,7 +361,14 @@ protected boolean containsPackage(PackageIdentifier packageName) {
316
361
}
317
362
318
363
@ Override
319
- protected String toStringWithoutRepository () {
364
+ protected String asString (boolean includeDoubleSlash ) {
365
+ // Note that "//..." is the desired result, not "...", even under the legacy behavior of
366
+ // includeDoubleSlash=false.
367
+ return "//..." ;
368
+ }
369
+
370
+ @ Override
371
+ protected String asStringWithoutRepository () {
320
372
return "//..." ;
321
373
}
322
374
@@ -329,11 +381,6 @@ public boolean equals(Object o) {
329
381
public int hashCode () {
330
382
return "//..." .hashCode ();
331
383
}
332
-
333
- @ Override
334
- public String toString () {
335
- return "//..." ;
336
- }
337
384
}
338
385
339
386
/** Exception class to be thrown when a specification cannot be parsed. */
@@ -344,23 +391,36 @@ private InvalidPackageSpecificationException(String message) {
344
391
}
345
392
346
393
/**
347
- * A collection of {@link PackageSpecification}s from a {@code package_group}, which supports
348
- * testing a given package for containment (see {@link #containedPackages()}}.
394
+ * A collection of {@link PackageSpecification}s logically corresponding to a single {@code
395
+ * package_group}'s {@code packages} attribute.
396
+ *
397
+ * <p>Supports testing whether a given package is contained, taking into account negative specs.
398
+ *
399
+ * <p>Duplicate specs (e.g., ["//foo", "//foo"]) may or may not be deduplicated. Iteration order
400
+ * may vary from the order in which specs were provided, but is guaranteed to be deterministic.
401
+ *
402
+ * <p>For modeling a {@code package_group}'s transitive contents (i.e., via the {@code includes}
403
+ * attribute), see {@link PackageSpecificationProvider}.
349
404
*/
350
405
@ Immutable
351
406
public static final class PackageGroupContents {
352
- private final ImmutableMap <PackageIdentifier , PackageSpecification > singlePackages ;
353
- private final ImmutableList <PackageSpecification > negativePackageSpecifications ;
354
- private final ImmutableList <PackageSpecification > allSpecifications ;
407
+ // We separate PackageSpecifications by type.
408
+ // - Single positive specs are separate so that we can look them up quickly by package name,
409
+ // without requiring a linear search for a satisfying containsPackage().
410
+ // - Negative specs need to be separate because their semantics are different (they overrule
411
+ // any positive spec).
412
+ // We don't bother separating out single negative specs. Negatives are pretty rare anyway.
413
+ private final ImmutableMap <PackageIdentifier , PackageSpecification > singlePositives ;
414
+ private final ImmutableList <PackageSpecification > otherPositives ;
415
+ private final ImmutableList <PackageSpecification > negatives ;
355
416
356
417
private PackageGroupContents (
357
- ImmutableMap <PackageIdentifier , PackageSpecification > singlePackages ,
358
- ImmutableList <PackageSpecification > negativePackageSpecifications ,
359
- ImmutableList <PackageSpecification > allSpecifications ) {
360
-
361
- this .singlePackages = singlePackages ;
362
- this .negativePackageSpecifications = negativePackageSpecifications ;
363
- this .allSpecifications = allSpecifications ;
418
+ ImmutableMap <PackageIdentifier , PackageSpecification > singlePositives ,
419
+ ImmutableList <PackageSpecification > otherPositives ,
420
+ ImmutableList <PackageSpecification > negatives ) {
421
+ this .singlePositives = singlePositives ;
422
+ this .otherPositives = otherPositives ;
423
+ this .negatives = negatives ;
364
424
}
365
425
366
426
/**
@@ -369,84 +429,77 @@ private PackageGroupContents(
369
429
*/
370
430
public static PackageGroupContents create (
371
431
ImmutableList <PackageSpecification > packageSpecifications ) {
372
- LinkedHashMap <PackageIdentifier , PackageSpecification > singlePackageBuilder =
373
- new LinkedHashMap <>();
374
- ImmutableList .Builder <PackageSpecification > negativePackageSpecificationsBuilder =
375
- ImmutableList .builder ();
376
- ImmutableList .Builder <PackageSpecification > allSpecificationsBuilder =
377
- ImmutableList .builder ();
378
-
379
- for (PackageSpecification packageSpecification : packageSpecifications ) {
380
- if (packageSpecification instanceof SinglePackage ) {
381
- singlePackageBuilder .put (
382
- ((SinglePackage ) packageSpecification ).singlePackageName , packageSpecification );
383
- } else if (packageSpecification instanceof NegativePackageSpecification ) {
384
- negativePackageSpecificationsBuilder .add (packageSpecification );
432
+ ImmutableMap .Builder <PackageIdentifier , PackageSpecification > singlePositives =
433
+ ImmutableMap .builder ();
434
+ ImmutableList .Builder <PackageSpecification > otherPositives = ImmutableList .builder ();
435
+ ImmutableList .Builder <PackageSpecification > negatives = ImmutableList .builder ();
436
+
437
+ for (PackageSpecification spec : packageSpecifications ) {
438
+ if (spec instanceof SinglePackage ) {
439
+ singlePositives .put (((SinglePackage ) spec ).singlePackageName , spec );
440
+ } else if (spec instanceof AllPackages || spec instanceof AllPackagesBeneath ) {
441
+ otherPositives .add (spec );
442
+ } else if (spec instanceof NegativePackageSpecification ) {
443
+ negatives .add (spec );
385
444
} else {
386
- allSpecificationsBuilder .add (packageSpecification );
387
- if (!(packageSpecification instanceof AllPackages )
388
- && !(packageSpecification instanceof AllPackagesBeneath )) {
389
- throw new IllegalStateException (
390
- "Instance of unhandled class " + packageSpecification .getClass ());
391
- }
445
+ throw new IllegalStateException (
446
+ "Unhandled PackageSpecification subclass " + spec .getClass ());
392
447
}
393
448
}
394
449
return new PackageGroupContents (
395
- ImmutableMap .copyOf (singlePackageBuilder ),
396
- negativePackageSpecificationsBuilder .build (),
397
- allSpecificationsBuilder .build ());
450
+ singlePositives .buildKeepingLast (), otherPositives .build (), negatives .build ());
398
451
}
399
452
400
453
/**
401
- * Returns {@code true} if the package specifications include the provided {@code packageName}.
402
- * That is, at least one positive package specification matches, and no negative package
403
- * specifications match.
454
+ * Returns true if the given package matches at least one of this {@code PackageGroupContents}'
455
+ * positive specifications and none of its negative specifications.
404
456
*/
405
457
public boolean containsPackage (PackageIdentifier packageIdentifier ) {
406
458
// DO NOT use streams or iterators here as they create excessive garbage.
407
459
408
- // if some negative matches, returns false immediately.
409
- for (int i = 0 ; i < negativePackageSpecifications .size (); i ++) {
410
- if (negativePackageSpecifications .get (i ).containsPackage (packageIdentifier )) {
460
+ // Check negatives first. If there's a match we get to bail out early. If not, we'd still have
461
+ // to check all the negatives anyway.
462
+ for (int i = 0 ; i < negatives .size (); i ++) {
463
+ if (negatives .get (i ).containsPackage (packageIdentifier )) {
411
464
return false ;
412
465
}
413
466
}
414
-
415
- if (singlePackages .containsKey (packageIdentifier )) {
467
+ // Check the map in hopes of passing without having to do a linear scan over all other
468
+ // positive specs.
469
+ if (singlePositives .containsKey (packageIdentifier )) {
416
470
return true ;
417
471
}
418
-
419
- for (int i = 0 ; i < allSpecifications .size (); i ++) {
420
- if (allSpecifications .get (i ).containsPackage (packageIdentifier )) {
472
+ // Oh well.
473
+ for (int i = 0 ; i < otherPositives .size (); i ++) {
474
+ if (otherPositives .get (i ).containsPackage (packageIdentifier )) {
421
475
return true ;
422
476
}
423
477
}
424
478
return false ;
425
479
}
426
480
427
481
/**
428
- * Returns {@link String} representations of the component {@link PackageSpecification}s of the
429
- * same format accepted by {@link #fromString}.
482
+ * Maps {@link PackageSpecification#asString} to the component package specs.
483
+ *
484
+ * <p>Note that strings for specs that cross repositories can't be reparsed using {@link
485
+ * PackageSpecification#fromString}.
430
486
*/
431
- public Stream <String > containedPackages ( ) {
432
- return getStream ().map (PackageSpecification :: toString );
487
+ public Stream <String > streamPackageStrings ( boolean includeDoubleSlash ) {
488
+ return streamSpecs ().map (spec -> spec . asString ( includeDoubleSlash ) );
433
489
}
434
490
435
491
/**
436
- * Returns {@link String} representations of the component {@link PackageSpecification}s of the
437
- * same format accepted by {@link #fromString}.
492
+ * Maps {@link PackageSpecification#asStringWithoutRepository} to the component package specs.
438
493
*
439
- * <p>The returned {@link String}s are insensitive to the {@link RepositoryName} associated with
440
- * the {@link PackageSpecification}.
494
+ * <p>Note that this is ambiguous w.r.t. specs that reference other repositories.
441
495
*/
442
- public Stream <String > containedPackagesWithoutRepository () {
443
- return getStream ().map (PackageSpecification ::toStringWithoutRepository );
496
+ public Stream <String > streamPackageStringsWithoutRepository () {
497
+ return streamSpecs ().map (PackageSpecification ::asStringWithoutRepository );
444
498
}
445
499
446
- private Stream <PackageSpecification > getStream () {
447
- return Stream .concat (
448
- Stream .concat (allSpecifications .stream (), negativePackageSpecifications .stream ()),
449
- singlePackages .values ().stream ());
500
+ private Stream <PackageSpecification > streamSpecs () {
501
+ return Streams .concat (
502
+ otherPositives .stream (), negatives .stream (), singlePositives .values ().stream ());
450
503
}
451
504
}
452
505
}
0 commit comments