diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionContext.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionContext.java index 6dbf5e3e4a6748..fc9510c6fc1619 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionContext.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionContext.java @@ -24,6 +24,8 @@ import com.google.devtools.build.skyframe.SkyFunction.Environment; import java.util.Map; import javax.annotation.Nullable; +import net.starlark.java.annot.Param; +import net.starlark.java.annot.ParamType; import net.starlark.java.annot.StarlarkBuiltin; import net.starlark.java.annot.StarlarkMethod; import net.starlark.java.eval.EvalException; @@ -99,4 +101,19 @@ protected ImmutableMap getRemoteExecProperties() throws EvalExce public StarlarkList getModules() { return modules; } + + @StarlarkMethod( + name = "is_dev_dependency", + doc = "Returns whether the given tag was specified on the result of a use_extension call with " + + "devDependency = True.", + parameters = { + @Param( + name = "tag", + doc = "A tag obtained from bazel_module.tags.", + allowedTypes = {@ParamType(type = TypeCheckedTag.class)}) + }) + public boolean isDevDependency(TypeCheckedTag tag) { + return tag.isDevDependency(); + } } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java index abfd98bb1fc330..3a63263dd87558 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java @@ -375,27 +375,26 @@ public void registerToolchains(Sequence toolchainLabels) throws EvalException }, useStarlarkThread = true) public ModuleExtensionProxy useExtension( - String extensionBzlFile, String extensionName, boolean devDependency, StarlarkThread thread) - throws EvalException { + String extensionBzlFile, String extensionName, boolean devDependency, StarlarkThread thread) { ModuleExtensionProxy newProxy = new ModuleExtensionProxy(extensionBzlFile, extensionName, thread.getCallerLocation()); if (ignoreDevDeps && devDependency) { // This is a no-op proxy. - return newProxy; + return newProxy.withDevDependency(devDependency); } // Find an existing proxy object corresponding to this extension. for (ModuleExtensionProxy proxy : extensionProxies) { if (proxy.extensionBzlFile.equals(extensionBzlFile) && proxy.extensionName.equals(extensionName)) { - return proxy; + return proxy.withDevDependency(devDependency); } } // If no such proxy exists, we can just use a new one. extensionProxies.add(newProxy); - return newProxy; + return newProxy.withDevDependency(devDependency); } @StarlarkBuiltin(name = "module_extension_proxy", documented = false) @@ -405,6 +404,7 @@ class ModuleExtensionProxy implements Structure { private final Location location; private final HashBiMap imports; private final ImmutableList.Builder tags; + private final boolean devDependency; ModuleExtensionProxy(String extensionBzlFile, String extensionName, Location location) { this.extensionBzlFile = extensionBzlFile; @@ -412,6 +412,24 @@ class ModuleExtensionProxy implements Structure { this.location = location; this.imports = HashBiMap.create(); this.tags = ImmutableList.builder(); + this.devDependency = false; + } + + private ModuleExtensionProxy(ModuleExtensionProxy other, boolean devDependency) { + this.extensionBzlFile = other.extensionBzlFile; + this.extensionName = other.extensionName; + this.location = other.location; + this.imports = other.imports; + this.tags = other.tags; + this.devDependency = devDependency; + } + + /** + * Creates a proxy with the specified devDependency bit that shares accumulated imports and tags + * with the current one, thus preserving tag specification order. + */ + ModuleExtensionProxy withDevDependency(boolean devDependency) { + return devDependency ? new ModuleExtensionProxy(this, true) : this; } ModuleExtensionUsage buildUsage() { @@ -453,6 +471,7 @@ public void call(Dict kwargs, StarlarkThread thread) { Tag.builder() .setTagName(tagName) .setAttributeValues(kwargs) + .setDevDependency(devDependency) .setLocation(thread.getCallerLocation()) .build()); } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Tag.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Tag.java index 6d89b5a778cc29..498aacd427c49a 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Tag.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Tag.java @@ -33,6 +33,9 @@ public abstract class Tag { /** All keyword arguments supplied to the tag instance. */ public abstract Dict getAttributeValues(); + /** Whether this tag was created using a proxy created with dev_dependency = True. */ + public abstract boolean isDevDependency(); + /** The source location in the module file where this tag was created. */ public abstract Location getLocation(); @@ -48,6 +51,8 @@ public abstract static class Builder { public abstract Builder setAttributeValues(Dict value); + public abstract Builder setDevDependency(boolean value); + public abstract Builder setLocation(Location value); public abstract Tag build(); diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java index 50c4b19e68525e..b78eaee68c879c 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java @@ -34,10 +34,12 @@ public class TypeCheckedTag implements Structure { private final TagClass tagClass; private final Object[] attrValues; + private final boolean devDependency; - private TypeCheckedTag(TagClass tagClass, Object[] attrValues) { + private TypeCheckedTag(TagClass tagClass, Object[] attrValues, boolean devDependency) { this.tagClass = tagClass; this.attrValues = attrValues; + this.devDependency = devDependency; } /** Creates a {@link TypeCheckedTag}. */ @@ -95,7 +97,15 @@ public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter l attrValues[i] = Attribute.valueToStarlark(attr.getDefaultValueUnchecked()); } } - return new TypeCheckedTag(tagClass, attrValues); + return new TypeCheckedTag(tagClass, attrValues, tag.isDevDependency()); + } + + /** + * Whether the tag was specified on an extension proxy created with + * dev_dependency=True. + */ + public boolean isDevDependency() { + return devDependency; } @Override diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java index cc4dbaba79f53b..2af63ead6d9d35 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodTestUtil.java @@ -283,6 +283,7 @@ public static TagClass createTagClass(Attribute... attrs) { public static class TestTagBuilder { private final Dict.Builder attrValuesBuilder = Dict.builder(); private final String tagName; + private boolean devDependency = false; private TestTagBuilder(String tagName) { this.tagName = tagName; @@ -294,11 +295,18 @@ public TestTagBuilder addAttr(String attrName, Object attrValue) { return this; } + @CanIgnoreReturnValue + public TestTagBuilder setDevDependency() { + devDependency = true; + return this; + } + public Tag build() { return Tag.builder() .setTagName(tagName) .setLocation(Location.BUILTIN) .setAttributeValues(attrValuesBuilder.buildImmutable()) + .setDevDependency(devDependency) .build(); } } diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java index deeabce75e4b43..93b6575774739b 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java @@ -446,7 +446,7 @@ public void multipleModules_devDependency() throws Exception { " data_str = 'modules:'", " for mod in ctx.modules:", " for tag in mod.tags.tag:", - " data_str += ' ' + tag.data", + " data_str += ' ' + tag.data + ' ' + str(ctx.is_dev_dependency(tag))", " data_repo(name='ext_repo',data=data_str)", "tag=tag_class(attrs={'data':attr.string()})", "ext=module_extension(implementation=_ext_impl,tag_classes={'tag':tag})"); @@ -457,7 +457,7 @@ public void multipleModules_devDependency() throws Exception { if (result.hasError()) { throw result.getError().getException(); } - assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: root bar@2.0"); + assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: root True bar@2.0 False"); } @Test @@ -497,7 +497,7 @@ public void multipleModules_ignoreDevDependency() throws Exception { " data_str = 'modules:'", " for mod in ctx.modules:", " for tag in mod.tags.tag:", - " data_str += ' ' + tag.data", + " data_str += ' ' + tag.data + ' ' + str(ctx.is_dev_dependency(tag))", " data_repo(name='ext_repo',data=data_str)", "tag=tag_class(attrs={'data':attr.string()})", "ext=module_extension(implementation=_ext_impl,tag_classes={'tag':tag})"); @@ -511,7 +511,7 @@ public void multipleModules_ignoreDevDependency() throws Exception { if (result.hasError()) { throw result.getError().getException(); } - assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: bar@2.0"); + assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("modules: bar@2.0 False"); } @Test diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java index bd203ee4ed841f..266d2da5fdbd36 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java @@ -484,6 +484,7 @@ public void testModuleExtensions_good() throws Exception { Dict.builder() .put("key", "val") .buildImmutable()) + .setDevDependency(false) .setLocation( Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 4, 11)) .build()) @@ -501,6 +502,7 @@ public void testModuleExtensions_good() throws Exception { Dict.builder() .put("key1", "val1") .buildImmutable()) + .setDevDependency(false) .setLocation( Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 7, 12)) .build()) @@ -511,6 +513,7 @@ public void testModuleExtensions_good() throws Exception { Dict.builder() .put("key2", "val2") .buildImmutable()) + .setDevDependency(false) .setLocation( Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 8, 12)) .build()) @@ -529,6 +532,7 @@ public void testModuleExtensions_good() throws Exception { Dict.builder() .put("coord", "junit") .buildImmutable()) + .setDevDependency(false) .setLocation( Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 12, 10)) .build()) @@ -539,6 +543,7 @@ public void testModuleExtensions_good() throws Exception { Dict.builder() .put("coord", "guava") .buildImmutable()) + .setDevDependency(false) .setLocation( Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 14, 10)) .build()) @@ -551,12 +556,16 @@ public void testModuleExtensions_duplicateProxy_asRoot() throws Exception { scratch.file( rootDirectory.getRelative("MODULE.bazel").getPathString(), "myext1 = use_extension('//:defs.bzl','myext',dev_dependency=True)", + "myext1.tag(name = 'tag1')", "use_repo(myext1, 'alpha')", "myext2 = use_extension('//:defs.bzl','myext')", + "myext2.tag(name = 'tag2')", "use_repo(myext2, 'beta')", "myext3 = use_extension('//:defs.bzl','myext',dev_dependency=True)", + "myext3.tag(name = 'tag3')", "use_repo(myext3, 'gamma')", "myext4 = use_extension('//:defs.bzl','myext')", + "myext4.tag(name = 'tag4')", "use_repo(myext4, 'delta')"); ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of()); @@ -580,6 +589,34 @@ public void testModuleExtensions_duplicateProxy_asRoot() throws Exception { ImmutableBiMap.of( "alpha", "alpha", "beta", "beta", "gamma", "gamma", "delta", "delta")) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag1").buildImmutable()) + .setDevDependency(true) + .setLocation(Location.fromFileLineColumn("/MODULE.bazel", 2, 11)) + .build()) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag2").buildImmutable()) + .setDevDependency(false) + .setLocation(Location.fromFileLineColumn("/MODULE.bazel", 5, 11)) + .build()) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag3").buildImmutable()) + .setDevDependency(true) + .setLocation(Location.fromFileLineColumn("/MODULE.bazel", 8, 11)) + .build()) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag4").buildImmutable()) + .setDevDependency(false) + .setLocation(Location.fromFileLineColumn("/MODULE.bazel", 11, 11)) + .build()) .build()) .build()); } @@ -593,12 +630,16 @@ public void testModuleExtensions_duplicateProxy_asDep() throws Exception { createModuleKey("mymod", "1.0"), "module(name='mymod',version='1.0')", "myext1 = use_extension('//:defs.bzl','myext',dev_dependency=True)", + "myext1.tag(name = 'tag1')", "use_repo(myext1, 'alpha')", "myext2 = use_extension('//:defs.bzl','myext')", + "myext2.tag(name = 'tag2')", "use_repo(myext2, 'beta')", "myext3 = use_extension('//:defs.bzl','myext',dev_dependency=True)", + "myext3.tag(name = 'tag3')", "use_repo(myext3, 'gamma')", "myext4 = use_extension('//:defs.bzl','myext')", + "myext4.tag(name = 'tag4')", "use_repo(myext4, 'delta')"); ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl())); @@ -617,8 +658,22 @@ public void testModuleExtensions_duplicateProxy_asDep() throws Exception { ModuleExtensionUsage.builder() .setExtensionBzlFile("//:defs.bzl") .setExtensionName("myext") - .setLocation(Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 4, 23)) + .setLocation(Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 5, 23)) .setImports(ImmutableBiMap.of("beta", "beta", "delta", "delta")) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag2").buildImmutable()) + .setDevDependency(false) + .setLocation(Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 6, 11)) + .build()) + .addTag(Tag.builder() + .setTagName("tag") + .setAttributeValues( + Dict.builder().put("name", "tag4").buildImmutable()) + .setDevDependency(false) + .setLocation(Location.fromFileLineColumn("mymod@1.0/MODULE.bazel", 12, 11)) + .build()) .build()) .build()); } diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTagTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTagTest.java index c68ddb061901a4..0193e4ef9e0a56 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTagTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTagTest.java @@ -61,10 +61,11 @@ public void basic() throws Exception { TypeCheckedTag typeCheckedTag = TypeCheckedTag.create( createTagClass(attr("foo", Type.INTEGER).build()), - buildTag("tag_name").addAttr("foo", StarlarkInt.of(3)).build(), + buildTag("tag_name").addAttr("foo", StarlarkInt.of(3)).setDevDependency().build(), /*labelConverter=*/ null); assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo"); assertThat(getattr(typeCheckedTag, "foo")).isEqualTo(StarlarkInt.of(3)); + assertThat(typeCheckedTag.isDevDependency()).isTrue(); } @Test @@ -87,6 +88,7 @@ public void label() throws Exception { Label.parseCanonicalUnchecked("@myrepo//mypkg:thing1"), Label.parseCanonicalUnchecked("@myrepo//pkg:thing2"), Label.parseCanonicalUnchecked("@other_repo//pkg:thing3"))); + assertThat(typeCheckedTag.isDevDependency()).isFalse(); } @Test @@ -95,12 +97,13 @@ public void label_withoutDefaultValue() throws Exception { TypeCheckedTag.create( createTagClass( attr("foo", BuildType.LABEL).allowedFileTypes(FileTypeSet.ANY_FILE).build()), - buildTag("tag_name").build(), + buildTag("tag_name").setDevDependency().build(), new LabelConverter( PackageIdentifier.parse("@myrepo//mypkg"), createRepositoryMapping(createModuleKey("test", "1.0"), "repo", "other_repo"))); assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo"); assertThat(getattr(typeCheckedTag, "foo")).isEqualTo(Starlark.NONE); + assertThat(typeCheckedTag.isDevDependency()).isTrue(); } @Test @@ -119,6 +122,7 @@ public void stringListDict_default() throws Exception { Dict.builder() .put("key", StarlarkList.immutableOf("value1", "value2")) .buildImmutable()); + assertThat(typeCheckedTag.isDevDependency()).isFalse(); } @Test @@ -139,6 +143,7 @@ public void multipleAttributesAndDefaults() throws Exception { assertThat(getattr(typeCheckedTag, "bar")).isEqualTo(StarlarkInt.of(3)); assertThat(getattr(typeCheckedTag, "quux")) .isEqualTo(StarlarkList.immutableOf("quuxValue1", "quuxValue2")); + assertThat(typeCheckedTag.isDevDependency()).isFalse(); } @Test