Feat: Add id attribute (gav) support to Dependency, Exclusion, Mixin#11904
Feat: Add id attribute (gav) support to Dependency, Exclusion, Mixin#11904rbygrave wants to merge 13 commits into
Conversation
Based on design discussion on Issue apache#11500, the proposal is to support `groupId:artifactId:version` format for Dependency, Exclusion and Mixin. This means that we can define dependencies in the form: ```xml <dependency id="groupId:artifactId:version" /> ``` Examples: ```xml <dependency id="org.slf4j:slf4j-api:2.0.17"/> ``` ```xml <dependency id="org.postgresql:postgresql:42.7.3"> <exclusions> <exclusion id="*:*"/> </exclusions> </dependency> ``` ```xml <mixin id="com.example.mixins:java-mixin:1.0.0" /> ```
Add more tests and make them more specific to the validation that is being tested by each test.
| <superClass>Parent</superClass> | ||
| <fields> | ||
| <field xml.attribute="true" xml.tagName="id"> | ||
| <name>gav</name> |
There was a problem hiding this comment.
Using field name gav with attribute name id ... in order to avoid the conflict with the existing Parent.getId()
gnodet
left a comment
There was a problem hiding this comment.
Claude Code on behalf of Guillaume Nodet
Overall a clean implementation. The strict validation, test coverage, and the Mixin gav field workaround are well thought out. A few issues to address:
-
Dependency management dependencies are not expanded --
mergeDuplicatesonly expandsidonmodel.getDependencies()but not onmodel.getDependencyManagement().getDependencies(), nor on profile-scoped dependencies/dependency-management. The validator correctly validates all of these (lines 536-596 of DefaultModelValidator), so managed deps withidwill pass validation but never get expanded. -
Normalization runs after raw validation --
validateRawModelruns indoReadRawModel()(line 1818) beforemergeDuplicates(line 1362 inreadInputModel). The validator's duplicate-detection key logic (usingdependency.getId()directly when set) handles this correctly, but it means the validator sees the unexpanded state. This is fine for the conflict checks but worth keeping in mind. -
Dependency.getId()naming clash -- TheDependencyclass now has a newidfield withgetId()/setId()via the model generator. ButParent(and thereforeMixin) has a computedgetId()method in a codeSegment that returnsgroupId:artifactId:pom:version. SinceMixin extends Parent, the new field was wisely renamed togav. However, does Dependency have a similar computedgetId()method anywhere? If so, the newidfield's generated getter could shadow or conflict with it. Worth verifying the generated code compiles cleanly. -
isBlankdoes not trim -- TheisBlankhelper in the normalizer only checks for null/empty, not for whitespace-only strings. The nameisBlankis misleading sinceString.isBlank()checks whitespace. Consider renaming toisNullOrEmptyor usingString.isBlank()which handles whitespace in coordinates (e.g.," ").
| } | ||
| return builder.build(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Nit: this method name is misleading -- String.isBlank() in Java checks for whitespace-only strings, but this only checks null/empty. Consider renaming to isNullOrEmpty to avoid confusion, or use s == null || s.isBlank() to also handle whitespace-only values in coordinates.
| private static boolean isNullOrEmpty(String s) { | |
| return s == null || s.isEmpty(); | |
| } |
| if (dependency.getId() != null && !dependency.getId().isEmpty()) { | ||
| key = dependency.getId(); | ||
| } else { | ||
| key = dependency.getManagementKey(); |
There was a problem hiding this comment.
Good approach using dependency.getId() as the dedup key when set, since normalization hasn't run yet at this point. However, two dependencies with the same id attribute but different scopes/classifiers would collide here (since the key is just the raw id string, not the management key). After normalization, they'd have distinct management keys (which includes type and classifier). Is this the intended behavior?
… profiles - Expand id attributes on dependencies in dependencyManagement, not just top-level dependencies - Expand id attributes on dependencies within profile sections (both regular dependencies and profile dependencyManagement) - Extract expandAndDeduplicateDependencies() and expandProfileDependencyIds() helper methods - Rename isBlank to isNullOrEmpty for clarity - Add tests for dependency management and profile id expansion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add compact `id` XML attribute to Dependency, Exclusion, and Mixin elements in the Maven model (4.2.0+). Supported formats: - Dependency: g:a:v, g:a:type:v, g:a:type:classifier:v - Exclusion: g:a - Mixin (as `gav`/`id`): g:a:v The normalizer expands `id` into individual fields and clears it so consumer POMs never contain the attribute. Uses forceCopy=true in builders to ensure `id(null)` actually clears the field. Includes comprehensive unit tests (normalizer, validator), consumer POM transformation tests, and integration test verifying no id attributes leak into deployed POMs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…attributes Add DependencyIdStrategy that migrates verbose dependency/exclusion child elements into compact `id` attributes when upgrading to model version 4.2.0 via mvnup. The strategy runs at @priority(25), after InferenceStrategy (30), so only dependencies that still have full explicit coordinates after inference are collapsed. Triggered by --model-version 4.2.0 or --all. Supported transformations: - <dependency> g:a:v / g:a:type:v / g:a:type:classifier:v - <exclusion> g:a - Processes all sections: dependencies, dependencyManagement, profiles, plugin dependencies Inferred ids (partial coordinates like `:a` or `g:a` without version) are intentionally not supported — the `id` attribute is a shorthand for explicit coordinates, not a replacement for Maven's inference mechanism. Dependencies with missing coordinates after inference are left in their expanded form. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ribute Add support for 2-part `g:a` format and trailing-colon formats (`g:a:`, `g:a:type:`, `g:a:type:classifier:`) for dependencies whose version is provided by dependencyManagement. The trailing colon convention signals that version is managed, not missing. Leave aside inferred ids (`:a:` with inferred groupId) for now. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend the compact id attribute format from groupId:artifactId:version to groupId:artifactId:version[@scope][?], where @scope sets the dependency scope and trailing ? marks the dependency as optional. Examples: - org.junit:junit-jupiter-api:5.0@test - commons-io:commons-io:2.11.0? - org.apache.maven:maven-core:3.9.0@provided? Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ature/dependency-id-attribute Combines type/classifier/managed-version support with @scope/? optional markers in the dependency id attribute compact syntax. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Empty segments in the compact id attribute are now skipped rather than setting fields to empty strings, allowing groupId/version/type to be left unset for later inference from dependencyManagement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The formal syntax incorrectly showed version before type. Maven consistently uses groupId:artifactId:type:classifier:version order (as in Artifact.key() and Dependency.getManagementKey()). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dator - Remove redundant model version guard in DependencyIdStrategy (the second check alone suffices) - Simplify redundant type removal branches in collapseDependency() - Remove redundant validateExclusionIdAttribute call from effective model validation (id is already cleared by normalizer before effective validation) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Based on design discussion on Issue #11500, the proposal is to support a compact
idattribute format for Dependency, Exclusion and Mixin elements.Dependency
idFormatThe
idattribute supports the following compact formats, using the standard Maven coordinate order (groupId:artifactId:type:classifier:version):groupId:artifactId<dependencyManagement>groupId:artifactId:versiongroupId:artifactId:type:versiongroupId:artifactId:type:classifier:versionFull syntax:
groupId:artifactId[[:type[:classifier]]:version][@scope][?]A trailing
:(empty version) indicates the version is managed (e.g.groupId:artifactId:type:).Any segment may be left empty to skip setting that field, leaving it for later inference (e.g. from
dependencyManagement). For example,:artifactIdleaves groupId unset, and:artifactId:versionleaves groupId unset with an explicit version.An optional
@scopesuffix sets the dependency scope (test,provided,runtime,compile,system,import). A trailing?marks the dependency as optional.Examples
Validation
idis used, child elementsgroupId,artifactId, andversionmust not be specified (even if the corresponding segment in the id is empty).typeconflict is only checked when theidhas 4+ parts (3+ colons, i.e. type is encoded).classifierconflict is only checked when theidhas 5 parts (4 colons, i.e. classifier is encoded).@scopeis present in theid, a<scope>child element must not be specified.?is present, an<optional>child element must not be specified.idattribute.Inference
Empty segments are skipped during normalization, leaving the corresponding field unset for later inference. This allows patterns like:
:artifactId— groupId and version left unset for inference fromdependencyManagement:artifactId:version— groupId left unsetgroupId:artifactId:type:— version left unset (managed), type explicitNotes
idbut usesgavunderneath as the Java field, to work around a name clash withgetId()from Parent.idinto component fields and clears theidattribute, so downstream code works with standard dependency fields.idattribute only fills in fields that are currently null/empty.Following this checklist to help us incorporate your
contribution quickly and easily:
Note that commits might be squashed by a maintainer on merge.
This may not always be possible but is a best-practice.
mvn verifyto make sure basic checks pass.A more thorough check will be performed on your pull request automatically.
If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.
To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.