Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constraints checking apparently not honoring platform constraints #755

Open
ianbrandt opened this issue Mar 27, 2023 · 5 comments
Open

Constraints checking apparently not honoring platform constraints #755

ianbrandt opened this issue Mar 27, 2023 · 5 comments

Comments

@ianbrandt
Copy link

Continuing #727, it appears that checkConstraints is not considering constraints sourced from another platform.

Reproducer: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/ (commit as of this writing: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/tree/09d5e70d2f78789a045f5442fd05fa6b62d41594).

I've configured checkConstraints and checkBuildEnvironmentConstraints to true: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/blob/09d5e70d2f78789a045f5442fd05fa6b62d41594/build.gradle.kts#L26-L27.

I've specified an upper bound for my Spring versions: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/blob/09d5e70d2f78789a045f5442fd05fa6b62d41594/gradle/libs.versions.toml#L13-L14.

I've sourced the BOMs for those versions in my platform: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/blob/09d5e70d2f78789a045f5442fd05fa6b62d41594/platforms/app-platform/build.gradle.kts#L17-L18.

I've additionally configured a BOM alignment rule: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/blob/09d5e70d2f78789a045f5442fd05fa6b62d41594/platforms/app-platform/build.gradle.kts#L14. (I'm not clear on whether this should be done in the platform, so for the moment I've duplicated the configuration in my project as well: https://github.com/ianbrandt/gradle-versions-plugin-platform-constraints/blob/09d5e70d2f78789a045f5442fd05fa6b62d41594/build-logic/src/main/kotlin/com.ianbrandt.buildlogic.spring-project.gradle.kts#L9.)

With this I'd expect only Spring and Spring Boot components less than 6.x and 3.x respectively to be recommended. The actual behavior with version 0.46.0 is that newer versions are recommended:

The following dependencies have later milestone versions:
 - org.springframework:spring-context [5.3.25 -> 6.0.7]
     https://github.com/spring-projects/spring-framework
 - org.springframework.boot:org.springframework.boot.gradle.plugin [2.7.10 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot-autoconfigure [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot-starter [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot-starter-log4j2 [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot-starter-test [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
 - org.springframework.boot:spring-boot-starter-webflux [2.7.8 -> 3.0.5]
     https://spring.io/projects/spring-boot
@ben-manes
Copy link
Owner

ben-manes commented Mar 27, 2023

The upper bounds in the constraints won't be honored for version upgrade queries. That is only to determine your current version. This plugins informs you of possible upgrades, e.g. to bump the constraint upwards, so it uses a dynamic version to query against. Plugins like Spring's dependency management will allow these to pass through and resolve for the report. You can use resolution strategies to control the ranges in the report if you want to restrict it.

It is more typical for a build to use constraints for transitives with known security issues force a minimum version and to use resolution strategies to force version ranges for direct dependencies. That may simply be due to the order of the features becoming available, but is also more explicit in letting you specify a rejection reason for debugging your own dependencies.

You can look at our usages calling the supportsConstraints method. These add the coordinate into the resolution query for the report.

@ianbrandt
Copy link
Author

I can look into configuring a ResolutionStrategy. I was initially dissuaded from doing so by this in the user guide:

In contrast to the other concepts covered in this chapter, like dependency constraints or component metadata rules, which are all inputs to resolution, the following mechanisms allow you to write rules which are directly injected into the resolution engine. Because of this, they can be seen as brute force solutions, that may hide future problems (e.g. if new dependencies are added). Therefore, the general advice is to only use the following mechanisms if other means are not sufficient. If you are authoring a library, you should always prefer dependency constraints as they are published for your consumers.

@ben-manes
Copy link
Owner

The publishing is both good and bad. I tried using constraints in a library for plugin/test transitive dependencies to resolve security analyzers reporting possible exploits. These were not library dependencies, but were exposed to consumers and impacted their builds because constraints were published in the module metadata. I had to be more careful with how I used constraints to avoid this metadata leak. I'd generally try to avoid impacting someone else's build with constraints that they cannot disable, but find them very useful elsewhere.

@ianbrandt
Copy link
Author

ianbrandt commented Mar 28, 2023

Understood. Given the Jakarta EE transition, publishers that depend on Spring and Spring Boot 5.x/2.x may very well not be compatible with consumers depending on Spring and Spring Boot 6.x/3.x. As such, I do want my dependency constraints published in this case. I imagine that would be common when it comes to major version changes, when SemVer is followed at least.

All the same, I've been trying to concoct a resolution strategy to force the version range as suggested, but no luck so far. I may very well may be doing it wrong, as I'm not finding examples of using resolution strategies for constraining ranges (just forcing specific versions, e.g. "Denying a particular version with a replacement" and ResolutionStrategy#force).

My first attempt was to add this to my project convention, with the expectation that the Versions Plugin would inform me of the now GA 5.3.26 and 2.7.10 versions:

configurations.all {
	resolutionStrategy.eachDependency {
		when (requested.group) {
			"org.springframework" -> useVersion("[5.3, 6[!!5.3.25")
			"org.springframework.boot" -> useVersion("[2.7, 3[!!2.7.9")
		}
	}
}

Instead I get several errors like the following:

Failed to determine the latest version for the following dependencies (use --info for details):
 - org.springframework:spring-context
The exception that is the cause of unresolved state: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find org.springframework:spring-context:[5.3, 6[!!5.3.25.
Required by:
    project :subprojects:app

Then I tried:

configurations.all {
	resolutionStrategy.eachDependency {
		when (requested.group) {
			"org.springframework" -> useVersion("[5.3, 6[")
			"org.springframework.boot" -> useVersion("[2.7, 3[")
		}
	}
}

But then the Versions Plugin reports:

The following dependencies are using the latest milestone version:
[...]
 - org.springframework:spring-context:5.3.26
 - org.springframework.boot:spring-boot:2.7.10
[...]

So in other words, this apparently overrides the preferred version from my version catalog with a version range without a preferred version, which is not what I want for the sake of build stability.

As I've worked through this I've reread and refreshed my understanding of the "Declaring Versions" and "Controlling Transitives" sections of the Gradle User Guide. It has reinforced my belief that resolution strategies are too low-level and brute-force for my use case, and that dependency constraints are the way to go. They're simpler to declare via rich versions vs. coding imperative resolution strategies. They convey to consumers, as is correct for my use case. They also keep my version management between my version catalog and platforms, as opposed to those plus my project convention (which results in duplicated version information since you can't yet reference version catalogs in precompiled script plugins per gradle/gradle#15383).

Given that, it would be very helpful if the Versions Plugin would recommend potential updates respective of my dependency constraints. In fact, it's not really clear to me what checkConstraints = true is for if not for this. Showing versions beyond my declared constrained ranges, and not the potential updates within the ranges, is not particularly useful. I've already made the explicit choice that I'm not ready to upgrade beyond the range at this time, only within it. If I wanted the unconstrained report, I would think checkConstraints = false would get me that.

Just to add, I really appreciate this plugin and all the work you and all the maintainers put into it. My only intent here is to provide constructive feedback based on my use case.

@ben-manes
Copy link
Owner

@anuraaga kindly contributed the constraint update logic (#351). I think the original intent didn't consider version ranges but rather a minimum requirement on transitive dependencies within a project, e.g. ensure asm is at a compatible jdk version. These are not explicit dependencies that the application developers typically manage, but since they are transitively pulled in they are forced to be aware. A resolution strategy wouldn't let them know about upgrade opportunities, whereas a dependency constraint would. I think this is why your use-case differs because both are valid needs.

You might try using a component selection rule. This will walk the versions from latest downward until a candidate is accepted. If all are rejected then you'll receive a resolution error. This is what the our own rejectVersionIf is a dsl for and can be a powerful way to filter out undesirable later versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants