Skip to content

Commit b2ff9d2

Browse files
committed
snap,wrappers: fix common id selection with multiple desktop plugs
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>
1 parent a99cf1a commit b2ff9d2

3 files changed

Lines changed: 122 additions & 23 deletions

File tree

snap/info.go

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -968,44 +968,57 @@ func (s *Info) DesktopPrefix() string {
968968
// Note: DesktopPlugFileIDs doesn't check if the desktop plug is connected because
969969
// the desktop-file-ids attribute is controlled by an allow-installation rule.
970970
func (s *Info) DesktopPlugFileIDs() ([]string, error) {
971-
var desktopPlug *PlugInfo
972-
for _, plug := range s.Plugs {
971+
desktopPlugNames := make([]string, 0, len(s.Plugs))
972+
for name, plug := range s.Plugs {
973973
if plug.Interface == "desktop" {
974-
desktopPlug = plug
975-
break
974+
desktopPlugNames = append(desktopPlugNames, name)
976975
}
977976
}
978-
if desktopPlug == nil {
979-
return nil, nil
980-
}
981-
982-
attrVal, exists := desktopPlug.Lookup("desktop-file-ids")
983-
if !exists {
984-
// desktop-file-ids attribute is optional
977+
if len(desktopPlugNames) == 0 {
985978
return nil, nil
986979
}
980+
sort.Strings(desktopPlugNames)
987981

988982
// TODO: The internal errors below should never happen due to validation
989983
// in the desktop interface. It would be a good candidate for telemetry
990984
// error reporting.
991985

992-
// desktop-file-ids must be a list of strings
993-
attrList, ok := attrVal.([]any)
994-
if !ok {
995-
return nil, errors.New(`internal error: "desktop-file-ids" must be a list of strings`)
996-
}
986+
desktopFileIDs := make([]string, 0)
987+
seenDesktopFileIDs := make(map[string]bool)
988+
for _, plugName := range desktopPlugNames {
989+
desktopPlug := s.Plugs[plugName]
990+
991+
attrVal, exists := desktopPlug.Lookup("desktop-file-ids")
992+
if !exists {
993+
// desktop-file-ids attribute is optional
994+
continue
995+
}
997996

998-
desktopFileIDs := make([]string, 0, len(attrList))
999-
for _, val := range attrList {
1000-
desktopFileID, ok := val.(string)
997+
// desktop-file-ids must be a list of strings
998+
attrList, ok := attrVal.([]any)
1001999
if !ok {
10021000
return nil, errors.New(`internal error: "desktop-file-ids" must be a list of strings`)
10031001
}
1004-
if !strings.HasSuffix(desktopFileID, ".desktop") {
1005-
logger.Noticef("adding missing .desktop suffix to desktop file ID %s (snap %s)", desktopFileID, s.InstanceName())
1006-
desktopFileID = desktopFileID + ".desktop"
1002+
1003+
for _, val := range attrList {
1004+
desktopFileID, ok := val.(string)
1005+
if !ok {
1006+
return nil, errors.New(`internal error: "desktop-file-ids" must be a list of strings`)
1007+
}
1008+
if !strings.HasSuffix(desktopFileID, ".desktop") {
1009+
logger.Noticef("adding missing .desktop suffix to desktop file ID %s (snap %s)", desktopFileID, s.InstanceName())
1010+
desktopFileID = desktopFileID + ".desktop"
1011+
}
1012+
if seenDesktopFileIDs[desktopFileID] {
1013+
continue
1014+
}
1015+
seenDesktopFileIDs[desktopFileID] = true
1016+
desktopFileIDs = append(desktopFileIDs, desktopFileID)
10071017
}
1008-
desktopFileIDs = append(desktopFileIDs, desktopFileID)
1018+
}
1019+
1020+
if len(desktopFileIDs) == 0 {
1021+
return nil, nil
10091022
}
10101023
return desktopFileIDs, nil
10111024
}

snap/info_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,63 @@ plugs:
27192719
}
27202720
}
27212721

2722+
func (s *infoSuite) TestDesktopFileIDsMergedFromAllDesktopPlugs(c *C) {
2723+
const desktopAppYaml = `
2724+
name: foo
2725+
version: 1.0
2726+
plugs:
2727+
desktop:
2728+
desktop-extra:
2729+
interface: desktop
2730+
desktop-file-ids: [org.example.Foo, org.example.Bar]
2731+
desktop-file:
2732+
interface: desktop
2733+
desktop-file-ids: [org.example.desktop, org.example.Foo.desktop]
2734+
`
2735+
2736+
info, err := snap.InfoFromSnapYaml([]byte(desktopAppYaml))
2737+
c.Assert(err, IsNil)
2738+
2739+
desktopFileIDs, err := info.DesktopPlugFileIDs()
2740+
c.Assert(err, IsNil)
2741+
c.Assert(desktopFileIDs, DeepEquals, []string{"org.example.Foo.desktop", "org.example.Bar.desktop", "org.example.desktop"})
2742+
}
2743+
2744+
func (s *infoSuite) TestAppDesktopFileUsesDesktopFileIDsAcrossDesktopPlugs(c *C) {
2745+
const desktopAppYaml = `
2746+
name: sample
2747+
version: 1
2748+
apps:
2749+
app:
2750+
command: foo
2751+
common-id: org.example.CommonID.Foo
2752+
plugs:
2753+
desktop:
2754+
desktop-file:
2755+
interface: desktop
2756+
desktop-file-ids:
2757+
- org.example.CommonID.Foo
2758+
`
2759+
2760+
snaptest.MockSnap(c, desktopAppYaml, &snap.SideInfo{})
2761+
snapInfo, err := snap.ReadInfo("sample", &snap.SideInfo{})
2762+
c.Assert(err, IsNil)
2763+
c.Assert(snapInfo.Plugs["desktop"], NotNil)
2764+
c.Assert(snapInfo.Plugs["desktop-file"], NotNil)
2765+
2766+
c.Assert(os.MkdirAll(dirs.SnapDesktopFilesDir, 0755), IsNil)
2767+
const desktopFileName = "org.example.CommonID.Foo.desktop"
2768+
const mockDesktopFile = `[Desktop Entry]
2769+
X-SnapInstanceName=sample
2770+
Name=foo
2771+
X-SnapAppName=app
2772+
Exec=sample.app
2773+
`
2774+
c.Assert(os.WriteFile(filepath.Join(dirs.SnapDesktopFilesDir, desktopFileName), []byte(mockDesktopFile), 0644), IsNil)
2775+
2776+
c.Check(snapInfo.Apps["app"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/org.example.CommonID.Foo.desktop`)
2777+
}
2778+
27222779
func (s *infoSuite) TestMangleDesktopFileName(c *C) {
27232780
const desktopAppYaml = `
27242781
name: foo

wrappers/desktop_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,35 @@ func (s *desktopSuite) TestEnsurePackageDesktopFilesWithDesktopFileName(c *C) {
188188
s.testEnsurePackageDesktopFilesWithDesktopInterface(c, desktopFileID)
189189
}
190190

191+
func (s *desktopSuite) TestEnsurePackageDesktopFilesWithMultipleDesktopPlugs(c *C) {
192+
const desktopAppYaml = `
193+
name: foo
194+
version: 1.0
195+
apps:
196+
foobar:
197+
plugs:
198+
desktop:
199+
desktop-file:
200+
interface: desktop
201+
desktop-file-ids: [org.example.Foo]
202+
`
203+
204+
info := snaptest.MockSnap(c, desktopAppYaml, &snap.SideInfo{Revision: snap.R(11)})
205+
c.Assert(info.Plugs["desktop"], NotNil)
206+
c.Assert(info.Plugs["desktop-file"], NotNil)
207+
208+
baseDir := info.MountDir()
209+
c.Assert(os.MkdirAll(filepath.Join(baseDir, "meta", "gui"), 0755), IsNil)
210+
c.Assert(os.WriteFile(filepath.Join(baseDir, "meta", "gui", "org.example.Foo.desktop"), mockDesktopFile, 0644), IsNil)
211+
c.Assert(os.WriteFile(filepath.Join(baseDir, "meta", "gui", "foobar.desktop"), mockDesktopFile, 0644), IsNil)
212+
213+
err := wrappers.EnsureSnapDesktopFiles([]*snap.Info{info})
214+
c.Assert(err, IsNil)
215+
216+
c.Check(filepath.Join(dirs.SnapDesktopFilesDir, "org.example.Foo.desktop"), testutil.FilePresent)
217+
c.Check(filepath.Join(dirs.SnapDesktopFilesDir, "foo_foobar.desktop"), testutil.FilePresent)
218+
}
219+
191220
func (s *desktopSuite) TestEnsurePackageDesktopFilesWithBadDesktopFileIDs(c *C) {
192221
const desktopAppYamlTemplate = `
193222
name: foo

0 commit comments

Comments
 (0)