From 89e2a20145d3852666727ffb4a30187f957089f1 Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Mon, 20 Apr 2026 19:03:57 -0700 Subject: [PATCH 1/5] feat(package list): add --synthesize-debug-packages to package list Augments 'azldev package list' with synthetic '-debuginfo' packages (one per reported package, sharing the original publish channel) and '-debugsource' packages (one per component, resolved with no per-package override so they fall back to the configured default publishing channel). This is a short-term approach until we're able to properly merge changes that shift us over to an inheritance-driven declaration for where source packages, debuginfo packages, etc. should go for a given component. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../user/reference/cli/azldev_package_list.md | 7 +- internal/app/azldev/cmds/pkg/list.go | 69 +++++++++++++ internal/app/azldev/cmds/pkg/list_test.go | 98 +++++++++++++++++++ .../TestMCPServerMode_1.snap.json | 5 + 4 files changed, 176 insertions(+), 3 deletions(-) diff --git a/docs/user/reference/cli/azldev_package_list.md b/docs/user/reference/cli/azldev_package_list.md index 144e9a27..250d1da3 100644 --- a/docs/user/reference/cli/azldev_package_list.md +++ b/docs/user/reference/cli/azldev_package_list.md @@ -42,9 +42,10 @@ azldev package list [package-name...] [flags] ### Options ``` - -a, --all-packages List all explicitly-configured binary packages - -h, --help help for list - -p, --package stringArray Package name to look up (repeatable) + -a, --all-packages List all explicitly-configured binary packages + -h, --help help for list + -p, --package stringArray Package name to look up (repeatable) + --synthesize-debug-packages Also synthesize '-debuginfo' packages (per reported package) and '-debugsource' packages (per component) ``` ### Options inherited from parent commands diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go index e39b1dc6..cba2b26b 100644 --- a/internal/app/azldev/cmds/pkg/list.go +++ b/internal/app/azldev/cmds/pkg/list.go @@ -21,6 +21,13 @@ type ListPackageOptions struct { // PackageNames contains specific binary package names to look up. // If a package is not in any explicit config it is still resolved using project defaults. PackageNames []string + + // SynthesizeDebugPackages, when true, augments the result list with synthetic + // '-debuginfo' packages (one per reported package, sharing the original package's + // publish channel) and synthetic '-debugsource' packages (one per component in the + // project configuration, with no explicit publish channel — so they resolve to the + // configured default publishing channel). + SynthesizeDebugPackages bool } func listOnAppInit(_ *azldev.App, parent *cobra.Command) { @@ -66,6 +73,8 @@ Resolution order (lowest to highest priority): cmd.Flags().BoolVarP(&options.All, "all-packages", "a", false, "List all explicitly-configured binary packages") cmd.Flags().StringArrayVarP(&options.PackageNames, "package", "p", []string{}, "Package name to look up (repeatable)") + cmd.Flags().BoolVar(&options.SynthesizeDebugPackages, "synthesize-debug-packages", false, + "Also synthesize '-debuginfo' packages (per reported package) and '-debugsource' packages (per component)") azldev.ExportAsMCPTool(cmd) @@ -186,6 +195,10 @@ func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListRe }) } + if options.SynthesizeDebugPackages { + results = synthesizeDebugPackages(results, proj) + } + // Sort by package name for deterministic, readable output. sort.Slice(results, func(i, j int) bool { return results[i].PackageName < results[j].PackageName @@ -193,3 +206,59 @@ func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListRe return results, nil } + +// synthesizeDebugPackages augments results with synthetic '-debuginfo' packages (one per +// already-resolved package, sharing the original publish channel) and '-debugsource' packages +// (one per component, with no publish channel — so they fall back to the configured default +// publishing channel). +// +// Synthetic entries that collide with an already-present (real) package name are skipped so +// real configuration always wins. +func synthesizeDebugPackages( + results []PackageListResult, proj *projectconfig.ProjectConfig, +) []PackageListResult { + existing := make(map[string]struct{}, len(results)) + for _, result := range results { + existing[result.PackageName] = struct{}{} + } + + // One '-debuginfo' per originally-reported package, sharing its publish channel and + // group/component attribution. + debugInfoEntries := make([]PackageListResult, 0, len(results)) + + for _, result := range results { + name := result.PackageName + "-debuginfo" + if _, exists := existing[name]; exists { + continue + } + + existing[name] = struct{}{} + debugInfoEntries = append(debugInfoEntries, PackageListResult{ + PackageName: name, + Group: result.Group, + Component: result.Component, + Channel: result.Channel, + }) + } + + results = append(results, debugInfoEntries...) + + // One '-debugsource' per component. No publish info is synthesized — the channel is + // left blank so downstream consumers will fall back to the configured default + // publishing channel. + for compName := range proj.Components { + name := compName + "-debugsource" + if _, exists := existing[name]; exists { + continue + } + + existing[name] = struct{}{} + + results = append(results, PackageListResult{ + PackageName: name, + Component: compName, + }) + } + + return results +} diff --git a/internal/app/azldev/cmds/pkg/list_test.go b/internal/app/azldev/cmds/pkg/list_test.go index 62ba6c1d..30060185 100644 --- a/internal/app/azldev/cmds/pkg/list_test.go +++ b/internal/app/azldev/cmds/pkg/list_test.go @@ -213,3 +213,101 @@ func TestListPackages_DuplicatePackageAcrossComponents_ReturnsError(t *testing.T assert.Contains(t, err.Error(), "curl") assert.Contains(t, err.Error(), "other") } + +func TestListPackages_SynthesizeDebugPackages(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.DefaultPackageConfig = projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "default-channel"}, + } + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "devel-packages": { + Packages: []string{"curl-devel"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "devel"}, + }, + }, + } + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{Name: "curl"} + testEnv.Config.Components["wget"] = projectconfig.ComponentConfig{ + Name: "wget", + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "wget-default"}, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{ + All: true, + SynthesizeDebugPackages: true, + }) + + require.NoError(t, err) + + byName := make(map[string]pkgcmds.PackageListResult, len(results)) + for _, result := range results { + byName[result.PackageName] = result + } + + // Original package present. + require.Contains(t, byName, "curl-devel") + assert.Equal(t, "devel", byName["curl-devel"].Channel) + + // '-debuginfo' synthesized for each reported package on the same channel. + require.Contains(t, byName, "curl-devel-debuginfo") + assert.Equal(t, "devel", byName["curl-devel-debuginfo"].Channel) + assert.Equal(t, "devel-packages", byName["curl-devel-debuginfo"].Group) + + // '-debugsource' synthesized for each component with NO publish channel — + // downstream consumers fall back to the configured default publishing channel. + require.Contains(t, byName, "curl-debugsource") + assert.Empty(t, byName["curl-debugsource"].Channel) + assert.Equal(t, "curl", byName["curl-debugsource"].Component) + assert.Empty(t, byName["curl-debugsource"].Group) + + require.Contains(t, byName, "wget-debugsource") + assert.Empty(t, byName["wget-debugsource"].Channel) + assert.Equal(t, "wget", byName["wget-debugsource"].Component) +} + +func TestListPackages_SynthesizeDebugPackages_SkipsExisting(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "g": { + Packages: []string{"curl", "curl-debuginfo"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "real"}, + }, + }, + } + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{ + Name: "curl", + Packages: map[string]projectconfig.PackageConfig{ + "curl-debugsource": {Publish: projectconfig.PackagePublishConfig{Channel: "explicit"}}, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{ + All: true, + SynthesizeDebugPackages: true, + }) + + require.NoError(t, err) + + // No duplicate entries — real config wins for both -debuginfo and -debugsource. + seen := make(map[string]int) + for _, result := range results { + seen[result.PackageName]++ + } + + assert.Equal(t, 1, seen["curl-debuginfo"]) + assert.Equal(t, 1, seen["curl-debugsource"]) + // The pre-existing curl-debuginfo keeps its real channel, not a synthesized override. + for _, result := range results { + if result.PackageName == "curl-debuginfo" { + assert.Equal(t, "real", result.Channel) + } + + if result.PackageName == "curl-debugsource" { + assert.Equal(t, "explicit", result.Channel) + } + } +} diff --git a/scenario/__snapshots__/TestMCPServerMode_1.snap.json b/scenario/__snapshots__/TestMCPServerMode_1.snap.json index 47931a41..bd0bd5ea 100755 --- a/scenario/__snapshots__/TestMCPServerMode_1.snap.json +++ b/scenario/__snapshots__/TestMCPServerMode_1.snap.json @@ -549,6 +549,11 @@ "description": "only enable minimal output", "type": "boolean" }, + "synthesize-debug-packages": { + "default": false, + "description": "Also synthesize '-debuginfo' packages (per reported package) and '-debugsource' packages (per component)", + "type": "boolean" + }, "verbose": { "default": false, "description": "enable verbose output", From 1e81928ccd205db613d5803c914429cddab002ea Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Tue, 21 Apr 2026 00:59:36 -0700 Subject: [PATCH 2/5] fix(pkg): address PR feedback on debug-package synthesis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip synthesis for source names already ending in '-debuginfo' or '-debugsource' to avoid recursive doubled suffixes (e.g. 'foo-debuginfo- debuginfo') when those packages are already in the listed set. - Restore '-debugsource' channel resolution via ResolvePackageConfig so the result carries an honest channel value (component default → project default) rather than an implicit empty 'consumer applies default' contract. - Emit an slog.Warn that the flag is transitional and may change. - Document that '-debugsource' is emitted for every component regardless of the requested package set, since most listed packages come from groups with no component association. - Tests: add assertions that doubled-suffix names are NOT synthesized; add a '-p' + flag test covering the headline CLI path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/app/azldev/cmds/pkg/list.go | 56 +++++++++++++++++++---- internal/app/azldev/cmds/pkg/list_test.go | 53 +++++++++++++++++++-- 2 files changed, 95 insertions(+), 14 deletions(-) diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go index cba2b26b..ed4821ba 100644 --- a/internal/app/azldev/cmds/pkg/list.go +++ b/internal/app/azldev/cmds/pkg/list.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "sort" + "strings" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" @@ -196,7 +197,13 @@ func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListRe } if options.SynthesizeDebugPackages { - results = synthesizeDebugPackages(results, proj) + slog.Warn("'--synthesize-debug-packages' is a transitional flag and may change or be removed " + + "once first-class debug-package configuration is supported.") + + results, err = synthesizeDebugPackages(results, proj) + if err != nil { + return nil, err + } } // Sort by package name for deterministic, readable output. @@ -209,14 +216,21 @@ func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListRe // synthesizeDebugPackages augments results with synthetic '-debuginfo' packages (one per // already-resolved package, sharing the original publish channel) and '-debugsource' packages -// (one per component, with no publish channel — so they fall back to the configured default -// publishing channel). +// (one per component in the project, with the publish channel resolved through the normal +// component → project default chain). +// +// Note: '-debugsource' entries are emitted for every component in the project regardless of +// which packages were requested via '-p'. Components own packages via [ComponentConfig.Packages], +// but most listed packages come from package groups with no component association — so scoping +// debugsource emission to the requested package set cannot be done reliably with the current +// configuration model. // // Synthetic entries that collide with an already-present (real) package name are skipped so -// real configuration always wins. +// real configuration always wins. Source packages whose names already end in '-debuginfo' or +// '-debugsource' do not get a doubled suffix synthesized. func synthesizeDebugPackages( results []PackageListResult, proj *projectconfig.ProjectConfig, -) []PackageListResult { +) ([]PackageListResult, error) { existing := make(map[string]struct{}, len(results)) for _, result := range results { existing[result.PackageName] = struct{}{} @@ -227,6 +241,10 @@ func synthesizeDebugPackages( debugInfoEntries := make([]PackageListResult, 0, len(results)) for _, result := range results { + if isDebugPackageName(result.PackageName) { + continue + } + name := result.PackageName + "-debuginfo" if _, exists := existing[name]; exists { continue @@ -243,10 +261,14 @@ func synthesizeDebugPackages( results = append(results, debugInfoEntries...) - // One '-debugsource' per component. No publish info is synthesized — the channel is - // left blank so downstream consumers will fall back to the configured default - // publishing channel. - for compName := range proj.Components { + // One '-debugsource' per component. Resolve through the component → project default + // publish-channel chain so the entry carries an honest channel value rather than an + // implicit "consumer applies default" empty string. + for compName, comp := range proj.Components { + if isDebugPackageName(compName) { + continue + } + name := compName + "-debugsource" if _, exists := existing[name]; exists { continue @@ -254,11 +276,25 @@ func synthesizeDebugPackages( existing[name] = struct{}{} + compCopy := comp + + pkgConfig, err := projectconfig.ResolvePackageConfig(name, &compCopy, proj) + if err != nil { + return nil, fmt.Errorf("failed to resolve config for synthesized package %#q:\n%w", name, err) + } + results = append(results, PackageListResult{ PackageName: name, Component: compName, + Channel: pkgConfig.Publish.Channel, }) } - return results + return results, nil +} + +// isDebugPackageName reports whether name already has a '-debuginfo' or '-debugsource' suffix, +// so the caller can avoid synthesizing doubled-suffix names like 'foo-debuginfo-debuginfo'. +func isDebugPackageName(name string) bool { + return strings.HasSuffix(name, "-debuginfo") || strings.HasSuffix(name, "-debugsource") } diff --git a/internal/app/azldev/cmds/pkg/list_test.go b/internal/app/azldev/cmds/pkg/list_test.go index 30060185..8802e8f2 100644 --- a/internal/app/azldev/cmds/pkg/list_test.go +++ b/internal/app/azldev/cmds/pkg/list_test.go @@ -256,15 +256,15 @@ func TestListPackages_SynthesizeDebugPackages(t *testing.T) { assert.Equal(t, "devel", byName["curl-devel-debuginfo"].Channel) assert.Equal(t, "devel-packages", byName["curl-devel-debuginfo"].Group) - // '-debugsource' synthesized for each component with NO publish channel — - // downstream consumers fall back to the configured default publishing channel. + // '-debugsource' synthesized for each component, with its channel resolved through the + // component → project default chain. require.Contains(t, byName, "curl-debugsource") - assert.Empty(t, byName["curl-debugsource"].Channel) + assert.Equal(t, "default-channel", byName["curl-debugsource"].Channel) assert.Equal(t, "curl", byName["curl-debugsource"].Component) assert.Empty(t, byName["curl-debugsource"].Group) require.Contains(t, byName, "wget-debugsource") - assert.Empty(t, byName["wget-debugsource"].Channel) + assert.Equal(t, "wget-default", byName["wget-debugsource"].Channel) assert.Equal(t, "wget", byName["wget-debugsource"].Component) } @@ -300,6 +300,10 @@ func TestListPackages_SynthesizeDebugPackages_SkipsExisting(t *testing.T) { assert.Equal(t, 1, seen["curl-debuginfo"]) assert.Equal(t, 1, seen["curl-debugsource"]) + // The doubled-suffix names must NOT be synthesized — guard against recursive synthesis + // when a real '-debuginfo' / '-debugsource' is already in the listed set. + assert.NotContains(t, seen, "curl-debuginfo-debuginfo") + assert.NotContains(t, seen, "curl-debugsource-debuginfo") // The pre-existing curl-debuginfo keeps its real channel, not a synthesized override. for _, result := range results { if result.PackageName == "curl-debuginfo" { @@ -311,3 +315,44 @@ func TestListPackages_SynthesizeDebugPackages_SkipsExisting(t *testing.T) { } } } + +func TestListPackages_SynthesizeDebugPackages_ByName(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.DefaultPackageConfig = projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "default-channel"}, + } + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "devel-packages": { + Packages: []string{"curl-devel"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "devel"}, + }, + }, + } + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{Name: "curl"} + + // The headline CLI path: -p PKG with the synthesize flag. + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{ + PackageNames: []string{"curl-devel"}, + SynthesizeDebugPackages: true, + }) + + require.NoError(t, err) + + byName := make(map[string]pkgcmds.PackageListResult, len(results)) + for _, result := range results { + byName[result.PackageName] = result + } + + require.Contains(t, byName, "curl-devel") + assert.Equal(t, "devel", byName["curl-devel"].Channel) + + // '-debuginfo' synthesized on the same channel as the requested package. + require.Contains(t, byName, "curl-devel-debuginfo") + assert.Equal(t, "devel", byName["curl-devel-debuginfo"].Channel) + + // '-debugsource' synthesized for every component in the project, regardless of which + // packages were requested. Channel resolves to the project default. + require.Contains(t, byName, "curl-debugsource") + assert.Equal(t, "default-channel", byName["curl-debugsource"].Channel) +} From 089ee4a08e7784ca78e457ba95e37f9ab9a2af70 Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Tue, 21 Apr 2026 01:09:15 -0700 Subject: [PATCH 3/5] feat(pkg): suffix synthesized debug-package channels with -debuginfo Synthesized debug packages now publish to a parallel channel: any resolved, non-empty, non-'none' channel is suffixed with '-debuginfo' (channels that already end in '-debuginfo' are left as-is). Empty and 'none' channels pass through unchanged so 'do not publish' and 'use default' semantics are preserved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/app/azldev/cmds/pkg/list.go | 16 +++++- internal/app/azldev/cmds/pkg/list_test.go | 59 +++++++++++++++++++---- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go index ed4821ba..59acbfee 100644 --- a/internal/app/azldev/cmds/pkg/list.go +++ b/internal/app/azldev/cmds/pkg/list.go @@ -255,7 +255,7 @@ func synthesizeDebugPackages( PackageName: name, Group: result.Group, Component: result.Component, - Channel: result.Channel, + Channel: debugChannelName(result.Channel), }) } @@ -286,13 +286,25 @@ func synthesizeDebugPackages( results = append(results, PackageListResult{ PackageName: name, Component: compName, - Channel: pkgConfig.Publish.Channel, + Channel: debugChannelName(pkgConfig.Publish.Channel), }) } return results, nil } +// debugChannelName returns the publish-channel name to use for a synthesized debug package. +// Real (non-empty, non-"none") channels are suffixed with '-debuginfo' so debug artifacts are +// published to a parallel channel; ” and 'none' are passed through unchanged because they +// represent "default" and "do not publish" respectively. +func debugChannelName(channel string) string { + if channel == "" || channel == "none" || strings.HasSuffix(channel, "-debuginfo") { + return channel + } + + return channel + "-debuginfo" +} + // isDebugPackageName reports whether name already has a '-debuginfo' or '-debugsource' suffix, // so the caller can avoid synthesizing doubled-suffix names like 'foo-debuginfo-debuginfo'. func isDebugPackageName(name string) bool { diff --git a/internal/app/azldev/cmds/pkg/list_test.go b/internal/app/azldev/cmds/pkg/list_test.go index 8802e8f2..21ec4fdb 100644 --- a/internal/app/azldev/cmds/pkg/list_test.go +++ b/internal/app/azldev/cmds/pkg/list_test.go @@ -251,20 +251,20 @@ func TestListPackages_SynthesizeDebugPackages(t *testing.T) { require.Contains(t, byName, "curl-devel") assert.Equal(t, "devel", byName["curl-devel"].Channel) - // '-debuginfo' synthesized for each reported package on the same channel. + // '-debuginfo' synthesized for each reported package on the parallel debug channel. require.Contains(t, byName, "curl-devel-debuginfo") - assert.Equal(t, "devel", byName["curl-devel-debuginfo"].Channel) + assert.Equal(t, "devel-debuginfo", byName["curl-devel-debuginfo"].Channel) assert.Equal(t, "devel-packages", byName["curl-devel-debuginfo"].Group) // '-debugsource' synthesized for each component, with its channel resolved through the - // component → project default chain. + // component → project default chain and suffixed onto the parallel debug channel. require.Contains(t, byName, "curl-debugsource") - assert.Equal(t, "default-channel", byName["curl-debugsource"].Channel) + assert.Equal(t, "default-channel-debuginfo", byName["curl-debugsource"].Channel) assert.Equal(t, "curl", byName["curl-debugsource"].Component) assert.Empty(t, byName["curl-debugsource"].Group) require.Contains(t, byName, "wget-debugsource") - assert.Equal(t, "wget-default", byName["wget-debugsource"].Channel) + assert.Equal(t, "wget-default-debuginfo", byName["wget-debugsource"].Channel) assert.Equal(t, "wget", byName["wget-debugsource"].Component) } @@ -347,12 +347,53 @@ func TestListPackages_SynthesizeDebugPackages_ByName(t *testing.T) { require.Contains(t, byName, "curl-devel") assert.Equal(t, "devel", byName["curl-devel"].Channel) - // '-debuginfo' synthesized on the same channel as the requested package. + // '-debuginfo' synthesized on the parallel debug channel for the requested package. require.Contains(t, byName, "curl-devel-debuginfo") - assert.Equal(t, "devel", byName["curl-devel-debuginfo"].Channel) + assert.Equal(t, "devel-debuginfo", byName["curl-devel-debuginfo"].Channel) // '-debugsource' synthesized for every component in the project, regardless of which - // packages were requested. Channel resolves to the project default. + // packages were requested. Channel resolves to the project default + '-debuginfo'. require.Contains(t, byName, "curl-debugsource") - assert.Equal(t, "default-channel", byName["curl-debugsource"].Channel) + assert.Equal(t, "default-channel-debuginfo", byName["curl-debugsource"].Channel) +} + +func TestListPackages_SynthesizeDebugPackages_ChannelSuffixRules(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "none-grp": { + Packages: []string{"pkg-none"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "none"}, + }, + }, + "empty-grp": { + // No DefaultPackageConfig → resolves to "" (no channel configured). + Packages: []string{"pkg-empty"}, + }, + "already-grp": { + Packages: []string{"pkg-already"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "ms-debuginfo"}, + }, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{ + All: true, + SynthesizeDebugPackages: true, + }) + + require.NoError(t, err) + + byName := make(map[string]pkgcmds.PackageListResult, len(results)) + for _, result := range results { + byName[result.PackageName] = result + } + + // "none" passes through unchanged — debug artifacts inherit the do-not-publish intent. + assert.Equal(t, "none", byName["pkg-none-debuginfo"].Channel) + // Empty passes through unchanged — downstream applies the configured default. + assert.Empty(t, byName["pkg-empty-debuginfo"].Channel) + // Already-suffixed channels are not doubled. + assert.Equal(t, "ms-debuginfo", byName["pkg-already-debuginfo"].Channel) } From 156fa9d78dfbfac85ade5819c6bd56e4ecdf7920 Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Tue, 21 Apr 2026 01:47:22 -0700 Subject: [PATCH 4/5] chore: address PR feedback Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/app/azldev/cmds/pkg/list.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go index 59acbfee..eda503d9 100644 --- a/internal/app/azldev/cmds/pkg/list.go +++ b/internal/app/azldev/cmds/pkg/list.go @@ -24,10 +24,12 @@ type ListPackageOptions struct { PackageNames []string // SynthesizeDebugPackages, when true, augments the result list with synthetic - // '-debuginfo' packages (one per reported package, sharing the original package's - // publish channel) and synthetic '-debugsource' packages (one per component in the - // project configuration, with no explicit publish channel — so they resolve to the - // configured default publishing channel). + // '-debuginfo' packages (one per reported package, using a parallel publish + // channel derived from the original package's publish channel by appending + // '-debuginfo', except when the original channel is "" or "none") and synthetic + // '-debugsource' packages (one per component in the project configuration, with + // no explicit publish channel — so they resolve to the configured default + // publishing channel). SynthesizeDebugPackages bool } @@ -295,7 +297,7 @@ func synthesizeDebugPackages( // debugChannelName returns the publish-channel name to use for a synthesized debug package. // Real (non-empty, non-"none") channels are suffixed with '-debuginfo' so debug artifacts are -// published to a parallel channel; ” and 'none' are passed through unchanged because they +// published to a parallel channel; "" and 'none' are passed through unchanged because they // represent "default" and "do not publish" respectively. func debugChannelName(channel string) string { if channel == "" || channel == "none" || strings.HasSuffix(channel, "-debuginfo") { From cc0fe3698ace2a22f7cf85bac3bbeeca959a409a Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Tue, 21 Apr 2026 07:16:14 -0700 Subject: [PATCH 5/5] chore: apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/app/azldev/cmds/pkg/list.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go index eda503d9..43e0db61 100644 --- a/internal/app/azldev/cmds/pkg/list.go +++ b/internal/app/azldev/cmds/pkg/list.go @@ -27,9 +27,9 @@ type ListPackageOptions struct { // '-debuginfo' packages (one per reported package, using a parallel publish // channel derived from the original package's publish channel by appending // '-debuginfo', except when the original channel is "" or "none") and synthetic - // '-debugsource' packages (one per component in the project configuration, with - // no explicit publish channel — so they resolve to the configured default - // publishing channel). + // '-debugsource' packages (one per component in the project configuration, + // using the component's resolved publish channel after applying the same + // debug-channel derivation logic). SynthesizeDebugPackages bool } @@ -217,9 +217,10 @@ func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListRe } // synthesizeDebugPackages augments results with synthetic '-debuginfo' packages (one per -// already-resolved package, sharing the original publish channel) and '-debugsource' packages -// (one per component in the project, with the publish channel resolved through the normal -// component → project default chain). +// already-resolved package, using a parallel publish channel derived from the original +// package's publish channel by appending '-debuginfo', except when the original channel is +// "" or "none") and '-debugsource' packages (one per component in the project, with the +// publish channel resolved through the normal component → project default chain). // // Note: '-debugsource' entries are emitted for every component in the project regardless of // which packages were requested via '-p'. Components own packages via [ComponentConfig.Packages], @@ -263,9 +264,10 @@ func synthesizeDebugPackages( results = append(results, debugInfoEntries...) - // One '-debugsource' per component. Resolve through the component → project default - // publish-channel chain so the entry carries an honest channel value rather than an - // implicit "consumer applies default" empty string. + // One '-debugsource' per component. Resolve the synthesized package using the + // standard package-resolution chain for '-debugsource' so the entry + // reflects any explicit package override, package-group defaults, component + // settings, or project defaults instead of an implicit empty channel. for compName, comp := range proj.Components { if isDebugPackageName(compName) { continue