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
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.gradle;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.ExtraProperty;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Statement;
import org.openrewrite.maven.MavenDownloadingException;
import org.openrewrite.maven.internal.MavenPomDownloader;
import org.openrewrite.maven.tree.GroupArtifactVersion;
import org.openrewrite.maven.tree.MavenRepository;
import org.openrewrite.maven.tree.Pom;
import org.openrewrite.semver.LatestRelease;

import java.util.Collections;
import java.util.List;
import java.util.Map;

@Value
@EqualsAndHashCode(callSuper = false)
public class SyncGradleExtPropertiesWithBom extends Recipe {

@Option(displayName = "Group ID",
description = "The groupId of the BOM to sync with.",
example = "org.springframework.boot")
String groupId;

@Option(displayName = "Artifact ID",
description = "The artifactId of the BOM to sync with.",
example = "spring-boot-dependencies")
String artifactId;

@Option(displayName = "Version",
description = "The version of the BOM to sync with.",
example = "3.4.0")
String version;

@Option(displayName = "Remove redundant overrides",
description = "When enabled, ext properties whose value is lower than or equal to the BOM version " +
"will be removed entirely instead of updated, since the BOM default is now sufficient.",
required = false)
@Nullable
Boolean removeRedundantOverrides;

String displayName = "Sync Gradle ext properties with BOM";

String description = "Downloads a BOM and compares its properties against Gradle ext properties. " +
"When the BOM defines a higher version for a property, the ext property is updated to match " +
"(or removed if `removeRedundantOverrides` is enabled).";

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor<ExecutionContext>() {
final ExtraProperty.Matcher matcher = new ExtraProperty.Matcher().matchVariableDeclarations(false);
final LatestRelease versionComparator = new LatestRelease(null);
@Nullable
Map<String, String> bomProperties;

private Map<String, String> getBomProperties(ExecutionContext ctx) {
if (bomProperties != null) {
return bomProperties;
}
SourceFile sf = getCursor().firstEnclosing(SourceFile.class);
List<MavenRepository> repos = sf != null
? sf.getMarkers().findFirst(GradleProject.class)
.map(GradleProject::getMavenRepositories)
.orElse(Collections.singletonList(MavenRepository.MAVEN_CENTRAL))
: Collections.singletonList(MavenRepository.MAVEN_CENTRAL);
try {
MavenPomDownloader mpd = new MavenPomDownloader(ctx);
Pom bom = mpd.download(new GroupArtifactVersion(groupId, artifactId, version), null, null, repos);
bomProperties = bom.resolve(Collections.emptyList(), mpd, repos, ctx).getProperties();
} catch (MavenDownloadingException e) {
bomProperties = Collections.emptyMap();
}
return bomProperties;
}

@Override
public @Nullable Statement visitStatement(Statement statement, ExecutionContext ctx) {
if (statement instanceof J.Assignment || statement instanceof J.MethodInvocation) {
ExtraProperty prop = matcher.get(getCursor()).orElse(null);
if (prop != null) {
String bomVersion = getBomProperties(ctx).get(prop.getName());
if (bomVersion != null) {
int cmp = versionComparator.compare(null, prop.getValue(), bomVersion);
if (cmp < 0) {
// BOM version is higher
if (Boolean.TRUE.equals(removeRedundantOverrides)) {
return null;
}
return (Statement) prop.withValue(bomVersion).getTree();
}
}
}
}
return super.visitStatement(statement, ctx);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.RemoveExtension,Remo
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.RemoveRedundantDependencyVersions,Remove redundant explicit dependencies and versions,"Remove explicitly-specified dependency versions that are managed by a Gradle `platform`, `enforcedPlatform` or the `io.spring.dependency-management` plugin. Also removes redundant direct dependencies and dependency constraints that are already satisfied by transitive dependencies.",1,,Gradle,"[{""name"":""groupPattern"",""type"":""String"",""displayName"":""Group"",""description"":""Group glob expression pattern used to match dependencies that should be managed.Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.*""},{""name"":""artifactPattern"",""type"":""String"",""displayName"":""Artifact"",""description"":""Artifact glob expression pattern used to match dependencies that should be managed.Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava*""},{""name"":""onlyIfManagedVersionIs"",""type"":""Comparator"",""displayName"":""Only if managed version is ..."",""description"":""Only remove the explicit version if the managed version has the specified comparative relationship to the explicit version. For example, `gte` will only remove the explicit version if the managed version is the same or newer. Default `eq`."",""valid"":[""ANY"",""EQ"",""LT"",""LTE"",""GT"",""GTE""]}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.RemoveRedundantSecurityResolutionRules,Remove redundant security resolution rules,"Remove `resolutionStrategy.eachDependency` rules that pin dependencies to versions that are already being managed by a platform/BOM to equal or newer versions. Only removes rules that have a security advisory identifier (CVE or GHSA) in the `because` clause, unless a custom pattern is specified.",1,,Gradle,"[{""name"":""securityPattern"",""type"":""String"",""displayName"":""Security pattern"",""description"":""A regular expression pattern to identify security-related resolution rules by matching against the `because` clause. Rules matching this pattern will be considered for removal. The pattern is searched within the clause, so a `because` containing multiple identifiers (e.g., `CVE-2024-1234, GHSA-abcd-1234-efgh`) will match if any identifier matches. Default pattern matches CVE identifiers (e.g., `CVE-2024-1234`) and GitHub Security Advisory identifiers (e.g., `GHSA-xxxx-xxxx-xxxx`)."",""example"":""(CVE-\\d|GHSA-[a-z0-9])""}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.RemoveRepository,Remove repository,"Removes a repository from Gradle build scripts. Named repositories include ""jcenter"", ""mavenCentral"", ""mavenLocal"", and ""google"".",1,,Gradle,"[{""name"":""repository"",""type"":""String"",""displayName"":""Repository"",""description"":""The name of the repository to remove"",""example"":""jcenter"",""required"":true}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.SyncGradleExtPropertiesWithBom,Sync Gradle ext properties with BOM,"Downloads a BOM and compares its properties against Gradle ext properties. When the BOM defines a higher version for a property, the ext property is updated to match (or removed if `removeRedundantOverrides` is enabled).",1,,Gradle,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The groupId of the BOM to sync with."",""example"":""org.springframework.boot"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The artifactId of the BOM to sync with."",""example"":""spring-boot-dependencies"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the BOM to sync with."",""example"":""3.4.0"",""required"":true},{""name"":""removeRedundantOverrides"",""type"":""Boolean"",""displayName"":""Remove redundant overrides"",""description"":""When enabled, ext properties whose value is lower than or equal to the BOM version will be removed entirely instead of updated, since the BOM default is now sufficient.""}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.UpdateJavaCompatibility,Update Gradle project Java compatibility,Find and updates the Java compatibility for the Gradle project.,1,,Gradle,"[{""name"":""version"",""type"":""Integer"",""displayName"":""Java version"",""description"":""The Java version to upgrade to."",""example"":""11"",""required"":true},{""name"":""compatibilityType"",""type"":""CompatibilityType"",""displayName"":""Compatibility type"",""description"":""The compatibility type to change"",""valid"":[""source"",""target""]},{""name"":""declarationStyle"",""type"":""DeclarationStyle"",""displayName"":""Declaration style"",""description"":""The desired style to write the new version as when being written to the `sourceCompatibility` or `targetCompatibility` variables. Default, match current source style. (ex. Enum: `JavaVersion.VERSION_11`, Number: 11, or String: \""11\"")"",""valid"":[""Enum"",""Number"",""String""]},{""name"":""allowDowngrade"",""type"":""Boolean"",""displayName"":""Allow downgrade"",""description"":""Allow downgrading the Java version.""},{""name"":""addIfMissing"",""type"":""Boolean"",""displayName"":""Add compatibility type if missing"",""description"":""Adds the specified compatibility type if one is not found.""}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.AddDependency,Add Gradle dependency,Add a gradle dependency to a `build.gradle` file in the correct configuration based on where it is used.,1,,Gradle,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'com.google.guava:guava:VERSION'."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'com.google.guava:guava:VERSION'"",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""A configuration to use when it is not what can be inferred from usage. Most of the time this will be left empty, but is used when adding a new as of yet unused dependency."",""example"":""implementation""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using"",""description"":""Used to determine if the dependency will be added and in which scope it should be placed."",""example"":""org.junit.jupiter.api.*""},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""A classifier to add. Commonly used to select variants of a library."",""example"":""test""},{""name"":""extension"",""type"":""String"",""displayName"":""Extension"",""description"":""The extension of the dependency to add. If omitted Gradle defaults to assuming the type is \""jar\""."",""example"":""jar""},{""name"":""familyPattern"",""type"":""String"",""displayName"":""Family pattern"",""description"":""A pattern, applied to groupIds, used to determine which other dependencies should have aligned version numbers. Accepts '*' as a wildcard character."",""example"":""com.fasterxml.jackson*""},{""name"":""acceptTransitive"",""type"":""Boolean"",""displayName"":""Accept transitive"",""description"":""Default false. If enabled, the dependency will not be added if it is already on the classpath as a transitive dependency."",""example"":""true""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.AddPlatformDependency,Add Gradle platform dependency,Add a gradle platform dependency to a `build.gradle` file in the correct configuration based on where it is used.,1,,Gradle,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'com.google.guava:guava:VERSION'."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'com.google.guava:guava:VERSION'"",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""A configuration to use when it is not what can be inferred from usage. Most of the time this will be left empty, but is used when adding a new, as yet unused, dependency."",""example"":""implementation""},{""name"":""enforced"",""type"":""Boolean"",""displayName"":""Enforced"",""description"":""Used to determine whether the platform dependency should be enforcedPlatform."",""example"":""true""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
Expand Down
Loading