From 0f52ec61eb38df9c8994659dfde2965b5142d220 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Mon, 16 Sep 2024 16:31:08 +0200 Subject: [PATCH] Add configuration option for 'versionsProvidingConfiguration' This way, a build setup can be created with a Configuration available in ALL projects that resolves to the same version of every module used. Then the metadata input to the transform, to process 'requireAllDefinedDependencies', is the same for a certain Jar independent of the classpath it is used on. --- .../moduleinfo/ExtraJavaModuleInfoPlugin.java | 35 ++----- .../ExtraJavaModuleInfoPluginExtension.java | 1 + .../ExtraJavaModuleInfoTransform.java | 9 +- .../javamodule/moduleinfo/IdValidator.java | 4 + .../moduleinfo/PublishedMetadata.java | 96 +++++++++++++++---- 5 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java index 3cdc076..149c2b3 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java @@ -53,7 +53,6 @@ import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; import static org.gradle.api.attributes.Category.LIBRARY; @@ -195,16 +194,9 @@ private void registerTransform(String fileExtension, Project project, ExtraJavaM p.getMergeJarIds().set(artifacts.map(new IdExtractor())); p.getMergeJars().set(artifacts.map(new FileExtractor(project.getLayout()))); - p.getRequiresFromMetadata().set(project.provider(() -> sourceSets.stream().flatMap(s -> Stream.of( - s.getRuntimeClasspathConfigurationName(), - s.getCompileClasspathConfigurationName(), - s.getAnnotationProcessorConfigurationName() - )) - .flatMap(resolvable -> existingComponentsOfInterest(configurations.getByName(resolvable), extension)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k1)).entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, c -> new PublishedMetadata(c.getKey(), c.getValue(), project))) - )); - + Provider> componentsOfInterest = componentsOfInterest(extension); + p.getRequiresFromMetadata().set(componentsOfInterest.map(gaSet -> gaSet.stream() + .collect(Collectors.toMap(ga -> ga, ga -> new PublishedMetadata(ga, project, extension))))); p.getAdditionalKnownModules().set(extractFromModuleDependenciesPlugin(project)); }); t.getFrom().attribute(artifactType, fileExtension).attribute(javaModule, false); @@ -238,26 +230,17 @@ private Provider> extractFromModuleDependenciesPlugin(Projec }); } - private Stream> existingComponentsOfInterest(Configuration resolvable, ExtraJavaModuleInfoPluginExtension extension) { - Set componentsOfInterest = componentsOfInterest(extension); - if (componentsOfInterest.isEmpty()) { - return Stream.empty(); - } - - return resolvable.getIncoming().getResolutionResult().getAllComponents().stream() - .filter(c -> componentsOfInterest.contains(ga(c.getId()))) - .collect(Collectors.toMap(c -> c.getId().toString(), c -> resolvable)).entrySet().stream(); - } - - private static Set componentsOfInterest(ExtraJavaModuleInfoPluginExtension extension) { - return extension.getModuleSpecs().get().values().stream() + private static Provider> componentsOfInterest(ExtraJavaModuleInfoPluginExtension extension) { + return extension.getModuleSpecs().map(specs -> specs.values().stream() .filter(ExtraJavaModuleInfoPlugin::needsDependencies) .map(ModuleSpec::getIdentifier) - .collect(Collectors.toSet()); + .collect(Collectors.toSet())); } private static boolean needsDependencies(ModuleSpec moduleSpec) { - return moduleSpec instanceof ModuleInfo && ((ModuleInfo) moduleSpec).requireAllDefinedDependencies; + return moduleSpec instanceof ModuleInfo + && ((ModuleInfo) moduleSpec).requireAllDefinedDependencies + && IdValidator.isCoordinates(moduleSpec.getIdentifier()); } static String ga(ComponentIdentifier id) { diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java index 78e42b3..811901b 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java @@ -41,6 +41,7 @@ public abstract class ExtraJavaModuleInfoPluginExtension { public abstract Property getFailOnMissingModuleInfo(); public abstract Property getFailOnAutomaticModules(); public abstract Property getDeriveAutomaticModuleNamesFromFileNames(); + public abstract Property getVersionsProvidingConfiguration(); /** * Add full module information for a given Jar file. diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java index a30718d..0351af8 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java @@ -367,14 +367,19 @@ private byte[] addModuleInfo(ModuleInfo moduleInfo, Map> pr moduleVisitor.visitRequire("java.base", 0, null); if (moduleInfo.requireAllDefinedDependencies) { - String fullIdentifier = moduleInfo.getIdentifier() + ":" + version; - PublishedMetadata requires = getParameters().getRequiresFromMetadata().get().get(fullIdentifier); + String identifier = moduleInfo.getIdentifier(); + PublishedMetadata requires = getParameters().getRequiresFromMetadata().get().get(identifier); if (requires == null) { throw new RuntimeException("[requires directives from metadata] " + "Cannot find dependencies for '" + moduleInfo.getModuleName() + "'. " + "Are '" + moduleInfo.getIdentifier() + "' the correct component coordinates?"); } + if (requires.getErrorMessage() != null) { + throw new RuntimeException("[requires directives from metadata] " + + "Cannot read metadata for '" + moduleInfo.getModuleName() + "': " + + requires.getErrorMessage()); + } for (String ga : requires.getRequires()) { String depModuleName = gaToModuleName(ga); diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/IdValidator.java b/src/main/java/org/gradlex/javamodule/moduleinfo/IdValidator.java index 2981440..12473e3 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/IdValidator.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/IdValidator.java @@ -25,4 +25,8 @@ static void validateIdentifier(String identifier) { throw new RuntimeException("'" + identifier + "' are not valid coordinates (group:name) / is not a valid file name (name-1.0.jar)"); } } + + static boolean isCoordinates(String identifier) { + return identifier.matches(COORDINATES_PATTERN); + } } diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java b/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java index 40a24da..896e700 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java @@ -18,12 +18,21 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.result.DependencyResult; -import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.artifacts.result.ResolvedDependencyResult; +import org.gradle.api.artifacts.result.UnresolvedDependencyResult; import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; +import org.gradle.api.attributes.java.TargetJvmEnvironment; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.util.GradleVersion; import java.io.Serializable; import java.util.ArrayList; @@ -31,6 +40,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; import static org.gradle.api.attributes.Category.LIBRARY; @@ -38,16 +48,19 @@ public class PublishedMetadata implements Serializable { private static final Attribute CATEGORY_ATTRIBUTE_UNTYPED = Attribute.of(CATEGORY_ATTRIBUTE.getName(), String.class); + private static final String DEFAULT_VERSION_SOURCE_CONFIGURATION = "definedDependenciesVersions"; private final String gav; private final List requires = new ArrayList<>(); private final List requiresTransitive = new ArrayList<>(); private final List requiresStaticTransitive = new ArrayList<>(); + private String errorMessage = null; - PublishedMetadata(String gav, Configuration origin, Project project) { + PublishedMetadata(String gav, Project project, ExtraJavaModuleInfoPluginExtension extension) { this.gav = gav; - List compileDependencies = componentVariant(origin, project, Usage.JAVA_API); - List runtimeDependencies = componentVariant(origin, project, Usage.JAVA_RUNTIME); + + List compileDependencies = componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_API); + List runtimeDependencies = componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_RUNTIME); Stream.concat(compileDependencies.stream(), runtimeDependencies.stream()).distinct().forEach(ga -> { if (compileDependencies.contains(ga) && runtimeDependencies.contains(ga)) { @@ -60,26 +73,73 @@ public class PublishedMetadata implements Serializable { }); } - private List componentVariant(Configuration origin, Project project, String usage) { + private List componentVariant(Provider versionsProvidingConfiguration, Project project, String usage) { + Configuration versionsSource; + if (versionsProvidingConfiguration.isPresent()) { + versionsSource = project.getConfigurations().getByName(versionsProvidingConfiguration.get()); + } else { + // version provider is not configured, create on adhoc based on ALL classpaths of the project + versionsSource = maybeCreateDefaultVersionSourcConfiguration(project.getConfigurations(), project.getObjects(), + project.getExtensions().findByType(SourceSetContainer.class)); + } + Configuration singleComponentVariantResolver = project.getConfigurations().detachedConfiguration(project.getDependencies().create(gav)); singleComponentVariantResolver.setCanBeConsumed(false); - singleComponentVariantResolver.shouldResolveConsistentlyWith(origin); - origin.getAttributes().keySet().forEach(a -> { + singleComponentVariantResolver.shouldResolveConsistentlyWith(versionsSource); + versionsSource.getAttributes().keySet().forEach(a -> { @SuppressWarnings("rawtypes") Attribute untypedAttributeKey = a; //noinspection unchecked - singleComponentVariantResolver.getAttributes().attribute(untypedAttributeKey, requireNonNull(origin.getAttributes().getAttribute(a))); + singleComponentVariantResolver.getAttributes().attribute(untypedAttributeKey, requireNonNull(versionsSource.getAttributes().getAttribute(a))); }); singleComponentVariantResolver.getAttributes().attribute(USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, usage)); - return firstAndOnlyComponent(singleComponentVariantResolver).getDependencies().stream() - .filter(PublishedMetadata::filterComponentDependencies) - .map(PublishedMetadata::ga) - .collect(Collectors.toList()); + return firstAndOnlyComponentDependencies(singleComponentVariantResolver); + } + + private Configuration maybeCreateDefaultVersionSourcConfiguration(ConfigurationContainer configurations, ObjectFactory objects, SourceSetContainer sourceSets) { + String name = DEFAULT_VERSION_SOURCE_CONFIGURATION; + Configuration existing = configurations.findByName(name); + if (existing != null) { + return existing; + } + + return configurations.create(name, c -> { + c.setCanBeResolved(true); + c.setCanBeConsumed(false); + c.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME)); + c.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY)); + c.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR)); + c.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.EXTERNAL)); + if (GradleVersion.current().compareTo(GradleVersion.version("7.0")) >= 0) { + c.getAttributes().attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, + objects.named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); + } + + if (sourceSets != null) { + for (SourceSet sourceSet : sourceSets) { + Configuration implementation = configurations.getByName(sourceSet.getImplementationConfigurationName()); + Configuration compileOnly = configurations.getByName(sourceSet.getCompileOnlyConfigurationName()); + Configuration runtimeOnly = configurations.getByName(sourceSet.getRuntimeOnlyConfigurationName()); + Configuration annotationProcessor = configurations.getByName(sourceSet.getAnnotationProcessorConfigurationName()); + c.extendsFrom(implementation, compileOnly, runtimeOnly, annotationProcessor); + } + } + }); } - private ResolvedComponentResult firstAndOnlyComponent(Configuration singleComponentVariantResolver) { - ResolvedDependencyResult onlyResult = (ResolvedDependencyResult) singleComponentVariantResolver.getIncoming().getResolutionResult() - .getRoot().getDependencies().iterator().next(); - return onlyResult.getSelected(); + private List firstAndOnlyComponentDependencies(Configuration singleComponentVariantResolver) { + DependencyResult result = singleComponentVariantResolver + .getIncoming().getResolutionResult().getRoot() + .getDependencies().iterator().next(); + + if (result instanceof UnresolvedDependencyResult) { + errorMessage = ((UnresolvedDependencyResult) result).getFailure().getMessage(); + return emptyList(); + } else { + return ((ResolvedDependencyResult) result).getSelected().getDependencies().stream() + .filter(PublishedMetadata::filterComponentDependencies) + .map(PublishedMetadata::ga) + .collect(Collectors.toList()); + } } private static boolean filterComponentDependencies(DependencyResult d) { @@ -113,4 +173,8 @@ public List getRequiresTransitive() { public List getRequiresStaticTransitive() { return requiresStaticTransitive; } + + public String getErrorMessage() { + return errorMessage; + } }