1// Package spec holds the vendored Wayland protocol XML, the manifest that
2// describes it, the shared XML model, and the package naming rule.
3package spec
4
5import (
6 "embed"
7 "encoding/json"
8 "fmt"
9 "io/fs"
10 "sort"
11)
12
13// ModulePath is the import path prefix of the generated bindings.
14const ModulePath = "github.com/bnema/go-wayland-bindings"
15
16//go:embed manifest.json xml
17var files embed.FS
18
19// Manifest lists upstream sources and the protocols vendored from them.
20// It is written by cmd/wlbsync and never edited by hand.
21type Manifest struct {
22 Sources []Source `json:"sources"`
23 Protocols []ProtocolEntry `json:"protocols"`
24}
25
26// Source is a pinned upstream repository.
27type Source struct {
28 ID string `json:"id"`
29 URL string `json:"url"`
30 Ref string `json:"ref"` // tag or branch name
31 Commit string `json:"commit"` // resolved commit SHA
32}
33
34// ProtocolEntry describes one vendored protocol.
35type ProtocolEntry struct {
36 Package string `json:"package"`
37 XML string `json:"xml"` // file name under spec/xml
38 Source string `json:"source"` // Source.ID
39 Path string `json:"path"` // path inside the upstream repository
40 SHA256 string `json:"sha256"`
41 License string `json:"license"` // SPDX identifier
42 Interfaces []InterfaceEntry `json:"interfaces"`
43 Deps []string `json:"deps"` // packages owning referenced interfaces
44}
45
46// InterfaceEntry records an interface and its highest version.
47type InterfaceEntry struct {
48 Name string `json:"name"`
49 Version int `json:"version"`
50}
51
52// Load returns the embedded manifest.
53func Load() (*Manifest, error) {
54 return LoadFS(files)
55}
56
57// LoadFS reads manifest.json from fsys.
58func LoadFS(fsys fs.FS) (*Manifest, error) {
59 b, err := fs.ReadFile(fsys, "manifest.json")
60 if err != nil {
61 return nil, fmt.Errorf("read manifest: %w", err)
62 }
63 var m Manifest
64 if err := json.Unmarshal(b, &m); err != nil {
65 return nil, fmt.Errorf("decode manifest: %w", err)
66 }
67 return &m, nil
68}
69
70// Open returns the embedded XML of package pkg.
71func Open(pkg string) ([]byte, error) {
72 m, err := Load()
73 if err != nil {
74 return nil, err
75 }
76 return m.Open(pkg)
77}
78
79// Open returns the embedded XML of package pkg, which must be listed in m.
80func (m *Manifest) Open(pkg string) ([]byte, error) {
81 e, ok := m.Package(pkg)
82 if !ok {
83 return nil, fmt.Errorf("unknown protocol package %q", pkg)
84 }
85 return fs.ReadFile(files, "xml/"+e.XML)
86}
87
88// Package returns the entry named pkg.
89func (m *Manifest) Package(pkg string) (ProtocolEntry, bool) {
90 for _, e := range m.Protocols {
91 if e.Package == pkg {
92 return e, true
93 }
94 }
95 return ProtocolEntry{}, false
96}
97
98// Owners maps every interface name to the package that defines it.
99func (m *Manifest) Owners() map[string]string {
100 out := make(map[string]string)
101 for _, e := range m.Protocols {
102 for _, it := range e.Interfaces {
103 out[it.Name] = e.Package
104 }
105 }
106 return out
107}
108
109// Lookup returns the package defining interface iface.
110func (m *Manifest) Lookup(iface string) (string, bool) {
111 p, ok := m.Owners()[iface]
112 return p, ok
113}
114
115// Sort orders sources by ID and protocols by package, for stable output.
116func (m *Manifest) Sort() {
117 sort.Slice(m.Sources, func(i, j int) bool { return m.Sources[i].ID < m.Sources[j].ID })
118 sort.Slice(m.Protocols, func(i, j int) bool { return m.Protocols[i].Package < m.Protocols[j].Package })
119}
120
121// Marshal encodes m as indented JSON with a trailing newline.
122func (m *Manifest) Marshal() ([]byte, error) {
123 b, err := json.MarshalIndent(m, "", " ")
124 if err != nil {
125 return nil, err
126 }
127 return append(b, '\n'), nil
128}