apache / apache/grails-core

Consolidate duplicated Grails version comparison logic (VersionComparator vs GrailsVersion/Snapshot)

Open
#15,695 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Groovy
Stars
2.9k
Forks
975
Avg merge
1d 22h
Merged PRs (30d)
92

Description

## Background

There are currently three independent implementations of Grails version parsing/comparison in the repository:

| Class | Module | Notes |
|-------|--------|-------|
| `grails.plugins.VersionComparator` | `grails-bootstrap` | Lenient `Comparator`; accepts 2-part versions, wildcards (`*`) and arbitrary plugin-declared ranges. Used by the plugin compatibility check and URL mapping version precedence. |
| `org.grails.datastore.mapping.core.grailsversion.GrailsVersion` / `Snapshot` | `grails-datastore-core` | Strict; requires 3-4 part versions and validates qualifiers against `^(M\|RC\|Final)\d*$`. The canonical milestone/RC/snapshot ordering. |
| `grails.init.GrailsVersion` | `grails-wrapper` | Another full parser/comparator used by the wrapper. |

The milestone/release-candidate ordering logic (`M < RC < SNAPSHOT < final`) is therefore implemented more than once. This was highlighted while fixing #14058, where `VersionComparator` had to re-implement the same ordering that `GrailsVersion`/`Snapshot` already encode, because:

1. `grails-bootstrap` is a deliberately dependency-light, low-level module and cannot reasonably depend on `grails-datastore-core` (GORM core) just to reuse `GrailsVersion`.
2. `GrailsVersion` is stricter than `VersionComparator` needs to be (it throws `IllegalArgumentException` on 2-part versions such as `4.0`, `1.0`, `2.5` that `VersionComparator` and its callers rely on).

## Proposal

Extract the shared version-ordering core (numeric component comparison + qualifier tier ordering) into a single low-level location that every module can reuse - for example a class in `grails-common` (which `grails-datastore-core` already depends on, and which `grails-bootstrap` could depend on without a cycle).

Goals:

- One implementation of the milestone/RC/snapshot ordering, reused by `VersionComparator`, `GrailsVersion`/`Snapshot`, and ideally the wrapper's `GrailsVersion`.
- Preserve the lenient behaviour required by `VersionComparator` (2-part versions, `*` wildcard, unknown qualifiers) while keeping the strict validation `GrailsVersion` exposes to its own callers.
- No behaviour change for existing consumers; consolidate behind the existing public APIs.

## Notes

- This is a refactor/maintenance task, not a bug. The behaviour itself is fixed in #14058.
- Because it would move/extract public classes and touch the GORM datastore module, it needs the cross-module review that dependency/structure changes require (see `CONTRIBUTING.md`).

Contributor guide

Open the contributing guide

Research direction

Start by reading the existing VersionComparator, org.grails.datastore.mapping.core.grailsversion.GrailsVersion/Snapshot, and grails.init.GrailsVersion implementations, then review CONTRIBUTING.md for cross-module dependency guidance. Compare their qualifier ordering, validation, and lenient cases before evaluating the proposed grails-common location. Done means the existing public APIs preserve their behavior while sharing one ordering implementation across the applicable modules.

Written by the indexing model from the issue text.

Assessment

Tech stack
groovy
Domain
backend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.