As part of future changes, we're introducing a means to parse
Dockerfiles for any `apk add` commands, so we can propose updates to any
packages using the APK datasource.
Before we do this, we need to correct a couple of bugs in our
implementation of the APK versioning scheme, so we can handle i.e.
apk add curl=~8.12.1
When the `apk` CLI builds a version constraint, it `OR`s one flag per
operator character[0].
For instance, `~` sets both "fuzzy" and "equal".
These operator characters may be in any order and may repeat, so `~`,
`=~` and `~=` are all the same operator, and `>>` and `>` are the same.
To correct our parsing, we should take this into account, rather than
using known patterns.
When we perform a fuzzy match, this is performed over version _tokens_
not the version itself, which means that:
- `~1.6` matches `1.6`, `1.6.0_pre1`, `1.6.0`, `1.6.5` and `1.6.9_p1`
- `~1.6` does not match `1.60`
- `~8.12.1` matches every `8.12.1-rN` i.e. `8.12.1-r1` and `8.12.1-r100`
While doing this, we also fix related issues:
- `~` previously treated this as an npm-style tilde ("same major and
minor, and greater than or equal"),
- admitted fuzzy versions that `apk` rejects - `~2.39.0-r0` should not have
matched `2.39.1-r0`
- rejected fuzzy versions that `apk` accepts - `~2.39.0` should have
matched `2.39.0-r1`
- `=~` and `~=` were not allowed
- `isSingleVersion('=~1.2.3')` was true, so a range looked like a pin
- `isLessThanRange` compared against the raw range, so `_parse` failed on
the operator and it always returned false
- `minSatisfyingVersion` was inherited, so it only ever found an exact
match
- `getNewValue` dropped the operator entirely, rewriting `~8.12.1` to a
bare version
We also set `supportsRanges`, which we'd forgotten to set despite `~`
and `>=` being ranges, and note that we support `pin`, `replace` and
`bump` strategies.
When using `getNewValue`, we now keep the precision the constraint was
written with, as otherwise we'd quietly narrow what the user asked for:
- `~8.12.1` becomes `~8.13.0` (previously `~8.13.0-r0`)
- `~8.12` becomes `~8.13`
- `~8.12.1-r0` becomes `~8.13.0-r2` (with a new revision number)
For `bump`, we raise a lower bound which includes its boundary, as with
`npm` and `pep440`, so:
- `>=8.12.1` becomes `>=8.13.0`
- `>~8.12` becomes `>~8.13`
Otherwise we return `null`, logging which constraint we couldn't handle
in the `bump` case, as `npm` does:
- `>` on its own can't be bumped, as `>8.13.0` would exclude the version
we bump to
- `<` and `<=` are upper bounds, so there's no lower bound to raise
- for `replace`, there's no single obvious new bound for `<`, `<=`, `>`,
`>=`, `>~` or `<~`, and a bound which already admits the new version
doesn't need one
- a prefix constraint whose version doesn't parse, i.e. `~foo`, which
would otherwise throw
`><` constrains a package to an identity hash rather than to a version,
so it's rejected outright.
Additionally, `isLessThanRange` and `matches` weren't agreeing on how to
resolve a fuzzy constraint.
For instance, `_compare` ranks `2.39.0_git20240101` below `2.39.0`, but
`~2.39.0` matches it, this would lead to a rollback of sorts.
Instead, we can ignore the `_git` and `_p` suffixes, which are common in
Alpine and Wolfi packages.
[0]: https://gitlab.alpinelinux.org/alpine/apk-tools/-/blob/44dcdfc255c26a4e19a4a27f66a54169cce5ca72/src/version.c
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Alpine Package Keeper (APK) versioning is used for packages served from APK repositories, such as the official Alpine Linux repositories or the Wolfi APK package repository
This implementation follows the Alpine Linux Package versions for version formatting.
Versions are similar to other Linux distributions, e.g. 3.2.1-r0
- The first segment follows semantic versioning
- Alpha, release candidates (_rc2), etc are prefixed with an underscore, not a hyphen.
- Subsequent package versions are -r0, -r1, and so on; the number is from $pkgver.
- Subsequent package fixes are _p0, _p1 (typically seen if not using major.minor.patch, e.g. 6.5_p20250503-r0)
Version Format Examples
2.39.0-r0- Standard version with release number2.39.0_rc1-r0- Release candidate (pre-release)6.5_p20250503-r0- Package fix with date-based patch
Pre-release Handling
_rcpatterns (e.g.,_rc1,_rc2) are treated as pre-release identifiers_ppatterns (e.g.,_p20250503) are treated as part of the version number
Version constraints
Constraints follow apk-world(5), which builds the operator from its characters, so the characters may be given in any order and may repeat.
This means ~, =~ and ~= all mean the same thing.
| Operator | Meaning |
|---|---|
| (none) | exact version |
= |
exact version |
< |
less than |
<= |
less than or equal |
> |
greater than |
>= |
greater than or equal |
~ |
prefix match |
>~ |
greater than or prefix match |
<~ |
less than or prefix match |
A prefix match compares token by token rather than by string, so ~1.6 matches 1.6, 1.6.0_pre1, 1.6.0, 1.6.5 and 1.6.9_p1, but not 1.60.
Because a revision is a token of its own, ~8.12.1 matches every 8.12.1-rN, which is how Wolfi and Chainguard images commonly pin their packages.
Renovate keeps the precision a prefix constraint was written with, so ~8.12.1 becomes ~8.13.0 rather than ~8.13.0-r0, and ~8.12 becomes ~8.13.
With rangeStrategy=bump, Renovate raises a lower bound which includes its boundary to the new version, so >=8.12.1 becomes >=8.13.0-r0 and >~8.12 becomes >~8.13.0-r0.
A bare > is left alone, as >8.13.0-r0 would exclude the very version it was bumped to, and < and <= are upper bounds so there is nothing to raise.
Otherwise Renovate does not propose a replacement for the <, <=, >, >=, >~ and <~ operators, as there is no single obvious new bound for them.
The >< operator constrains a package to an identity hash rather than to a version, so it is not a version constraint Renovate can resolve.