diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/JavaRules.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/JavaRules.java index 3c8627f7edeaf7..b1b788ad726397 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/JavaRules.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/JavaRules.java @@ -34,6 +34,7 @@ import com.google.devtools.build.lib.rules.java.JavaInfo; import com.google.devtools.build.lib.rules.java.JavaOptions; import com.google.devtools.build.lib.rules.java.JavaPackageConfigurationRule; +import com.google.devtools.build.lib.rules.java.JavaPluginInfo; import com.google.devtools.build.lib.rules.java.JavaRuleClasses.IjarBaseRule; import com.google.devtools.build.lib.rules.java.JavaRuleClasses.JavaRuntimeBaseRule; import com.google.devtools.build.lib.rules.java.JavaRuleClasses.JavaToolchainBaseRule; @@ -92,6 +93,7 @@ public void init(ConfiguredRuleClassProvider.Builder builder) { new JavaBootstrap( new JavaStarlarkCommon(BazelJavaSemantics.INSTANCE), JavaInfo.PROVIDER, + JavaPluginInfo.PROVIDER, new JavaProtoStarlarkCommon(), JavaCcLinkParamsProvider.PROVIDER, ProguardSpecProvider.PROVIDER)); diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaPluginInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaPluginInfo.java index 8c5a3149617407..f36df8b7f5098a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaPluginInfo.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaPluginInfo.java @@ -24,16 +24,21 @@ import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.packages.BuiltinProvider; import com.google.devtools.build.lib.packages.NativeInfo; +import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; -import com.google.devtools.build.lib.starlarkbuildapi.java.JavaPluginInfoApi.JavaPluginDataApi; +import com.google.devtools.build.lib.starlarkbuildapi.java.JavaPluginInfoApi; import java.util.ArrayList; import java.util.List; +import net.starlark.java.eval.EvalException; +import net.starlark.java.eval.Sequence; +import net.starlark.java.eval.Starlark; /** Provider for users of Java plugins. */ @AutoCodec @Immutable @AutoValue -public abstract class JavaPluginInfo extends NativeInfo { +public abstract class JavaPluginInfo extends NativeInfo + implements JavaPluginInfoApi { public static final String PROVIDER_NAME = "JavaPluginInfo"; public static final Provider PROVIDER = new Provider(); @@ -43,17 +48,44 @@ public Provider getProvider() { } /** Provider class for {@link JavaPluginInfo} objects. */ - public static class Provider extends BuiltinProvider { + public static class Provider extends BuiltinProvider + implements JavaPluginInfoApi.Provider { private Provider() { super(PROVIDER_NAME, JavaPluginInfo.class); } + + @Override + public JavaPluginInfoApi javaPluginInfo( + Sequence runtimeDeps, Object processorClass, Object processorData, Boolean generatesApi) + throws EvalException { + NestedSet processorClasses = + processorClass == Starlark.NONE + ? NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER) + : NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, (String) processorClass); + NestedSet processorClasspath = + JavaInfo.merge(Sequence.cast(runtimeDeps, JavaInfo.class, "runtime_deps")) + .getProvider(JavaCompilationArgsProvider.class) + .getRuntimeJars(); + + final NestedSet data; + if (processorData instanceof Depset) { + data = Depset.cast(processorData, Artifact.class, "data"); + } else { + data = + NestedSetBuilder.wrap( + Order.NAIVE_LINK_ORDER, Sequence.cast(processorData, Artifact.class, "data")); + } + + return JavaPluginInfo.create( + JavaPluginData.create(processorClasses, processorClasspath, data), generatesApi); + } } /** Information about a Java plugin, except for whether it generates API. */ @AutoCodec @Immutable @AutoValue - public abstract static class JavaPluginData implements JavaPluginDataApi { + public abstract static class JavaPluginData implements JavaPluginInfoApi.JavaPluginDataApi { public static JavaPluginData create( NestedSet processorClasses, diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java index 05dd62ef0da3e4..f17f4cb8c7686b 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java @@ -25,6 +25,7 @@ public class JavaBootstrap implements Bootstrap { private final JavaCommonApi javaCommonApi; private final JavaInfoProviderApi javaInfoProviderApi; + private final JavaPluginInfoApi.Provider javaPluginInfoProviderApi; private final JavaProtoCommonApi javaProtoCommonApi; private final JavaCcLinkParamsProviderApi.Provider javaCcLinkParamsProviderApiProvider; private final ProguardSpecProviderApi.Provider proguardSpecProvider; @@ -32,11 +33,13 @@ public class JavaBootstrap implements Bootstrap { public JavaBootstrap( JavaCommonApi javaCommonApi, JavaInfoProviderApi javaInfoProviderApi, + JavaPluginInfoApi.Provider javaPluginInfoProviderApi, JavaProtoCommonApi javaProtoCommonApi, JavaCcLinkParamsProviderApi.Provider javaCcLinkParamsProviderApiProvider, ProguardSpecProviderApi.Provider proguardSpecProvider) { this.javaCommonApi = javaCommonApi; this.javaInfoProviderApi = javaInfoProviderApi; + this.javaPluginInfoProviderApi = javaPluginInfoProviderApi; this.javaProtoCommonApi = javaProtoCommonApi; this.javaCcLinkParamsProviderApiProvider = javaCcLinkParamsProviderApiProvider; this.proguardSpecProvider = proguardSpecProvider; @@ -46,6 +49,7 @@ public JavaBootstrap( public void addBindingsToBuilder(ImmutableMap.Builder builder) { builder.put("java_common", javaCommonApi); builder.put("JavaInfo", javaInfoProviderApi); + builder.put("JavaPluginInfo", javaPluginInfoProviderApi); builder.put("java_proto_common", javaProtoCommonApi); builder.put("JavaCcLinkParamsInfo", javaCcLinkParamsProviderApiProvider); diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaPluginInfoApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaPluginInfoApi.java index 8cbfa069ba7b6a..b27bbfd99b60fd 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaPluginInfoApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaPluginInfoApi.java @@ -14,11 +14,19 @@ package com.google.devtools.build.lib.starlarkbuildapi.java; import com.google.devtools.build.docgen.annot.DocCategory; +import com.google.devtools.build.docgen.annot.StarlarkConstructor; import com.google.devtools.build.lib.collect.nestedset.Depset; +import com.google.devtools.build.lib.starlarkbuildapi.FileApi; +import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi; import com.google.devtools.build.lib.starlarkbuildapi.core.StructApi; import com.google.devtools.build.lib.starlarkbuildapi.java.JavaPluginInfoApi.JavaPluginDataApi; +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; +import net.starlark.java.eval.NoneType; +import net.starlark.java.eval.Sequence; import net.starlark.java.eval.StarlarkValue; /** Info object encapsulating information about Java plugins. */ @@ -72,4 +80,61 @@ interface JavaPluginDataApi extends StarlarkValue { structField = true) Depset /**/ getProcessorDataForStarlark(); } + + /** Provider class for {@link JavaPluginInfoApi} objects. */ + @StarlarkBuiltin(name = "Provider", documented = false) + interface Provider> extends ProviderApi { + + @StarlarkMethod( + name = "JavaPluginInfo", + doc = "The JavaPluginInfo constructor.", + parameters = { + @Param( + name = "runtime_deps", + allowedTypes = { + @ParamType(type = Sequence.class, generic1 = JavaInfoApi.class), + }, + named = true, + doc = "The library containing an annotation processor."), + @Param( + name = "processor_class", + named = true, + positional = false, + allowedTypes = { + @ParamType(type = String.class), + @ParamType(type = NoneType.class), + }, + doc = + "The fully qualified class name that the Java compiler uses as " + + "an entry point to the annotation processor."), + @Param( + name = "data", + allowedTypes = { + @ParamType(type = Sequence.class, generic1 = FileApi.class), + @ParamType(type = Depset.class, generic1 = FileApi.class), + }, + named = true, + positional = false, + defaultValue = "[]", + doc = "The files needed by this annotation processor during execution."), + @Param( + name = "generates_api", + named = true, + positional = false, + defaultValue = "False", + doc = + "Set to true when this annotation processor generates API code. " + + "

Such annotation processor is applied to a Java target before producing " + + "its header jars (which contains method signatures). When no API plugins " + + "are present, header jars are generated from the sources, reducing the " + + "critical path. " + + "

WARNING: This parameter affects build " + + "performance, use it only if necessary."), + }, + selfCall = true) + @StarlarkConstructor + JavaPluginInfoApi javaPluginInfo( + Sequence runtimeDeps, Object processorClass, Object processorData, Boolean generatesApi) + throws EvalException; + } } diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java index ebc923494507e4..c4b8775a09a756 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java @@ -1140,6 +1140,74 @@ public void apiGeneratingjavaPlugin_javaInfoExposesPluginsToStarlark() throws Ex assertThat(apiPluginData).isEqualTo(pluginData); } + /** Tests that java_plugin exposes plugin information to Starlark. */ + @Test + public void javaPlugin_exposesPluginsToStarlark() throws Exception { + scratch.file( + "java/test/BUILD", + "java_library(", + " name = 'plugin_dep',", + " srcs = ['ProcessorDep.java'],", + " data = ['depfile.dat'],", + ")", + "java_plugin(", + " name = 'plugin',", + " srcs = ['AnnotationProcessor.java'],", + " processor_class = 'com.google.process.stuff',", + " deps = [':plugin_dep'],", + " data = ['pluginfile.dat'],", + ")"); + + JavaPluginData pluginData = + retrieveStarlarkPluginData( + "//java/test:plugin", /* provider = */ "JavaPluginInfo", /* apiGenerating = */ false); + JavaPluginData apiPluginData = + retrieveStarlarkPluginData( + "//java/test:plugin", /* provider = */ "JavaPluginInfo", /* apiGenerating = */ true); + + assertThat(pluginData.processorClasses().toList()).containsExactly("com.google.process.stuff"); + assertThat(pluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("libplugin.jar", "libplugin_dep.jar"); + assertThat(pluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile.dat"); + assertThat(apiPluginData).isEqualTo(JavaPluginData.empty()); + } + + /** Tests that api generating java_plugin exposes plugin information to Starlark. */ + @Test + public void apiGeneratingjavaPlugin_exposesPluginsToStarlark() throws Exception { + scratch.file( + "java/test/BUILD", + "java_library(", + " name = 'plugin_dep',", + " srcs = ['ProcessorDep.java'],", + " data = ['depfile.dat'],", + ")", + "java_plugin(", + " name = 'plugin',", + " generates_api = True,", + " srcs = ['AnnotationProcessor.java'],", + " processor_class = 'com.google.process.stuff',", + " deps = [':plugin_dep'],", + " data = ['pluginfile.dat'],", + ")"); + + JavaPluginData pluginData = + retrieveStarlarkPluginData( + "//java/test:plugin", /* provider = */ "JavaPluginInfo", /* apiGenerating = */ false); + JavaPluginData apiPluginData = + retrieveStarlarkPluginData( + "//java/test:plugin", /* provider = */ "JavaPluginInfo", /* apiGenerating = */ true); + + assertThat(apiPluginData.processorClasses().toList()) + .containsExactly("com.google.process.stuff"); + assertThat(apiPluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("libplugin.jar", "libplugin_dep.jar"); + assertThat(apiPluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile.dat"); + assertThat(apiPluginData).isEqualTo(pluginData); + } + /** Tests that java_library exposes exported plugin information to Starlark. */ @Test public void javaLibrary_exposesPluginsToStarlark() throws Exception { @@ -1189,6 +1257,192 @@ public void javaLibrary_exposesPluginsToStarlark() throws Exception { .containsExactly("pluginfile2.dat"); } + /** Tests the JavaPluginInfo provider's constructor. */ + @Test + public void javaPluginInfo_create() throws Exception { + scratch.file( + "java/test/myplugin.bzl", + "def _impl(ctx):", + " output_jar = ctx.actions.declare_file('lib.jar')", + " ctx.actions.write(output_jar, '')", + " dep = JavaInfo(output_jar = output_jar, compile_jar = None,", + " deps = [d[JavaInfo] for d in ctx.attr.deps])", + " return [JavaPluginInfo(", + " runtime_deps = [dep],", + " processor_class = ctx.attr.processor_class,", + " data = ctx.files.data,", + " )]", + "myplugin = rule(implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'processor_class': attr.string(),", + " 'data': attr.label_list(allow_files = True),", + " })"); + scratch.file( + "java/test/BUILD", + "load(':myplugin.bzl', 'myplugin')", + "java_library(name = 'plugin_dep1', srcs = ['A.java'], data = ['depfile1.dat'])", + "myplugin(", + " name = 'plugin',", + " processor_class = 'com.google.process.stuff',", + " deps = [':plugin_dep1'],", + " data = ['pluginfile1.dat'],", + ")"); + + JavaPluginInfo pluginInfo = + getConfiguredTarget("//java/test:plugin").get(JavaPluginInfo.PROVIDER); + JavaPluginData pluginData = pluginInfo.plugins(); + JavaPluginData apiPluginData = pluginInfo.apiGeneratingPlugins(); + + assertThat(pluginData.processorClasses().toList()).containsExactly("com.google.process.stuff"); + assertThat(pluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("lib.jar", "libplugin_dep1.jar"); + assertThat(pluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile1.dat"); + assertThat(apiPluginData.processorClasses().toList()).isEmpty(); + assertThat(apiPluginData.processorClasspath().toList()).isEmpty(); + assertThat(apiPluginData.data().toList()).isEmpty(); + } + + /** Tests the JavaPluginInfo provider's constructor for api generating plugin. */ + @Test + public void javaPluginInfo_createApiPlugin() throws Exception { + scratch.file( + "java/test/myplugin.bzl", + "def _impl(ctx):", + " output_jar = ctx.actions.declare_file('lib.jar')", + " ctx.actions.write(output_jar, '')", + " dep = JavaInfo(output_jar = output_jar, compile_jar = None,", + " deps = [d[JavaInfo] for d in ctx.attr.deps])", + " return [JavaPluginInfo(", + " runtime_deps = [dep],", + " processor_class = ctx.attr.processor_class,", + " data = ctx.files.data,", + " generates_api = True,", + " )]", + "myplugin = rule(implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'processor_class': attr.string(),", + " 'data': attr.label_list(allow_files = True),", + " })"); + scratch.file( + "java/test/BUILD", + "load(':myplugin.bzl', 'myplugin')", + "java_library(name = 'plugin_dep1', srcs = ['A.java'], data = ['depfile1.dat'])", + "myplugin(", + " name = 'plugin',", + " processor_class = 'com.google.process.stuff',", + " deps = [':plugin_dep1'],", + " data = ['pluginfile1.dat'],", + ")"); + + JavaPluginInfo pluginInfo = + getConfiguredTarget("//java/test:plugin").get(JavaPluginInfo.PROVIDER); + JavaPluginData pluginData = pluginInfo.plugins(); + JavaPluginData apiPluginData = pluginInfo.apiGeneratingPlugins(); + + assertThat(apiPluginData.processorClasses().toList()) + .containsExactly("com.google.process.stuff"); + assertThat(apiPluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("lib.jar", "libplugin_dep1.jar"); + assertThat(apiPluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile1.dat"); + assertThat(apiPluginData).isEqualTo(pluginData); + } + + /** Tests the JavaPluginInfo provider's constructor without processor class. */ + @Test + public void javaPluginInfo_createWithoutProcessorClass() throws Exception { + scratch.file( + "java/test/myplugin.bzl", + "def _impl(ctx):", + " output_jar = ctx.actions.declare_file('lib.jar')", + " ctx.actions.write(output_jar, '')", + " dep = JavaInfo(output_jar = output_jar, compile_jar = None,", + " deps = [d[JavaInfo] for d in ctx.attr.deps])", + " return [JavaPluginInfo(", + " runtime_deps = [dep],", + " processor_class = None,", + " data = ctx.files.data,", + " )]", + "myplugin = rule(implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'data': attr.label_list(allow_files = True),", + " })"); + scratch.file( + "java/test/BUILD", + "load(':myplugin.bzl', 'myplugin')", + "java_library(name = 'plugin_dep1', srcs = ['A.java'], data = ['depfile1.dat'])", + "myplugin(", + " name = 'plugin',", + " deps = [':plugin_dep1'],", + " data = ['pluginfile1.dat'],", + ")"); + + JavaPluginInfo pluginInfo = + getConfiguredTarget("//java/test:plugin").get(JavaPluginInfo.PROVIDER); + JavaPluginData pluginData = pluginInfo.plugins(); + JavaPluginData apiPluginData = pluginInfo.apiGeneratingPlugins(); + + assertThat(pluginData.processorClasses().toList()).isEmpty(); + assertThat(pluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("lib.jar", "libplugin_dep1.jar"); + assertThat(pluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile1.dat"); + assertThat(apiPluginData.processorClasses().toList()).isEmpty(); + assertThat(apiPluginData.processorClasspath().toList()).isEmpty(); + assertThat(apiPluginData.data().toList()).isEmpty(); + } + + /** Tests the JavaPluginInfo provider's constructor with data given as depset. */ + @Test + public void javaPluginInfo_createWithDataDepset() throws Exception { + scratch.file( + "java/test/myplugin.bzl", + "def _impl(ctx):", + " output_jar = ctx.actions.declare_file('lib.jar')", + " ctx.actions.write(output_jar, '')", + " dep = JavaInfo(output_jar = output_jar, compile_jar = None,", + " deps = [d[JavaInfo] for d in ctx.attr.deps])", + " return [JavaPluginInfo(", + " runtime_deps = [dep],", + " processor_class = ctx.attr.processor_class,", + " data = depset(ctx.files.data),", + " )]", + "myplugin = rule(implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'processor_class': attr.string(),", + " 'data': attr.label_list(allow_files = True),", + " })"); + scratch.file( + "java/test/BUILD", + "load(':myplugin.bzl', 'myplugin')", + "java_library(name = 'plugin_dep1', srcs = ['A.java'], data = ['depfile1.dat'])", + "myplugin(", + " name = 'plugin',", + " processor_class = 'com.google.process.stuff',", + " deps = [':plugin_dep1'],", + " data = ['pluginfile1.dat'],", + ")"); + + JavaPluginInfo pluginInfo = + getConfiguredTarget("//java/test:plugin").get(JavaPluginInfo.PROVIDER); + JavaPluginData pluginData = pluginInfo.plugins(); + JavaPluginData apiPluginData = pluginInfo.apiGeneratingPlugins(); + + assertThat(pluginData.processorClasses().toList()).containsExactly("com.google.process.stuff"); + assertThat(pluginData.processorClasspath().toList().stream().map(Artifact::getFilename)) + .containsExactly("lib.jar", "libplugin_dep1.jar"); + assertThat(pluginData.data().toList().stream().map(Artifact::getFilename)) + .containsExactly("pluginfile1.dat"); + assertThat(apiPluginData.processorClasses().toList()).isEmpty(); + assertThat(apiPluginData.processorClasspath().toList()).isEmpty(); + assertThat(apiPluginData.data().toList()).isEmpty(); + } + @Test public void testJavaProviderFieldsAreStarlarkAccessible() throws Exception { // The Starlark evaluation itself will test that compile_jars and