Introduce Gradle version catalog traits - #8376
Conversation
- Add document, library, and plugin traits for TOML catalogs - Support string and inline-table dependency/plugin entries - Coordinate shared version.ref updates - Centralize TOML table lookup and string accessors - Preserve TOML formatting during updates
| /** | ||
| * Finds a top-level table with the supplied name. | ||
| * | ||
| * @param name the table name to find | ||
| * @return the matching table, or {@code null} when it is absent | ||
| */ | ||
| public @Nullable Table findTable(String name) { | ||
| for (TomlValue value : values) { | ||
| if (!(value instanceof Table)) { | ||
| continue; | ||
| } | ||
| Table table = (Table) value; | ||
| Identifier tableName = table.getName(); | ||
| if (tableName != null && name.equals(tableName.getName())) { | ||
| return table; | ||
| } | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
We don't normally put finders like this off of the LST model itself. When interacting with the LST model normally, you'd override the visitTable and interact with the named table that you're interested in.
There was a problem hiding this comment.
gotcha, Im more or less rich domain guy. Doing 'table.find("version")' looks better for me than 'TomlTableValue.find(table, "version")' etc...
changed it
| public @Nullable KeyValue find(String key) { | ||
| for (Toml value : getValues()) { | ||
| if (!(value instanceof KeyValue)) { | ||
| continue; | ||
| } | ||
| KeyValue keyValue = (KeyValue) value; | ||
| if (!(keyValue.getKey() instanceof Identifier) || | ||
| !key.equals(((Identifier) keyValue.getKey()).getName())) { | ||
| continue; | ||
| } | ||
| return keyValue; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| public @Nullable String getString(String key) { | ||
| KeyValue keyValue = find(key); | ||
| if (keyValue == null || !(keyValue.getValue() instanceof Literal)) { | ||
| return null; | ||
| } | ||
| Object value = ((Literal) keyValue.getValue()).getValue(); | ||
| return value instanceof String ? (String) value : null; | ||
| } |
| * has neither a {@code version} nor a {@code version.ref} key, the {@code version} key is added.</li> | ||
| * </ul> | ||
| */ | ||
| public Toml.KeyValue withCoordinatesAndVersion( |
There was a problem hiding this comment.
I think this may be better modeled as something like this:
GradleVersionCatalogDependency withGroup(String);
GradleVersionCatalogDependency withName(String);
GradleVersionCatalogDependency withModule(String);
GradleVersionCatalogDependency withVersion(String);
This allows modifying the pieces in a chain.
Usage:
GradleVersionCatalogDependency dependency;
Toml.KeyValue entry = dependency.withGroup("com.example")
.withName("example-lib")
.withVersion("1.0")
.getValue()
| * {@code with*} methods; the updated {@link Toml.KeyValue} should be used as the replacement | ||
| * value in the enclosing map operation. | ||
| */ | ||
| public static @Nullable GradleVersionCatalogDependency extract( |
There was a problem hiding this comment.
This is already supported by the matcher and doesn't lose the ancestry.
| public boolean hasUnsupportedVersionDeclaration() { | ||
| if (!(getTree().getValue() instanceof Toml.Table)) { | ||
| return false; | ||
| } | ||
| Toml.Table table = (Toml.Table) getTree().getValue(); | ||
| return table.find("version") != null && version == null || | ||
| table.find("version.ref") != null && versionRef == null; | ||
| } |
There was a problem hiding this comment.
This feels like something a recipe should be knowledgeable about reconciling when it manifests to achieve a correct end state that is logically consistent. As in I'm not sure that it belongs here.
| return extractWithCursor(syntheticCursor, kv, groupPattern, artifactPattern); | ||
| } | ||
|
|
||
| private static @Nullable GradleVersionCatalogDependency extractWithCursor( |
There was a problem hiding this comment.
When we remove extract, then this can be inlined into test.
| } | ||
|
|
||
| private static boolean matches(String pluginId, @Nullable String pattern) { | ||
| return StringUtils.isBlank(pattern) || matchesGlob(pluginId, pattern); |
There was a problem hiding this comment.
| return StringUtils.isBlank(pattern) || matchesGlob(pluginId, pattern); | |
| return pattern == null || matchesGlob(pluginId, pattern); |
This matches consistently with what we do elsewhere.
| private static boolean matchesPatterns( | ||
| @Nullable String groupId, @Nullable String artifactId, | ||
| @Nullable String groupPattern, @Nullable String artifactPattern) { | ||
| if (groupPattern != null && !matchesGlob(groupId, groupPattern)) { | ||
| return false; | ||
| } | ||
| return artifactPattern == null || matchesGlob(artifactId, artifactPattern); | ||
| } |
There was a problem hiding this comment.
This can be replaced with DependencyMatcher.
| * Entries using {@code version.ref} are intentionally unchanged; their shared version | ||
| * entry is updated by the recipe after selecting a version. | ||
| */ | ||
| public Toml.KeyValue withVersion(String newVersion) { |
There was a problem hiding this comment.
This benefits with a similar API as discussed in the dependency trait.
GradleVersionCatalogPlugin withVersion(String)
| public Toml.KeyValue withVersion(String newVersion) { | ||
| if (newVersion.equals(version) || versionRef != null) { | ||
| return getTree(); | ||
| } | ||
| Toml.KeyValue kv = getTree(); | ||
| if (kv.getValue() instanceof Toml.Literal) { | ||
| Toml.Literal literal = (Toml.Literal) kv.getValue(); | ||
| if (!(literal.getValue() instanceof String)) { | ||
| return kv; | ||
| } | ||
| Dependency dependency = DependencyNotation.parse((String) literal.getValue()); | ||
| if (dependency == null) { | ||
| return kv; | ||
| } | ||
| String notation = DependencyNotation.toStringNotation(dependency.withGav(dependency.getGav().withVersion(newVersion))); | ||
| return kv.withValue(literal.withSource(TomlTableValue.quoted(literal, notation)).withValue(notation)); | ||
| } | ||
| if (kv.getValue() instanceof Toml.Table) { | ||
| Toml.Table inline = (Toml.Table) kv.getValue(); | ||
| return kv.withValue(inline.find("version") == null ? | ||
| TomlTableValue.withStringOrAdd(inline, "version", newVersion) : | ||
| TomlTableValue.withString(inline, "version", newVersion)); | ||
| } | ||
| return kv; | ||
| } |
There was a problem hiding this comment.
I would imagine that these become simpler by reusing the TOML visitors, such as ChangeValue. There also exists DeleteKey and it's synonym AddKey/AddKeyVisitor (if we don't want a recipe right now) would likely be particularly useful.
Usage:
Toml.KeyValue keyValue = getValue();
Toml.KeyValue updated = new ChangeValue("version", newVersion).getVisitor().visitNonNull(entry, new InMemoryExecutionContext()); // this may need to come in as a method argument, but for now this is fine.
There was a problem hiding this comment.
I think I got it, changed
|
Thanks @KamilPatora for the start on this! I've left some comments about various improvements. Let us know if you have any questions. |
…move unsupported version checks
…n to enhance value updating logic and streamline key management
…updates and improve dependency matching logic
|
Bump @shanman190 @timtebeek |
|
bump bump |
|
I'm not getting to a review here @KamilPatora , and will be out for a prolonged time, so I've unassigned myself from review. Hoping my colleagues can step in. |
|
Sure @timtebeek , no worries. I've moved this PR to #8699 |
What's changed?
version.refupdates.What's your motivation?
Provide a reusable, formatting-preserving semantic model for Gradle version catalogs and enable safe dependency, plugin, and shared-version updates.
Anything in particular you'd like reviewers to focus on?
Probably whole pr
Anyone you would like to review specifically?
Have you considered any alternatives or workarounds?
We do have our open-source 'version' of openrewrite where we handle TOML values: https://github.com/allegro/allwrite
Any additional context
Added positive and negative coverage for custom catalog paths, missing version references, versionless notation, malformed coordinates, visitor composition, and TOML quote preservation.
Created from this PR #8274
Checklist