Skip to content

Commit

Permalink
Add support for use_repo_rule in MODULE.bazel files
Browse files Browse the repository at this point in the history
This CL introduces a new directive in the MDOULE.bazel file, `use_repo_rule`, which is a convenient way to declare repos that are only visible to the current module. See #17141 (comment) for example usage.

Under the hood, this is implemented as an "innate" module extension for each module that uses `use_repo_rule`. The ID of this extension is a fake one: with the bzl file label of `@@name~version//:MODULE.bazel` and name of `__innate`. Each tag of this extension corresponds to a repo. The name of the tag is the string `${repo_rule_bzl_label}%${repo_rule_name}`, and the attributes of the tag are the attributes of the repo.

Fixes #17141.

PiperOrigin-RevId: 567722226
Change-Id: I16b8dc562dd20a676118867d831d3b2f5631f767
  • Loading branch information
Wyverald authored and copybara-github committed Sep 27, 2023
1 parent ea4ab7d commit b3401af
Show file tree
Hide file tree
Showing 9 changed files with 739 additions and 184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ java_library(
],
deps = [
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//src/main/java/net/starlark/java/eval",
"//third_party:auto_value",
Expand Down Expand Up @@ -192,6 +193,7 @@ java_library(
":module_extension",
":module_extension_metadata",
":registry",
":repo_rule_creator",
":resolution",
"//src/main/java/com/google/devtools/build/docgen/annot",
"//src/main/java/com/google/devtools/build/lib:runtime",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public abstract class ModuleExtensionId {
ModuleExtensionId::getIsolationKey,
emptiesFirst(IsolationKey.LEXICOGRAPHIC_COMPARATOR));

/**
* The "magical" name of innate extensions, which are fabricated extensions that modules with
* usages of {@code use_repo_rule} have.
*/
public static final String INNATE_EXTENSION_NAME = "_repo_rules";

/** A unique identifier for a single isolated usage of a fixed module extension. */
@AutoValue
abstract static class IsolationKey {
Expand Down Expand Up @@ -75,6 +81,10 @@ public static ModuleExtensionId create(
return new AutoValue_ModuleExtensionId(bzlFileLabel, extensionName, isolationKey);
}

public final boolean isInnate() {
return getExtensionName().equals(INNATE_EXTENSION_NAME);
}

public String asTargetString() {
String isolationKeyPart = getIsolationKey().map(key -> "%" + key).orElse("");
return String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.NonRootModuleFileValue;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.DotBazelFileSyntaxChecker;
Expand All @@ -43,7 +41,6 @@
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
Expand Down Expand Up @@ -350,15 +347,11 @@ private GetModuleFileResult getModuleFile(
if (env.getValue(FileValue.key(moduleFilePath)) == null) {
return null;
}
Label moduleFileLabel =
Label.createUnvalidated(
PackageIdentifier.create(key.getCanonicalRepoName(), PathFragment.EMPTY_FRAGMENT),
LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getBaseName());
GetModuleFileResult result = new GetModuleFileResult();
result.moduleFile =
ModuleFile.create(
readModuleFile(moduleFilePath.asPath()),
moduleFileLabel.getUnambiguousCanonicalForm());
key.moduleFileLabel().getUnambiguousCanonicalForm());
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,15 @@ public ModuleExtensionProxy useExtension(
String extensionName,
boolean devDependency,
boolean isolate,
StarlarkThread thread) {
StarlarkThread thread)
throws EvalException {
hadNonModuleCall = true;

if (extensionName.equals(ModuleExtensionId.INNATE_EXTENSION_NAME)) {
throw Starlark.errorf(
"innate extensions cannot be directly used; try `use_repo_rule` instead");
}

String extensionBzlFile = normalizeLabelString(rawExtensionBzlFile);
ModuleExtensionUsageBuilder newUsageBuilder =
new ModuleExtensionUsageBuilder(
Expand Down Expand Up @@ -560,11 +566,11 @@ private ModuleExtensionProxy(boolean devDependency) {
this.devDependency = devDependency;
}

void addImport(String localRepoName, String exportedName, Location location)
void addImport(String localRepoName, String exportedName, String byWhat, Location location)
throws EvalException {
RepositoryName.validateUserProvidedRepoName(localRepoName);
RepositoryName.validateUserProvidedRepoName(exportedName);
addRepoNameUsage(localRepoName, "by a use_repo() call", location);
addRepoNameUsage(localRepoName, byWhat, location);
if (imports.containsValue(exportedName)) {
String collisionRepoName = imports.inverse().get(exportedName);
throw Starlark.errorf(
Expand All @@ -577,26 +583,33 @@ void addImport(String localRepoName, String exportedName, Location location)
}
}

@Nullable
class TagCallable implements StarlarkValue {
final String tagName;

TagCallable(String tagName) {
this.tagName = tagName;
}

@StarlarkMethod(
name = "call",
selfCall = true,
documented = false,
extraKeywords = @Param(name = "kwargs"),
useStarlarkThread = true)
public void call(Dict<String, Object> kwargs, StarlarkThread thread) {
tags.add(
Tag.builder()
.setTagName(tagName)
.setAttributeValues(AttributeValues.create(kwargs))
.setDevDependency(devDependency)
.setLocation(thread.getCallerLocation())
.build());
}
}

@Override
public Object getValue(String tagName) throws EvalException {
return new StarlarkValue() {
@StarlarkMethod(
name = "call",
selfCall = true,
documented = false,
extraKeywords = @Param(name = "kwargs"),
useStarlarkThread = true)
public void call(Dict<String, Object> kwargs, StarlarkThread thread) {
tags.add(
Tag.builder()
.setTagName(tagName)
.setAttributeValues(AttributeValues.create(kwargs))
.setDevDependency(devDependency)
.setLocation(thread.getCallerLocation())
.build());
}
};
public TagCallable getValue(String tagName) throws EvalException {
return new TagCallable(tagName);
}

@Override
Expand Down Expand Up @@ -651,11 +664,85 @@ public void useRepo(
hadNonModuleCall = true;
Location location = thread.getCallerLocation();
for (String arg : Sequence.cast(args, String.class, "args")) {
extensionProxy.addImport(arg, arg, location);
extensionProxy.addImport(arg, arg, "by a use_repo() call", location);
}
for (Map.Entry<String, String> entry :
Dict.cast(kwargs, String.class, String.class, "kwargs").entrySet()) {
extensionProxy.addImport(entry.getKey(), entry.getValue(), location);
extensionProxy.addImport(entry.getKey(), entry.getValue(), "by a use_repo() call", location);
}
}

@StarlarkMethod(
name = "use_repo_rule",
doc =
"Returns a proxy value that can be directly invoked in the MODULE.bazel file as a"
+ " repository rule, one or more times. Repos created in such a way are only visible"
+ " to the current module, under the name declared using the <code>name</code>"
+ " attribute on the proxy. The implicit Boolean <code>dev_dependency</code>"
+ " attribute can also be used on the proxy to denote that a certain repo is only to"
+ " be created when the current module is the root module.",
parameters = {
@Param(
name = "repo_rule_bzl_file",
doc = "A label to the Starlark file defining the repo rule."),
@Param(
name = "repo_rule_name",
doc =
"The name of the repo rule to use. A symbol with this name must be exported by the"
+ " Starlark file."),
},
useStarlarkThread = true)
public RepoRuleProxy useRepoRule(String bzlFile, String ruleName, StarlarkThread thread) {
hadNonModuleCall = true;
// The builder for the singular "innate" extension of this module. Note that there's only one
// such usage (and it's fabricated), so the usage location just points to this file.
ModuleExtensionUsageBuilder newUsageBuilder =
new ModuleExtensionUsageBuilder(
"//:MODULE.bazel",
ModuleExtensionId.INNATE_EXTENSION_NAME,
/* isolate= */ false,
Location.fromFile(thread.getCallerLocation().file()));
for (ModuleExtensionUsageBuilder usageBuilder : extensionUsageBuilders) {
if (usageBuilder.extensionBzlFile.equals(newUsageBuilder.extensionBzlFile)
&& usageBuilder.extensionName.equals(newUsageBuilder.extensionName)) {
return new RepoRuleProxy(usageBuilder, bzlFile + '%' + ruleName);
}
}
extensionUsageBuilders.add(newUsageBuilder);
return new RepoRuleProxy(newUsageBuilder, bzlFile + '%' + ruleName);
}

@StarlarkBuiltin(name = "repo_rule_proxy", documented = false)
class RepoRuleProxy implements StarlarkValue {
private final ModuleExtensionUsageBuilder usageBuilder;
private final String tagName;

private RepoRuleProxy(ModuleExtensionUsageBuilder usageBuilder, String tagName) {
this.usageBuilder = usageBuilder;
this.tagName = tagName;
}

@StarlarkMethod(
name = "call",
selfCall = true,
documented = false,
parameters = {
@Param(name = "name", positional = false, named = true),
@Param(name = "dev_dependency", positional = false, named = true, defaultValue = "False")
},
extraKeywords = @Param(name = "kwargs"),
useStarlarkThread = true)
public void call(
String name, boolean devDependency, Dict<String, Object> kwargs, StarlarkThread thread)
throws EvalException {
RepositoryName.validateUserProvidedRepoName(name);
if (ignoreDevDeps && devDependency) {
return;
}
kwargs.putEntry("name", name);
ModuleExtensionProxy extensionProxy = usageBuilder.getProxy(devDependency);
extensionProxy.getValue(tagName).call(kwargs, thread);
extensionProxy.addImport(name, name, "by a repo rule", thread.getCallerLocation());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Comparator;
import java.util.List;

Expand Down Expand Up @@ -62,6 +66,13 @@ public static ModuleKey create(String name, Version version) {
/** The version of the module. Must be empty iff the module has a {@link NonRegistryOverride}. */
public abstract Version getVersion();

/** Returns the label that points to the MODULE.bazel file of this module. */
public final Label moduleFileLabel() {
return Label.createUnvalidated(
PackageIdentifier.create(getCanonicalRepoName(), PathFragment.EMPTY_FRAGMENT),
LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getBaseName());
}

@Override
public final String toString() {
if (this.equals(ROOT)) {
Expand Down
Loading

0 comments on commit b3401af

Please sign in to comment.