Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ configurations.runtimeClasspath {
}
```

In addition to creating a common configuration for resolution, a platform project can also be enabled to enforce
versions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I have one more question for clarification and to understand what setups we can recommend to users. If you set versionsProvidingConfiguration to a configuration that resolves all dependencies that you compute from your platform (see discussion in #207), then you don't need the addition of this PR, right?

So this is an alternative to versionsProvidingConfiguration, correct? Which then indeed makes the setup more concise as you do not have to deal with all the ceremony around versionsProvidingConfiguration (see my comment here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To partly answer my own question:

  • The versionsProvidingConfiguration approach has the advantage (may be considered a disadvantage) that it also provides the versions of all (well, almost all) transitive dependencies. In your own platform project, you may only define versions of you direct dependencies. Then this approach still works.
  • The platformDependency approach (introduced in this PR) requires you to define all versions in the platform, but has the advantage of a simpler overall setup as I wrote above. Maybe you want to define all versions anyway to have more control and overview over all transitives. Or you mainly rely on an existing platform, like spring-framework-bom, that defines all the versions for you already.

I think then, for the moment, both are valid approaches and adding this PR, while also keeping versionsProvidingConfiguration, is a good addition.

```kotlin
extraJavaModuleInfo {
platformDependency = project.provider { project.dependencies.platform(project.dependencies.create("org.example:bom:1.0.0")) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
platformDependency = project.provider { project.dependencies.platform(project.dependencies.create("org.example:bom:1.0.0")) }
platformDependency = provider { dependencies.platform(dependencies.create("org.example:bom:1.0.0")) }

💄 We are always in the project context here. Lets do this as short as possible.

Would be great if there would be ca notation without provider{ }, but I think there is no way to do that.


Alternatively, we could think about providing one (or two) String-based method as for the dependency:

   platformDependency = "org.example:bom:1.0.0"
   platformDependencyStrict = true

Then, you won't need the validation and instead could always create a platform dependency from the coordinates. I like this a bit better, as it is easier to read for users that skim the build scripts and do not know all the details. It looks more like a DSL then. You would need to check if the string is coordinates or project name (starts with :) to decide if you need to project(...) when creating the dependency. This would be consistent with what we do here.
But I think I am also fine with the current solution. What do you think?

}
```

## I have many automatic modules in my project. How can I convert them into proper modules and control what they export or require?

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.model.ObjectFactory;
Expand Down Expand Up @@ -44,6 +45,8 @@ public abstract class ExtraJavaModuleInfoPluginExtension {

public abstract Property<String> getVersionsProvidingConfiguration();

public abstract Property<Dependency> getPlatformDependency();

/**
* Add full module information for a given Jar file.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
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.ENFORCED_PLATFORM;
import static org.gradle.api.attributes.Category.LIBRARY;
import static org.gradle.api.attributes.Category.REGULAR_PLATFORM;
import static org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE;

import java.io.Serializable;
Expand All @@ -15,6 +17,8 @@
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.result.DependencyResult;
import org.gradle.api.artifacts.result.ResolvedDependencyResult;
import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
Expand All @@ -34,6 +38,8 @@
public class PublishedMetadata implements Serializable {
private static final Attribute<String> CATEGORY_ATTRIBUTE_UNTYPED =
Attribute.of(CATEGORY_ATTRIBUTE.getName(), String.class);
private static final Attribute<Category> CATEGORY_ATTRIBUTE_TYPED =
Attribute.of(CATEGORY_ATTRIBUTE.getName(), Category.class);
private static final String DEFAULT_VERSION_SOURCE_CONFIGURATION = "definedDependenciesVersions";

private final String gav;
Expand All @@ -47,10 +53,16 @@ public class PublishedMetadata implements Serializable {
PublishedMetadata(String gav, Project project, ExtraJavaModuleInfoPluginExtension extension) {
this.gav = gav;

List<String> compileDependencies =
componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_API);
List<String> runtimeDependencies =
componentVariant(extension.getVersionsProvidingConfiguration(), project, Usage.JAVA_RUNTIME);
List<String> compileDependencies = componentVariant(
extension.getVersionsProvidingConfiguration(),
extension.getPlatformDependency(),
project,
Usage.JAVA_API);
List<String> runtimeDependencies = componentVariant(
extension.getVersionsProvidingConfiguration(),
extension.getPlatformDependency(),
project,
Usage.JAVA_RUNTIME);

Stream.concat(compileDependencies.stream(), runtimeDependencies.stream())
.distinct()
Expand All @@ -67,7 +79,10 @@ public class PublishedMetadata implements Serializable {

@SuppressWarnings({"UnstableApiUsage", "unchecked"})
private List<String> componentVariant(
Provider<String> versionsProvidingConfiguration, Project project, String usage) {
Provider<String> versionsProvidingConfiguration,
Provider<Dependency> platformDependencyProvider,
Project project,
String usage) {
Configuration versionsSource;
if (versionsProvidingConfiguration.isPresent()) {
versionsSource = project.getConfigurations()
Expand All @@ -81,8 +96,29 @@ private List<String> componentVariant(
project.getExtensions().findByType(SourceSetContainer.class));
}

Configuration singleComponentVariantResolver = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(gav));
List<Dependency> dependencies = new ArrayList<>();
dependencies.add(project.getDependencies().create(gav));
if (platformDependencyProvider.isPresent()) {
if (!ModuleDependency.class.isAssignableFrom(
platformDependencyProvider.get().getClass())) {
Comment on lines +102 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!ModuleDependency.class.isAssignableFrom(
platformDependencyProvider.get().getClass())) {
if (!(platformDependencyProvider.get() instanceof ModuleDependency)) {

💄 For consistency: do it similar as other instanceof checks in this codebase.

throw new IllegalArgumentException("Unable to determine dependency '"
+ platformDependencyProvider.get().getName() + "' type");
}

ModuleDependency platformDependency = (ModuleDependency) platformDependencyProvider.get();
// A platform dependency must have the platform attribute specified.
Category category = platformDependency.getAttributes().getAttribute(CATEGORY_ATTRIBUTE_TYPED);
if (category == null
|| (!category.getName().equals(REGULAR_PLATFORM)
&& !category.getName().equals(ENFORCED_PLATFORM))) {
throw new IllegalArgumentException(
"Dependency '" + platformDependency.getName() + "' is not a platform");
}
dependencies.add(platformDependency);
}

Configuration singleComponentVariantResolver =
project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[0]));
singleComponentVariantResolver.setCanBeConsumed(false);
singleComponentVariantResolver.shouldResolveConsistentlyWith(versionsSource);
versionsSource.getAttributes().keySet().forEach(a -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,25 @@ class EdgeCasesFunctionalTest extends Specification {
expect:
build()
}

def "resolve against a platform project if specified"() {
given:
buildFile << """
val springBom = "org.springframework:spring-framework-bom:6.2.9"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test fails on Gradle 6.x because it is hard (probably impossible) to run that with Java versions >11. Maybe just choose an older version of Sping boot here that does not require Java 17 as minimum as it is just a test example.

We may drop support for 6.x soon, but that's a different story. :)

dependencies {
implementation(platform(springBom))
implementation("org.springframework:spring-jcl")
}

extraJavaModuleInfo {
failOnAutomaticModules.set(true)
platformDependency.set(project.provider { project.dependencies.platform(project.dependencies.create(springBom)) })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
platformDependency.set(project.provider { project.dependencies.platform(project.dependencies.create(springBom)) })
platformDependency.set(provider { dependencies.platform(dependencies.create(springBom)) })

💄 consistent with doc change

module("org.springframework:spring-jcl", "spring.jcl")
}
"""

expect:
build()
}

}
Loading