Skip to content

Commit 1a719ce

Browse files
fmeumcopybara-github
authored andcommitted
Add module_ctx.is_dev_dependency
Allows module extensions to determine whether a given tag represents a dev dependency. Fixes #17101 Work towards #17908 Closes #17909. PiperOrigin-RevId: 520645663 Change-Id: I3e3136a09d01d25fc706bcd0dfd7e53b6e7d5285
1 parent 986ef7b commit 1a719ce

File tree

8 files changed

+213
-67
lines changed

8 files changed

+213
-67
lines changed

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionContext.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import com.google.devtools.build.skyframe.SkyFunction.Environment;
2525
import java.util.Map;
2626
import javax.annotation.Nullable;
27+
import net.starlark.java.annot.Param;
28+
import net.starlark.java.annot.ParamType;
2729
import net.starlark.java.annot.StarlarkBuiltin;
2830
import net.starlark.java.annot.StarlarkMethod;
2931
import net.starlark.java.eval.EvalException;
@@ -99,4 +101,20 @@ protected ImmutableMap<String, String> getRemoteExecProperties() throws EvalExce
99101
public StarlarkList<StarlarkBazelModule> getModules() {
100102
return modules;
101103
}
104+
105+
@StarlarkMethod(
106+
name = "is_dev_dependency",
107+
doc =
108+
"Returns whether the given tag was specified on the result of a <a "
109+
+ "href=\"globals.html#use_extension\">use_extension</a> call with "
110+
+ "<code>devDependency = True</code>.",
111+
parameters = {
112+
@Param(
113+
name = "tag",
114+
doc = "A tag obtained from <a href=\"bazel_module.html#tags\">bazel_module.tags</a>.",
115+
allowedTypes = {@ParamType(type = TypeCheckedTag.class)})
116+
})
117+
public boolean isDevDependency(TypeCheckedTag tag) {
118+
return tag.isDevDependency();
119+
}
102120
}

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.common.collect.ImmutableList;
2626
import com.google.common.collect.ImmutableMap;
2727
import com.google.devtools.build.docgen.annot.DocumentMethods;
28+
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileGlobals.ModuleExtensionUsageBuilder.ModuleExtensionProxy;
2829
import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
2930
import com.google.devtools.build.lib.cmdline.RepositoryName;
3031
import java.util.ArrayList;
@@ -63,7 +64,7 @@ public class ModuleFileGlobals {
6364
private final boolean ignoreDevDeps;
6465
private final Module.Builder module;
6566
private final Map<String, ModuleKey> deps = new LinkedHashMap<>();
66-
private final List<ModuleExtensionProxy> extensionProxies = new ArrayList<>();
67+
private final List<ModuleExtensionUsageBuilder> extensionUsageBuilders = new ArrayList<>();
6768
private final Map<String, ModuleOverride> overrides = new HashMap<>();
6869
private final Map<String, RepoNameUsage> repoNameUsages = new HashMap<>();
6970

@@ -375,38 +376,37 @@ public void registerToolchains(Sequence<?> toolchainLabels) throws EvalException
375376
},
376377
useStarlarkThread = true)
377378
public ModuleExtensionProxy useExtension(
378-
String extensionBzlFile, String extensionName, boolean devDependency, StarlarkThread thread)
379-
throws EvalException {
380-
ModuleExtensionProxy newProxy =
381-
new ModuleExtensionProxy(extensionBzlFile, extensionName, thread.getCallerLocation());
379+
String extensionBzlFile, String extensionName, boolean devDependency, StarlarkThread thread) {
380+
ModuleExtensionUsageBuilder newUsageBuilder =
381+
new ModuleExtensionUsageBuilder(
382+
extensionBzlFile, extensionName, thread.getCallerLocation());
382383

383384
if (ignoreDevDeps && devDependency) {
384385
// This is a no-op proxy.
385-
return newProxy;
386+
return newUsageBuilder.getProxy(devDependency);
386387
}
387388

388-
// Find an existing proxy object corresponding to this extension.
389-
for (ModuleExtensionProxy proxy : extensionProxies) {
390-
if (proxy.extensionBzlFile.equals(extensionBzlFile)
391-
&& proxy.extensionName.equals(extensionName)) {
392-
return proxy;
389+
// Find an existing usage builder corresponding to this extension.
390+
for (ModuleExtensionUsageBuilder usageBuilder : extensionUsageBuilders) {
391+
if (usageBuilder.extensionBzlFile.equals(extensionBzlFile)
392+
&& usageBuilder.extensionName.equals(extensionName)) {
393+
return usageBuilder.getProxy(devDependency);
393394
}
394395
}
395396

396397
// If no such proxy exists, we can just use a new one.
397-
extensionProxies.add(newProxy);
398-
return newProxy;
398+
extensionUsageBuilders.add(newUsageBuilder);
399+
return newUsageBuilder.getProxy(devDependency);
399400
}
400401

401-
@StarlarkBuiltin(name = "module_extension_proxy", documented = false)
402-
class ModuleExtensionProxy implements Structure {
402+
class ModuleExtensionUsageBuilder {
403403
private final String extensionBzlFile;
404404
private final String extensionName;
405405
private final Location location;
406406
private final HashBiMap<String, String> imports;
407407
private final ImmutableList.Builder<Tag> tags;
408408

409-
ModuleExtensionProxy(String extensionBzlFile, String extensionName, Location location) {
409+
ModuleExtensionUsageBuilder(String extensionBzlFile, String extensionName, Location location) {
410410
this.extensionBzlFile = extensionBzlFile;
411411
this.extensionName = extensionName;
412412
this.location = location;
@@ -424,50 +424,69 @@ ModuleExtensionUsage buildUsage() {
424424
.build();
425425
}
426426

427-
void addImport(String localRepoName, String exportedName, Location location)
428-
throws EvalException {
429-
RepositoryName.validateUserProvidedRepoName(localRepoName);
430-
RepositoryName.validateUserProvidedRepoName(exportedName);
431-
addRepoNameUsage(localRepoName, "by a use_repo() call", location);
432-
if (imports.containsValue(exportedName)) {
433-
String collisionRepoName = imports.inverse().get(exportedName);
434-
throw Starlark.errorf(
435-
"The repo exported as '%s' by module extension '%s' is already imported at %s",
436-
exportedName, extensionName, repoNameUsages.get(collisionRepoName).getWhere());
437-
}
438-
imports.put(localRepoName, exportedName);
427+
/**
428+
* Creates a proxy with the specified dev_dependency bit that shares accumulated imports and
429+
* tags with all other such proxies, thus preserving their order across dev/non-dev deps.
430+
*/
431+
ModuleExtensionProxy getProxy(boolean devDependency) {
432+
return new ModuleExtensionProxy(devDependency);
439433
}
440434

441-
@Nullable
442-
@Override
443-
public Object getValue(String tagName) throws EvalException {
444-
return new StarlarkValue() {
445-
@StarlarkMethod(
446-
name = "call",
447-
selfCall = true,
448-
documented = false,
449-
extraKeywords = @Param(name = "kwargs"),
450-
useStarlarkThread = true)
451-
public void call(Dict<String, Object> kwargs, StarlarkThread thread) {
452-
tags.add(
453-
Tag.builder()
454-
.setTagName(tagName)
455-
.setAttributeValues(kwargs)
456-
.setLocation(thread.getCallerLocation())
457-
.build());
435+
@StarlarkBuiltin(name = "module_extension_proxy", documented = false)
436+
class ModuleExtensionProxy implements Structure {
437+
438+
private final boolean devDependency;
439+
440+
private ModuleExtensionProxy(boolean devDependency) {
441+
this.devDependency = devDependency;
442+
}
443+
444+
void addImport(String localRepoName, String exportedName, Location location)
445+
throws EvalException {
446+
RepositoryName.validateUserProvidedRepoName(localRepoName);
447+
RepositoryName.validateUserProvidedRepoName(exportedName);
448+
addRepoNameUsage(localRepoName, "by a use_repo() call", location);
449+
if (imports.containsValue(exportedName)) {
450+
String collisionRepoName = imports.inverse().get(exportedName);
451+
throw Starlark.errorf(
452+
"The repo exported as '%s' by module extension '%s' is already imported at %s",
453+
exportedName, extensionName, repoNameUsages.get(collisionRepoName).getWhere());
458454
}
459-
};
460-
}
455+
imports.put(localRepoName, exportedName);
456+
}
461457

462-
@Override
463-
public ImmutableCollection<String> getFieldNames() {
464-
return ImmutableList.of();
465-
}
458+
@Nullable
459+
@Override
460+
public Object getValue(String tagName) throws EvalException {
461+
return new StarlarkValue() {
462+
@StarlarkMethod(
463+
name = "call",
464+
selfCall = true,
465+
documented = false,
466+
extraKeywords = @Param(name = "kwargs"),
467+
useStarlarkThread = true)
468+
public void call(Dict<String, Object> kwargs, StarlarkThread thread) {
469+
tags.add(
470+
Tag.builder()
471+
.setTagName(tagName)
472+
.setAttributeValues(kwargs)
473+
.setDevDependency(devDependency)
474+
.setLocation(thread.getCallerLocation())
475+
.build());
476+
}
477+
};
478+
}
466479

467-
@Nullable
468-
@Override
469-
public String getErrorMessageForUnknownField(String field) {
470-
return null;
480+
@Override
481+
public ImmutableCollection<String> getFieldNames() {
482+
return ImmutableList.of();
483+
}
484+
485+
@Nullable
486+
@Override
487+
public String getErrorMessageForUnknownField(String field) {
488+
return null;
489+
}
471490
}
472491
}
473492

@@ -824,8 +843,8 @@ public Module buildModule() {
824843
.setDeps(ImmutableMap.copyOf(deps))
825844
.setOriginalDeps(ImmutableMap.copyOf(deps))
826845
.setExtensionUsages(
827-
extensionProxies.stream()
828-
.map(ModuleExtensionProxy::buildUsage)
846+
extensionUsageBuilders.stream()
847+
.map(ModuleExtensionUsageBuilder::buildUsage)
829848
.collect(toImmutableList()))
830849
.build();
831850
}

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Tag.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public abstract class Tag {
3333
/** All keyword arguments supplied to the tag instance. */
3434
public abstract Dict<String, Object> getAttributeValues();
3535

36+
/** Whether this tag was created using a proxy created with dev_dependency = True. */
37+
public abstract boolean isDevDependency();
38+
3639
/** The source location in the module file where this tag was created. */
3740
public abstract Location getLocation();
3841

@@ -48,6 +51,8 @@ public abstract static class Builder {
4851

4952
public abstract Builder setAttributeValues(Dict<String, Object> value);
5053

54+
public abstract Builder setDevDependency(boolean value);
55+
5156
public abstract Builder setLocation(Location value);
5257

5358
public abstract Tag build();

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
public class TypeCheckedTag implements Structure {
3535
private final TagClass tagClass;
3636
private final Object[] attrValues;
37+
private final boolean devDependency;
3738

38-
private TypeCheckedTag(TagClass tagClass, Object[] attrValues) {
39+
private TypeCheckedTag(TagClass tagClass, Object[] attrValues, boolean devDependency) {
3940
this.tagClass = tagClass;
4041
this.attrValues = attrValues;
42+
this.devDependency = devDependency;
4143
}
4244

4345
/** Creates a {@link TypeCheckedTag}. */
@@ -95,7 +97,15 @@ public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter l
9597
attrValues[i] = Attribute.valueToStarlark(attr.getDefaultValueUnchecked());
9698
}
9799
}
98-
return new TypeCheckedTag(tagClass, attrValues);
100+
return new TypeCheckedTag(tagClass, attrValues, tag.isDevDependency());
101+
}
102+
103+
/**
104+
* Whether the tag was specified on an extension proxy created with <code>dev_dependency=True
105+
* </code>.
106+
*/
107+
public boolean isDevDependency() {
108+
return devDependency;
99109
}
100110

101111
@Override

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ public static TagClass createTagClass(Attribute... attrs) {
283283
public static class TestTagBuilder {
284284
private final Dict.Builder<String, Object> attrValuesBuilder = Dict.builder();
285285
private final String tagName;
286+
private boolean devDependency = false;
286287

287288
private TestTagBuilder(String tagName) {
288289
this.tagName = tagName;
@@ -294,11 +295,18 @@ public TestTagBuilder addAttr(String attrName, Object attrValue) {
294295
return this;
295296
}
296297

298+
@CanIgnoreReturnValue
299+
public TestTagBuilder setDevDependency() {
300+
devDependency = true;
301+
return this;
302+
}
303+
297304
public Tag build() {
298305
return Tag.builder()
299306
.setTagName(tagName)
300307
.setLocation(Location.BUILTIN)
301308
.setAttributeValues(attrValuesBuilder.buildImmutable())
309+
.setDevDependency(devDependency)
302310
.build();
303311
}
304312
}

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public void multipleModules_devDependency() throws Exception {
446446
" data_str = 'modules:'",
447447
" for mod in ctx.modules:",
448448
" for tag in mod.tags.tag:",
449-
" data_str += ' ' + tag.data",
449+
" data_str += ' ' + tag.data + ' ' + str(ctx.is_dev_dependency(tag))",
450450
" data_repo(name='ext_repo',data=data_str)",
451451
"tag=tag_class(attrs={'data':attr.string()})",
452452
"ext=module_extension(implementation=_ext_impl,tag_classes={'tag':tag})");
@@ -457,7 +457,8 @@ public void multipleModules_devDependency() throws Exception {
457457
if (result.hasError()) {
458458
throw result.getError().getException();
459459
}
460-
assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: root bar@2.0");
460+
assertThat(result.get(skyKey).getModule().getGlobal("data"))
461+
.isEqualTo("modules: root True bar@2.0 False");
461462
}
462463

463464
@Test
@@ -497,7 +498,7 @@ public void multipleModules_ignoreDevDependency() throws Exception {
497498
" data_str = 'modules:'",
498499
" for mod in ctx.modules:",
499500
" for tag in mod.tags.tag:",
500-
" data_str += ' ' + tag.data",
501+
" data_str += ' ' + tag.data + ' ' + str(ctx.is_dev_dependency(tag))",
501502
" data_repo(name='ext_repo',data=data_str)",
502503
"tag=tag_class(attrs={'data':attr.string()})",
503504
"ext=module_extension(implementation=_ext_impl,tag_classes={'tag':tag})");
@@ -511,7 +512,8 @@ public void multipleModules_ignoreDevDependency() throws Exception {
511512
if (result.hasError()) {
512513
throw result.getError().getException();
513514
}
514-
assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: bar@2.0");
515+
assertThat(result.get(skyKey).getModule().getGlobal("data"))
516+
.isEqualTo("modules: bar@2.0 False");
515517
}
516518

517519
@Test

0 commit comments

Comments
 (0)