diff --git a/CHANGELOG.md b/CHANGELOG.md index 4866d56..dd29be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Extra Java Module Info Gradle Plugin - Changelog ## Version 1.11 +* [New] [#134](https://github.com/gradlex-org/extra-java-module-info/pull/134) - Add 'disable(...)' and 'enable(...)' options to control when the plugin is active * [New] [#161](https://github.com/gradlex-org/extra-java-module-info/pull/161) - Add 'skipLocalJars' option * [New] [#106](https://github.com/gradlex-org/extra-java-module-info/pull/106) - Actionable error message when plugin is used at configuration time diff --git a/README.md b/README.md index 2261c49..53d12f5 100644 --- a/README.md +++ b/README.md @@ -126,51 +126,29 @@ Sample uses Gradle's Kotlin DSL (`build.gradle.kts` file). The Groovy DSL syntax # FAQ -## How do I deactivate the plugin functionality for a certain classpath? +## How do I (de)activate the plugin functionality for a certain classpath? -This can be useful for the test classpath if it should be used for unit testing on the classpath (rather than the module path). -If you use the [shadow plugin](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) and [encounter this issue](https://github.com/gradlex-org/extra-java-module-info/issues/7), -you can deactivate it for the runtime classpath as the module information is irrelevant for a fat Jar in any case. +The plugin activates Jar transformation functionality individually for each +[resolvable configuration](https://docs.gradle.org/current/userguide/declaring_configurations.html#sec:configuration-flags-roles) +(.e.g `compileClasspath`, `runtimeClasspath`, `testRuntimeClasspath`). +When any of these configurations are used, for example by the `compileJava` task, the transformed Jars will be used +instead of the original ones. By default, the plugin activates for all configurations that are linked to a _source set_, +which are: `CompileClasspath`, `RuntimeClasspath` and `AnnotationProcessor`. -**Kotlin DSL** -``` -// Disable for a single Classpath (Configuration) -configurations { - runtimeClasspath { // testRuntimeClasspath, testCompileClasspath, ... - attributes { attribute(Attribute.of("javaModule", Boolean::class.javaObjectType), false) } - } -} - -// Disable for all 'annotationProcessor' paths -sourceSets.all { - configurations.getByName(annotationProcessorConfigurationName) { - attributes { attribute(Attribute.of("javaModule", Boolean::class.javaObjectType), false) } - } -} -``` +You can customize this as follows: -**Groovy DSL** -``` -// Disable for a single Classpath (Configuration) -configurations { - runtimeClasspath { // testRuntimeClasspath, testCompileClasspath, ... - attributes { attribute(Attribute.of("javaModule", Boolean), false) } - } -} - -// Disable for all 'annotationProcessor' paths -sourceSets.all { - configurations.getByName(annotationProcessorConfigurationName) { - attributes { attribute(Attribute.of("javaModule", Boolean), false) } - } -} -``` +- Completely deactivate the plugin for a source set:
+ `extraJavaModuleInfo { deactivate(sourceSets.test) }` +- Deactivate the plugin for a selected configuration:
+ `extraJavaModuleInfo { deactivate(configurations.annotationProcessor) }` +- Activate the plugin for a custom configuration that is not linked to a source set:
+ `extraJavaModuleInfo { activate(configurations["myCustomPath"]) }` ## How do I deactivate the plugin functionality for my own Jars? A major use case of the plugin is to transform Jars from 3rd party repositories that you do not control. By default, however, the plugin looks at all Jars on the module paths – including the Jars Gradle builds from you own modules. -This is working well in most cases. The jars are analyzed and the plugin detects that they are infact modules and does not modify them. +This is working well in most cases. The jars are analyzed and the plugin detects that they are in fact modules and does not modify them. You can still optimize the plugin execution to completely skip analysis of locally-built Jars by setting `skipLocalJars = true`. ## How do I add `provides ... with ...` declarations to the `module-info.class` descriptor? diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java index 87d721c..1896ac3 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java @@ -21,7 +21,6 @@ import org.gradle.api.Project; import org.gradle.api.Transformer; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.dsl.DependencyHandler; @@ -59,6 +58,7 @@ import static org.gradle.api.attributes.Usage.JAVA_RUNTIME; import static org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE; import static org.gradle.api.plugins.JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME; +import static org.gradlex.javamodule.moduleinfo.ExtraJavaModuleInfoPluginExtension.JAVA_MODULE_ATTRIBUTE; /** * Entry point of the plugin. @@ -161,50 +161,41 @@ private void configureTransform(Project project, ExtraJavaModuleInfoPluginExtens }); Attribute artifactType = Attribute.of("artifactType", String.class); - Attribute javaModule = Attribute.of("javaModule", Boolean.class); project.getExtensions().getByType(SourceSetContainer.class).all(sourceSet -> { - Configuration runtimeClasspath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); - Configuration compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); - Configuration annotationProcessor = project.getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName()); - Configuration runtimeElements = project.getConfigurations().findByName(sourceSet.getRuntimeElementsConfigurationName()); - Configuration apiElements = project.getConfigurations().findByName(sourceSet.getApiElementsConfigurationName()); - - // compile, runtime and annotation processor classpaths express that they only accept modules by requesting the javaModule=true attribute - runtimeClasspath.getAttributes().attribute(javaModule, true); - compileClasspath.getAttributes().attribute(javaModule, true); - annotationProcessor.getAttributes().attribute(javaModule, true); + // by default, activate plugin for all source sets + extension.activate(sourceSet); // outgoing variants may express that they already provide a modular Jar and can hence skip the transform altogether + Configuration runtimeElements = project.getConfigurations().findByName(sourceSet.getRuntimeElementsConfigurationName()); + Configuration apiElements = project.getConfigurations().findByName(sourceSet.getApiElementsConfigurationName()); if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) >= 0) { if (runtimeElements != null) { - runtimeElements.getOutgoing().getAttributes().attributeProvider(javaModule, extension.getSkipLocalJars()); + runtimeElements.getOutgoing().getAttributes().attributeProvider(JAVA_MODULE_ATTRIBUTE, extension.getSkipLocalJars()); } if (apiElements != null) { - apiElements.getOutgoing().getAttributes().attributeProvider(javaModule, extension.getSkipLocalJars()); + apiElements.getOutgoing().getAttributes().attributeProvider(JAVA_MODULE_ATTRIBUTE, extension.getSkipLocalJars()); } } else { project.afterEvaluate(p -> { if (runtimeElements != null) { - runtimeElements.getOutgoing().getAttributes().attribute(javaModule, extension.getSkipLocalJars().get()); + runtimeElements.getOutgoing().getAttributes().attribute(JAVA_MODULE_ATTRIBUTE, extension.getSkipLocalJars().get()); } if (apiElements != null) { - apiElements.getOutgoing().getAttributes().attribute(javaModule, extension.getSkipLocalJars().get()); + apiElements.getOutgoing().getAttributes().attribute(JAVA_MODULE_ATTRIBUTE, extension.getSkipLocalJars().get()); } }); } }); // Jars may be transformed (or merged into) Module Jars - registerTransform("jar", project, extension, javaModulesMergeJars, artifactType, javaModule); + registerTransform("jar", project, extension, javaModulesMergeJars, artifactType, JAVA_MODULE_ATTRIBUTE); // Classpath entries may also be zip files that may be merged into Module Jars (from the docs: "Class paths to the .jar, .zip or .class files)" - registerTransform("zip", project, extension, javaModulesMergeJars, artifactType, javaModule); + registerTransform("zip", project, extension, javaModulesMergeJars, artifactType, JAVA_MODULE_ATTRIBUTE); } private void registerTransform(String fileExtension, Project project, ExtraJavaModuleInfoPluginExtension extension, Configuration javaModulesMergeJars, Attribute artifactType, Attribute javaModule) { DependencyHandler dependencies = project.getDependencies(); - ConfigurationContainer configurations = project.getConfigurations(); - SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); // all Jars have a javaModule=false attribute by default; the transform also recognizes modules and returns them without modification dependencies.getArtifactTypes().maybeCreate(fileExtension).getAttributes().attribute(javaModule, false); diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java index a80e6bb..8d5cd2d 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java @@ -17,11 +17,16 @@ package org.gradlex.javamodule.moduleinfo; import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectProvider; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.MinimalExternalModuleDependency; +import org.gradle.api.attributes.Attribute; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; import javax.annotation.Nullable; import javax.inject.Inject; @@ -33,10 +38,14 @@ */ @SuppressWarnings("unused") public abstract class ExtraJavaModuleInfoPluginExtension { + static Attribute JAVA_MODULE_ATTRIBUTE = Attribute.of("javaModule", Boolean.class); @Inject protected abstract ObjectFactory getObjects(); + @Inject + protected abstract ConfigurationContainer getConfigurations(); + public abstract MapProperty getModuleSpecs(); public abstract Property getFailOnMissingModuleInfo(); public abstract Property getFailOnAutomaticModules(); @@ -209,4 +218,97 @@ public void knownModule(String coordinates, String moduleName) { public void knownModule(Provider alias, String moduleName) { knownModule(alias.get().getModule().toString(), moduleName); } + + /** + * Activate the plugin's functionality for dependencies of all scopes of the given source set + * (runtimeClasspath, compileClasspath, annotationProcessor). + * Note that the plugin activates the functionality for all source sets by default. + * Therefore, this method only has an effect for source sets for which a {@link #deactivate(SourceSet)} + * has been performed. + * + * @param sourceSet the Source Set to activate (e.g. sourceSets.test) + */ + public void activate(SourceSet sourceSet) { + Configuration runtimeClasspath = getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); + Configuration compileClasspath = getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); + Configuration annotationProcessor = getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName()); + + activate(runtimeClasspath); + activate(compileClasspath); + activate(annotationProcessor); + } + + /** + * Activate the plugin's functionality for a single resolvable Configuration. + * This is useful to use the plugins for scopes that are not tied to a Source Set, + * for which the plugin does not activate automatically. + * + * @param resolvable a resolvable Configuration (e.g. configurations["customClasspath"]) + */ + public void activate(Configuration resolvable) { + resolvable.getAttributes().attribute(JAVA_MODULE_ATTRIBUTE, true); + } + + /** + * Variant of {@link #activate(SourceSet)} and {@link #activate(Configuration)} that accepts either a + * Provider of {@link SourceSet} or a Provider of {@link Configuration}. This is a convenience to use + * notations like 'activate(sourceSets.main)' in Kotlin DSL. + * + * @param sourceSetOrResolvable the Source Set or Configuration to activate + */ + public void activate(NamedDomainObjectProvider sourceSetOrResolvable) { + Object realized = sourceSetOrResolvable.get(); + if (realized instanceof SourceSet) { + activate((SourceSet) realized); + } else if (realized instanceof Configuration) { + activate((Configuration) realized); + } else { + throw new RuntimeException("Not SourceSet or Configuration: " + realized); + } + } + + /** + * Deactivate the plugin's functionality for dependencies of all scopes of the given source set + * (runtimeClasspath, compileClasspath, annotationProcessor). + * + * @param sourceSet the Source Set to deactivate (e.g. sourceSets.test) + */ + public void deactivate(SourceSet sourceSet) { + Configuration runtimeClasspath = getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); + Configuration compileClasspath = getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); + Configuration annotationProcessor = getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName()); + + deactivate(runtimeClasspath); + deactivate(compileClasspath); + deactivate(annotationProcessor); + } + + /** + * Deactivate the plugin's functionality for a single resolvable Configuration. + * This is useful if selected scopes do not use the Module Path and therefore + * module information is not required. + * + * @param resolvable a resolvable Configuration (e.g. configurations.annotationProcessor) + */ + public void deactivate(Configuration resolvable) { + resolvable.getAttributes().attribute(JAVA_MODULE_ATTRIBUTE, false); + } + + /** + * Variant of {@link #deactivate(SourceSet)} and {@link #deactivate(Configuration)} that accepts either a + * Provider of {@link SourceSet} or a Provider of {@link Configuration}. This is a convenience to use + * notations like 'deactivate(sourceSets.test)' in Kotlin DSL. + * + * @param sourceSetOrResolvable the Source Set or Configuration to activate + */ + public void deactivate(NamedDomainObjectProvider sourceSetOrResolvable) { + Object realized = sourceSetOrResolvable.get(); + if (realized instanceof SourceSet) { + deactivate((SourceSet) realized); + } else if (realized instanceof Configuration) { + deactivate((Configuration) realized); + } else { + throw new RuntimeException("Not SourceSet or Configuration: " + realized); + } + } } diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/PluginActivationFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/PluginActivationFunctionalTest.groovy new file mode 100644 index 0000000..30208a4 --- /dev/null +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/PluginActivationFunctionalTest.groovy @@ -0,0 +1,112 @@ +package org.gradlex.javamodule.moduleinfo.test + +import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild +import spock.lang.Specification + +class PluginActivationFunctionalTest extends Specification { + + @Delegate + GradleBuild build = new GradleBuild() + + def setup() { + settingsFile << 'rootProject.name = "test-project"' + buildFile << ''' + plugins { + id("java-library") + id("org.gradlex.extra-java-module-info") + } + val customPath = configurations.create("customPath") + dependencies { + implementation("commons-cli:commons-cli:1.4") + annotationProcessor("commons-cli:commons-cli:1.4") + customPath("commons-cli:commons-cli:1.4") + } + extraJavaModuleInfo { module("commons-cli:commons-cli", "org.apache.commons.cli") } + tasks.register("printRuntimeCP") { + inputs.files(configurations.runtimeClasspath) + doLast { print("RCP: "); println(inputs.files.files.map { it.name }) } + } + tasks.register("printCompileCP") { + inputs.files(configurations.compileClasspath) + doLast { print("CCP: "); println(inputs.files.files.map { it.name }) } + } + tasks.register("printAP") { + inputs.files(configurations.annotationProcessor) + doLast { print("AP: "); println(inputs.files.files.map { it.name }) } + } + tasks.register("printCustom") { + inputs.files(configurations["customPath"]) + doLast { print("CUS: "); println(inputs.files.files.map { it.name }) } + } + ''' + } + + def "plugin is a activated by default for all configurations of a source set"() { + when: + def result = task('printRuntimeCP', 'printCompileCP', 'printAP', '-q') + + then: + result.output.contains('RCP: [commons-cli-1.4-module.jar]') + result.output.contains('CCP: [commons-cli-1.4-module.jar]') + result.output.contains('AP: [commons-cli-1.4-module.jar]') + } + + def "plugin can be deactivated for a source set"() { + given: + buildFile << 'extraJavaModuleInfo { deactivate(sourceSets.main) }' + + when: + def result = task('printRuntimeCP', 'printCompileCP', 'printAP', '-q') + + then: + result.output.contains('RCP: [commons-cli-1.4.jar]') + result.output.contains('CCP: [commons-cli-1.4.jar]') + result.output.contains('AP: [commons-cli-1.4.jar]') + } + + def "plugin can be deactivated and later re-activated for a source set"() { + given: + buildFile << 'extraJavaModuleInfo { deactivate(sourceSets.main.get()) }\n' + buildFile << 'extraJavaModuleInfo { activate(sourceSets.main) }\n' + + when: + def result = task('printRuntimeCP', 'printCompileCP', 'printAP', '-q') + + then: + result.output.contains('RCP: [commons-cli-1.4-module.jar]') + result.output.contains('CCP: [commons-cli-1.4-module.jar]') + result.output.contains('AP: [commons-cli-1.4-module.jar]') + } + + def "plugin can be deactivated for a single configuration"() { + given: + buildFile << 'extraJavaModuleInfo { deactivate(configurations.annotationProcessor) }' + + when: + def result = task('printRuntimeCP', 'printCompileCP', 'printAP', '-q') + + then: + result.output.contains('RCP: [commons-cli-1.4-module.jar]') + result.output.contains('CCP: [commons-cli-1.4-module.jar]') + result.output.contains('AP: [commons-cli-1.4.jar]') + } + + def "plugin is not active for custom configurations by default"() { + when: + def result = task('printCustom', '-q') + + then: + result.output.contains('CUS: [commons-cli-1.4.jar]') + } + + def "plugin can be activated for a single custom configuration"() { + given: + buildFile << 'extraJavaModuleInfo { activate(customPath) }' + + when: + def result = task('printCustom', '-q') + + then: + result.output.contains('CUS: [commons-cli-1.4-module.jar]') + } +}