Skip to content

Commit

Permalink
Add DSL for module dependencies that cannot be defined in module-info
Browse files Browse the repository at this point in the history
  • Loading branch information
jjohannes committed Jun 13, 2023
1 parent 6411b61 commit ffe75b9
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package org.gradlex.javamodule.dependencies;

import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import org.gradle.api.artifacts.VersionConstraint;
Expand All @@ -41,11 +44,14 @@
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfoCache;

import javax.inject.Inject;
import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static java.util.Optional.empty;

Expand Down Expand Up @@ -142,6 +148,53 @@ private Provider<String> mapByPrefix(Provider<String> moduleName) {
);
}

public Provider<Dependency> create(String moduleName, SourceSet sourceSetWithModuleInfo) {
return getProviders().provider(() -> {
Map<String, String> allProjectNamesAndGroups = getProject().getRootProject().getSubprojects().stream().collect(
Collectors.toMap(Project::getName, p -> (String) p.getGroup()));

Provider<Map<String, Object>> gav = getModuleNameToGA().getting(moduleName).orElse(mapByPrefix(getProviders().provider(() -> moduleName))).map(ga -> findGav(ga, moduleName));

ModuleInfo moduleInfo = getModuleInfoCache().get(sourceSetWithModuleInfo);
String ownModuleNamesPrefix = moduleInfo.moduleNamePrefix(getProject().getName(), sourceSetWithModuleInfo.getName());

String moduleNameSuffix = ownModuleNamesPrefix == null ? null :
moduleName.startsWith(ownModuleNamesPrefix + ".") ? moduleName.substring(ownModuleNamesPrefix.length() + 1) :
ownModuleNamesPrefix.isEmpty() ? moduleName : null;

String parentPath = getProject().getParent() == null ? "" : getProject().getParent().getPath();
Optional<String> perfectMatch = allProjectNamesAndGroups.keySet().stream().filter(p -> p.replace("-", ".").equals(moduleNameSuffix)).findFirst();
Optional<String> existingProjectName = allProjectNamesAndGroups.keySet().stream().filter(p -> moduleNameSuffix != null && moduleNameSuffix.startsWith(p.replace("-", ".") + "."))
.max(Comparator.comparingInt(String::length));

if (perfectMatch.isPresent()) {
Dependency projectDependency = getDependencies().create(getProject().project(parentPath + ":" + perfectMatch.get()));
projectDependency.because(moduleName);
return projectDependency;
} else if (existingProjectName.isPresent()) {
// no exact match -> add capability to point at Module in other source set
String projectName = existingProjectName.get();
ProjectDependency projectDependency = (ProjectDependency) getDependencies().create(getProject().project(parentPath + ":" + projectName));
String capabilityName = projectName + moduleNameSuffix.substring(projectName.length()).replace(".", "-");
projectDependency.capabilities(c -> c.requireCapabilities(
allProjectNamesAndGroups.get(projectName) + ":" + capabilityName));
projectDependency.because(moduleName);
return projectDependency;
} else if (gav.isPresent()) {
Dependency dependency = getDependencies().create(gav.get());
dependency.because(moduleName);
if (!gav.get().containsKey(GAV.VERSION)) {
warnVersionMissing(moduleName, gav.get(), moduleInfo.getFilePath());
}
return dependency;
} else {
getProject().getLogger().lifecycle(
"[WARN] [Java Module Dependencies] javaModuleDependencies.moduleNameToGA.put(\"" + moduleName + "\", \"group:artifact\") mapping is missing.");
return null;
}
});
}

/**
* Converts 'Module Name' and 'Version' to GAV coordinates that can be used in
* dependency declarations as String: "group:name:version"
Expand Down Expand Up @@ -179,10 +232,6 @@ public Provider<Map<String, Object>> gav(String moduleName) {
return ga(moduleName).map(ga -> findGav(ga, moduleName));
}

Provider<Map<String, Object>> gavNoError(String moduleName) {
return getModuleNameToGA().getting(moduleName).orElse(mapByPrefix(getProviders().provider(() -> moduleName))).map(ga -> findGav(ga, moduleName));
}

/**
* If a Version Catalog is used:
* Converts 'Module Name' and the matching 'Version' from the Version Catalog to
Expand Down Expand Up @@ -308,6 +357,23 @@ private <T> Provider<T> errorIfNotFound(Provider<String> moduleName) {
});
}

private void warnVersionMissing(String moduleName, Map<String, Object> ga, File moduleInfoFile) {
if (getWarnForMissingVersions().get()) {
getProject().getLogger().warn("[WARN] [Java Module Dependencies] No version defined in catalog - " + ga.get(GAV.GROUP) + ":" + ga.get(GAV.ARTIFACT) + " - "
+ moduleDebugInfo(moduleName.replace('.', '_'), moduleInfoFile, getProject().getRootDir()));
}
}

private String moduleDebugInfo(String moduleName, File moduleInfoFile, File rootDir) {
return moduleName
+ " (required in "
+ moduleInfoFile.getAbsolutePath().substring(rootDir.getAbsolutePath().length() + 1)
+ ")";
}

@Inject
protected abstract Project getProject();

@Inject
protected abstract ObjectFactory getObjects();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.util.GradleVersion;
import org.gradlex.javamodule.dependencies.dsl.AllDirectives;
import org.gradlex.javamodule.dependencies.dsl.GradleOnlyDirectives;
import org.gradlex.javamodule.dependencies.internal.bridges.DependencyAnalysisBridge;
import org.gradlex.javamodule.dependencies.internal.bridges.ExtraJavaModuleInfoBridge;
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo;
Expand All @@ -41,13 +41,8 @@
import org.gradlex.javamodule.dependencies.tasks.ModulePathAnalysis;
import org.gradlex.javamodule.dependencies.tasks.ModuleVersionRecommendation;

import javax.annotation.Nullable;
import java.io.File;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.gradle.api.plugins.HelpTasksPlugin.HELP_GROUP;
import static org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP;
Expand Down Expand Up @@ -99,6 +94,8 @@ private void setupForJavaProject(Project project, JavaModuleDependenciesExtensio
t.setDescription("Check scope and order of directives in 'module-info.java' files");
});

setupDirectivesDSL(project, javaModuleDependencies);

setupOrderingCheckTasks(project, checkAllModuleInfo, javaModuleDependencies);
setupModuleDependenciesTask(project);
setupReportTasks(project, javaModuleDependencies);
Expand All @@ -114,6 +111,21 @@ private void setupExtraJavaModulePluginBridge(Project project, JavaModuleDepende
e -> ExtraJavaModuleInfoBridge.autoRegisterPatchedModuleMappings(project, javaModuleDependencies));
}

private void setupDirectivesDSL(Project project, JavaModuleDependenciesExtension javaModuleDependencies) {
ExtensionContainer extensions = project.getExtensions();
SourceSetContainer sourceSets = extensions.getByType(SourceSetContainer.class);
SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
sourceSets.all(sourceSet -> {
File moduleInfoFile = new File(sourceSet.getJava().getSrcDirs().iterator().next(), "module-info.java");
String extensionName = sourceSet.getName() + "ModuleInfo";
if (moduleInfoFile.exists()) {
extensions.create(extensionName, GradleOnlyDirectives.class, sourceSet, mainSourceSet, javaModuleDependencies);
} else {
extensions.create(extensionName, AllDirectives.class, sourceSet, mainSourceSet, javaModuleDependencies);
}
});
}

private void setupModuleDependenciesTask(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
TaskProvider<ModuleDependencyReport> moduleDependencies = project.getTasks().register("moduleDependencies", ModuleDependencyReport.class, t -> {
Expand Down Expand Up @@ -235,69 +247,18 @@ private void readModuleInfo(ModuleInfo.Directive moduleDirective, SourceSet sour
return;
}
ModuleInfo moduleInfo = javaModuleDependenciesExtension.getModuleInfoCache().get(sourceSet);
String ownModuleNamesPrefix = moduleInfo.moduleNamePrefix(project.getName(), sourceSet.getName());
for (String moduleName : moduleInfo.get(moduleDirective)) {
declareDependency(moduleName, ownModuleNamesPrefix, moduleInfo.getFilePath(), project, configuration, javaModuleDependenciesExtension);
declareDependency(moduleName, moduleInfo.getFilePath(), project, sourceSet, configuration, javaModuleDependenciesExtension);
}
}

private void declareDependency(String moduleName, @Nullable String ownModuleNamesPrefix, File moduleInfoFile, Project project, Configuration configuration, JavaModuleDependenciesExtension javaModuleDependencies) {
private void declareDependency(String moduleName, File moduleInfoFile, Project project, SourceSet sourceSet, Configuration configuration, JavaModuleDependenciesExtension javaModuleDependencies) {
if (JDKInfo.MODULES.contains(moduleName)) {
// The module is part of the JDK, no dependency required
return;
}

Map<String, String> allProjectNamesAndGroups = project.getRootProject().getSubprojects().stream().collect(
Collectors.toMap(Project::getName, p -> (String) p.getGroup()));

Provider<Map<String, Object>> gav = javaModuleDependencies.gavNoError(moduleName);
String moduleNameSuffix = ownModuleNamesPrefix == null ? null :
moduleName.startsWith(ownModuleNamesPrefix + ".") ? moduleName.substring(ownModuleNamesPrefix.length() + 1) :
ownModuleNamesPrefix.isEmpty() ? moduleName : null;

String parentPath = project.getParent() == null ? "" : project.getParent().getPath();
Optional<String> perfectMatch = allProjectNamesAndGroups.keySet().stream().filter(p -> p.replace("-", ".").equals(moduleNameSuffix)).findFirst();
Optional<String> existingProjectName = allProjectNamesAndGroups.keySet().stream().filter(p -> moduleNameSuffix != null && moduleNameSuffix.startsWith(p.replace("-", ".") + "."))
.max(Comparator.comparingInt(String::length));

if (perfectMatch.isPresent()) {
Dependency projectDependency = project.getDependencies().add(
configuration.getName(), project.project(parentPath + ":" + perfectMatch.get()));
assert projectDependency != null;
projectDependency.because(moduleName);
} else if (existingProjectName.isPresent()) {
// no exact match -> add capability to point at Module in other source set
String projectName = existingProjectName.get();
ProjectDependency projectDependency = (ProjectDependency) project.getDependencies().add(
configuration.getName(), project.project(parentPath + ":" + projectName));
assert projectDependency != null;
String capabilityName = projectName + moduleNameSuffix.substring(projectName.length()).replace(".", "-");
projectDependency.capabilities(c -> c.requireCapabilities(
allProjectNamesAndGroups.get(projectName) + ":" + capabilityName));
projectDependency.because(moduleName);
} else if (gav.isPresent()) {
project.getDependencies().addProvider(configuration.getName(), gav, d -> d.because(moduleName));
if (!gav.get().containsKey(GAV.VERSION)) {
warnVersionMissing(moduleName, gav.get(), moduleInfoFile, project, javaModuleDependencies);
}
} else {
project.getLogger().lifecycle(
"[WARN] [Java Module Dependencies] javaModuleDependencies.moduleNameToGA.put(\"" + moduleName + "\", \"group:artifact\") mapping is missing.");
}
}

private void warnVersionMissing(String moduleName, Map<String, Object> ga, File moduleInfoFile, Project project, JavaModuleDependenciesExtension javaModuleDependencies) {
if (javaModuleDependencies.getWarnForMissingVersions().get()) {
project.getLogger().warn("[WARN] [Java Module Dependencies] No version defined in catalog - " + ga.get(GAV.GROUP) + ":" + ga.get(GAV.ARTIFACT) + " - "
+ moduleDebugInfo(moduleName.replace('.', '_'), moduleInfoFile, project.getRootDir()));
}
}

private String moduleDebugInfo(String moduleName, File moduleInfoFile, File rootDir) {
return moduleName
+ " (required in "
+ moduleInfoFile.getAbsolutePath().substring(rootDir.getAbsolutePath().length() + 1)
+ ")";
project.getDependencies().addProvider(configuration.getName(), javaModuleDependencies.create(moduleName, sourceSet));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.gradlex.javamodule.dependencies.dsl;

import org.gradle.api.tasks.SourceSet;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension;

abstract public class AllDirectives extends GradleOnlyDirectives {

public AllDirectives(SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) {
super(sourceSet, mainSourceSet, javaModuleDependencies);
}

public void requires(String moduleName) {
add(sourceSet.getImplementationConfigurationName(), moduleName);
}

public void requiresTransitive(String moduleName) {
add(sourceSet.getApiConfigurationName(), moduleName);
}

public void requiresStatic(String moduleName) {
add(sourceSet.getCompileOnlyConfigurationName(), moduleName);
}

public void requiresStaticTransitive(String moduleName) {
add(sourceSet.getCompileOnlyApiConfigurationName(), moduleName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.gradlex.javamodule.dependencies.dsl;

import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.tasks.SourceSet;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension;

import javax.inject.Inject;

public abstract class GradleOnlyDirectives {

@Inject
protected abstract DependencyHandler getDependencies();

protected final SourceSet sourceSet;
protected final SourceSet mainSourceSet;
protected final JavaModuleDependenciesExtension javaModuleDependencies;

public GradleOnlyDirectives(SourceSet sourceSet, SourceSet mainSourceSet, JavaModuleDependenciesExtension javaModuleDependencies) {
this.sourceSet = sourceSet;
this.mainSourceSet = mainSourceSet;
this.javaModuleDependencies = javaModuleDependencies;
}

protected void add(String scope, String moduleName) {
getDependencies().addProvider(scope, javaModuleDependencies.create(moduleName, mainSourceSet));
}

public void runtimeOnly(String moduleName) {
add(sourceSet.getRuntimeOnlyConfigurationName(), moduleName);
}

public void annotationProcessor(String moduleName) {
add(sourceSet.getAnnotationProcessorConfigurationName(), moduleName);
}
}

0 comments on commit ffe75b9

Please sign in to comment.