// 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.common.base.Verify; import com.google.common.collect.ImmutableList; 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.RepositoryName; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.stream.Stream; /** * Represents one of the following: * * * *

Typically (exclusively?) used for package visibility, as part of a {@link PackageGroup} * target. * *

A package specification is specific to a single {@link RepositoryName} unless it is the "all * packages" specification. */ public abstract class PackageSpecification { private static final String PACKAGE_LABEL = "__pkg__"; private static final String SUBTREE_LABEL = "__subpackages__"; private static final String ALL_BENEATH_SUFFIX = "/..."; private static final String NEGATIVE_PREFIX = "-"; /** Returns {@code true} if the package spec includes the provided {@code packageName}. */ protected abstract boolean containsPackage(PackageIdentifier packageName); /** * Returns a {@link String} representation of the {@link PackageSpecification} of the same format * accepted by {@link #fromString}. * *

The returned {@link String} is insensitive to the {@link RepositoryName} associated with the * {@link PackageSpecification}. */ protected abstract String toStringWithoutRepository(); /** Returns {@code true} if the package specification represents a negated match. */ protected boolean negative() { return false; } /** * Parses the provided {@link String} into a {@link PackageSpecification}. * *

The {@link String} must have one of the following forms: * *

* *

If and only if the {@link String} is one of the first two forms, the returned {@link * PackageSpecification} is specific to the provided {@link RepositoryName}. Note that it is not * possible to construct a repository-specific {@link PackageSpecification} for all transitive * subpackages of the root package (i.e. a repository-specific "//..."). * *

Throws {@link InvalidPackageSpecificationException} if the {@link String} cannot be parsed. */ public static PackageSpecification fromString(RepositoryName repositoryName, String spec) throws InvalidPackageSpecificationException { String result = spec; boolean negative = false; if (result.startsWith(NEGATIVE_PREFIX)) { negative = true; result = result.substring(NEGATIVE_PREFIX.length()); } PackageSpecification packageSpecification = fromStringPositive(repositoryName, result); return negative ? new NegativePackageSpecification(packageSpecification) : packageSpecification; } private static PackageSpecification fromStringPositive(RepositoryName repositoryName, String spec) throws InvalidPackageSpecificationException { String result = spec; boolean allBeneath = false; if (result.endsWith(ALL_BENEATH_SUFFIX)) { allBeneath = true; result = result.substring(0, result.length() - ALL_BENEATH_SUFFIX.length()); if (result.equals("/")) { // spec was "//...". return AllPackages.EVERYTHING; } } if (!spec.startsWith("//")) { throw new InvalidPackageSpecificationException( String.format("invalid package name '%s': must start with '//'", spec)); } PackageIdentifier packageId; try { packageId = PackageIdentifier.parse(result); } catch (LabelSyntaxException e) { throw new InvalidPackageSpecificationException( String.format("invalid package name '%s': %s", spec, e.getMessage())); } Verify.verify(packageId.getRepository().isDefault()); PackageIdentifier packageIdForSpecifiedRepository = PackageIdentifier.create(repositoryName, packageId.getPackageFragment()); return allBeneath ? new AllPackagesBeneath(packageIdForSpecifiedRepository) : new SinglePackage(packageIdForSpecifiedRepository); } /** * Parses the provided {@link Label} into a {@link PackageSpecification} specific to the {@link * RepositoryName} associated with the label. * *

If {@code label.getName.equals("__pkg__")} then this results in a {@link * PackageSpecification} that contains exactly the named package. * *

If {@code label.getName.equals("__subpackages__")} then this results in a {@link * PackageSpecification} that contains all transitive subpackages of that package, inclusive. * *

If the label's name is neither "__pkg__" nor "__subpackages__", this returns {@code null}. * *

Note that there is no {@link Label} associated with the {@link RepositoryName}-agnostic "all * packages" specification (corresponding to {@code #fromString(null, "//...")}). */ 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.EVERYTHING; } @AutoCodec @VisibleForSerialization static class SinglePackage extends PackageSpecification { private PackageIdentifier singlePackageName; @VisibleForSerialization SinglePackage(PackageIdentifier singlePackageName) { this.singlePackageName = singlePackageName; } @Override protected boolean containsPackage(PackageIdentifier packageName) { return this.singlePackageName.equals(packageName); } @Override protected String toStringWithoutRepository() { return "//" + singlePackageName.getPackageFragment().getPathString(); } @Override public String toString() { return singlePackageName.toString(); } @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(); } } @AutoCodec @VisibleForSerialization static class AllPackagesBeneath extends PackageSpecification { private PackageIdentifier prefix; @VisibleForSerialization 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 toStringWithoutRepository() { return "//" + prefix.getPackageFragment().getPathString() + ALL_BENEATH_SUFFIX; } @Override public String toString() { if (prefix.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) { return "//..."; } return prefix + "/..."; } @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/...}. */ @AutoCodec @VisibleForSerialization static class NegativePackageSpecification extends PackageSpecification { private final PackageSpecification delegate; NegativePackageSpecification(PackageSpecification delegate) { this.delegate = delegate; } @Override protected boolean negative() { return true; } @Override protected boolean containsPackage(PackageIdentifier packageName) { return delegate.containsPackage(packageName); } @Override protected String toStringWithoutRepository() { return "-" + delegate.toStringWithoutRepository(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } return obj instanceof NegativePackageSpecification && delegate.equals(((NegativePackageSpecification) obj).delegate); } @Override public String toString() { return "-" + delegate; } } @AutoCodec @VisibleForSerialization static class AllPackages extends PackageSpecification { private static final PackageSpecification EVERYTHING = new AllPackages(); @Override protected boolean containsPackage(PackageIdentifier packageName) { return true; } @Override protected String toStringWithoutRepository() { return "//..."; } @Override public boolean equals(Object o) { return o instanceof AllPackages; } @Override public int hashCode() { return "//...".hashCode(); } @Override public String toString() { return "//..."; } } /** Exception class to be thrown when a specification cannot be parsed. */ static class InvalidPackageSpecificationException extends Exception { private InvalidPackageSpecificationException(String message) { super(message); } } /** * A collection of {@link PackageSpecification}s from a {@code package_group}, which supports * testing a given package for containment (see {@link #containedPackages()}}. */ @Immutable @AutoCodec public static final class PackageGroupContents { private final ImmutableList packageSpecifications; @VisibleForSerialization PackageGroupContents(ImmutableList packageSpecifications) { this.packageSpecifications = packageSpecifications; } /** * Creates a {@link PackageGroupContents} representing a collection of {@link * PackageSpecification}s. */ public static PackageGroupContents create( ImmutableList packageSpecifications) { return new PackageGroupContents(packageSpecifications); } /** * Returns {@code true} if the package specifications include the provided {@code packageName}. * That is, at least one positive package specification matches, and no negative package * specifications match. */ public boolean containsPackage(PackageIdentifier packageIdentifier) { boolean match = false; for (PackageSpecification p : packageSpecifications) { if (p.containsPackage(packageIdentifier)) { if (p.negative()) { return false; } else { match = true; } } } return match; } /** * Returns {@link String} representations of the component {@link PackageSpecification}s of the * same format accepted by {@link #fromString}. */ public Stream containedPackages() { return packageSpecifications.stream().map(PackageSpecification::toString); } /** * Returns {@link String} representations of the component {@link PackageSpecification}s of the * same format accepted by {@link #fromString}. * *

The returned {@link String}s are insensitive to the {@link RepositoryName} associated with * the {@link PackageSpecification}. */ public Stream containedPackagesWithoutRepository() { return packageSpecifications.stream().map(PackageSpecification::toStringWithoutRepository); } } }