├── .gitignore ├── gradle ├── gradle-daemon-jvm.properties └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── samples ├── module-info-dsl │ ├── app │ │ ├── src │ │ │ ├── test │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── app │ │ │ │ │ └── AppTest.java │ │ │ ├── testFunctional │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── app │ │ │ │ │ └── test │ │ │ │ │ └── AppFunctionalTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── App.java │ │ └── build.gradle.kts │ ├── lib │ │ ├── src │ │ │ ├── test │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── lib │ │ │ │ │ └── LibTest.java │ │ │ ├── testFunctional │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── lib │ │ │ │ │ └── test │ │ │ │ │ └── LibFunctionalTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── lib │ │ │ │ └── Lib.java │ │ └── build.gradle.kts │ ├── build.gradle.kts │ ├── build.sample.conf │ ├── gradle │ │ └── plugins │ │ │ ├── src │ │ │ └── main │ │ │ │ └── kotlin │ │ │ │ ├── org.example.root.gradle.kts │ │ │ │ ├── org.example.java-module-app.gradle.kts │ │ │ │ ├── org.example.java-module.gradle.kts │ │ │ │ ├── org.example.java-module-versions.gradle.kts │ │ │ │ └── org.example.java-base.gradle.kts │ │ │ ├── settings.gradle.kts │ │ │ └── build.gradle.kts │ ├── settings.gradle.kts │ ├── versions │ │ └── build.gradle.kts │ └── build.out ├── kotlin │ ├── build.out │ ├── build.sample.conf │ ├── lib │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── test │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── lib │ │ │ │ └── test │ │ │ │ └── MyLibTest.java │ │ │ └── main │ │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── lib │ │ │ │ └── Util.kt │ │ │ └── java │ │ │ └── module-info.java │ ├── app │ │ ├── src │ │ │ ├── test │ │ │ │ ├── java │ │ │ │ │ └── module-info.java │ │ │ │ └── kotlin │ │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── test │ │ │ │ │ └── AppTest.kt │ │ │ └── main │ │ │ │ ├── java │ │ │ │ └── module-info.java │ │ │ │ └── kotlin │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── App.kt │ │ └── build.gradle.kts │ ├── settings.gradle.kts │ ├── build-logic │ │ ├── build.gradle.kts │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── org.my.gradle.kotlin-java-module.gradle.kts │ └── gradle │ │ └── libs.versions.toml ├── module-info-dsl-no-platform │ ├── app │ │ ├── src │ │ │ ├── test │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── app │ │ │ │ │ └── AppTest.java │ │ │ ├── testFunctional │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── app │ │ │ │ │ └── test │ │ │ │ │ └── AppFunctionalTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── App.java │ │ └── build.gradle.kts │ ├── lib │ │ ├── src │ │ │ ├── test │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── lib │ │ │ │ │ └── LibTest.java │ │ │ ├── testFunctional │ │ │ │ ├── resources │ │ │ │ │ └── data.txt │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── lib │ │ │ │ │ └── test │ │ │ │ │ └── LibFunctionalTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── lib │ │ │ │ └── Lib.java │ │ └── build.gradle.kts │ ├── build.gradle.kts │ ├── build.sample.conf │ ├── gradle │ │ └── plugins │ │ │ ├── src │ │ │ └── main │ │ │ │ └── kotlin │ │ │ │ ├── org.example.root.gradle.kts │ │ │ │ ├── org.example.java-module.gradle.kts │ │ │ │ ├── org.example.java-module-app.gradle.kts │ │ │ │ └── org.example.java-base.gradle.kts │ │ │ ├── settings.gradle.kts │ │ │ └── build.gradle.kts │ ├── settings.gradle.kts │ └── build.out ├── configuration-cache │ ├── gradle.properties │ ├── lib │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── module-info.java │ │ │ └── test │ │ │ └── java │ │ │ ├── module-info.java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── test │ │ │ └── MyLibTest.java │ ├── build.sample.conf │ ├── app │ │ ├── src │ │ │ ├── test │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── test │ │ │ │ │ └── AppTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── App.java │ │ └── build.gradle.kts │ ├── build-logic │ │ ├── build.gradle.kts │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── org.my.gradle.java-module.gradle.kts │ ├── settings.gradle.kts │ ├── gradle │ │ └── libs.versions.toml │ └── build.out ├── versions-in-catalog │ ├── build.sample.conf │ ├── lib │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── test │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── lib │ │ │ │ └── test │ │ │ │ └── MyLibTest.java │ │ │ └── main │ │ │ └── java │ │ │ └── module-info.java │ ├── app │ │ ├── src │ │ │ ├── test │ │ │ │ └── java │ │ │ │ │ ├── module-info.java │ │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── test │ │ │ │ │ └── AppTest.java │ │ │ └── main │ │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── App.java │ │ └── build.gradle.kts │ ├── build-logic │ │ ├── build.gradle.kts │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── org.my.gradle.java-module.gradle.kts │ ├── settings.gradle.kts │ ├── gradle │ │ └── libs.versions.toml │ └── build.out └── versions-in-platform │ ├── build.sample.conf │ ├── lib │ ├── build.gradle.kts │ └── src │ │ ├── test │ │ └── java │ │ │ ├── module-info.java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── test │ │ │ └── MyLibTest.java │ │ └── main │ │ └── java │ │ └── module-info.java │ ├── app │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── test │ │ │ │ └── AppTest.java │ │ └── main │ │ │ └── java │ │ │ ├── module-info.java │ │ │ └── org │ │ │ └── my │ │ │ └── app │ │ │ └── App.java │ └── build.gradle.kts │ ├── build-logic │ ├── build.gradle.kts │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ ├── org.my.gradle.platform.gradle.kts │ │ │ └── org.my.gradle.java-module.gradle.kts │ └── settings.gradle.kts │ ├── settings.gradle.kts │ ├── platform │ └── build.gradle.kts │ └── build.out ├── gradle.properties ├── renovate.json ├── settings.gradle.kts ├── src ├── main │ ├── java │ │ └── org │ │ │ └── gradlex │ │ │ └── javamodule │ │ │ └── dependencies │ │ │ ├── package-info.java │ │ │ ├── dsl │ │ │ ├── package-info.java │ │ │ ├── AllDirectives.java │ │ │ ├── GradleOnlyDirectives.java │ │ │ └── ModuleVersions.java │ │ │ ├── tasks │ │ │ ├── package-info.java │ │ │ ├── SyntheticModuleInfoFoldersGenerate.java │ │ │ ├── ModuleDependencyReport.java │ │ │ ├── ModuleDirectivesOrderingCheck.java │ │ │ ├── CatalogGenerate.java │ │ │ └── ModuleInfoGenerate.java │ │ │ ├── initialization │ │ │ ├── package-info.java │ │ │ ├── JavaModuleDependenciesSettingsPlugin.java │ │ │ ├── RootPluginsExtension.java │ │ │ ├── Module.java │ │ │ └── Directory.java │ │ │ ├── internal │ │ │ ├── dsl │ │ │ │ ├── package-info.java │ │ │ │ ├── GradleOnlyDirectivesInternal.java │ │ │ │ └── AllDirectivesInternal.java │ │ │ ├── utils │ │ │ │ ├── package-info.java │ │ │ │ ├── TaskConfigurationUtil.java │ │ │ │ ├── ModuleInfoClassCreator.java │ │ │ │ ├── ValueSourceModuleInfo.java │ │ │ │ ├── ModuleNamingUtil.java │ │ │ │ ├── ValueModuleDirectoryListing.java │ │ │ │ ├── DependencyDeclarationsUtil.java │ │ │ │ ├── ModuleJar.java │ │ │ │ ├── ModuleInfoCache.java │ │ │ │ └── ModuleInfo.java │ │ │ ├── bridges │ │ │ │ ├── package-info.java │ │ │ │ ├── ExtraJavaModuleInfoBridge.java │ │ │ │ └── DependencyAnalysisBridge.java │ │ │ └── diagnostics │ │ │ │ ├── package-info.java │ │ │ │ ├── RenderableJavaModuleResult.java │ │ │ │ ├── StyledNodeRenderer.java │ │ │ │ ├── AsciiModuleDependencyReportRenderer.java │ │ │ │ └── RenderableModuleDependencyResult.java │ │ │ ├── SharedMappings.java │ │ │ ├── LocalModule.java │ │ │ └── JDKInfo.java │ └── resources │ │ └── org │ │ └── gradlex │ │ └── javamodule │ │ └── dependencies │ │ └── modules.properties ├── testSamples │ └── java │ │ └── org │ │ └── gradlex │ │ └── javamodule │ │ └── dependencies │ │ └── test │ │ └── samples │ │ ├── SamplesTest.java │ │ └── PluginBuildLocationSampleModifier.java └── test │ └── java │ └── org │ └── gradlex │ └── javamodule │ └── dependencies │ └── test │ ├── WarningsTest.java │ ├── fixture │ ├── Io.java │ ├── Directory.java │ ├── WritableFile.java │ └── GradleBuild.java │ ├── groupmapping │ └── GroupMappingTest.java │ ├── tasks │ ├── BasicFunctionalityTest.java │ └── OrderingCheckTest.java │ ├── extension │ └── ExtensionTest.java │ ├── localmodules │ └── LocalModuleMappingsTest.java │ ├── variants │ └── NonMainVariantsTest.java │ ├── initialization │ ├── SettingsPluginVersionManagementTest.java │ └── SettingsPluginIncludeTest.java │ ├── configcache │ └── ConfigurationCacheTest.java │ ├── ModuleInfoParseTest.java │ └── CustomizationTest.java ├── .github └── workflows │ ├── dependency-submission.yml │ ├── ci-build.yml │ └── release.yml ├── gradlew.bat └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | -------------------------------------------------------------------------------- /gradle/gradle-daemon-jvm.properties: -------------------------------------------------------------------------------- 1 | toolchainVersion=17 -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/test/resources/data.txt: -------------------------------------------------------------------------------- 1 | ABC -------------------------------------------------------------------------------- /samples/kotlin/build.out: -------------------------------------------------------------------------------- 1 | > Task :app:run 2 | org.my.app 3 | -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/test/resources/data.txt: -------------------------------------------------------------------------------- 1 | 2*21 -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/testFunctional/resources/data.txt: -------------------------------------------------------------------------------- 1 | DEF -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/testFunctional/resources/data.txt: -------------------------------------------------------------------------------- 1 | 42 -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/test/resources/data.txt: -------------------------------------------------------------------------------- 1 | ABC -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/test/resources/data.txt: -------------------------------------------------------------------------------- 1 | 2*21 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/testFunctional/resources/data.txt: -------------------------------------------------------------------------------- 1 | DEF -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/testFunctional/resources/data.txt: -------------------------------------------------------------------------------- 1 | 42 -------------------------------------------------------------------------------- /samples/configuration-cache/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.configuration-cache=true 2 | -------------------------------------------------------------------------------- /samples/kotlin/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/module-info-dsl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.root") 3 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.root") 3 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/versions-in-catalog/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/versions-in-platform/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/kotlin/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.kotlin-java-module") 3 | id("java-library") 4 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradlex-org/java-module-dependencies/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/configuration-cache/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | -------------------------------------------------------------------------------- /samples/versions-in-platform/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | -------------------------------------------------------------------------------- /samples/configuration-cache/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | expected-output-file: build.out 3 | allow-disordered-output: true 4 | -------------------------------------------------------------------------------- /samples/configuration-cache/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /samples/kotlin/app/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.test { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.gradlex.internal-build-conventions") version "0.8" } 2 | 3 | rootProject.name = "java-module-dependencies" 4 | -------------------------------------------------------------------------------- /samples/kotlin/lib/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.test { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/src/main/kotlin/org.example.root.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.autonomousapps.dependency-analysis") 3 | } 4 | -------------------------------------------------------------------------------- /samples/configuration-cache/app/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.test { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/src/main/kotlin/org.example.root.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.autonomousapps.dependency-analysis") 3 | } 4 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/app/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.test { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/app/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.test { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/configuration-cache/lib/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.test { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/lib/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.test { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/versions-in-platform/lib/src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.test { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/src/main/kotlin/org.example.java-module-app.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-base") 3 | id("application") 4 | } 5 | -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/src/main/kotlin/org.example.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-base") 3 | id("java-library") 4 | } 5 | 6 | -------------------------------------------------------------------------------- /samples/kotlin/lib/src/test/java/org/my/lib/test/MyLibTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.test; 2 | 3 | class MyLibTest { 4 | 5 | @org.junit.jupiter.api.Test 6 | void test() {} 7 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-module") 3 | } 4 | 5 | testModuleInfo { 6 | requires("org.junit.jupiter.api") 7 | } 8 | -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.example.app { 2 | requires org.example.lib; 3 | requires org.slf4j; 4 | 5 | exports org.example.app; 6 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/src/main/kotlin/org.example.java-module-versions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-platform") 3 | id("org.gradlex.java-module-versions") 4 | } -------------------------------------------------------------------------------- /samples/configuration-cache/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-dependencies:1.11") 7 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/src/main/kotlin/org.example.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-base") 3 | id("java-library") 4 | } 5 | 6 | -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/testFunctional/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.example.app.test.functional { 2 | requires org.example.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-dependencies:1.11") 7 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-dependencies:1.11") 7 | } -------------------------------------------------------------------------------- /samples/configuration-cache/lib/src/test/java/org/my/lib/test/MyLibTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.test; 2 | 3 | class MyLibTest { 4 | 5 | @org.junit.jupiter.api.Test 6 | void test() {} 7 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/testFunctional/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.example.lib.test.functional { 2 | requires org.example.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.example.app { 2 | requires org.example.lib; 3 | requires org.slf4j; 4 | 5 | exports org.example.app; 6 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/testFunctional/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.example.app.test.functional { 2 | requires org.example.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/kotlin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/testFunctional/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.example.lib.test.functional { 2 | requires org.example.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/configuration-cache/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.slf4j; 3 | requires org.my.lib; 4 | requires static org.apache.xmlbeans; 5 | 6 | exports org.my.app; 7 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.slf4j; 3 | requires org.my.lib; 4 | requires static org.apache.xmlbeans; 5 | 6 | exports org.my.app; 7 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("gradle/plugins") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | -------------------------------------------------------------------------------- /samples/configuration-cache/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | 10 | -------------------------------------------------------------------------------- /samples/kotlin/lib/src/main/kotlin/org/my/lib/Util.kt: -------------------------------------------------------------------------------- 1 | package org.my.lib 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | 5 | class Util { 6 | 7 | fun doWork() { 8 | ObjectMapper() 9 | } 10 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/src/main/kotlin/org.example.java-module-app.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-base") 3 | id("application") 4 | id("org.gradlex.java-module-versions") 5 | } 6 | -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-module") 3 | } 4 | 5 | testModuleInfo { 6 | requires("org.junit.jupiter.api") 7 | } 8 | 9 | // println(configurations.compileClasspath.get().files) -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.example.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | requires java.logging; // JDK module 5 | 6 | exports org.example.lib; 7 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | // JDK modules 5 | requires java.logging; 6 | requires jdk.charsets; 7 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | // JDK modules 5 | requires java.logging; 6 | requires jdk.charsets; 7 | } -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/dsl/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.dsl; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.tasks; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /samples/kotlin/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.slf4j; 3 | requires org.my.lib; 4 | requires static org.apache.xmlbeans; 5 | 6 | requires kotlin.stdlib; 7 | 8 | exports org.my.app; 9 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.example.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | requires java.logging; // JDK module 5 | 6 | exports org.example.lib; 7 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | org_apache_xmlbeans = "5.0.1" 3 | com_fasterxml_jackson_databind = "2.12.5" 4 | org_slf4j = "1.7.28" 5 | org_slf4j_simple = "1.7.28" 6 | 7 | org-junit-jupiter-api = "5.7.2" 8 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/lib/src/test/java/org/my/lib/test/MyLibTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class MyLibTest { 6 | 7 | @Test 8 | void test() { 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/lib/src/test/java/org/my/lib/test/MyLibTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class MyLibTest { 6 | 7 | @Test 8 | void test() { 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /samples/kotlin/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-dependencies:1.11") 7 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0") 8 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("gradle/plugins") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | include("versions") 10 | -------------------------------------------------------------------------------- /samples/versions-in-platform/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | include("platform") 10 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/initialization/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.initialization; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/dsl/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.internal.dsl; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.internal.utils; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /samples/kotlin/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | requires kotlin.stdlib; 5 | 6 | // JDK modules 7 | requires java.logging; 8 | requires jdk.charsets; 9 | } -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/bridges/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.internal.bridges; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/main/java/org/example/lib/Lib.java: -------------------------------------------------------------------------------- 1 | package org.example.lib; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | public class Lib { 6 | 7 | public void process(ObjectMapper mapper) { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/diagnostics/package-info.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | @NullMarked 3 | package org.gradlex.javamodule.dependencies.internal.diagnostics; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | -------------------------------------------------------------------------------- /samples/configuration-cache/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | org_apache_xmlbeans = "5.0.1" 3 | com_fasterxml_jackson_databind = "2.12.5" 4 | org_slf4j = "1.7.32" 5 | org_slf4j_simple = "1.7.32" 6 | 7 | org_junit_jupiter_api = "5.7.2" 8 | org_junit_jupiter_engine = "5.7.2" 9 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/main/java/org/example/lib/Lib.java: -------------------------------------------------------------------------------- 1 | package org.example.lib; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | public class Lib { 6 | 7 | public void process(ObjectMapper mapper) { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/versions-in-platform/build-logic/src/main/kotlin/org.my.gradle.platform.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-platform") 3 | id("org.gradlex.java-module-dependencies") 4 | } 5 | 6 | group = "org.my" 7 | 8 | javaPlatform.allowDependencies() // Use existing Platforms/BOMs 9 | -------------------------------------------------------------------------------- /samples/versions-in-platform/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.my.lib; 3 | requires org.slf4j; 4 | 5 | requires static org.apache.xmlbeans; 6 | 7 | requires /*runtime*/ org.slf4j.simple; 8 | 9 | exports org.my.app; 10 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.App") 9 | mainModule.set("org.my.app") 10 | } 11 | -------------------------------------------------------------------------------- /samples/kotlin/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin_stdlib = "1.6.10" 3 | 4 | com_fasterxml_jackson_databind = "2.12.5" 5 | org_apache_xmlbeans = "5.0.1" 6 | org_slf4j = "1.7.28" 7 | org_slf4j_simple = "1.7.28" 8 | 9 | org_junit_jupiter_api = "5.7.2" 10 | org_junit_jupiter_engine = "5.7.2" 11 | -------------------------------------------------------------------------------- /samples/kotlin/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/module-info-dsl/versions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-module-versions") 3 | } 4 | 5 | moduleInfo { 6 | version("org.slf4j", "2.0.7") 7 | version("org.slf4j.simple", "2.0.7") 8 | version("com.fasterxml.jackson.databind", "2.14.0") 9 | version("org.junit.jupiter.api", "5.9.3") 10 | } 11 | -------------------------------------------------------------------------------- /samples/configuration-cache/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/versions-in-platform/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/kotlin/app/src/test/kotlin/org/my/app/test/AppTest.kt: -------------------------------------------------------------------------------- 1 | package org.my.app.test 2 | 3 | import org.junit.jupiter.api.Test 4 | import org.my.app.App 5 | 6 | import org.junit.jupiter.api.Assertions.assertTrue 7 | 8 | class AppTest { 9 | 10 | @Test 11 | fun appDoesNotExplode() { 12 | assertTrue(App.doWork()); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parentFile.parent) 7 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parentFile.parent) 7 | -------------------------------------------------------------------------------- /samples/configuration-cache/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.App") 9 | mainModule.set("org.my.app") 10 | } 11 | 12 | dependencies { 13 | runtimeOnly(javaModuleDependencies.gav("org.slf4j.simple")) 14 | } 15 | -------------------------------------------------------------------------------- /samples/kotlin/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.kotlin-java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.AppKt") 9 | mainModule.set("org.my.app") 10 | } 11 | 12 | dependencies { 13 | runtimeOnly(javaModuleDependencies.gav("org.slf4j.simple")) 14 | } 15 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.App") 9 | mainModule.set("org.my.app") 10 | } 11 | 12 | dependencies { 13 | runtimeOnly(javaModuleDependencies.gav("org.slf4j.simple")) 14 | } 15 | -------------------------------------------------------------------------------- /samples/configuration-cache/app/src/test/java/org/my/app/test/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.app.App; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class AppTest { 9 | 10 | @Test 11 | public void appDoesNotExplode() { 12 | assertTrue(App.doWork()); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/app/src/test/java/org/my/app/test/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.app.App; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class AppTest { 9 | 10 | @Test 11 | public void appDoesNotExplode() { 12 | assertTrue(App.doWork()); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/app/src/test/java/org/my/app/test/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.app.App; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class AppTest { 9 | 10 | @Test 11 | public void appDoesNotExplode() { 12 | assertTrue(App.doWork()); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("com.autonomousapps:dependency-analysis-gradle-plugin:3.5.1") 7 | implementation("org.gradlex:java-module-dependencies:1.11") 8 | implementation("org.gradlex:java-module-testing:1.8") 9 | implementation("org.gradlex:jvm-dependency-conflict-resolution:2.5") 10 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("com.autonomousapps:dependency-analysis-gradle-plugin:3.5.1") 7 | implementation("org.gradlex:java-module-dependencies:1.11") 8 | implementation("org.gradlex:java-module-testing:1.8") 9 | implementation("org.gradlex:jvm-dependency-conflict-resolution:2.5") 10 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-module-app") 3 | } 4 | 5 | application { 6 | applicationDefaultJvmArgs = listOf("-ea") 7 | mainClass.set("org.example.app.App") 8 | mainModule.set("org.example.app") 9 | } 10 | 11 | mainModuleInfo { 12 | runtimeOnly("org.slf4j.simple") 13 | } 14 | 15 | testModuleInfo { 16 | requires("org.junit.jupiter.api") 17 | } 18 | -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/main/java/org/example/app/App.java: -------------------------------------------------------------------------------- 1 | package org.example.app; 2 | 3 | import org.example.lib.Lib; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class App { 7 | 8 | public static void main(String[] args) { 9 | LoggerFactory.getLogger(App.class).info("App running..."); 10 | doWork(); 11 | } 12 | 13 | public static void doWork() { 14 | new Lib(); 15 | } 16 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/main/java/org/example/app/App.java: -------------------------------------------------------------------------------- 1 | package org.example.app; 2 | 3 | import org.example.lib.Lib; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class App { 7 | 8 | public static void main(String[] args) { 9 | LoggerFactory.getLogger(App.class).info("App running..."); 10 | doWork(); 11 | } 12 | 13 | public static void doWork() { 14 | new Lib(); 15 | } 16 | } -------------------------------------------------------------------------------- /samples/versions-in-platform/platform/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.platform") 3 | } 4 | 5 | 6 | dependencies { 7 | api(platform("com.fasterxml.jackson:jackson-bom:2.20.1")) 8 | api(platform("org.junit:junit-bom:6.0.1")) 9 | } 10 | 11 | dependencies.constraints { 12 | javaModuleDependencies { 13 | api(gav("org.apache.xmlbeans", "5.0.1")) 14 | api(gav("org.slf4j", "1.7.28")) 15 | api(gav("org.slf4j.simple", "1.7.28")) 16 | } 17 | } -------------------------------------------------------------------------------- /.github/workflows/dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Submission 2 | 3 | on: [ push ] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | dependency-submission: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: gradle/actions/dependency-submission@v5 14 | with: 15 | build-scan-publish: true 16 | build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" 17 | build-scan-terms-of-use-agree: "yes" 18 | -------------------------------------------------------------------------------- /samples/configuration-cache/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.gradlex.java-module-dependencies") 4 | } 5 | 6 | group = "org.my" 7 | 8 | tasks.test { 9 | useJUnitPlatform() 10 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 11 | } 12 | 13 | dependencies { 14 | javaModuleDependencies { 15 | testRuntimeOnly(gav("org.junit.jupiter.engine")) 16 | testRuntimeOnly(gav("org.junit.platform.launcher")) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/testSamples/java/org/gradlex/javamodule/dependencies/test/samples/SamplesTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.samples; 3 | 4 | import org.gradle.exemplar.test.runner.SampleModifiers; 5 | import org.gradle.exemplar.test.runner.SamplesRoot; 6 | import org.gradle.exemplar.test.runner.SamplesRunner; 7 | import org.junit.runner.RunWith; 8 | 9 | @RunWith(SamplesRunner.class) 10 | @SamplesRoot("samples") 11 | @SampleModifiers(PluginBuildLocationSampleModifier.class) 12 | public class SamplesTest {} 13 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.gradlex.java-module-dependencies") 4 | } 5 | 6 | group = "org.my" 7 | 8 | javaModuleDependencies.versionsFromConsistentResolution(":app") 9 | 10 | tasks.test { 11 | useJUnitPlatform() 12 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 13 | } 14 | 15 | dependencies { 16 | javaModuleDependencies { 17 | testRuntimeOnly(gav("org.junit.jupiter.engine")) 18 | testRuntimeOnly(gav("org.junit.platform.launcher")) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: Build Plugin 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | gradle-build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: git clone 14 | uses: actions/checkout@v6 15 | - name: Set up JDK 16 | uses: actions/setup-java@v5 17 | with: 18 | distribution: temurin 19 | java-version: 17 20 | - name: Set up Gradle 21 | uses: gradle/actions/setup-gradle@v5 22 | - name: Set up TestLens 23 | uses: testlens-app/setup-testlens@v1.5.1 24 | - run: "./gradlew build" -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.java-module-app") 3 | } 4 | 5 | application { 6 | applicationDefaultJvmArgs = listOf("-ea") 7 | mainClass.set("org.example.app.App") 8 | mainModule.set("org.example.app") 9 | } 10 | 11 | mainModuleInfo { 12 | runtimeOnly("org.slf4j.simple") 13 | } 14 | 15 | testModuleInfo { 16 | requires("org.junit.jupiter.api") 17 | } 18 | 19 | moduleInfo { 20 | version("org.slf4j", "2.0.7") 21 | version("org.slf4j.simple", "2.0.7") 22 | version("com.fasterxml.jackson.databind", "2.14.0") 23 | version("org.junit.jupiter.api", "5.9.3") 24 | } 25 | -------------------------------------------------------------------------------- /samples/versions-in-platform/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.gradlex.java-module-dependencies") 4 | } 5 | 6 | group = "org.my" 7 | 8 | javaModuleDependencies.versionsFromConsistentResolution(":app") 9 | 10 | tasks.test { 11 | useJUnitPlatform() 12 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 13 | } 14 | 15 | dependencies { 16 | implementation(platform(project(":platform"))) 17 | javaModuleDependencies { 18 | testRuntimeOnly(ga("org.junit.jupiter.engine")) 19 | testRuntimeOnly(ga("org.junit.platform.launcher")) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/versions-in-catalog/build.out: -------------------------------------------------------------------------------- 1 | > Task :lib:compileJava 2 | > Task :lib:processResources NO-SOURCE 3 | > Task :lib:classes 4 | > Task :lib:jar 5 | > Task :app:compileJava 6 | > Task :app:processResources NO-SOURCE 7 | > Task :app:classes 8 | > Task :app:jar 9 | > Task :app:startScripts 10 | > Task :app:distTar 11 | > Task :app:distZip 12 | > Task :app:assemble 13 | > Task :app:compileTestJava 14 | > Task :app:processTestResources NO-SOURCE 15 | > Task :app:testClasses 16 | > Task :app:test 17 | > Task :app:check 18 | > Task :app:build 19 | > Task :lib:assemble 20 | > Task :lib:compileTestJava 21 | > Task :lib:processTestResources NO-SOURCE 22 | > Task :lib:testClasses 23 | > Task :lib:test 24 | > Task :lib:check 25 | > Task :lib:build 26 | 27 | > Task :app:run 28 | org.my.app -------------------------------------------------------------------------------- /samples/module-info-dsl/gradle/plugins/src/main/kotlin/org.example.java-base.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.gradlex.java-module-dependencies") 4 | id("org.gradlex.java-module-testing") 5 | id("org.gradlex.jvm-dependency-conflict-resolution") 6 | } 7 | 8 | group = "org.example" 9 | 10 | java.toolchain.languageVersion.set(JavaLanguageVersion.of(11)) 11 | testing.suites.register("testFunctional") 12 | tasks.check { dependsOn(tasks.named("testFunctional")) } 13 | 14 | jvmDependencyConflicts { 15 | consistentResolution { 16 | platform(":versions") 17 | providesVersions(":app") 18 | } 19 | } 20 | 21 | tasks.withType().configureEach { 22 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 23 | } 24 | -------------------------------------------------------------------------------- /samples/kotlin/app/src/main/kotlin/org/my/app/App.kt: -------------------------------------------------------------------------------- 1 | package org.my.app 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import org.apache.xmlbeans.impl.tool.XMLBean 5 | import org.slf4j.spi.LoggerFactoryBinder 6 | 7 | fun main() { 8 | App.doWork() 9 | } 10 | 11 | object App { 12 | fun doWork(): Boolean { 13 | val om = ObjectMapper() 14 | if (!om.canSerialize(LoggerFactoryBinder::class.java)) { 15 | throw RuntimeException("Boom!") 16 | } 17 | println(App::class.java.module.name) 18 | try { 19 | XMLBean() 20 | throw RuntimeException("Boom!") 21 | } catch (e: NoClassDefFoundError) { 22 | // This is expected at runtime! 23 | } 24 | return true 25 | } 26 | } -------------------------------------------------------------------------------- /samples/configuration-cache/build.out: -------------------------------------------------------------------------------- 1 | > Task :build-logic:jar 2 | > Task :lib:compileJava 3 | > Task :lib:processResources NO-SOURCE 4 | > Task :lib:classes 5 | > Task :lib:jar 6 | > Task :app:compileJava 7 | > Task :app:processResources NO-SOURCE 8 | > Task :app:classes 9 | > Task :app:jar 10 | > Task :app:startScripts 11 | > Task :app:distTar 12 | > Task :app:distZip 13 | > Task :app:assemble 14 | > Task :app:compileTestJava 15 | > Task :app:processTestResources NO-SOURCE 16 | > Task :app:testClasses 17 | > Task :app:test 18 | > Task :app:check 19 | > Task :app:build 20 | > Task :lib:assemble 21 | > Task :lib:compileTestJava 22 | > Task :lib:processTestResources NO-SOURCE 23 | > Task :lib:testClasses 24 | > Task :lib:test 25 | > Task :lib:check 26 | > Task :lib:build 27 | 28 | > Task :app:run 29 | org.my.app -------------------------------------------------------------------------------- /samples/versions-in-platform/app/src/main/java/org/my/app/App.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.xmlbeans.impl.tool.XMLBean; 5 | import org.slf4j.spi.LoggerFactoryBinder; 6 | 7 | public class App { 8 | public static void main(String[] args) { 9 | doWork(); 10 | } 11 | 12 | public static boolean doWork() { 13 | ObjectMapper om = new ObjectMapper(); 14 | LoggerFactoryBinder lfb; 15 | System.out.println(App.class.getModule().getName()); 16 | 17 | try { 18 | new XMLBean(); 19 | throw new RuntimeException("Boom!"); 20 | } catch (NoClassDefFoundError e) { 21 | // This is expected at runtime! 22 | } 23 | return true; 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/TaskConfigurationUtil.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import org.gradle.api.Task; 5 | import org.gradle.api.tasks.SourceSet; 6 | import org.gradle.api.tasks.compile.JavaCompile; 7 | import org.gradle.api.tasks.javadoc.Javadoc; 8 | 9 | public abstract class TaskConfigurationUtil { 10 | 11 | public static boolean isJavaCompileTask(Task task, SourceSet sourceSet) { 12 | return task instanceof JavaCompile && task.getName().equals(sourceSet.getCompileJavaTaskName()); 13 | } 14 | 15 | public static boolean isJavadocTask(Task task, SourceSet sourceSet) { 16 | return task instanceof Javadoc && task.getName().equals(sourceSet.getJavadocTaskName()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/test/java/org/example/lib/LibTest.java: -------------------------------------------------------------------------------- 1 | package org.example.lib; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | import static java.nio.charset.StandardCharsets.UTF_8; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | class LibTest { 13 | 14 | @Test 15 | void testLib() throws IOException { 16 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 17 | assertEquals("org.example.lib", LibTest.class.getModule().getName()); 18 | try (InputStream is = LibTest.class.getResourceAsStream("/data.txt")) { 19 | assertNotNull(is); 20 | assertEquals("2*21", new String(is.readAllBytes(), UTF_8)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/test/java/org/example/lib/LibTest.java: -------------------------------------------------------------------------------- 1 | package org.example.lib; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | import static java.nio.charset.StandardCharsets.UTF_8; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | class LibTest { 13 | 14 | @Test 15 | void testLib() throws IOException { 16 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 17 | assertEquals("org.example.lib", LibTest.class.getModule().getName()); 18 | try (InputStream is = LibTest.class.getResourceAsStream("/data.txt")) { 19 | assertNotNull(is); 20 | assertEquals("2*21", new String(is.readAllBytes(), UTF_8)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/versions-in-platform/build.out: -------------------------------------------------------------------------------- 1 | > Task :lib:compileJava 2 | > Task :lib:processResources NO-SOURCE 3 | > Task :lib:classes 4 | > Task :lib:jar 5 | > Task :app:compileJava 6 | > Task :app:processResources NO-SOURCE 7 | > Task :app:classes 8 | > Task :app:jar 9 | > Task :app:startScripts 10 | > Task :app:distTar 11 | > Task :app:distZip 12 | > Task :app:assemble 13 | > Task :app:compileTestJava 14 | > Task :app:processTestResources NO-SOURCE 15 | > Task :app:testClasses 16 | > Task :app:test 17 | > Task :app:check 18 | > Task :app:build 19 | > Task :lib:assemble 20 | > Task :lib:compileTestJava 21 | > Task :lib:processTestResources NO-SOURCE 22 | > Task :lib:testClasses 23 | > Task :lib:test 24 | > Task :lib:check 25 | > Task :lib:build 26 | > Task :platform:assemble UP-TO-DATE 27 | > Task :platform:check UP-TO-DATE 28 | > Task :platform:build UP-TO-DATE 29 | 30 | > Task :app:run 31 | org.my.app -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/WarningsTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class WarningsTest { 10 | 11 | GradleBuild build = new GradleBuild(); 12 | 13 | @Test 14 | void prints_warning_for_missing_mapping() { 15 | build.appModuleInfoFile.writeText( 16 | """ 17 | module org.my.app { 18 | requires commons.math3; 19 | }"""); 20 | 21 | var result = build.fail(); 22 | 23 | assertThat(result.getOutput()) 24 | .contains("[WARN] [Java Module Dependencies] commons.math3=group:artifact missing in"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/configuration-cache/app/src/main/java/org/my/app/App.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.xmlbeans.impl.tool.XMLBean; 5 | import org.slf4j.spi.LoggerFactoryBinder; 6 | 7 | public class App { 8 | public static void main(String[] args) { 9 | doWork(); 10 | } 11 | 12 | public static boolean doWork() { 13 | ObjectMapper om = new ObjectMapper(); 14 | if (!om.canSerialize(LoggerFactoryBinder.class)) { 15 | throw new RuntimeException("Boom!"); 16 | } 17 | System.out.println(App.class.getModule().getName()); 18 | 19 | try { 20 | new XMLBean(); 21 | throw new RuntimeException("Boom!"); 22 | } catch (NoClassDefFoundError e) { 23 | // This is expected at runtime! 24 | } 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /samples/versions-in-catalog/app/src/main/java/org/my/app/App.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.xmlbeans.impl.tool.XMLBean; 5 | import org.slf4j.spi.LoggerFactoryBinder; 6 | 7 | public class App { 8 | public static void main(String[] args) { 9 | doWork(); 10 | } 11 | 12 | public static boolean doWork() { 13 | ObjectMapper om = new ObjectMapper(); 14 | if (!om.canSerialize(LoggerFactoryBinder.class)) { 15 | throw new RuntimeException("Boom!"); 16 | } 17 | System.out.println(App.class.getModule().getName()); 18 | 19 | try { 20 | new XMLBean(); 21 | throw new RuntimeException("Boom!"); 22 | } catch (NoClassDefFoundError e) { 23 | // This is expected at runtime! 24 | } 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | release-build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: git clone 11 | uses: actions/checkout@v6 12 | - name: Set up JDK 13 | uses: actions/setup-java@v5 14 | with: 15 | distribution: temurin 16 | java-version: 17 17 | - name: Set up Gradle 18 | uses: gradle/actions/setup-gradle@v5 19 | - name: Set up TestLens 20 | uses: testlens-app/setup-testlens@v1.5.1 21 | - run: "./gradlew :publishPlugin --no-configuration-cache" 22 | env: 23 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 24 | SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 25 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 26 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/gradle/plugins/src/main/kotlin/org.example.java-base.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.gradlex.java-module-dependencies") 4 | id("org.gradlex.java-module-testing") 5 | id("org.gradlex.jvm-dependency-conflict-resolution") 6 | } 7 | 8 | group = "org.example" 9 | 10 | java.toolchain.languageVersion.set(JavaLanguageVersion.of(11)) 11 | testing.suites.register("testFunctional") 12 | tasks.check { dependsOn(tasks.named("testFunctional")) } 13 | 14 | jvmDependencyConflicts { 15 | consistentResolution { 16 | providesVersions(":app") 17 | } 18 | } 19 | dependencies { 20 | implementation(platform(project(":app")) as ModuleDependency) { 21 | capabilities { requireCapability("${project.group}:app-platform") } 22 | } 23 | } 24 | 25 | tasks.withType().configureEach { 26 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 27 | } 28 | -------------------------------------------------------------------------------- /samples/module-info-dsl/lib/src/testFunctional/java/org/example/lib/test/LibFunctionalTest.java: -------------------------------------------------------------------------------- 1 | package org.example.lib.test; 2 | 3 | import org.example.lib.Lib; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | class LibFunctionalTest { 14 | 15 | @Test 16 | void testLibFunctional() throws IOException { 17 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 18 | assertEquals("org.example.lib.test.functional", LibFunctionalTest.class.getModule().getName()); 19 | try (InputStream is = LibFunctionalTest.class.getResourceAsStream("/data.txt")) { 20 | assertNotNull(is); 21 | assertEquals("42", new String(is.readAllBytes(), UTF_8)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/lib/src/testFunctional/java/org/example/lib/test/LibFunctionalTest.java: -------------------------------------------------------------------------------- 1 | package org.example.lib.test; 2 | 3 | import org.example.lib.Lib; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | class LibFunctionalTest { 14 | 15 | @Test 16 | void testLibFunctional() throws IOException { 17 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 18 | assertEquals("org.example.lib.test.functional", LibFunctionalTest.class.getModule().getName()); 19 | try (InputStream is = LibFunctionalTest.class.getResourceAsStream("/data.txt")) { 20 | assertNotNull(is); 21 | assertEquals("42", new String(is.readAllBytes(), UTF_8)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/test/java/org/example/app/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.example.app; 2 | 3 | import org.example.lib.Lib; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class AppTest { 14 | 15 | @Test 16 | public void testApp() throws IOException { 17 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 18 | assertEquals("org.example.app", App.class.getModule().getName()); 19 | assertEquals("org.example.app", AppTest.class.getModule().getName()); 20 | try (InputStream is = AppTest.class.getResourceAsStream("/data.txt")) { 21 | assertNotNull(is); 22 | assertEquals("ABC", new String(is.readAllBytes(), UTF_8)); 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /samples/module-info-dsl/app/src/testFunctional/java/org/example/app/test/AppFunctionalTest.java: -------------------------------------------------------------------------------- 1 | package org.example.app.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.example.app.App; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class AppFunctionalTest { 14 | 15 | @Test 16 | void testAppFunctional() throws IOException { 17 | assertEquals("org.example.app", App.class.getModule().getName()); 18 | assertEquals("org.example.app.test.functional", AppFunctionalTest.class.getModule().getName()); 19 | try (InputStream is = AppFunctionalTest.class.getResourceAsStream("/data.txt")) { 20 | assertNotNull(is); 21 | assertEquals("DEF", new String(is.readAllBytes(), UTF_8)); 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/dsl/GradleOnlyDirectivesInternal.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.dsl; 3 | 4 | import java.util.List; 5 | import org.gradle.api.tasks.SourceSet; 6 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 7 | import org.gradlex.javamodule.dependencies.dsl.GradleOnlyDirectives; 8 | 9 | public abstract class GradleOnlyDirectivesInternal extends GradleOnlyDirectives { 10 | 11 | public GradleOnlyDirectivesInternal( 12 | SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) { 13 | super(sourceSet, mainSourceSet, javaModuleDependencies); 14 | } 15 | 16 | public List getCompileClasspathModules() { 17 | return compileClasspathModules; 18 | } 19 | 20 | public List getRuntimeClasspathModules() { 21 | return runtimeClasspathModules; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/test/java/org/example/app/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.example.app; 2 | 3 | import org.example.lib.Lib; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class AppTest { 14 | 15 | @Test 16 | public void testApp() throws IOException { 17 | assertEquals("org.example.lib", Lib.class.getModule().getName()); 18 | assertEquals("org.example.app", App.class.getModule().getName()); 19 | assertEquals("org.example.app", AppTest.class.getModule().getName()); 20 | try (InputStream is = AppTest.class.getResourceAsStream("/data.txt")) { 21 | assertNotNull(is); 22 | assertEquals("ABC", new String(is.readAllBytes(), UTF_8)); 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/app/src/testFunctional/java/org/example/app/test/AppFunctionalTest.java: -------------------------------------------------------------------------------- 1 | package org.example.app.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.example.app.App; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class AppFunctionalTest { 14 | 15 | @Test 16 | void testAppFunctional() throws IOException { 17 | assertEquals("org.example.app", App.class.getModule().getName()); 18 | assertEquals("org.example.app.test.functional", AppFunctionalTest.class.getModule().getName()); 19 | try (InputStream is = AppFunctionalTest.class.getResourceAsStream("/data.txt")) { 20 | assertNotNull(is); 21 | assertEquals("DEF", new String(is.readAllBytes(), UTF_8)); 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/fixture/Io.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.fixture; 3 | 4 | import java.io.IOException; 5 | import java.io.UncheckedIOException; 6 | 7 | class Io { 8 | 9 | private Io() {} 10 | 11 | static T unchecked(IoSupplier supplier) { 12 | try { 13 | return supplier.get(); 14 | } catch (IOException e) { 15 | throw new UncheckedIOException(e); 16 | } 17 | } 18 | 19 | static void unchecked(T t, IoConsumer consumer) { 20 | try { 21 | consumer.accept(t); 22 | } catch (IOException e) { 23 | throw new UncheckedIOException(e); 24 | } 25 | } 26 | 27 | @FunctionalInterface 28 | interface IoSupplier { 29 | T get() throws IOException; 30 | } 31 | 32 | @FunctionalInterface 33 | interface IoConsumer { 34 | void accept(T t) throws IOException; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.initialization; 3 | 4 | import org.gradle.api.GradleException; 5 | import org.gradle.api.Plugin; 6 | import org.gradle.api.initialization.Settings; 7 | import org.gradle.util.GradleVersion; 8 | 9 | public abstract class JavaModuleDependenciesSettingsPlugin implements Plugin { 10 | 11 | @Override 12 | public void apply(Settings settings) { 13 | if (GradleVersion.current().compareTo(GradleVersion.version("8.8")) < 0) { 14 | throw new GradleException("This settings plugin requires Gradle 8.8+"); 15 | } 16 | registerExtension(settings); 17 | } 18 | 19 | private void registerExtension(Settings settings) { 20 | settings.getExtensions().create("rootPlugins", RootPluginsExtension.class, settings); 21 | settings.getExtensions().create("javaModules", JavaModulesExtension.class, settings); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/module-info-dsl-no-platform/build.out: -------------------------------------------------------------------------------- 1 | > Task :lib:compileJava 2 | > Task :lib:processResources NO-SOURCE 3 | > Task :lib:classes 4 | > Task :lib:jar 5 | > Task :app:compileJava 6 | > Task :app:processResources NO-SOURCE 7 | > Task :app:classes 8 | > Task :app:jar 9 | > Task :app:startScripts 10 | > Task :app:distTar 11 | > Task :app:distZip 12 | > Task :app:assemble 13 | > Task :app:compileTestJava 14 | > Task :app:processTestResources 15 | > Task :app:testClasses 16 | > Task :app:test 17 | > Task :app:compileTestFunctionalJava 18 | > Task :app:processTestFunctionalResources 19 | > Task :app:testFunctionalClasses 20 | > Task :app:testFunctionalJar 21 | > Task :app:testFunctional 22 | > Task :app:check 23 | > Task :app:build 24 | > Task :lib:assemble 25 | > Task :lib:compileTestJava 26 | > Task :lib:processTestResources 27 | > Task :lib:testClasses 28 | > Task :lib:test 29 | > Task :lib:compileTestFunctionalJava 30 | > Task :lib:processTestFunctionalResources 31 | > Task :lib:testFunctionalClasses 32 | > Task :lib:testFunctionalJar 33 | > Task :lib:testFunctional 34 | > Task :lib:check 35 | > Task :lib:build 36 | 37 | > Task :app:run 38 | [main] INFO org.example.app.App - App running... -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/SyntheticModuleInfoFoldersGenerate.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.tasks; 3 | 4 | import org.gradle.api.DefaultTask; 5 | import org.gradle.api.file.DirectoryProperty; 6 | import org.gradle.api.provider.ListProperty; 7 | import org.gradle.api.tasks.CacheableTask; 8 | import org.gradle.api.tasks.Input; 9 | import org.gradle.api.tasks.OutputDirectory; 10 | import org.gradle.api.tasks.TaskAction; 11 | import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfoClassCreator; 12 | 13 | @CacheableTask 14 | public abstract class SyntheticModuleInfoFoldersGenerate extends DefaultTask { 15 | 16 | @Input 17 | public abstract ListProperty getModuleNames(); 18 | 19 | @OutputDirectory 20 | public abstract DirectoryProperty getSyntheticModuleInfoFolder(); 21 | 22 | @TaskAction 23 | public void generate() { 24 | for (String moduleName : getModuleNames().get()) { 25 | ModuleInfoClassCreator.createEmpty( 26 | getSyntheticModuleInfoFolder().get().dir(moduleName).getAsFile()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/dsl/AllDirectives.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.dsl; 3 | 4 | import org.gradle.api.tasks.SourceSet; 5 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 6 | 7 | public abstract class AllDirectives extends GradleOnlyDirectives { 8 | 9 | public AllDirectives( 10 | SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) { 11 | super(sourceSet, mainSourceSet, javaModuleDependencies); 12 | } 13 | 14 | public void requires(String moduleName) { 15 | runtimeClasspathModules.add(moduleName); 16 | compileClasspathModules.add(moduleName); 17 | add(sourceSet.getImplementationConfigurationName(), moduleName); 18 | } 19 | 20 | public void requiresStatic(String moduleName) { 21 | compileClasspathModules.add(moduleName); 22 | add(sourceSet.getCompileOnlyConfigurationName(), moduleName); 23 | } 24 | 25 | public void exportsTo(String moduleName) { 26 | exportsToModules.add(moduleName); 27 | } 28 | 29 | public void opensTo(String moduleName) { 30 | opensToModules.add(moduleName); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/module-info-dsl/build.out: -------------------------------------------------------------------------------- 1 | > Task :lib:compileJava 2 | > Task :lib:processResources NO-SOURCE 3 | > Task :lib:classes 4 | > Task :lib:jar 5 | > Task :app:compileJava 6 | > Task :app:processResources NO-SOURCE 7 | > Task :app:classes 8 | > Task :app:jar 9 | > Task :app:startScripts 10 | > Task :app:distTar 11 | > Task :app:distZip 12 | > Task :app:assemble 13 | > Task :app:compileTestJava 14 | > Task :app:processTestResources 15 | > Task :app:testClasses 16 | > Task :app:test 17 | > Task :app:compileTestFunctionalJava 18 | > Task :app:processTestFunctionalResources 19 | > Task :app:testFunctionalClasses 20 | > Task :app:testFunctionalJar 21 | > Task :app:testFunctional 22 | > Task :app:check 23 | > Task :app:build 24 | > Task :lib:assemble 25 | > Task :lib:compileTestJava 26 | > Task :lib:processTestResources 27 | > Task :lib:testClasses 28 | > Task :lib:test 29 | > Task :lib:compileTestFunctionalJava 30 | > Task :lib:processTestFunctionalResources 31 | > Task :lib:testFunctionalClasses 32 | > Task :lib:testFunctionalJar 33 | > Task :lib:testFunctional 34 | > Task :lib:check 35 | > Task :lib:build 36 | > Task :versions:assemble UP-TO-DATE 37 | > Task :versions:check UP-TO-DATE 38 | > Task :versions:build UP-TO-DATE 39 | 40 | > Task :app:run 41 | [main] INFO org.example.app.App - App running... 42 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/dsl/AllDirectivesInternal.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.dsl; 3 | 4 | import java.util.List; 5 | import org.gradle.api.tasks.SourceSet; 6 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 7 | import org.gradlex.javamodule.dependencies.dsl.AllDirectives; 8 | 9 | /** 10 | * Note: These methods are used by the 'java-module-testing' plugin to access information 11 | * defined in the Module Info DSL. 12 | */ 13 | public abstract class AllDirectivesInternal extends AllDirectives { 14 | 15 | public AllDirectivesInternal( 16 | SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) { 17 | super(sourceSet, mainSourceSet, javaModuleDependencies); 18 | } 19 | 20 | public List getCompileClasspathModules() { 21 | return compileClasspathModules; 22 | } 23 | 24 | public List getRuntimeClasspathModules() { 25 | return runtimeClasspathModules; 26 | } 27 | 28 | public List getExportsToModules() { 29 | return exportsToModules; 30 | } 31 | 32 | public List getOpensToModules() { 33 | return opensToModules; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/testSamples/java/org/gradlex/javamodule/dependencies/test/samples/PluginBuildLocationSampleModifier.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.samples; 3 | 4 | import java.io.File; 5 | import java.util.Arrays; 6 | import org.gradle.exemplar.model.Command; 7 | import org.gradle.exemplar.model.Sample; 8 | import org.gradle.exemplar.test.runner.SampleModifier; 9 | 10 | public class PluginBuildLocationSampleModifier implements SampleModifier { 11 | @Override 12 | public Sample modify(Sample sampleIn) { 13 | Command cmd = sampleIn.getCommands().remove(0); 14 | File pluginProjectDir = new File("."); 15 | sampleIn.getCommands() 16 | .add(new Command( 17 | new File(pluginProjectDir, "gradlew").getAbsolutePath(), 18 | cmd.getExecutionSubdirectory(), 19 | Arrays.asList("build", "run", "-PpluginLocation=" + pluginProjectDir.getAbsolutePath()), 20 | cmd.getFlags(), 21 | cmd.getExpectedOutput(), 22 | cmd.isExpectFailure(), 23 | true, 24 | cmd.isAllowDisorderedOutput(), 25 | cmd.getUserInputs())); 26 | return sampleIn; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoClassCreator.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import static org.objectweb.asm.Opcodes.ACC_MANDATED; 5 | import static org.objectweb.asm.Opcodes.ACC_MODULE; 6 | import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import org.objectweb.asm.ClassWriter; 12 | import org.objectweb.asm.ModuleVisitor; 13 | 14 | public abstract class ModuleInfoClassCreator { 15 | 16 | public static void createEmpty(File targetFolder) { 17 | //noinspection ResultOfMethodCallIgnored 18 | targetFolder.mkdirs(); 19 | 20 | ClassWriter cw = new ClassWriter(0); 21 | cw.visit(53, ACC_MODULE, "module-info", null, null, null); 22 | ModuleVisitor moduleVisitor = cw.visitModule(targetFolder.getName(), ACC_SYNTHETIC, null); 23 | moduleVisitor.visitRequire("java.base", ACC_MANDATED, null); 24 | cw.visitEnd(); 25 | try (FileOutputStream s = new FileOutputStream(new File(targetFolder, "module-info.class"))) { 26 | s.write(cw.toByteArray()); 27 | } catch (IOException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ValueSourceModuleInfo.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.Scanner; 7 | import org.gradle.api.file.DirectoryProperty; 8 | import org.gradle.api.provider.ValueSource; 9 | import org.gradle.api.provider.ValueSourceParameters; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public abstract class ValueSourceModuleInfo implements ValueSource { 13 | 14 | interface Parameter extends ValueSourceParameters { 15 | DirectoryProperty getDir(); 16 | } 17 | 18 | @Override 19 | public @Nullable ModuleInfo obtain() { 20 | Parameter parameters = getParameters(); 21 | File file = new File(parameters.getDir().get().getAsFile(), "module-info.java"); 22 | if (file.isFile()) { 23 | try { 24 | try (Scanner scan = new Scanner(file)) { 25 | scan.useDelimiter("\\Z"); 26 | String content = scan.next(); 27 | return new ModuleInfo(content); 28 | } 29 | } catch (IOException e) { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.initialization; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javax.inject.Inject; 7 | import org.gradle.api.IsolatedAction; 8 | import org.gradle.api.Project; 9 | import org.gradle.api.initialization.Settings; 10 | 11 | public abstract class RootPluginsExtension { 12 | 13 | private final List ids = new ArrayList<>(); 14 | 15 | @Inject 16 | public RootPluginsExtension(Settings settings) { 17 | settings.getGradle().getLifecycle().beforeProject(new ApplyPluginAction(ids)); 18 | } 19 | 20 | public void id(String id) { 21 | ids.add(id); 22 | } 23 | 24 | private static class ApplyPluginAction implements IsolatedAction { 25 | 26 | private final List ids; 27 | 28 | public ApplyPluginAction(List ids) { 29 | this.ids = ids; 30 | } 31 | 32 | @Override 33 | public void execute(Project project) { 34 | if (isRoot(project)) { 35 | for (String id : ids) { 36 | project.getPlugins().apply(id); 37 | } 38 | } 39 | } 40 | 41 | private boolean isRoot(Project project) { 42 | return project == project.getRootProject(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/fixture/Directory.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.fixture; 3 | 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Comparator; 8 | import java.util.stream.Stream; 9 | 10 | public class Directory { 11 | 12 | private final Path directory; 13 | 14 | Directory(Path directory) { 15 | this.directory = directory; 16 | Io.unchecked(() -> Files.createDirectories(directory)); 17 | } 18 | 19 | public WritableFile file(String path) { 20 | Path file = directory.resolve(path); 21 | Io.unchecked(() -> Files.createDirectories(file.getParent())); 22 | return new WritableFile(file); 23 | } 24 | 25 | public Directory dir(String path) { 26 | Path dir = directory.resolve(path); 27 | return new Directory(dir); 28 | } 29 | 30 | public void delete() { 31 | try (Stream walk = Files.walk(directory)) { 32 | walk.sorted(Comparator.reverseOrder()).forEach(p -> Io.unchecked(p, Files::delete)); 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | public Path getAsPath() { 39 | return directory; 40 | } 41 | 42 | public String canonicalPath() { 43 | return Io.unchecked(() -> getAsPath().toFile().getCanonicalPath()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/fixture/WritableFile.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.fixture; 3 | 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.StandardOpenOption; 7 | 8 | public class WritableFile { 9 | 10 | private final Path file; 11 | 12 | public WritableFile(Path file) { 13 | this.file = file; 14 | } 15 | 16 | public WritableFile(Directory parent, String filePath) { 17 | this.file = Io.unchecked(() -> Files.createDirectories( 18 | parent.getAsPath().resolve(filePath).getParent())) 19 | .resolve(Path.of(filePath).getFileName()); 20 | } 21 | 22 | public WritableFile writeText(String text) { 23 | Io.unchecked(() -> Files.writeString( 24 | file, text, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)); 25 | return this; 26 | } 27 | 28 | public WritableFile appendText(String text) { 29 | Io.unchecked(() -> Files.writeString(file, text, StandardOpenOption.CREATE, StandardOpenOption.APPEND)); 30 | return this; 31 | } 32 | 33 | public WritableFile delete() { 34 | Io.unchecked(file, Files::delete); 35 | return this; 36 | } 37 | 38 | public boolean exists() { 39 | return Files.exists(file); 40 | } 41 | 42 | public Path getAsPath() { 43 | return file; 44 | } 45 | 46 | public String text() { 47 | return Io.unchecked(() -> Files.readString(file)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/bridges/ExtraJavaModuleInfoBridge.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.bridges; 3 | 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | import org.gradle.api.Project; 7 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 8 | import org.gradlex.javamodule.moduleinfo.ExtraJavaModuleInfoPluginExtension; 9 | import org.gradlex.javamodule.moduleinfo.ModuleSpec; 10 | 11 | public class ExtraJavaModuleInfoBridge { 12 | 13 | public static void autoRegisterPatchedModuleMappings( 14 | Project project, JavaModuleDependenciesExtension javaModuleDependencies) { 15 | ExtraJavaModuleInfoPluginExtension extraJavaModuleInfo = 16 | project.getExtensions().getByType(ExtraJavaModuleInfoPluginExtension.class); 17 | javaModuleDependencies 18 | .getModuleNameToGA() 19 | .putAll(extraJavaModuleInfo.getModuleSpecs().map(moduleSpecs -> moduleSpecs.entrySet().stream() 20 | .collect(Collectors.toMap( 21 | ExtraJavaModuleInfoBridge::moduleNameKey, Map.Entry::getKey, (a, b) -> b)))); 22 | } 23 | 24 | private static String moduleNameKey(Map.Entry entry) { 25 | if (!entry.getKey().contains(":")) { 26 | // Entry is not usable as mapping (e.g., because it uses file names instead of GA coordinates). 27 | // Invalidate this mapping entry by creating a key that is not a valid module name. 28 | return "__" + entry.getValue().getModuleName(); 29 | } 30 | return entry.getValue().getModuleName(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleNamingUtil.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import java.util.Arrays; 5 | import java.util.stream.Collectors; 6 | import org.gradle.api.tasks.SourceSet; 7 | import org.jspecify.annotations.Nullable; 8 | 9 | public abstract class ModuleNamingUtil { 10 | 11 | public static String sourceSetToModuleName(String projectName, String sourceSetName) { 12 | if (sourceSetName.equals(SourceSet.MAIN_SOURCE_SET_NAME)) { 13 | return toDottedCase(projectName); 14 | } 15 | return toDottedCase(projectName) + "." + toDottedCase(sourceSetName); 16 | } 17 | 18 | @Nullable 19 | public static String sourceSetToCapabilitySuffix(String sourceSetName) { 20 | if (sourceSetName.equals(SourceSet.MAIN_SOURCE_SET_NAME)) { 21 | return null; 22 | } 23 | return toKebabCase(sourceSetName); 24 | } 25 | 26 | /** 27 | * Converts 'camelCase' and 'kebab-case' to 'dotted.case'. 28 | */ 29 | private static String toDottedCase(String sourceSetName) { 30 | return Arrays.stream(sourceSetName.replace("-", ".").split("(? mappings = loadModuleNameToGAProperties(); 12 | 13 | static Map loadModuleNameToGAProperties() { 14 | Properties properties = new Properties() { 15 | @Override 16 | public synchronized @Nullable Object put(Object key, Object value) { 17 | if (get(key) != null) { 18 | throw new IllegalArgumentException(key + " already present."); 19 | } 20 | return super.put(key, value); 21 | } 22 | }; 23 | @SuppressWarnings({"unchecked", "rawtypes"}) 24 | Map propertiesAsMap = (Map) properties; 25 | 26 | try (InputStream coordinatesFile = 27 | JavaModuleDependenciesExtension.class.getResourceAsStream("unique_modules.properties")) { 28 | properties.load(coordinatesFile); 29 | } catch (IOException e) { 30 | throw new RuntimeException(e); 31 | } 32 | 33 | try (InputStream coordinatesFile = 34 | JavaModuleDependenciesExtension.class.getResourceAsStream("modules.properties")) { 35 | properties.load(coordinatesFile); 36 | } catch (IOException e) { 37 | throw new RuntimeException(e); 38 | } 39 | return propertiesAsMap; 40 | } 41 | 42 | private SharedMappings() {} 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/diagnostics/RenderableJavaModuleResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.diagnostics; 3 | 4 | import java.util.LinkedHashSet; 5 | import java.util.Set; 6 | import org.gradle.api.artifacts.result.DependencyResult; 7 | import org.gradle.api.artifacts.result.ResolvedArtifactResult; 8 | import org.gradle.api.artifacts.result.ResolvedComponentResult; 9 | import org.gradle.api.artifacts.result.ResolvedDependencyResult; 10 | import org.gradle.api.artifacts.result.UnresolvedDependencyResult; 11 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency; 12 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult; 13 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableUnresolvedDependencyResult; 14 | 15 | public class RenderableJavaModuleResult extends RenderableModuleResult { 16 | 17 | private final Set resolvedJars; 18 | 19 | public RenderableJavaModuleResult(ResolvedComponentResult module, Set resolvedJars) { 20 | super(module); 21 | this.resolvedJars = resolvedJars; 22 | } 23 | 24 | @Override 25 | public Set getChildren() { 26 | Set out = new LinkedHashSet<>(); 27 | for (DependencyResult d : module.getDependencies()) { 28 | if (d instanceof UnresolvedDependencyResult) { 29 | out.add(new RenderableUnresolvedDependencyResult((UnresolvedDependencyResult) d)); 30 | } else { 31 | out.add(new RenderableModuleDependencyResult((ResolvedDependencyResult) d, resolvedJars)); 32 | } 33 | } 34 | return out; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/groupmapping/GroupMappingTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.groupmapping; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; 6 | 7 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class GroupMappingTest { 11 | 12 | GradleBuild build = new GradleBuild(); 13 | 14 | @Test 15 | void can_map_overlapping_groups() { 16 | 17 | var lib2ModuleInfoFile = build.file("lib-b/src/main/java/module-info.java"); 18 | var lib2BuildFile = build.file("lib-b/build.gradle.kts").writeText(build.libBuildFile.text()); 19 | build.settingsFile.appendText("include(\"lib-b\")"); 20 | 21 | build.libModuleInfoFile.appendText("module com.lib { }"); 22 | build.libBuildFile.appendText("group = \"com.foo\""); 23 | lib2ModuleInfoFile.appendText("module com.example.lib.b { }"); 24 | lib2BuildFile.appendText("group = \"com.example\""); 25 | build.appModuleInfoFile.appendText( 26 | """ 27 | module org.gradlex.test.app { 28 | requires com.lib; 29 | requires com.example.lib.b; 30 | }"""); 31 | build.appBuildFile.appendText( 32 | """ 33 | javaModuleDependencies { 34 | moduleNamePrefixToGroup.put("com.", "com.foo") 35 | moduleNamePrefixToGroup.put("com.example.", "com.example") 36 | }"""); 37 | 38 | var result = build.runner(false, "assemble").build().task(":app:compileJava"); 39 | 40 | assertThat(result).isNotNull(); 41 | assertThat(result.getOutcome()).isEqualTo(SUCCESS); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/dsl/GradleOnlyDirectives.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.dsl; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javax.inject.Inject; 7 | import org.gradle.api.artifacts.dsl.DependencyHandler; 8 | import org.gradle.api.tasks.SourceSet; 9 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 10 | 11 | public abstract class GradleOnlyDirectives { 12 | 13 | protected final SourceSet sourceSet; 14 | protected final SourceSet mainSourceSet; 15 | protected final JavaModuleDependenciesExtension javaModuleDependencies; 16 | 17 | protected final List compileClasspathModules = new ArrayList<>(); 18 | protected final List runtimeClasspathModules = new ArrayList<>(); 19 | protected final List exportsToModules = new ArrayList<>(); 20 | protected final List opensToModules = new ArrayList<>(); 21 | 22 | @Inject 23 | protected abstract DependencyHandler getDependencies(); 24 | 25 | public GradleOnlyDirectives( 26 | SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) { 27 | this.sourceSet = sourceSet; 28 | this.mainSourceSet = mainSourceSet; 29 | this.javaModuleDependencies = javaModuleDependencies; 30 | } 31 | 32 | protected void add(String scope, String moduleName) { 33 | getDependencies().addProvider(scope, javaModuleDependencies.create(moduleName, mainSourceSet)); 34 | } 35 | 36 | public void runtimeOnly(String moduleName) { 37 | runtimeClasspathModules.add(moduleName); 38 | add(sourceSet.getRuntimeOnlyConfigurationName(), moduleName); 39 | } 40 | 41 | public void annotationProcessor(String moduleName) { 42 | add(sourceSet.getAnnotationProcessorConfigurationName(), moduleName); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/tasks/BasicFunctionalityTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.tasks; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class BasicFunctionalityTest { 10 | 11 | GradleBuild build = new GradleBuild(true); 12 | 13 | @Test 14 | void can_configure_all_tasks_in_a_build_without_error() { 15 | build.libModuleInfoFile.writeText("module abc.lib { }"); 16 | build.appModuleInfoFile.writeText( 17 | """ 18 | module org.gradlex.test.app { 19 | requires abc.lib; 20 | } 21 | """); 22 | 23 | var result = build.runner("tasks").build(); 24 | 25 | assertThat(result.getOutput()) 26 | .contains( 27 | """ 28 | Java modules tasks 29 | ------------------ 30 | checkModuleInfo - Check order of directives in 'module-info.java' in 'main' source set 31 | checkTestFixturesModuleInfo - Check order of directives in 'module-info.java' in 'testFixtures' source set 32 | checkTestModuleInfo - Check order of directives in 'module-info.java' in 'test' source set 33 | generateAllModuleInfoFiles - Generate 'module-info.java' files in all source sets 34 | generateBuildFileDependencies - Generate 'dependencies' block in 'build.gradle.kts' 35 | generateCatalog - Generate 'libs.versions.toml' file 36 | generateModuleInfoFile - Generate 'module-info.java' in 'main' source set 37 | generateTestFixturesModuleInfoFile - Generate 'module-info.java' in 'testFixtures' source set 38 | generateTestModuleInfoFile - Generate 'module-info.java' in 'test' source set"""); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/diagnostics/StyledNodeRenderer.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.diagnostics; 3 | 4 | import static org.gradle.internal.logging.text.StyledTextOutput.Style.Description; 5 | import static org.gradle.internal.logging.text.StyledTextOutput.Style.Failure; 6 | import static org.gradle.internal.logging.text.StyledTextOutput.Style.Info; 7 | 8 | import org.gradle.api.tasks.diagnostics.internal.graph.NodeRenderer; 9 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency; 10 | import org.gradle.internal.logging.text.StyledTextOutput; 11 | 12 | public class StyledNodeRenderer implements NodeRenderer { 13 | 14 | @Override 15 | public void renderNode(StyledTextOutput output, RenderableDependency node, boolean alreadyRendered) { 16 | String name = node.getName(); 17 | if (name.startsWith("[BOM]")) { 18 | output.withStyle(Description).text(name); 19 | } else if (name.startsWith("[AUTO]")) { 20 | output.withStyle(Failure).text(name); 21 | } else if (name.startsWith("[CLASSPATH]")) { 22 | output.withStyle(Failure).text(name); 23 | } else { 24 | int idx = name.indexOf('|'); 25 | output.text(name.substring(0, idx)).withStyle(Description).text(name.substring(idx)); 26 | } 27 | switch (node.getResolutionState()) { 28 | case FAILED: 29 | output.withStyle(Failure).text(" FAILED"); 30 | break; 31 | case RESOLVED: 32 | if (alreadyRendered && !node.getChildren().isEmpty()) { 33 | output.withStyle(Info).text(" (*)"); 34 | } 35 | break; 36 | case RESOLVED_CONSTRAINT: 37 | output.withStyle(Info).text(" (c)"); 38 | break; 39 | case UNRESOLVED: 40 | output.withStyle(Info).text(" (n)"); 41 | break; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/LocalModule.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies; 3 | 4 | import java.io.Serializable; 5 | import java.util.Objects; 6 | import org.jspecify.annotations.Nullable; 7 | 8 | public class LocalModule implements Comparable, Serializable { 9 | private final String moduleName; 10 | private final String projectPath; 11 | 12 | @Nullable 13 | private final String capability; 14 | 15 | public LocalModule(String moduleName, String projectPath, @Nullable String capability) { 16 | this.moduleName = moduleName; 17 | this.projectPath = projectPath; 18 | this.capability = capability; 19 | } 20 | 21 | /** 22 | * @return the module name as defined in the module-info.java file, e.g. 'org.example.module-a' 23 | */ 24 | public String getModuleName() { 25 | return moduleName; 26 | } 27 | 28 | /** 29 | * @return the full Gradle project path for the subproject defining the module, e.g. ':module-a' 30 | */ 31 | public String getProjectPath() { 32 | return projectPath; 33 | } 34 | 35 | /** 36 | * @return the capability to address the module in a dependency if it is not in the 'main' source set, e.g. 'org.example:module-a-test-fixtures' 37 | */ 38 | @Nullable 39 | public String getCapability() { 40 | return capability; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "[" + "moduleName='" 46 | + moduleName + '\'' + ", projectPath='" 47 | + projectPath + '\'' + ", capability='" 48 | + capability + '\'' + ']'; 49 | } 50 | 51 | @Override 52 | public int compareTo(LocalModule o) { 53 | return moduleName.compareTo(o.moduleName); 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (o == null || getClass() != o.getClass()) return false; 59 | LocalModule that = (LocalModule) o; 60 | return Objects.equals(moduleName, that.moduleName); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(moduleName); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/ModuleDependencyReport.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.tasks; 3 | 4 | import java.util.Collections; 5 | import java.util.Set; 6 | import javax.inject.Inject; 7 | import org.gradle.api.artifacts.ArtifactCollection; 8 | import org.gradle.api.artifacts.Configuration; 9 | import org.gradle.api.file.ConfigurableFileCollection; 10 | import org.gradle.api.provider.MapProperty; 11 | import org.gradle.api.provider.ProviderFactory; 12 | import org.gradle.api.tasks.Classpath; 13 | import org.gradle.api.tasks.Internal; 14 | import org.gradle.api.tasks.diagnostics.DependencyReportTask; 15 | import org.gradlex.javamodule.dependencies.internal.diagnostics.AsciiModuleDependencyReportRenderer; 16 | 17 | public abstract class ModuleDependencyReport extends DependencyReportTask { 18 | 19 | @Internal 20 | public abstract MapProperty getModuleArtifacts(); 21 | 22 | /** 23 | * Required to track all Jar files as input of the task. 24 | * Although they are only accessed through getModuleArtifacts(). 25 | */ 26 | @Classpath 27 | public abstract ConfigurableFileCollection getModulePath(); 28 | 29 | @Inject 30 | protected abstract ProviderFactory getProviders(); 31 | 32 | public ModuleDependencyReport() { 33 | setRenderer(new AsciiModuleDependencyReportRenderer(getModuleArtifacts())); 34 | } 35 | 36 | @Override 37 | public void setConfiguration(String configurationName) { 38 | super.setConfiguration(configurationName); 39 | configurationsChanged(); 40 | } 41 | 42 | @Override 43 | public void setConfigurations(Set configurations) { 44 | super.setConfigurations(configurations); 45 | configurationsChanged(); 46 | } 47 | 48 | private void configurationsChanged() { 49 | getModulePath().setFrom(); 50 | getModuleArtifacts().set(Collections.emptyMap()); 51 | for (Configuration conf : getConfigurations()) { 52 | getModulePath().from(conf); 53 | getModuleArtifacts().put(conf.getName(), getProviders().provider(() -> conf.getIncoming() 54 | .getArtifacts())); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/JDKInfo.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies; 3 | 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | interface JDKInfo { 8 | List MODULES = Arrays.asList( 9 | "java.base", 10 | "java.compiler", 11 | "java.datatransfer", 12 | "java.desktop", 13 | "java.instrument", 14 | "java.logging", 15 | "java.management", 16 | "java.management.rmi", 17 | "java.naming", 18 | "java.net.http", 19 | "java.prefs", 20 | "java.rmi", 21 | "java.scripting", 22 | "java.se", 23 | "java.security.jgss", 24 | "java.security.sasl", 25 | "java.smartcardio", 26 | "java.sql", 27 | "java.sql.rowset", 28 | "java.transaction.xa", 29 | "java.xml", 30 | "java.xml.crypto", 31 | "jdk.accessibility", 32 | "jdk.attach", 33 | "jdk.charsets", 34 | "jdk.compiler", 35 | "jdk.crypto.cryptoki", 36 | "jdk.crypto.ec", 37 | "jdk.dynalink", 38 | "jdk.editpad", 39 | "jdk.hotspot.agent", 40 | "jdk.httpserver", 41 | "jdk.incubator.foreign", 42 | "jdk.incubator.vector", 43 | "jdk.jartool", 44 | "jdk.javadoc", 45 | "jdk.jcmd", 46 | "jdk.jconsole", 47 | "jdk.jdeps", 48 | "jdk.jdi", 49 | "jdk.jdwp.agent", 50 | "jdk.jfr", 51 | "jdk.jlink", 52 | "jdk.jpackage", 53 | "jdk.jshell", 54 | "jdk.jsobject", 55 | "jdk.jstatd", 56 | "jdk.localedata", 57 | "jdk.management", 58 | "jdk.management.agent", 59 | "jdk.management.jfr", 60 | "jdk.naming.dns", 61 | "jdk.naming.rmi", 62 | "jdk.net", 63 | "jdk.nio.mapmode", 64 | "jdk.sctp", 65 | "jdk.security.auth", 66 | "jdk.security.jgss", 67 | "jdk.unsupported", 68 | "jdk.unsupported.desktop", 69 | "jdk.xml.dom", 70 | "jdk.zipfs"); 71 | } 72 | -------------------------------------------------------------------------------- /samples/kotlin/build-logic/src/main/kotlin/org.my.gradle.kotlin-java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradlex.java-module-dependencies") 3 | id("org.jetbrains.kotlin.jvm") 4 | } 5 | 6 | group = "org.my" 7 | val moduleName = "${project.group}.${project.name}" 8 | val testModuleName = "${moduleName}.test" 9 | 10 | val javaLanguageVersion = JavaLanguageVersion.of(11) 11 | java { 12 | toolchain.languageVersion.set(javaLanguageVersion) 13 | } 14 | kotlin.jvmToolchain { 15 | languageVersion.set(javaLanguageVersion) 16 | } 17 | 18 | // this is needed because we have a separate compile step in this example with the 'module-info.java' is in 'main/java' and the Kotlin code is in 'main/kotlin' 19 | tasks.compileJava { 20 | // Compiling module-info in the 'main/java' folder needs to see already compiled Kotlin code 21 | options.compilerArgs = listOf("--patch-module", "$moduleName=${sourceSets.main.get().output.asPath}") 22 | } 23 | 24 | // Testing with JUnit5 (which is available in modules) 25 | tasks.compileTestKotlin { 26 | // Make sure only module Jars are on the classpath and not the classes folders of the current project 27 | libraries.setFrom(configurations.testCompileClasspath.get().filter { f -> f.isFile }) 28 | } 29 | tasks.compileTestJava { 30 | // Compiling module-info in the 'test/java' folder needs to see already compiled Kotlin code 31 | options.compilerArgs = listOf("--patch-module", "$testModuleName=${sourceSets.test.get().output.asPath}") 32 | // Make sure only module Jars are on the classpath and not the classes folders of the current project 33 | classpath = configurations.testCompileClasspath.get() 34 | } 35 | val testJar = tasks.register(sourceSets.test.get().jarTaskName) { 36 | // Package test code/resources as Jar so that they are a proper module at runtime 37 | archiveClassifier.set("tests") 38 | from(sourceSets.test.get().output) 39 | } 40 | tasks.test { 41 | classpath = configurations.testRuntimeClasspath.get().filter { f -> f.isFile } + files(testJar) 42 | useJUnitPlatform() 43 | jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") 44 | } 45 | 46 | tasks.test { 47 | useJUnitPlatform() 48 | } 49 | 50 | dependencies { 51 | testRuntimeOnly(javaModuleDependencies.gav("org.junit.jupiter.engine")) 52 | testRuntimeOnly(javaModuleDependencies.gav("org.junit.platform.launcher")) 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ValueModuleDirectoryListing.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | import org.gradle.api.provider.Property; 12 | import org.gradle.api.provider.SetProperty; 13 | import org.gradle.api.provider.ValueSource; 14 | import org.gradle.api.provider.ValueSourceParameters; 15 | 16 | public abstract class ValueModuleDirectoryListing 17 | implements ValueSource, ValueModuleDirectoryListing.Parameter> { 18 | 19 | public interface Parameter extends ValueSourceParameters { 20 | Property getDir(); 21 | 22 | SetProperty getExplicitlyConfiguredFolders(); 23 | 24 | SetProperty getExclusions(); 25 | 26 | Property getRequiresBuildFile(); 27 | } 28 | 29 | @Override 30 | public List obtain() { 31 | Path path = getParameters().getDir().get().toPath(); 32 | try (Stream directoryStream = 33 | Files.find(path, 1, (unused, basicFileAttributes) -> basicFileAttributes.isDirectory())) { 34 | return directoryStream 35 | .filter(x -> !getParameters() 36 | .getExplicitlyConfiguredFolders() 37 | .get() 38 | .contains(x.getFileName().toString())) 39 | .filter(x -> getParameters().getExclusions().get().stream() 40 | .noneMatch(r -> x.getFileName().toString().matches(r))) 41 | .filter(x -> checkBuildFile(x, getParameters())) 42 | .map(x -> x.getFileName().toString()) 43 | .sorted() 44 | .collect(Collectors.toList()); 45 | 46 | } catch (IOException e) { 47 | throw new RuntimeException("Failed to inspect: " + path, e); 48 | } 49 | } 50 | 51 | private boolean checkBuildFile(Path x, Parameter parameters) { 52 | if (!parameters.getRequiresBuildFile().get()) { 53 | return true; 54 | } 55 | return Files.isRegularFile(x.resolve("build.gradle.kts")) || Files.isRegularFile(x.resolve("build.gradle")); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/extension/ExtensionTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.extension; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class ExtensionTest { 10 | 11 | GradleBuild build = new GradleBuild(); 12 | 13 | @Test 14 | void can_access_mapping_information_from_extension() { 15 | build.appBuildFile.appendText( 16 | """ 17 | javaModuleDependencies.moduleNamePrefixToGroup.put("org.example.app.", "org.example.gr") 18 | 19 | javaModuleDependencies { 20 | println(ga("com.fasterxml.jackson.core").get()) 21 | println(ga(provider { "com.fasterxml.jackson.databind" }).get()) 22 | println(gav("com.fasterxml.jackson.core", "1.0").get()) 23 | println(gav(provider { "com.fasterxml.jackson.databind" }, provider { "1.0" }).get()) 24 | println(moduleName("com.fasterxml.jackson.core:jackson-core").get()) 25 | println(moduleName(provider { "com.fasterxml.jackson.core:jackson-databind" }).get()) 26 | 27 | println(ga("org.example.app.my.mod1").get()) 28 | println(ga(provider { "org.example.app.my.mod2.impl" }).get()) 29 | println(gav("org.example.app.my.mod3.impl.int", "1.0").get()) 30 | println(gav(provider { "org.example.app.mod4" }, provider { "1.0" }).get()) 31 | println(moduleName("org.example.gr:mod8.ab").get()) 32 | println(moduleName(provider { "org.example.gr:mod.z7.i9" }).get()) 33 | }"""); 34 | 35 | var result = build.build(); 36 | 37 | assertThat(result.getOutput()) 38 | .contains( 39 | """ 40 | com.fasterxml.jackson.core:jackson-core 41 | com.fasterxml.jackson.core:jackson-databind 42 | com.fasterxml.jackson.core:jackson-core:1.0 43 | com.fasterxml.jackson.core:jackson-databind:1.0 44 | com.fasterxml.jackson.core 45 | com.fasterxml.jackson.databind 46 | org.example.gr:my.mod1 47 | org.example.gr:my.mod2.impl 48 | org.example.gr:my.mod3.impl.int:1.0 49 | org.example.gr:mod4:1.0 50 | org.example.app.mod8.ab 51 | org.example.app.mod.z7.i9"""); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/tasks/OrderingCheckTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.tasks; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class OrderingCheckTest { 10 | 11 | GradleBuild build = new GradleBuild(true); 12 | 13 | @Test 14 | void order_is_expected_to_be_alphabetic_for_each_scope_individually() { 15 | build.appModuleInfoFile.writeText( 16 | """ 17 | module org.example.app { 18 | requires a.b.c; 19 | requires b.f.g; 20 | requires b.z.u; 21 | 22 | requires static c.w.q; 23 | requires static c.z.u; 24 | }"""); 25 | 26 | build.runner(":app:checkAllModuleInfo").build(); 27 | } 28 | 29 | @Test 30 | void if_order_is_not_alphabetic_for_a_scope_an_advice_is_given() { 31 | build.appModuleInfoFile.writeText( 32 | """ 33 | module org.example.app { 34 | requires a.b.c; 35 | requires b.z.u; 36 | requires b.f.g; 37 | 38 | requires static c.w.q; 39 | requires static c.z.u; 40 | }"""); 41 | 42 | var result = build.runner(":app:checkAllModuleInfo").buildAndFail(); 43 | assertThat(result.getOutput()) 44 | .contains( 45 | """ 46 | > %s/app/src/main/java/module-info.java 47 | \s 48 | 'requires' are not declared in alphabetical order. Please use this order: 49 | requires a.b.c; 50 | requires b.f.g; 51 | requires b.z.u;""" 52 | .formatted(build.projectDir.canonicalPath())); 53 | } 54 | 55 | @Test 56 | void own_modules_are_expected_to_go_first() { 57 | build.appModuleInfoFile.writeText( 58 | """ 59 | module org.example.app { 60 | requires org.example.h; 61 | requires org.example.j; 62 | 63 | requires a.b.c; 64 | requires b.f.g; 65 | requires z.z.u; 66 | 67 | requires static org.example.z; 68 | requires static c.w.q; 69 | requires static c.z.u; 70 | }"""); 71 | 72 | build.runner(":app:checkAllModuleInfo").build(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/dsl/ModuleVersions.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.dsl; 3 | 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | import javax.inject.Inject; 7 | import org.gradle.api.Action; 8 | import org.gradle.api.artifacts.Configuration; 9 | import org.gradle.api.artifacts.DependencyConstraint; 10 | import org.gradle.api.artifacts.MutableVersionConstraint; 11 | import org.gradle.api.artifacts.dsl.DependencyHandler; 12 | import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; 13 | 14 | public abstract class ModuleVersions { 15 | 16 | private final Map declaredVersions = new LinkedHashMap<>(); 17 | private final Configuration configuration; 18 | protected final JavaModuleDependenciesExtension javaModuleDependencies; 19 | 20 | public Map getDeclaredVersions() { 21 | return declaredVersions; 22 | } 23 | 24 | @Inject 25 | protected abstract DependencyHandler getDependencies(); 26 | 27 | public ModuleVersions(Configuration configuration, JavaModuleDependenciesExtension javaModuleDependencies) { 28 | this.configuration = configuration; 29 | this.javaModuleDependencies = javaModuleDependencies; 30 | } 31 | 32 | public void version(String moduleName, String version) { 33 | version(moduleName, version, a -> {}); 34 | } 35 | 36 | public void version(String moduleName, Action version) { 37 | version(moduleName, "", version); 38 | } 39 | 40 | public void version(String moduleName, String requiredVersion, Action version) { 41 | getDependencies() 42 | .getConstraints() 43 | .add( 44 | configuration.getName(), 45 | javaModuleDependencies.ga(moduleName).map(ga -> { 46 | String mainComponentCoordinates; 47 | if (ga.contains("|")) { 48 | mainComponentCoordinates = ga.substring(0, ga.indexOf("|")) + ":" + requiredVersion; 49 | } else { 50 | mainComponentCoordinates = ga + ":" + requiredVersion; 51 | } 52 | DependencyConstraint dependencyConstraint = 53 | getDependencies().getConstraints().create(mainComponentCoordinates); 54 | dependencyConstraint.version(version); 55 | return dependencyConstraint; 56 | })); 57 | declaredVersions.put(moduleName, requiredVersion); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/localmodules/LocalModuleMappingsTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.localmodules; 3 | 4 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class LocalModuleMappingsTest { 8 | 9 | GradleBuild build = new GradleBuild(); 10 | 11 | @Test 12 | void automatically_maps_local_modules_if_name_prefix_matches() { 13 | build.libModuleInfoFile.writeText("module org.gradlex.test.lib { }"); 14 | build.appModuleInfoFile.writeText( 15 | """ 16 | module org.gradlex.test.app { 17 | requires org.gradlex.test.lib; 18 | }"""); 19 | 20 | build.build(); 21 | } 22 | 23 | @Test 24 | void automatically_maps_local_modules_if_name_matches() { 25 | build.libModuleInfoFile.writeText("module lib { }"); 26 | build.appModuleInfoFile.writeText(""" 27 | module app { 28 | requires lib; 29 | }"""); 30 | 31 | build.build(); 32 | } 33 | 34 | @Test 35 | void a_prefix_to_group_mapping_can_be_used() { 36 | build.appBuildFile.appendText( 37 | """ 38 | javaModuleDependencies.moduleNamePrefixToGroup.put("abc.", "foo.gr") 39 | """); 40 | build.libBuildFile.appendText(""" 41 | group = "foo.gr" 42 | """); 43 | 44 | build.libModuleInfoFile.writeText("module abc.lib { }"); 45 | build.appModuleInfoFile.writeText( 46 | """ 47 | module org.gradlex.test.app { 48 | requires abc.lib; 49 | }"""); 50 | 51 | build.runner(false, "build").build(); 52 | } 53 | 54 | @Test 55 | void does_not_fail_if_there_are_two_project_with_same_name_but_different_path() { 56 | build.libModuleInfoFile.writeText( 57 | """ 58 | module org.gradlex.test.lib { 59 | requires org.gradlex.test.anotherlib; 60 | } 61 | """); 62 | build.settingsFile.appendText("include(\"another:lib\")"); 63 | build.file("another/lib/build.gradle.kts") 64 | .writeText( 65 | """ 66 | plugins { 67 | id("org.gradlex.java-module-dependencies") 68 | id("java-library") 69 | } 70 | group = "another" 71 | """); 72 | build.file("another/lib/src/main/java/module-info.java") 73 | .writeText(""" 74 | module org.gradlex.test.anotherlib { } 75 | """); 76 | build.file("gradle/modules.properties") 77 | .writeText(""" 78 | org.gradlex.test.anotherlib=another:lib 79 | """); 80 | 81 | build.runner(false, "build"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.initialization; 3 | 4 | import java.io.File; 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | import javax.inject.Inject; 9 | import org.gradle.api.provider.ListProperty; 10 | import org.gradle.api.provider.Property; 11 | 12 | public abstract class Module { 13 | 14 | /** 15 | * The 'artifact' name of the Module. This corresponds to the Gradle subproject name. If the Module is published 16 | * to a Maven repository, this is the 'artifact' in the 'group:artifact' identifier to address the published Jar. 17 | */ 18 | public abstract Property getArtifact(); 19 | 20 | /** 21 | * The 'group' of the Module. This corresponds to setting the 'group' property in a build.gradle file. If the 22 | * Module is published to a Maven repository, this is the 'group' in the 'group:artifact' identifier to address 23 | * the published Jar. The group needs to be configured here (rather than in build.gradle files) for the plugin 24 | * to support additional Modules inside a subproject that other modules depend on, such as a 'testFixtures' module. 25 | */ 26 | public abstract Property getGroup(); 27 | 28 | /** 29 | * The paths of the module-info.java files inside the project directory. Usually, this does not need to be adjusted. 30 | * By default, it contains all 'src/$sourceSetName/java/module-info.java' files that exist. 31 | */ 32 | public abstract ListProperty getModuleInfoPaths(); 33 | 34 | /** 35 | * {@link Module#plugin(String)} 36 | */ 37 | public abstract ListProperty getPlugins(); 38 | 39 | File directory; 40 | 41 | @Inject 42 | public Module(File directory) { 43 | this.directory = directory; 44 | getArtifact().convention(directory.getName()); 45 | getModuleInfoPaths() 46 | .convention(listSrcChildren() 47 | .map(srcDir -> new File(srcDir, "java/module-info.java")) 48 | .filter(File::exists) 49 | .map(moduleInfo -> "src/" 50 | + moduleInfo.getParentFile().getParentFile().getName() + "/java") 51 | .collect(Collectors.toList())); 52 | } 53 | 54 | /** 55 | * Apply a plugin to the Module project. This is the same as using the 'plugins { }' block in the Module's 56 | * build.gradle file. Applying plugins here allows you to omit build.gradle files completely. 57 | */ 58 | public void plugin(String id) { 59 | getPlugins().add(id); 60 | } 61 | 62 | private Stream listSrcChildren() { 63 | File[] children = new File(directory, "src").listFiles(); 64 | return children == null ? Stream.empty() : Arrays.stream(children); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.initialization; 3 | 4 | import java.io.File; 5 | import java.util.Arrays; 6 | import java.util.LinkedHashMap; 7 | import java.util.Map; 8 | import javax.inject.Inject; 9 | import org.gradle.api.Action; 10 | import org.gradle.api.model.ObjectFactory; 11 | import org.gradle.api.provider.ListProperty; 12 | import org.gradle.api.provider.Property; 13 | 14 | public abstract class Directory { 15 | 16 | private final File root; 17 | final Map customizedModules = new LinkedHashMap<>(); 18 | 19 | /** 20 | * {@link Module#getGroup()} 21 | */ 22 | public abstract Property getGroup(); 23 | 24 | /** 25 | * {@link Module#plugin(String)} 26 | */ 27 | public abstract ListProperty getPlugins(); 28 | 29 | @Inject 30 | public Directory(File root) { 31 | this.root = root; 32 | getExclusions().convention(Arrays.asList("build", "\\..*")); 33 | getRequiresBuildFile().convention(false); 34 | } 35 | 36 | @Inject 37 | protected abstract ObjectFactory getObjects(); 38 | 39 | /** 40 | * {@link Module#plugin(String)} 41 | */ 42 | public void plugin(String id) { 43 | getPlugins().add(id); 44 | } 45 | 46 | /** 47 | * {@link Directory#module(String, Action)} 48 | */ 49 | public void module(String subDirectory) { 50 | module(subDirectory, m -> {}); 51 | } 52 | 53 | /** 54 | * Configure details of a Module in a subdirectory of this directory. 55 | * Note that Modules that are located in direct children of this directory are discovered automatically and 56 | * do not need to be explicitly mentioned. 57 | */ 58 | public void module(String subDirectory, Action action) { 59 | Module module = addModule(subDirectory); 60 | action.execute(module); 61 | customizedModules.put(subDirectory, module); 62 | } 63 | 64 | Module addModule(String subDirectory) { 65 | Module module = getObjects().newInstance(Module.class, new File(root, subDirectory)); 66 | module.getGroup().convention(getGroup()); 67 | module.getPlugins().addAll(getPlugins()); 68 | return module; 69 | } 70 | 71 | /** 72 | * Configure which folders should be ignored when searching for Modules. 73 | * This can be tweaked to optimize the configuration cache hit ratio. 74 | * Defaults to: 'build', '.*' 75 | */ 76 | public abstract ListProperty getExclusions(); 77 | 78 | /** 79 | * Configure if only folders that contain a 'build.gradle' or 'build.gradle.kts' 80 | * should be considered when searching for Modules. 81 | * Setting this to true may improve configuration cache hit ratio if you know 82 | * that all modules have build files in addition to the 'module-info.java' files. 83 | */ 84 | public abstract Property getRequiresBuildFile(); 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/bridges/DependencyAnalysisBridge.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.bridges; 3 | 4 | import com.autonomousapps.AbstractExtension; 5 | import java.io.File; 6 | import org.gradle.api.Project; 7 | import org.gradle.api.Task; 8 | import org.gradle.api.artifacts.Configuration; 9 | import org.gradle.api.tasks.SourceSetContainer; 10 | import org.gradle.api.tasks.TaskContainer; 11 | import org.gradle.api.tasks.TaskProvider; 12 | import org.gradlex.javamodule.dependencies.tasks.ModuleDirectivesOrderingCheck; 13 | import org.gradlex.javamodule.dependencies.tasks.ModuleDirectivesScopeCheck; 14 | import org.jspecify.annotations.Nullable; 15 | 16 | public class DependencyAnalysisBridge { 17 | 18 | public static void registerDependencyAnalysisPostProcessingTask( 19 | Project project, @Nullable TaskProvider checkAllModuleInfo) { 20 | TaskContainer tasks = project.getTasks(); 21 | SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); 22 | 23 | TaskProvider checkModuleDirectivesScope = 24 | tasks.register("checkModuleDirectivesScope", ModuleDirectivesScopeCheck.class, t -> t.getReport() 25 | .convention(project.getLayout() 26 | .getBuildDirectory() 27 | .file("reports/module-info-analysis/scopes.txt"))); 28 | 29 | sourceSets.all(sourceSet -> checkModuleDirectivesScope.configure(t -> { 30 | File moduleInfo = 31 | new File(sourceSet.getJava().getSrcDirs().iterator().next(), "module-info.java"); 32 | if (!moduleInfo.exists()) { 33 | moduleInfo = project.getBuildFile(); // no module-info: dependencies are declared in build file 34 | } 35 | t.getSourceSets().put(sourceSet.getName(), moduleInfo.getAbsolutePath()); 36 | 37 | Configuration cpClasspath = 38 | project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); 39 | Configuration rtClasspath = 40 | project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); 41 | t.getModuleArtifacts() 42 | .add(project.provider(() -> cpClasspath.getIncoming().getArtifacts())); 43 | t.getModuleArtifacts() 44 | .add(project.provider(() -> rtClasspath.getIncoming().getArtifacts())); 45 | })); 46 | 47 | project.getExtensions() 48 | .getByType(AbstractExtension.class) 49 | .registerPostProcessingTask(checkModuleDirectivesScope); 50 | 51 | if (checkAllModuleInfo != null) { 52 | checkAllModuleInfo.configure(t -> t.dependsOn(checkModuleDirectivesScope)); 53 | } 54 | tasks.withType(ModuleDirectivesOrderingCheck.class) 55 | .configureEach(t -> t.mustRunAfter(checkModuleDirectivesScope)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/ModuleDirectivesOrderingCheck.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.tasks; 3 | 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import org.gradle.api.DefaultTask; 10 | import org.gradle.api.file.RegularFileProperty; 11 | import org.gradle.api.provider.Property; 12 | import org.gradle.api.tasks.CacheableTask; 13 | import org.gradle.api.tasks.Input; 14 | import org.gradle.api.tasks.Optional; 15 | import org.gradle.api.tasks.OutputFile; 16 | import org.gradle.api.tasks.TaskAction; 17 | import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo; 18 | 19 | @CacheableTask 20 | public abstract class ModuleDirectivesOrderingCheck extends DefaultTask { 21 | 22 | @Input 23 | @Optional 24 | public abstract Property getModuleInfoPath(); 25 | 26 | @Input 27 | @Optional 28 | public abstract Property getModuleNamePrefix(); 29 | 30 | @Input 31 | public abstract Property getModuleInfo(); 32 | 33 | @OutputFile 34 | public abstract RegularFileProperty getReport(); 35 | 36 | @TaskAction 37 | public void checkOrder() throws IOException { 38 | StringBuilder sb = new StringBuilder(); 39 | for (ModuleInfo.Directive directive : ModuleInfo.Directive.values()) { 40 | List originalOrder = getModuleInfo().get().get(directive).stream() 41 | .map(name -> name + ";") 42 | .collect(Collectors.toList()); 43 | 44 | List sorted = new ArrayList<>(originalOrder); 45 | sorted.sort((m1, m2) -> { 46 | // own modules go first 47 | if (getModuleNamePrefix().isPresent()) { 48 | if (m1.startsWith(getModuleNamePrefix().get()) 49 | && !m2.startsWith(getModuleNamePrefix().get())) { 50 | return -1; 51 | } 52 | if (!m1.startsWith(getModuleNamePrefix().get()) 53 | && m2.startsWith(getModuleNamePrefix().get())) { 54 | return 1; 55 | } 56 | } 57 | return m1.compareTo(m2); 58 | }); 59 | 60 | if (!originalOrder.equals(sorted)) { 61 | p(sb, "'" + directive.literal() + "' are not declared in alphabetical order. Please use this order:"); 62 | for (String entry : sorted) { 63 | p(sb, " " + directive.literal() + " " + entry); 64 | } 65 | p(sb, ""); 66 | } 67 | } 68 | 69 | Files.write(getReport().get().getAsFile().toPath(), sb.toString().getBytes()); 70 | 71 | if (sb.length() > 0) { 72 | throw new RuntimeException(getModuleInfoPath().get() + "\n\n" + sb); 73 | } 74 | } 75 | 76 | private void p(StringBuilder sb, String toPrint) { 77 | sb.append(toPrint); 78 | sb.append("\n"); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/diagnostics/AsciiModuleDependencyReportRenderer.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.diagnostics; 3 | 4 | import static java.util.Objects.requireNonNull; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | import org.gradle.api.artifacts.ArtifactCollection; 9 | import org.gradle.api.artifacts.result.ResolvedComponentResult; 10 | import org.gradle.api.provider.Provider; 11 | import org.gradle.api.tasks.diagnostics.internal.ConfigurationDetails; 12 | import org.gradle.api.tasks.diagnostics.internal.ProjectDetails; 13 | import org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer; 14 | import org.gradle.api.tasks.diagnostics.internal.graph.DependencyGraphsRenderer; 15 | import org.gradle.api.tasks.diagnostics.internal.graph.NodeRenderer; 16 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency; 17 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult; 18 | import org.gradle.internal.graph.GraphRenderer; 19 | import org.gradle.internal.logging.text.StyledTextOutput; 20 | import org.jspecify.annotations.Nullable; 21 | 22 | public class AsciiModuleDependencyReportRenderer extends AsciiDependencyReportRenderer { 23 | 24 | private @Nullable DependencyGraphsRenderer dependencyGraphRenderer; 25 | private final Provider> resolvedJars; 26 | 27 | public AsciiModuleDependencyReportRenderer(Provider> resolvedJars) { 28 | this.resolvedJars = resolvedJars; 29 | } 30 | 31 | @Override 32 | public void startProject(ProjectDetails project) { 33 | super.startProject(project); 34 | GraphRenderer renderer = new GraphRenderer(this.getTextOutput()); 35 | this.dependencyGraphRenderer = new DependencyGraphsRenderer( 36 | this.getTextOutput(), renderer, NodeRenderer.NO_OP, new StyledNodeRenderer()); 37 | } 38 | 39 | @Override 40 | public void render(ConfigurationDetails configuration) { 41 | if (configuration.isCanBeResolved()) { 42 | ResolvedComponentResult result = 43 | requireNonNull(configuration.getResolutionResultRoot()).get(); 44 | RenderableModuleResult root = new RenderableJavaModuleResult( 45 | result, resolvedJars.get().get(configuration.getName()).getArtifacts()); 46 | renderNow(root); 47 | } else { 48 | renderNow(requireNonNull(configuration.getUnresolvableResult())); 49 | } 50 | } 51 | 52 | private void renderNow(RenderableDependency root) { 53 | if (root.getChildren().isEmpty()) { 54 | this.getTextOutput().withStyle(StyledTextOutput.Style.Info).text("No dependencies"); 55 | this.getTextOutput().println(); 56 | } else if (this.dependencyGraphRenderer != null) { 57 | this.dependencyGraphRenderer.render(Collections.singletonList(root)); 58 | } 59 | } 60 | 61 | public void complete() { 62 | if (this.dependencyGraphRenderer != null) { 63 | this.dependencyGraphRenderer.complete(); 64 | } 65 | super.complete(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/variants/NonMainVariantsTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.variants; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class NonMainVariantsTest { 10 | 11 | GradleBuild build = new GradleBuild(); 12 | 13 | @Test 14 | void finds_test_fixtures_module() { 15 | build.appModuleInfoFile.writeText( 16 | """ 17 | module org.gradlex.test.app { 18 | requires org.gradlex.test.lib.test.fixtures; 19 | }"""); 20 | build.libModuleInfoFile.writeText(""" 21 | module org.gradlex.test.lib { 22 | }"""); 23 | build.file("lib/src/testFixtures/java/module-info.java") 24 | .writeText(""" 25 | module org.gradlex.test.lib.test.fixtures { 26 | }"""); 27 | 28 | var result = build.printRuntimeJars(); 29 | 30 | assertThat(result.getOutput()).contains("[lib-test-fixtures.jar, lib.jar]"); 31 | } 32 | 33 | @Test 34 | void finds_feature_variant_module() { 35 | build.libBuildFile.appendText( 36 | """ 37 | val extraFeature = sourceSets.create("extraFeature") 38 | java.registerFeature(extraFeature.name) { 39 | usingSourceSet(extraFeature) 40 | }"""); 41 | 42 | build.appModuleInfoFile.writeText( 43 | """ 44 | module org.gradlex.test.app { 45 | requires org.gradlex.test.lib.extra.feature; 46 | }"""); 47 | build.libModuleInfoFile.writeText(""" 48 | module org.gradlex.test.lib { 49 | }"""); 50 | build.file("lib/src/extraFeature/java/module-info.java") 51 | .appendText(""" 52 | module org.gradlex.test.lib.extra.feature { 53 | }"""); 54 | 55 | var result = build.printRuntimeJars(); 56 | 57 | assertThat(result.getOutput()).contains("[lib-extra-feature.jar"); 58 | } 59 | 60 | @Test 61 | void finds_published_feature_variant_when_corresponding_mapping_is_defined() { 62 | // There are no modules published like this anywhere public right now. 63 | // We test that the expected Jar file would have been downloaded if "org.slf4j" would have test fixtures. 64 | build.appBuildFile.appendText( 65 | """ 66 | javaModuleDependencies { 67 | moduleNameToGA.put("org.slf4j.test.fixtures", "org.slf4j:slf4j-api|org.slf4j:slf4j-api-test-fixtures") 68 | } 69 | dependencies.constraints { 70 | javaModuleDependencies { implementation(gav("org.slf4j", "2.0.3")) } 71 | }"""); 72 | build.appModuleInfoFile.appendText( 73 | """ 74 | module org.gradlex.test.app { 75 | requires org.slf4j.test.fixtures; 76 | }"""); 77 | 78 | var result = build.fail(); 79 | 80 | assertThat(result.getOutput()) 81 | .contains("Unable to find a variant", "requested capability", "org.slf4j:slf4j-api-test-fixtures"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginVersionManagementTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.initialization; 3 | 4 | import static java.util.Objects.requireNonNull; 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; 7 | import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE; 8 | 9 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Tag; 12 | import org.junit.jupiter.api.Test; 13 | 14 | @Tag("no-cross-version") 15 | class SettingsPluginVersionManagementTest { 16 | 17 | GradleBuild build = new GradleBuild(); 18 | 19 | @BeforeEach 20 | void setup() { 21 | var buildFile = 22 | """ 23 | plugins { id("java-library") } 24 | dependencies { implementation(platform(project(":versions"))) }"""; 25 | build.settingsFile.writeText( 26 | """ 27 | plugins { id("org.gradlex.java-module-dependencies") } 28 | dependencyResolutionManagement { repositories.mavenCentral() } 29 | """); 30 | build.appBuildFile.writeText(buildFile); 31 | build.libBuildFile.writeText(buildFile); 32 | } 33 | 34 | @Test 35 | void can_define_a_version_providing_project_in_settings() { 36 | build.settingsFile.appendText( 37 | """ 38 | javaModules { 39 | directory(".") 40 | versions("gradle/versions") 41 | }"""); 42 | build.libModuleInfoFile.writeText("module abc.lib { }"); 43 | build.appModuleInfoFile.writeText( 44 | """ 45 | module org.gradlex.test.app { 46 | requires abc.lib; 47 | requires java.inject; 48 | }"""); 49 | build.file("gradle/versions/build.gradle.kts") 50 | .writeText( 51 | """ 52 | moduleInfo { 53 | version("java.inject", "1.0.5") 54 | }"""); 55 | 56 | var result = build.runner(":app:compileJava").build(); 57 | 58 | assertThat(requireNonNull(result.task(":app:compileJava")).getOutcome()).isEqualTo(SUCCESS); 59 | } 60 | 61 | @Test 62 | void can_define_a_version_providing_project_in_settings_with_additional_plugin() { 63 | build.settingsFile.appendText( 64 | """ 65 | javaModules { 66 | directory(".") 67 | versions("gradle/versions") { plugin("maven-publish") } 68 | }"""); 69 | build.libModuleInfoFile.writeText("module abc.lib { }"); 70 | build.appModuleInfoFile.writeText( 71 | """ 72 | module org.gradlex.test.app { 73 | requires abc.lib; 74 | requires java.inject; 75 | }"""); 76 | build.file("gradle/versions/build.gradle.kts") 77 | .writeText( 78 | """ 79 | moduleInfo { 80 | version("java.inject", "1.0.5") 81 | }"""); 82 | 83 | var result = build.runner(":versions:publish").build(); 84 | 85 | assertThat(requireNonNull(result.task(":versions:publish")).getOutcome()) 86 | .isEqualTo(UP_TO_DATE); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/CatalogGenerate.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.tasks; 3 | 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.util.ArrayList; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.stream.Collectors; 12 | import org.gradle.api.DefaultTask; 13 | import org.gradle.api.file.RegularFileProperty; 14 | import org.gradle.api.provider.Property; 15 | import org.gradle.api.provider.Provider; 16 | import org.gradle.api.provider.SetProperty; 17 | import org.gradle.api.tasks.Internal; 18 | import org.gradle.api.tasks.TaskAction; 19 | import org.jspecify.annotations.Nullable; 20 | 21 | public abstract class CatalogGenerate extends DefaultTask { 22 | 23 | public static class CatalogEntry implements Comparator { 24 | private final String moduleName; 25 | private final Provider fullId; 26 | private final @Nullable String version; 27 | 28 | public CatalogEntry(String moduleName, Provider fullId, @Nullable String version) { 29 | this.moduleName = moduleName; 30 | this.fullId = fullId; 31 | this.version = version; 32 | } 33 | 34 | @Override 35 | public int compare(CatalogEntry e1, CatalogEntry e2) { 36 | return e1.moduleName.compareTo(e2.moduleName); 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | CatalogEntry that = (CatalogEntry) o; 44 | return Objects.equals(moduleName, that.moduleName); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(moduleName); 50 | } 51 | } 52 | 53 | @Internal 54 | public abstract SetProperty getEntries(); 55 | 56 | @Internal 57 | public abstract Property getOwnProjectGroup(); 58 | 59 | @Internal 60 | public abstract RegularFileProperty getCatalogFile(); 61 | 62 | @TaskAction 63 | public void generate() throws IOException { 64 | File catalog = getCatalogFile().get().getAsFile(); 65 | //noinspection ResultOfMethodCallIgnored 66 | catalog.getParentFile().mkdirs(); 67 | 68 | List content = new ArrayList<>(); 69 | content.add("[libraries]"); 70 | content.addAll(getEntries().get().stream() 71 | .map(this::toDeclarationString) 72 | .filter(Objects::nonNull) 73 | .sorted() 74 | .collect(Collectors.toList())); 75 | 76 | Files.write(catalog.toPath(), content); 77 | } 78 | 79 | @Nullable 80 | private String toDeclarationString(CatalogEntry entry) { 81 | String group = entry.fullId.get().split(":")[0]; 82 | if (group.equals(getOwnProjectGroup().get())) { 83 | return null; 84 | } 85 | String notation; 86 | if (entry.version == null) { 87 | notation = "{ module = \"" + entry.fullId.get() + "\" }"; 88 | } else { 89 | notation = "{ module = \"" + entry.fullId.get() + "\", version = \"" + entry.version + "\" }"; 90 | } 91 | return entry.moduleName.replace('.', '-') + " = " + notation; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/configcache/ConfigurationCacheTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.configcache; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.gradle.util.GradleVersion.version; 6 | import static org.gradlex.javamodule.dependencies.test.fixture.GradleBuild.GRADLE_VERSION_UNDER_TEST; 7 | 8 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 9 | import org.junit.jupiter.api.Tag; 10 | import org.junit.jupiter.api.Test; 11 | 12 | @Tag("no-cross-version") // can be removed once the min version is increased to 7.6.5 13 | class ConfigurationCacheTest { 14 | 15 | GradleBuild build = new GradleBuild(); 16 | 17 | final String noCacheMessage = GRADLE_VERSION_UNDER_TEST == null 18 | || version(GRADLE_VERSION_UNDER_TEST).compareTo(version("8.8")) >= 0 19 | ? "Calculating task graph as no cached configuration is available for tasks: :app:compileJava" 20 | : "Calculating task graph as no configuration cache is available for tasks: :app:compileJava"; 21 | 22 | @Test 23 | void configurationCacheHit() { 24 | build.libModuleInfoFile.writeText("module abc.lib { }"); 25 | 26 | build.appModuleInfoFile.writeText( 27 | """ 28 | module abc.app { 29 | requires abc.lib; 30 | }"""); 31 | 32 | var runner = build.runner("--configuration-cache", ":app:compileJava"); 33 | var result = runner.build(); 34 | 35 | assertThat(result.getOutput()).contains(noCacheMessage); 36 | 37 | result = runner.build(); 38 | 39 | assertThat(result.getOutput()).contains("Reusing configuration cache."); 40 | } 41 | 42 | @Test 43 | void configurationCacheHitIrrelevantChange() { 44 | build.libModuleInfoFile.writeText("module abc.lib { }"); 45 | build.appModuleInfoFile.writeText( 46 | """ 47 | module abc.app { 48 | requires abc.lib; 49 | }"""); 50 | 51 | var runner = build.runner("--configuration-cache", ":app:compileJava"); 52 | var result = runner.build(); 53 | 54 | assertThat(result.getOutput()).contains(noCacheMessage); 55 | 56 | build.appModuleInfoFile.writeText( 57 | """ 58 | module abc.app { 59 | requires abc.lib; //This is a comment and should not break the configurationCache 60 | } 61 | """); 62 | result = runner.build(); 63 | 64 | assertThat(result.getOutput()).contains("Reusing configuration cache."); 65 | } 66 | 67 | @Test 68 | void configurationCacheMissRelevantChange() { 69 | build.libModuleInfoFile.writeText("module abc.lib { }"); 70 | build.appModuleInfoFile.writeText( 71 | """ 72 | module abc.app { 73 | requires abc.lib; 74 | }"""); 75 | 76 | var runner = build.runner("--configuration-cache", ":app:compileJava"); 77 | var result = runner.build(); 78 | 79 | assertThat(result.getOutput()).contains(noCacheMessage); 80 | 81 | build.appModuleInfoFile.writeText( 82 | """ 83 | module abc.app { 84 | //dependency removed; so thats indeed a configuration change 85 | }"""); 86 | result = runner.build(); 87 | 88 | assertThat(result.getOutput()) 89 | .contains( 90 | "Calculating task graph as configuration cache cannot be reused because a build logic input of type 'ValueSourceModuleInfo' has changed.\n"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/DependencyDeclarationsUtil.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; 5 | import static org.gradle.api.attributes.Category.LIBRARY; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | import org.gradle.api.Project; 11 | import org.gradle.api.artifacts.ConfigurationContainer; 12 | import org.gradle.api.artifacts.Dependency; 13 | import org.gradle.api.artifacts.ModuleDependency; 14 | import org.gradle.api.artifacts.MutableVersionConstraint; 15 | import org.gradle.api.artifacts.ProjectDependency; 16 | import org.gradle.api.artifacts.VersionConstraint; 17 | import org.gradle.api.attributes.Category; 18 | import org.gradle.api.capabilities.Capability; 19 | import org.gradle.api.provider.Provider; 20 | 21 | public abstract class DependencyDeclarationsUtil { 22 | 23 | public static Provider> declaredDependencies(Project project, String configuration) { 24 | ConfigurationContainer configurations = project.getConfigurations(); 25 | return project.provider(() -> configurations.getNames().contains(configuration) 26 | ? configurations.getByName(configuration).getDependencies().stream() 27 | .filter(DependencyDeclarationsUtil::isLibraryDependency) 28 | .map(d -> toIdentifier(project, d)) 29 | .collect(Collectors.toList()) 30 | : Collections.emptyList()); 31 | } 32 | 33 | private static String toIdentifier(Project project, Dependency dependency) { 34 | if (dependency instanceof ProjectDependency) { 35 | // assume Module Name of local Module 36 | ProjectDependency projectDependency = (ProjectDependency) dependency; 37 | if (projectDependency.getRequestedCapabilities().isEmpty()) { 38 | return project.getGroup() + "." + dependency.getName(); 39 | } else { 40 | Capability capability = 41 | projectDependency.getRequestedCapabilities().get(0); 42 | return capability.getGroup() + "." + capability.getName().replace("-", "."); 43 | } 44 | } 45 | return dependency.getGroup() + ":" + dependency.getName(); 46 | } 47 | 48 | private static boolean isLibraryDependency(Dependency dependency) { 49 | if (dependency instanceof ModuleDependency) { 50 | ModuleDependency moduleDependency = (ModuleDependency) dependency; 51 | Category category = moduleDependency.getAttributes().getAttribute(CATEGORY_ATTRIBUTE); 52 | return category == null || category.getName().equals(LIBRARY); 53 | } 54 | return false; 55 | } 56 | 57 | /** 58 | * Fill a MutableVersionConstraint with the information from another VersionConstraint object retrieved 59 | * from a version catalog. 60 | */ 61 | public static void copyVersionConstraint(VersionConstraint version, MutableVersionConstraint copy) { 62 | String branch = version.getBranch(); 63 | String requiredVersion = version.getRequiredVersion(); 64 | String preferredVersion = version.getPreferredVersion(); 65 | String strictVersion = version.getStrictVersion(); 66 | 67 | if (branch != null && !branch.isEmpty()) { 68 | copy.setBranch(branch); 69 | } 70 | if (!requiredVersion.isEmpty()) { 71 | copy.require(requiredVersion); 72 | } 73 | if (!preferredVersion.isEmpty()) { 74 | copy.prefer(preferredVersion); 75 | } 76 | if (!strictVersion.isEmpty()) { 77 | copy.strictly(strictVersion); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/tasks/ModuleInfoGenerate.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.tasks; 3 | 4 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.*; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | import org.gradle.api.DefaultTask; 14 | import org.gradle.api.file.RegularFileProperty; 15 | import org.gradle.api.provider.ListProperty; 16 | import org.gradle.api.provider.MapProperty; 17 | import org.gradle.api.provider.Property; 18 | import org.gradle.api.tasks.Input; 19 | import org.gradle.api.tasks.OutputFile; 20 | import org.gradle.api.tasks.TaskAction; 21 | import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo; 22 | import org.jspecify.annotations.Nullable; 23 | 24 | public abstract class ModuleInfoGenerate extends DefaultTask { 25 | 26 | @Input 27 | public abstract Property getModuleName(); 28 | 29 | @Input 30 | public abstract ListProperty getApiDependencies(); 31 | 32 | @Input 33 | public abstract ListProperty getImplementationDependencies(); 34 | 35 | @Input 36 | public abstract ListProperty getCompileOnlyApiDependencies(); 37 | 38 | @Input 39 | public abstract ListProperty getCompileOnlyDependencies(); 40 | 41 | @Input 42 | public abstract ListProperty getRuntimeOnlyDependencies(); 43 | 44 | @Input 45 | public abstract MapProperty getModuleNameToGA(); 46 | 47 | @OutputFile 48 | public abstract RegularFileProperty getModuleInfoFile(); 49 | 50 | @TaskAction 51 | public void generate() throws IOException { 52 | File moduleInfo = getModuleInfoFile().get().getAsFile(); 53 | List content = new ArrayList<>(); 54 | 55 | content.add("module " + getModuleName().get() + " {"); 56 | if (!getApiDependencies().get().isEmpty()) { 57 | content.addAll(dependenciesToModuleDirectives(getApiDependencies().get(), REQUIRES_TRANSITIVE)); 58 | content.add(""); 59 | } 60 | if (!getImplementationDependencies().get().isEmpty()) { 61 | content.addAll(dependenciesToModuleDirectives( 62 | getImplementationDependencies().get(), REQUIRES)); 63 | content.add(""); 64 | } 65 | if (!getCompileOnlyApiDependencies().get().isEmpty()) { 66 | content.addAll(dependenciesToModuleDirectives( 67 | getCompileOnlyApiDependencies().get(), REQUIRES_STATIC_TRANSITIVE)); 68 | content.add(""); 69 | } 70 | if (!getCompileOnlyDependencies().get().isEmpty()) { 71 | content.addAll( 72 | dependenciesToModuleDirectives(getCompileOnlyDependencies().get(), REQUIRES_STATIC)); 73 | content.add(""); 74 | } 75 | if (!getRuntimeOnlyDependencies().get().isEmpty()) { 76 | content.addAll( 77 | dependenciesToModuleDirectives(getRuntimeOnlyDependencies().get(), REQUIRES_RUNTIME)); 78 | content.add(""); 79 | } 80 | content.add("}"); 81 | 82 | Files.write(moduleInfo.toPath(), content); 83 | } 84 | 85 | private Collection dependenciesToModuleDirectives( 86 | List dependencies, ModuleInfo.Directive directive) { 87 | return dependencies.stream() 88 | .map(gaOrProjectModuleName -> { 89 | String moduleName = moduleName(gaOrProjectModuleName); 90 | if (moduleName == null) { 91 | getLogger() 92 | .lifecycle("Skipping '" + gaOrProjectModuleName 93 | + "' - no mapping - run ':analyzeModulePath' for more details"); 94 | return " // " + directive.literal() + " " + gaOrProjectModuleName + ";"; 95 | } else { 96 | return " " + directive.literal() + " " + moduleName + ";"; 97 | } 98 | }) 99 | .collect(Collectors.toList()); 100 | } 101 | 102 | private @Nullable String moduleName(String gaOrProjectModuleName) { 103 | if (!gaOrProjectModuleName.contains(":")) { 104 | return gaOrProjectModuleName; 105 | } 106 | return getModuleNameToGA().get().get(gaOrProjectModuleName); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/ModuleInfoParseTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.REQUIRES; 6 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.REQUIRES_RUNTIME; 7 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.REQUIRES_STATIC; 8 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.REQUIRES_STATIC_TRANSITIVE; 9 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo.Directive.REQUIRES_TRANSITIVE; 10 | 11 | import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo; 12 | import org.junit.jupiter.api.Test; 13 | 14 | class ModuleInfoParseTest { 15 | 16 | @Test 17 | void ignores_single_line_comments() { 18 | var moduleInfo = new ModuleInfo( 19 | """ 20 | module some.thing { 21 | // requires com.bla.blub; 22 | requires transitive foo.bar.la; 23 | }"""); 24 | 25 | assertThat(moduleInfo.moduleNamePrefix("thing", "main", false)).isEqualTo("some"); 26 | assertThat(moduleInfo.get(REQUIRES)).isEmpty(); 27 | assertThat(moduleInfo.get(REQUIRES_TRANSITIVE)).containsExactly("foo.bar.la"); 28 | assertThat(moduleInfo.get(REQUIRES_STATIC)).isEmpty(); 29 | assertThat(moduleInfo.get(REQUIRES_STATIC_TRANSITIVE)).isEmpty(); 30 | assertThat(moduleInfo.get(REQUIRES_RUNTIME)).isEmpty(); 31 | } 32 | 33 | @Test 34 | void ignores_single_line_comments_late_in_line() { 35 | var moduleInfo = new ModuleInfo( 36 | """ 37 | module some.thing { // module some.thing.else 38 | requires transitive foo.bar.la; 39 | }"""); 40 | 41 | assertThat(moduleInfo.moduleNamePrefix("thing", "main", false)).isEqualTo("some"); 42 | assertThat(moduleInfo.get(REQUIRES)).isEmpty(); 43 | assertThat(moduleInfo.get(REQUIRES_TRANSITIVE)).containsExactly("foo.bar.la"); 44 | assertThat(moduleInfo.get(REQUIRES_STATIC)).isEmpty(); 45 | assertThat(moduleInfo.get(REQUIRES_STATIC_TRANSITIVE)).isEmpty(); 46 | assertThat(moduleInfo.get(REQUIRES_RUNTIME)).isEmpty(); 47 | } 48 | 49 | @Test 50 | void ignores_multi_line_comments() { 51 | var moduleInfo = new ModuleInfo( 52 | """ 53 | module some.thing { 54 | /* requires com.bla.blub; 55 | requires transitive foo.bar.la; 56 | */ 57 | requires static foo.bar.la; 58 | } 59 | """); 60 | 61 | assertThat(moduleInfo.get(REQUIRES)).isEmpty(); 62 | assertThat(moduleInfo.get(REQUIRES_TRANSITIVE)).isEmpty(); 63 | assertThat(moduleInfo.get(REQUIRES_STATIC)).containsExactly("foo.bar.la"); 64 | assertThat(moduleInfo.get(REQUIRES_STATIC_TRANSITIVE)).isEmpty(); 65 | assertThat(moduleInfo.get(REQUIRES_RUNTIME)).isEmpty(); 66 | } 67 | 68 | @Test 69 | void ignores_multi_line_comments_between_keywords() { 70 | var moduleInfo = new ModuleInfo( 71 | """ 72 | module some.thing { 73 | /*odd comment*/ requires transitive foo.bar.la; 74 | requires/* weird comment*/ static foo.bar.lo; 75 | requires /*something to say*/foo.bar.li; /* 76 | requires only.a.comment 77 | */ 78 | }"""); 79 | 80 | assertThat(moduleInfo.get(REQUIRES)).containsExactly("foo.bar.li"); 81 | assertThat(moduleInfo.get(REQUIRES_TRANSITIVE)).containsExactly("foo.bar.la"); 82 | assertThat(moduleInfo.get(REQUIRES_STATIC)).containsExactly("foo.bar.lo"); 83 | assertThat(moduleInfo.get(REQUIRES_STATIC_TRANSITIVE)).isEmpty(); 84 | assertThat(moduleInfo.get(REQUIRES_RUNTIME)).isEmpty(); 85 | } 86 | 87 | @Test 88 | void supports_runtime_dependencies_through_special_keyword() { 89 | var moduleInfo = new ModuleInfo( 90 | """ 91 | module some.thing { 92 | requires /*runtime*/ foo.bar.lo; 93 | } 94 | """); 95 | 96 | assertThat(moduleInfo.get(REQUIRES)).isEmpty(); 97 | assertThat(moduleInfo.get(REQUIRES_TRANSITIVE)).isEmpty(); 98 | assertThat(moduleInfo.get(REQUIRES_STATIC)).isEmpty(); 99 | assertThat(moduleInfo.get(REQUIRES_STATIC_TRANSITIVE)).isEmpty(); 100 | assertThat(moduleInfo.get(REQUIRES_RUNTIME)).containsExactly("foo.bar.lo"); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleJar.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.nio.file.Files; 8 | import java.util.jar.JarInputStream; 9 | import java.util.jar.Manifest; 10 | import java.util.regex.Pattern; 11 | import java.util.zip.ZipEntry; 12 | import org.jspecify.annotations.Nullable; 13 | import org.objectweb.asm.ClassReader; 14 | import org.objectweb.asm.ClassVisitor; 15 | import org.objectweb.asm.ModuleVisitor; 16 | import org.objectweb.asm.Opcodes; 17 | 18 | public class ModuleJar { 19 | private static final String AUTOMATIC_MODULE_NAME_ATTRIBUTE = "Automatic-Module-Name"; 20 | private static final String MULTI_RELEASE_ATTRIBUTE = "Multi-Release"; 21 | private static final String MODULE_INFO_CLASS_FILE = "module-info.class"; 22 | private static final Pattern MODULE_INFO_CLASS_MRJAR_PATH = 23 | Pattern.compile("META-INF/versions/\\d+/module-info.class"); 24 | 25 | @Nullable 26 | public static String readModuleNameFromJarFile(File jarFileOrClassFolder) throws IOException { 27 | if (jarFileOrClassFolder.isDirectory()) { 28 | // class folder 29 | File moduleInfo = new File(jarFileOrClassFolder, MODULE_INFO_CLASS_FILE); 30 | if (!moduleInfo.exists()) { 31 | return null; 32 | } 33 | return readNameFromModuleInfoClass(Files.newInputStream(moduleInfo.toPath())); 34 | } 35 | try (JarInputStream jarStream = new JarInputStream(Files.newInputStream(jarFileOrClassFolder.toPath()))) { 36 | String moduleName = getAutomaticModuleName(jarStream.getManifest()); 37 | if (moduleName != null) { 38 | return moduleName; 39 | } 40 | boolean isMultiReleaseJar = containsMultiReleaseJarEntry(jarStream); 41 | ZipEntry next = jarStream.getNextEntry(); 42 | while (next != null) { 43 | if (MODULE_INFO_CLASS_FILE.equals(next.getName())) { 44 | return readNameFromModuleInfoClass(jarStream); 45 | } 46 | if (isMultiReleaseJar 47 | && MODULE_INFO_CLASS_MRJAR_PATH.matcher(next.getName()).matches()) { 48 | return readNameFromModuleInfoClass(jarStream); 49 | } 50 | next = jarStream.getNextEntry(); 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | public static boolean isRealModule(File jarFileOrClassFolder) throws IOException { 57 | if (jarFileOrClassFolder.isDirectory()) { 58 | // class folder 59 | return new File(jarFileOrClassFolder, MODULE_INFO_CLASS_FILE).exists(); 60 | } 61 | try (JarInputStream jarStream = new JarInputStream(Files.newInputStream(jarFileOrClassFolder.toPath()))) { 62 | boolean isMultiReleaseJar = containsMultiReleaseJarEntry(jarStream); 63 | ZipEntry next = jarStream.getNextEntry(); 64 | while (next != null) { 65 | if (MODULE_INFO_CLASS_FILE.equals(next.getName())) { 66 | return true; 67 | } 68 | if (isMultiReleaseJar 69 | && MODULE_INFO_CLASS_MRJAR_PATH.matcher(next.getName()).matches()) { 70 | return true; 71 | } 72 | next = jarStream.getNextEntry(); 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | @Nullable 79 | private static String getAutomaticModuleName(@Nullable Manifest manifest) { 80 | if (manifest == null) { 81 | return null; 82 | } 83 | return manifest.getMainAttributes().getValue(AUTOMATIC_MODULE_NAME_ATTRIBUTE); 84 | } 85 | 86 | private static boolean containsMultiReleaseJarEntry(JarInputStream jarStream) { 87 | Manifest manifest = jarStream.getManifest(); 88 | return manifest != null 89 | && Boolean.parseBoolean(manifest.getMainAttributes().getValue(MULTI_RELEASE_ATTRIBUTE)); 90 | } 91 | 92 | private static String readNameFromModuleInfoClass(InputStream input) throws IOException { 93 | ClassReader classReader = new ClassReader(input); 94 | String[] moduleName = new String[1]; 95 | classReader.accept( 96 | new ClassVisitor(Opcodes.ASM8) { 97 | @Override 98 | public ModuleVisitor visitModule(String name, int access, String version) { 99 | moduleName[0] = name; 100 | return super.visitModule(name, access, version); 101 | } 102 | }, 103 | 0); 104 | return moduleName[0]; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/resources/org/gradlex/javamodule/dependencies/modules.properties: -------------------------------------------------------------------------------- 1 | com.amihaiemil.eoyaml=com.amihaiemil.web:eo-yaml 2 | com.github.dockerjava.api=com.github.docker-java:docker-java-api 3 | com.github.dockerjava.core=com.github.docker-java:docker-java-core 4 | com.github.dockerjava.transport.zerodep=com.github.docker-java:docker-java-transport-zerodep 5 | com.github.spotbugs.annotations=com.github.spotbugs:spotbugs-annotations 6 | com.github.virtuald.curvesapi=com.github.virtuald:curvesapi 7 | com.google.common.util.concurrent.internal=com.google.guava:failureaccess 8 | com.google.common=com.google.guava:guava 9 | com.google.gson=com.google.code.gson:gson 10 | com.google.guice.extensions.servlet=com.google.inject.extensions:guice-servlet 11 | com.google.guice=com.google.inject:guice 12 | com.google.protobuf.util=com.google.protobuf:protobuf-java-util 13 | com.google.protobuf=com.google.protobuf:protobuf-java 14 | com.google.zxing.javase=com.google.zxing:javase 15 | com.google.zxing=com.google.zxing:core 16 | com.microsoft.sqlserver.jdbc=com.microsoft.sqlserver:mssql-jdbc 17 | com.sun.jna.platform=net.java.dev.jna:jna-platform 18 | com.sun.jna=net.java.dev.jna:jna 19 | com.sun.tools.xjc=com.sun.xml.bind:jaxb-xjc 20 | io.grpc=io.helidon.grpc:io.grpc 21 | io.netty.buffer=io.netty:netty-buffer 22 | io.netty.codec.http2=io.netty:netty-codec-http2 23 | io.netty.codec.http=io.netty:netty-codec-http 24 | io.netty.common=io.netty:netty-common 25 | io.netty.handler.proxy=io.netty:netty-handler-proxy 26 | io.netty.handler=io.netty:netty-handler 27 | io.netty.resolver.dns=io.netty:netty-resolver-dns 28 | io.netty.resolver=io.netty:netty-resolver 29 | io.netty.transport.epoll.linux.aarch_64=io.netty:netty-transport-native-epoll|linux-aarch_64 30 | io.netty.transport.epoll.linux.x86_64=io.netty:netty-transport-native-epoll|linux-x86_64 31 | io.netty.transport.epoll=io.netty:netty-transport-native-epoll 32 | io.netty.transport=io.netty:netty-transport 33 | io.swagger.v3.oas.annotations=io.swagger.core.v3:swagger-annotations 34 | io.swagger.v3.oas.models=io.swagger.core.v3:swagger-models 35 | jakarta.messaging=jakarta.jms:jakarta.jms-api 36 | java.annotation=javax.annotation:javax.annotation-api 37 | java.inject=jakarta.inject:jakarta.inject-api 38 | java.validation=jakarta.validation:jakarta.validation-api 39 | javafx.base=org.openjfx:javafx-base 40 | javafx.controls=org.openjfx:javafx-controls 41 | javafx.fxml=org.openjfx:javafx-fxml 42 | javafx.graphics=org.openjfx:javafx-graphics 43 | javafx.media=org.openjfx:javafx-media 44 | javafx.swing=org.openjfx:javafx-swing 45 | javafx.web=org.openjfx:javafx-web 46 | jetty.servlet.api=org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api 47 | jetty.websocket.api=org.eclipse.jetty.toolchain:jetty-jakarta-websocket-api 48 | junit=junit:junit 49 | liquibase.core=org.liquibase:liquibase-core 50 | org.antlr.antlr4.runtime=org.antlr:antlr4-runtime 51 | org.apache.commons.codec=commons-codec:commons-codec 52 | org.apache.commons.io=commons-io:commons-io 53 | org.apache.commons.logging=commons-logging:commons-logging 54 | org.apache.httpcomponents.httpclient.fluent=org.apache.httpcomponents:fluent-hc 55 | org.apache.httpcomponents.httpclient=org.apache.httpcomponents:httpclient 56 | org.apache.httpcomponents.httpcore=org.apache.httpcomponents:httpcore 57 | org.apache.httpcomponents.httpmime=org.apache.httpcomponents:httpmime 58 | org.apache.logging.log4j.slf4j=org.apache.logging.log4j:log4j-slf4j2-impl 59 | org.apache.pdfbox.tools=org.apache.pdfbox:pdfbox-tools 60 | org.apache.pdfbox=org.apache.pdfbox:pdfbox 61 | org.checkerframework.dataflow.nullaway=org.checkerframework:dataflow-nullaway 62 | org.codehaus.stax2=org.codehaus.woodstox:stax2-api 63 | org.eclipse.jdt.annotation=org.eclipse.jdt:org.eclipse.jdt.annotation 64 | org.eclipse.jetty.websocket.javax.websocket.server=org.eclipse.jetty.websocket:javax-websocket-server-impl 65 | org.hamcrest=org.hamcrest:hamcrest 66 | org.hibernate.orm.agroal=org.hibernate.orm:hibernate-agroal 67 | org.hibernate.orm.ant=org.hibernate.orm:hibernate-ant 68 | org.hibernate.orm.c3p0=org.hibernate.orm:hibernate-c3p0 69 | org.hibernate.orm.core=org.hibernate.orm:hibernate-core 70 | org.hibernate.orm.envers=org.hibernate.orm:hibernate-envers 71 | org.hibernate.orm.graalvm=org.hibernate.orm:hibernate-graalvm 72 | org.hibernate.orm.hikaricp=org.hibernate.orm:hibernate-hikaricp 73 | org.hibernate.orm.jcache=org.hibernate.orm:hibernate-jcache 74 | org.hibernate.orm.jpamodelgen=org.hibernate.orm:hibernate-jpamodelgen 75 | org.hibernate.orm.micrometer=org.hibernate.orm:hibernate-micrometer 76 | org.hibernate.orm.proxool=org.hibernate.orm:hibernate-proxool 77 | org.hibernate.orm.spatial=org.hibernate.orm:hibernate-spatial 78 | org.hibernate.orm.testing=org.hibernate.orm:hibernate-testing 79 | org.hibernate.orm.vibur=org.hibernate.orm:hibernate-vibur 80 | org.jose4j=org.bitbucket.b_c:jose4j 81 | org.keycloak.authz.client=org.keycloak:keycloak-authz-client 82 | org.keycloak.common=org.keycloak:keycloak-common 83 | org.keycloak.core=org.keycloak:keycloak-core 84 | org.objenesis=org.objenesis:objenesis 85 | org.postgresql.jdbc=org.postgresql:postgresql 86 | org.reactivestreams=org.reactivestreams:reactive-streams 87 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/diagnostics/RenderableModuleDependencyResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.diagnostics; 3 | 4 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleJar.isRealModule; 5 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleJar.readModuleNameFromJarFile; 6 | 7 | import java.io.IOException; 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | import org.gradle.api.artifacts.component.ComponentIdentifier; 11 | import org.gradle.api.artifacts.component.ComponentSelector; 12 | import org.gradle.api.artifacts.component.ModuleComponentIdentifier; 13 | import org.gradle.api.artifacts.component.ModuleComponentSelector; 14 | import org.gradle.api.artifacts.result.DependencyResult; 15 | import org.gradle.api.artifacts.result.ResolvedArtifactResult; 16 | import org.gradle.api.artifacts.result.ResolvedDependencyResult; 17 | import org.gradle.api.artifacts.result.UnresolvedDependencyResult; 18 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency; 19 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependencyResult; 20 | import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableUnresolvedDependencyResult; 21 | 22 | public class RenderableModuleDependencyResult extends RenderableDependencyResult { 23 | private final ResolvedDependencyResult dependency; 24 | private final Set resolvedJars; 25 | 26 | public RenderableModuleDependencyResult( 27 | ResolvedDependencyResult dependency, Set resolvedJars) { 28 | super(dependency); 29 | this.dependency = dependency; 30 | this.resolvedJars = resolvedJars; 31 | } 32 | 33 | @Override 34 | public Set getChildren() { 35 | Set out = new LinkedHashSet<>(); 36 | for (DependencyResult d : dependency.getSelected().getDependencies()) { 37 | if (d instanceof UnresolvedDependencyResult) { 38 | out.add(new RenderableUnresolvedDependencyResult((UnresolvedDependencyResult) d)); 39 | } else { 40 | ResolvedDependencyResult resolved = (ResolvedDependencyResult) d; 41 | resolvedJars.stream() 42 | .filter(a -> a.getId() 43 | .getComponentIdentifier() 44 | .equals(resolved.getSelected().getId())) 45 | .findFirst() 46 | .ifPresent(artifact -> out.add(new RenderableModuleDependencyResult(resolved, resolvedJars))); 47 | } 48 | } 49 | return out; 50 | } 51 | 52 | @Override 53 | public String getName() { 54 | ComponentSelector requested = getRequested(); 55 | ComponentIdentifier selected = getActual(); 56 | ResolvedArtifactResult artifact = resolvedJars.stream() 57 | .filter(a -> a.getId().getComponentIdentifier().equals(selected)) 58 | .findFirst() 59 | .orElse(null); 60 | 61 | try { 62 | if (artifact == null) { 63 | return "[BOM] " + selected.getDisplayName(); 64 | } else { 65 | String actualModuleName = readModuleNameFromJarFile(artifact.getFile()); 66 | if (actualModuleName == null) { 67 | return "[CLASSPATH] " + selected.getDisplayName(); 68 | } else { 69 | String version = ""; 70 | String coordinates = selected.getDisplayName(); 71 | String jarName = artifact.getFile().getName(); 72 | if (selected instanceof ModuleComponentIdentifier) { 73 | String selectedVersion = ((ModuleComponentIdentifier) selected).getVersion(); 74 | version = " (" + selectedVersion + ")"; 75 | if (requested instanceof ModuleComponentSelector) { 76 | String requestedVersion = ((ModuleComponentSelector) requested).getVersion(); 77 | if (!requestedVersion.isEmpty() && !selectedVersion.equals(requestedVersion)) { 78 | version = " (" + requestedVersion + " -> " + selectedVersion + ")"; 79 | } 80 | } 81 | coordinates = ((ModuleComponentIdentifier) selected) 82 | .getModuleIdentifier() 83 | .toString(); 84 | } 85 | String auto = isRealModule(artifact.getFile()) ? "" : "[AUTO] "; 86 | return auto + actualModuleName + version + " | " + coordinates 87 | + (isConstraint() ? "" : " | " + jarName); 88 | } 89 | } 90 | } catch (IOException e) { 91 | throw new RuntimeException(e); 92 | } 93 | } 94 | 95 | private boolean isConstraint() { 96 | return getResolutionState() == ResolutionState.RESOLVED_CONSTRAINT; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleNamingUtil.sourceSetToCapabilitySuffix; 5 | 6 | import java.io.File; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import javax.inject.Inject; 13 | import org.gradle.api.logging.Logger; 14 | import org.gradle.api.provider.Provider; 15 | import org.gradle.api.provider.ProviderFactory; 16 | import org.gradle.api.tasks.SourceSet; 17 | import org.gradlex.javamodule.dependencies.LocalModule; 18 | import org.jspecify.annotations.Nullable; 19 | import org.slf4j.LoggerFactory; 20 | 21 | public abstract class ModuleInfoCache { 22 | private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(ModuleInfoCache.class); 23 | 24 | private final boolean initializedInSettings; 25 | private final Map moduleInfo = new HashMap<>(); 26 | private final Map localModules = new HashMap<>(); 27 | 28 | @Inject 29 | public ModuleInfoCache(boolean initializedInSettings) { 30 | this.initializedInSettings = initializedInSettings; 31 | } 32 | 33 | public boolean isInitializedInSettings() { 34 | return initializedInSettings; 35 | } 36 | 37 | /** 38 | * Returns the module-info.java for the given SourceSet. If the SourceSet has multiple source folders with multiple 39 | * module-info files (which is usually a broken setup) the first file found is returned. 40 | * 41 | * @param sourceSet the SourceSet representing a module 42 | * @return parsed module-info.java for the given SourceSet 43 | */ 44 | public ModuleInfo get(SourceSet sourceSet, ProviderFactory providers) { 45 | for (File folder : sourceSet.getJava().getSrcDirs()) { 46 | if (maybePutModuleInfo(folder, providers)) { 47 | return moduleInfo.get(folder); 48 | } 49 | } 50 | return ModuleInfo.EMPTY; 51 | } 52 | 53 | public @Nullable File getFolder(SourceSet sourceSet, ProviderFactory providers) { 54 | for (File folder : sourceSet.getJava().getSrcDirs()) { 55 | if (maybePutModuleInfo(folder, providers)) { 56 | return folder; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | /** 63 | * @param projectRoot the project that should hold a Java module 64 | * @return parsed module-info.java for the given project assuming a standard Java project layout 65 | */ 66 | public ModuleInfo put( 67 | File projectRoot, 68 | String moduleInfoPath, 69 | String projectPath, 70 | String artifact, 71 | Provider group, 72 | ProviderFactory providers) { 73 | File folder = new File(projectRoot, moduleInfoPath); 74 | if (maybePutModuleInfo(folder, providers)) { 75 | ModuleInfo thisModuleInfo = moduleInfo.get(folder); 76 | String moduleName = thisModuleInfo.getModuleName(); 77 | String capability = null; 78 | Path parentDirectory = Paths.get(moduleInfoPath).getParent(); 79 | String capabilitySuffix = parentDirectory == null 80 | ? null 81 | : sourceSetToCapabilitySuffix(parentDirectory.getFileName().toString()); 82 | if (capabilitySuffix != null) { 83 | if (group.isPresent()) { 84 | capability = group.get() + ":" + artifact + "-" + capabilitySuffix; 85 | } else { 86 | LOGGER.lifecycle("[WARN] [Java Module Dependencies] " + moduleName + " - 'group' not defined!"); 87 | } 88 | } 89 | localModules.put(moduleName, new LocalModule(moduleName, projectPath, capability)); 90 | return thisModuleInfo; 91 | } 92 | return ModuleInfo.EMPTY; 93 | } 94 | 95 | public @Nullable LocalModule getLocalModule(String moduleName) { 96 | return localModules.get(moduleName); 97 | } 98 | 99 | public Collection getAllLocalModules() { 100 | return localModules.values(); 101 | } 102 | 103 | private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { 104 | Provider moduleInfoProvider = provideModuleInfo(folder, providers); 105 | if (moduleInfoProvider.isPresent()) { 106 | if (!moduleInfo.containsKey(folder)) { 107 | moduleInfo.put(folder, moduleInfoProvider.get()); 108 | } 109 | return true; 110 | } 111 | return false; 112 | } 113 | 114 | private Provider provideModuleInfo(File folder, ProviderFactory providers) { 115 | return providers.of( 116 | ValueSourceModuleInfo.class, 117 | spec -> spec.parameters(param -> param.getDir().set(folder))); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginIncludeTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.initialization; 3 | 4 | import static java.util.Objects.requireNonNull; 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; 7 | 8 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Tag; 11 | import org.junit.jupiter.api.Test; 12 | 13 | @Tag("no-cross-version") 14 | class SettingsPluginIncludeTest { 15 | 16 | GradleBuild build = new GradleBuild(); 17 | 18 | @BeforeEach 19 | void setup() { 20 | build.settingsFile.writeText("plugins { id(\"org.gradlex.java-module-dependencies\") }"); 21 | build.appBuildFile.delete(); 22 | build.libBuildFile.delete(); 23 | } 24 | 25 | @Test 26 | void can_define_included_subprojects_as_modules() { 27 | build.settingsFile.appendText( 28 | """ 29 | include(":project:with:custom:path") 30 | javaModules { 31 | module(project(":project:with:custom:path")) { 32 | group = "org.example" 33 | plugin("java-library") 34 | } 35 | module(project(":project:with:custom")) { 36 | group = "org.example" 37 | plugin("java-library") 38 | } 39 | }"""); 40 | 41 | build.file("project/with/custom/path/src/main/java/module-info.java").writeText("module abc.liba { }"); 42 | build.file("project/with/custom/src/main/java/module-info.java") 43 | .writeText(""" 44 | module abc.libb { 45 | requires abc.liba; 46 | }"""); 47 | 48 | var result = build.runner(":project:with:custom:compileJava").build(); 49 | 50 | assertThat(requireNonNull(result.task(":project:with:custom:path:compileJava")) 51 | .getOutcome()) 52 | .isEqualTo(SUCCESS); 53 | assertThat(requireNonNull(result.task(":project:with:custom:compileJava")) 54 | .getOutcome()) 55 | .isEqualTo(SUCCESS); 56 | } 57 | 58 | @Test 59 | void can_define_included_subprojects_with_custom_project_directory_as_modules() { 60 | build.projectDir.dir("project/with/custom/path"); 61 | build.settingsFile.appendText( 62 | """ 63 | include(":project:with:custom:path") 64 | project(":project:with:custom:path").projectDir = file("lib") 65 | project(":project:with:custom").projectDir = file("app") 66 | javaModules { 67 | module(project(":project:with:custom:path")) { 68 | group = "org.example" 69 | plugin("java-library") 70 | } 71 | module(project(":project:with:custom")) { 72 | group = "org.example" 73 | plugin("java-library") 74 | } 75 | }"""); 76 | 77 | build.libModuleInfoFile.writeText("module abc.lib { }"); 78 | build.appModuleInfoFile.writeText( 79 | """ 80 | module abc.app { 81 | requires abc.lib; 82 | }"""); 83 | 84 | var result = build.runner(":project:with:custom:jar").build(); 85 | 86 | assertThat(requireNonNull(result.task(":project:with:custom:path:compileJava")) 87 | .getOutcome()) 88 | .isEqualTo(SUCCESS); 89 | assertThat(requireNonNull(result.task(":project:with:custom:compileJava")) 90 | .getOutcome()) 91 | .isEqualTo(SUCCESS); 92 | assertThat(build.file("lib/build/libs/path.jar").getAsPath()).exists(); 93 | assertThat(build.file("app/build/libs/custom.jar").getAsPath()).exists(); 94 | } 95 | 96 | @Test 97 | void projects_with_same_name_but_different_paths_are_supported() { 98 | build.settingsFile.appendText( 99 | """ 100 | include(":app1:feature1:data") 101 | include(":app1:feature2:data") 102 | 103 | rootProject.children.forEach { appContainer -> 104 | appContainer.children.forEach { featureContainer -> 105 | featureContainer.children.forEach { module -> 106 | javaModules.module(module) { plugin("java-library") } 107 | } 108 | } 109 | }"""); 110 | 111 | build.file("app1/feature1/data/src/main/java/module-info.java").writeText("module f1x.data { }"); 112 | build.file("app1/feature2/data/src/main/java/module-info.java") 113 | .writeText(""" 114 | module f2x.data { 115 | requires f1x.data; 116 | }"""); 117 | 118 | var result = build.runner(":app1:feature2:data:jar").build(); 119 | 120 | assertThat(requireNonNull(result.task(":app1:feature1:data:jar")).getOutcome()) 121 | .isEqualTo(SUCCESS); 122 | assertThat(requireNonNull(result.task(":app1:feature2:data:jar")).getOutcome()) 123 | .isEqualTo(SUCCESS); 124 | assertThat(build.file("app1/feature1/data/build/libs/data.jar").getAsPath()) 125 | .exists(); 126 | assertThat(build.file("app1/feature2/data/build/libs/data.jar").getAsPath()) 127 | .exists(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfo.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.internal.utils; 3 | 4 | import static org.gradlex.javamodule.dependencies.internal.utils.ModuleNamingUtil.sourceSetToModuleName; 5 | 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import org.jspecify.annotations.Nullable; 13 | 14 | public class ModuleInfo implements Serializable { 15 | 16 | public enum Directive { 17 | REQUIRES, 18 | REQUIRES_TRANSITIVE, 19 | REQUIRES_STATIC, 20 | REQUIRES_STATIC_TRANSITIVE, 21 | REQUIRES_RUNTIME; 22 | 23 | public String literal() { 24 | return toString().toLowerCase().replace("_", " ").replace("runtime", RUNTIME_KEYWORD); 25 | } 26 | } 27 | 28 | public static final String RUNTIME_KEYWORD = "/*runtime*/"; 29 | 30 | public static final ModuleInfo EMPTY = new ModuleInfo(""); 31 | 32 | private String moduleName = ""; 33 | private final List requires = new ArrayList<>(); 34 | private final List requiresTransitive = new ArrayList<>(); 35 | private final List requiresStatic = new ArrayList<>(); 36 | private final List requiresStaticTransitive = new ArrayList<>(); 37 | private final List requiresRuntime = new ArrayList<>(); 38 | 39 | public ModuleInfo(String moduleInfoFileContent) { 40 | boolean insideComment = false; 41 | for (String line : moduleInfoFileContent.split("\n")) { 42 | insideComment = parse(line, insideComment); 43 | } 44 | } 45 | 46 | public String getModuleName() { 47 | return moduleName; 48 | } 49 | 50 | public List get(Directive directive) { 51 | if (directive == Directive.REQUIRES) { 52 | return requires; 53 | } 54 | if (directive == Directive.REQUIRES_TRANSITIVE) { 55 | return requiresTransitive; 56 | } 57 | if (directive == Directive.REQUIRES_STATIC) { 58 | return requiresStatic; 59 | } 60 | if (directive == Directive.REQUIRES_STATIC_TRANSITIVE) { 61 | return requiresStaticTransitive; 62 | } 63 | if (directive == Directive.REQUIRES_RUNTIME) { 64 | return requiresRuntime; 65 | } 66 | return Collections.emptyList(); 67 | } 68 | 69 | @Nullable 70 | public String moduleNamePrefix(String projectName, String sourceSetName, boolean fail) { 71 | if (moduleName.equals(projectName)) { 72 | return ""; 73 | } 74 | 75 | String projectPlusSourceSetName = sourceSetToModuleName(projectName, sourceSetName); 76 | if (moduleName.endsWith("." + projectPlusSourceSetName)) { 77 | return moduleName.substring(0, moduleName.length() - projectPlusSourceSetName.length() - 1); 78 | } 79 | if (moduleName.equals(projectPlusSourceSetName)) { 80 | return ""; 81 | } 82 | if (moduleName.endsWith("." + projectName)) { 83 | return moduleName.substring(0, moduleName.length() - projectName.length() - 1); 84 | } 85 | if (this != EMPTY && fail) { 86 | throw new RuntimeException( 87 | "Module name '" + moduleName + "' does not fit the project and source set names; " 88 | + "expected name '" + projectPlusSourceSetName + "'."); 89 | } 90 | return null; 91 | } 92 | 93 | /** 94 | * @return true, if we are inside a multi-line comment after this line 95 | */ 96 | private boolean parse(String moduleLine, boolean insideComment) { 97 | if (insideComment) { 98 | return !moduleLine.contains("*/"); 99 | } 100 | 101 | List tokens = Arrays.asList(moduleLine 102 | .replace(";", "") 103 | .replace("{", "") 104 | .replace("}", "") 105 | .replace(RUNTIME_KEYWORD, "runtime") 106 | .replaceAll("/\\*.*?\\*/", " ") 107 | .trim() 108 | .split("\\s+")); 109 | int singleLineCommentStartIndex = tokens.indexOf("//"); 110 | if (singleLineCommentStartIndex >= 0) { 111 | tokens = tokens.subList(0, singleLineCommentStartIndex); 112 | } 113 | 114 | if (tokens.contains("module")) { 115 | moduleName = tokens.get(tokens.size() - 1); 116 | } 117 | if (tokens.size() > 1 && tokens.get(0).equals("requires")) { 118 | if (tokens.size() > 3 && tokens.contains("static") && tokens.contains("transitive")) { 119 | requiresStaticTransitive.add(tokens.get(3)); 120 | } else if (tokens.size() > 2 && tokens.contains("transitive")) { 121 | requiresTransitive.add(tokens.get(2)); 122 | } else if (tokens.size() > 2 && tokens.contains("static")) { 123 | requiresStatic.add(tokens.get(2)); 124 | } else if (tokens.size() > 2 && tokens.contains("runtime")) { 125 | requiresRuntime.add(tokens.get(2)); 126 | } else { 127 | requires.add(tokens.get(1)); 128 | } 129 | } 130 | return moduleLine.lastIndexOf("/*") > moduleLine.lastIndexOf("*/"); 131 | } 132 | 133 | @Override 134 | public boolean equals(Object o) { 135 | if (this == o) return true; 136 | if (o == null || getClass() != o.getClass()) return false; 137 | ModuleInfo that = (ModuleInfo) o; 138 | return Objects.equals(moduleName, that.moduleName) 139 | && Objects.equals(requires, that.requires) 140 | && Objects.equals(requiresTransitive, that.requiresTransitive) 141 | && Objects.equals(requiresStatic, that.requiresStatic) 142 | && Objects.equals(requiresStaticTransitive, that.requiresStaticTransitive) 143 | && Objects.equals(requiresRuntime, that.requiresRuntime); 144 | } 145 | 146 | @Override 147 | public int hashCode() { 148 | return Objects.hash( 149 | moduleName, requires, requiresTransitive, requiresStatic, requiresStaticTransitive, requiresRuntime); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/fixture/GradleBuild.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test.fixture; 3 | 4 | import static java.util.function.Function.identity; 5 | 6 | import java.io.IOException; 7 | import java.lang.management.ManagementFactory; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.Stream; 14 | import org.gradle.testkit.runner.BuildResult; 15 | import org.gradle.testkit.runner.GradleRunner; 16 | 17 | public class GradleBuild { 18 | 19 | private final boolean withHelpTasks; 20 | 21 | public final Directory projectDir; 22 | public final WritableFile settingsFile; 23 | public final WritableFile appBuildFile; 24 | public final WritableFile libBuildFile; 25 | public final WritableFile appModuleInfoFile; 26 | public final WritableFile libModuleInfoFile; 27 | 28 | public static final String GRADLE_VERSION_UNDER_TEST = System.getProperty("gradleVersionUnderTest"); 29 | 30 | public GradleBuild() { 31 | this(false, createBuildTmpDir()); 32 | } 33 | 34 | public GradleBuild(boolean withHelpTasks) { 35 | this(withHelpTasks, createBuildTmpDir()); 36 | } 37 | 38 | public GradleBuild(boolean withHelpTasks, Path dir) { 39 | this.withHelpTasks = withHelpTasks; 40 | this.projectDir = new Directory(dir); 41 | this.settingsFile = new WritableFile(projectDir, "settings.gradle.kts"); 42 | this.appBuildFile = new WritableFile(projectDir.dir("app"), "build.gradle.kts"); 43 | this.libBuildFile = new WritableFile(projectDir.dir("lib"), "build.gradle.kts"); 44 | this.appModuleInfoFile = new WritableFile(projectDir.dir("app/src/main/java"), "module-info.java"); 45 | this.libModuleInfoFile = new WritableFile(projectDir.dir("lib/src/main/java"), "module-info.java"); 46 | 47 | settingsFile.writeText( 48 | """ 49 | dependencyResolutionManagement { repositories.mavenCentral() } 50 | rootProject.name = "test-project" 51 | include("lib", "app") 52 | includeBuild(".") 53 | """); 54 | appBuildFile.writeText( 55 | """ 56 | plugins { 57 | id("org.gradlex.java-module-dependencies") 58 | id("org.gradlex.java-module-versions") 59 | id("application") 60 | } 61 | dependencies { 62 | implementation(platform(project(":app")) as ModuleDependency) { 63 | capabilities { requireCapability("${project.group}:app-platform") } 64 | } 65 | } 66 | application { 67 | mainModule.set("org.gradlex.test.app") 68 | mainClass.set("org.gradlex.test.app.Main") 69 | } 70 | tasks.register("printRuntimeJars") { 71 | inputs.files(configurations.runtimeClasspath) 72 | doLast { println(inputs.files.map { it.name }) } 73 | } 74 | tasks.register("printCompileJars") { 75 | inputs.files(configurations.compileClasspath) 76 | doLast { println(inputs.files.map { it.name }) } 77 | } 78 | """); 79 | libBuildFile.writeText( 80 | """ 81 | plugins { 82 | id("org.gradlex.java-module-dependencies") 83 | id("java-library") 84 | id("java-test-fixtures") 85 | } 86 | """); 87 | } 88 | 89 | public WritableFile file(String path) { 90 | return new WritableFile(projectDir, path); 91 | } 92 | 93 | public BuildResult build() { 94 | return runner("build").build(); 95 | } 96 | 97 | public BuildResult run() { 98 | return runner("run").build(); 99 | } 100 | 101 | public BuildResult printRuntimeJars() { 102 | return runner(":app:printRuntimeJars", "-q").build(); 103 | } 104 | 105 | public BuildResult printCompileJars() { 106 | return runner(":app:printCompileJars", "-q").build(); 107 | } 108 | 109 | public BuildResult fail() { 110 | return runner("build").buildAndFail(); 111 | } 112 | 113 | public GradleRunner runner(String... args) { 114 | return runner(true, args); 115 | } 116 | 117 | public GradleRunner runner(boolean projectIsolation, String... args) { 118 | boolean debugMode = ManagementFactory.getRuntimeMXBean() 119 | .getInputArguments() 120 | .toString() 121 | .contains("-agentlib:jdwp"); 122 | List latestFeaturesArgs = GRADLE_VERSION_UNDER_TEST != null || !projectIsolation 123 | ? List.of() 124 | : List.of( 125 | "--configuration-cache", 126 | "-Dorg.gradle.unsafe.isolated-projects=true", 127 | // "getGroup" in "JavaModuleDependenciesExtension.create" 128 | "--configuration-cache-problems=warn", 129 | "-Dorg.gradle.configuration-cache.max-problems=3"); 130 | Stream standardArgs = Stream.of( 131 | "-s", 132 | "--warning-mode=fail", 133 | "-Porg.gradlex.java-module-dependencies.register-help-tasks=" + withHelpTasks); 134 | GradleRunner runner = GradleRunner.create() 135 | .forwardOutput() 136 | .withPluginClasspath() 137 | .withDebug(debugMode) 138 | .withProjectDir(projectDir.getAsPath().toFile()) 139 | .withArguments(Stream.of(Arrays.stream(args), latestFeaturesArgs.stream(), standardArgs) 140 | .flatMap(identity()) 141 | .collect(Collectors.toList())); 142 | if (GRADLE_VERSION_UNDER_TEST != null) { 143 | runner.withGradleVersion(GRADLE_VERSION_UNDER_TEST); 144 | } 145 | return runner; 146 | } 147 | 148 | private static Path createBuildTmpDir() { 149 | try { 150 | return Files.createTempDirectory("gradle-build"); 151 | } catch (IOException e) { 152 | throw new RuntimeException(e); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/dependencies/test/CustomizationTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | package org.gradlex.javamodule.dependencies.test; 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class CustomizationTest { 10 | 11 | GradleBuild build = new GradleBuild(); 12 | 13 | @Test 14 | void can_add_custom_mapping() { 15 | build.appBuildFile.appendText( 16 | """ 17 | javaModuleDependencies { 18 | // Override because there are multiple alternatives 19 | moduleNameToGA.put("jakarta.mail", "com.sun.mail:jakarta.mail") 20 | } 21 | dependencies.constraints { 22 | javaModuleDependencies { 23 | implementation(gav("jakarta.mail", "2.0.1")) 24 | } 25 | }"""); 26 | build.appModuleInfoFile.appendText( 27 | """ 28 | module org.gradlex.test.app { 29 | requires jakarta.mail; 30 | }"""); 31 | 32 | var result = build.printRuntimeJars(); 33 | 34 | assertThat(result.getOutput()).contains("[jakarta.mail-2.0.1.jar, jakarta.activation-2.0.1.jar]"); 35 | } 36 | 37 | @Test 38 | void can_add_custom_mapping_via_properties_file_in_default_location() { 39 | var customModulesPropertiesFile = build.file("gradle/modules.properties"); 40 | 41 | customModulesPropertiesFile.writeText("jakarta.mail=com.sun.mail:jakarta.mail"); 42 | build.appBuildFile.appendText("moduleInfo { version(\"jakarta.mail\", \"2.0.1\") }"); 43 | 44 | build.appModuleInfoFile.writeText( 45 | """ 46 | module org.gradlex.test.app { 47 | requires jakarta.mail; 48 | }"""); 49 | var result = build.printRuntimeJars(); 50 | 51 | assertThat(result.getOutput()).contains("[jakarta.mail-2.0.1.jar, jakarta.activation-2.0.1.jar]"); 52 | } 53 | 54 | @Test 55 | void can_add_custom_mapping_via_properties_file_in_custom_location() { 56 | var customModulesPropertiesFile = build.file(".hidden/modules.properties"); 57 | 58 | customModulesPropertiesFile.writeText("jakarta.mail=com.sun.mail:jakarta.mail"); 59 | build.appBuildFile.appendText( 60 | """ 61 | moduleInfo { version("jakarta.mail", "2.0.1") } 62 | javaModuleDependencies { 63 | modulesProperties.set(File(rootDir,".hidden/modules.properties")) 64 | }"""); 65 | 66 | build.appModuleInfoFile.writeText( 67 | """ 68 | module org.gradlex.test.app { 69 | requires jakarta.mail; 70 | }"""); 71 | 72 | var result = build.printRuntimeJars(); 73 | 74 | assertThat(result.getOutput()).contains("[jakarta.mail-2.0.1.jar, jakarta.activation-2.0.1.jar]"); 75 | } 76 | 77 | @Test 78 | void can_use_custom_catalog() { 79 | build.settingsFile.appendText( 80 | """ 81 | dependencyResolutionManagement.versionCatalogs.create("moduleLibs") { 82 | version("org.apache.xmlbeans", "5.0.1") 83 | version("com.fasterxml.jackson.databind", "2.12.5") 84 | }"""); 85 | build.appBuildFile.appendText(""" 86 | javaModuleDependencies.versionCatalogName.set("moduleLibs")"""); 87 | build.appModuleInfoFile.writeText( 88 | """ 89 | module org.gradlex.test.app { 90 | requires com.fasterxml.jackson.databind; 91 | requires static org.apache.xmlbeans; 92 | } 93 | """); 94 | 95 | var runtime = build.printRuntimeJars(); 96 | assertThat(runtime.getOutput()) 97 | .contains("[jackson-annotations-2.12.5.jar, jackson-core-2.12.5.jar, jackson-databind-2.12.5.jar]"); 98 | 99 | var compile = build.printCompileJars(); 100 | assertThat(compile.getOutput()) 101 | .contains( 102 | "[xmlbeans-5.0.1.jar, jackson-annotations-2.12.5.jar, jackson-core-2.12.5.jar, jackson-databind-2.12.5.jar, log4j-api-2.14.0.jar]"); 103 | } 104 | 105 | @Test 106 | void can_define_versions_in_platform_with_different_notations() { 107 | var customModulesPropertiesFile = build.file("gradle/modules.properties"); 108 | 109 | customModulesPropertiesFile.writeText("jakarta.mail=com.sun.mail:jakarta.mail"); 110 | build.appBuildFile.appendText( 111 | """ 112 | moduleInfo { 113 | version("jakarta.mail", "2.0.1") 114 | version("jakarta.servlet", "6.0.0") { reject("[7.0.0,)") } 115 | version("java.inject") { require("1.0.5"); reject("[2.0.0,)") } 116 | }"""); 117 | 118 | build.appModuleInfoFile.writeText( 119 | """ 120 | module org.gradlex.test.app { 121 | requires jakarta.mail; 122 | requires jakarta.servlet; 123 | requires java.inject; 124 | }"""); 125 | 126 | var result = build.printRuntimeJars(); 127 | assertThat(result.getOutput()) 128 | .contains( 129 | "[jakarta.mail-2.0.1.jar, jakarta.servlet-api-6.0.0.jar, jakarta.inject-api-1.0.5.jar, jakarta.activation-2.0.1.jar]"); 130 | } 131 | 132 | @Test 133 | void can_use_toml_catalog_with_underscore_for_dot() { 134 | build.file("gradle/libs.versions.toml") 135 | .writeText(""" 136 | [versions] 137 | org_apache_xmlbeans = "5.0.1" 138 | """); 139 | build.appModuleInfoFile.appendText( 140 | """ 141 | module org.gradlex.test.app { 142 | requires org.apache.xmlbeans; 143 | }"""); 144 | 145 | var result = build.build(); 146 | 147 | assertThat(result.getOutput()).doesNotContain("[WARN]"); 148 | } 149 | 150 | @Test 151 | void can_use_toml_catalog_with_dash_for_dot() { 152 | build.file("gradle/libs.versions.toml") 153 | .writeText(""" 154 | [versions] 155 | org-apache-xmlbeans = "5.0.1" 156 | """); 157 | build.appModuleInfoFile.appendText( 158 | """ 159 | module org.gradlex.test.app { 160 | requires org.apache.xmlbeans; 161 | }"""); 162 | 163 | var result = build.build(); 164 | 165 | assertThat(result.getOutput()).doesNotContain("[WARN]"); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Java Module Dependencies Gradle Plugin - Changelog 2 | 3 | ## Version 1.11 4 | * [#245](https://github.com/gradlex-org/java-module-dependencies/issues/245) Add 'allLocalModules' access to extension 5 | * [#245](https://github.com/gradlex-org/java-module-dependencies/issues/247) Defining a 'versions' project in settings supports applying additional plugins 6 | * Update module name mappings 7 | 8 | ## Version 1.10 9 | * [#235](https://github.com/gradlex-org/java-module-dependencies/issues/235) Upgrade to Gradle 9.1, address deprecation 10 | * [#221](https://github.com/gradlex-org/java-module-dependencies/issues/221) Upgrade to Gradle 9, remove deprecated features 11 | * [#209](https://github.com/gradlex-org/java-module-dependencies/issues/209) Fix: configuration cache issue when building kotlin-dsl plugins 12 | * Update module name mappings 13 | 14 | ## Version 1.9.2 15 | * Update module name mappings 16 | 17 | ## Version 1.9.1 18 | * Never attempt to create dependency for JDK core module 19 | * Update and fix ([#199](https://github.com/gradlex-org/java-module-dependencies/pull/199)) module name mappings 20 | 21 | ## Version 1.9 22 | * [#188](https://github.com/gradlex-org/java-module-dependencies/pull/188) Add `exportsTo` and `opensTo` statements to Module Info DSL 23 | 24 | ## Version 1.8.1 25 | * Update module name mappings 26 | * Update 'org.ow2.asm:asm' to 9.8 27 | 28 | ## Version 1.8 29 | * [#136](https://github.com/gradlex-org/java-module-dependencies/pull/136) Support hierarchical project paths in Settings DSL 30 | * [#141](https://github.com/gradlex-org/java-module-dependencies/pull/141) Introduce `org.gradlex.java-module-dependencies.register-help-tasks` property 31 | * [#127](https://github.com/gradlex-org/java-module-dependencies/issues/127) Less configuration cache misses when modifying `module-info.java` (Thanks [TheGoesen](https://github.com/TheGoesen)) 32 | * [#128](https://github.com/gradlex-org/java-module-dependencies/issues/128) Less configuration cache misses when using Settings plugin (Thanks [TheGoesen](https://github.com/TheGoesen)) 33 | * [#135](https://github.com/gradlex-org/java-module-dependencies/issues/135) Improve performance of ApplyPluginsAction 34 | 35 | ## Version 1.7.1 36 | * Update module name mappings 37 | 38 | ## Version 1.7 39 | * [#112](https://github.com/gradlex-org/java-module-dependencies/issues/112) Settings plugin to configure module locations and identity 40 | 41 | ## Version 1.6.6 42 | * [#113](https://github.com/gradlex-org/java-module-dependencies/issues/113) Fix: Do not fail for duplicated project names (Thanks [TheGoesen](https://github.com/TheGoesen)) 43 | * [#111](https://github.com/gradlex-org/java-module-dependencies/issues/111) Fix: Do not use 'MapProperty.unset' (Thanks [TheGoesen](https://github.com/TheGoesen)) 44 | * [#112](https://github.com/gradlex-org/java-module-dependencies/issues/112) Improve compatibility with Project Isolation 45 | 46 | ## Version 1.6.5 47 | * [#104](https://github.com/gradlex-org/java-module-dependencies/issues/104) Fix: ModuleDependencyReport task does not correctly track inputs 48 | 49 | ## Version 1.6.4 50 | * Enhance output of 'moduleDependencies' task 51 | * Update 'org.ow2.asm:asm' to 9.7 52 | 53 | ## Version 1.6.3 54 | * Update module name mappings 55 | 56 | ## Version 1.6.2 57 | * [#90](https://github.com/gradlex-org/java-module-dependencies/issues/90) Fix: 'moduleNamePrefixToGroup' mapping uses best fit instead of first match 58 | * [#91](https://github.com/gradlex-org/java-module-dependencies/issues/91) Fix: handle duplicated module names in 'extra-module-info' bridge 59 | 60 | ## Version 1.6.1 61 | * Fix in setup of new utility tasks 62 | 63 | ## Version 1.6 64 | * Add more utility tasks to migrate from/to module-info based dependencies 65 | * Additional notation for module version DSL 66 | 67 | ## Version 1.5.2 68 | * Fix for requires /*runtime*/ support 69 | 70 | ## Version 1.5.1 71 | * Make `module-info.java` analysis tasks cacheable 72 | * Make `recommendModuleVersions` configuration cache compatible 73 | * Further tweak `requires /*runtime*/` support 74 | 75 | ## Version 1.5 76 | * [#67](https://github.com/gradlex-org/java-module-dependencies/issues/67) Support local `modules.properties` for custom mappings 77 | * [#65](https://github.com/gradlex-org/java-module-dependencies/issues/65) Error if a local Module Name does not match project name 78 | * [#24](https://github.com/gradlex-org/java-module-dependencies/issues/24) Improve support for `requires /*runtime*/` 79 | 80 | ## Version 1.4.3 81 | * Support '.' to '-' conversion in 'moduleNamePrefixToGroup' 82 | * Fix issue in integration with 'extra-module-info' 83 | * Improve support for Capability Coordinates in mappings 84 | * Remove 'version missing in catalog' warning (triggered when catalog is used for different things) 85 | 86 | ## Version 1.4.2 87 | * Fix Gradle 8.6 compatibility 88 | 89 | ## Version 1.4.1 90 | * [#47](https://github.com/gradlex-org/java-module-dependencies/issues/47) Fix Gradle 8.3 compatibility 91 | 92 | ## Version 1.4 93 | * [#31](https://github.com/gradlex-org/java-module-dependencies/issues/31) DSL for module dependencies that cannot be defined in module-info 94 | * [#45](https://github.com/gradlex-org/java-module-dependencies/issues/45) Support Capability Coordinates in mappings 95 | 96 | ## Version 1.3.1 97 | * Fix integration with analysis plugin if root projects are involved 98 | * Fix in module name calculation for additional source sets 99 | * Improve dependency analysis reporting for source sets without module-info.java 100 | 101 | ## Version 1.3 102 | * [#25](https://github.com/gradlex-org/java-module-dependencies/issues/25) Add 'moduleDependencies' help task - similar to 'dependencies' but with Module Names 103 | * [#27](https://github.com/gradlex-org/java-module-dependencies/issues/27) Add task to check scopes of requires directives (by integrating with 'dependency-analysis' plugin) 104 | * [#22](https://github.com/gradlex-org/java-module-dependencies/issues/22) Add task to check ordering (alphabetical) of requires directives 105 | * [#29](https://github.com/gradlex-org/java-module-dependencies/issues/29) Add convenience to enable consistent resolution 106 | * [#30](https://github.com/gradlex-org/java-module-dependencies/issues/30) Add an 'analyse only' mode 107 | * [#23](https://github.com/gradlex-org/java-module-dependencies/issues/23) Consider 'moduleNamePrefixToGroup' entries in: ga(), gav(), moduleName() 108 | 109 | ## Version 1.2 110 | * [#20](https://github.com/gradlex-org/java-module-dependencies/issues/20) Improve support for `requires /*runtime*/` 111 | 112 | ## Version 1.1 113 | * [#19](https://github.com/gradlex-org/java-module-dependencies/issues/19) Support for `requires /*runtime*/` 114 | 115 | ## Version 1.0 116 | * Moved project to [GradleX](https://gradlex.org) - new plugin ID: `org.gradlex.java-module-dependencies` 117 | 118 | ## Versions 0.11 119 | * [#18](https://github.com/gradlex-org/java-module-dependencies/issues/18) Fix bug with single line comments in module-info.java 120 | * More mappings for Modules on Maven Central 121 | 122 | ## Versions 0.10 123 | * `moduleNamePrefixToGroup.put("..", "..")` to register mappings for a group of modules 124 | * More mappings for Modules on Maven Central 125 | 126 | ## Versions 0.8 127 | * More mappings for Modules on Maven Central 128 | 129 | ## Versions 0.1 - 0.7 130 | * Initial features added 131 | --------------------------------------------------------------------------------