1package main
2
3import (
4 "path"
5 "regexp"
6 "strconv"
7 "strings"
8)
9
10// Stability ranks upstream protocol maturity; higher wins when several files
11// map to one package.
12type Stability int
13
14// Stability levels.
15const (
16 Unstable Stability = iota + 1
17 Staging
18 Stable
19)
20
21// PinKind says how the default ref of a source is chosen.
22type PinKind int
23
24const (
25 // PinTag follows the latest tag accepted by upstream.TagRe and upstream.TagOK.
26 PinTag PinKind = iota
27 // PinBranch follows the HEAD commit of upstream.Branch.
28 PinBranch
29)
30
31// upstream describes an upstream repository and which of its files are vendored.
32type upstream struct {
33 ID string
34 URL string
35
36 Kind PinKind
37 TagRe *regexp.Regexp // PinTag: tags must match
38 TagOK func(tag string) bool // PinTag: optional extra filter
39 Branch string // PinBranch: branch name
40
41 // Match reports whether the slash-separated repository path rel is a
42 // vendored protocol, and its stability.
43 Match func(rel string) (Stability, bool)
44}
45
46var (
47 tagThreeParts = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
48 tagTwoParts = regexp.MustCompile(`^\d+\.\d+$`)
49 tagVThreePart = regexp.MustCompile(`^v\d+\.\d+\.\d+$`)
50)
51
52// waylandTagOK accepts release tags X.Y.Z only; patch 90 and above are
53// release candidates regardless of the minor version.
54func waylandTagOK(tag string) bool {
55 v := versionParts(tag)
56 return len(v) == 3 && v[2] < 90
57}
58
59// exactly matches only the listed repository paths, all with stability s.
60func exactly(s Stability, paths ...string) func(string) (Stability, bool) {
61 set := make(map[string]bool, len(paths))
62 for _, p := range paths {
63 set[p] = true
64 }
65 return func(rel string) (Stability, bool) {
66 if !set[rel] {
67 return 0, false
68 }
69 return s, true
70 }
71}
72
73func matchWaylandProtocols(rel string) (Stability, bool) {
74 parts := strings.Split(rel, "/")
75 if len(parts) < 2 || !strings.HasSuffix(rel, ".xml") {
76 return 0, false
77 }
78 for _, p := range parts[:len(parts)-1] {
79 if p == "tests" || p == "experimental" {
80 return 0, false
81 }
82 }
83 switch parts[0] {
84 case "stable":
85 return Stable, true
86 case "staging":
87 return Staging, true
88 case "unstable":
89 return Unstable, true
90 }
91 return 0, false
92}
93
94func matchWlrProtocols(rel string) (Stability, bool) {
95 if path.Dir(rel) == "unstable" && strings.HasSuffix(rel, ".xml") {
96 return Unstable, true
97 }
98 return 0, false
99}
100
101// defaultSources lists the upstream repositories synced into spec/.
102func defaultSources() []upstream {
103 return []upstream{
104 {
105 ID: "wayland",
106 URL: "https://gitlab.freedesktop.org/wayland/wayland.git",
107 Kind: PinTag,
108 TagRe: tagThreeParts,
109 TagOK: waylandTagOK,
110 Match: exactly(Stable, "protocol/wayland.xml"),
111 },
112 {
113 ID: "wayland-protocols",
114 URL: "https://gitlab.freedesktop.org/wayland/wayland-protocols.git",
115 Kind: PinTag,
116 TagRe: tagTwoParts,
117 Match: matchWaylandProtocols,
118 },
119 {
120 ID: "wlr-protocols",
121 URL: "https://gitlab.freedesktop.org/wlroots/wlr-protocols.git",
122 Kind: PinBranch,
123 Branch: "master",
124 Match: matchWlrProtocols,
125 },
126 {
127 ID: "wlroots",
128 URL: "https://gitlab.freedesktop.org/wlroots/wlroots.git",
129 Kind: PinTag,
130 TagRe: tagThreeParts,
131 Match: exactly(Unstable,
132 "protocol/input-method-unstable-v2.xml",
133 "protocol/virtual-keyboard-unstable-v1.xml"),
134 },
135 {
136 ID: "plasma-wayland-protocols",
137 URL: "https://invent.kde.org/libraries/plasma-wayland-protocols.git",
138 Kind: PinTag,
139 TagRe: tagVThreePart,
140 Match: exactly(Unstable, "src/protocols/server-decoration.xml"),
141 },
142 }
143}
144
145// versionParts splits "v1.2.3" or "1.2" into its numeric components.
146func versionParts(tag string) []int {
147 var out []int
148 for _, f := range strings.Split(strings.TrimPrefix(tag, "v"), ".") {
149 n, err := strconv.Atoi(f)
150 if err != nil {
151 return nil
152 }
153 out = append(out, n)
154 }
155 return out
156}
157
158// compareVersions orders two tags numerically.
159func compareVersions(a, b string) int {
160 x, y := versionParts(a), versionParts(b)
161 for i := 0; i < len(x) && i < len(y); i++ {
162 if x[i] != y[i] {
163 if x[i] < y[i] {
164 return -1
165 }
166 return 1
167 }
168 }
169 return len(x) - len(y)
170}
171
172var majorSuffix = regexp.MustCompile(`-v(\d+)$`)
173
174// fileMajor returns the "vN" major of an XML file name; none means 1.
175func fileMajor(file string) int {
176 base := strings.TrimSuffix(path.Base(file), ".xml")
177 if m := majorSuffix.FindStringSubmatch(base); m != nil {
178 n, _ := strconv.Atoi(m[1])
179 return n
180 }
181 return 1
182}