| // Copyright 2014 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.packages; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; | |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; | |
| import com.google.devtools.build.lib.cmdline.RepositoryMapping; | |
| import com.google.devtools.build.lib.cmdline.RepositoryName; | |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetsShouldBeInternedByEquality; | |
| import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import javax.annotation.Nullable; | |
| import net.starlark.java.eval.EvalException; | |
| import net.starlark.java.eval.Starlark; | |
| /** | |
| * Represents one of the following: | |
| * | |
| * <ul> | |
| * <li>A single package (e.g. "//foo/bar") | |
| * <li>All transitive subpackages of a package, inclusive (e.g. "//foo/bar/...", which includes | |
| * "//foo/bar") | |
| * <li>All packages (i.e. "//...") | |
| * </ul> | |
| * | |
| * <p>Typically (exclusively?) used for package visibility, as part of a {@link PackageGroup} | |
| * target. | |
| * | |
| * <p>A package specification is specific to a single {@link RepositoryName} unless it is the "all | |
| * packages" specification. | |
| */ | |
| // TODO(b/279784354): Delete PackageSpecification; reimplement bzl visibility using something else. | |
| public abstract class PackageSpecification { | |
| private static final String PUBLIC_VISIBILITY = "public"; | |
| private static final String PRIVATE_VISIBILITY = "private"; | |
| private static final String ALL_BENEATH_SUFFIX = "/..."; | |
| private static final String NEGATIVE_PREFIX = "-"; | |
| // Used for interpreting `visibility` labels. | |
| private static final String PACKAGE_LABEL = "__pkg__"; | |
| private static final String SUBTREE_LABEL = "__subpackages__"; | |
| /** Returns {@code true} if the package spec includes the provided {@code packageName}. */ | |
| protected abstract boolean containsPackage(PackageIdentifier packageName); | |
| /** | |
| * Returns a string representation of this package spec. | |
| * | |
| * <p>The repository is included, unless it is the main repository, in which case there will be no | |
| * leading {@literal @}. For instance, {@code "@somerepo//pkg/subpkg"} and {@code | |
| * "//otherpkg/..."} are both valid outputs. | |
| * | |
| * <p>Note that since {@link #fromString} does not accept label strings with repositories, this | |
| * representation is not guaranteed to be round-trippable. | |
| * | |
| * <p>If {@code includeDoubleSlash} is false, then in the case of the main repository, the leading | |
| * {@code //} will also be omitted, so that the output looks like {@code otherpkg/...}. This form | |
| * is deprecated. | |
| */ | |
| // TODO(b/77598306): Remove the parameter after switching all callers to pass true. | |
| protected abstract String asString(boolean includeDoubleSlash); | |
| @Override | |
| public String toString() { | |
| return asString(/*includeDoubleSlash=*/ false); | |
| } | |
| /** | |
| * Parses the string {@code spec} into a {@link PackageSpecification}, within the context of the | |
| * given repository name. | |
| * | |
| * <p>{@code spec} may have the following forms: | |
| * | |
| * <ol> | |
| * <li>The full name of a single package, without repository qualification, prefixed with "//" | |
| * (e.g. "//foo/bar"). The resulting specification contains exactly that package. | |
| * <li>The same, but suffixed with "/..." for a non-root package ("//foo/bar/...") or "..." for | |
| * the root package ("//..."). The resulting specification contains that package and all its | |
| * subpackages. | |
| * <li>The string constants "public" or "private". The resulting specification contains either | |
| * all packages or no packages, respectively. | |
| * </ol> | |
| * | |
| * In the first two cases, the repository of the given package name is taken to be {@code | |
| * repositoryName}. In the third case the repository name is ignored. | |
| * | |
| * <p>In the first two cases, {@code spec} may also be prefixed by a "-". The resulting | |
| * specification contains the same set of packages but is marked as being negated. (Negation logic | |
| * is applied at the level of {@link PackageGroupContents}.) | |
| * | |
| * <p>Setting {@code allowPublicPrivate} to false disallows the string constants "public" and | |
| * "private". Note that if {@link #asString} is called with {@code includeDoubleSlash} set to | |
| * false, the stringification of "public" and "//public" is ambiguous (likewise for private), | |
| * hence why it might be appropriate to prohibit these forms. | |
| * | |
| * <p>Setting {@code repoRootMeansCurrentRepo} to false restores the following legacy behavior: In | |
| * the specific case where {@code spec} is "//..." (or its negation), the package specification | |
| * contains <i>all</i> packages (possibly marked as negated) rather than just those packages in | |
| * {@code repositoryName}. In other words, "//..." behaves the same as "public". However, "//" | |
| * still represents just the root package of {@code repositoryName}. | |
| * | |
| * <p>To protect against requiring users to update to a disallowed syntax, it is illegal to | |
| * specify {@code repoRootMeansCurrentRepo} without also specifying {@code allowPublicPrivate}. | |
| * | |
| * @throws InvalidPackageSpecificationException if the string does not fit one of these forms | |
| */ | |
| // TODO(#16365): Remove allowPublicPrivate. | |
| // TODO(#16324): Remove legacy behavior and repoRootMeansCurrentRepo param. | |
| public static PackageSpecification fromString( | |
| RepositoryMapping repositoryMapping, | |
| RepositoryName repositoryName, | |
| String spec, | |
| boolean allowPublicPrivate, | |
| boolean repoRootMeansCurrentRepo) | |
| throws InvalidPackageSpecificationException { | |
| if (repoRootMeansCurrentRepo && !allowPublicPrivate) { | |
| throw new InvalidPackageSpecificationException( | |
| "Cannot use new \"//...\" meaning without allowing new \"public\" syntax. Try enabling" | |
| + " --incompatible_package_group_has_public_syntax or disabling" | |
| + " --incompatible_fix_package_group_reporoot_syntax."); | |
| } | |
| if (!allowPublicPrivate | |
| && (spec.equals(PUBLIC_VISIBILITY) || spec.equals(PRIVATE_VISIBILITY))) { | |
| throw new InvalidPackageSpecificationException( | |
| String.format( | |
| "Use of \"%s\" package specification requires enabling" | |
| + " --incompatible_package_group_has_public_syntax", | |
| spec)); | |
| } | |
| boolean negative = false; | |
| if (spec.startsWith(NEGATIVE_PREFIX)) { | |
| negative = true; | |
| spec = spec.substring(NEGATIVE_PREFIX.length()); | |
| if (spec.equals(PUBLIC_VISIBILITY) || spec.equals(PRIVATE_VISIBILITY)) { | |
| throw new InvalidPackageSpecificationException( | |
| String.format("Cannot negate \"%s\" package specification", spec)); | |
| } | |
| } | |
| PackageSpecification packageSpecification = | |
| fromStringPositive(repositoryMapping, repositoryName, spec, repoRootMeansCurrentRepo); | |
| return negative ? new NegativePackageSpecification(packageSpecification) : packageSpecification; | |
| } | |
| private static PackageSpecification fromStringPositive( | |
| RepositoryMapping repositoryMapping, | |
| RepositoryName repositoryName, | |
| String spec, | |
| boolean repoRootMeansCurrentRepo) | |
| throws InvalidPackageSpecificationException { | |
| if (spec.equals(PUBLIC_VISIBILITY)) { | |
| return AllPackages.INSTANCE; | |
| } else if (spec.equals(PRIVATE_VISIBILITY)) { | |
| return NoPackages.INSTANCE; | |
| } | |
| if (!spec.startsWith("//") && !spec.startsWith("@")) { | |
| throw new InvalidPackageSpecificationException( | |
| String.format( | |
| "invalid package name '%s': must start with '//', '@', or be 'public' or 'private'", | |
| spec)); | |
| } | |
| try { | |
| Label label = | |
| Label.parseWithRepoContext(spec, Label.RepoContext.of(repositoryName, repositoryMapping)); | |
| PackageSpecification mappedSpec = fromLabel(label); | |
| if (mappedSpec != null) { | |
| return mappedSpec; | |
| } | |
| } catch (LabelSyntaxException e) { | |
| // Fall through to parse as a package path (e.g. //foo/...) | |
| } | |
| String pkgPath; | |
| boolean allBeneath = false; | |
| if (spec.endsWith(ALL_BENEATH_SUFFIX)) { | |
| allBeneath = true; | |
| pkgPath = spec.substring(0, spec.length() - ALL_BENEATH_SUFFIX.length()); | |
| if (pkgPath.equals("/")) { | |
| // spec was "//...". | |
| if (repoRootMeansCurrentRepo) { | |
| pkgPath = "//"; | |
| } else { | |
| // Legacy behavior: //... is "public". | |
| return AllPackages.INSTANCE; | |
| } | |
| } else if (spec.endsWith("//...")) { | |
| // spec was "@repo//..." | |
| pkgPath += "/"; | |
| } | |
| } else { | |
| pkgPath = spec; | |
| } | |
| PackageIdentifier pkgId; | |
| try { | |
| pkgId = | |
| Label.parseWithRepoContext( | |
| pkgPath + ":__pkg__", Label.RepoContext.of(repositoryName, repositoryMapping)) | |
| .getPackageIdentifier(); | |
| } catch (LabelSyntaxException e) { | |
| throw new InvalidPackageSpecificationException( | |
| String.format("invalid package name '%s': %s", spec, e.getMessage())); | |
| } | |
| return allBeneath ? new AllPackagesBeneath(pkgId) : new SinglePackage(pkgId); | |
| } | |
| /** | |
| * Parses a string to a {@code PackageSpecification} for use with .bzl load visibility. | |
| * | |
| * <p>This rejects negative package patterns, and translates the exception type into {@code | |
| * EvalException}. | |
| * | |
| * <p>Note that load visibility package specifications always behave as if {@code | |
| * --incompatible_package_group_has_public_syntax} and {@code | |
| * --incompatible_fix_package_group_reporoot_syntax} are enabled. | |
| */ | |
| public static PackageSpecification fromStringForBzlVisibility( | |
| RepositoryMapping repoMapping, RepositoryName repositoryName, String spec) | |
| throws EvalException { | |
| PackageSpecification result; | |
| try { | |
| result = | |
| fromString( | |
| repoMapping, | |
| repositoryName, | |
| spec, | |
| /* allowPublicPrivate= */ true, | |
| /* repoRootMeansCurrentRepo= */ true); | |
| } catch (InvalidPackageSpecificationException e) { | |
| throw new EvalException(e.getMessage()); | |
| } | |
| if (result instanceof NegativePackageSpecification) { | |
| throw Starlark.errorf("Cannot use negative package patterns here"); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Parses the provided {@link Label} into a {@link PackageSpecification} specific to the {@link | |
| * RepositoryName} associated with the label. | |
| * | |
| * <p>If {@code label.getName.equals("__pkg__")} then this results in a {@link | |
| * PackageSpecification} that contains exactly the named package. | |
| * | |
| * <p>If {@code label.getName.equals("__subpackages__")} then this results in a {@link | |
| * PackageSpecification} that contains all transitive subpackages of that package, inclusive. | |
| * | |
| * <p>If the label's name is neither "__pkg__" nor "__subpackages__", this returns {@code null}. | |
| * | |
| * <p>Note that there is no {@link Label} associated with the {@link RepositoryName}-agnostic | |
| * "public" specification ("//..." under legacy semantics). | |
| */ | |
| @Nullable | |
| static PackageSpecification fromLabel(Label label) { | |
| if (label.getName().equals(PACKAGE_LABEL)) { | |
| return new SinglePackage(label.getPackageIdentifier()); | |
| } else if (label.getName().equals(SUBTREE_LABEL)) { | |
| return new AllPackagesBeneath(label.getPackageIdentifier()); | |
| } else { | |
| return null; | |
| } | |
| } | |
| public static PackageSpecification everything() { | |
| return AllPackages.INSTANCE; | |
| } | |
| public static PackageSpecification nothing() { | |
| return NoPackages.INSTANCE; | |
| } | |
| private static final class SinglePackage extends PackageSpecification { | |
| private final PackageIdentifier singlePackageName; | |
| SinglePackage(PackageIdentifier singlePackageName) { | |
| this.singlePackageName = singlePackageName; | |
| } | |
| @Override | |
| protected boolean containsPackage(PackageIdentifier packageName) { | |
| return this.singlePackageName.equals(packageName); | |
| } | |
| @Override | |
| protected String asString(boolean includeDoubleSlash) { | |
| return PackageGroupContents.stringForSinglePackage(singlePackageName, includeDoubleSlash); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (!(o instanceof SinglePackage)) { | |
| return false; | |
| } | |
| SinglePackage that = (SinglePackage) o; | |
| return singlePackageName.equals(that.singlePackageName); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return singlePackageName.hashCode(); | |
| } | |
| } | |
| private static final class AllPackagesBeneath extends PackageSpecification { | |
| private final PackageIdentifier prefix; | |
| AllPackagesBeneath(PackageIdentifier prefix) { | |
| this.prefix = prefix; | |
| } | |
| @Override | |
| protected boolean containsPackage(PackageIdentifier packageName) { | |
| return packageName.getRepository().equals(prefix.getRepository()) | |
| && packageName.getPackageFragment().startsWith(prefix.getPackageFragment()); | |
| } | |
| @Override | |
| protected String asString(boolean includeDoubleSlash) { | |
| return PackageGroupContents.stringForAllPackagesBeneath(prefix, includeDoubleSlash); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (!(o instanceof AllPackagesBeneath)) { | |
| return false; | |
| } | |
| AllPackagesBeneath that = (AllPackagesBeneath) o; | |
| return prefix.equals(that.prefix); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return prefix.hashCode(); | |
| } | |
| } | |
| /** A package specification for a negative match, e.g. {@code -//pkg/sub/...}. */ | |
| private static final class NegativePackageSpecification extends PackageSpecification { | |
| private final PackageSpecification delegate; | |
| NegativePackageSpecification(PackageSpecification delegate) { | |
| this.delegate = delegate; | |
| } | |
| @Override | |
| protected boolean containsPackage(PackageIdentifier packageName) { | |
| return delegate.containsPackage(packageName); | |
| } | |
| @Override | |
| protected String asString(boolean includeDoubleSlash) { | |
| return "-" + delegate.asString(includeDoubleSlash); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) { | |
| return true; | |
| } | |
| return obj instanceof NegativePackageSpecification | |
| && delegate.equals(((NegativePackageSpecification) obj).delegate); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return NegativePackageSpecification.class.hashCode() ^ delegate.hashCode(); | |
| } | |
| } | |
| @VisibleForSerialization | |
| static final class AllPackages extends PackageSpecification { | |
| @SerializationConstant @VisibleForSerialization | |
| static final PackageSpecification INSTANCE = new AllPackages(); | |
| @Override | |
| protected boolean containsPackage(PackageIdentifier packageName) { | |
| return true; | |
| } | |
| @Override | |
| protected String asString(boolean includeDoubleSlash) { | |
| return PackageGroupContents.stringForAllPackages(includeDoubleSlash); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| return o instanceof AllPackages; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return AllPackages.class.hashCode(); | |
| } | |
| } | |
| @VisibleForSerialization | |
| static final class NoPackages extends PackageSpecification { | |
| @SerializationConstant @VisibleForSerialization | |
| static final PackageSpecification INSTANCE = new NoPackages(); | |
| @Override | |
| protected boolean containsPackage(PackageIdentifier packageName) { | |
| return false; | |
| } | |
| @Override | |
| protected String asString(boolean includeDoubleSlash) { | |
| return "private"; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| return o instanceof NoPackages; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return NoPackages.class.hashCode(); | |
| } | |
| } | |
| /** Exception class to be thrown when a specification cannot be parsed. */ | |
| static class InvalidPackageSpecificationException extends Exception { | |
| private InvalidPackageSpecificationException(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * Represents a collection of {@link PackageSpecification}s logically corresponding to a single | |
| * {@code package_group}'s {@code packages} attribute. | |
| * | |
| * <p>Supports testing whether a given package is contained, taking into account negative specs. | |
| * | |
| * <p>Duplicate specs (e.g., ["//foo", "//foo"]) may or may not be deduplicated. Iteration order | |
| * may vary from the order in which specs were provided, but is guaranteed to be deterministic. | |
| * | |
| * <p>For modeling a {@code package_group}'s transitive contents (i.e., via the {@code includes} | |
| * attribute), see {@link PackageSpecificationProvider}. | |
| */ | |
| @AutoValue | |
| public abstract static class PackageGroupContents | |
| implements NestedSetsShouldBeInternedByEquality { | |
| // This class is optimized for memory and cpu. | |
| // TODO(b/279784354): Further improvements are possible. | |
| // | |
| // PackageGroupContents instances are retained through and after the analysis phase. Therefore, | |
| // in order to save memory, we don't retain PackageSpecification instances but instead unroll | |
| // them, storing just lists and sets of PackageIdentifier instances. | |
| // | |
| // To save cpu, we store the PackageIdentifier instances corresponding to positive/negative | |
| // single package specifications in sets so we get a fast-path hit on them. Also, we check | |
| // negatives first since package_group semantics require we check all negatives no matter what. | |
| // Both of these cpu optimizations combine well in practice since there are some package_group | |
| // targets with very large lists of negative single package specifications. | |
| abstract ImmutableSet<PackageIdentifier> singlePackagePositives(); | |
| abstract ImmutableList<PackageIdentifier> allPackagesBeneathPositives(); | |
| abstract boolean hasPositiveAllPackages(); | |
| abstract ImmutableSet<PackageIdentifier> singlePackageNegatives(); | |
| abstract ImmutableList<PackageIdentifier> allPackagesBeneathNegatives(); | |
| abstract boolean hasPrivate(); | |
| abstract boolean hasNegativeAllPackages(); | |
| /** | |
| * Creates a {@link PackageGroupContents} representing a collection of {@link | |
| * PackageSpecification}s. | |
| */ | |
| public static PackageGroupContents create( | |
| ImmutableList<PackageSpecification> packageSpecifications) { | |
| var singlePackagePositivesBuilder = ImmutableSet.<PackageIdentifier>builder(); | |
| var allPackagesBeneathPositivesBuilder = ImmutableList.<PackageIdentifier>builder(); | |
| boolean hasPositiveAllPackages = false; | |
| var singlePackageNegativesBuilder = ImmutableSet.<PackageIdentifier>builder(); | |
| var allPackagesBeneathNegativesBuilder = ImmutableList.<PackageIdentifier>builder(); | |
| boolean hasPrivate = false; | |
| boolean hasNegativeAllPackages = false; | |
| for (PackageSpecification spec : packageSpecifications) { | |
| if (spec instanceof SinglePackage) { | |
| singlePackagePositivesBuilder.add(((SinglePackage) spec).singlePackageName); | |
| continue; | |
| } | |
| if (spec instanceof AllPackagesBeneath) { | |
| allPackagesBeneathPositivesBuilder.add(((AllPackagesBeneath) spec).prefix); | |
| continue; | |
| } | |
| if (spec instanceof AllPackages) { | |
| hasPositiveAllPackages = true; | |
| continue; | |
| } | |
| if (spec instanceof NoPackages) { | |
| // We can't drop NoPackages because it still needs to be serialized, e.g. in bazel query | |
| // output. | |
| hasPrivate = true; | |
| continue; | |
| } | |
| PackageSpecification delegate = ((NegativePackageSpecification) spec).delegate; | |
| if (delegate instanceof SinglePackage) { | |
| singlePackageNegativesBuilder.add(((SinglePackage) delegate).singlePackageName); | |
| continue; | |
| } | |
| if (delegate instanceof AllPackagesBeneath) { | |
| allPackagesBeneathNegativesBuilder.add(((AllPackagesBeneath) delegate).prefix); | |
| continue; | |
| } | |
| if (delegate instanceof AllPackages) { | |
| hasNegativeAllPackages = true; | |
| continue; | |
| } | |
| throw new IllegalStateException(spec.toString()); | |
| } | |
| return new AutoValue_PackageSpecification_PackageGroupContents( | |
| singlePackagePositivesBuilder.build(), | |
| allPackagesBeneathPositivesBuilder.build(), | |
| hasPositiveAllPackages, | |
| singlePackageNegativesBuilder.build(), | |
| allPackagesBeneathNegativesBuilder.build(), | |
| hasPrivate, | |
| hasNegativeAllPackages); | |
| } | |
| /** | |
| * Returns true if the given package matches at least one of this {@code PackageGroupContents}' | |
| * positive specifications and none of its negative specifications. | |
| */ | |
| public final boolean containsPackage(PackageIdentifier packageIdentifier) { | |
| // DO NOT use streams or iterators here as they create excessive garbage. | |
| if (hasNegativeAllPackages()) { | |
| return false; | |
| } | |
| if (singlePackageNegatives().contains(packageIdentifier)) { | |
| return false; | |
| } | |
| // The following line is just so that we don't call the method inside the loop (which may or | |
| // may not be optimized away... better be on the safe side). | |
| var allPackagesBeneathNegatives = allPackagesBeneathNegatives(); | |
| for (int i = 0; i < allPackagesBeneathNegatives.size(); i++) { | |
| if (matchesAllPackagesBeneath(packageIdentifier, allPackagesBeneathNegatives.get(i))) { | |
| return false; | |
| } | |
| } | |
| if (hasPositiveAllPackages()) { | |
| return true; | |
| } | |
| if (singlePackagePositives().contains(packageIdentifier)) { | |
| return true; | |
| } | |
| var allPackagesBeneathPositives = allPackagesBeneathPositives(); | |
| for (int i = 0; i < allPackagesBeneathPositives.size(); i++) { | |
| if (matchesAllPackagesBeneath(packageIdentifier, allPackagesBeneathPositives.get(i))) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private static boolean matchesAllPackagesBeneath( | |
| PackageIdentifier pkgId, PackageIdentifier prefix) { | |
| return pkgId.getRepository().equals(prefix.getRepository()) | |
| && pkgId.getPackageFragment().startsWith(prefix.getPackageFragment()); | |
| } | |
| /** | |
| * Does the equivalent of mapping {@link PackageSpecification#asString} to the component package | |
| * specs. | |
| * | |
| * <p>Note that strings for specs that cross repositories can't be reparsed using {@link | |
| * PackageSpecification#fromString}. | |
| * | |
| * <p>The special public constant will serialize as {@code "public"} if {@code | |
| * includeDoubleSlash} is true, and {@code "//..."} otherwise. The private constant will always | |
| * serialize as {@code "private"}, | |
| */ | |
| public final ImmutableList<String> packageStrings(boolean includeDoubleSlash) { | |
| ImmutableList.Builder<String> resultBuilder = ImmutableList.builder(); | |
| for (PackageIdentifier pkgId : singlePackagePositives()) { | |
| resultBuilder.add(stringForSinglePackage(pkgId, includeDoubleSlash)); | |
| } | |
| for (PackageIdentifier pkgId : allPackagesBeneathPositives()) { | |
| resultBuilder.add(stringForAllPackagesBeneath(pkgId, includeDoubleSlash)); | |
| } | |
| for (PackageIdentifier pkgId : singlePackageNegatives()) { | |
| resultBuilder.add("-" + stringForSinglePackage(pkgId, includeDoubleSlash)); | |
| } | |
| for (PackageIdentifier pkgId : allPackagesBeneathNegatives()) { | |
| resultBuilder.add("-" + stringForAllPackagesBeneath(pkgId, includeDoubleSlash)); | |
| } | |
| if (hasPositiveAllPackages()) { | |
| resultBuilder.add(stringForAllPackages(includeDoubleSlash)); | |
| } | |
| if (hasPrivate()) { | |
| resultBuilder.add("private"); | |
| } | |
| if (hasNegativeAllPackages()) { | |
| resultBuilder.add("-" + stringForAllPackages(includeDoubleSlash)); | |
| } | |
| return resultBuilder.build(); | |
| } | |
| private static String stringForAllPackages(boolean includeDoubleSlash) { | |
| // Under legacy formatting rules, use legacy syntax. This avoids ambiguity between "public" | |
| // and "//public", and ensures that AllPackages is round-trippable when the value of | |
| // includeDoubleSlash matches allowPublicPrivate. | |
| return includeDoubleSlash ? "public" : "//..."; | |
| } | |
| private static String stringForSinglePackage( | |
| PackageIdentifier pkgId, boolean includeDoubleSlash) { | |
| if (includeDoubleSlash) { | |
| return pkgId.getCanonicalForm(); | |
| } else { | |
| // PackageIdentifier#toString implements the legacy behavior of omitting the double slash | |
| // for the main repo. | |
| return pkgId.toString(); | |
| } | |
| } | |
| private static String stringForAllPackagesBeneath( | |
| PackageIdentifier pkgId, boolean includeDoubleSlash) { | |
| if (pkgId.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) { | |
| // Special case: Emit "//..." rather than suffixing "/...", which would yield "/...". | |
| // Make sure not to strip the repo in the case of "@repo//...". | |
| // | |
| // Note that "//..." is the desired result, not "...", even under the legacy behavior of | |
| // includeDoubleSlash=false. | |
| return pkgId.getCanonicalForm() + "..."; | |
| } | |
| if (includeDoubleSlash) { | |
| return pkgId.getCanonicalForm() + ALL_BENEATH_SUFFIX; | |
| } else { | |
| // PackageIdentifier#toString implements the legacy behavior of omitting the double slash | |
| // for the main repo. | |
| return pkgId.toString() + ALL_BENEATH_SUFFIX; | |
| } | |
| } | |
| /** | |
| * /** Returns a string representation of this package spec without the repository, and which is | |
| * round-trippable through {@link #fromString}. | |
| * | |
| * <p>For instance, {@code @somerepo//pkg/subpkg/...} turns into {@code "//pkg/subpkg/..."}. | |
| * | |
| * <p>Omitting the repository means that the returned strings are ambiguous in the absence of | |
| * additional context. But, for instance, if interpreted with respect to a {@code | |
| * package_group}'s {@code packages} attribute, the strings always have the same repository as | |
| * the package group. | |
| * | |
| * <p>Note that this is ambiguous w.r.t. specs that reference other repositories. | |
| * | |
| * <p>The special public and private constants will serialize as {@code "public"} and {@code | |
| * "private"} respectively. | |
| */ | |
| public final ImmutableList<String> packageStringsWithDoubleSlashAndWithoutRepository() { | |
| ImmutableList.Builder<String> resultBuilder = ImmutableList.builder(); | |
| for (PackageIdentifier pkgId : singlePackagePositives()) { | |
| resultBuilder.add(stringForSinglePackageWithDoubleSlashAndWithoutRepository(pkgId)); | |
| } | |
| for (PackageIdentifier pkgId : allPackagesBeneathPositives()) { | |
| resultBuilder.add(stringForAllPackagesBeneathWithDoubleSlashAndWithoutRepository(pkgId)); | |
| } | |
| for (PackageIdentifier pkgId : singlePackageNegatives()) { | |
| resultBuilder.add("-" + stringForSinglePackageWithDoubleSlashAndWithoutRepository(pkgId)); | |
| } | |
| for (PackageIdentifier pkgId : allPackagesBeneathNegatives()) { | |
| resultBuilder.add( | |
| "-" + stringForAllPackagesBeneathWithDoubleSlashAndWithoutRepository(pkgId)); | |
| } | |
| if (hasPositiveAllPackages()) { | |
| resultBuilder.add("public"); | |
| } | |
| if (hasPrivate()) { | |
| resultBuilder.add("private"); | |
| } | |
| return resultBuilder.build(); | |
| } | |
| private static String stringForSinglePackageWithDoubleSlashAndWithoutRepository( | |
| PackageIdentifier pkgId) { | |
| return "//" + pkgId.getPackageFragment().getPathString(); | |
| } | |
| private static String stringForAllPackagesBeneathWithDoubleSlashAndWithoutRepository( | |
| PackageIdentifier pkgId) { | |
| PathFragment pathFragment = pkgId.getPackageFragment(); | |
| return pathFragment.equals(PathFragment.EMPTY_FRAGMENT) | |
| ? "//..." | |
| : "//" + pathFragment.getPathString() + ALL_BENEATH_SUFFIX; | |
| } | |
| } | |
| } |