Skip to content

Add 'disable(...)' and 'enable(...)' options #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
52 changes: 15 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<sourcSet>CompileClasspath`, `<sourcSet>RuntimeClasspath` and `<sourceSet>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:<br/>
`extraJavaModuleInfo { deactivate(sourceSets.test) }`
- Deactivate the plugin for a selected configuration:<br/>
`extraJavaModuleInfo { deactivate(configurations.annotationProcessor) }`
- Activate the plugin for a custom configuration that is not linked to a source set:<br/>
`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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -161,50 +161,41 @@ private void configureTransform(Project project, ExtraJavaModuleInfoPluginExtens
});

Attribute<String> artifactType = Attribute.of("artifactType", String.class);
Attribute<Boolean> 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<String> artifactType, Attribute<Boolean> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,10 +38,14 @@
*/
@SuppressWarnings("unused")
public abstract class ExtraJavaModuleInfoPluginExtension {
static Attribute<Boolean> JAVA_MODULE_ATTRIBUTE = Attribute.of("javaModule", Boolean.class);

@Inject
protected abstract ObjectFactory getObjects();

@Inject
protected abstract ConfigurationContainer getConfigurations();

public abstract MapProperty<String, ModuleSpec> getModuleSpecs();
public abstract Property<Boolean> getFailOnMissingModuleInfo();
public abstract Property<Boolean> getFailOnAutomaticModules();
Expand Down Expand Up @@ -209,4 +218,97 @@ public void knownModule(String coordinates, String moduleName) {
public void knownModule(Provider<MinimalExternalModuleDependency> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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]')
}
}