Repository: chainguard-dev/apko
Pull request #1619 (2025-04-11 20:11:26)

version.go: Ignore soname-versioned provides/depends for shlibs
Currently, packages that ship shared libraries in the archive will have versioned provides for those shlibs.  These versions are calculated from the soname version, e.g.:

```
provides = so:libfoo.so.6=6
```

With the advent of
https://github.com/chainguard-dev/melange/pull/1871, the new versions will reflect the package version.  This can introduce a package resolution problem when the soname version is greater than the package version, e.g.:

```
  libfoo-4.0-r1
  provides = so:libfoo.so.6=6
```

vs.

```
  libfoo-4.0-r2
  provides = so:libfoo.so.6=4.0-r2
```

It's clear that we should select libfoo-4.0-r2 as the most recent package, but apko will currently select libfoo-4.0-r1 instead.

The solution proposed here is to ignore the versioned depends/provides *iff* the version that comes after "=" does NOT contain a release substring (`-rN`).  This will effectively make the case above be:

```
  libfoo-4.0-r1
  provides = so:libfoo.so.6
```

vs.

```
  libfoo-4.0-r2
  provides = so:libfoo.so.6=4.0-r2
```

which will force apko to select the latest package.

Thanks to @dannf, @justinvreeland  and @jonjohnsonjr for the brainstorm sessions.

sergiodj: Huh, interesting...

https://github.com/chainguard-dev/apko/blob/8f267519ba25b98f89cf8efabbd1bed9c8e657e8/pkg/apk/apk/repo_test.go#L906-L929

More specifically, the comment:

```go
	// When two options provide the different versions of a virtual, we expect to take the higher virtual version.
```

This is why the tests are currently failing.

It seems to me that there's a mismatch between what we *expect* apko to do and what it actually does (also based on @xnox's comment [here](https://github.com/chainguard-dev/melange/pull/1871#discussion_r2037940710)).

I'll mark this PR as a draft for now.

Patch:
diff --git a/pkg/apk/apk/version.go b/pkg/apk/apk/version.go
index 185dbf7..f675723 100644
--- a/pkg/apk/apk/version.go
+++ b/pkg/apk/apk/version.go
@@ -356,7 +356,28 @@ type parsedConstraint struct {
 	pin     string
 }
 
+var endsWithReleaseStr = regexp.MustCompile(`-r\d+$`)
+
 func resolvePackageNameVersionPin(pkgName string) parsedConstraint {
+	// Due to https://github.com/chainguard-dev/melange/pull/1871,
+	// we have to treat shared library depends/provides
+	// differently.
+	//
+	// If a shared library's depends/provides has an explicitly
+	// version, and if the version does NOT end with a release
+	// substring (`-rN`), then we tweak its version to sort
+	// *after* the ones that end with `-rN`.
+	//
+	// We only care if the version string ends with a release
+	// substring (`-rN`), as that indicates that it has a proper
+	// versioned depends/provides containing the package version.
+	if strings.HasPrefix(pkgName, "so:") {
+		onlyPkgName, pkgVersion, found := strings.Cut(pkgName, "=")
+		if found && !endsWithReleaseStr.MatchString(pkgVersion) {
+			pkgName = onlyPkgName + "=0." + pkgVersion
+		}
+	}
+
 	parts := packageNameRegex.FindAllStringSubmatch(pkgName, -1)
 	if len(parts) == 0 || len(parts[0]) < 2 {
 		return parsedConstraint{


Test patch:
diff --git a/pkg/apk/apk/version_test.go b/pkg/apk/apk/version_test.go
index 1e99665..07240d1 100644
--- a/pkg/apk/apk/version_test.go
+++ b/pkg/apk/apk/version_test.go
@@ -935,6 +935,8 @@ func TestResolverPackageNameVersionPin(t *testing.T) {
 		{"name<=1.2.3", "name", "1.2.3", versionLessEqual, ""},
 		{"name@edge=1.2.3", "name@edge=1.2.3", "", versionAny, ""}, // wrong order, so just returns the whole thing
 		{"name=1.2.3@community", "name", "1.2.3", versionEqual, "community"},
+		{"so:libfoo.so.6=6", "so:libfoo.so.6", "0.6", versionEqual, ""},
+		{"so:libfoo.so.6=1.0-r3", "so:libfoo.so.6", "1.0-r3", versionEqual, ""},
 	}
 
 	for _, tt := range tests {