Skip to content

Add DependencyManagement handling #5623

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft

Add DependencyManagement handling #5623

wants to merge 6 commits into from

Conversation

Jenson3210
Copy link
Contributor

@Jenson3210 Jenson3210 commented Jun 13, 2025

What's changed?

By using a Trait we can both match dependencyManagement sections from recipe perspectives. I also wrote a method that updates the Trait's cursor with an updated dependencyManagement. We can have several more overload methods that call this one with null. Passing null for group, artifact or version makes these fields untouched.

What's your motivation?

It has been introduced also in the ChangeDependency recipe as an optional boolean (which defaults to true) as that is also how it is done in maven recipe/equivalents

Anything in particular you'd like reviewers to focus on?

I tried to minimize code duplications as it was really becoming a lot of copy pasting / error prone to changes.
I stopped at a point where I feel if we further try to remove duplications, we end up in a hard to follow code flow.

Anyone you would like to review specifically?

@shanman190 or @sambsnyd would be great for an early/conceptual review. Please do not focus on code duplications etc yet. I want to fail fast before spending time in optimizing.

Have you considered any alternatives or workarounds?

As suggested by @shanman190, I went for a postVisit recipe that is optionally adding the dependencyManagement to be aligned with maven equivalents.
ATM, the Trait does not allow for templated strings yet as we would need scanning phases for that so want to come up with support for no-templates strings first and then add templated strings in a separate PR later if required.

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Jun 13, 2025
@Jenson3210 Jenson3210 self-assigned this Jun 13, 2025
@Jenson3210 Jenson3210 added the enhancement New feature or request label Jun 13, 2025
…leDependencyManagementEntryTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@shanman190
Copy link
Contributor

When I think about the logistics of it, it feels like ChangeDependency should be altering an external dependency that the project explicitly pulls in. The Spring Dependency Management plugin is more about declaring a managed dependency which at least to me is more akin to declaring a dependency constraint. Neither has any effect upon dependencies until it exists within the dependency graph.

What if we introduced a ChangeManagedDependency and had it update the constraints and dependency management sections? Like in Maven managed dependency has no effect until the dependency is actually present, so I think this aligns more closely in expectations.

@Jenson3210
Copy link
Contributor Author

@shanman190,

  • you would then have to call both these recipes from everywhere you want to bump a dependency? Or would you still call it from the ChangeDependency?
    Are we then not also going to have to create UpgradeManagedDependencyVersion etc?
    Or do they all delegate to that one?
  • You then mean not to put the actual dependencyManagement change in the Trait but in a recipe? Or is it fine to keep it in the Trait and then from that recipe for the Spring's dependency management part use the Trait over there and for constraints use another visitor/Trait and...?

@shanman190
Copy link
Contributor

shanman190 commented Jun 14, 2025

Here's my analysis of rewrite-maven as a parallel:

  • maven.AddDependency - only add direct dependency
  • maven.AddManagedDependency - only add managed dependency
  • maven.ChangeDependencyGroupIdAndArtifactId - has option defaulted to true to change managed dependencies as well via doAfterVisit
  • maven.ChangeManagedDependencyGroupIdAndArtifactId - only change managed dependency
  • maven.RemoveDependency - only remove direct dependency
  • maven.RemoveManagedDependency - only remove managed dependency
  • maven.UpgradeDependencyVersion - upgrades a direct or managed dependency

So it would make sense I suppose to follow the established pattern. Honestly, I didn't realize that the maven.ChangeDependencyGroupIdAndArtifactId had been updated to effect managed dependencies by default. I probably would have kept them separate following the single responsibility principle myself, but at least the recipes are separate with the one invoking the other when requested.

I think having the trait is fine, but I think the design that I think would work best is a gradle.ChangeManagedDependency which encompasses changing a constraint using the trait from #5604 and then your dependency management trait here. Then we update gradle.ChangeDependency to have an option akin to the Maven variant and when that option is true execute to the gradle.ChangeManagedDependency recipe in a doAfterVisit.

// Individual dependencies tend to appear in several places within a given dependency graph.
// Minimize the number of allocations by caching the updated dependencies.
transient Map<org.openrewrite.maven.tree.Dependency, org.openrewrite.maven.tree.Dependency> updatedRequested = new HashMap<>();
transient Map<org.openrewrite.maven.tree.ResolvedDependency, org.openrewrite.maven.tree.ResolvedDependency> updatedResolved = new HashMap<>();
transient MavenMetadataFailures mavenMetadataFailures = new MavenMetadataFailures(this);

@JsonCreator
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I still need to find a way to have this here without it failing but without @JsonCreator as @jkschneider mentioned using @JsonCreator is to be avoided. I looked at the other maven counterpart, but there it is done using the annotation.

if (group != null) {
dependency = new GroupArtifactVersion(group, entry, version);
}
} else if (argument instanceof J.MethodInvocation && "mapOf".equals(((J.MethodInvocation) argument).getSimpleName())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

callout: Not a part of this PR, but this may be an improvement that we can make for the GradleDependency trait as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Leaving this open for now so that we can create a new issue from this comment later on

@Jenson3210 Jenson3210 marked this pull request as ready for review June 16, 2025 12:40
@Jenson3210 Jenson3210 changed the title Initial proposal that will add DependencyManagement handling Add DependencyManagement handling Jun 16, 2025
@Jenson3210 Jenson3210 marked this pull request as draft June 16, 2025 13:32
Comment on lines +26 to +28
public static SpringDependencyManagementPluginEntry.Matcher springDependencyManagementPluginEntry() {
return new SpringDependencyManagementPluginEntry.Matcher();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

As @timtebeek confirmed for me, we're no longer going to use the factory pattern going forward, so we can drop this method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

2 participants