From e6a3cdc0f3ec2618023eabd0f578cd7c678c9ced Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 08:23:13 +0200 Subject: [PATCH 01/12] Introduce setting plugin to configure module locations and identity --- .../JavaModuleDependenciesExtension.java | 4 +- .../JavaModuleDependenciesPlugin.java | 20 ++- .../JavaModuleDependenciesSettingsPlugin.java | 19 ++ .../initialization/JavaModulesExtension.java | 168 ++++++++++++++++++ .../dependencies/initialization/Module.java | 25 +++ .../dependencies/initialization/Modules.java | 45 +++++ .../initialization/RootPluginsExtension.java | 66 +++++++ .../internal/utils/ModuleInfo.java | 4 + .../internal/utils/ModuleInfoCache.java | 50 ++++-- 9 files changed, 381 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java create mode 100644 src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java create mode 100644 src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java create mode 100644 src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java create mode 100644 src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java index dac5f65..d803c81 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java @@ -218,7 +218,7 @@ public Provider create(String moduleName, SourceSet sourceSetWithMod Provider coordinates = getModuleNameToGA().getting(moduleName).orElse(mapByPrefix(getProviders().provider(() -> moduleName))); - ModuleInfo moduleInfo = getModuleInfoCache().get(sourceSetWithModuleInfo); + ModuleInfo moduleInfo = getModuleInfoCache().get(sourceSetWithModuleInfo, getProviders()); String ownModuleNamesPrefix = moduleInfo.moduleNamePrefix(getProject().getName(), sourceSetWithModuleInfo.getName(), getModuleNameCheck().get()); String moduleNameSuffix = ownModuleNamesPrefix == null ? null : @@ -478,7 +478,7 @@ public void addRequiresRuntimeSupport(SourceSet sourceSetForModuleInfo, SourceSe } void doAddRequiresRuntimeSupport(SourceSet sourceSetForModuleInfo, SourceSet sourceSetForClasspath) { - List requiresRuntime = getModuleInfoCache().get(sourceSetForModuleInfo).get(REQUIRES_RUNTIME); + List requiresRuntime = getModuleInfoCache().get(sourceSetForModuleInfo, getProviders()).get(REQUIRES_RUNTIME); String generatorTaskName = sourceSetForClasspath.getTaskName("generate", "syntheticModuleInfoFolders"); if (requiresRuntime.isEmpty() || getProject().getTasks().getNames().contains(generatorTaskName)) { // Already active or not needed for this source set diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java index 276e2b1..f6cdbd8 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java @@ -24,6 +24,8 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.VersionCatalogsExtension; +import org.gradle.api.initialization.Settings; +import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.SourceSet; @@ -32,6 +34,7 @@ import org.gradle.util.GradleVersion; import org.gradlex.javamodule.dependencies.dsl.AllDirectives; import org.gradlex.javamodule.dependencies.dsl.GradleOnlyDirectives; +import org.gradlex.javamodule.dependencies.initialization.JavaModuleDependenciesSettingsPlugin; import org.gradlex.javamodule.dependencies.internal.bridges.DependencyAnalysisBridge; import org.gradlex.javamodule.dependencies.internal.bridges.ExtraJavaModuleInfoBridge; import org.gradlex.javamodule.dependencies.internal.dsl.AllDirectivesInternal; @@ -62,16 +65,23 @@ @SuppressWarnings("unused") @NonNullApi -public abstract class JavaModuleDependenciesPlugin implements Plugin { +public abstract class JavaModuleDependenciesPlugin implements Plugin { private static final String EXTRA_JAVA_MODULE_INFO_PLUGIN_ID = "org.gradlex.extra-java-module-info"; @Override - public void apply(Project project) { + public void apply(ExtensionAware projectOrSettings) { if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) < 0) { throw new GradleException("This plugin requires Gradle 7.4+"); } + if (projectOrSettings instanceof Project) { + applyProject((Project) projectOrSettings); + } else if (projectOrSettings instanceof Settings) { + ((Settings) projectOrSettings).getPlugins().apply(JavaModuleDependenciesSettingsPlugin.class); + } + } + private void applyProject(Project project) { VersionCatalogsExtension versionCatalogs = project.getExtensions().findByType(VersionCatalogsExtension.class); JavaModuleDependenciesExtension javaModuleDependencies = project.getExtensions().create( JAVA_MODULE_DEPENDENCIES, JavaModuleDependenciesExtension.class, versionCatalogs); @@ -224,7 +234,7 @@ private void setupOrderingCheckTasks(Project project, TaskProvider checkAl t.setGroup("java modules"); t.setDescription("Check order of directives in 'module-info.java' in '" + sourceSet.getName() + "' source set"); - ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet); + ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet, project.getProviders()); t.getModuleInfoPath().convention(moduleInfo.getFilePath().getAbsolutePath()); t.getModuleNamePrefix().convention(moduleInfo.moduleNamePrefix(project.getName(), sourceSet.getName(), false)); @@ -254,7 +264,7 @@ private void readModuleInfo(ModuleInfo.Directive moduleDirective, SourceSet sour if (javaModuleDependenciesExtension.getAnalyseOnly().get()) { return; } - ModuleInfo moduleInfo = javaModuleDependenciesExtension.getModuleInfoCache().get(sourceSet); + ModuleInfo moduleInfo = javaModuleDependenciesExtension.getModuleInfoCache().get(sourceSet, project.getProviders()); for (String moduleName : moduleInfo.get(moduleDirective)) { declareDependency(moduleName, moduleInfo.getFilePath(), project, sourceSet, configuration, javaModuleDependenciesExtension); } @@ -270,7 +280,7 @@ private void declareDependency(String moduleName, File moduleInfoFile, Project p } private List collectDependencies(Project project, JavaModuleDependenciesExtension javaModuleDependencies, SourceSet sourceSet, ModuleInfo.Directive directive, String scope) { - ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet); + ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet, project.getProviders()); if (moduleInfo == ModuleInfo.EMPTY) { // check if there is a whiltebox module-info we can use isntead File sourceSetDir = sourceSet.getJava().getSrcDirs().iterator().next().getParentFile(); diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java new file mode 100644 index 0000000..0b4b91e --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java @@ -0,0 +1,19 @@ +package org.gradlex.javamodule.dependencies.initialization; + +import org.gradle.api.NonNullApi; +import org.gradle.api.Plugin; +import org.gradle.api.initialization.Settings; + +@NonNullApi +public abstract class JavaModuleDependenciesSettingsPlugin implements Plugin { + + @Override + public void apply(Settings settings) { + registerExtension(settings); + } + + private void registerExtension(Settings settings) { + settings.getExtensions().create("rootPlugins", RootPluginsExtension.class, settings); + settings.getExtensions().create("javaModules", JavaModulesExtension.class, settings); + } +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java new file mode 100644 index 0000000..229e25e --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -0,0 +1,168 @@ +/* + * 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.initialization; + +import org.gradle.api.Action; +import org.gradle.api.IsolatedAction; +import org.gradle.api.NonNullApi; +import org.gradle.api.Project; +import org.gradle.api.initialization.ProjectDescriptor; +import org.gradle.api.initialization.Settings; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.ApplicationPlugin; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaPlatformPlugin; +import org.gradle.util.GradleVersion; +import org.gradlex.javamodule.dependencies.JavaModuleDependenciesPlugin; +import org.gradlex.javamodule.dependencies.JavaModuleVersionsPlugin; +import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo; +import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfoCache; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.io.File; +import java.nio.file.Paths; +import java.util.List; + +public abstract class JavaModulesExtension { + + static final boolean SUPPORT_PROJECT_ISOLATION = + GradleVersion.current().compareTo(GradleVersion.version("8.8")) >= 0; + + private final Settings settings; + private final ModuleInfoCache moduleInfoCache; + + @Inject + public abstract ObjectFactory getObjects(); + + @Inject + public JavaModulesExtension(Settings settings) { + this.settings = settings; + this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class); + } + + public void module(String folder) { + module(folder, m -> {}); + } + + public void module(String folder, Action action) { + Module module = getObjects().newInstance(Module.class); + module.getFolder().set(folder); + action.execute(module); + includeModule(module, new File(settings.getRootDir(), module.getFolder().get())); + } + + public void modules(String folder) { + modules(folder, m -> {}); + } + + public void modules(String folder, Action action) { + Modules moduleGroup = getObjects().newInstance(Modules.class); + action.execute(moduleGroup); + + File[] projectFolders = new File(settings.getRootDir(), folder).listFiles(); + if (projectFolders == null) { + throw new RuntimeException("Failed to inspect: " + new File(settings.getRootDir(), folder)); + } + + for (File projectFolder : projectFolders) { + if (moduleGroup.customizedModules.containsKey(projectFolder.getName())) { + includeModule(moduleGroup.customizedModules.get(projectFolder.getName()), projectFolder); + } else { + includeModule(moduleGroup.addModule(projectFolder.getName()), projectFolder); + } + } + } + + public void versions(String folder) { + String projectName = Paths.get(folder).getFileName().toString(); + settings.include(projectName); + settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), folder)); + if (SUPPORT_PROJECT_ISOLATION) { + settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); + } else { + settings.getGradle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); + } + } + + private void includeModule(Module module, File projectDir) { + ModuleInfo moduleInfo = moduleInfoCache.get(projectDir, module.getModuleInfoPath().get(), + module.getArtifact().get(), module.getGroup(), settings.getProviders()); + if (moduleInfo == ModuleInfo.EMPTY) { + return; + } + + String artifact = module.getArtifact().get(); + settings.include(artifact); + ProjectDescriptor project = settings.project(":" + artifact); + project.setProjectDir(projectDir); + + String group = module.getGroup().getOrNull(); + + List plugins = module.getPlugins().get(); + if (SUPPORT_PROJECT_ISOLATION) { + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, moduleInfo.getModuleName())); + } else { + settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, moduleInfo.getModuleName())); + } + } + + @NonNullApi + private static class ApplyPluginsAction implements IsolatedAction, Action { + + private final String artifact; + private final String group; + private final List plugins; + private final String modueName; + + public ApplyPluginsAction(String artifact, @Nullable String group, List plugins, String modueName) { + this.artifact = artifact; + this.group = group; + this.plugins = plugins; + this.modueName = modueName; + } + + @Override + public void execute(Project project) { + if (project.getName().equals(artifact)) { + if (group != null) project.setGroup(group); + project.getPlugins().apply(JavaModuleDependenciesPlugin.class); + plugins.forEach(id -> project.getPlugins().apply(id)); + project.getPlugins().withType(ApplicationPlugin.class, p -> + project.getExtensions().getByType(JavaApplication.class).getMainModule().set(modueName)); + } + } + } + + @NonNullApi + private static class ApplyJavaModuleVersionsPluginAction implements IsolatedAction, Action { + + private final String projectName; + + public ApplyJavaModuleVersionsPluginAction(String projectName) { + this.projectName = projectName; + } + + @Override + public void execute(Project project) { + if (projectName.equals(project.getName())) { + project.getPlugins().apply(JavaPlatformPlugin.class); + project.getPlugins().apply(JavaModuleVersionsPlugin.class); + } + } + } +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java new file mode 100644 index 0000000..137458b --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -0,0 +1,25 @@ +package org.gradlex.javamodule.dependencies.initialization; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; + +import java.nio.file.Paths; + +public abstract class Module { + public abstract Property getFolder(); + public abstract Property getArtifact(); + public abstract Property getGroup(); + public abstract Property getModuleInfoPath(); + public abstract ListProperty getPlugins(); + + public Module() { + getArtifact().convention(getFolder().map(f -> Paths.get(f).getFileName().toString())); + getModuleInfoPath().convention("src/main/java"); + } + + public void plugin(String id) { + getPlugins().add(id); + } + + +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java new file mode 100644 index 0000000..9585725 --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java @@ -0,0 +1,45 @@ +package org.gradlex.javamodule.dependencies.initialization; + +import org.gradle.api.Action; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; + +import javax.inject.Inject; +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class Modules { + + final Map customizedModules = new LinkedHashMap<>(); + + public abstract Property getGroup(); + public abstract Property getModuleInfoPath(); + public abstract ListProperty getPlugins(); + + @Inject + public abstract ObjectFactory getObjects(); + + public Modules() { + getModuleInfoPath().convention("src/main/java"); + } + + public void plugin(String id) { + getPlugins().add(id); + } + + public void module(String moduleFolder, Action action) { + Module module = addModule(moduleFolder); + action.execute(module); + customizedModules.put(moduleFolder, module); + } + + Module addModule(String moduleFolder) { + Module module = getObjects().newInstance(Module.class); + module.getFolder().convention(moduleFolder); + module.getGroup().convention(getGroup()); + module.getPlugins().addAll(getPlugins()); + module.getModuleInfoPath().convention(getModuleInfoPath()); + return module; + } +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java new file mode 100644 index 0000000..74f0ceb --- /dev/null +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java @@ -0,0 +1,66 @@ +/* + * 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.initialization; + +import org.gradle.api.Action; +import org.gradle.api.IsolatedAction; +import org.gradle.api.NonNullApi; +import org.gradle.api.Project; +import org.gradle.api.initialization.Settings; + +import javax.inject.Inject; + +import static org.gradlex.javamodule.dependencies.initialization.JavaModulesExtension.SUPPORT_PROJECT_ISOLATION; + +public abstract class RootPluginsExtension { + + private final Settings settings; + + @Inject + public RootPluginsExtension(Settings settings) { + this.settings = settings; + } + + public void id(String id) { + if (SUPPORT_PROJECT_ISOLATION) { + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginAction(id)); + } else { + settings.getGradle().beforeProject(new ApplyPluginAction(id)); + } + } + + @NonNullApi + private static class ApplyPluginAction implements IsolatedAction, Action { + + private final String id; + + public ApplyPluginAction(String id) { + this.id = id; + } + + @Override + public void execute(Project project) { + if (isRoot(project)) { + project.getPlugins().apply(id); + } + } + + private boolean isRoot(Project project) { + return project == project.getRootProject(); + } + } +} diff --git a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfo.java b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfo.java index 4fcc1fc..5a3f6b1 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfo.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfo.java @@ -62,6 +62,10 @@ public ModuleInfo(String moduleInfoFileContent, File filePath) { } } + public String getModuleName() { + return moduleName; + } + public List get(Directive directive) { if (directive == Directive.REQUIRES) { return requires; 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 a4a9601..6721c83 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 @@ -16,8 +16,8 @@ package org.gradlex.javamodule.dependencies.internal.utils; -import org.gradle.api.file.ProjectLayout; -import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; @@ -30,12 +30,11 @@ public abstract class ModuleInfoCache { private final Map moduleInfo = new HashMap<>(); + private final Map moduleNameToProjectPath = new HashMap<>(); + private final Map moduleNameToGA = new HashMap<>(); @Inject - protected abstract ProviderFactory getProviders(); - - @Inject - protected abstract ProjectLayout getLayout(); + public abstract ObjectFactory getObjects(); /** * Returns the module-info.java for the given SourceSet. If the SourceSet has multiple source folders with multiple @@ -44,17 +43,42 @@ public abstract class ModuleInfoCache { * @param sourceSet the SourceSet representing a module * @return parsed module-info.java for the given SourceSet */ - public ModuleInfo get(SourceSet sourceSet) { + public ModuleInfo get(SourceSet sourceSet, ProviderFactory providers) { for (File folder : sourceSet.getJava().getSrcDirs()) { - Provider moduleInfoFile = getLayout().file(getProviders().provider(() -> new File(folder, "module-info.java"))); - Provider moduleInfoContent = getProviders().fileContents(moduleInfoFile).getAsText(); - if (moduleInfoContent.isPresent()) { - if (!moduleInfo.containsKey(folder)) { - moduleInfo.put(folder, new ModuleInfo(moduleInfoContent.get(), moduleInfoFile.get().getAsFile())); - } + if (maybePutModuleInfo(folder, providers)) { return moduleInfo.get(folder); } } return ModuleInfo.EMPTY; } + + /** + * @param projectRoot the project that should hold a Java module + * @return parsed module-info.java for the given project assuming a standard Java project layout + */ + public ModuleInfo get(File projectRoot, String moduleInfoPath, String artifact, Provider group, ProviderFactory providers) { + File folder = new File(projectRoot, moduleInfoPath); + if (maybePutModuleInfo(folder, providers)) { + ModuleInfo thisModuleInfo = moduleInfo.get(folder); + moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact); + if (group.isPresent()) { + moduleNameToGA.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact); + } + return thisModuleInfo; + } + return ModuleInfo.EMPTY; + } + + private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { + RegularFileProperty moduleInfoFile = getObjects().fileProperty(); + moduleInfoFile.set(new File(folder, "module-info.java")); + Provider moduleInfoContent = providers.fileContents(moduleInfoFile).getAsText(); + if (moduleInfoContent.isPresent()) { + if (!moduleInfo.containsKey(folder)) { + moduleInfo.put(folder, new ModuleInfo(moduleInfoContent.get(), moduleInfoFile.get().getAsFile())); + } + return true; + } + return false; + } } From e003bc2200ace1f5923220c55b736f19b0488290 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 09:28:13 +0200 Subject: [PATCH 02/12] Multiple source sets and capabilities support --- .../JavaModuleDependenciesExtension.java | 86 +++++++++++++------ .../initialization/JavaModulesExtension.java | 35 +++++--- .../dependencies/initialization/Module.java | 23 +++-- .../dependencies/initialization/Modules.java | 11 +-- .../internal/utils/ModuleInfoCache.java | 24 +++++- .../internal/utils/ModuleNamingUtil.java | 16 ++++ 6 files changed, 141 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java index d803c81..0564388 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java @@ -212,13 +212,38 @@ private String toProjectName(String moduleNameSuffix) { } public Provider create(String moduleName, SourceSet sourceSetWithModuleInfo) { + if (moduleInfoCache.initializedInSettings()) { + return createPrecise(moduleName, sourceSetWithModuleInfo); + } else { + return createWithGuessing(moduleName, sourceSetWithModuleInfo); + } + } + + public Provider createPrecise(String moduleName, SourceSet sourceSetWithModuleInfo) { + return getProviders().provider(() -> { + String projectPath = moduleInfoCache.getProjectPath(moduleName); + String capability = moduleInfoCache.getCapability(moduleName); + + if (projectPath != null) { + // local project + ProjectDependency projectDependency = (ProjectDependency) getDependencies().create(getProject().project(projectPath)); + projectDependency.because(moduleName); + if (capability != null) { + projectDependency.capabilities(c -> c.requireCapabilities(capability)); + } + return projectDependency; + } else { + return createExternalDependency(moduleName); + } + }); + } + + public Provider createWithGuessing(String moduleName, SourceSet sourceSetWithModuleInfo) { return getProviders().provider(() -> { Map allProjectNamesAndGroups = getProject().getRootProject().getSubprojects().stream().collect( Collectors.toMap(Project::getName, p -> (String) p.getGroup(), (a, b) -> a)); - Provider coordinates = getModuleNameToGA().getting(moduleName).orElse(mapByPrefix(getProviders().provider(() -> moduleName))); - - ModuleInfo moduleInfo = getModuleInfoCache().get(sourceSetWithModuleInfo, getProviders()); + ModuleInfo moduleInfo = moduleInfoCache.get(sourceSetWithModuleInfo, getProviders()); String ownModuleNamesPrefix = moduleInfo.moduleNamePrefix(getProject().getName(), sourceSetWithModuleInfo.getName(), getModuleNameCheck().get()); String moduleNameSuffix = ownModuleNamesPrefix == null ? null : @@ -243,34 +268,41 @@ public Provider create(String moduleName, SourceSet sourceSetWithMod allProjectNamesAndGroups.get(projectName) + ":" + capabilityName)); projectDependency.because(moduleName); return projectDependency; - } else if (coordinates.isPresent()) { - Map component; - String capability; - if (coordinates.get().contains("|")) { - String[] split = coordinates.get().split("\\|"); - component = findGav(split[0], moduleName); - if (split[1].contains(":")) { - capability = split[1]; - } else { - // only classifier was specified - capability = split[0] + "-" + split[1]; - } + } + + return createExternalDependency(moduleName); + }); + } + + private ModuleDependency createExternalDependency(String moduleName) { + Provider coordinates = getModuleNameToGA().getting(moduleName).orElse(mapByPrefix(getProviders().provider(() -> moduleName))); + if (coordinates.isPresent()) { + Map component; + String capability; + if (coordinates.get().contains("|")) { + String[] split = coordinates.get().split("\\|"); + component = findGav(split[0], moduleName); + if (split[1].contains(":")) { + capability = split[1]; } else { - component = findGav(coordinates.get(), moduleName); - capability = null; - } - ModuleDependency dependency = (ModuleDependency) getDependencies().create(component); - dependency.because(moduleName); - if (capability != null) { - dependency.capabilities(c -> c.requireCapability(capability)); + // only classifier was specified + capability = split[0] + "-" + split[1]; } - return dependency; } else { - getProject().getLogger().lifecycle( - "[WARN] [Java Module Dependencies] " + moduleName + "=group:artifact missing in " + getModulesProperties().get().getAsFile()); - return null; + component = findGav(coordinates.get(), moduleName); + capability = null; } - }); + ModuleDependency dependency = (ModuleDependency) getDependencies().create(component); + dependency.because(moduleName); + if (capability != null) { + dependency.capabilities(c -> c.requireCapability(capability)); + } + return dependency; + } else { + getProject().getLogger().lifecycle( + "[WARN] [Java Module Dependencies] " + moduleName + "=group:artifact missing in " + getModulesProperties().get().getAsFile()); + return null; + } } /** diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 229e25e..7f6c9d2 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -60,7 +60,7 @@ public void module(String folder) { } public void module(String folder, Action action) { - Module module = getObjects().newInstance(Module.class); + Module module = getObjects().newInstance(Module.class, settings.getRootDir()); module.getFolder().set(folder); action.execute(module); includeModule(module, new File(settings.getRootDir(), module.getFolder().get())); @@ -71,7 +71,7 @@ public void modules(String folder) { } public void modules(String folder, Action action) { - Modules moduleGroup = getObjects().newInstance(Modules.class); + Modules moduleGroup = getObjects().newInstance(Modules.class, new File(settings.getRootDir(), folder)); action.execute(moduleGroup); File[] projectFolders = new File(settings.getRootDir(), folder).listFiles(); @@ -100,9 +100,8 @@ public void versions(String folder) { } private void includeModule(Module module, File projectDir) { - ModuleInfo moduleInfo = moduleInfoCache.get(projectDir, module.getModuleInfoPath().get(), - module.getArtifact().get(), module.getGroup(), settings.getProviders()); - if (moduleInfo == ModuleInfo.EMPTY) { + List modulePaths = module.getModuleInfoPaths().get(); + if (modulePaths.isEmpty()) { return; } @@ -111,13 +110,21 @@ private void includeModule(Module module, File projectDir) { ProjectDescriptor project = settings.project(":" + artifact); project.setProjectDir(projectDir); - String group = module.getGroup().getOrNull(); + String mainModuleName = null; + for (String path : modulePaths) { + ModuleInfo moduleInfo = moduleInfoCache.put(projectDir, path, + module.getArtifact().get(), module.getGroup(), settings.getProviders()); + if (path.contains("/main/")) { + mainModuleName = moduleInfo.getModuleName(); + } + } + String group = module.getGroup().getOrNull(); List plugins = module.getPlugins().get(); if (SUPPORT_PROJECT_ISOLATION) { - settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, moduleInfo.getModuleName())); + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName)); } else { - settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, moduleInfo.getModuleName())); + settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName)); } } @@ -127,13 +134,13 @@ private static class ApplyPluginsAction implements IsolatedAction, Acti private final String artifact; private final String group; private final List plugins; - private final String modueName; + private final String mainModuleName; - public ApplyPluginsAction(String artifact, @Nullable String group, List plugins, String modueName) { + public ApplyPluginsAction(String artifact, @Nullable String group, List plugins, @Nullable String mainModuleName) { this.artifact = artifact; this.group = group; this.plugins = plugins; - this.modueName = modueName; + this.mainModuleName = mainModuleName; } @Override @@ -142,8 +149,10 @@ public void execute(Project project) { if (group != null) project.setGroup(group); project.getPlugins().apply(JavaModuleDependenciesPlugin.class); plugins.forEach(id -> project.getPlugins().apply(id)); - project.getPlugins().withType(ApplicationPlugin.class, p -> - project.getExtensions().getByType(JavaApplication.class).getMainModule().set(modueName)); + if (mainModuleName != null) { + project.getPlugins().withType(ApplicationPlugin.class, p -> + project.getExtensions().getByType(JavaApplication.class).getMainModule().set(mainModuleName)); + } } } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java index 137458b..ee2ff1b 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -3,23 +3,36 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import javax.inject.Inject; +import java.io.File; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; public abstract class Module { public abstract Property getFolder(); public abstract Property getArtifact(); public abstract Property getGroup(); - public abstract Property getModuleInfoPath(); + public abstract ListProperty getModuleInfoPaths(); public abstract ListProperty getPlugins(); - public Module() { + @Inject + public Module(File root) { getArtifact().convention(getFolder().map(f -> Paths.get(f).getFileName().toString())); - getModuleInfoPath().convention("src/main/java"); + getModuleInfoPaths().convention(getFolder().map(projectDir -> listChildren(root, projectDir + "/src") + .map(srcDir -> new File(srcDir, "java/module-info.java")) + .filter(File::exists) + .map(moduleInfo -> "src/" + moduleInfo.getParentFile().getParentFile().getName() + "/java") + .collect(Collectors.toList()))); + } + + private Stream listChildren(File root, String projectDir) { + File[] children = new File(root, projectDir).listFiles(); + return children == null ? Stream.empty() : Arrays.stream(children); } public void plugin(String id) { getPlugins().add(id); } - - } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java index 9585725..6d70ad5 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java @@ -6,22 +6,24 @@ import org.gradle.api.provider.Property; import javax.inject.Inject; +import java.io.File; import java.util.LinkedHashMap; import java.util.Map; public abstract class Modules { + private final File root; final Map customizedModules = new LinkedHashMap<>(); public abstract Property getGroup(); - public abstract Property getModuleInfoPath(); public abstract ListProperty getPlugins(); @Inject public abstract ObjectFactory getObjects(); - public Modules() { - getModuleInfoPath().convention("src/main/java"); + @Inject + public Modules(File root) { + this.root = root; } public void plugin(String id) { @@ -35,11 +37,10 @@ public void module(String moduleFolder, Action action) { } Module addModule(String moduleFolder) { - Module module = getObjects().newInstance(Module.class); + Module module = getObjects().newInstance(Module.class, root); module.getFolder().convention(moduleFolder); module.getGroup().convention(getGroup()); module.getPlugins().addAll(getPlugins()); - module.getModuleInfoPath().convention(getModuleInfoPath()); return module; } } 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 6721c83..77de9ab 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 @@ -24,14 +24,17 @@ import javax.inject.Inject; import java.io.File; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import static org.gradlex.javamodule.dependencies.internal.utils.ModuleNamingUtil.sourceSetToCapabilitySuffix; + public abstract class ModuleInfoCache { private final Map moduleInfo = new HashMap<>(); private final Map moduleNameToProjectPath = new HashMap<>(); - private final Map moduleNameToGA = new HashMap<>(); + private final Map moduleNameToCapability = new HashMap<>(); @Inject public abstract ObjectFactory getObjects(); @@ -56,19 +59,28 @@ public ModuleInfo get(SourceSet sourceSet, ProviderFactory providers) { * @param projectRoot the project that should hold a Java module * @return parsed module-info.java for the given project assuming a standard Java project layout */ - public ModuleInfo get(File projectRoot, String moduleInfoPath, String artifact, Provider group, ProviderFactory providers) { + public ModuleInfo put(File projectRoot, String moduleInfoPath, String artifact, Provider group, ProviderFactory providers) { File folder = new File(projectRoot, moduleInfoPath); if (maybePutModuleInfo(folder, providers)) { ModuleInfo thisModuleInfo = moduleInfo.get(folder); moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact); - if (group.isPresent()) { - moduleNameToGA.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact); + String capabilitySuffix = sourceSetToCapabilitySuffix(Paths.get(moduleInfoPath).getFileName().toString()); + if (group.isPresent() && capabilitySuffix != null) { + moduleNameToCapability.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact + "-" + capabilitySuffix); } return thisModuleInfo; } return ModuleInfo.EMPTY; } + public String getProjectPath(String moduleName) { + return moduleNameToProjectPath.get(moduleName); + } + + public String getCapability(String moduleName) { + return moduleNameToCapability.get(moduleName); + } + private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { RegularFileProperty moduleInfoFile = getObjects().fileProperty(); moduleInfoFile.set(new File(folder, "module-info.java")); @@ -81,4 +93,8 @@ private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { } return false; } + + public boolean initializedInSettings() { + return false; + } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleNamingUtil.java b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleNamingUtil.java index 1d12622..4d7269e 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleNamingUtil.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleNamingUtil.java @@ -30,6 +30,13 @@ public static String sourceSetToModuleName(String projectName, String sourceSetN return toDottedCase(projectName) + "." + toDottedCase(sourceSetName); } + public static String sourceSetToCapabilitySuffix(String sourceSetName) { + if (sourceSetName.equals(SourceSet.MAIN_SOURCE_SET_NAME)) { + return null; + } + return toKebabCase(sourceSetName); + } + /** * Converts 'camelCase' and 'kebab-case' to 'dotted.case'. */ @@ -38,4 +45,13 @@ private static String toDottedCase(String sourceSetName) { .split("(? Date: Mon, 15 Jul 2024 09:38:23 +0200 Subject: [PATCH 03/12] Support using a build-wide shared module info cache --- .../JavaModuleDependenciesExtension.java | 19 ++++++++----------- .../JavaModuleDependenciesPlugin.java | 6 +++--- .../initialization/JavaModulesExtension.java | 12 ++++++++---- .../internal/utils/ModuleInfoCache.java | 14 ++++++++++---- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java index 0564388..4957e9c 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java @@ -77,7 +77,8 @@ public abstract class JavaModuleDependenciesExtension { private static final String INTERNAL = "internal"; private final VersionCatalogsExtension versionCatalogs; - private final ModuleInfoCache moduleInfoCache; + + public abstract Property getModuleInfoCache(); /** * Custom mappings can be defined in a property files in your build. @@ -134,7 +135,7 @@ public abstract class JavaModuleDependenciesExtension { public JavaModuleDependenciesExtension(VersionCatalogsExtension versionCatalogs) { this.versionCatalogs = versionCatalogs; - this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class); + getModuleInfoCache().convention(getProviders().provider(() -> getObjects().newInstance(ModuleInfoCache.class, false))); getModulesProperties().set(new File(getProject().getRootDir(), "gradle/modules.properties")); getVersionCatalogName().convention("libs"); getModuleNameCheck().convention(true); @@ -212,7 +213,7 @@ private String toProjectName(String moduleNameSuffix) { } public Provider create(String moduleName, SourceSet sourceSetWithModuleInfo) { - if (moduleInfoCache.initializedInSettings()) { + if (getModuleInfoCache().get().isInitializedInSettings()) { return createPrecise(moduleName, sourceSetWithModuleInfo); } else { return createWithGuessing(moduleName, sourceSetWithModuleInfo); @@ -221,8 +222,8 @@ public Provider create(String moduleName, SourceSet sourceSetWithMod public Provider createPrecise(String moduleName, SourceSet sourceSetWithModuleInfo) { return getProviders().provider(() -> { - String projectPath = moduleInfoCache.getProjectPath(moduleName); - String capability = moduleInfoCache.getCapability(moduleName); + String projectPath = getModuleInfoCache().get().getProjectPath(moduleName); + String capability = getModuleInfoCache().get().getCapability(moduleName); if (projectPath != null) { // local project @@ -243,7 +244,7 @@ public Provider createWithGuessing(String moduleName, SourceSet sour Map allProjectNamesAndGroups = getProject().getRootProject().getSubprojects().stream().collect( Collectors.toMap(Project::getName, p -> (String) p.getGroup(), (a, b) -> a)); - ModuleInfo moduleInfo = moduleInfoCache.get(sourceSetWithModuleInfo, getProviders()); + ModuleInfo moduleInfo = getModuleInfoCache().get().get(sourceSetWithModuleInfo, getProviders()); String ownModuleNamesPrefix = moduleInfo.moduleNamePrefix(getProject().getName(), sourceSetWithModuleInfo.getName(), getModuleNameCheck().get()); String moduleNameSuffix = ownModuleNamesPrefix == null ? null : @@ -510,7 +511,7 @@ public void addRequiresRuntimeSupport(SourceSet sourceSetForModuleInfo, SourceSe } void doAddRequiresRuntimeSupport(SourceSet sourceSetForModuleInfo, SourceSet sourceSetForClasspath) { - List requiresRuntime = getModuleInfoCache().get(sourceSetForModuleInfo, getProviders()).get(REQUIRES_RUNTIME); + List requiresRuntime = getModuleInfoCache().get().get(sourceSetForModuleInfo, getProviders()).get(REQUIRES_RUNTIME); String generatorTaskName = sourceSetForClasspath.getTaskName("generate", "syntheticModuleInfoFolders"); if (requiresRuntime.isEmpty() || getProject().getTasks().getNames().contains(generatorTaskName)) { // Already active or not needed for this source set @@ -573,8 +574,4 @@ private String moduleDebugInfo(String moduleName, File moduleInfoFile, File root @Inject protected abstract SourceSetContainer getSourceSets(); - - ModuleInfoCache getModuleInfoCache() { - return moduleInfoCache; - } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java index f6cdbd8..687a4b3 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesPlugin.java @@ -234,7 +234,7 @@ private void setupOrderingCheckTasks(Project project, TaskProvider checkAl t.setGroup("java modules"); t.setDescription("Check order of directives in 'module-info.java' in '" + sourceSet.getName() + "' source set"); - ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet, project.getProviders()); + ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get().get(sourceSet, project.getProviders()); t.getModuleInfoPath().convention(moduleInfo.getFilePath().getAbsolutePath()); t.getModuleNamePrefix().convention(moduleInfo.moduleNamePrefix(project.getName(), sourceSet.getName(), false)); @@ -264,7 +264,7 @@ private void readModuleInfo(ModuleInfo.Directive moduleDirective, SourceSet sour if (javaModuleDependenciesExtension.getAnalyseOnly().get()) { return; } - ModuleInfo moduleInfo = javaModuleDependenciesExtension.getModuleInfoCache().get(sourceSet, project.getProviders()); + ModuleInfo moduleInfo = javaModuleDependenciesExtension.getModuleInfoCache().get().get(sourceSet, project.getProviders()); for (String moduleName : moduleInfo.get(moduleDirective)) { declareDependency(moduleName, moduleInfo.getFilePath(), project, sourceSet, configuration, javaModuleDependenciesExtension); } @@ -280,7 +280,7 @@ private void declareDependency(String moduleName, File moduleInfoFile, Project p } private List collectDependencies(Project project, JavaModuleDependenciesExtension javaModuleDependencies, SourceSet sourceSet, ModuleInfo.Directive directive, String scope) { - ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get(sourceSet, project.getProviders()); + ModuleInfo moduleInfo = javaModuleDependencies.getModuleInfoCache().get().get(sourceSet, project.getProviders()); if (moduleInfo == ModuleInfo.EMPTY) { // check if there is a whiltebox module-info we can use isntead File sourceSetDir = sourceSet.getJava().getSrcDirs().iterator().next().getParentFile(); diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 7f6c9d2..1858b9a 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -27,6 +27,7 @@ import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlatformPlugin; import org.gradle.util.GradleVersion; +import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; import org.gradlex.javamodule.dependencies.JavaModuleDependenciesPlugin; import org.gradlex.javamodule.dependencies.JavaModuleVersionsPlugin; import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo; @@ -52,7 +53,7 @@ public abstract class JavaModulesExtension { @Inject public JavaModulesExtension(Settings settings) { this.settings = settings; - this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class); + this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class, true); } public void module(String folder) { @@ -122,9 +123,9 @@ private void includeModule(Module module, File projectDir) { String group = module.getGroup().getOrNull(); List plugins = module.getPlugins().get(); if (SUPPORT_PROJECT_ISOLATION) { - settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName)); + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName, moduleInfoCache)); } else { - settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName)); + settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName, moduleInfoCache)); } } @@ -135,12 +136,14 @@ private static class ApplyPluginsAction implements IsolatedAction, Acti private final String group; private final List plugins; private final String mainModuleName; + private final ModuleInfoCache moduleInfoCache; - public ApplyPluginsAction(String artifact, @Nullable String group, List plugins, @Nullable String mainModuleName) { + public ApplyPluginsAction(String artifact, @Nullable String group, List plugins, @Nullable String mainModuleName, ModuleInfoCache moduleInfoCache) { this.artifact = artifact; this.group = group; this.plugins = plugins; this.mainModuleName = mainModuleName; + this.moduleInfoCache = moduleInfoCache; } @Override @@ -148,6 +151,7 @@ public void execute(Project project) { if (project.getName().equals(artifact)) { if (group != null) project.setGroup(group); project.getPlugins().apply(JavaModuleDependenciesPlugin.class); + project.getExtensions().getByType(JavaModuleDependenciesExtension.class).getModuleInfoCache().set(moduleInfoCache); plugins.forEach(id -> project.getPlugins().apply(id)); if (mainModuleName != null) { project.getPlugins().withType(ApplicationPlugin.class, p -> 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 77de9ab..92cea7e 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 @@ -32,6 +32,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<>(); @@ -39,6 +40,15 @@ public abstract class ModuleInfoCache { @Inject public abstract ObjectFactory getObjects(); + @Inject + public ModuleInfoCache(boolean initializedInSettings) { + this.initializedInSettings = initializedInSettings; + } + + public boolean isInitializedInSettings() { + return initializedInSettings; + } + /** * Returns the module-info.java for the given SourceSet. If the SourceSet has multiple source folders with multiple * module-info files (which is usually a broken setup) the first file found is returned. @@ -93,8 +103,4 @@ private boolean maybePutModuleInfo(File folder, ProviderFactory providers) { } return false; } - - public boolean initializedInSettings() { - return false; - } } From f3d779620639d18665d1dd72f0443e2aa97897da Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 09:40:49 +0200 Subject: [PATCH 04/12] Add header comments --- .../JavaModuleDependenciesSettingsPlugin.java | 16 ++++++++++++++++ .../dependencies/initialization/Module.java | 16 ++++++++++++++++ .../dependencies/initialization/Modules.java | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java index 0b4b91e..e733ca2 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java @@ -1,3 +1,19 @@ +/* + * 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.initialization; import org.gradle.api.NonNullApi; diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java index ee2ff1b..5aaec5b 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -1,3 +1,19 @@ +/* + * 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.initialization; import org.gradle.api.provider.ListProperty; diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java index 6d70ad5..eb497f7 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java @@ -1,3 +1,19 @@ +/* + * 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.initialization; import org.gradle.api.Action; From 4f914a8058b20cf3fd41bbec5465bd998d715ca8 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 13:52:03 +0200 Subject: [PATCH 05/12] Renames and tweaks --- .../JavaModuleDependenciesExtension.java | 4 +- .../{Modules.java => Directory.java} | 6 +-- .../initialization/JavaModulesExtension.java | 40 +++++++++---------- .../dependencies/initialization/Module.java | 6 +-- .../internal/utils/ModuleInfoCache.java | 14 +++++-- 5 files changed, 39 insertions(+), 31 deletions(-) rename src/main/java/org/gradlex/javamodule/dependencies/initialization/{Modules.java => Directory.java} (93%) diff --git a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java index 4957e9c..a06694a 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java @@ -220,7 +220,7 @@ public Provider create(String moduleName, SourceSet sourceSetWithMod } } - public Provider createPrecise(String moduleName, SourceSet sourceSetWithModuleInfo) { + private Provider createPrecise(String moduleName, SourceSet sourceSetWithModuleInfo) { return getProviders().provider(() -> { String projectPath = getModuleInfoCache().get().getProjectPath(moduleName); String capability = getModuleInfoCache().get().getCapability(moduleName); @@ -239,7 +239,7 @@ public Provider createPrecise(String moduleName, SourceSet sourceSet }); } - public Provider createWithGuessing(String moduleName, SourceSet sourceSetWithModuleInfo) { + private Provider createWithGuessing(String moduleName, SourceSet sourceSetWithModuleInfo) { return getProviders().provider(() -> { Map allProjectNamesAndGroups = getProject().getRootProject().getSubprojects().stream().collect( Collectors.toMap(Project::getName, p -> (String) p.getGroup(), (a, b) -> a)); diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java similarity index 93% rename from src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java rename to src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java index eb497f7..e6ed8f3 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Modules.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java @@ -26,7 +26,7 @@ import java.util.LinkedHashMap; import java.util.Map; -public abstract class Modules { +public abstract class Directory { private final File root; final Map customizedModules = new LinkedHashMap<>(); @@ -38,7 +38,7 @@ public abstract class Modules { public abstract ObjectFactory getObjects(); @Inject - public Modules(File root) { + public Directory(File root) { this.root = root; } @@ -54,7 +54,7 @@ public void module(String moduleFolder, Action action) { Module addModule(String moduleFolder) { Module module = getObjects().newInstance(Module.class, root); - module.getFolder().convention(moduleFolder); + module.getDirectory().convention(moduleFolder); module.getGroup().convention(getGroup()); module.getPlugins().addAll(getPlugins()); return module; diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 1858b9a..a3ed91e 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -56,43 +56,43 @@ public JavaModulesExtension(Settings settings) { this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class, true); } - public void module(String folder) { - module(folder, m -> {}); + public void module(String path) { + module(path, m -> {}); } - public void module(String folder, Action action) { + public void module(String path, Action action) { Module module = getObjects().newInstance(Module.class, settings.getRootDir()); - module.getFolder().set(folder); + module.getDirectory().set(path); action.execute(module); - includeModule(module, new File(settings.getRootDir(), module.getFolder().get())); + includeModule(module, new File(settings.getRootDir(), module.getDirectory().get())); } - public void modules(String folder) { - modules(folder, m -> {}); + public void directory(String path) { + directory(path, m -> {}); } - public void modules(String folder, Action action) { - Modules moduleGroup = getObjects().newInstance(Modules.class, new File(settings.getRootDir(), folder)); - action.execute(moduleGroup); + public void directory(String path, Action action) { + Directory directory = getObjects().newInstance(Directory.class, new File(settings.getRootDir(), path)); + action.execute(directory); - File[] projectFolders = new File(settings.getRootDir(), folder).listFiles(); - if (projectFolders == null) { - throw new RuntimeException("Failed to inspect: " + new File(settings.getRootDir(), folder)); + File[] projectDirs = new File(settings.getRootDir(), path).listFiles(); + if (projectDirs == null) { + throw new RuntimeException("Failed to inspect: " + new File(settings.getRootDir(), path)); } - for (File projectFolder : projectFolders) { - if (moduleGroup.customizedModules.containsKey(projectFolder.getName())) { - includeModule(moduleGroup.customizedModules.get(projectFolder.getName()), projectFolder); + for (File projectDir : projectDirs) { + if (directory.customizedModules.containsKey(projectDir.getName())) { + includeModule(directory.customizedModules.get(projectDir.getName()), projectDir); } else { - includeModule(moduleGroup.addModule(projectFolder.getName()), projectFolder); + includeModule(directory.addModule(projectDir.getName()), projectDir); } } } - public void versions(String folder) { - String projectName = Paths.get(folder).getFileName().toString(); + public void versions(String directory) { + String projectName = Paths.get(directory).getFileName().toString(); settings.include(projectName); - settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), folder)); + settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), directory)); if (SUPPORT_PROJECT_ISOLATION) { settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); } else { diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java index 5aaec5b..056afd2 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -27,7 +27,7 @@ import java.util.stream.Stream; public abstract class Module { - public abstract Property getFolder(); + public abstract Property getDirectory(); public abstract Property getArtifact(); public abstract Property getGroup(); public abstract ListProperty getModuleInfoPaths(); @@ -35,8 +35,8 @@ public abstract class Module { @Inject public Module(File root) { - getArtifact().convention(getFolder().map(f -> Paths.get(f).getFileName().toString())); - getModuleInfoPaths().convention(getFolder().map(projectDir -> listChildren(root, projectDir + "/src") + getArtifact().convention(getDirectory().map(f -> Paths.get(f).getFileName().toString())); + getModuleInfoPaths().convention(getDirectory().map(projectDir -> listChildren(root, projectDir + "/src") .map(srcDir -> new File(srcDir, "java/module-info.java")) .filter(File::exists) .map(moduleInfo -> "src/" + moduleInfo.getParentFile().getParentFile().getName() + "/java") 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 92cea7e..1899a46 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 @@ -17,10 +17,12 @@ package org.gradlex.javamodule.dependencies.internal.utils; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.File; @@ -31,6 +33,7 @@ import static org.gradlex.javamodule.dependencies.internal.utils.ModuleNamingUtil.sourceSetToCapabilitySuffix; public abstract class ModuleInfoCache { + private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(ModuleInfoCache.class); private final boolean initializedInSettings; private final Map moduleInfo = new HashMap<>(); @@ -74,9 +77,14 @@ public ModuleInfo put(File projectRoot, String moduleInfoPath, String artifact, if (maybePutModuleInfo(folder, providers)) { ModuleInfo thisModuleInfo = moduleInfo.get(folder); moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact); - String capabilitySuffix = sourceSetToCapabilitySuffix(Paths.get(moduleInfoPath).getFileName().toString()); - if (group.isPresent() && capabilitySuffix != null) { - moduleNameToCapability.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact + "-" + capabilitySuffix); + String capabilitySuffix = sourceSetToCapabilitySuffix(Paths.get(moduleInfoPath).getParent().getFileName().toString()); + if (capabilitySuffix != null) { + if (group.isPresent()) { + moduleNameToCapability.put(thisModuleInfo.getModuleName(), group.get() + ":" + artifact + "-" + capabilitySuffix); + } else { + LOGGER.lifecycle( + "[WARN] [Java Module Dependencies] " + thisModuleInfo.getModuleName() + " - 'group' not defined!"); + } } return thisModuleInfo; } From 5a8196f12c8bc11c88c1ff93f366a6d4161660d6 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 13:52:12 +0200 Subject: [PATCH 06/12] Add test --- .../test/SettingsPluginTest.groovy | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy diff --git a/src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy b/src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy new file mode 100644 index 0000000..6ccf8e3 --- /dev/null +++ b/src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy @@ -0,0 +1,138 @@ +package org.gradlex.javamodule.dependencies.test + +import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild +import spock.lang.Specification + +import static org.gradle.testkit.runner.TaskOutcome.NO_SOURCE +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class SettingsPluginTest extends Specification { + + @Delegate + GradleBuild build = new GradleBuild() + + def setup() { + settingsFile.text = ''' + plugins { id("org.gradlex.java-module-dependencies") } + ''' + appBuildFile.delete() + libBuildFile.delete() + } + + def "can define individual modules"() { + given: + settingsFile << ''' + javaModules { + module("app") { plugin("application") } + module("lib") { plugin("java-library") } + } + ''' + libModuleInfoFile << 'module abc.lib { }' + appModuleInfoFile << ''' + module org.gradlex.test.app { + requires abc.lib; + } + ''' + + when: + def result = runner(':app:compileJava').build() + + then: + result.task(":app:compileJava").outcome == SUCCESS + result.task(":lib:compileJava").outcome == SUCCESS + } + + def "finds all modules in a directory"() { + given: + settingsFile << ''' + javaModules { + directory(".") { plugin("java-library") } + } + ''' + libModuleInfoFile << 'module abc.lib { }' + appModuleInfoFile << ''' + module org.gradlex.test.app { + requires abc.lib; + } + ''' + + when: + def result = runner(':app:build').build() + + then: + result.task(":app:compileJava").outcome == SUCCESS + result.task(":lib:compileJava").outcome == SUCCESS + } + + def "automatically sets module for application plugin"() { + given: + settingsFile << ''' + javaModules { + directory(".") { + plugin("java-library") + module("app") { plugin("application") } + } + } + ''' + libModuleInfoFile << 'module abc.libxyz { }' + appModuleInfoFile << ''' + module org.gradlex.test.app { + requires abc.libxyz; + } + ''' + appBuildFile << 'application.mainClass = "app.App"' + file("app/src/main/java/app/App.java") << 'package app; public class App { public static void main(String[] args) { } }' + + when: + def result = runner(':app:run').build() + + then: + result.task(":app:run").outcome == SUCCESS + result.task(":app:compileJava").outcome == SUCCESS + result.task(":lib:compileJava").outcome == SUCCESS + } + + def "can depend on test fixtures module"() { + given: + settingsFile << ''' + javaModules { + directory(".") { + group = "bar.foo" + plugin("java-library") + plugin("java-test-fixtures") + } + } + ''' + libModuleInfoFile << 'module foo.bar.m { }' + file('lib/src/testFixtures/java/module-info.java') << 'module abc.libxyz.dsdsds { }' + appModuleInfoFile << ''' + module org.gradlex.test.app { + requires foo.bar.m; + requires abc.libxyz.dsdsds; + } + ''' + + when: + def result = runner(':app:compileJava').build() + + then: + result.task(":app:compileJava").outcome == SUCCESS + result.task(":lib:compileJava").outcome == SUCCESS + result.task(":lib:compileTestFixturesJava").outcome == SUCCESS + } + + def 'can apply root project plugin from settings'() { + settingsFile << ''' + rootPlugins { + id("java") + } + ''' + + when: + def result = runner(':compileJava').build() + + then: + result.task(":compileJava").outcome == NO_SOURCE + } + +} From de52ab9b238d7f9f981c4fb72b0d445d104126dd Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 14:49:11 +0200 Subject: [PATCH 07/12] Limit new feature to Gradle 8.8+ This is because: - IsolatedAction is not available earlier - Using extension accessors in settings file is not available earlier --- build.gradle.kts | 1 + .../JavaModuleDependenciesSettingsPlugin.java | 5 +++++ .../initialization/JavaModulesExtension.java | 15 ++------------- .../initialization/RootPluginsExtension.java | 8 +------- .../SettingsPluginTest.groovy | 2 +- 5 files changed, 10 insertions(+), 21 deletions(-) rename src/test/groovy/org/gradlex/javamodule/dependencies/test/{ => initialization}/SettingsPluginTest.groovy (98%) diff --git a/build.gradle.kts b/build.gradle.kts index f1f685f..a2c6319 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,7 @@ testing.suites.named("test") { description = "Runs tests against Gradle $gradleVersionUnderTest" systemProperty("gradleVersionUnderTest", gradleVersionUnderTest) exclude("**/*SamplesTest.class") // Not yet cross-version ready + exclude("**/initialization/**") // Settings plugin only for latest Gradle version } } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java index e733ca2..a8a5973 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModuleDependenciesSettingsPlugin.java @@ -16,15 +16,20 @@ package org.gradlex.javamodule.dependencies.initialization; +import org.gradle.api.GradleException; import org.gradle.api.NonNullApi; import org.gradle.api.Plugin; import org.gradle.api.initialization.Settings; +import org.gradle.util.GradleVersion; @NonNullApi public abstract class JavaModuleDependenciesSettingsPlugin implements Plugin { @Override public void apply(Settings settings) { + if (GradleVersion.current().compareTo(GradleVersion.version("8.8")) < 0) { + throw new GradleException("This settings plugin requires Gradle 8.8+"); + } registerExtension(settings); } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index a3ed91e..0a96ce2 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -41,9 +41,6 @@ public abstract class JavaModulesExtension { - static final boolean SUPPORT_PROJECT_ISOLATION = - GradleVersion.current().compareTo(GradleVersion.version("8.8")) >= 0; - private final Settings settings; private final ModuleInfoCache moduleInfoCache; @@ -93,11 +90,7 @@ public void versions(String directory) { String projectName = Paths.get(directory).getFileName().toString(); settings.include(projectName); settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), directory)); - if (SUPPORT_PROJECT_ISOLATION) { - settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); - } else { - settings.getGradle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); - } + settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); } private void includeModule(Module module, File projectDir) { @@ -122,11 +115,7 @@ private void includeModule(Module module, File projectDir) { String group = module.getGroup().getOrNull(); List plugins = module.getPlugins().get(); - if (SUPPORT_PROJECT_ISOLATION) { - settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName, moduleInfoCache)); - } else { - settings.getGradle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName, moduleInfoCache)); - } + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginsAction(artifact, group, plugins, mainModuleName, moduleInfoCache)); } @NonNullApi diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java index 74f0ceb..5e2a60d 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/RootPluginsExtension.java @@ -24,8 +24,6 @@ import javax.inject.Inject; -import static org.gradlex.javamodule.dependencies.initialization.JavaModulesExtension.SUPPORT_PROJECT_ISOLATION; - public abstract class RootPluginsExtension { private final Settings settings; @@ -36,11 +34,7 @@ public RootPluginsExtension(Settings settings) { } public void id(String id) { - if (SUPPORT_PROJECT_ISOLATION) { - settings.getGradle().getLifecycle().beforeProject(new ApplyPluginAction(id)); - } else { - settings.getGradle().beforeProject(new ApplyPluginAction(id)); - } + settings.getGradle().getLifecycle().beforeProject(new ApplyPluginAction(id)); } @NonNullApi diff --git a/src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy similarity index 98% rename from src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy rename to src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy index 6ccf8e3..a9dec51 100644 --- a/src/test/groovy/org/gradlex/javamodule/dependencies/test/SettingsPluginTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy @@ -1,4 +1,4 @@ -package org.gradlex.javamodule.dependencies.test +package org.gradlex.javamodule.dependencies.test.initialization import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild import spock.lang.Specification From 86236a0d4a82ccf6ecdd617ac1844fb2f6bf5c05 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 15 Jul 2024 15:07:15 +0200 Subject: [PATCH 08/12] Extend test coverage --- .../internal/utils/ModuleInfoCache.java | 4 ++- .../initialization/SettingsPluginTest.groovy | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) 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 1899a46..fc689b1 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 @@ -26,6 +26,7 @@ import javax.inject.Inject; import java.io.File; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -77,7 +78,8 @@ public ModuleInfo put(File projectRoot, String moduleInfoPath, String artifact, if (maybePutModuleInfo(folder, providers)) { ModuleInfo thisModuleInfo = moduleInfo.get(folder); moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact); - String capabilitySuffix = sourceSetToCapabilitySuffix(Paths.get(moduleInfoPath).getParent().getFileName().toString()); + 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); diff --git a/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy index a9dec51..e88379b 100644 --- a/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginTest.groovy @@ -24,7 +24,10 @@ class SettingsPluginTest extends Specification { settingsFile << ''' javaModules { module("app") { plugin("application") } - module("lib") { plugin("java-library") } + module("lib") { + plugin("java-library") + artifact = "lib-x" + } } ''' libModuleInfoFile << 'module abc.lib { }' @@ -39,7 +42,7 @@ class SettingsPluginTest extends Specification { then: result.task(":app:compileJava").outcome == SUCCESS - result.task(":lib:compileJava").outcome == SUCCESS + result.task(":lib-x:compileJava").outcome == SUCCESS } def "finds all modules in a directory"() { @@ -135,4 +138,31 @@ class SettingsPluginTest extends Specification { result.task(":compileJava").outcome == NO_SOURCE } + def 'can have module-info in custom location'() { + given: + settingsFile << ''' + javaModules { + module("app") { plugin("application") } + module("lib") { + plugin("java-library") + moduleInfoPaths.add("src") + } + } + ''' + libBuildFile << 'sourceSets.main { java.setSrcDirs(listOf("src")) }' + file("lib/src/module-info.java") << 'module abc.lib { }' + appModuleInfoFile << ''' + module org.gradlex.test.app { + requires abc.lib; + } + ''' + + when: + def result = runner(':app:compileJava').build() + + then: + result.task(":app:compileJava").outcome == SUCCESS + result.task(":lib:compileJava").outcome == SUCCESS + } + } From cdaf79f52fb9e30d3c41df1b4378c87b4087322a Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 16 Jul 2024 08:02:19 +0200 Subject: [PATCH 09/12] Support modules in nested folders inside a dircetory --- .../dependencies/initialization/Directory.java | 14 +++++++++----- .../initialization/JavaModulesExtension.java | 16 ++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java index e6ed8f3..9c0e20e 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java @@ -46,15 +46,19 @@ public void plugin(String id) { getPlugins().add(id); } - public void module(String moduleFolder, Action action) { - Module module = addModule(moduleFolder); + public void module(String subDirectory) { + module(subDirectory, m -> {}); + } + + public void module(String subDirectory, Action action) { + Module module = addModule(subDirectory); action.execute(module); - customizedModules.put(moduleFolder, module); + customizedModules.put(subDirectory, module); } - Module addModule(String moduleFolder) { + Module addModule(String subDirectory) { Module module = getObjects().newInstance(Module.class, root); - module.getDirectory().convention(moduleFolder); + module.getDirectory().convention(subDirectory); module.getGroup().convention(getGroup()); module.getPlugins().addAll(getPlugins()); return module; diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 0a96ce2..1d51696 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -38,6 +38,7 @@ import java.io.File; import java.nio.file.Paths; import java.util.List; +import java.util.Map; public abstract class JavaModulesExtension { @@ -69,18 +70,21 @@ public void directory(String path) { } public void directory(String path, Action action) { - Directory directory = getObjects().newInstance(Directory.class, new File(settings.getRootDir(), path)); + File modulesDirectory = new File(settings.getRootDir(), path); + Directory directory = getObjects().newInstance(Directory.class, modulesDirectory); action.execute(directory); - File[] projectDirs = new File(settings.getRootDir(), path).listFiles(); + File[] projectDirs = modulesDirectory.listFiles(); if (projectDirs == null) { - throw new RuntimeException("Failed to inspect: " + new File(settings.getRootDir(), path)); + throw new RuntimeException("Failed to inspect: " + modulesDirectory); + } + + for (Module module : directory.customizedModules.values()) { + includeModule(module, new File(modulesDirectory, module.getDirectory().get())); } for (File projectDir : projectDirs) { - if (directory.customizedModules.containsKey(projectDir.getName())) { - includeModule(directory.customizedModules.get(projectDir.getName()), projectDir); - } else { + if (!directory.customizedModules.containsKey(projectDir.getName())) { includeModule(directory.addModule(projectDir.getName()), projectDir); } } From cbacb9351f189072e4764b535c43368f13702c0c Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 16 Jul 2024 08:02:54 +0200 Subject: [PATCH 10/12] Tweak comment --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index a2c6319..f7ef998 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,7 +73,7 @@ testing.suites.named("test") { description = "Runs tests against Gradle $gradleVersionUnderTest" systemProperty("gradleVersionUnderTest", gradleVersionUnderTest) exclude("**/*SamplesTest.class") // Not yet cross-version ready - exclude("**/initialization/**") // Settings plugin only for latest Gradle version + exclude("**/initialization/**") // Settings plugin only for Gradle 8.8+ } } } From aec1f80b296587ea743dd265777bb0e3c1877944 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 16 Jul 2024 09:03:26 +0200 Subject: [PATCH 11/12] Add documentation --- README.MD | 179 +++++++++++------- .../initialization/Directory.java | 20 +- .../initialization/JavaModulesExtension.java | 56 ++++-- .../dependencies/initialization/Module.java | 37 +++- 4 files changed, 195 insertions(+), 97 deletions(-) diff --git a/README.MD b/README.MD index 4fc55bb..9ce7e83 100644 --- a/README.MD +++ b/README.MD @@ -3,11 +3,15 @@ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fgradlex-org%2Fjava-module-dependencies%2Fbadge%3Fref%3Dmain&style=flat)](https://actions-badge.atrox.dev/gradlex-org/java-module-dependencies/goto?ref=main) [![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v?label=Plugin%20Portal&metadataUrl=https%3A%2F%2Fplugins.gradle.org%2Fm2%2Forg%2Fgradlex%2Fjava-module-dependencies%2Forg.gradlex.java-module-dependencies.gradle.plugin%2Fmaven-metadata.xml)](https://plugins.gradle.org/plugin/org.gradlex.java-module-dependencies) -A Gradle 7.4+ plugin to make Gradle use dependencies from _module-info.java_ files automatically. +A Gradle plugin to make Gradle use dependencies from _module-info.java_ files automatically. If you have a project that fully uses Java Modules, you do **not** need to declare dependencies in the `dependencies { }` block anymore. Gradle will use the information from your `module-info.java` directly. -To manage the versions of Java Modules, the plugin conveniently integrates with +Minimal required Gradle version: +- **Gradle 7.4** if you **not** use the plugin in `settings.gradle.kts` +- **Gradle 8.8** to use the plugin in `settings.gradle.kts` and the [additional functionality](#project-structure-definition-when-using-this-plugin-as-settings-plugin) that comes with it. + +To manage the versions of Java Modules, the plugin integrates with [Platform Projects](https://docs.gradle.org/current/userguide/java_platform_plugin.html#sec:java_platform_usage) and [Dependency Versions Constraints](https://docs.gradle.org/current/userguide/dependency_constraints.html#sec:adding-constraints-transitive-deps) in general as well as [Version Catalogs](https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog). @@ -22,36 +26,28 @@ There is a [CHANGELOG.md](CHANGELOG.md). # Java Modules with Gradle -If you plan to build Java Modules with Gradle, you should consider using these plugins on top of Gradle core: +If you build Java Modules with Gradle, you should consider using these plugins on top of Gradle core: -- [`id("org.gradlex.java-module-dependencies")`](https://github.com/gradlex-org/java-module-dependencies) +- [`id("org.gradlex.java-module-dependencies")`](https://github.com/gradlex-org/java-module-dependencies) (this plugin) Avoid duplicated dependency definitions and get your Module Path under control - [`id("org.gradlex.java-module-testing")`](https://github.com/gradlex-org/java-module-testing) Proper test setup for Java Modules - [`id("org.gradlex.extra-java-module-info")`](https://github.com/gradlex-org/extra-java-module-info) - Only if your (existing) project cannot avoid using non-module legacy Jars - -[Here is a sample](https://github.com/gradlex-org/java-module-testing/tree/main/samples/use-all-java-module-plugins) -that shows all plugins in combination. + Only if you cannot avoid using non-module legacy Jars [In episodes 31, 32, 33 of Understanding Gradle](https://github.com/jjohannes/understanding-gradle) I explain what these plugins do and why they are needed. [](https://www.youtube.com/watch?v=X9u1taDwLSA&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) [](https://www.youtube.com/watch?v=T9U0BOlVc-c&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) [](https://www.youtube.com/watch?v=6rFEDcP8Noc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) -[Full Java Module System Project Setup](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup using these plugins. -[](https://www.youtube.com/watch?v=uRieSnovlVc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) - # How to use? -For a quick start, you can find some samples here: -* [samples/versions-in-platform](samples/versions-in-platform) -* [samples/versions-in-catalog](samples/versions-in-catalog) -* [samples/module-info-dsl](samples/module-info-dsl) -* [samples/module-info-dsl-no-platform](samples/module-info-dsl-no-platform) -* [samples/kotlin](samples/kotlin) +Working (example) projects to inspect: +- [java-module-system](https://github.com/jjohannes/java-module-system) contains a compact sample and further documentation +- [gradle-project-setup-howto](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup +- [hedera-services](https://github.com/hashgraph/hedera-services) is an open-source Java project using this plugin large scale -For general information about how to structure Gradle builds and apply community plugins like this one to all subprojects +For general information about how to structure Gradle builds and apply community plugins like this one you can check out my [Understanding Gradle video series](https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE). ## Plugin dependency @@ -61,7 +57,7 @@ Add this to the build file of your convention plugin's build ``` dependencies { - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") } ``` @@ -72,12 +68,14 @@ dependencies { ## Apply the plugin -In your convention plugin, apply the plugin. +The plugin can be used in two ways: + +1. As _Settings Plugin_ in `settings.gradle(.kts)` file **(recommended)** +2. As _Project Plugin_ in `build.gradle(.kts)` files (sometimes easier to add to existing setups) ``` plugins { - ... - id("org.gradlex.java-module-dependencies") + id("org.gradlex.java-module-dependencies") } ``` @@ -87,15 +85,54 @@ Once the plugin is applied, dependencies are automatically determined based on t ``` module org.example.mymodule { - requires com.fasterxml.jackson.core; // -> implementation("com.fasterxml.jackson.core:jackson-core") - requires transitive org.slf4j; // -> api("org.slf4j:slf4j-api") - requires static jakarta.servlet; // -> compileOnly("jakarta.servlet:jakarta.servlet-api") - requires /*runtime*/ org.slf4j.simple; // -> runtimeOnly("org.slf4j:slf4j-simple") + requires com.fasterxml.jackson.core; // -> implementation("com.fasterxml.jackson.core:jackson-core") + requires transitive org.slf4j; // -> api("org.slf4j:slf4j-api") + requires static jakarta.servlet; // -> compileOnly("jakarta.servlet:jakarta.servlet-api") + requires /*runtime*/ org.slf4j.simple; // -> runtimeOnly("org.slf4j:slf4j-simple") } ``` Note that `requires /*runtime*/` is a directive specifically supported by this plugin to allow the specification of _runtime only_ dependencies. +## Project structure definition when using this plugin as Settings Plugin + +The plugin offers a Gradle DSL extension to configure the location of _Java Modules_ in the project structure to be used +in the `settings.gradle(.kts)` file. It is an alternative to Gradle's native `include(...)` statement to configure +subprojects. The advantage of using this is that it is more compact than Gradle's `include(...)` and allows the plugin +to pick up more information during the [initialization phase](https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:initialization). +By this, the plugin is later able to establish dependencies between your own modules without making assumptions about +how they need to be named (which is different when you use the plugin as +[Project Plugin](#project-structure-definition-when-using-this-plugin-as-project-plugin)). + +``` +// settings.gradle(.kts) +javaModules { // use instead of 'include(...)' + module("module-a") // Module in directory, discovers 'src/*/java/module-info.java' files + + module("module-b") { + group = "org.example" // define group early so that all subprojects know all groups + artifact = "lib-x" // Gradle subproject name (if differnt than directory) + plugin("java-library") // apply plugin to the Module's subproject to omit 'build.gradle' + } + + directory("modules") { // Auto-include all Modules in subfolders of 'modules' + group = "org.example" // group for all Modules + plugin("java-library") // apply plugin to all Modules' subprojects + module("app") { ... } // individualise Module (only if needed) + } + + versions("gradle/versions") // subproject configured as Platform Project +} +``` + +## Project structure definition when using this plugin as Project Plugin + +In this setup, subprojects with Java Modules are configured as in any traditional Gradle build: by using the +`include(...)` statement in `settings.gradle(.kts)`. The plugin is then applied in all subprojects with Java Modules, +ideally through a convention plugin. If you use the plugin like this, it needs to [make some assumption](#naming-patterns-for-modules-in-the-build-if-used-as-project-plugin) +due to missing information and thus, for example, requires you to have the Gradle _project names_, _groups_ and _Java Module Names_ align. +The preferred way to use the plugin is to use it as [Settings Plugin](#project-structure-definition-when-using-this-plugin-as-settings-plugin). + ## Define additional module dependencies in build files With this plugin you move dependency definitions into `module-info.java` files and no longer use the `dependencies {}` block in build files. @@ -104,8 +141,8 @@ For this, the plugin offers an extension of Gradle's DSL to be used in `build.gr ``` mainModuleInfo { - runtimeOnly("org.slf4j.simple") // runtime only dependency for the 'main' module - annotationProcessor("dagger.compiler") // annotation processor dependency for the 'main' module + runtimeOnly("org.slf4j.simple") // runtime only dependency for the 'main' module + annotationProcessor("dagger.compiler") // annotation processor dependency for the 'main' module } ``` @@ -116,9 +153,9 @@ The only case where this should be used is for whitebox testing activated via th ``` testModuleInfo { - requires("org.assertj.core") - requires("org.hamcrest") - requires("org.junit.jupiter.api") + requires("org.assertj.core") + requires("org.hamcrest") + requires("org.junit.jupiter.api") } ``` @@ -142,18 +179,18 @@ org.apache.commons.lang3.test.fixtures=org.apache.commons:commons-lang3|test-fix ``` javaModuleDependencies { - // Module Name to Component GA Coordinates - moduleNameToGA.put("org.apache.commons.lang3", "org.apache.commons:commons-lang3") + // Module Name to Component GA Coordinates + moduleNameToGA.put("org.apache.commons.lang3", "org.apache.commons:commons-lang3") - // Module Name to Component GA Coordinates & Capability GA Coordinates - moduleNameToGA.put("org.apache.commons.lang3.test.fixtures", "org.apache.commons:commons-lang3|test-fixtures") + // Module Name to Component GA Coordinates & Capability GA Coordinates + moduleNameToGA.put("org.apache.commons.lang3.test.fixtures", "org.apache.commons:commons-lang3|test-fixtures") } ``` There is also the option to register a mapping for all Modules that share a common _name prefix_ and _group_. For example: `moduleNamePrefixToGroup.put("com.example.product.module.", "com.example.product")`. -## Naming patterns for Modules in the build +## Naming patterns for Modules in the build (if used as Project Plugin) This plugin makes the following assumption about _Module Names_ of your own Modules in the build to establish dependencies between them: @@ -177,22 +214,22 @@ For libraries that consist of multiple components and have a BOM for version man ``` plugins { - id("java-platform") - id("org.gradlex.java-module-versions") + id("java-platform") + id("org.gradlex.java-module-versions") } // Define versions for Modules via the Module Name moduleInfo { - version("org.apache.xmlbeans", "5.0.1") - version("org.slf4j", "2.0.7") - version("org.slf4j.simple", "2.0.7") + version("org.apache.xmlbeans", "5.0.1") + version("org.slf4j", "2.0.7") + version("org.slf4j.simple", "2.0.7") } // Use BOMs for Modules that are part of a library of multiple Modules javaPlatform.allowDependencies() dependencies { - api(platform("com.fasterxml.jackson:jackson-bom:2.13.2")) - api(platform("org.junit:junit-bom:5.8.2")) + api(platform("com.fasterxml.jackson:jackson-bom:2.13.2")) + api(platform("org.junit:junit-bom:5.8.2")) } ``` @@ -201,9 +238,9 @@ For example: ``` dependencies { - javaModuleDependencies { - testRuntimeOnly(ga("org.junit.jupiter.engine")) - } + javaModuleDependencies { + testRuntimeOnly(ga("org.junit.jupiter.engine")) + } } ``` @@ -216,13 +253,13 @@ Alternatively, versions can be defined in the `[version]` block of a [version ca ``` dependencyResolutionManagement { - versionCatalogs.create("libs") { - version("org.apache.xmlbeans", "5.0.1") - version("com.fasterxml.jackson.databind", "2.12.5") - version("org.slf4j", "2.0.7") - - version("org.junit.jupiter.api", "5.8.2") - } + versionCatalogs.create("libs") { + version("org.apache.xmlbeans", "5.0.1") + version("com.fasterxml.jackson.databind", "2.12.5") + version("org.slf4j", "2.0.7") + + version("org.junit.jupiter.api", "5.8.2") + } } ``` @@ -250,19 +287,19 @@ $ ./gradlew :app:recommendModuleVersions -q Latest Stable Versions of Java Modules - use in your platform project's build.gradle(.kts) ========================================================================================== moduleInfo { - version("com.fasterxml.jackson.annotation", "2.13.2") - version("com.fasterxml.jackson.core", "2.13.2") - version("com.fasterxml.jackson.databind", "2.13.2.2") - version("org.apache.logging.log4j", "2.17.2") - version("org.apache.xmlbeans", "5.0.3") - version("org.junit.jupiter.api", "5.8.2") - version("org.junit.jupiter.engine", "5.8.2") - version("org.junit.platform.commons", "1.8.2") - version("org.junit.platform.engine", "1.8.2") - version("org.junit.platform.launcher", "1.8.2") - version("org.opentest4j", "1.2.0") - version("org.slf4j", "1.7.36") - version("org.slf4j.simple", "1.7.36") + version("com.fasterxml.jackson.annotation", "2.13.2") + version("com.fasterxml.jackson.core", "2.13.2") + version("com.fasterxml.jackson.databind", "2.13.2.2") + version("org.apache.logging.log4j", "2.17.2") + version("org.apache.xmlbeans", "5.0.3") + version("org.junit.jupiter.api", "5.8.2") + version("org.junit.jupiter.engine", "5.8.2") + version("org.junit.platform.commons", "1.8.2") + version("org.junit.platform.engine", "1.8.2") + version("org.junit.platform.launcher", "1.8.2") + version("org.opentest4j", "1.2.0") + version("org.slf4j", "1.7.36") + version("org.slf4j.simple", "1.7.36") } ``` @@ -391,14 +428,14 @@ Module Name mappings for Jars that were patched with extra module info will be a ``` plugins { - id("org.gradlex.extra-java-module-info") - id("org.gradlex.java-module-dependencies") + id("org.gradlex.extra-java-module-info") + id("org.gradlex.java-module-dependencies") } extraJavaModuleInfo { - automaticModule("org.apache.commons:commons-math3", "commons.math3") - // Module Dependencies plugin automatically knows that - // 'commons.math3' now maps to 'org.apache.commons:commons-math3' + automaticModule("org.apache.commons:commons-math3", "commons.math3") + // Module Dependencies plugin automatically knows that + // 'commons.math3' now maps to 'org.apache.commons:commons-math3' } ``` diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java index 9c0e20e..9160e6e 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java @@ -31,25 +31,43 @@ public abstract class Directory { private final File root; final Map customizedModules = new LinkedHashMap<>(); + /** + * {@link Module#getGroup()} + */ public abstract Property getGroup(); + + /** + * {@link Module#plugin(String)} + */ public abstract ListProperty getPlugins(); @Inject - public abstract ObjectFactory getObjects(); + protected abstract ObjectFactory getObjects(); @Inject public Directory(File root) { this.root = root; } + /** + * {@link Module#plugin(String)} + */ public void plugin(String id) { getPlugins().add(id); } + /** + * {@link Directory#module(String, Action)} + */ public void module(String subDirectory) { module(subDirectory, m -> {}); } + /** + * Configure details of a Module in a subdirectory of this directory. + * Note that Modules that are located in direct children of this directory are discovered automatically and + * do not need to be explicitly mentioned. + */ public void module(String subDirectory, Action action) { Module module = addModule(subDirectory); action.execute(module); diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 1d51696..e0a6c94 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -25,8 +25,8 @@ import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaPlatformExtension; import org.gradle.api.plugins.JavaPlatformPlugin; -import org.gradle.util.GradleVersion; import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension; import org.gradlex.javamodule.dependencies.JavaModuleDependenciesPlugin; import org.gradlex.javamodule.dependencies.JavaModuleVersionsPlugin; @@ -38,7 +38,6 @@ import java.io.File; import java.nio.file.Paths; import java.util.List; -import java.util.Map; public abstract class JavaModulesExtension { @@ -54,42 +53,61 @@ public JavaModulesExtension(Settings settings) { this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class, true); } - public void module(String path) { - module(path, m -> {}); + /** + * {@link JavaModulesExtension#module(String, Action)} + */ + public void module(String directory) { + module(directory, m -> {}); } - public void module(String path, Action action) { + /** + * Register and configure Module located in the given folder, relative to the build root directory. + */ + public void module(String directory, Action action) { Module module = getObjects().newInstance(Module.class, settings.getRootDir()); - module.getDirectory().set(path); + module.getDirectory().set(directory); action.execute(module); includeModule(module, new File(settings.getRootDir(), module.getDirectory().get())); } - public void directory(String path) { - directory(path, m -> {}); + /** + * {@link JavaModulesExtension#directory(String, Action)} + */ + public void directory(String directory) { + directory(directory, m -> {}); } - public void directory(String path, Action action) { - File modulesDirectory = new File(settings.getRootDir(), path); - Directory directory = getObjects().newInstance(Directory.class, modulesDirectory); - action.execute(directory); + /** + * Register and configure ALL Modules located in direct subfolders of the given folder. + */ + public void directory(String directory, Action action) { + File modulesDirectory = new File(settings.getRootDir(), directory); + Directory moduleDirectory = getObjects().newInstance(Directory.class, modulesDirectory); + action.execute(moduleDirectory); File[] projectDirs = modulesDirectory.listFiles(); if (projectDirs == null) { throw new RuntimeException("Failed to inspect: " + modulesDirectory); } - for (Module module : directory.customizedModules.values()) { + for (Module module : moduleDirectory.customizedModules.values()) { includeModule(module, new File(modulesDirectory, module.getDirectory().get())); } for (File projectDir : projectDirs) { - if (!directory.customizedModules.containsKey(projectDir.getName())) { - includeModule(directory.addModule(projectDir.getName()), projectDir); + if (!moduleDirectory.customizedModules.containsKey(projectDir.getName())) { + Module module = moduleDirectory.addModule(projectDir.getName()); + if (!module.getModuleInfoPaths().get().isEmpty()) { + // only auto-include if there is at least one module-info.java + includeModule(module, projectDir); + } } } } + /** + * Configure a subproject as Platform for defining Module versions. + */ public void versions(String directory) { String projectName = Paths.get(directory).getFileName().toString(); settings.include(projectName); @@ -98,18 +116,13 @@ public void versions(String directory) { } private void includeModule(Module module, File projectDir) { - List modulePaths = module.getModuleInfoPaths().get(); - if (modulePaths.isEmpty()) { - return; - } - String artifact = module.getArtifact().get(); settings.include(artifact); ProjectDescriptor project = settings.project(":" + artifact); project.setProjectDir(projectDir); String mainModuleName = null; - for (String path : modulePaths) { + for (String path : module.getModuleInfoPaths().get()) { ModuleInfo moduleInfo = moduleInfoCache.put(projectDir, path, module.getArtifact().get(), module.getGroup(), settings.getProviders()); if (path.contains("/main/")) { @@ -168,6 +181,7 @@ public void execute(Project project) { if (projectName.equals(project.getName())) { project.getPlugins().apply(JavaPlatformPlugin.class); project.getPlugins().apply(JavaModuleVersionsPlugin.class); + project.getExtensions().getByType(JavaPlatformExtension.class).allowDependencies(); } } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java index 056afd2..5f98e7c 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -27,10 +27,35 @@ import java.util.stream.Stream; public abstract class Module { + + /** + * The directory, relative to the build root directory, in which the Module is located. + */ public abstract Property getDirectory(); + + /** + * The 'artifact' name of the Module. This corresponds to the Gradle subproject name. If the Module is published + * to a Maven repository, this is the 'artifact' in the 'group:artifact' identifier to address the published Jar. + */ public abstract Property getArtifact(); + + /** + * The 'group' of the Module. This corresponds to setting the 'group' property in a build.gradle file. If the + * Module is published to a Maven repository, this is the 'group' in the 'group:artifact' identifier to address + * the published Jar. The group needs to be configured here (rather than in build.gradle files) for the plugin + * to support additional Modules inside a subproject that other modules depend on, such as a 'testFixtures' module. + */ public abstract Property getGroup(); + + /** + * The paths of the module-info.java files inside the project directory. Usually, this does not need to be adjusted. + * By default, it contains all 'src/$sourceSetName/java/module-info.java' files that exist. + */ public abstract ListProperty getModuleInfoPaths(); + + /** + * {@link Module#plugin(String)} + */ public abstract ListProperty getPlugins(); @Inject @@ -43,12 +68,16 @@ public Module(File root) { .collect(Collectors.toList()))); } + /** + * Apply a plugin to the Module project. This is the same as using the 'plugins { }' block in the Module's + * build.gradle file. Applying plugins here allows you to omit build.gradle files completely. + */ + public void plugin(String id) { + getPlugins().add(id); + } + private Stream listChildren(File root, String projectDir) { File[] children = new File(root, projectDir).listFiles(); return children == null ? Stream.empty() : Arrays.stream(children); } - - public void plugin(String id) { - getPlugins().add(id); - } } From 19bfbb3ce9b73e3eee0fb0aac84db90fdff3c5db Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 16 Jul 2024 09:28:20 +0200 Subject: [PATCH 12/12] Update CHANGELOG and increase version to 1.7 --- CHANGELOG.md | 3 +++ build.gradle.kts | 2 +- samples/configuration-cache/build-logic/build.gradle.kts | 2 +- samples/kotlin/build-logic/build.gradle.kts | 2 +- .../gradle/plugins/build.gradle.kts | 2 +- samples/module-info-dsl/gradle/plugins/build.gradle.kts | 2 +- samples/versions-in-catalog/build-logic/build.gradle.kts | 2 +- samples/versions-in-platform/build-logic/build.gradle.kts | 2 +- 8 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b7df6..8c31082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Java Module Dependencies Gradle Plugin - Changelog +## Version 1.7 +* [#112](https://github.com/gradlex-org/java-module-dependencies/issues/114) Settings plugin to configure module locations and identity + ## Version 1.6.6 * [#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)) * [#111](https://github.com/gradlex-org/java-module-dependencies/issues/111) Fix: Do not use 'MapProperty.unset' (Thanks [TheGoesen](https://github.com/TheGoesen)) diff --git a/build.gradle.kts b/build.gradle.kts index f7ef998..a479ea7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "org.gradlex" -version = "1.6.6" +version = "1.7" tasks.withType().configureEach { options.release = 8 diff --git a/samples/configuration-cache/build-logic/build.gradle.kts b/samples/configuration-cache/build-logic/build.gradle.kts index 07bb93c..0de4c8d 100644 --- a/samples/configuration-cache/build-logic/build.gradle.kts +++ b/samples/configuration-cache/build-logic/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } dependencies { - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") } \ No newline at end of file diff --git a/samples/kotlin/build-logic/build.gradle.kts b/samples/kotlin/build-logic/build.gradle.kts index 84dd16d..eba30c6 100644 --- a/samples/kotlin/build-logic/build.gradle.kts +++ b/samples/kotlin/build-logic/build.gradle.kts @@ -3,6 +3,6 @@ plugins { } dependencies { - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21") } \ No newline at end of file diff --git a/samples/module-info-dsl-no-platform/gradle/plugins/build.gradle.kts b/samples/module-info-dsl-no-platform/gradle/plugins/build.gradle.kts index 7fcae29..6213452 100644 --- a/samples/module-info-dsl-no-platform/gradle/plugins/build.gradle.kts +++ b/samples/module-info-dsl-no-platform/gradle/plugins/build.gradle.kts @@ -4,6 +4,6 @@ plugins { dependencies { implementation("com.autonomousapps:dependency-analysis-gradle-plugin:1.21.0") - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") implementation("org.gradlex:java-module-testing:1.2.1") } \ No newline at end of file diff --git a/samples/module-info-dsl/gradle/plugins/build.gradle.kts b/samples/module-info-dsl/gradle/plugins/build.gradle.kts index 7fcae29..6213452 100644 --- a/samples/module-info-dsl/gradle/plugins/build.gradle.kts +++ b/samples/module-info-dsl/gradle/plugins/build.gradle.kts @@ -4,6 +4,6 @@ plugins { dependencies { implementation("com.autonomousapps:dependency-analysis-gradle-plugin:1.21.0") - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") implementation("org.gradlex:java-module-testing:1.2.1") } \ No newline at end of file diff --git a/samples/versions-in-catalog/build-logic/build.gradle.kts b/samples/versions-in-catalog/build-logic/build.gradle.kts index 07bb93c..0de4c8d 100644 --- a/samples/versions-in-catalog/build-logic/build.gradle.kts +++ b/samples/versions-in-catalog/build-logic/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } dependencies { - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") } \ No newline at end of file diff --git a/samples/versions-in-platform/build-logic/build.gradle.kts b/samples/versions-in-platform/build-logic/build.gradle.kts index 07bb93c..0de4c8d 100644 --- a/samples/versions-in-platform/build-logic/build.gradle.kts +++ b/samples/versions-in-platform/build-logic/build.gradle.kts @@ -3,5 +3,5 @@ plugins { } dependencies { - implementation("org.gradlex:java-module-dependencies:1.6.6") + implementation("org.gradlex:java-module-dependencies:1.7") } \ No newline at end of file