Skip to content

Commit 05ea60c

Browse files
authored
Add skipLocalJars option and error message when used at config time (#165)
1 parent 9b2e09c commit 05ea60c

File tree

7 files changed

+189
-14
lines changed

7 files changed

+189
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Extra Java Module Info Gradle Plugin - Changelog
22

3+
## Version 1.11
4+
* [New] [#161](https://github.com/gradlex-org/extra-java-module-info/pull/161) - Add 'skipLocalJars' option
5+
* [New] [#106](https://github.com/gradlex-org/extra-java-module-info/pull/106) - Actionable error message when plugin is used at configuration time
6+
37
## Version 1.10.1
48
* [Fix] [#164](https://github.com/gradlex-org/extra-java-module-info/pull/164) - fix: 'preserveExisting' does not duplicate 'provides' entries
59

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ plugins {
6767
6868
// add module information for all direct and transitive dependencies that are not modules
6969
extraJavaModuleInfo {
70-
// failOnMissingModuleInfo.set(false)
70+
// failOnMissingModuleInfo = false
71+
// failOnAutomaticModules = true
72+
// skipLocalJars = true
7173
module("commons-beanutils:commons-beanutils", "org.apache.commons.beanutils") {
7274
exports("org.apache.commons.beanutils")
7375
// or granuarly allowing access to a package by specific modules
@@ -164,6 +166,13 @@ sourceSets.all {
164166
}
165167
```
166168

169+
## How do I deactivate the plugin functionality for my own Jars?
170+
171+
A major use case of the plugin is to transform Jars from 3rd party repositories that you do not control.
172+
By default, however, the plugin looks at all Jars on the module paths – including the Jars Gradle builds from you own modules.
173+
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.
174+
You can still optimize the plugin execution to completely skip analysis of locally-built Jars by setting `skipLocalJars = true`.
175+
167176
## How do I add `provides ... with ...` declarations to the `module-info.class` descriptor?
168177

169178
The plugin will automatically retrofit all the available `META-INF/services/*` descriptors into `module-info.class` for you. The `META-INF/services/*` descriptors will be preserved so that a transformed JAR will continue to work if it is placed on the classpath.
@@ -263,15 +272,15 @@ The plugin provides a set of `<sourceSet>moduleDescriptorRecommendations` tasks
263272

264273
This task generates module info spec for the JARs that do not contain the proper `module-info.class` descriptors.
265274

266-
NOTE: This functionality requires Gradle to be run with Java 11+ and failing on missing module information should be disabled via `failOnMissingModuleInfo.set(false)`.
275+
NOTE: This functionality requires Gradle to be run with Java 11+ and failing on missing module information should be disabled via `failOnMissingModuleInfo = false`.
267276

268277
## How can I ensure there are no automatic modules in my dependency graph?
269278

270279
If your goal is to fully modularize your application, you should enable the following configuration setting, which is disabled by default.
271280

272281
```
273282
extraJavaModuleInfo {
274-
failOnAutomaticModules.set(true)
283+
failOnAutomaticModules = true
275284
}
276285
```
277286

@@ -282,7 +291,7 @@ dependencies {
282291
implementation("org.yaml:snakeyaml:1.33")
283292
}
284293
extraJavaModuleInfo {
285-
failOnAutomaticModules.set(true)
294+
failOnAutomaticModules = true
286295
module("org.yaml:snakeyaml", "org.yaml.snakeyaml") {
287296
closeModule()
288297
exports("org.yaml.snakeyaml")
@@ -351,7 +360,7 @@ However, if you get started and just want things to be put on the Module Path, y
351360

352361
```
353362
extraJavaModuleInfo {
354-
deriveAutomaticModuleNamesFromFileNames.set(true)
363+
deriveAutomaticModuleNamesFromFileNames = true
355364
}
356365
```
357366

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public void apply(Project project) {
7676
ExtraJavaModuleInfoPluginExtension extension = project.getExtensions().create("extraJavaModuleInfo", ExtraJavaModuleInfoPluginExtension.class);
7777
extension.getFailOnMissingModuleInfo().convention(true);
7878
extension.getFailOnAutomaticModules().convention(false);
79+
extension.getSkipLocalJars().convention(false);
7980
extension.getDeriveAutomaticModuleNamesFromFileNames().convention(false);
8081

8182
// setup the transform and the tasks for all projects in the build
@@ -166,11 +167,32 @@ private void configureTransform(Project project, ExtraJavaModuleInfoPluginExtens
166167
Configuration runtimeClasspath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName());
167168
Configuration compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName());
168169
Configuration annotationProcessor = project.getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName());
170+
Configuration runtimeElements = project.getConfigurations().findByName(sourceSet.getRuntimeElementsConfigurationName());
171+
Configuration apiElements = project.getConfigurations().findByName(sourceSet.getApiElementsConfigurationName());
169172

170173
// compile, runtime and annotation processor classpaths express that they only accept modules by requesting the javaModule=true attribute
171174
runtimeClasspath.getAttributes().attribute(javaModule, true);
172175
compileClasspath.getAttributes().attribute(javaModule, true);
173176
annotationProcessor.getAttributes().attribute(javaModule, true);
177+
178+
// outgoing variants may express that they already provide a modular Jar and can hence skip the transform altogether
179+
if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) >= 0) {
180+
if (runtimeElements != null) {
181+
runtimeElements.getOutgoing().getAttributes().attributeProvider(javaModule, extension.getSkipLocalJars());
182+
}
183+
if (apiElements != null) {
184+
apiElements.getOutgoing().getAttributes().attributeProvider(javaModule, extension.getSkipLocalJars());
185+
}
186+
} else {
187+
project.afterEvaluate(p -> {
188+
if (runtimeElements != null) {
189+
runtimeElements.getOutgoing().getAttributes().attribute(javaModule, extension.getSkipLocalJars().get());
190+
}
191+
if (apiElements != null) {
192+
apiElements.getOutgoing().getAttributes().attribute(javaModule, extension.getSkipLocalJars().get());
193+
}
194+
});
195+
}
174196
});
175197

176198
// Jars may be transformed (or merged into) Module Jars

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public abstract class ExtraJavaModuleInfoPluginExtension {
4040
public abstract MapProperty<String, ModuleSpec> getModuleSpecs();
4141
public abstract Property<Boolean> getFailOnMissingModuleInfo();
4242
public abstract Property<Boolean> getFailOnAutomaticModules();
43+
public abstract Property<Boolean> getSkipLocalJars();
4344
public abstract Property<Boolean> getDeriveAutomaticModuleNamesFromFileNames();
4445
public abstract Property<String> getVersionsProvidingConfiguration();
4546

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ public void transform(TransformOutputs outputs) {
133133
return;
134134
}
135135

136+
checkInputExists(originalJar);
137+
136138
// We return the original Jar without further analysis, if there is
137139
// (1) no spec (2) no auto-module check (3) no missing module-info check (4) no auto-name derivation
138140
if (moduleSpec == null
@@ -184,6 +186,19 @@ public void transform(TransformOutputs outputs) {
184186
}
185187
}
186188

189+
private void checkInputExists(File jar) {
190+
if (!jar.isFile()) {
191+
// If the jar does not exist, it is most likely a locally-built Jar that does not yet exist because the
192+
// transform was triggered at configuration time. See:
193+
// - https://github.com/gradle/gradle/issues/26155
194+
// - https://github.com/gradlex-org/extra-java-module-info/issues/15
195+
// - https://github.com/gradlex-org/extra-java-module-info/issues/78
196+
throw new RuntimeException("File does not exist: " + jar
197+
+ "\n This is likely because a tool or another plugin performs early dependency resolution."
198+
+ "\n You can prevent this error by setting 'skipLocalJars = true'.");
199+
}
200+
}
201+
187202
@Nullable
188203
private ModuleSpec findModuleSpec(File originalJar) {
189204
Map<String, ModuleSpec> moduleSpecs = getParameters().getModuleSpecs().get();
@@ -219,15 +234,6 @@ private boolean willBeMerged(File originalJar, Collection<ModuleSpec> modules) {
219234
}
220235

221236
private boolean isModule(File jar) {
222-
if (!jar.isFile()) {
223-
// If the jar does not exist, we assume that the file, which is produced later is a local artifact and a module.
224-
// For local files this behavior is ok, because this transform is targeting published artifacts.
225-
// Still, this can cause an error: https://github.com/gradle/gradle/issues/27372
226-
// See also:
227-
// - https://github.com/gradlex-org/extra-java-module-info/issues/15
228-
// - https://github.com/gradlex-org/extra-java-module-info/issues/78
229-
return true;
230-
}
231237
try (JarInputStream inputStream = new JarInputStream(Files.newInputStream(jar.toPath()))) {
232238
boolean isMultiReleaseJar = containsMultiReleaseJarEntry(inputStream);
233239
ZipEntry next = inputStream.getNextEntry();
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.gradlex.javamodule.moduleinfo.test
2+
3+
import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
4+
import spock.lang.Specification
5+
6+
class LocalJarTransformFunctionalTest extends Specification {
7+
8+
@Delegate
9+
GradleBuild build = new GradleBuild()
10+
11+
def setup() {
12+
settingsFile << '''
13+
rootProject.name = "test-project"
14+
include(":sub")
15+
'''
16+
file("sub/build.gradle.kts") << '''
17+
plugins {
18+
id("java-library")
19+
id("org.gradlex.extra-java-module-info")
20+
id("maven-publish")
21+
}
22+
'''
23+
buildFile << '''
24+
plugins {
25+
id("java-library")
26+
id("org.gradlex.extra-java-module-info")
27+
}
28+
dependencies {
29+
implementation(project(":sub"))
30+
}
31+
'''
32+
}
33+
34+
def "a locally produced Jar is transformed"() {
35+
given:
36+
buildFile << '''
37+
extraJavaModuleInfo {
38+
// transform local Jar to assert that it has gone through transformation
39+
module("sub.jar", "org.example.sub")
40+
}
41+
tasks.register("printCP") {
42+
inputs.files(configurations.runtimeClasspath)
43+
doLast { println(inputs.files.files.map { it.name }) }
44+
}
45+
'''
46+
47+
when:
48+
def result = task('printCP', '-q')
49+
50+
then:
51+
result.output.trim() == "[sub-module.jar]"
52+
}
53+
54+
def "transformation of locally produced Jars can be deactivates"() {
55+
given:
56+
buildFile << '''
57+
tasks.register("printCP") {
58+
inputs.files(configurations.runtimeClasspath)
59+
doLast { println(inputs.files.files.map { it.name }) }
60+
}
61+
'''
62+
file("sub/build.gradle.kts") << """
63+
extraJavaModuleInfo { skipLocalJars.set(true) }
64+
"""
65+
66+
when:
67+
def result = task('printCP', '-q')
68+
69+
then:
70+
result.output.trim() == "[sub.jar]"
71+
}
72+
73+
74+
def "deactivation of locally produced Jars does not cause additional attributes to be published"() {
75+
given:
76+
def repo = file("repo")
77+
file("sub/build.gradle.kts") << """
78+
group = "foo"
79+
version = "1"
80+
publishing {
81+
publications.create<MavenPublication>("lib").from(components["java"])
82+
repositories.maven("${repo.absolutePath}")
83+
}
84+
extraJavaModuleInfo { skipLocalJars.set(true) }
85+
"""
86+
87+
when:
88+
task('publish')
89+
90+
then:
91+
!new File(repo, 'foo/sub/1/sub-1.module').text.contains('"javaModule":')
92+
}
93+
94+
def "if transform fails due to missing local Jar, an actionable error message is given"() {
95+
given:
96+
buildFile << '''
97+
tasks.register("printCP") {
98+
inputs.files(configurations.runtimeClasspath.get().files) // provoke error: access at configuration time
99+
doLast { println(inputs.files.files.map { it.name }) }
100+
}
101+
'''
102+
103+
when:
104+
def result = failTask('printCP', '-q')
105+
106+
then:
107+
result.output.contains("File does not exist:")
108+
result.output.contains("You can prevent this error by setting 'skipLocalJars = true'")
109+
}
110+
111+
def "resolving early does not fail if transformation is disabled for locally produced Jars"() {
112+
given:
113+
buildFile << '''
114+
tasks.register("printCP") {
115+
inputs.files(configurations.runtimeClasspath.get().files) // provoke resolution at configuration time
116+
doLast { println(inputs.files.files.map { it.name }) }
117+
}
118+
'''
119+
file("sub/build.gradle.kts") << '''
120+
extraJavaModuleInfo { skipLocalJars.set(true) }
121+
'''
122+
123+
when:
124+
def result = task('printCP', '-q')
125+
126+
then:
127+
result.output.trim() == "[sub.jar]"
128+
}
129+
}

src/test/groovy/org/gradlex/javamodule/moduleinfo/test/fixture/GradleBuild.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class GradleBuild {
5050
runner(taskNames).build()
5151
}
5252

53+
BuildResult failTask(String... taskNames) {
54+
runner(taskNames).buildAndFail()
55+
}
56+
5357
GradleRunner runner(String... args) {
5458
if (buildFile.exists()) {
5559
buildFile << '\nrepositories.mavenCentral()'

0 commit comments

Comments
 (0)