jamesfredley opened a new pull request, #15693:
URL: https://github.com/apache/grails-core/pull/15693

   Fixes #14058
   
   ## Summary
   
   `grails.plugins.VersionComparator` did not understand milestone (`M`) or 
release candidate (`RC`) version qualifiers. The plugin compatibility check in 
`DefaultGrailsPluginManager` delegates to this comparator, so plugins targeting 
(or applications running on) a milestone or release candidate produced 
incorrect "may not be compatible with this application" warnings. This PR makes 
the comparator qualifier-aware and adds the missing test coverage requested by 
the issue.
   
   ## The bug
   
   `VersionComparator` tokenised a version by splitting on `.` and keeping only 
purely numeric tokens (`==~ /\d+/`). `-SNAPSHOT` / `.BUILD-SNAPSHOT` were 
handled as a special case, but `M`/`RC` qualifiers were not, so the qualifier - 
and often the patch number - was silently discarded:
   
   | Input | Parsed as (before) | Problem |
   |-------|--------------------|---------|
   | `7.0.0-M1` | `[7, 0]` | patch `0` and `-M1` both dropped |
   | `7.0.5-M1` | `[7, 0]` | patch `5` lost; compares as `7.0` |
   | `7.0.0.M1` | `[7, 0, 0]` | `M1` dropped; compares equal to final `7.0.0` |
   
   This led to two concrete defects in the compatibility check:
   
   1. **Lost patch number.** A plugin requiring `7.0.3 > *` running on 
`7.0.5-M1` was wrongly flagged incompatible, because `7.0.5-M1` was parsed as 
`7.0` (< `7.0.3`).
   2. **Milestones/RCs of a base version all compared equal**, and equal to the 
final release. A plugin requiring the final `7.0.0` was treated as compatible 
with the pre-release `7.0.0-RC1`, and milestone ordering carried no meaning.
   
   ### Why this looked like it already had coverage
   
   The codebase has three independent version representations. The well-tested 
milestone/RC ordering lives on classes the compatibility check never calls:
   
   | Class | Handles `M`/`RC`? | Tested for `M`/`RC`? | Used by the 
compatibility check? |
   
|-------|-------------------|----------------------|----------------------------------|
   | `grails.plugins.VersionComparator` | No (fixed here) | No (added here) | 
**Yes** |
   | `org.grails.datastore.mapping.core.grailsversion.GrailsVersion` / 
`Snapshot` | Yes | Yes | No |
   | `grails.init.GrailsVersion` (wrapper) | Yes | Yes | No |
   
   ## The fix
   
   `VersionComparator` now parses a version into its numeric components plus an 
optional qualifier:
   
   - Numeric components are compared first, zero padding the shorter side so 
`7.0` == `7.0.0` and `7.0.1` > `7.0.0-RC9`.
   - When the numeric components are equal, the qualifier breaks the tie using 
the same ordering as `GrailsVersion` / `Snapshot`:
   
     ```
     7.0.0-M1 < 7.0.0-M2 < 7.0.0-RC1 < 7.0.0-RC2 < 7.0.0-SNAPSHOT < 7.0.0
     ```
   
   - Dotted (`7.0.0.M1`) and hyphenated (`7.0.0-M1`) forms are treated as 
equivalent, and qualifier matching is case insensitive (`7.0.0-rc3` == 
`7.0.0-RC3`).
   - Milestone and release candidate numbers are compared numerically, so 
`7.0.0-RC10` > `7.0.0-RC2`.
   - Unrecognised qualifiers are still treated as a final release, preserving 
the previous "ignore unknown suffix" behaviour.
   
   The protected `deSnapshot` / `isSnapshot` methods are retained for backwards 
compatibility.
   
   ## Behaviour preserved
   
   All pre-existing `VersionComparatorSpec` and 
`DefaultGrailsPluginManagerSpec` cases are unchanged and still pass (numeric 
comparisons, `BUILD-SNAPSHOT < release`, the existing range checks, etc.).
   
   ## Blast radius
   
   `VersionComparator` has four callers, all verified safe:
   
   - `GrailsVersionUtils.isValidVersion` - already strips qualifiers via 
`trimTag()` before comparing, so its behaviour is unchanged.
   - `GrailsVersionUtils.isVersionGreaterThan` - no callers.
   - `RegexUrlMapping` - compares user-defined API versions (plain numeric, no 
`M`/`RC`).
   - `DefaultGrailsPluginManager.isCompatiblePlugin` - the path this PR fixes.
   
   ## Tests
   
   - `VersionComparatorSpec`: pre-release vs final ordering, patch preservation 
(`7.0.5-M1` > `7.0.0`), milestone/RC numeric ordering (`7.0.0-RC10` > 
`7.0.0-RC2`), the full tier order, dotted/hyphenated and snapshot-form 
equivalence, numeric-base dominance, plus a sort assertion over a mixed list.
   - `DefaultGrailsPluginManagerSpec`: `isCompatiblePlugin` rows for milestone, 
release candidate and snapshot versions on both the application and the plugin.
   
   ## Verification
   
   - `./gradlew :grails-core:test --tests 
"grails.plugins.VersionComparatorSpec" --tests 
"grails.plugins.DefaultGrailsPluginManagerSpec"` - passing.
   - `./gradlew :grails-bootstrap:codenarcMain` - passing.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to