Skip to content

snap,wrappers: fix common id selection with multiple desktop plugs - #17016

Merged
ernestl merged 3 commits into
canonical:masterfrom
olivercalder:fix-snap-common-id-selection
May 21, 2026
Merged

snap,wrappers: fix common id selection with multiple desktop plugs#17016
ernestl merged 3 commits into
canonical:masterfrom
olivercalder:fix-snap-common-id-selection

Conversation

@olivercalder

Copy link
Copy Markdown
Member

When multiple desktop plugs are present for a given app, we need to ensure that any desktop-file-ids attributes across all such plugs are considered when selecting the desktop file ID for the app.

This commit ensures that all desktop plugs are collected and sorted by name first, and then for each, if the desktop-file-ids plug is present, add its IDs to the list of common IDs for the app.

This ensures that if any desktop-file-ids attribute is present, the IDs listed always have precedence over the fallback common ID. It also ensures that if multiple plugs are present, the output is always deterministic.

This fixes a bug first reported by @3v1n0

This work is tracked internally by https://warthogs.atlassian.net/browse/SNAPDENG-36476

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes desktop file ID selection for snaps that define multiple desktop plugs by ensuring desktop-file-ids are gathered from all desktop plugs in a deterministic way (sorted by plug name). This improves correctness for wrapper desktop file installation and for AppInfo.DesktopFile() selection, avoiding non-deterministic behavior caused by ranging over the plugs map.

Changes:

  • Update snap.Info.DesktopPlugFileIDs() to collect desktop-file-ids across all desktop plugs, sort plug names for determinism, and deduplicate IDs.
  • Add wrapper-level regression coverage to ensure unmangled desktop IDs are honored when multiple desktop plugs exist.
  • Add snap-level unit tests to confirm merged ID behavior and DesktopFile() selection when IDs are provided via a non-default desktop plug.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
snap/info.go Collect and deterministically merge desktop-file-ids from all desktop plugs (sorted, deduped).
snap/info_test.go Add tests validating merged ID ordering/deduping and AppInfo.DesktopFile() behavior across multiple desktop plugs.
wrappers/desktop_test.go Add regression test ensuring EnsureSnapDesktopFiles honors desktop-file-ids when multiple desktop plugs are present.

@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.13%. Comparing base (0e773f3) to head (20181d0).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #17016      +/-   ##
==========================================
+ Coverage   79.07%   79.13%   +0.05%     
==========================================
  Files        1376     1369       -7     
  Lines      191657   191547     -110     
  Branches     2465     2465              
==========================================
+ Hits       151555   151572      +17     
+ Misses      30972    30852     -120     
+ Partials     9130     9123       -7     
Flag Coverage Δ
unittests 79.13% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Wed May 20 18:43:09 UTC 2026
The following results are from: https://github.com/canonical/snapd/actions/runs/26160419726

Failures:

Preparing:

  • openstack:amazon-linux-2-64:tests/main/security-logging
  • openstack:opensuse-16.0-64:

Executing:

  • openstack:centos-9-64:tests/main/selinux-clean
  • openstack:debian-sid-64:tests/unit/go:static
  • openstack:fedora-42-64:tests/main/selinux-clean
  • openstack:opensuse-tumbleweed-selinux-64:tests/main/security-logging
  • openstack:opensuse-tumbleweed-selinux-64:tests/main/cgroup-devices-v2
  • openstack:opensuse-tumbleweed-64:tests/main/security-logging

Restoring:

  • openstack:amazon-linux-2-64:tests/main/security-logging
  • openstack:debian-sid-64:tests/unit/go:static
  • openstack:opensuse-16.0-64:
  • openstack:ubuntu-24.04-64:tests/cross/go-build:arm64

Skipped tests from snapd-testing-skip

If you wish to have any of the below tests run in your PR, in your PR description, add 'unskip:' followed by a copy-and-pasted list (without variants) of the below tests you wish to run (unskip plus test list must be valid yaml)

  • garden:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/i18n
  • openstack-arm:ubuntu-core-24-arm-64:tests/main/i18n
  • openstack:debian-sid-64:tests/main/interfaces-network-status-classic
  • openstack:ubuntu-24.04-64:tests/main/i18n
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:audio_record_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:audio_record_timespan_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:audio_record_timespan_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_fd_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_path_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_write_same_path_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:special_characters
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-25.10-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:audio_record_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:audio_record_timespan_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:audio_record_timespan_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_fd_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_path_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_write_same_path_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:special_characters
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-26.04-64:tests/main/i18n
  • openstack:ubuntu-26.04-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-core-26-64:tests/main/snapctl-is-ready

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Change looks really good, Thanks for fixing!

Would you mind updating snap under tests/main/desktop-file-ids/task.yaml to test for connection combinations of multiple desktop interfaces? I am interested in stress testing what happens on sequential connection/disconnection

Comment thread snap/info.go
if seenDesktopFileIDs[desktopFileID] {
continue
}
seenDesktopFileIDs[desktopFileID] = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
seenDesktopFileIDs[desktopFileID] = true
seenDesktopFileIDs[desktopFileID] = struct{}{}

Instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I prefer using map[string]bool so it's semantically just a set and we can do if seenDesktopFileIDs[desktopFileID] without needing to check the val, ok of the result.

@natibek natibek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! Maybe worth adding info to https://snapcraft.io/docs/reference/interfaces/desktop-interface/ about the precedence of desktop-file-ids and that all desktop plugs are collected and sorted by name.

@andrewphelpsj andrewphelpsj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, impl looks good. Yeah, I'd update tests/main/desktop-file-ids/task.yaml as Zeyad suggested.

@olivercalder olivercalder added cross-distro Runs all spread systems in parallel Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system labels May 10, 2026
@olivercalder
olivercalder force-pushed the fix-snap-common-id-selection branch from 7caecc2 to ca3fb72 Compare May 10, 2026 14:15
@olivercalder

Copy link
Copy Markdown
Member Author

I considered going crazy with combinations of Exec= lines matching/not matching apps, apps declaring/not declaring matching common-ids, number and uniqueness of desktop-file-ids across different plugs and apps. But I think this test is not the place for such things, as it's more tightly coupled with common-ids and also the contents of the .desktop files. Perhaps a dedicated test with many variants would be good in the future.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated 4 comments.

Comment thread tests/main/desktop-file-ids/task.yaml Outdated
Comment thread tests/main/desktop-file-ids/task.yaml Outdated
Comment thread tests/main/desktop-file-ids/task.yaml
Comment thread tests/main/desktop-file-ids/task.yaml Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated 1 comment.

Comment thread snap/info.go
Comment on lines 964 to 978
// DesktopPlugFileIDs returns desktop-file-ids desktop plug attribute entries.
// The desktop-file-ids attribute is optional so an empty list is returned if
// the it is not found.
//
// Note: DesktopPlugFileIDs doesn't check if the desktop plug is connected because
// the desktop-file-ids attribute is controlled by an allow-installation rule.
func (s *Info) DesktopPlugFileIDs() ([]string, error) {
var desktopPlug *PlugInfo
for _, plug := range s.Plugs {
desktopPlugNames := make([]string, 0, len(s.Plugs))
for name, plug := range s.Plugs {
if plug.Interface == "desktop" {
desktopPlug = plug
break
desktopPlugNames = append(desktopPlugNames, name)
}
}
if desktopPlug == nil {
return nil, nil
}

attrVal, exists := desktopPlug.Lookup("desktop-file-ids")
if !exists {
// desktop-file-ids attribute is optional
if len(desktopPlugNames) == 0 {
return nil, nil

@andrewphelpsj andrewphelpsj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, Thanks!

Comment on lines 28 to +53
plugs:
desktop:
desktop-ids:
interface: desktop
desktop-file-ids:
- org.example.desktop
- org.example.Foo.desktop
- org.example.Fallback
- org.example.MyCommonID.desktop
- org.example.NonexistentFile.desktop
# Declare lots of desktop plugs without desktop-file-ids to make sure the
# correct desktop file ID is chosen
desktop-decoy1:
interface: desktop
desktop-decoy2:
interface: desktop
desktop-decoy3:
interface: desktop
desktop-decoy4:
interface: desktop
desktop-decoy5:
interface: desktop
desktop-decoy6:
interface: desktop
desktop-decoy7:
interface: desktop
desktop-decoy8:
interface: desktop

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since desktop-file-ids are getting merged anyway, might be worth adding some ids on other plugs and check they get merged.

nitpick, not a blocker for this PR

When multiple `desktop` plugs are present for a given app, we need to
ensure that any `desktop-file-ids` attributes across all such plugs are
considered when selecting the desktop file ID for the app.

This commit ensures that all `desktop` plugs are collected and sorted by
name first, and then for each, if the `desktop-file-ids` plug is
present, add its IDs to the list of common IDs for the app.

This ensures that if any `desktop-file-ids` attribute is present, the
IDs listed always have precedence over the fallback common ID. It also
ensures that if multiple plugs are present, the output is always
deterministic.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
…ds test

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
@olivercalder
olivercalder force-pushed the fix-snap-common-id-selection branch from 6cb8b4a to 20181d0 Compare May 20, 2026 11:45
@ernestl
ernestl merged commit f18fd11 into canonical:master May 21, 2026
496 of 543 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system cross-distro Runs all spread systems in parallel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants