|  | // Copyright 2015 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 com.google.common.collect.ComparisonChain; | 
|  | import com.google.common.collect.Interner; | 
|  | import com.google.devtools.build.lib.concurrent.BlazeInterners; | 
|  | import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; | 
|  | import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; | 
|  | import com.google.devtools.build.lib.util.Preconditions; | 
|  | import com.google.devtools.build.lib.vfs.Canonicalizer; | 
|  | import com.google.devtools.build.lib.vfs.PathFragment; | 
|  | import java.io.Serializable; | 
|  | import java.util.Objects; | 
|  | import javax.annotation.concurrent.Immutable; | 
|  |  | 
|  | /** | 
|  | * Uniquely identifies a package, given a repository name and a package's path fragment. | 
|  | * | 
|  | * <p>The repository the build is happening in is the <i>default workspace</i>, and is identified by | 
|  | * the workspace name "". Other repositories can be named in the WORKSPACE file. These workspaces | 
|  | * are prefixed by {@literal @}. | 
|  | */ | 
|  | @Immutable | 
|  | public final class PackageIdentifier | 
|  | implements Comparable<PackageIdentifier>, Serializable, SkylarkValue { | 
|  |  | 
|  | private static final Interner<PackageIdentifier> INTERNER = BlazeInterners.newWeakInterner(); | 
|  |  | 
|  | public static PackageIdentifier create(String repository, PathFragment pkgName) | 
|  | throws LabelSyntaxException { | 
|  | return create(RepositoryName.create(repository), pkgName); | 
|  | } | 
|  |  | 
|  | public static PackageIdentifier create(RepositoryName repository, PathFragment pkgName) { | 
|  | return INTERNER.intern(new PackageIdentifier(repository, pkgName)); | 
|  | } | 
|  |  | 
|  | public static final PackageIdentifier EMPTY_PACKAGE_ID = createInMainRepo( | 
|  | PathFragment.EMPTY_FRAGMENT); | 
|  |  | 
|  | public static PackageIdentifier createInMainRepo(String name) { | 
|  | return createInMainRepo(PathFragment.create(name)); | 
|  | } | 
|  |  | 
|  | public static PackageIdentifier createInMainRepo(PathFragment name) { | 
|  | return create(RepositoryName.MAIN, name); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Tries to infer the package identifier from the given exec path. This method does not perform | 
|  | * any I/O, but looks solely at the structure of the exec path. The resulting identifier may | 
|  | * actually be a subdirectory of a package rather than a package, e.g.: | 
|  | * <pre><code> | 
|  | * + WORKSPACE | 
|  | * + foo/BUILD | 
|  | * + foo/bar/bar.java | 
|  | * </code></pre> | 
|  | * | 
|  | * In this case, this method returns a package identifier for foo/bar, even though that is not a | 
|  | * package. Callers need to look up the actual package if needed. | 
|  | * | 
|  | * @throws LabelSyntaxException if the exec path seems to be for an external repository that doe | 
|  | *         not have a valid repository name (see {@link RepositoryName#create}) | 
|  | */ | 
|  | public static PackageIdentifier discoverFromExecPath(PathFragment execPath, boolean forFiles) | 
|  | throws LabelSyntaxException { | 
|  | Preconditions.checkArgument(!execPath.isAbsolute(), execPath); | 
|  | PathFragment tofind = forFiles | 
|  | ? Preconditions.checkNotNull( | 
|  | execPath.getParentDirectory(), "Must pass in files, not root directory") | 
|  | : execPath; | 
|  | if (tofind.startsWith(Label.EXTERNAL_PATH_PREFIX)) { | 
|  | // TODO(ulfjack): Remove this when kchodorow@'s exec root rearrangement has been rolled out. | 
|  | RepositoryName repository = RepositoryName.create("@" + tofind.getSegment(1)); | 
|  | return PackageIdentifier.create(repository, tofind.subFragment(2, tofind.segmentCount())); | 
|  | } else if (!tofind.normalize().isNormalized()) { | 
|  | RepositoryName repository = RepositoryName.create("@" + tofind.getSegment(1)); | 
|  | return PackageIdentifier.create(repository, tofind.subFragment(2, tofind.segmentCount())); | 
|  | } else { | 
|  | return PackageIdentifier.createInMainRepo(tofind); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The identifier for this repository. This is either "" or prefixed with an "@", | 
|  | * e.g., "@myrepo". | 
|  | */ | 
|  | private final RepositoryName repository; | 
|  |  | 
|  | /** The name of the package. */ | 
|  | private final PathFragment pkgName; | 
|  |  | 
|  | /** | 
|  | * Precomputed hash code. Hash/equality is based on repository and pkgName. Note that due to | 
|  | * interning, x.equals(y) <=> x==y. | 
|  | **/ | 
|  | private final int hashCode; | 
|  |  | 
|  | private PackageIdentifier(RepositoryName repository, PathFragment pkgName) { | 
|  | this.repository = Preconditions.checkNotNull(repository); | 
|  | if (!pkgName.isNormalized()) { | 
|  | pkgName = pkgName.normalize(); | 
|  | } | 
|  | this.pkgName = Canonicalizer.fragments().intern(Preconditions.checkNotNull(pkgName)); | 
|  | this.hashCode = Objects.hash(repository, pkgName); | 
|  | } | 
|  |  | 
|  | public static PackageIdentifier parse(String input) throws LabelSyntaxException { | 
|  | String repo; | 
|  | String packageName; | 
|  | int packageStartPos = input.indexOf("//"); | 
|  | if (input.startsWith("@") && packageStartPos > 0) { | 
|  | repo = input.substring(0, packageStartPos); | 
|  | packageName = input.substring(packageStartPos + 2); | 
|  | } else if (input.startsWith("@")) { | 
|  | throw new LabelSyntaxException("starts with a '@' but does not contain '//'"); | 
|  | } else if (packageStartPos == 0) { | 
|  | repo = RepositoryName.DEFAULT_REPOSITORY; | 
|  | packageName = input.substring(2); | 
|  | } else { | 
|  | repo = RepositoryName.DEFAULT_REPOSITORY; | 
|  | packageName = input; | 
|  | } | 
|  |  | 
|  | String error = RepositoryName.validate(repo); | 
|  | if (error != null) { | 
|  | throw new LabelSyntaxException(error); | 
|  | } | 
|  |  | 
|  | error = LabelValidator.validatePackageName(packageName); | 
|  | if (error != null) { | 
|  | throw new LabelSyntaxException(error); | 
|  | } | 
|  |  | 
|  | return create(repo, PathFragment.create(packageName)); | 
|  | } | 
|  |  | 
|  | public RepositoryName getRepository() { | 
|  | return repository; | 
|  | } | 
|  |  | 
|  | public PathFragment getPackageFragment() { | 
|  | return pkgName; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a relative path to the source code for this package. Returns pkgName if this is in the | 
|  | * main repository or external/[repository name]/[pkgName] if not. | 
|  | */ | 
|  | public PathFragment getSourceRoot() { | 
|  | return repository.getSourceRoot().getRelative(pkgName); | 
|  | } | 
|  |  | 
|  | public PathFragment getPathUnderExecRoot() { | 
|  | return repository.getPathUnderExecRoot().getRelative(pkgName); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the runfiles/execRoot path for this repository (relative to the x.runfiles/main-repo/ | 
|  | * directory). | 
|  | */ | 
|  | public PathFragment getRunfilesPath() { | 
|  | return repository.getRunfilesPath().getRelative(pkgName); | 
|  | } | 
|  |  | 
|  | public PackageIdentifier makeAbsolute() { | 
|  | if (!repository.isDefault()) { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | return create(RepositoryName.MAIN, pkgName); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the name of this package. | 
|  | * | 
|  | * <p>There are certain places that expect the path fragment as the package name ('foo/bar') as a | 
|  | * package identifier. This isn't specific enough for packages in other repositories, so their | 
|  | * stringified version is '@baz//foo/bar'.</p> | 
|  | */ | 
|  | @Override | 
|  | public String toString() { | 
|  | return (repository.isDefault() || repository.isMain() ? "" : repository + "//") + pkgName; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object object) { | 
|  | if (this == object) { | 
|  | return true; | 
|  | } | 
|  | if (!(object instanceof PackageIdentifier)) { | 
|  | return false; | 
|  | } | 
|  | PackageIdentifier that = (PackageIdentifier) object; | 
|  | return this.hashCode == that.hashCode && pkgName.equals(that.pkgName) | 
|  | && repository.equals(that.repository); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return this.hashCode; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int compareTo(PackageIdentifier that) { | 
|  | return ComparisonChain.start() | 
|  | .compare(repository.toString(), that.repository.toString()) | 
|  | .compare(pkgName, that.pkgName) | 
|  | .result(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void repr(SkylarkPrinter printer) { | 
|  | printer.repr(toString()); | 
|  | } | 
|  | } |