Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
src/cmd/apt_query/apt_query*
apt_query_test*
*.log
Binary file modified apt_query-arm64
Binary file not shown.
Binary file modified apt_query-x86
Binary file not shown.
6 changes: 6 additions & 0 deletions src/cmd/apt_query/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ func TestNormalizedList_VirtualPackagesExists_StdoutsConcretePackage(t *testing.
result := cmdtesting.New(t, createReplayLogs).Run("normalized-list", "libvips")
result.ExpectSuccessfulOut("libvips42=8.9.1-2")
}

func TestNormalizedList_VirtualPackageWithNoProviders_StderrsErrorMessage(t *testing.T) {
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list", "python")
result.ExpectError(`Encountered error resolving some or all package names, see combined std[out,err] below.
virtual package 'python' has no concrete package providers available`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
2025/10/04 06:59:56 Debug log created at /home/runner/work/cache-apt-pkgs-action/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
2025/10/04 07:00:00 EXECUTION-OBJ-START
{
"Cmd": "apt-cache --quiet=0 --no-all-versions show python",
"CombinedOut": "N: Can't select candidate version from package python as it has no candidate\nN: Can't select versions from package 'python' as it is purely virtual\nN: No packages found\n",
"ExitCode": 0
}
EXECUTION-OBJ-END
2025/10/04 07:00:00 EXECUTION-OBJ-START
{
"Cmd": "bash -c apt-cache showpkg python | grep -A 1 \"Reverse Provides\" | tail -1",
"CombinedOut": "Reverse Provides: \n",
"ExitCode": 0
}
EXECUTION-OBJ-END
2025/10/04 07:00:00 Encountered error resolving some or all package names, see combined std[out,err] below.
virtual package 'python' has no concrete package providers available
21 changes: 16 additions & 5 deletions src/internal/common/apt.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ func isErrLine(line string) bool {
return strings.HasPrefix(line, "E: ") || strings.HasPrefix(line, "N: ")
}

// isVirtualPackage checks if a string indicates a purely virtual package message.
func isVirtualPackage(message string) bool {
return strings.Contains(message, "as it is purely virtual")
}

// Resolves virtual packages names to their concrete one.
// Returns the first available concrete package provider, or an error if none exist.
func getNonVirtualPackage(executor exec.Executor, name string) (pkg *AptPackage, err error) {
execution := executor.Exec("bash", "-c", fmt.Sprintf("apt-cache showpkg %s | grep -A 1 \"Reverse Provides\" | tail -1", name))
err = execution.Error()
Expand All @@ -42,10 +48,19 @@ func getNonVirtualPackage(executor exec.Executor, name string) (pkg *AptPackage,
if isErrLine(execution.CombinedOut) {
return pkg, execution.Error()
}

// Check if the output is just "Reverse Provides:" with no actual package info
trimmedOutput := strings.TrimSpace(execution.CombinedOut)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other code locations where virtual packages are evaluated. Consolidate this logic and how it is reported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidated virtual package detection by introducing isVirtualPackage helper function (commit 49b6053). This centralizes the logic for detecting "as it is purely virtual" messages and makes the code more maintainable.

if trimmedOutput == "Reverse Provides:" {
return pkg, fmt.Errorf("virtual package '%s' has no concrete package providers available", name)
}

splitLine := GetSplitLine(execution.CombinedOut, " ", 3)
if len(splitLine.Words) < 2 {
return pkg, fmt.Errorf("unable to parse space delimited line's package name and version from apt-cache showpkg output below:\n%s", execution.CombinedOut)
}

// Successfully found a concrete package provider
return &AptPackage{Name: splitLine.Words[0], Version: splitLine.Words[1]}, nil
}

Expand All @@ -67,7 +82,7 @@ func getPackage(executor exec.Executor, paragraph string) (pkg *AptPackage, err

case "N":
// e.g. Can't select versions from package 'libvips' as it is purely virtual
if strings.Contains(splitLine.Words[1], "as it is purely virtual") {
if isVirtualPackage(splitLine.Words[1]) {
return getNonVirtualPackage(executor, GetSplitLine(splitLine.Words[1], "'", 4).Words[2])
}
if strings.HasPrefix(splitLine.Words[1], "Unable to locate package") && !ArrContainsString(errMsgs, splitLine.Line) {
Expand Down Expand Up @@ -112,10 +127,6 @@ func GetAptPackages(executor exec.Executor, names []string) (AptPackages, error)
}
}

if len(errMsgs) > 0 {
errMsgs = append(errMsgs, strings.Join(errMsgs, "\n"))
}

sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Name < pkgs[j].Name
})
Expand Down