| // 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.cmdline; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ComparisonChain; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Interner; | |
| import com.google.devtools.build.lib.actions.CommandLineItem; | |
| import com.google.devtools.build.lib.cmdline.LabelValidator.BadLabelException; | |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; | |
| import com.google.devtools.build.lib.skylarkinterface.Param; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; | |
| import com.google.devtools.build.lib.syntax.Printer; | |
| import com.google.devtools.build.lib.syntax.SkylarkType; | |
| import com.google.devtools.build.lib.syntax.StarlarkSemantics; | |
| import com.google.devtools.build.lib.syntax.StarlarkThread; | |
| import com.google.devtools.build.lib.syntax.StarlarkValue; | |
| import com.google.devtools.build.lib.util.StringUtilities; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.skyframe.SkyFunctionName; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import java.io.InvalidObjectException; | |
| import java.io.ObjectInputStream; | |
| import java.io.Serializable; | |
| import java.util.Arrays; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A class to identify a BUILD target. All targets belong to exactly one package. The name of a | |
| * target is called its label. A typical label looks like this: //dir1/dir2:target_name where | |
| * 'dir1/dir2' identifies the package containing a BUILD file, and 'target_name' identifies the | |
| * target within the package. | |
| * | |
| * <p>Parsing is robust against bad input, for example, from the command line. | |
| */ | |
| @SkylarkModule( | |
| name = "Label", | |
| category = SkylarkModuleCategory.BUILTIN, | |
| doc = "A BUILD target identifier.") | |
| @AutoCodec | |
| @Immutable | |
| @ThreadSafe | |
| public final class Label | |
| implements Comparable<Label>, Serializable, StarlarkValue, SkyKey, CommandLineItem { | |
| /** The Starlark type symbol for Label values. */ | |
| public static final SkylarkType TYPE = SkylarkType.of(Label.class); | |
| /** | |
| * Package names that aren't made relative to the current repository because they mean special | |
| * things to Bazel. | |
| */ | |
| public static final ImmutableSet<PathFragment> ABSOLUTE_PACKAGE_NAMES = | |
| ImmutableSet.of( | |
| // Used for select | |
| PathFragment.create("conditions"), | |
| // Visibility is labels aren't actually targets | |
| PathFragment.create("visibility"), | |
| // There is only one //external package | |
| LabelConstants.EXTERNAL_PACKAGE_NAME); | |
| public static final SkyFunctionName TRANSITIVE_TRAVERSAL = | |
| SkyFunctionName.createHermetic("TRANSITIVE_TRAVERSAL"); | |
| private static final Interner<Label> LABEL_INTERNER = BlazeInterners.newWeakInterner(); | |
| /** | |
| * Factory for Labels from absolute string form. e.g. | |
| * | |
| * <pre> | |
| * //foo/bar | |
| * //foo/bar:quux | |
| * {@literal @}foo | |
| * {@literal @}foo//bar | |
| * {@literal @}foo//bar:baz | |
| * </pre> | |
| * | |
| * <p>Treats labels in the default repository as being in the main repository instead. | |
| * | |
| * <p>Labels that begin with a repository name may have the repository name remapped to a | |
| * different name if it appears in {@code repositoryMapping}. This happens if the current | |
| * repository being evaluated is external to the main repository and the main repository set the | |
| * {@code repo_mapping} attribute when declaring this repository. | |
| * | |
| * @param absName label-like string to be parsed | |
| * @param repositoryMapping map of repository names from the local name found in the current | |
| * repository to the global name declared in the main repository | |
| */ | |
| public static Label parseAbsolute( | |
| String absName, ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) | |
| throws LabelSyntaxException { | |
| return parseAbsolute( | |
| absName, /* defaultToMain= */ true, repositoryMapping); | |
| } | |
| /** | |
| * Factory for Labels from absolute string form. e.g. | |
| * | |
| * <pre> | |
| * //foo/bar | |
| * //foo/bar:quux | |
| * {@literal @}foo | |
| * {@literal @}foo//bar | |
| * {@literal @}foo//bar:baz | |
| * </pre> | |
| * | |
| * <p>Labels that begin with a repository name may have the repository name remapped to a | |
| * different name if it appears in {@code repositoryMapping}. This happens if the current | |
| * repository being evaluated is external to the main repository and the main repository set the | |
| * {@code repo_mapping} attribute when declaring this repository. | |
| * | |
| * @param absName label-like string to be parsed | |
| * @param defaultToMain Treat labels in the default repository as being in the main one instead. | |
| * @param repositoryMapping map of repository names from the local name found in the current | |
| * repository to the global name declared in the main repository | |
| */ | |
| public static Label parseAbsolute( | |
| String absName, | |
| boolean defaultToMain, | |
| ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) | |
| throws LabelSyntaxException { | |
| Preconditions.checkNotNull(repositoryMapping); | |
| String repo = defaultToMain ? "@" : RepositoryName.DEFAULT_REPOSITORY; | |
| int packageStartPos = absName.indexOf("//"); | |
| if (packageStartPos > 0) { | |
| repo = absName.substring(0, packageStartPos); | |
| absName = absName.substring(packageStartPos); | |
| } else if (absName.startsWith("@")) { | |
| repo = absName; | |
| absName = "//:" + absName.substring(1); | |
| } | |
| String error = RepositoryName.validate(repo); | |
| if (error != null) { | |
| throw new LabelSyntaxException( | |
| "invalid repository name '" + StringUtilities.sanitizeControlChars(repo) + "': " + error); | |
| } | |
| try { | |
| LabelValidator.PackageAndTarget labelParts = LabelValidator.parseAbsoluteLabel(absName); | |
| PackageIdentifier pkgId = | |
| validatePackageName( | |
| labelParts.getPackageName(), labelParts.getTargetName(), repo, repositoryMapping); | |
| PathFragment packageFragment = pkgId.getPackageFragment(); | |
| if (repo.isEmpty() && ABSOLUTE_PACKAGE_NAMES.contains(packageFragment)) { | |
| pkgId = | |
| PackageIdentifier.create(getGlobalRepoName("@", repositoryMapping), packageFragment); | |
| } | |
| return create(pkgId, labelParts.getTargetName()); | |
| } catch (BadLabelException e) { | |
| throw new LabelSyntaxException(e.getMessage()); | |
| } | |
| } | |
| private static RepositoryName getGlobalRepoName( | |
| String repo, ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) | |
| throws LabelSyntaxException { | |
| Preconditions.checkNotNull(repositoryMapping); | |
| RepositoryName repoName = RepositoryName.create(repo); | |
| return repositoryMapping.getOrDefault(repoName, repoName); | |
| } | |
| /** | |
| * Alternate factory method for Labels from absolute strings. This is a convenience method for | |
| * cases when a Label needs to be initialized statically, so the declared exception is | |
| * inconvenient. | |
| * | |
| * <p>Do not use this when the argument is not hard-wired. | |
| */ | |
| @Deprecated | |
| // TODO(b/110698008): create parseAbsoluteUnchecked that passes repositoryMapping | |
| public static Label parseAbsoluteUnchecked(String absName, boolean defaultToMain) { | |
| try { | |
| return parseAbsolute(absName, defaultToMain, /* repositoryMapping= */ ImmutableMap.of()); | |
| } catch (LabelSyntaxException e) { | |
| throw new IllegalArgumentException(e); | |
| } | |
| } | |
| public static Label parseAbsoluteUnchecked(String absName) { | |
| return parseAbsoluteUnchecked(absName, true); | |
| } | |
| /** | |
| * Factory for Labels from separate components. | |
| * | |
| * @param packageName The name of the package. The package name does <b>not</b> include {@code | |
| * //}. Must be valid according to {@link LabelValidator#validatePackageName}. | |
| * @param targetName The name of the target within the package. Must be valid according to {@link | |
| * LabelValidator#validateTargetName}. | |
| * @throws LabelSyntaxException if either of the arguments was invalid. | |
| */ | |
| public static Label create(String packageName, String targetName) throws LabelSyntaxException { | |
| return create(validatePackageName(packageName, targetName), targetName); | |
| } | |
| /** | |
| * Similar factory to above, but takes a package identifier to allow external repository labels to | |
| * be created. | |
| */ | |
| public static Label create(PackageIdentifier packageId, String targetName) | |
| throws LabelSyntaxException { | |
| return createUnvalidated(packageId, validateTargetName(packageId, targetName)); | |
| } | |
| /** | |
| * Similar factory to above, but does not perform target name validation. | |
| * | |
| * <p>Only call this method if you know what you're doing; in particular, don't call it on | |
| * arbitrary {@code name} inputs | |
| */ | |
| @AutoCodec.Instantiator | |
| public static Label createUnvalidated(PackageIdentifier packageIdentifier, String name) { | |
| return LABEL_INTERNER.intern(new Label(packageIdentifier, name)); | |
| } | |
| /** | |
| * Resolves a relative label using a workspace-relative path to the current working directory. The | |
| * method handles these cases: | |
| * | |
| * <ul> | |
| * <li>The label is absolute. | |
| * <li>The label starts with a colon. | |
| * <li>The label consists of a relative path, a colon, and a local part. | |
| * <li>The label consists only of a local part. | |
| * </ul> | |
| * | |
| * <p>Note that this method does not support any of the special syntactic constructs otherwise | |
| * supported on the command line, like ":all", "/...", and so on. | |
| * | |
| * <p>It would be cleaner to use the TargetPatternEvaluator for this resolution, but that is not | |
| * possible, because it is sometimes necessary to resolve a relative label before the package path | |
| * is setup (maybe not anymore...) | |
| * | |
| * @throws LabelSyntaxException if the resulting label is not valid | |
| */ | |
| public static Label parseCommandLineLabel(String label, PathFragment workspaceRelativePath) | |
| throws LabelSyntaxException { | |
| Preconditions.checkArgument(!workspaceRelativePath.isAbsolute()); | |
| if (LabelValidator.isAbsolute(label)) { | |
| return parseAbsolute(label, ImmutableMap.of()); | |
| } | |
| int index = label.indexOf(':'); | |
| if (index < 0) { | |
| index = 0; | |
| label = ":" + label; | |
| } | |
| PathFragment path = workspaceRelativePath.getRelative(label.substring(0, index)); | |
| // Use the String, String constructor, to make sure that the package name goes through the | |
| // validity check. | |
| return create(path.getPathString(), label.substring(index + 1)); | |
| } | |
| /** | |
| * Validates the given target name and returns a normalized name if it is valid. Otherwise it | |
| * throws a SyntaxException. | |
| */ | |
| private static String validateTargetName(PackageIdentifier packageIdentifier, String name) | |
| throws LabelSyntaxException { | |
| String error = LabelValidator.validateTargetName(name); | |
| if (error != null) { | |
| error = "invalid target name '" + StringUtilities.sanitizeControlChars(name) + "': " + error; | |
| if (packageIdentifier.getPackageFragment().getPathString().endsWith("/" + name)) { | |
| error += " (perhaps you meant \":" + name + "\"?)"; | |
| } | |
| throw new LabelSyntaxException(error); | |
| } | |
| // TODO(bazel-team): This should be an error, but we can't make it one for legacy reasons. | |
| if (name.endsWith("/.")) { | |
| name = name.substring(0, name.length() - 2); | |
| } | |
| return name; | |
| } | |
| private static PackageIdentifier validatePackageName(String packageIdentifier, String name) | |
| throws LabelSyntaxException { | |
| return validatePackageName( | |
| packageIdentifier, name, /* repo= */ null, /* repositoryMapping= */ null); | |
| } | |
| /** | |
| * Validates the given package name and returns a canonical {@link PackageIdentifier} instance if | |
| * it is valid. Otherwise it throws a SyntaxException. | |
| */ | |
| private static PackageIdentifier validatePackageName( | |
| String packageIdentifier, | |
| String name, | |
| String repo, | |
| ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) | |
| throws LabelSyntaxException { | |
| String error = null; | |
| try { | |
| return PackageIdentifier.parse(packageIdentifier, repo, repositoryMapping); | |
| } catch (LabelSyntaxException e) { | |
| error = e.getMessage(); | |
| error = "invalid package name '" + packageIdentifier + "': " + error; | |
| // This check is just for a more helpful error message | |
| // i.e. valid target name, invalid package name, colon-free label form | |
| // used => probably they meant "//foo:bar.c" not "//foo/bar.c". | |
| if (packageIdentifier.endsWith("/" + name)) { | |
| error += " (perhaps you meant \":" + name + "\"?)"; | |
| } | |
| throw new LabelSyntaxException(error); | |
| } | |
| } | |
| /** The name and repository of the package. */ | |
| private final PackageIdentifier packageIdentifier; | |
| /** The name of the target within the package. Canonical. */ | |
| private final String name; | |
| private Label(PackageIdentifier packageIdentifier, String name) { | |
| Preconditions.checkNotNull(packageIdentifier); | |
| Preconditions.checkNotNull(name); | |
| this.packageIdentifier = packageIdentifier; | |
| this.name = name; | |
| } | |
| private Object writeReplace() { | |
| return new LabelSerializationProxy(getUnambiguousCanonicalForm()); | |
| } | |
| private void readObject(ObjectInputStream unusedStream) throws InvalidObjectException { | |
| throw new InvalidObjectException("Serialization is allowed only by proxy"); | |
| } | |
| public PackageIdentifier getPackageIdentifier() { | |
| return packageIdentifier; | |
| } | |
| /** | |
| * Returns the name of the package in which this rule was declared (e.g. {@code | |
| * //file/base:fileutils_test} returns {@code file/base}). | |
| */ | |
| @SkylarkCallable( | |
| name = "package", | |
| structField = true, | |
| doc = | |
| "The package part of this label. " | |
| + "For instance:<br>" | |
| + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>" | |
| ) | |
| public String getPackageName() { | |
| return packageIdentifier.getPackageFragment().getPathString(); | |
| } | |
| /** | |
| * Returns the execution root for the workspace, relative to the execroot (e.g., for label | |
| * {@code @repo//pkg:b}, it will returns {@code external/repo/pkg} and for label {@code //pkg:a}, | |
| * it will returns an empty string. | |
| */ | |
| @SkylarkCallable( | |
| name = "workspace_root", | |
| structField = true, | |
| doc = | |
| "Returns the execution root for the workspace of this label, relative to the execroot. " | |
| + "For instance:<br>" | |
| + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root ==" | |
| + " \"external/repo\"</pre>", | |
| useStarlarkSemantics = true) | |
| public String getWorkspaceRoot(StarlarkSemantics semantics) { | |
| if (semantics.experimentalSiblingRepositoryLayout()) { | |
| return packageIdentifier.getRepository().getExecPath(true).toString(); | |
| } else { | |
| return packageIdentifier.getRepository().getSourceRoot().toString(); | |
| } | |
| } | |
| /** | |
| * Returns the path fragment of the package in which this rule was declared (e.g. {@code | |
| * //file/base:fileutils_test} returns {@code file/base}). | |
| * | |
| * <p>This is <b>not</b> suitable for inferring a path under which files related to a rule with | |
| * this label will be under the exec root, in particular, it won't work for rules in external | |
| * repositories. | |
| */ | |
| public PathFragment getPackageFragment() { | |
| return packageIdentifier.getPackageFragment(); | |
| } | |
| /** | |
| * Returns the label as a path fragment, using the package and the label name. | |
| * | |
| * <p>Make sure that the label refers to a file. Non-file labels do not necessarily have | |
| * PathFragment representations. | |
| */ | |
| public PathFragment toPathFragment() { | |
| // PathFragments are normalized, so if we do this on a non-file target named '.' | |
| // then the package would be returned. Detect this and throw. | |
| // A target named '.' can never refer to a file. | |
| Preconditions.checkArgument(!name.equals(".")); | |
| return packageIdentifier.getPackageFragment().getRelative(name); | |
| } | |
| /** | |
| * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz} returns {@code | |
| * baz}). | |
| */ | |
| @SkylarkCallable( | |
| name = "name", | |
| structField = true, | |
| doc = | |
| "The name of this label within the package. " | |
| + "For instance:<br>" | |
| + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>" | |
| ) | |
| public String getName() { | |
| return name; | |
| } | |
| /** | |
| * Renders this label in canonical form. | |
| * | |
| * <p>invariant: {@code parseAbsolute(x.toString(), false).equals(x)} | |
| */ | |
| @Override | |
| public String toString() { | |
| return getCanonicalForm(); | |
| } | |
| /** | |
| * Renders this label in canonical form. | |
| * | |
| * <p>invariant: {@code parseAbsolute(x.getCanonicalForm(), false).equals(x)} | |
| */ | |
| public String getCanonicalForm() { | |
| return getDefaultCanonicalForm(); | |
| } | |
| public String getUnambiguousCanonicalForm() { | |
| return packageIdentifier.getRepository() | |
| + "//" | |
| + packageIdentifier.getPackageFragment() | |
| + ":" | |
| + name; | |
| } | |
| /** Return the name of the repository label refers to without the leading `at` symbol. */ | |
| @SkylarkCallable( | |
| name = "workspace_name", | |
| structField = true, | |
| doc = | |
| "The repository part of this label. For instance, " | |
| + "<pre class=language-python>Label(\"@foo//bar:baz\").workspace_name" | |
| + " == \"foo\"</pre>") | |
| public String getWorkspaceName() { | |
| return packageIdentifier.getRepository().strippedName(); | |
| } | |
| /** | |
| * Renders this label in canonical form, except with labels in the main and default repositories | |
| * conflated. | |
| */ | |
| public String getDefaultCanonicalForm() { | |
| String repository = packageIdentifier.getRepository().getDefaultCanonicalForm(); | |
| return repository + "//" + packageIdentifier.getPackageFragment() + ":" + name; | |
| } | |
| /** | |
| * Renders this label in shorthand form. | |
| * | |
| * <p>Labels with canonical form {@code //foo/bar:bar} have the shorthand form {@code //foo/bar}. | |
| * All other labels have identical shorthand and canonical forms. | |
| */ | |
| public String toShorthandString() { | |
| if (!getPackageFragment().getBaseName().equals(name)) { | |
| return toString(); | |
| } | |
| String repository; | |
| if (packageIdentifier.getRepository().isMain()) { | |
| repository = ""; | |
| } else { | |
| repository = packageIdentifier.getRepository().getName(); | |
| } | |
| return repository + "//" + getPackageFragment(); | |
| } | |
| /** | |
| * Returns a label in the same package as this label with the given target name. | |
| * | |
| * @throws LabelSyntaxException if {@code targetName} is not a valid target name | |
| */ | |
| public Label getLocalTargetLabel(String targetName) throws LabelSyntaxException { | |
| return create(packageIdentifier, targetName); | |
| } | |
| /** | |
| * Resolves a relative or absolute label name. If given name is absolute, then this method calls | |
| * {@link #parseAbsolute}. Otherwise, it calls {@link #getLocalTargetLabel}. | |
| * | |
| * <p>For example: {@code :quux} relative to {@code //foo/bar:baz} is {@code //foo/bar:quux}; | |
| * {@code //wiz:quux} relative to {@code //foo/bar:baz} is {@code //wiz:quux}. | |
| * | |
| * @param relName the relative label name; must be non-empty. | |
| * @param thread the Starlark thread, which must provide a thread-local {@code HasRepoMapping}. | |
| */ | |
| @SkylarkCallable( | |
| name = "relative", | |
| doc = | |
| "Resolves a label that is either absolute (starts with <code>//</code>) or relative to " | |
| + "the current package. If this label is in a remote repository, the argument will " | |
| + "be resolved relative to that repository. If the argument contains a repository " | |
| + "name, the current label is ignored and the argument is returned as-is, except " | |
| + "that the repository name is rewritten if it is in the current repository mapping. " | |
| + "Reserved labels will also be returned as-is.<br>" | |
| + "For example:<br>" | |
| + "<pre class=language-python>\n" | |
| + "Label(\"//foo/bar:baz\").relative(\":quux\") == Label(\"//foo/bar:quux\")\n" | |
| + "Label(\"//foo/bar:baz\").relative(\"//wiz:quux\") == Label(\"//wiz:quux\")\n" | |
| + "Label(\"@repo//foo/bar:baz\").relative(\"//wiz:quux\") == " | |
| + "Label(\"@repo//wiz:quux\")\n" | |
| + "Label(\"@repo//foo/bar:baz\").relative(\"//visibility:public\") == " | |
| + "Label(\"//visibility:public\")\n" | |
| + "Label(\"@repo//foo/bar:baz\").relative(\"@other//wiz:quux\") == " | |
| + "Label(\"@other//wiz:quux\")\n" | |
| + "</pre>" | |
| + "<p>If the repository mapping passed in is <code>{'@other' : '@remapped'}</code>, " | |
| + "then the following remapping will take place:<br>" | |
| + "<pre class=language-python>\n" | |
| + "Label(\"@repo//foo/bar:baz\").relative(\"@other//wiz:quux\") == " | |
| + "Label(\"@remapped//wiz:quux\")\n" | |
| + "</pre>", | |
| parameters = { | |
| @Param( | |
| name = "relName", | |
| type = String.class, | |
| doc = "The label that will be resolved relative to this one.") | |
| }, | |
| useStarlarkThread = true) | |
| public Label getRelative(String relName, StarlarkThread thread) throws LabelSyntaxException { | |
| HasRepoMapping hrm = thread.getThreadLocal(HasRepoMapping.class); | |
| return getRelativeWithRemapping(relName, hrm.getRepoMapping()); | |
| } | |
| /** | |
| * An interface for retrieving a repository mapping. | |
| * | |
| * <p>This has only a single implementation, {@code BazelStarlarkContext}, but we can't mention | |
| * that type here because logically it belongs in Bazel, above this package. | |
| */ | |
| public interface HasRepoMapping { | |
| ImmutableMap<RepositoryName, RepositoryName> getRepoMapping(); | |
| } | |
| /** | |
| * Resolves a relative or absolute label name. If given name is absolute, then this method calls | |
| * {@link #parseAbsolute}. Otherwise, it calls {@link #getLocalTargetLabel}. | |
| * | |
| * <p>For example: {@code :quux} relative to {@code //foo/bar:baz} is {@code //foo/bar:quux}; | |
| * {@code //wiz:quux} relative to {@code //foo/bar:baz} is {@code //wiz:quux}; | |
| * {@code @repo//foo:bar} relative to anything will be {@code @repo//foo:bar} if {@code @repo} is | |
| * not in {@code repositoryMapping} but will be {@code @other_repo//foo:bar} if there is an entry | |
| * {@code @repo -> @other_repo} in {@code repositoryMapping} | |
| * | |
| * @param relName the relative label name; must be non-empty | |
| * @param repositoryMapping the map of local repository names in external repository to global | |
| * repository names in main repo; can be empty, but not null | |
| */ | |
| public Label getRelativeWithRemapping( | |
| String relName, ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) | |
| throws LabelSyntaxException { | |
| Preconditions.checkNotNull(repositoryMapping); | |
| if (relName.length() == 0) { | |
| throw new LabelSyntaxException("empty package-relative label"); | |
| } | |
| if (LabelValidator.isAbsolute(relName)) { | |
| return resolveRepositoryRelative(parseAbsolute(relName, false, repositoryMapping)); | |
| } else if (relName.equals(":")) { | |
| throw new LabelSyntaxException("':' is not a valid package-relative label"); | |
| } else if (relName.charAt(0) == ':') { | |
| return getLocalTargetLabel(relName.substring(1)); | |
| } else { | |
| return getLocalTargetLabel(relName); | |
| } | |
| } | |
| /** | |
| * Resolves the repository of a label in the context of another label. | |
| * | |
| * <p>This is necessary so that dependency edges in remote repositories do not need to explicitly | |
| * mention their repository name. Otherwise, referring to e.g. <code>//a:b</code> in a remote | |
| * repository would point back to the main repository, which is usually not what is intended. | |
| * | |
| * <p>The return value will not be in the default repository. | |
| */ | |
| public Label resolveRepositoryRelative(Label relative) { | |
| if (packageIdentifier.getRepository().isDefault() | |
| || !relative.packageIdentifier.getRepository().isDefault()) { | |
| return relative; | |
| } else { | |
| try { | |
| return create( | |
| PackageIdentifier.create( | |
| packageIdentifier.getRepository(), relative.getPackageFragment()), | |
| relative.getName()); | |
| } catch (LabelSyntaxException e) { | |
| // We are creating the new label from an existing one which is guaranteed to be valid, so | |
| // this can't happen | |
| throw new IllegalStateException(e); | |
| } | |
| } | |
| } | |
| @Override | |
| public SkyFunctionName functionName() { | |
| return TRANSITIVE_TRAVERSAL; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return hashCode(name, packageIdentifier); | |
| } | |
| /** Two labels are equal iff both their name and their package name are equal. */ | |
| @Override | |
| public boolean equals(Object other) { | |
| if (!(other instanceof Label)) { | |
| return false; | |
| } | |
| Label otherLabel = (Label) other; | |
| // Package identifiers are interned so we compare them first. | |
| return packageIdentifier.equals(otherLabel.packageIdentifier) && name.equals(otherLabel.name); | |
| } | |
| /** | |
| * Defines the order between labels. | |
| * | |
| * <p>Labels are ordered primarily by package name and secondarily by target name. Both components | |
| * are ordered lexicographically. Thus {@code //a:b/c} comes before {@code //a/b:a}, i.e. the | |
| * position of the colon is significant to the order. | |
| */ | |
| @Override | |
| public int compareTo(Label other) { | |
| return ComparisonChain.start() | |
| .compare(packageIdentifier, other.packageIdentifier) | |
| .compare(name, other.name) | |
| .result(); | |
| } | |
| /** | |
| * Returns a suitable string for the user-friendly representation of the Label. Works even if the | |
| * argument is null. | |
| */ | |
| public static String print(@Nullable Label label) { | |
| return label == null ? "(unknown)" : label.toString(); | |
| } | |
| /** | |
| * Returns a {@link PathFragment} corresponding to the directory in which {@code label} would | |
| * reside, if it were interpreted to be a path. | |
| */ | |
| public static PathFragment getContainingDirectory(Label label) { | |
| PathFragment pkg = label.getPackageFragment(); | |
| String name = label.getName(); | |
| if (name.equals(".")) { | |
| return pkg; | |
| } | |
| if (PathFragment.isNormalizedRelativePath(name) && !PathFragment.containsSeparator(name)) { | |
| // Optimize for the common case of a label like '//pkg:target'. | |
| return pkg; | |
| } | |
| return pkg.getRelative(name).getParentDirectory(); | |
| } | |
| @Override | |
| public boolean isImmutable() { | |
| return true; | |
| } | |
| @Override | |
| public void repr(Printer printer) { | |
| printer.append("Label("); | |
| printer.repr(getCanonicalForm()); | |
| printer.append(")"); | |
| } | |
| @Override | |
| public void str(Printer printer) { | |
| printer.append(getCanonicalForm()); | |
| } | |
| @Override | |
| public String expandToCommandLine() { | |
| return getCanonicalForm(); | |
| } | |
| /** | |
| * Specialization of {@link Arrays#hashCode()} that does not require constructing a 2-element | |
| * array. | |
| */ | |
| private static final int hashCode(Object obj1, Object obj2) { | |
| int result = 31 + (obj1 == null ? 0 : obj1.hashCode()); | |
| return 31 * result + (obj2 == null ? 0 : obj2.hashCode()); | |
| } | |
| } |