diff --git a/README.md b/README.md index 1e44fc6..62f6d9b 100644 --- a/README.md +++ b/README.md @@ -163,12 +163,15 @@ The plugin also allows you to ignore some unwanted services from being automatic ```kotlin extraJavaModuleInfo { module("groovy-all-2.4.15.jar", "groovy.all", "2.4.15") { - requiresTransitive("java.scripting") - requires("java.logging") - requires("java.desktop") - ignoreServiceProvider("org.codehaus.groovy.runtime.ExtensionModule") - ignoreServiceProvider("org.codehaus.groovy.plugins.Runners") - ignoreServiceProvider("org.codehaus.groovy.source.Extensions") + requiresTransitive("java.scripting") + requires("java.logging") + requires("java.desktop") + ignoreServiceProvider("org.codehaus.groovy.runtime.ExtensionModule") + ignoreServiceProvider("org.codehaus.groovy.plugins.Runners") + ignoreServiceProvider("org.codehaus.groovy.source.Extensions") + + // or add a service provider registration if it is missing + // provides("org.hibernate.dialect.Dialect", "org.sqlite.hibernate.dialect.SQLiteDialect") } } ``` diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java index 744d169..4e236d6 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java @@ -463,6 +463,7 @@ private byte[] addModuleInfo( explicitlyHandledPackage.addAll(moduleInfo.exports.keySet()); explicitlyHandledPackage.addAll(autoExportedPackages); explicitlyHandledPackage.addAll(removedPackages); + Map> additionalProviders = moduleInfo.getProviders(); ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9, classWriter) { @Override @@ -619,6 +620,15 @@ private void addModuleInfoEntries( .toArray(String[]::new)); } } + for (Map.Entry> provider : moduleInfo.getProviders().entrySet()) { + if (!provider.getValue().isEmpty()) { + moduleVisitor.visitProvide( + packageToPath(provider.getKey()), + provider.getValue().stream() + .map(ExtraJavaModuleInfoTransform::packageToPath) + .toArray(String[]::new)); + } + } } private void mergeJars( diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java index 73ca225..9b70d5d 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java @@ -29,6 +29,7 @@ public class ModuleInfo extends ModuleSpec { final Set requiresStaticTransitive = new LinkedHashSet<>(); final Map> ignoreServiceProviders = new LinkedHashMap<>(); final Set uses = new LinkedHashSet<>(); + final Map> providers = new LinkedHashMap<>(); final Set exportAllPackagesExceptions = new LinkedHashSet<>(); boolean exportAllPackages; @@ -95,6 +96,22 @@ public void requiresStaticTransitive(String requiresStaticTransitive) { addOrThrow(this.requiresStaticTransitive, requiresStaticTransitive); } + /** + * @param provides corresponds to the directive in a 'module-info.java' file + * @param with specifys the package(s) containing provided service classes + */ + public void provides(String provides, String... with) { + addOrThrow(this.providers, provides, with); + } + + /** + * Getter for the providers map (needed for the transformation logic) + * @return + */ + public Map> getProviders() { + return providers; + } + /** * @param uses corresponds to the directive in a 'module-info.java' file */ diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/ProvidesWithFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/ProvidesWithFunctionalTest.groovy new file mode 100644 index 0000000..276e579 --- /dev/null +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/ProvidesWithFunctionalTest.groovy @@ -0,0 +1,67 @@ +package org.gradlex.javamodule.moduleinfo.test + +import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild +import spock.lang.Specification + +class ProvidesWithFunctionalTest extends Specification { + + @Delegate + GradleBuild build = new GradleBuild() + + def setup() { + settingsFile << 'rootProject.name = "test-project"' + buildFile << """ + plugins { + id("application") + id("org.gradlex.extra-java-module-info") + } + application { + mainModule.set("org.gradle.sample.app") + mainClass.set("org.gradle.sample.app.Main") + } + """ + } + + def "can define missing service providers"() { + setup: + file("src/main/java/module-info.java") << """ + module org.gradle.sample.app { + requires org.hibernate.sqlite; + requires org.hibernate.orm.core; + uses org.hibernate.dialect.Dialect; + } + """ + file("src/main/java/org/gradle/sample/app/Main.java") << """ + package org.gradle.sample.app; + + public class Main { + public static void main(String[] args) { + java.util.ServiceLoader.load(org.hibernate.dialect.Dialect.class).forEach( + d -> System.out.println(d.getClass())); + } + } + """ + buildFile << """ + dependencies { + implementation("com.github.gwenn:sqlite-dialect:0.2.0") + } + extraJavaModuleInfo { + module("com.github.gwenn:sqlite-dialect", "org.hibernate.sqlite") { + requires("java.sql") + requires("org.hibernate.orm.core") + provides("org.hibernate.dialect.Dialect", "org.sqlite.hibernate.dialect.SQLiteDialect") + } + module("org.hibernate:hibernate-core", "org.hibernate.orm.core") { + requires("java.persistence") + requires("java.sql") + exportAllPackages() + } + module("antlr:antlr", "org.antlr") { } + } + """ + + expect: + def result = run() + result.output.contains("INFO: HHH000400: Using dialect: org.sqlite.hibernate.dialect.SQLiteDialect") + } +}