Skip to content

Commit 604b746

Browse files
committed
Allow opening individual packages in module descriptors
Resolves #30
1 parent ba0e347 commit 604b746

File tree

9 files changed

+157
-19
lines changed

9 files changed

+157
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* [New] Minimal Gradle version is now 6.8 for integration with recently added features like the Dependency Version Catalog
55
* [New] [#46](https://github.com/gradlex-org/extra-java-module-info/issues/46) - Validation coordinates and module names
66
* [New] [#41](https://github.com/gradlex-org/extra-java-module-info/issues/41) - Support version catalog accessors to express dependency coordinates (Thanks [Giuseppe Barbieri](https://github.com/elect86) for suggesting!)
7+
* [New] [#30](https://github.com/gradlex-org/extra-java-module-info/issues/30) - Add 'opens(...)' to module DSL (Thanks [Wexalian](https://github.com/Wexalian) for suggesting!)
78
* [Fixed] [#47](https://github.com/gradlex-org/extra-java-module-info/issues/47) - requireAllDefinedDependencies() gives error when dependency only appears on runtime path (Thanks [Sola](https://github.com/unlimitedsola) for reporting!)
89
* [Fixed] [#45](https://github.com/gradlex-org/extra-java-module-info/issues/45) - Sub-folders in 'META-INF/services' are not ignored (Thanks [Jonas Beyer](https://github.com/j-beyer) for reporting!)
910
* [Fixed] [#44](https://github.com/gradlex-org/extra-java-module-info/issues/44) - Name resolution for jars with '-' character failing if Jars are taken from local .m2 repository (Thanks [Aidan Do](https://github.com/REslim30) for reporting!)

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ extraJavaModuleInfo {
6767
exports("org.apache.commons.beanutils")
6868
// exportAllPackages()
6969
70-
requires("org.apache.commons.logging")
70+
requiresTransitive("org.apache.commons.logging")
7171
requires("java.sql")
7272
requires("java.desktop")
7373
74+
// closeModule()
75+
// opens("org.apache.commons.beanutils")
76+
7477
// requiresTransitive(...)
7578
// requiresStatic(...)
7679

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import org.gradle.api.Action;
2020
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
21+
import org.gradle.api.model.ObjectFactory;
2122
import org.gradle.api.provider.MapProperty;
2223
import org.gradle.api.provider.Property;
2324
import org.gradle.api.provider.Provider;
2425

2526
import javax.annotation.Nullable;
27+
import javax.inject.Inject;
2628

2729
/**
2830
* A data class to collect all the module information we want to add.
@@ -32,8 +34,11 @@
3234
@SuppressWarnings("unused")
3335
public abstract class ExtraJavaModuleInfoPluginExtension {
3436

35-
abstract public MapProperty<String, ModuleSpec> getModuleSpecs();
36-
abstract public Property<Boolean> getFailOnMissingModuleInfo();
37+
@Inject
38+
protected abstract ObjectFactory getObjects();
39+
40+
public abstract MapProperty<String, ModuleSpec> getModuleSpecs();
41+
public abstract Property<Boolean> getFailOnMissingModuleInfo();
3742

3843
/**
3944
* Add full module information for a given Jar file.
@@ -108,7 +113,7 @@ public void module(Provider<MinimalExternalModuleDependency> alias, String modul
108113
* @param conf configure exported packages, dependencies and Jar merging, see {@link ModuleInfo}
109114
*/
110115
public void module(String identifier, String moduleName, @Nullable String moduleVersion, @Nullable Action<? super ModuleInfo> conf) {
111-
ModuleInfo moduleInfo = new ModuleInfo(identifier, moduleName, moduleVersion);
116+
ModuleInfo moduleInfo = new ModuleInfo(identifier, moduleName, moduleVersion, getObjects());
112117
if (conf != null) {
113118
conf.execute(moduleInfo);
114119
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
*/
7474
@CacheableRule
7575
@NonNullApi
76-
abstract public class ExtraJavaModuleInfoTransform implements TransformAction<ExtraJavaModuleInfoTransform.Parameter> {
76+
public abstract class ExtraJavaModuleInfoTransform implements TransformAction<ExtraJavaModuleInfoTransform.Parameter> {
7777

7878
private static final Pattern MODULE_INFO_CLASS_MRJAR_PATH = Pattern.compile("META-INF/versions/\\d+/module-info.class");
7979
private static final Pattern JAR_SIGNATURE_PATH = Pattern.compile("^META-INF/[^/]+\\.(SF|RSA|DSA|sf|rsa|dsa)$");
@@ -288,8 +288,9 @@ private List<String> extractImplementations(byte[] content) {
288288
private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, List<String>> providers, @Nullable String version, Set<String> autoExportedPackages) {
289289
ClassWriter classWriter = new ClassWriter(0);
290290
classWriter.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null);
291+
int openModule = moduleInfo.openModule ? Opcodes.ACC_OPEN : 0;
291292
String moduleVersion = moduleInfo.getModuleVersion() == null ? version : moduleInfo.getModuleVersion();
292-
ModuleVisitor moduleVisitor = classWriter.visitModule(moduleInfo.getModuleName(), Opcodes.ACC_OPEN, moduleVersion);
293+
ModuleVisitor moduleVisitor = classWriter.visitModule(moduleInfo.getModuleName(), openModule, moduleVersion);
293294

294295
for (String packageName : autoExportedPackages) {
295296
moduleVisitor.visitExport(packageName, 0);
@@ -298,6 +299,10 @@ private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, List<String>> pr
298299
moduleVisitor.visitExport(packageName.replace('.', '/'), 0);
299300
}
300301

302+
for (String packageName : moduleInfo.opens) {
303+
moduleVisitor.visitOpen(packageName.replace('.', '/'), 0);
304+
}
305+
301306
moduleVisitor.visitRequire("java.base", 0, null);
302307

303308
if (moduleInfo.requireAllDefinedDependencies) {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2022 the GradleX team.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.gradlex.javamodule.moduleinfo;
218

319
class IdValidator {

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.gradlex.javamodule.moduleinfo;
1818

19+
import org.gradle.api.model.ObjectFactory;
20+
1921
import java.util.LinkedHashSet;
2022
import java.util.Set;
2123

@@ -27,7 +29,9 @@ public class ModuleInfo extends ModuleSpec {
2729

2830
private final String moduleVersion;
2931

32+
boolean openModule = true;
3033
final Set<String> exports = new LinkedHashSet<>();
34+
final Set<String> opens = new LinkedHashSet<>();
3135
final Set<String> requires = new LinkedHashSet<>();
3236
final Set<String> requiresTransitive = new LinkedHashSet<>();
3337
final Set<String> requiresStatic = new LinkedHashSet<>();
@@ -37,11 +41,28 @@ public class ModuleInfo extends ModuleSpec {
3741
boolean exportAllPackages;
3842
boolean requireAllDefinedDependencies;
3943

40-
ModuleInfo(String identifier, String moduleName, String moduleVersion) {
44+
ModuleInfo(String identifier, String moduleName, String moduleVersion, ObjectFactory objectFactory) {
4145
super(identifier, moduleName);
4246
this.moduleVersion = moduleVersion;
4347
}
4448

49+
/**
50+
* Should this be a 'module' instead of an 'open module'?
51+
*/
52+
public void closeModule() {
53+
openModule = false;
54+
}
55+
56+
/**
57+
* Calling this method at least once automatically makes this a "closed" module: 'module' instead of 'open module'.
58+
*
59+
* @param opens corresponds to the directive in a 'module-info.java' file
60+
*/
61+
public void opens(String opens) {
62+
closeModule();
63+
addOrThrow(this.opens, opens);
64+
}
65+
4566
/**
4667
* @param exports corresponds to the directive in a 'module-info.java' file
4768
*/

src/test/groovy/org/gradlex/javamodule/moduleinfo/test/IdValidationFunctionalTest.groovy

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package org.gradlex.javamodule.moduleinfo.test
22

33
import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
4-
import org.gradlex.javamodule.moduleinfo.test.fixture.LegacyLibraries
54
import spock.lang.Specification
65

76
class IdValidationFunctionalTest extends Specification {
87

98
@Delegate
109
GradleBuild build = new GradleBuild()
1110

12-
LegacyLibraries libs = new LegacyLibraries(false)
13-
1411
def setup() {
1512
settingsFile << 'rootProject.name = "test-project"'
1613
buildFile << '''
@@ -39,10 +36,6 @@ class IdValidationFunctionalTest extends Specification {
3936
def "fails for wrong file name"() {
4037
given:
4138
buildFile << """
42-
dependencies {
43-
implementation(${libs.commonsCollections})
44-
}
45-
4639
extraJavaModuleInfo {
4740
module("/dummy/some/my.jar", "apache.commons.logging")
4841
}
@@ -55,11 +48,7 @@ class IdValidationFunctionalTest extends Specification {
5548

5649
def "fails for wrong module name"() {
5750
given:
58-
buildFile << """
59-
dependencies {
60-
implementation(${libs.commonsCollections})
61-
}
62-
51+
buildFile << """
6352
extraJavaModuleInfo {
6453
module("commons-logging:commons-logging", "apache.commons:logging")
6554
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.gradlex.javamodule.moduleinfo.test
2+
3+
import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
4+
import org.gradlex.javamodule.moduleinfo.test.fixture.LegacyLibraries
5+
import spock.lang.Specification
6+
7+
class OpensFunctionalTest extends Specification {
8+
9+
@Delegate
10+
GradleBuild build = new GradleBuild()
11+
12+
LegacyLibraries libs = new LegacyLibraries(false)
13+
14+
def setup() {
15+
settingsFile << 'rootProject.name = "test-project"'
16+
buildFile << '''
17+
plugins {
18+
id("application")
19+
id("org.gradlex.extra-java-module-info")
20+
}
21+
application {
22+
mainModule.set("org.gradle.sample.app")
23+
mainClass.set("org.gradle.sample.app.Main")
24+
}
25+
'''
26+
file("src/main/java/module-info.java") << """
27+
module org.gradle.sample.app {
28+
requires apache.commons.collections;
29+
}
30+
"""
31+
file("src/main/java/org/gradle/sample/app/Main.java") << """
32+
package org.gradle.sample.app;
33+
34+
public class Main {
35+
public static void main(String[] args) throws ClassNotFoundException {
36+
Class.forName("org.apache.commons.collections.buffer.BlockingBuffer").getDeclaredMethods()[0].setAccessible(true);
37+
Class.forName("org.apache.commons.collections.bag.HashBag").getDeclaredMethods()[0].setAccessible(true);
38+
}
39+
}
40+
"""
41+
}
42+
43+
def "a module is open by default"() {
44+
given:
45+
buildFile << """
46+
dependencies {
47+
implementation("commons-collections:commons-collections:3.2.2")
48+
}
49+
extraJavaModuleInfo {
50+
module(${libs.commonsCollections}, "apache.commons.collections")
51+
}
52+
"""
53+
54+
expect:
55+
run()
56+
}
57+
58+
def "a module can be closed"() {
59+
given:
60+
buildFile << """
61+
dependencies {
62+
implementation("commons-collections:commons-collections:3.2.2")
63+
}
64+
extraJavaModuleInfo {
65+
module(${libs.commonsCollections}, "apache.commons.collections") {
66+
closeModule()
67+
}
68+
}
69+
"""
70+
71+
expect:
72+
def out = failRun()
73+
out.output.contains('module apache.commons.collections does not "exports org.apache.commons.collections.buffer" to module org.gradle.sample.app')
74+
}
75+
76+
def "a module is closed once it has an open package"() {
77+
given:
78+
79+
buildFile << """
80+
dependencies {
81+
implementation("commons-collections:commons-collections:3.2.2")
82+
}
83+
extraJavaModuleInfo {
84+
module(${libs.commonsCollections}, "apache.commons.collections") {
85+
opens("org.apache.commons.collections.buffer")
86+
}
87+
}
88+
"""
89+
90+
expect:
91+
def out = failRun()
92+
out.output.contains('module apache.commons.collections does not "opens org.apache.commons.collections.bag" to module org.gradle.sample.app')
93+
}
94+
}

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
@@ -34,6 +34,10 @@ class GradleBuild {
3434
runner('run').build()
3535
}
3636

37+
BuildResult failRun() {
38+
runner('run').buildAndFail()
39+
}
40+
3741
BuildResult test() {
3842
runner('test').build()
3943
}

0 commit comments

Comments
 (0)