Skip to content

Commit eaadb8e

Browse files
authored
Add configuration option for 'versionsProvidingConfiguration' (#142)
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.
1 parent 3c0cdba commit eaadb8e

File tree

9 files changed

+255
-46
lines changed

9 files changed

+255
-46
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Extra Java Module Info Gradle Plugin - Changelog
22

3+
## Version 1.9
4+
* [New] [#137](https://github.com/gradlex-org/extra-java-module-info/pull/137) - Configuration option for 'versionsProvidingConfiguration'
5+
* [New] [#130](https://github.com/gradlex-org/extra-java-module-info/pull/130) - Support classifier in coordinates notation
6+
* [New] [#138](https://github.com/gradlex-org/extra-java-module-info/pull/138) - 'javaModulesMergeJars' extends 'internal' if available
7+
* [Fixed] [#129](https://github.com/gradlex-org/extra-java-module-info/pull/129) - Find Jar for coordinates when version in Jar nam differs
8+
* [Fixed] [#100](https://github.com/gradlex-org/extra-java-module-info/pull/100) - Fix error message about automatic module name mismatch
9+
310
## Version 1.8
411
* [New] [#99](https://github.com/gradlex-org/extra-java-module-info/issues/99) - Default behavior for 'module(id, name)' notation without configuration block
512
* [New] - Use custom mappings from 'java-module-dependencies' for 'known modules' (if available)

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,64 @@ extraJavaModuleInfo {
199199
}
200200
```
201201

202+
## How can I avoid that the same Jar is transformed multiple times when using requireAllDefinedDependencies?
203+
204+
When using the `requireAllDefinedDependencies` option, all metadata of the dependencies on your classpath is input to the Jar transformation.
205+
In a multi-project however, each subproject typically has different classpaths and not all metadata is available everywhere.
206+
This leads to a situation, where Gradle's transformation system does not know if transforming the same Jar will lead to the same result.
207+
Then, the same Jar is transformed many times. This is not necessary a problem, as the results of the transforms are cached
208+
and do not run on every build invocation. However, the effect of this is still visible:
209+
for example when you import the project in IntelliJ IDEA.
210+
You see the same dependency many times in the _External Libraries_ list and IntelliJ is doing additional indexing work.
211+
212+
To circumvent this, you need to construct a common classpath – as a _resolvable configuration_ – that the transform can use.
213+
This needs to be done in all subprojects. You use the `versionsProvidingConfiguration` to tell the plugin about the commons classpath.
214+
215+
```
216+
extraJavaModuleInfo {
217+
versionsProvidingConfiguration = "mainRuntimeClasspath"
218+
}
219+
```
220+
221+
To create such a common classpath, some setup work is needed.
222+
And it depends on your overall project structure if and how to do that.
223+
Here is an example setup you may use:
224+
225+
```
226+
val consistentResolutionAttribute = Attribute.of("consistent-resolution", String::class.java)
227+
228+
// Define an Outgoing Variant (aka Consumable Configuration) that knows about all dependencies
229+
configurations.create("allDependencies") {
230+
isCanBeConsumed = true
231+
isCanBeResolved = false
232+
sourceSets.all {
233+
extendsFrom(
234+
configurations[this.implementationConfigurationName],
235+
configurations[this.compileOnlyConfigurationName],
236+
configurations[this.runtimeOnlyConfigurationName],
237+
configurations[this.annotationProcessorConfigurationName]
238+
)
239+
}
240+
attributes { attribute(consistentResolutionAttribute, "global") }
241+
}
242+
243+
// Define a "global claspath" (as Resolvable Configuration)
244+
val mainRuntimeClasspath = configurations.create("mainRuntimeClasspath") {
245+
isCanBeConsumed = false
246+
isCanBeResolved = true
247+
attributes.attribute(consistentResolutionAttribute, "global")
248+
}
249+
250+
// Add a dependency to the 'main' project(s) (:app ins this example) that transitively
251+
// depend on all subprojects to create a depenedency graph wih "everything"
252+
dependencies { mainRuntimeClasspath(project(":app")) }
253+
254+
// Use the global classpath for consisten resolution (optional)
255+
configurations.runtimeClasspath {
256+
shouldResolveConsistentlyWith(mainRuntimeClasspath)
257+
}
258+
```
259+
202260
## I have many automatic modules in my project. How can I convert them into proper modules and control what they export or require?
203261

204262
The plugin provides a set of `<sourceSet>moduleDescriptorRecommendations` tasks that generate the real module declarations utilizing [jdeps](https://docs.oracle.com/en/java/javase/11/tools/jdeps.html) and dependency metadata.

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPlugin.java

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import java.util.Properties;
5454
import java.util.Set;
5555
import java.util.stream.Collectors;
56-
import java.util.stream.Stream;
5756

5857
import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE;
5958
import static org.gradle.api.attributes.Category.LIBRARY;
@@ -202,16 +201,9 @@ private void registerTransform(String fileExtension, Project project, ExtraJavaM
202201
p.getMergeJarIds().set(artifacts.map(new IdExtractor()));
203202
p.getMergeJars().set(artifacts.map(new FileExtractor(project.getLayout())));
204203

205-
p.getRequiresFromMetadata().set(project.provider(() -> sourceSets.stream().flatMap(s -> Stream.of(
206-
s.getRuntimeClasspathConfigurationName(),
207-
s.getCompileClasspathConfigurationName(),
208-
s.getAnnotationProcessorConfigurationName()
209-
))
210-
.flatMap(resolvable -> existingComponentsOfInterest(configurations.getByName(resolvable), extension))
211-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k1)).entrySet().stream()
212-
.collect(Collectors.toMap(Map.Entry::getKey, c -> new PublishedMetadata(c.getKey(), c.getValue(), project)))
213-
));
214-
204+
Provider<Set<String>> componentsOfInterest = componentsOfInterest(extension);
205+
p.getRequiresFromMetadata().set(componentsOfInterest.map(gaSet -> gaSet.stream()
206+
.collect(Collectors.toMap(ga -> ga, ga -> new PublishedMetadata(ga, project, extension)))));
215207
p.getAdditionalKnownModules().set(extractFromModuleDependenciesPlugin(project));
216208
});
217209
t.getFrom().attribute(artifactType, fileExtension).attribute(javaModule, false);
@@ -245,26 +237,17 @@ private Provider<Map<String, String>> extractFromModuleDependenciesPlugin(Projec
245237
});
246238
}
247239

248-
private Stream<Map.Entry<String, Configuration>> existingComponentsOfInterest(Configuration resolvable, ExtraJavaModuleInfoPluginExtension extension) {
249-
Set<String> componentsOfInterest = componentsOfInterest(extension);
250-
if (componentsOfInterest.isEmpty()) {
251-
return Stream.empty();
252-
}
253-
254-
return resolvable.getIncoming().getResolutionResult().getAllComponents().stream()
255-
.filter(c -> componentsOfInterest.contains(ga(c.getId())))
256-
.collect(Collectors.toMap(c -> c.getId().toString(), c -> resolvable)).entrySet().stream();
257-
}
258-
259-
private static Set<String> componentsOfInterest(ExtraJavaModuleInfoPluginExtension extension) {
260-
return extension.getModuleSpecs().get().values().stream()
240+
private static Provider<Set<String>> componentsOfInterest(ExtraJavaModuleInfoPluginExtension extension) {
241+
return extension.getModuleSpecs().map(specs -> specs.values().stream()
261242
.filter(ExtraJavaModuleInfoPlugin::needsDependencies)
262243
.map(ModuleSpec::getIdentifier)
263-
.collect(Collectors.toSet());
244+
.collect(Collectors.toSet()));
264245
}
265246

266247
private static boolean needsDependencies(ModuleSpec moduleSpec) {
267-
return moduleSpec instanceof ModuleInfo && ((ModuleInfo) moduleSpec).requireAllDefinedDependencies;
248+
return moduleSpec instanceof ModuleInfo
249+
&& ((ModuleInfo) moduleSpec).requireAllDefinedDependencies
250+
&& IdValidator.isCoordinates(moduleSpec.getIdentifier());
268251
}
269252

270253
static String ga(ComponentIdentifier id) {

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public abstract class ExtraJavaModuleInfoPluginExtension {
4141
public abstract Property<Boolean> getFailOnMissingModuleInfo();
4242
public abstract Property<Boolean> getFailOnAutomaticModules();
4343
public abstract Property<Boolean> getDeriveAutomaticModuleNamesFromFileNames();
44+
public abstract Property<String> getVersionsProvidingConfiguration();
4445

4546
/**
4647
* Add full module information for a given Jar file.

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,19 @@ private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, List<String>> pr
380380
moduleVisitor.visitRequire("java.base", 0, null);
381381

382382
if (moduleInfo.requireAllDefinedDependencies) {
383-
String fullIdentifier = moduleInfo.getIdentifier() + ":" + version;
384-
PublishedMetadata requires = getParameters().getRequiresFromMetadata().get().get(fullIdentifier);
383+
String identifier = moduleInfo.getIdentifier();
384+
PublishedMetadata requires = getParameters().getRequiresFromMetadata().get().get(identifier);
385385

386386
if (requires == null) {
387387
throw new RuntimeException("[requires directives from metadata] " +
388388
"Cannot find dependencies for '" + moduleInfo.getModuleName() + "'. " +
389389
"Are '" + moduleInfo.getIdentifier() + "' the correct component coordinates?");
390390
}
391+
if (requires.getErrorMessage() != null) {
392+
throw new RuntimeException("[requires directives from metadata] " +
393+
"Cannot read metadata for '" + moduleInfo.getModuleName() + "': " +
394+
requires.getErrorMessage());
395+
}
391396

392397
for (String ga : requires.getRequires()) {
393398
String depModuleName = gaToModuleName(ga);

src/main/java/org/gradlex/javamodule/moduleinfo/IdValidator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ static void validateIdentifier(String identifier) {
2525
throw new RuntimeException("'" + identifier + "' are not valid coordinates (group:name) / is not a valid file name (name-1.0.jar)");
2626
}
2727
}
28+
29+
static boolean isCoordinates(String identifier) {
30+
return identifier.matches(COORDINATES_PATTERN);
31+
}
2832
}

src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,36 +18,49 @@
1818

1919
import org.gradle.api.Project;
2020
import org.gradle.api.artifacts.Configuration;
21+
import org.gradle.api.artifacts.ConfigurationContainer;
2122
import org.gradle.api.artifacts.result.DependencyResult;
22-
import org.gradle.api.artifacts.result.ResolvedComponentResult;
2323
import org.gradle.api.artifacts.result.ResolvedDependencyResult;
24+
import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
2425
import org.gradle.api.attributes.Attribute;
26+
import org.gradle.api.attributes.Bundling;
2527
import org.gradle.api.attributes.Category;
28+
import org.gradle.api.attributes.LibraryElements;
2629
import org.gradle.api.attributes.Usage;
30+
import org.gradle.api.attributes.java.TargetJvmEnvironment;
31+
import org.gradle.api.model.ObjectFactory;
32+
import org.gradle.api.provider.Provider;
33+
import org.gradle.api.tasks.SourceSet;
34+
import org.gradle.api.tasks.SourceSetContainer;
35+
import org.gradle.util.GradleVersion;
2736

2837
import java.io.Serializable;
2938
import java.util.ArrayList;
3039
import java.util.List;
3140
import java.util.stream.Collectors;
3241
import java.util.stream.Stream;
3342

43+
import static java.util.Collections.emptyList;
3444
import static java.util.Objects.requireNonNull;
3545
import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE;
3646
import static org.gradle.api.attributes.Category.LIBRARY;
3747
import static org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE;
3848

3949
public class PublishedMetadata implements Serializable {
4050
private static final Attribute<String> CATEGORY_ATTRIBUTE_UNTYPED = Attribute.of(CATEGORY_ATTRIBUTE.getName(), String.class);
51+
private static final String DEFAULT_VERSION_SOURCE_CONFIGURATION = "definedDependenciesVersions";
4152

4253
private final String gav;
4354
private final List<String> requires = new ArrayList<>();
4455
private final List<String> requiresTransitive = new ArrayList<>();
4556
private final List<String> requiresStaticTransitive = new ArrayList<>();
57+
private String errorMessage = null;
4658

47-
PublishedMetadata(String gav, Configuration origin, Project project) {
59+
PublishedMetadata(String gav, Project project, ExtraJavaModuleInfoPluginExtension extension) {
4860
this.gav = gav;
49-
List<String> compileDependencies = componentVariant(origin, project, Usage.JAVA_API);
50-
List<String> runtimeDependencies = componentVariant(origin, project, Usage.JAVA_RUNTIME);
61+
62+
List<String> compileDependencies = componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_API);
63+
List<String> runtimeDependencies = componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_RUNTIME);
5164

5265
Stream.concat(compileDependencies.stream(), runtimeDependencies.stream()).distinct().forEach(ga -> {
5366
if (compileDependencies.contains(ga) && runtimeDependencies.contains(ga)) {
@@ -60,26 +73,73 @@ public class PublishedMetadata implements Serializable {
6073
});
6174
}
6275

63-
private List<String> componentVariant(Configuration origin, Project project, String usage) {
76+
private List<String> componentVariant(Provider<String> versionsProvidingConfiguration, Project project, String usage) {
77+
Configuration versionsSource;
78+
if (versionsProvidingConfiguration.isPresent()) {
79+
versionsSource = project.getConfigurations().getByName(versionsProvidingConfiguration.get());
80+
} else {
81+
// version provider is not configured, create on adhoc based on ALL classpaths of the project
82+
versionsSource = maybeCreateDefaultVersionSourcConfiguration(project.getConfigurations(), project.getObjects(),
83+
project.getExtensions().findByType(SourceSetContainer.class));
84+
}
85+
6486
Configuration singleComponentVariantResolver = project.getConfigurations().detachedConfiguration(project.getDependencies().create(gav));
6587
singleComponentVariantResolver.setCanBeConsumed(false);
66-
singleComponentVariantResolver.shouldResolveConsistentlyWith(origin);
67-
origin.getAttributes().keySet().forEach(a -> {
88+
singleComponentVariantResolver.shouldResolveConsistentlyWith(versionsSource);
89+
versionsSource.getAttributes().keySet().forEach(a -> {
6890
@SuppressWarnings("rawtypes") Attribute untypedAttributeKey = a;
6991
//noinspection unchecked
70-
singleComponentVariantResolver.getAttributes().attribute(untypedAttributeKey, requireNonNull(origin.getAttributes().getAttribute(a)));
92+
singleComponentVariantResolver.getAttributes().attribute(untypedAttributeKey, requireNonNull(versionsSource.getAttributes().getAttribute(a)));
7193
});
7294
singleComponentVariantResolver.getAttributes().attribute(USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, usage));
73-
return firstAndOnlyComponent(singleComponentVariantResolver).getDependencies().stream()
74-
.filter(PublishedMetadata::filterComponentDependencies)
75-
.map(PublishedMetadata::ga)
76-
.collect(Collectors.toList());
95+
return firstAndOnlyComponentDependencies(singleComponentVariantResolver);
96+
}
97+
98+
private Configuration maybeCreateDefaultVersionSourcConfiguration(ConfigurationContainer configurations, ObjectFactory objects, SourceSetContainer sourceSets) {
99+
String name = DEFAULT_VERSION_SOURCE_CONFIGURATION;
100+
Configuration existing = configurations.findByName(name);
101+
if (existing != null) {
102+
return existing;
103+
}
104+
105+
return configurations.create(name, c -> {
106+
c.setCanBeResolved(true);
107+
c.setCanBeConsumed(false);
108+
c.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME));
109+
c.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
110+
c.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
111+
c.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.EXTERNAL));
112+
if (GradleVersion.current().compareTo(GradleVersion.version("7.0")) >= 0) {
113+
c.getAttributes().attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
114+
objects.named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM));
115+
}
116+
117+
if (sourceSets != null) {
118+
for (SourceSet sourceSet : sourceSets) {
119+
Configuration implementation = configurations.getByName(sourceSet.getImplementationConfigurationName());
120+
Configuration compileOnly = configurations.getByName(sourceSet.getCompileOnlyConfigurationName());
121+
Configuration runtimeOnly = configurations.getByName(sourceSet.getRuntimeOnlyConfigurationName());
122+
Configuration annotationProcessor = configurations.getByName(sourceSet.getAnnotationProcessorConfigurationName());
123+
c.extendsFrom(implementation, compileOnly, runtimeOnly, annotationProcessor);
124+
}
125+
}
126+
});
77127
}
78128

79-
private ResolvedComponentResult firstAndOnlyComponent(Configuration singleComponentVariantResolver) {
80-
ResolvedDependencyResult onlyResult = (ResolvedDependencyResult) singleComponentVariantResolver.getIncoming().getResolutionResult()
81-
.getRoot().getDependencies().iterator().next();
82-
return onlyResult.getSelected();
129+
private List<String> firstAndOnlyComponentDependencies(Configuration singleComponentVariantResolver) {
130+
DependencyResult result = singleComponentVariantResolver
131+
.getIncoming().getResolutionResult().getRoot()
132+
.getDependencies().iterator().next();
133+
134+
if (result instanceof UnresolvedDependencyResult) {
135+
errorMessage = ((UnresolvedDependencyResult) result).getFailure().getMessage();
136+
return emptyList();
137+
} else {
138+
return ((ResolvedDependencyResult) result).getSelected().getDependencies().stream()
139+
.filter(PublishedMetadata::filterComponentDependencies)
140+
.map(PublishedMetadata::ga)
141+
.collect(Collectors.toList());
142+
}
83143
}
84144

85145
private static boolean filterComponentDependencies(DependencyResult d) {
@@ -113,4 +173,8 @@ public List<String> getRequiresTransitive() {
113173
public List<String> getRequiresStaticTransitive() {
114174
return requiresStaticTransitive;
115175
}
176+
177+
public String getErrorMessage() {
178+
return errorMessage;
179+
}
116180
}

0 commit comments

Comments
 (0)