// 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.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; 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. * *

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

   * //foo/bar
   * //foo/bar:quux
   * {@literal @}foo
   * {@literal @}foo//bar
   * {@literal @}foo//bar:baz
   * 
* *

Treats labels in the default repository as being in the main repository instead. * *

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 repositoryMapping) throws LabelSyntaxException { return parseAbsolute( absName, /* defaultToMain= */ true, repositoryMapping); } /** * Factory for Labels from absolute string form. e.g. * *

   * //foo/bar
   * //foo/bar:quux
   * {@literal @}foo
   * {@literal @}foo//bar
   * {@literal @}foo//bar:baz
   * 
* *

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 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 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. * *

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 not 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. * *

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: * *

* *

Note that this method does not support any of the special syntactic constructs otherwise * supported on the command line, like ":all", "/...", and so on. * *

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; in particular, before the tools/defaults package is created. * * @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 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:
" + "

Label(\"//pkg/foo:abc\").package == \"pkg/foo\"
" ) 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:
" + "
Label(\"@repo//pkg/foo:abc\").workspace_root =="
            + " \"external/repo\"
" ) public String getWorkspaceRoot() { 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}). * *

This is not 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. * *

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:
" + "

Label(\"//pkg/foo:abc\").name == \"abc\"
" ) public String getName() { return name; } /** * Renders this label in canonical form. * *

invariant: {@code parseAbsolute(x.toString(), false).equals(x)} */ @Override public String toString() { return getCanonicalForm(); } /** * Renders this label in canonical form. * *

invariant: {@code parseAbsolute(x.getCanonicalForm(), false).equals(x)} */ public String getCanonicalForm() { return getDefaultCanonicalForm(); } public String getUnambiguousCanonicalForm() { return packageIdentifier.getRepository() + "//" + packageIdentifier.getPackageFragment() + ":" + name; } /** * Renders this label in canonical form, except with labels in the main and default repositories * conflated. */ public String getDefaultCanonicalForm() { String repository; if (packageIdentifier.getRepository().isMain()) { repository = ""; } else { repository = packageIdentifier.getRepository().getName(); } return repository + "//" + packageIdentifier.getPackageFragment() + ":" + name; } /** * Renders this label in shorthand form. * *

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}. * *

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. */ @SkylarkCallable( name = "relative", doc = "Resolves a label that is either absolute (starts with //) 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, it" + " will be returned as-is. Reserved labels will also be returned as-is.
" + "For example:
" + "

\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"
            + "
", parameters = { @Param( name = "relName", type = String.class, doc = "The label that will be resolved relative to this one." ) } ) public Label getRelative(String relName) throws LabelSyntaxException { return getRelativeWithRemapping(relName, /* repositoryMapping= */ ImmutableMap.of()); } /** * Resolves a relative or absolute label name. If given name is absolute, then this method calls * {@link #parseAbsolute}. Otherwise, it calls {@link #getLocalTargetLabel}. * *

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 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. * *

This is necessary so that dependency edges in remote repositories do not need to explicitly * mention their repository name. Otherwise, referring to e.g. //a:b in a remote * repository would point back to the main repository, which is usually not what is intended. * *

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. * *

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(); } @Override public boolean isImmutable() { return true; } @Override public void repr(SkylarkPrinter printer) { printer.append("Label("); printer.repr(getCanonicalForm()); printer.append(")"); } @Override public void str(SkylarkPrinter 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()); } }