diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java index be40a4d900a81d..52b81fb0876a2c 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java @@ -120,11 +120,12 @@ abstract static class PackageContextImpl implements PackageContext {} */ public static Label parseCanonical(String raw) throws LabelSyntaxException { Parts parts = Parts.parse(raw); + parts.checkPkgDoesNotEndWithTripleDots(); parts.checkPkgIsAbsolute(); RepositoryName repoName = - parts.repo == null ? RepositoryName.MAIN : RepositoryName.createUnvalidated(parts.repo); + parts.repo() == null ? RepositoryName.MAIN : RepositoryName.createUnvalidated(parts.repo()); return createUnvalidated( - PackageIdentifier.create(repoName, PathFragment.create(parts.pkg)), parts.target); + PackageIdentifier.create(repoName, PathFragment.create(parts.pkg())), parts.target()); } /** Like {@link #parseCanonical}, but throws an unchecked exception instead. */ @@ -139,18 +140,18 @@ public static Label parseCanonicalUnchecked(String raw) { /** Computes the repo name for the label, within the context of a current repo. */ private static RepositoryName computeRepoNameWithRepoContext( Parts parts, RepoContext repoContext) { - if (parts.repo == null) { + if (parts.repo() == null) { // Certain package names when used without a "@" part are always absolutely in the main repo, // disregarding the current repo and repo mappings. - return ABSOLUTE_PACKAGE_NAMES.contains(parts.pkg) + return ABSOLUTE_PACKAGE_NAMES.contains(parts.pkg()) ? RepositoryName.MAIN : repoContext.currentRepo(); } - if (parts.repoIsCanonical) { + if (parts.repoIsCanonical()) { // This label uses the canonical label literal syntax starting with two @'s ("@@foo//bar"). - return RepositoryName.createUnvalidated(parts.repo); + return RepositoryName.createUnvalidated(parts.repo()); } - return repoContext.repoMapping().get(parts.repo); + return repoContext.repoMapping().get(parts.repo()); } /** @@ -162,10 +163,11 @@ private static RepositoryName computeRepoNameWithRepoContext( public static Label parseWithRepoContext(String raw, RepoContext repoContext) throws LabelSyntaxException { Parts parts = Parts.parse(raw); + parts.checkPkgDoesNotEndWithTripleDots(); parts.checkPkgIsAbsolute(); RepositoryName repoName = computeRepoNameWithRepoContext(parts, repoContext); return createUnvalidated( - PackageIdentifier.create(repoName, PathFragment.create(parts.pkg)), parts.target); + PackageIdentifier.create(repoName, PathFragment.create(parts.pkg())), parts.target()); } /** @@ -178,14 +180,15 @@ public static Label parseWithRepoContext(String raw, RepoContext repoContext) public static Label parseWithPackageContext(String raw, PackageContext packageContext) throws LabelSyntaxException { Parts parts = Parts.parse(raw); + parts.checkPkgDoesNotEndWithTripleDots(); // pkg is either absolute or empty - if (!parts.pkg.isEmpty()) { + if (!parts.pkg().isEmpty()) { parts.checkPkgIsAbsolute(); } RepositoryName repoName = computeRepoNameWithRepoContext(parts, packageContext); PathFragment pkgFragment = - parts.pkgIsAbsolute ? PathFragment.create(parts.pkg) : packageContext.packageFragment(); - return createUnvalidated(PackageIdentifier.create(repoName, pkgFragment), parts.target); + parts.pkgIsAbsolute() ? PathFragment.create(parts.pkg()) : packageContext.packageFragment(); + return createUnvalidated(PackageIdentifier.create(repoName, pkgFragment), parts.target()); } /** @@ -200,7 +203,7 @@ public static Label parseWithPackageContext(String raw, PackageContext packageCo public static Label create(String packageName, String targetName) throws LabelSyntaxException { return createUnvalidated( PackageIdentifier.parse(packageName), - validateAndProcessTargetName(packageName, targetName)); + validateAndProcessTargetName(packageName, targetName, /* pkgEndsWithTripleDots= */ false)); } /** @@ -211,7 +214,10 @@ public static Label create(PackageIdentifier packageId, String targetName) throws LabelSyntaxException { return createUnvalidated( packageId, - validateAndProcessTargetName(packageId.getPackageFragment().getPathString(), targetName)); + validateAndProcessTargetName( + packageId.getPackageFragment().getPathString(), + targetName, + /* pkgEndsWithTripleDots= */ false)); } /** diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java b/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java index 6dc3f6c8dff459..5666e1ab8fc55b 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java @@ -14,6 +14,8 @@ package com.google.devtools.build.lib.cmdline; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; import com.google.devtools.build.lib.util.StringUtilities; import com.google.errorprone.annotations.FormatMethod; import javax.annotation.Nullable; @@ -26,82 +28,83 @@ private LabelParser() {} * Contains the parsed elements of a label string. The parts are validated (they don't contain * invalid characters). See {@link #parse} for valid label patterns. */ - static final class Parts { + @AutoValue + abstract static class Parts { /** * The {@code @repo} or {@code @@canonical_repo} part of the string (sans any leading * {@literal @}s); can be null if it doesn't have such a part (i.e. if it doesn't start with a * {@literal @}). */ - @Nullable final String repo; + @Nullable + abstract String repo(); /** * Whether the repo part is using the canonical repo syntax (two {@literal @}s) or not (one * {@literal @}). If there is no repo part, this is false. */ - final boolean repoIsCanonical; + abstract boolean repoIsCanonical(); /** * Whether the package part of the string is prefixed by double-slash. This can only be false if * the repo part is missing. */ - final boolean pkgIsAbsolute; - /** The package part of the string (sans double-slash, if any). */ - final String pkg; + abstract boolean pkgIsAbsolute(); + /** + * The package part of the string (sans the leading double-slash, if present; also sans the + * final '...' segment, if present). + */ + abstract String pkg(); + /** Whether the package part of the string ends with a '...' segment. */ + abstract boolean pkgEndsWithTripleDots(); /** The target part of the string (sans colon). */ - final String target; + abstract String target(); /** The original unparsed raw string. */ - final String raw; + abstract String raw(); - private Parts( - @Nullable String repo, - boolean repoIsCanonical, - boolean pkgIsAbsolute, - String pkg, - String target, - String raw) { - this.repo = repo; - this.repoIsCanonical = repoIsCanonical; - this.pkgIsAbsolute = pkgIsAbsolute; - this.pkg = pkg; - this.target = target; - this.raw = raw; - } - - private static Parts validateAndCreate( + @VisibleForTesting + static Parts validateAndCreate( @Nullable String repo, boolean repoIsCanonical, boolean pkgIsAbsolute, String pkg, + boolean pkgEndsWithTripleDots, String target, String raw) throws LabelSyntaxException { validateRepoName(repo); validatePackageName(pkg, target); - return new Parts( + return new AutoValue_LabelParser_Parts( repo, repoIsCanonical, pkgIsAbsolute, pkg, - validateAndProcessTargetName(pkg, target), + pkgEndsWithTripleDots, + validateAndProcessTargetName(pkg, target, pkgEndsWithTripleDots), raw); } /** * Parses a raw label string into parts. The logic can be summarized by the following table: * - * {@code - * raw | repo | repoIsCanonical | pkgIsAbsolute | pkg | target - * ----------------------+--------+-----------------+---------------+-----------+----------- - * foo/bar | null | false | false | "" | "foo/bar" - * //foo/bar | null | false | true | "foo/bar" | "bar" - * @repo | "repo" | false | true | "" | "repo" - * @@repo | "repo" | true | true | "" | "repo" - * @repo//foo/bar | "repo" | false | true | "foo/bar" | "bar" - * @@repo//foo/bar | "repo" | true | true | "foo/bar" | "bar" - * :quux | null | false | false | "" | "quux" - * foo/bar:quux | null | false | false | "foo/bar" | "quux" - * //foo/bar:quux | null | false | true | "foo/bar" | "quux" - * @repo//foo/bar:quux | "repo" | false | true | "foo/bar" | "quux" - * @@repo//foo/bar:quux | "repo" | true | true | "foo/bar" | "quux" - * } + *
{@code
+     *  raw                  | repo   | repoIs-   | pkgIs-   | pkg       | pkgEndsWith- | target
+     *                       |        | Canonical | Absolute |           | TripleDots   |
+     * ----------------------+--------+-----------+----------+-----------+--------------+-----------
+     * "foo/bar"             | null   | false     | false    | ""        | false        | "foo/bar"
+     * "..."                 | null   | false     | false    | ""        | true         | ""
+     * "...:all"             | null   | false     | false    | ""        | true         | "all"
+     * "foo/..."             | null   | false     | false    | "foo"     | true         | ""
+     * "//foo/bar"           | null   | false     | true     | "foo/bar" | false        | "bar"
+     * "//foo/..."           | null   | false     | true     | "foo"     | true         | ""
+     * "//foo/...:all"       | null   | false     | true     | "foo"     | true         | "all"
+     * "//foo/all"           | null   | false     | true     | "foo/all" | false        | "all"
+     * "@repo"               | "repo" | false     | true     | ""        | false        | "repo"
+     * "@@repo"              | "repo" | true      | true     | ""        | false        | "repo"
+     * "@repo//foo/bar"      | "repo" | false     | true     | "foo/bar" | false        | "bar"
+     * "@@repo//foo/bar"     | "repo" | true      | true     | "foo/bar" | false        | "bar"
+     * ":quux"               | null   | false     | false    | ""        | false        | "quux"
+     * "foo/bar:quux"        | null   | false     | false    | "foo/bar" | false        | "quux"
+     * "//foo/bar:quux"      | null   | false     | true     | "foo/bar" | false        | "quux"
+     * "@repo//foo/bar:quux" | "repo" | false     | true     | "foo/bar" | false        | "quux"
+     * }
*/ static Parts parse(String rawLabel) throws LabelSyntaxException { @Nullable final String repo; @@ -116,9 +119,10 @@ static Parts parse(String rawLabel) throws LabelSyntaxException { return validateAndCreate( repo, repoIsCanonical, - /*pkgIsAbsolute=*/ true, - /*pkg=*/ "", - /*target=*/ repo, + /* pkgIsAbsolute= */ true, + /* pkg= */ "", + /* pkgEndsWithTripleDots= */ false, + /* target= */ repo, rawLabel); } else { repo = rawLabel.substring(repoIsCanonical ? 2 : 1, doubleSlashIndex); @@ -136,20 +140,39 @@ static Parts parse(String rawLabel) throws LabelSyntaxException { final String pkg; final String target; final int colonIndex = rawLabel.indexOf(':', startOfPackage); - if (colonIndex >= 0) { - pkg = rawLabel.substring(startOfPackage, colonIndex); - target = rawLabel.substring(colonIndex + 1); - } else if (pkgIsAbsolute) { - // Special case: the label "[@repo]//foo/bar" is synonymous with "[@repo]//foo/bar:bar". - pkg = rawLabel.substring(startOfPackage); - // The target name is the last package segment (works even if `pkg` contains no slash) - target = pkg.substring(pkg.lastIndexOf('/') + 1); - } else { + final String rawPkg = + rawLabel.substring(startOfPackage, colonIndex >= 0 ? colonIndex : rawLabel.length()); + final boolean pkgEndsWithTripleDots = rawPkg.endsWith("/...") || rawPkg.equals("..."); + if (colonIndex < 0 && pkgEndsWithTripleDots) { + // Special case: if the entire label ends in '...', the target name is empty. + pkg = stripTrailingTripleDots(rawPkg); + target = ""; + } else if (colonIndex < 0 && !pkgIsAbsolute) { // Special case: the label "foo/bar" is synonymous with ":foo/bar". pkg = ""; target = rawLabel.substring(startOfPackage); + } else { + pkg = stripTrailingTripleDots(rawPkg); + if (colonIndex >= 0) { + target = rawLabel.substring(colonIndex + 1); + } else { + // Special case: the label "[@repo]//foo/bar" is synonymous with "[@repo]//foo/bar:bar". + // The target name is the last package segment (works even if `pkg` contains no slash) + target = pkg.substring(pkg.lastIndexOf('/') + 1); + } + } + return validateAndCreate( + repo, repoIsCanonical, pkgIsAbsolute, pkg, pkgEndsWithTripleDots, target, rawLabel); + } + + private static String stripTrailingTripleDots(String pkg) { + if (pkg.endsWith("/...")) { + return pkg.substring(0, pkg.length() - 4); } - return validateAndCreate(repo, repoIsCanonical, pkgIsAbsolute, pkg, target, rawLabel); + if (pkg.equals("...")) { + return ""; + } + return pkg; } private static void validateRepoName(@Nullable String repo) throws LabelSyntaxException { @@ -167,8 +190,14 @@ private static void validatePackageName(String pkg, String target) throws LabelS } void checkPkgIsAbsolute() throws LabelSyntaxException { - if (!pkgIsAbsolute) { - throw syntaxErrorf("invalid label '%s': absolute label must begin with '@' or '//'", raw); + if (!pkgIsAbsolute()) { + throw syntaxErrorf("invalid label '%s': absolute label must begin with '@' or '//'", raw()); + } + } + + void checkPkgDoesNotEndWithTripleDots() throws LabelSyntaxException { + if (pkgEndsWithTripleDots()) { + throw syntaxErrorf("invalid label '%s': package name cannot contain '...'", raw()); } } } @@ -183,8 +212,12 @@ private static String perhapsYouMeantMessage(String pkg, String target) { return pkg.endsWith('/' + target) ? " (perhaps you meant \":" + target + "\"?)" : ""; } - static String validateAndProcessTargetName(String pkg, String target) - throws LabelSyntaxException { + static String validateAndProcessTargetName( + String pkg, String target, boolean pkgEndsWithTripleDots) throws LabelSyntaxException { + if (pkgEndsWithTripleDots && target.isEmpty()) { + // Allow empty target name if the package part ends in '...'. + return target; + } String targetError = LabelValidator.validateTargetName(target); if (targetError != null) { throw syntaxErrorf( diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java index 86a12c07b8e5d6..b945c6198381b0 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java @@ -121,8 +121,8 @@ public static PackageIdentifier parse(String input) throws LabelSyntaxException } LabelParser.Parts parts = LabelParser.Parts.parse(input + ":dummy_target"); RepositoryName repoName = - parts.repo == null ? RepositoryName.MAIN : RepositoryName.createUnvalidated(parts.repo); - return create(repoName, PathFragment.create(parts.pkg)); + parts.repo() == null ? RepositoryName.MAIN : RepositoryName.createUnvalidated(parts.repo()); + return create(repoName, PathFragment.create(parts.pkg())); } public RepositoryName getRepository() { diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java index d034efdb960f1f..d9886b3d60579d 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java @@ -41,7 +41,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -850,12 +849,6 @@ public String toString() { @Immutable public static final class Parser { - // A valid pattern either starts with exactly 0 slashes (relative pattern) or exactly two - // slashes (absolute pattern). - private static final Pattern VALID_SLASH_PREFIX = Pattern.compile("(//)?([^/]|$)"); - - // TODO(bazel-team): Merge the Label functionality that requires similar constants into this - // class. /** * The set of target-pattern suffixes which indicate wildcards over all rules in a * single package. @@ -869,34 +862,6 @@ public static final class Parser { private static final ImmutableList ALL_TARGETS_IN_SUFFIXES = ImmutableList.of("*", "all-targets"); - private static final List SUFFIXES; - - static { - SUFFIXES = - ImmutableList.builder() - .addAll(ALL_RULES_IN_SUFFIXES) - .addAll(ALL_TARGETS_IN_SUFFIXES) - .add("/...") - .build(); - } - - /** - * Returns whether the given pattern is simple, i.e., not starting with '-' and using none of - * the target matching suffixes. - */ - public static boolean isSimpleTargetPattern(String pattern) { - if (pattern.startsWith("-")) { - return false; - } - - for (String suffix : SUFFIXES) { - if (pattern.endsWith(":" + suffix)) { - return false; - } - } - return true; - } - /** * Directory prefix to use when resolving relative labels (rather than absolute ones). For * example, if the working directory is "/foo", then this should be "foo", which @@ -930,117 +895,73 @@ public Parser( * @throws TargetParsingException if the pattern is invalid */ public TargetPattern parse(String pattern) throws TargetParsingException { - // The structure of this method is by cases, according to the usage string - // constant (see lib/blaze/commands/target-syntax.txt). - - String originalPattern = pattern; - final boolean includesRepo = pattern.startsWith("@"); - RepositoryName repository; - if (!includesRepo) { - repository = currentRepo; - } else { - int pkgStart = pattern.indexOf("//"); - if (pkgStart < 0) { - throw new TargetParsingException( - "Couldn't find package in target " + pattern, TargetPatterns.Code.PACKAGE_NOT_FOUND); - } - boolean isCanonicalRepoName = pattern.startsWith("@@"); - String repoPart = pattern.substring(isCanonicalRepoName ? 2 : 1, pkgStart); - try { - RepositoryName.validate(repoPart); - } catch (LabelSyntaxException e) { - throw new TargetParsingException(e.getMessage(), TargetPatterns.Code.LABEL_SYNTAX_ERROR); - } - if (isCanonicalRepoName) { - repository = RepositoryName.createUnvalidated(repoPart); - } else { - repository = repoMapping.get(repoPart); - if (!repository.isVisible()) { - throw new TargetParsingException( - String.format( - "No repository visible as '@%s' from %s", - repository.getName(), repository.getOwnerRepoDisplayString()), - Code.PACKAGE_NOT_FOUND); - } - } - - pattern = pattern.substring(pkgStart); - } - - if (!VALID_SLASH_PREFIX.matcher(pattern).lookingAt()) { - throw new TargetParsingException( - "not a valid absolute pattern (absolute target patterns " - + "must start with exactly two slashes): '" - + pattern - + "'", - TargetPatterns.Code.ABSOLUTE_TARGET_PATTERN_INVALID); - } - - final boolean wasOriginallyAbsolute = pattern.startsWith("//"); - // We now ensure the relativeDirectory is applied to relative patterns. - pattern = absolutize(pattern).substring(2); - - if (pattern.isEmpty()) { - throw new TargetParsingException( - "the empty string is not a valid target", - TargetPatterns.Code.TARGET_CANNOT_BE_EMPTY_STRING); - } - - int colonIndex = pattern.lastIndexOf(':'); - String packagePart = colonIndex < 0 ? pattern : pattern.substring(0, colonIndex); - String targetPart = colonIndex < 0 ? "" : pattern.substring(colonIndex + 1); - - if (packagePart.equals("...")) { - packagePart = "/..."; // special case this for easier parsing + LabelParser.Parts parts; + try { + parts = LabelParser.Parts.parse(pattern); + } catch (LabelSyntaxException e) { + throw new TargetParsingException(e.getMessage(), TargetPatterns.Code.LABEL_SYNTAX_ERROR); } - if (packagePart.endsWith("/")) { - throw new TargetParsingException( - "The package part of '" + originalPattern + "' should not end in a slash", - TargetPatterns.Code.PACKAGE_PART_CANNOT_END_IN_SLASH); + // Special case: For a target pattern that just looks like `foo/bar/baz`, we treat this as a + // file path. LabelParser parses it as `:foo/bar/baz`, so we need to distinguish this case by + // checking if the original pattern contains a colon. + if (!parts.pkgIsAbsolute() + && currentRepo.isMain() + && parts.pkg().isEmpty() + && !parts.pkgEndsWithTripleDots() + && !pattern.contains(":")) { + return new InterpretPathAsTarget( + pattern, relativeDirectory.getRelative(parts.target()).getPathString()); } - if (packagePart.endsWith("/...")) { - String realPackagePart = packagePart.substring(0, packagePart.length() - "/...".length()); - PackageIdentifier packageIdentifier = createPackageIdentifier(repository, realPackagePart); - if (targetPart.isEmpty() || ALL_RULES_IN_SUFFIXES.contains(targetPart)) { - return new TargetsBelowDirectory(originalPattern, packageIdentifier, true); - } else if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) { - return new TargetsBelowDirectory(originalPattern, packageIdentifier, false); + PackageIdentifier packageIdentifier = createPackageIdentifierFromParts(parts); + if (parts.pkgEndsWithTripleDots()) { + if (parts.target().isEmpty() || ALL_RULES_IN_SUFFIXES.contains(parts.target())) { + return new TargetsBelowDirectory(pattern, packageIdentifier, true); + } else if (ALL_TARGETS_IN_SUFFIXES.contains(parts.target())) { + return new TargetsBelowDirectory(pattern, packageIdentifier, false); } + throw new TargetParsingException( + "Invalid target pattern " + pattern + ": '...' can only be used with wildcard targets", + Code.LABEL_SYNTAX_ERROR); } - if (ALL_RULES_IN_SUFFIXES.contains(targetPart)) { + if (pattern.contains(":") && ALL_RULES_IN_SUFFIXES.contains(parts.target())) { return new TargetsInPackage( - originalPattern, - createPackageIdentifier(repository, packagePart), - targetPart, - wasOriginallyAbsolute, - true); + pattern, packageIdentifier, parts.target(), parts.pkgIsAbsolute(), true); } - if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) { + if (pattern.contains(":") && ALL_TARGETS_IN_SUFFIXES.contains(parts.target())) { return new TargetsInPackage( - originalPattern, - createPackageIdentifier(repository, packagePart), - targetPart, - wasOriginallyAbsolute, - false); + pattern, packageIdentifier, parts.target(), parts.pkgIsAbsolute(), false); } - if (includesRepo || !repository.isMain() || wasOriginallyAbsolute || pattern.contains(":")) { - Label label; - try { - label = Label.parseCanonical(repository.getNameWithAt() + "//" + pattern); - } catch (LabelSyntaxException e) { + return new SingleTarget(pattern, Label.createUnvalidated(packageIdentifier, parts.target())); + } + + private PackageIdentifier createPackageIdentifierFromParts(LabelParser.Parts parts) + throws TargetParsingException { + RepositoryName repo; + if (parts.repo() == null) { + repo = currentRepo; + } else if (parts.repoIsCanonical()) { + repo = RepositoryName.createUnvalidated(parts.repo()); + } else { + repo = repoMapping.get(parts.repo()); + if (!repo.isVisible()) { throw new TargetParsingException( - "invalid target format '" + originalPattern + "': " + e.getMessage(), - TargetPatterns.Code.TARGET_FORMAT_INVALID); + String.format( + "No repository visible as '@%s' from %s", + repo.getName(), repo.getOwnerRepoDisplayString()), + Code.PACKAGE_NOT_FOUND); } - return new SingleTarget(originalPattern, label); } - return new InterpretPathAsTarget(originalPattern, pattern); + PathFragment packagePathFragment = + parts.pkgIsAbsolute() + ? PathFragment.create(parts.pkg()) + : relativeDirectory.getRelative(parts.pkg()); + return PackageIdentifier.create(repo, packagePathFragment); } public RepositoryMapping getRepoMapping() { @@ -1055,16 +976,6 @@ public PathFragment getRelativeDirectory() { return relativeDirectory; } - private PackageIdentifier createPackageIdentifier(RepositoryName repoName, String pkg) - throws TargetParsingException { - String pkgError = LabelValidator.validatePackageName(pkg); - if (pkgError != null) { - throw new TargetParsingException( - "Invalid package name '" + pkg + "': " + pkgError, Code.LABEL_SYNTAX_ERROR); - } - return PackageIdentifier.create(repoName, PathFragment.create(pkg)); - } - /** * Parses a constant string TargetPattern, throwing IllegalStateException on invalid pattern. */ diff --git a/src/test/java/com/google/devtools/build/lib/cmdline/LabelParserTest.java b/src/test/java/com/google/devtools/build/lib/cmdline/LabelParserTest.java new file mode 100644 index 00000000000000..3039f001e42d41 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/cmdline/LabelParserTest.java @@ -0,0 +1,69 @@ +// Copyright 2023 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.cmdline; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.lib.cmdline.LabelParser.Parts.parse; +import static com.google.devtools.build.lib.cmdline.LabelParser.Parts.validateAndCreate; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link LabelParser}. */ +@RunWith(JUnit4.class) +public class LabelParserTest { + + /** Checks that the javadoc of {@link LabelParser.Parts#parse} is accurate. */ + @Test + public void parserTable() throws Exception { + assertThat(parse("foo/bar")) + .isEqualTo(validateAndCreate(null, false, false, "", false, "foo/bar", "foo/bar")); + assertThat(parse("...")).isEqualTo(validateAndCreate(null, false, false, "", true, "", "...")); + assertThat(parse("...:all")) + .isEqualTo(validateAndCreate(null, false, false, "", true, "all", "...:all")); + assertThat(parse("foo/...")) + .isEqualTo(validateAndCreate(null, false, false, "foo", true, "", "foo/...")); + assertThat(parse("//foo/bar")) + .isEqualTo(validateAndCreate(null, false, true, "foo/bar", false, "bar", "//foo/bar")); + assertThat(parse("//foo/...")) + .isEqualTo(validateAndCreate(null, false, true, "foo", true, "", "//foo/...")); + assertThat(parse("//foo/...:all")) + .isEqualTo(validateAndCreate(null, false, true, "foo", true, "all", "//foo/...:all")); + assertThat(parse("//foo/all")) + .isEqualTo(validateAndCreate(null, false, true, "foo/all", false, "all", "//foo/all")); + assertThat(parse("@repo")) + .isEqualTo(validateAndCreate("repo", false, true, "", false, "repo", "@repo")); + assertThat(parse("@@repo")) + .isEqualTo(validateAndCreate("repo", true, true, "", false, "repo", "@@repo")); + assertThat(parse("@repo//foo/bar")) + .isEqualTo( + validateAndCreate("repo", false, true, "foo/bar", false, "bar", "@repo//foo/bar")); + assertThat(parse("@@repo//foo/bar")) + .isEqualTo( + validateAndCreate("repo", true, true, "foo/bar", false, "bar", "@@repo//foo/bar")); + assertThat(parse(":quux")) + .isEqualTo(validateAndCreate(null, false, false, "", false, "quux", ":quux")); + assertThat(parse("foo/bar:quux")) + .isEqualTo(validateAndCreate(null, false, false, "foo/bar", false, "quux", "foo/bar:quux")); + assertThat(parse("//foo/bar:quux")) + .isEqualTo( + validateAndCreate(null, false, true, "foo/bar", false, "quux", "//foo/bar:quux")); + assertThat(parse("@repo//foo/bar:quux")) + .isEqualTo( + validateAndCreate( + "repo", false, true, "foo/bar", false, "quux", "@repo//foo/bar:quux")); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/cmdline/TargetPatternTest.java b/src/test/java/com/google/devtools/build/lib/cmdline/TargetPatternTest.java index eaeb528a698d57..69345ff84d69e2 100644 --- a/src/test/java/com/google/devtools/build/lib/cmdline/TargetPatternTest.java +++ b/src/test/java/com/google/devtools/build/lib/cmdline/TargetPatternTest.java @@ -55,6 +55,7 @@ public void validPatterns_mainRepo_atRepoRoot() throws TargetParsingException { ImmutableMap.of("repo", RepositoryName.createUnvalidated("canonical_repo")), RepositoryName.MAIN)); + assertThat(parser.parse(":foo")).isEqualTo(new SingleTarget(":foo", label("@@//:foo"))); assertThat(parser.parse("foo:bar")) .isEqualTo(new SingleTarget("foo:bar", label("@@//foo:bar"))); assertThat(parser.parse("foo:all")) @@ -80,6 +81,8 @@ public void validPatterns_mainRepo_atRepoRoot() throws TargetParsingException { assertThat(parser.parse("//...")) .isEqualTo(new TargetsBelowDirectory("//...", pkg("@@//"), true)); + assertThat(parser.parse("@repo")) + .isEqualTo(new SingleTarget("@repo", label("@@canonical_repo//:repo"))); assertThat(parser.parse("@repo//foo:bar")) .isEqualTo(new SingleTarget("@repo//foo:bar", label("@@canonical_repo//foo:bar"))); assertThat(parser.parse("@repo//foo:all")) @@ -90,6 +93,9 @@ public void validPatterns_mainRepo_atRepoRoot() throws TargetParsingException { .isEqualTo(new SingleTarget("@repo//:bar", label("@@canonical_repo//:bar"))); assertThat(parser.parse("@repo//...")) .isEqualTo(new TargetsBelowDirectory("@repo//...", pkg("@@canonical_repo//"), true)); + + assertThat(parser.parse("@@repo")) + .isEqualTo(new SingleTarget("@@repo", label("@@repo//:repo"))); assertThat(parser.parse("@@repo//foo:all")) .isEqualTo(new TargetsInPackage("@@repo//foo:all", pkg("@@repo//foo"), "all", true, true)); assertThat(parser.parse("@@repo//:bar")) @@ -106,6 +112,7 @@ public void validPatterns_mainRepo_inSomeRelativeDirectory() throws TargetParsin ImmutableMap.of("repo", RepositoryName.createUnvalidated("canonical_repo")), RepositoryName.MAIN)); + assertThat(parser.parse(":foo")).isEqualTo(new SingleTarget(":foo", label("@@//base:foo"))); assertThat(parser.parse("foo:bar")) .isEqualTo(new SingleTarget("foo:bar", label("@@//base/foo:bar"))); assertThat(parser.parse("foo:all")) @@ -133,6 +140,8 @@ public void validPatterns_mainRepo_inSomeRelativeDirectory() throws TargetParsin assertThat(parser.parse("//...")) .isEqualTo(new TargetsBelowDirectory("//...", pkg("@@//"), true)); + assertThat(parser.parse("@repo")) + .isEqualTo(new SingleTarget("@repo", label("@@canonical_repo//:repo"))); assertThat(parser.parse("@repo//foo:bar")) .isEqualTo(new SingleTarget("@repo//foo:bar", label("@@canonical_repo//foo:bar"))); assertThat(parser.parse("@repo//foo:all")) @@ -143,6 +152,9 @@ public void validPatterns_mainRepo_inSomeRelativeDirectory() throws TargetParsin .isEqualTo(new SingleTarget("@repo//:bar", label("@@canonical_repo//:bar"))); assertThat(parser.parse("@repo//...")) .isEqualTo(new TargetsBelowDirectory("@repo//...", pkg("@@canonical_repo//"), true)); + + assertThat(parser.parse("@@repo")) + .isEqualTo(new SingleTarget("@@repo", label("@@repo//:repo"))); assertThat(parser.parse("@@repo//foo:all")) .isEqualTo(new TargetsInPackage("@@repo//foo:all", pkg("@@repo//foo"), "all", true, true)); assertThat(parser.parse("@@repo//:bar")) @@ -159,6 +171,7 @@ public void validPatterns_nonMainRepo() throws TargetParsingException { ImmutableMap.of("repo", RepositoryName.createUnvalidated("canonical_repo")), RepositoryName.createUnvalidated("my_repo"))); + assertThat(parser.parse(":foo")).isEqualTo(new SingleTarget(":foo", label("@@my_repo//:foo"))); assertThat(parser.parse("foo:bar")) .isEqualTo(new SingleTarget("foo:bar", label("@@my_repo//foo:bar"))); assertThat(parser.parse("foo:all")) @@ -167,15 +180,11 @@ public void validPatterns_nonMainRepo() throws TargetParsingException { .isEqualTo(new TargetsBelowDirectory("foo/...:all", pkg("@@my_repo//foo"), true)); assertThat(parser.parse("foo:*")) .isEqualTo(new TargetsInPackage("foo:*", pkg("@@my_repo//foo"), "*", false, false)); - // TODO(wyv): This is surprising. This means that in a non-main repo, - // `register_toolchains("foo")` is equivalent to `r_t("//foo:foo")`, but in the main repo, it's - // equivalent to `r_t("//:foo")` (which is the more intuitive behavior). - // Granted, nobody probably ever writes this, but we should look into fixing it nonetheless. - assertThat(parser.parse("foo")).isEqualTo(new SingleTarget("foo", label("@@my_repo//foo:foo"))); + assertThat(parser.parse("foo")).isEqualTo(new SingleTarget("foo", label("@@my_repo//:foo"))); assertThat(parser.parse("...")) .isEqualTo(new TargetsBelowDirectory("...", pkg("@@my_repo//"), true)); assertThat(parser.parse("foo/bar")) - .isEqualTo(new SingleTarget("foo/bar", label("@@my_repo//foo/bar:bar"))); + .isEqualTo(new SingleTarget("foo/bar", label("@@my_repo//:foo/bar"))); assertThat(parser.parse("//foo")) .isEqualTo(new SingleTarget("//foo", label("@@my_repo//foo:foo"))); @@ -191,6 +200,8 @@ public void validPatterns_nonMainRepo() throws TargetParsingException { assertThat(parser.parse("//...")) .isEqualTo(new TargetsBelowDirectory("//...", pkg("@@my_repo//"), true)); + assertThat(parser.parse("@repo")) + .isEqualTo(new SingleTarget("@repo", label("@@canonical_repo//:repo"))); assertThat(parser.parse("@repo//foo:bar")) .isEqualTo(new SingleTarget("@repo//foo:bar", label("@@canonical_repo//foo:bar"))); assertThat(parser.parse("@repo//foo:all")) @@ -201,6 +212,9 @@ public void validPatterns_nonMainRepo() throws TargetParsingException { .isEqualTo(new SingleTarget("@repo//:bar", label("@@canonical_repo//:bar"))); assertThat(parser.parse("@repo//...")) .isEqualTo(new TargetsBelowDirectory("@repo//...", pkg("@@canonical_repo//"), true)); + + assertThat(parser.parse("@@repo")) + .isEqualTo(new SingleTarget("@@repo", label("@@repo//:repo"))); assertThat(parser.parse("@@repo//foo:all")) .isEqualTo(new TargetsInPackage("@@repo//foo:all", pkg("@@repo//foo"), "all", true, true)); assertThat(parser.parse("@@repo//:bar")) @@ -210,7 +224,7 @@ public void validPatterns_nonMainRepo() throws TargetParsingException { @Test public void invalidPatterns() throws Exception { ImmutableList badPatterns = - ImmutableList.of("//Bar\\java", "", "@", "@foo", "@foo//", "@@", "@@foo"); + ImmutableList.of("//Bar\\java", "", "/foo", "///foo", "@", "@foo//", "@@"); ImmutableMap repoMappingEntries = ImmutableMap.of("repo", RepositoryName.createUnvalidated("canonical_repo")); for (TargetPattern.Parser parser : diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java index 29ac1530eabd0a..403bf501c392b0 100644 --- a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java +++ b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java @@ -249,8 +249,7 @@ public void testMistypedTarget() { assertThat(e) .hasMessageThat() .contains( - "invalid target format 'foo//bar:missing': invalid package name 'foo//bar': package" - + " names may not contain '//' path separators"); + "invalid package name 'foo//bar': package names may not contain '//' path separators"); ParsingFailedEvent err = tester.findPostOnce(ParsingFailedEvent.class); assertThat(err.getPattern()).isEqualTo("foo//bar:missing"); } @@ -258,7 +257,7 @@ public void testMistypedTarget() { @Test public void testEmptyTarget() { TargetParsingException e = assertThrows(TargetParsingException.class, () -> tester.load("")); - assertThat(e).hasMessageThat().contains("the empty string is not a valid target"); + assertThat(e).hasMessageThat().contains("invalid target name '': empty target name"); } @Test @@ -266,8 +265,7 @@ public void testMistypedTargetKeepGoing() throws Exception { TargetPatternPhaseValue result = tester.loadKeepGoing("foo//bar:missing"); assertThat(result.hasError()).isTrue(); tester.assertContainsError( - "invalid target format 'foo//bar:missing': invalid package name 'foo//bar': package names" - + " may not contain '//' path separators"); + "invalid package name 'foo//bar': package names may not contain '//' path separators"); ParsingFailedEvent err = tester.findPostOnce(ParsingFailedEvent.class); assertThat(err.getPattern()).isEqualTo("foo//bar:missing"); } @@ -1143,8 +1141,7 @@ private void expectError(String pattern, String message) { public void testPatternWithSingleSlashIsError() { expectError( "/single/slash", - "not a valid absolute pattern (absolute target patterns must start with exactly " - + "two slashes): '/single/slash'"); + "invalid target name '/single/slash': target names may not start with '/'"); } @Test @@ -1152,26 +1149,26 @@ public void testPatternWithSingleSlashIsErrorAndOffset() { tester.setRelativeWorkingDirectory("base"); expectError( "/single/slash", - "not a valid absolute pattern (absolute target patterns must start with exactly " - + "two slashes): '/single/slash'"); + "invalid target name '/single/slash': target names may not start with '/'"); } @Test public void testPatternWithTripleSlashIsError() { expectError( "///triple/slash", - "not a valid absolute pattern (absolute target patterns must start with exactly " - + "two slashes): '///triple/slash'"); + "invalid package name '/triple/slash': package names may not start with '/'"); } @Test public void testPatternEndingWithSingleSlashIsError() { - expectError("foo/", "The package part of 'foo/' should not end in a slash"); + expectError("foo/", "invalid target name 'foo/': target names may not end with '/'"); } @Test public void testPatternStartingWithDotDotSlash() { - expectError("../foo", "couldn't determine target from filename '../foo'"); + expectError( + "../foo", + "invalid target name '../foo': target names may not contain up-level references '..'"); } private void runTestPackageLoadingError(boolean keepGoing, String... patterns) throws Exception { diff --git a/src/test/java/com/google/devtools/build/lib/query2/testutil/AbstractQueryTest.java b/src/test/java/com/google/devtools/build/lib/query2/testutil/AbstractQueryTest.java index 8c24a7e41a6e04..9fd8a59da6d62c 100644 --- a/src/test/java/com/google/devtools/build/lib/query2/testutil/AbstractQueryTest.java +++ b/src/test/java/com/google/devtools/build/lib/query2/testutil/AbstractQueryTest.java @@ -82,11 +82,6 @@ public abstract class AbstractQueryTest { private static final String DEFAULT_UNIVERSE = "//...:*"; - protected static final String BAD_PACKAGE_NAME = - "package names may contain " - + "A-Z, a-z, 0-9, or any of ' !\"#$%&'()*+,-./;<=>?[]^_`{|}~' " - + "(most 7-bit ascii characters except 0-31, 127, ':', or '\\')"; - protected MockToolsConfig mockToolsConfig; protected QueryHelper helper; protected AnalysisMock analysisMock; @@ -323,7 +318,7 @@ public void testBadTargetLiterals() throws Exception { protected final void checkResultofBadTargetLiterals(String message, FailureDetail failureDetail) { assertThat(failureDetail.getTargetPatterns().getCode()) .isEqualTo(TargetPatterns.Code.LABEL_SYNTAX_ERROR); - assertThat(message).isEqualTo("Invalid package name 'bad:*': " + BAD_PACKAGE_NAME); + assertThat(message).isEqualTo("invalid target name '*:*': target names may not contain ':'"); } @Test diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java index 929c23a7bea459..93d2ce512af385 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java @@ -259,7 +259,7 @@ public void testRegisterToolchainsInvalidPattern() throws Exception { EvaluationResult evaluationResult = eval(key); Package pkg = evaluationResult.get(key).getPackage(); assertThat(pkg.containsErrors()).isTrue(); - assertContainsEvent("not a valid absolute pattern"); + assertContainsEvent("error parsing target pattern"); } @Test diff --git a/src/test/shell/integration/loading_phase_test.sh b/src/test/shell/integration/loading_phase_test.sh index 478410cd79427c..da44fb8589b24c 100755 --- a/src/test/shell/integration/loading_phase_test.sh +++ b/src/test/shell/integration/loading_phase_test.sh @@ -597,18 +597,6 @@ EOF assert_contains "^${filename}$" $(bazel info "${PRODUCT_NAME}-bin")/$pkg/paths.txt } -function test_path_from_subdir() { - local -r pkg="${FUNCNAME}" - mkdir -p "$pkg/subdir" || fail "could not create \"$pkg/subdir\"" - touch "$pkg/subdir/BUILD" || fail "Could not touch" - echo 'filegroup(name = "foo", srcs = [])' > "$pkg/BUILD" || fail "echo" - cd "$pkg/subdir" - bazel query '../BUILD + ../foo' >output 2> "$TEST_log" \ - || fail "Expected success" - assert_contains "^//$pkg:BUILD" output - assert_contains "^//$pkg:foo" output -} - function test_target_with_BUILD() { local -r pkg="${FUNCNAME}" mkdir -p "$pkg" || fail "could not create \"$pkg\"" diff --git a/src/test/shell/integration/toolchain_test.sh b/src/test/shell/integration/toolchain_test.sh index 24623e24840f8c..035802af91cde3 100755 --- a/src/test/shell/integration/toolchain_test.sh +++ b/src/test/shell/integration/toolchain_test.sh @@ -888,7 +888,7 @@ use_toolchain( EOF bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected" - expect_log "error parsing target pattern \"/:invalid:label:syntax\": not a valid absolute pattern" + expect_log "error parsing target pattern \"/:invalid:label:syntax\": invalid package name '/': package names may not start with '/'" } function test_register_toolchain_error_invalid_target() {