diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index 58dde63a9247f4..a5951fc8d28d75 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -390,7 +390,7 @@ public String getUnambiguousCanonicalForm() {
}
/**
- * Returns a label string that is suitable for display, i.e., it resolves to this label when
+ * Returns a full label string that is suitable for display, i.e., it resolves to this label when
* parsed in the context of the main repository and has a repository part that is as simple as
* possible.
*
@@ -401,6 +401,34 @@ public String getDisplayForm(RepositoryMapping mainRepositoryMapping) {
return packageIdentifier.getDisplayForm(mainRepositoryMapping) + ":" + name;
}
+ /**
+ * Returns a shorthand label string that is suitable for display, i.e. in addition to simplifying
+ * the repository part, labels of the form {@code [@repo]//foo/bar:bar} are simplified to the
+ * shorthand form {@code [@repo]//foo/bar}, and labels of the form {@code @repo//:repo} and
+ * {@code @@repo//:repo} are simplified to {@code @repo}. The returned shorthand string resolves
+ * back to this label only when parsed in the context of the main repository whose repository
+ * mapping is provided.
+ *
+ *
Unlike {@link #getDisplayForm}, this method elides the name part of the label if possible.
+ *
+ *
Unlike {@link #toShorthandString}, this method respects {@link RepositoryMapping}.
+ *
+ * @param mainRepositoryMapping the {@link RepositoryMapping} of the main repository
+ */
+ public String getShorthandDisplayForm(RepositoryMapping mainRepositoryMapping) {
+ if (getPackageFragment().getBaseName().equals(name)) {
+ return packageIdentifier.getDisplayForm(mainRepositoryMapping);
+ } else if (getPackageFragment().getBaseName().isEmpty()) {
+ String repositoryDisplayForm =
+ getPackageIdentifier().getRepository().getDisplayForm(mainRepositoryMapping);
+ // Simplify @foo//:foo or @@foo//:foo to @foo; note that `name` cannot start with '@'
+ if (repositoryDisplayForm.equals("@" + name) || repositoryDisplayForm.equals("@@" + name)) {
+ return repositoryDisplayForm;
+ }
+ }
+ return getDisplayForm(mainRepositoryMapping);
+ }
+
/** Return the name of the repository label refers to without the leading `at` symbol. */
@StarlarkMethod(
name = "workspace_name",
@@ -419,6 +447,8 @@ public String getWorkspaceName() throws EvalException {
*
*
Labels with canonical form {@code //foo/bar:bar} have the shorthand form {@code //foo/bar}.
* All other labels have identical shorthand and canonical forms.
+ *
+ *
Unlike {@link #getShorthandDisplayForm}, this method does not respect repository mapping.
*/
public String toShorthandString() {
if (!getPackageFragment().getBaseName().equals(name)) {
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
index b945c6198381b0..4388d38154e4b0 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
@@ -189,7 +189,7 @@ public String getUnambiguousCanonicalForm() {
/**
* Returns a label representation for this package that is suitable for display. The returned
* string is as simple as possible while referencing the current package when parsed in the
- * context of the main repository.
+ * context of the main repository whose repository mapping is provided.
*
* @param mainRepositoryMapping the {@link RepositoryMapping} of the main repository
* @return
@@ -204,24 +204,8 @@ public String getUnambiguousCanonicalForm() {
* from the main module
*/
public String getDisplayForm(RepositoryMapping mainRepositoryMapping) {
- Preconditions.checkArgument(
- mainRepositoryMapping.ownerRepo() == null || mainRepositoryMapping.ownerRepo().isMain());
- if (repository.isMain()) {
- // Packages in the main repository can always use repo-relative form.
- return "//" + getPackageFragment();
- }
- if (!mainRepositoryMapping.usesStrictDeps()) {
- // If the main repository mapping is not using strict visibility, then Bzlmod is certainly
- // disabled, which means that canonical and apparent names can be used interchangeably from
- // the context of the main repository.
- return repository.getNameWithAt() + "//" + getPackageFragment();
- }
- // If possible, represent the repository with a non-canonical label using the apparent name the
- // main repository has for it, otherwise fall back to a canonical label.
- return mainRepositoryMapping
- .getInverse(repository)
- .map(apparentName -> "@" + apparentName + "//" + getPackageFragment())
- .orElseGet(this::getUnambiguousCanonicalForm);
+ return String.format(
+ "%s//%s", getRepository().getDisplayForm(mainRepositoryMapping), getPackageFragment());
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java b/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java
index 2f9f6a0ac8b025..5b564ee2faaa91 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java
@@ -230,6 +230,45 @@ public String getCanonicalForm() {
return isMain() ? "" : getNameWithAt();
}
+ /**
+ * Returns the repository part of a {@link Label}'s string representation suitable for display.
+ * The returned string is as simple as possible in the context of the main repo whose repository
+ * mapping is provided: an empty string for the main repo, or a string prefixed with a leading
+ * "{@literal @}" or "{@literal @@}" otherwise.
+ *
+ * @param mainRepositoryMapping the {@link RepositoryMapping} of the main repository
+ * @return
+ *
+ *
the empty string
+ *
if this is the main repository
+ *
@protobuf
+ *
if this repository is a WORKSPACE dependency and its name is "protobuf",
+ * or if this repository is a Bzlmod dependency of the main module and its apparent name
+ * is "protobuf"
+ *
@@protobuf~3.19.2
+ *
only with Bzlmod, if this a repository that is not visible from the main module
+ */
+ public String getDisplayForm(RepositoryMapping mainRepositoryMapping) {
+ Preconditions.checkArgument(
+ mainRepositoryMapping.ownerRepo() == null || mainRepositoryMapping.ownerRepo().isMain());
+ if (isMain()) {
+ // Packages in the main repository can always use repo-relative form.
+ return "";
+ }
+ if (!mainRepositoryMapping.usesStrictDeps()) {
+ // If the main repository mapping is not using strict visibility, then Bzlmod is certainly
+ // disabled, which means that canonical and apparent names can be used interchangeably from
+ // the context of the main repository.
+ return getNameWithAt();
+ }
+ // If possible, represent the repository with a non-canonical label using the apparent name the
+ // main repository has for it, otherwise fall back to a canonical label.
+ return mainRepositoryMapping
+ .getInverse(this)
+ .map(apparentName -> "@" + apparentName)
+ .orElse("@" + getNameWithAt());
+ }
+
/**
* Returns the runfiles/execRoot path for this repository. If we don't know the name of this repo
* (i.e., it is in the main repository), return an empty path fragment.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index cc32c2da2a54d2..8be96d9681bb60 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -2084,7 +2084,6 @@ public Object getDefaultValue() {
* Returns the default value of this attribute, even if it is a computed default, or a late-bound
* default.
*/
- @VisibleForTesting
public Object getDefaultValueUnchecked() {
return defaultValue;
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/BUILD b/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/BUILD
index 8d46e2657c761b..4bd1154d9ee574 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/BUILD
@@ -29,6 +29,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:repository_mapping_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
"//src/main/java/com/google/devtools/build/lib/util:filetype",
"//src/main/java/com/google/devtools/build/skydoc/rendering:function_util",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/ModuleInfoExtractor.java b/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/ModuleInfoExtractor.java
index 9847ef2fae29f5..8d0ee2691cab2e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/ModuleInfoExtractor.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/starlarkdocextract/ModuleInfoExtractor.java
@@ -15,9 +15,12 @@
package com.google.devtools.build.lib.rules.starlarkdocextract;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleClassFunctions.StarlarkRuleFunction;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.RuleClass;
@@ -35,17 +38,23 @@
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderNameGroup;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
+import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.StarlarkFunction;
+import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.Structure;
/** API documentation extractor for a compiled, loaded Starlark module. */
final class ModuleInfoExtractor {
+ private final Predicate isWantedName;
+ private final RepositoryMapping repositoryMapping;
+
@VisibleForTesting
static final AttributeInfo IMPLICIT_NAME_ATTRIBUTE_INFO =
AttributeInfo.newBuilder()
@@ -59,14 +68,21 @@ final class ModuleInfoExtractor {
// FakeRepositoryModule currently does?
/**
- * Extracts structured documentation for the exported symbols (meaning symbols whose first
- * character is alphabetic) of a given module.
+ * Constructs an instance of {@code ModuleInfoExtractor}.
*
- * @param isWantedName a filter applied to symbols; only those symbols for which the filter
- * returns true will be documented
+ * @param isWantedName a filter applied to symbols names; only those symbols which both are
+ * exportable (meaning the first character is alphabetic) and for which the filter returns
+ * true will be documented
+ * @param repositoryMapping the repository mapping for the repo in which we want to render labels
+ * as strings
*/
- public static ModuleInfo extractFrom(Module module, Predicate isWantedName)
- throws ExtractionException {
+ public ModuleInfoExtractor(Predicate isWantedName, RepositoryMapping repositoryMapping) {
+ this.isWantedName = isWantedName;
+ this.repositoryMapping = repositoryMapping;
+ }
+
+ /** Extracts structured documentation for the exported symbols of a given module. */
+ public ModuleInfo extractFrom(Module module) throws ExtractionException {
ModuleInfo.Builder builder = ModuleInfo.newBuilder();
Optional.ofNullable(module.getDocumentation()).ifPresent(builder::setModuleDocstring);
for (var entry : module.getGlobals().entrySet()) {
@@ -103,9 +119,8 @@ public ExtractionException(String message, Throwable cause) {
* for field bar of exported struct foo
* @param value documentable Starlark value
*/
- private static void addInfo(ModuleInfo.Builder builder, String name, Object value)
+ private void addInfo(ModuleInfo.Builder builder, String name, Object value)
throws ExtractionException {
- // Note that may be exported under a different name than its getName() value.
if (value instanceof StarlarkRuleFunction) {
addRuleInfo(builder, name, (StarlarkRuleFunction) value);
} else if (value instanceof StarlarkProvider) {
@@ -126,7 +141,7 @@ private static void addInfo(ModuleInfo.Builder builder, String name, Object valu
// TODO(b/276733504): should we recurse into dicts to search for documentable values?
}
- private static void addStructureInfo(ModuleInfo.Builder builder, String name, Structure structure)
+ private void addStructureInfo(ModuleInfo.Builder builder, String name, Structure structure)
throws ExtractionException {
for (String fieldName : structure.getFieldNames()) {
if (isExportableName(fieldName)) {
@@ -182,7 +197,54 @@ private static AttributeType getAttributeType(Attribute attribute, String where)
where, attribute.getPublicName(), type.getClass().getSimpleName()));
}
- private static AttributeInfo buildAttributeInfo(Attribute attribute, String where)
+ /**
+ * Recursively transforms labels to strings via {@link Label#getShorthandDisplayForm}.
+ *
+ * @return the label's shorthand display string if {@code o} is a label; a container with label
+ * elements transformed into shorthand display strings recursively if {@code o} is a Starlark
+ * container; or the original object {@code o} if no label stringification was performed.
+ */
+ private Object stringifyLabels(Object o) {
+ if (o instanceof Label) {
+ return ((Label) o).getShorthandDisplayForm(repositoryMapping);
+ } else if (o instanceof Map) {
+ return stringifyLabelsOfMap((Map, ?>) o);
+ } else if (o instanceof List) {
+ return stringifyLabelsOfList((List>) o);
+ } else {
+ return o;
+ }
+ }
+
+ private Object stringifyLabelsOfMap(Map, ?> dict) {
+ boolean neededToStringify = false;
+ ImmutableMap.Builder