diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c13797..080940b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Java Module Dependencies Gradle Plugin - Changelog +## Version 1.10.1 +* [#245](https://github.com/gradlex-org/java-module-dependencies/issues/245) Add 'allLocalModules' access to extension +* Update module name mappings + ## Version 1.10 * [#235](https://github.com/gradlex-org/java-module-dependencies/issues/235) Upgrade to Gradle 9.1, address deprecation * [#221](https://github.com/gradlex-org/java-module-dependencies/issues/221) Upgrade to Gradle 9, remove deprecated features diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java index 6e896433..da895d27 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java @@ -56,11 +56,12 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; import static java.util.Optional.empty; @@ -226,15 +227,14 @@ public Provider create(String moduleName, SourceSet sourceSetWithMod private Provider createPrecise(String moduleName) { return getProviders().provider(() -> { - String projectPath = getModuleInfoCache().get().getProjectPath(moduleName); - String capability = getModuleInfoCache().get().getCapability(moduleName); + LocalModule localModule = getModuleInfoCache().get().getLocalModule(moduleName); - if (projectPath != null) { + if (localModule != null) { // local project - ProjectDependency projectDependency = (ProjectDependency) getDependencies().create(getProject().project(projectPath)); + ProjectDependency projectDependency = (ProjectDependency) getDependencies().create(getProject().project(localModule.getProjectPath())); projectDependency.because(moduleName); - if (capability != null) { - projectDependency.capabilities(c -> c.requireCapabilities(capability)); + if (localModule.getCapability() != null) { + projectDependency.capabilities(c -> c.requireCapabilities(localModule.getCapability())); } return projectDependency; } else { @@ -406,6 +406,13 @@ public Provider moduleName(Provider ga) { }); } + /** + * @return information about all modules defined in module-info.java files in the build + */ + public Set allLocalModules() { + return new TreeSet<>(getModuleInfoCache().get().getAllLocalModules()); + } + /** * @deprecated use the 'org.gradlex.jvm-dependency-conflict-resolution' plugin instead. */ diff --git a/src/main/java/org/gradlex/javamodule/dependencies/LocalModule.java b/src/main/java/org/gradlex/javamodule/dependencies/LocalModule.java new file mode 100644 index 00000000..bebf4dc5 --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/LocalModule.java @@ -0,0 +1,83 @@ +/* + * Copyright the GradleX team. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradlex.javamodule.dependencies; + +import org.jspecify.annotations.Nullable; + +import java.io.Serializable; +import java.util.Objects; + +public class LocalModule implements Comparable, Serializable { + private final String moduleName; + private final String projectPath; + @Nullable + private final String capability; + + public LocalModule(String moduleName, String projectPath, @Nullable String capability) { + this.moduleName = moduleName; + this.projectPath = projectPath; + this.capability = capability; + } + + /** + * @return the module name as defined in the module-info.java file, e.g. 'org.example.module-a' + */ + public String getModuleName() { + return moduleName; + } + + /** + * @return the full Gradle project path for the subproject defining the module, e.g. ':module-a' + */ + public String getProjectPath() { + return projectPath; + } + + /** + * @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' + */ + @Nullable + public String getCapability() { + return capability; + } + + @Override + public String toString() { + return "[" + + "moduleName='" + moduleName + '\'' + + ", projectPath='" + projectPath + '\'' + + ", capability='" + capability + '\'' + + ']'; + } + + @Override + public int compareTo(LocalModule o) { + return moduleName.compareTo(o.moduleName); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + LocalModule that = (LocalModule) o; + return Objects.equals(moduleName, that.moduleName); + } + + @Override + public int hashCode() { + return Objects.hash(moduleName); + } +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java index d50e1ee1..29e35cf2 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java @@ -20,6 +20,7 @@ import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; +import org.gradlex.javamodule.dependencies.LocalModule; import org.jspecify.annotations.Nullable; import org.slf4j.LoggerFactory; @@ -27,6 +28,7 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -37,8 +39,7 @@ public abstract class ModuleInfoCache { private final boolean initializedInSettings; private final Map moduleInfo = new HashMap<>(); - private final Map moduleNameToProjectPath = new HashMap<>(); - private final Map moduleNameToCapability = new HashMap<>(); + private final Map localModules = new HashMap<>(); @Inject public ModuleInfoCache(boolean initializedInSettings) { @@ -82,28 +83,30 @@ public ModuleInfo put(File projectRoot, String moduleInfoPath, String projectPat File folder = new File(projectRoot, moduleInfoPath); if (maybePutModuleInfo(folder, providers)) { ModuleInfo thisModuleInfo = moduleInfo.get(folder); - moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), projectPath); + String moduleName = thisModuleInfo.getModuleName(); + String capability = null; Path parentDirectory = Paths.get(moduleInfoPath).getParent(); String capabilitySuffix = parentDirectory == null ? null : sourceSetToCapabilitySuffix(parentDirectory.getFileName().toString()); if (capabilitySuffix != null) { if (group.isPresent()) { - moduleNameToCapability.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact + "-" + capabilitySuffix); + capability = group.get() + ":" + artifact + "-" + capabilitySuffix; } else { LOGGER.lifecycle( - "[WARN] [Java Module Dependencies] " + thisModuleInfo.getModuleName() + " - 'group' not defined!"); + "[WARN] [Java Module Dependencies] " + moduleName + " - 'group' not defined!"); } } + localModules.put(moduleName, new LocalModule(moduleName, projectPath, capability)); return thisModuleInfo; } return ModuleInfo.EMPTY; } - public @Nullable String getProjectPath(String moduleName) { - return moduleNameToProjectPath.get(moduleName); + public @Nullable LocalModule getLocalModule(String moduleName) { + return localModules.get(moduleName); } - public @Nullable String getCapability(String moduleName) { - return moduleNameToCapability.get(moduleName); + public Collection getAllLocalModules() { + return localModules.values(); } private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { diff --git a/src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.java b/src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.java index fc7514a2..c6bc0cd9 100644 --- a/src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.java +++ b/src/test/java/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.java @@ -282,4 +282,38 @@ void can_have_moduleinfo_in_custom_location() { assertThat(requireNonNull(result.task(":lib:compileJava")).getOutcome()).isEqualTo(SUCCESS); } + @Test + void can_access_local_module_information_in_project() { + build.settingsFile.appendText(""" + javaModules { + directory(".") { + group = "org.example" + module("aggregation") + } + }"""); + build.libModuleInfoFile.writeText("module abc.lib { }"); + build.file("lib/src/testFixtures/java/module-info.java").writeText("module abc.lib.test.fixtures { }"); + build.appModuleInfoFile.writeText(""" + module org.gradlex.test.app { + requires abc.lib; + }"""); + + build.file("aggregation/build.gradle.kts").writeText(""" + plugins { id("org.gradlex.java-module-dependencies") } + tasks.register("info") { + val info = javaModuleDependencies.allLocalModules().joinToString("\\n") + doLast { println(info) } + }"""); + + var result = build.runner(":aggregation:info").build(); + + assertThat(result.getOutput()).contains(""" + > Task :aggregation:info + [moduleName='abc.lib', projectPath=':lib', capability='null'] + [moduleName='abc.lib.test.fixtures', projectPath=':lib', capability='org.example:lib-test-fixtures'] + [moduleName='org.gradlex.test.app', projectPath=':app', capability='null'] + + """); + } + }