14
14
15
15
package com .google .devtools .build .lib .cmdline ;
16
16
17
+ import com .google .auto .value .AutoValue ;
18
+ import com .google .common .annotations .VisibleForTesting ;
17
19
import com .google .devtools .build .lib .util .StringUtilities ;
18
20
import com .google .errorprone .annotations .FormatMethod ;
19
21
import javax .annotation .Nullable ;
@@ -26,82 +28,83 @@ private LabelParser() {}
26
28
* Contains the parsed elements of a label string. The parts are validated (they don't contain
27
29
* invalid characters). See {@link #parse} for valid label patterns.
28
30
*/
29
- static final class Parts {
31
+ @ AutoValue
32
+ abstract static class Parts {
30
33
/**
31
34
* The {@code @repo} or {@code @@canonical_repo} part of the string (sans any leading
32
35
* {@literal @}s); can be null if it doesn't have such a part (i.e. if it doesn't start with a
33
36
* {@literal @}).
34
37
*/
35
- @ Nullable final String repo ;
38
+ @ Nullable
39
+ abstract String repo ();
36
40
/**
37
41
* Whether the repo part is using the canonical repo syntax (two {@literal @}s) or not (one
38
42
* {@literal @}). If there is no repo part, this is false.
39
43
*/
40
- final boolean repoIsCanonical ;
44
+ abstract boolean repoIsCanonical () ;
41
45
/**
42
46
* Whether the package part of the string is prefixed by double-slash. This can only be false if
43
47
* the repo part is missing.
44
48
*/
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 ();
48
57
/** The target part of the string (sans colon). */
49
- final String target ;
58
+ abstract String target () ;
50
59
/** The original unparsed raw string. */
51
- final String raw ;
60
+ abstract String raw () ;
52
61
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 (
69
64
@ Nullable String repo ,
70
65
boolean repoIsCanonical ,
71
66
boolean pkgIsAbsolute ,
72
67
String pkg ,
68
+ boolean pkgEndsWithTripleDots ,
73
69
String target ,
74
70
String raw )
75
71
throws LabelSyntaxException {
76
72
validateRepoName (repo );
77
73
validatePackageName (pkg , target );
78
- return new Parts (
74
+ return new AutoValue_LabelParser_Parts (
79
75
repo ,
80
76
repoIsCanonical ,
81
77
pkgIsAbsolute ,
82
78
pkg ,
83
- validateAndProcessTargetName (pkg , target ),
79
+ pkgEndsWithTripleDots ,
80
+ validateAndProcessTargetName (pkg , target , pkgEndsWithTripleDots ),
84
81
raw );
85
82
}
86
83
87
84
/**
88
85
* Parses a raw label string into parts. The logic can be summarized by the following table:
89
86
*
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>
105
108
*/
106
109
static Parts parse (String rawLabel ) throws LabelSyntaxException {
107
110
@ Nullable final String repo ;
@@ -116,9 +119,10 @@ static Parts parse(String rawLabel) throws LabelSyntaxException {
116
119
return validateAndCreate (
117
120
repo ,
118
121
repoIsCanonical ,
119
- /*pkgIsAbsolute=*/ true ,
120
- /*pkg=*/ "" ,
121
- /*target=*/ repo ,
122
+ /* pkgIsAbsolute= */ true ,
123
+ /* pkg= */ "" ,
124
+ /* pkgEndsWithTripleDots= */ false ,
125
+ /* target= */ repo ,
122
126
rawLabel );
123
127
} else {
124
128
repo = rawLabel .substring (repoIsCanonical ? 2 : 1 , doubleSlashIndex );
@@ -136,20 +140,39 @@ static Parts parse(String rawLabel) throws LabelSyntaxException {
136
140
final String pkg ;
137
141
final String target ;
138
142
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 ) {
148
151
// Special case: the label "foo/bar" is synonymous with ":foo/bar".
149
152
pkg = "" ;
150
153
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 );
151
171
}
152
- return validateAndCreate (repo , repoIsCanonical , pkgIsAbsolute , pkg , target , rawLabel );
172
+ if (pkg .equals ("..." )) {
173
+ return "" ;
174
+ }
175
+ return pkg ;
153
176
}
154
177
155
178
private static void validateRepoName (@ Nullable String repo ) throws LabelSyntaxException {
@@ -167,8 +190,14 @@ private static void validatePackageName(String pkg, String target) throws LabelS
167
190
}
168
191
169
192
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 ());
172
201
}
173
202
}
174
203
}
@@ -183,8 +212,12 @@ private static String perhapsYouMeantMessage(String pkg, String target) {
183
212
return pkg .endsWith ('/' + target ) ? " (perhaps you meant \" :" + target + "\" ?)" : "" ;
184
213
}
185
214
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
+ }
188
221
String targetError = LabelValidator .validateTargetName (target );
189
222
if (targetError != null ) {
190
223
throw syntaxErrorf (
0 commit comments