1414
1515package com .google .devtools .build .lib .cmdline ;
1616
17+ import com .google .auto .value .AutoValue ;
18+ import com .google .common .annotations .VisibleForTesting ;
1719import com .google .devtools .build .lib .util .StringUtilities ;
1820import com .google .errorprone .annotations .FormatMethod ;
1921import javax .annotation .Nullable ;
@@ -26,82 +28,83 @@ private LabelParser() {}
2628 * Contains the parsed elements of a label string. The parts are validated (they don't contain
2729 * invalid characters). See {@link #parse} for valid label patterns.
2830 */
29- static final class Parts {
31+ @ AutoValue
32+ abstract static class Parts {
3033 /**
3134 * The {@code @repo} or {@code @@canonical_repo} part of the string (sans any leading
3235 * {@literal @}s); can be null if it doesn't have such a part (i.e. if it doesn't start with a
3336 * {@literal @}).
3437 */
35- @ Nullable final String repo ;
38+ @ Nullable
39+ abstract String repo ();
3640 /**
3741 * Whether the repo part is using the canonical repo syntax (two {@literal @}s) or not (one
3842 * {@literal @}). If there is no repo part, this is false.
3943 */
40- final boolean repoIsCanonical ;
44+ abstract boolean repoIsCanonical () ;
4145 /**
4246 * Whether the package part of the string is prefixed by double-slash. This can only be false if
4347 * the repo part is missing.
4448 */
45- final boolean pkgIsAbsolute ;
46- /** The package part of the string (sans double-slash, if any). */
47- final String pkg ;
49+ abstract boolean pkgIsAbsolute ();
50+ /**
51+ * The package part of the string (sans the leading double-slash, if present; also sans the
52+ * final '...' segment, if present).
53+ */
54+ abstract String pkg ();
55+ /** Whether the package part of the string ends with a '...' segment. */
56+ abstract boolean pkgEndsWithTripleDots ();
4857 /** The target part of the string (sans colon). */
49- final String target ;
58+ abstract String target () ;
5059 /** The original unparsed raw string. */
51- final String raw ;
60+ abstract String raw () ;
5261
53- private Parts (
54- @ Nullable String repo ,
55- boolean repoIsCanonical ,
56- boolean pkgIsAbsolute ,
57- String pkg ,
58- String target ,
59- String raw ) {
60- this .repo = repo ;
61- this .repoIsCanonical = repoIsCanonical ;
62- this .pkgIsAbsolute = pkgIsAbsolute ;
63- this .pkg = pkg ;
64- this .target = target ;
65- this .raw = raw ;
66- }
67-
68- private static Parts validateAndCreate (
62+ @ VisibleForTesting
63+ static Parts validateAndCreate (
6964 @ Nullable String repo ,
7065 boolean repoIsCanonical ,
7166 boolean pkgIsAbsolute ,
7267 String pkg ,
68+ boolean pkgEndsWithTripleDots ,
7369 String target ,
7470 String raw )
7571 throws LabelSyntaxException {
7672 validateRepoName (repo );
7773 validatePackageName (pkg , target );
78- return new Parts (
74+ return new AutoValue_LabelParser_Parts (
7975 repo ,
8076 repoIsCanonical ,
8177 pkgIsAbsolute ,
8278 pkg ,
83- validateAndProcessTargetName (pkg , target ),
79+ pkgEndsWithTripleDots ,
80+ validateAndProcessTargetName (pkg , target , pkgEndsWithTripleDots ),
8481 raw );
8582 }
8683
8784 /**
8885 * Parses a raw label string into parts. The logic can be summarized by the following table:
8986 *
90- * {@code
91- * raw | repo | repoIsCanonical | pkgIsAbsolute | pkg | target
92- * ----------------------+--------+-----------------+---------------+-----------+-----------
93- * foo/bar | null | false | false | "" | "foo/bar"
94- * //foo/bar | null | false | true | "foo/bar" | "bar"
95- * @repo | "repo" | false | true | "" | "repo"
96- * @@repo | "repo" | true | true | "" | "repo"
97- * @repo//foo/bar | "repo" | false | true | "foo/bar" | "bar"
98- * @@repo//foo/bar | "repo" | true | true | "foo/bar" | "bar"
99- * :quux | null | false | false | "" | "quux"
100- * foo/bar:quux | null | false | false | "foo/bar" | "quux"
101- * //foo/bar:quux | null | false | true | "foo/bar" | "quux"
102- * @repo//foo/bar:quux | "repo" | false | true | "foo/bar" | "quux"
103- * @@repo//foo/bar:quux | "repo" | true | true | "foo/bar" | "quux"
104- * }
87+ * <pre>{@code
88+ * raw | repo | repoIs- | pkgIs- | pkg | pkgEndsWith- | target
89+ * | | Canonical | Absolute | | TripleDots |
90+ * ----------------------+--------+-----------+----------+-----------+--------------+-----------
91+ * "foo/bar" | null | false | false | "" | false | "foo/bar"
92+ * "..." | null | false | false | "" | true | ""
93+ * "...:all" | null | false | false | "" | true | "all"
94+ * "foo/..." | null | false | false | "foo" | true | ""
95+ * "//foo/bar" | null | false | true | "foo/bar" | false | "bar"
96+ * "//foo/..." | null | false | true | "foo" | true | ""
97+ * "//foo/...:all" | null | false | true | "foo" | true | "all"
98+ * "//foo/all" | null | false | true | "foo/all" | false | "all"
99+ * "@repo" | "repo" | false | true | "" | false | "repo"
100+ * "@@repo" | "repo" | true | true | "" | false | "repo"
101+ * "@repo//foo/bar" | "repo" | false | true | "foo/bar" | false | "bar"
102+ * "@@repo//foo/bar" | "repo" | true | true | "foo/bar" | false | "bar"
103+ * ":quux" | null | false | false | "" | false | "quux"
104+ * "foo/bar:quux" | null | false | false | "foo/bar" | false | "quux"
105+ * "//foo/bar:quux" | null | false | true | "foo/bar" | false | "quux"
106+ * "@repo//foo/bar:quux" | "repo" | false | true | "foo/bar" | false | "quux"
107+ * }</pre>
105108 */
106109 static Parts parse (String rawLabel ) throws LabelSyntaxException {
107110 @ Nullable final String repo ;
@@ -116,9 +119,10 @@ static Parts parse(String rawLabel) throws LabelSyntaxException {
116119 return validateAndCreate (
117120 repo ,
118121 repoIsCanonical ,
119- /*pkgIsAbsolute=*/ true ,
120- /*pkg=*/ "" ,
121- /*target=*/ repo ,
122+ /* pkgIsAbsolute= */ true ,
123+ /* pkg= */ "" ,
124+ /* pkgEndsWithTripleDots= */ false ,
125+ /* target= */ repo ,
122126 rawLabel );
123127 } else {
124128 repo = rawLabel .substring (repoIsCanonical ? 2 : 1 , doubleSlashIndex );
@@ -136,20 +140,39 @@ static Parts parse(String rawLabel) throws LabelSyntaxException {
136140 final String pkg ;
137141 final String target ;
138142 final int colonIndex = rawLabel .indexOf (':' , startOfPackage );
139- if (colonIndex >= 0 ) {
140- pkg = rawLabel .substring (startOfPackage , colonIndex );
141- target = rawLabel .substring (colonIndex + 1 );
142- } else if (pkgIsAbsolute ) {
143- // Special case: the label "[@repo]//foo/bar" is synonymous with "[@repo]//foo/bar:bar".
144- pkg = rawLabel .substring (startOfPackage );
145- // The target name is the last package segment (works even if `pkg` contains no slash)
146- target = pkg .substring (pkg .lastIndexOf ('/' ) + 1 );
147- } else {
143+ final String rawPkg =
144+ rawLabel .substring (startOfPackage , colonIndex >= 0 ? colonIndex : rawLabel .length ());
145+ final boolean pkgEndsWithTripleDots = rawPkg .endsWith ("/..." ) || rawPkg .equals ("..." );
146+ if (colonIndex < 0 && pkgEndsWithTripleDots ) {
147+ // Special case: if the entire label ends in '...', the target name is empty.
148+ pkg = stripTrailingTripleDots (rawPkg );
149+ target = "" ;
150+ } else if (colonIndex < 0 && !pkgIsAbsolute ) {
148151 // Special case: the label "foo/bar" is synonymous with ":foo/bar".
149152 pkg = "" ;
150153 target = rawLabel .substring (startOfPackage );
154+ } else {
155+ pkg = stripTrailingTripleDots (rawPkg );
156+ if (colonIndex >= 0 ) {
157+ target = rawLabel .substring (colonIndex + 1 );
158+ } else {
159+ // Special case: the label "[@repo]//foo/bar" is synonymous with "[@repo]//foo/bar:bar".
160+ // The target name is the last package segment (works even if `pkg` contains no slash)
161+ target = pkg .substring (pkg .lastIndexOf ('/' ) + 1 );
162+ }
163+ }
164+ return validateAndCreate (
165+ repo , repoIsCanonical , pkgIsAbsolute , pkg , pkgEndsWithTripleDots , target , rawLabel );
166+ }
167+
168+ private static String stripTrailingTripleDots (String pkg ) {
169+ if (pkg .endsWith ("/..." )) {
170+ return pkg .substring (0 , pkg .length () - 4 );
151171 }
152- return validateAndCreate (repo , repoIsCanonical , pkgIsAbsolute , pkg , target , rawLabel );
172+ if (pkg .equals ("..." )) {
173+ return "" ;
174+ }
175+ return pkg ;
153176 }
154177
155178 private static void validateRepoName (@ Nullable String repo ) throws LabelSyntaxException {
@@ -167,8 +190,14 @@ private static void validatePackageName(String pkg, String target) throws LabelS
167190 }
168191
169192 void checkPkgIsAbsolute () throws LabelSyntaxException {
170- if (!pkgIsAbsolute ) {
171- throw syntaxErrorf ("invalid label '%s': absolute label must begin with '@' or '//'" , raw );
193+ if (!pkgIsAbsolute ()) {
194+ throw syntaxErrorf ("invalid label '%s': absolute label must begin with '@' or '//'" , raw ());
195+ }
196+ }
197+
198+ void checkPkgDoesNotEndWithTripleDots () throws LabelSyntaxException {
199+ if (pkgEndsWithTripleDots ()) {
200+ throw syntaxErrorf ("invalid label '%s': package name cannot contain '...'" , raw ());
172201 }
173202 }
174203 }
@@ -183,8 +212,12 @@ private static String perhapsYouMeantMessage(String pkg, String target) {
183212 return pkg .endsWith ('/' + target ) ? " (perhaps you meant \" :" + target + "\" ?)" : "" ;
184213 }
185214
186- static String validateAndProcessTargetName (String pkg , String target )
187- throws LabelSyntaxException {
215+ static String validateAndProcessTargetName (
216+ String pkg , String target , boolean pkgEndsWithTripleDots ) throws LabelSyntaxException {
217+ if (pkgEndsWithTripleDots && target .isEmpty ()) {
218+ // Allow empty target name if the package part ends in '...'.
219+ return target ;
220+ }
188221 String targetError = LabelValidator .validateTargetName (target );
189222 if (targetError != null ) {
190223 throw syntaxErrorf (
0 commit comments