1// Command xtsync vendors the pinned xcb-proto sources. An explicit -source
2// directory supports offline refreshes; its provenance is retained in manifest.
3package main
4
5import (
6 "archive/tar"
7 "bytes"
8 "compress/gzip"
9 "crypto/sha256"
10 "encoding/hex"
11 "encoding/json"
12 "flag"
13 "fmt"
14 "io"
15 "net/http"
16 "os"
17 "path/filepath"
18 "time"
19)
20
21const upstream = "https://gitlab.freedesktop.org/xorg/proto/xcbproto"
22const tag = "xcb-proto-1.17.0"
23
24var files = []string{"xproto.xml", "bigreq.xml", "ge.xml", "xc_misc.xml", "composite.xml", "xfixes.xml", "res.xml", "render.xml", "shape.xml", "xcb.xsd", "COPYING"}
25
26type entry struct {
27 Path string `json:"path"`
28 SHA256 string `json:"sha256"`
29}
30type manifest struct {
31 Upstream string `json:"upstream"`
32 Tag string `json:"tag"`
33 Source string `json:"source"`
34 Files []entry `json:"files"`
35}
36
37func main() {
38 source := flag.String("source", "", "local source directory (COPYING must be included)")
39 flag.Parse()
40 if err := syncFiles(*source); err != nil {
41 fmt.Fprintln(os.Stderr, err)
42 os.Exit(1)
43 }
44}
45func syncFiles(source string) error {
46 data := make(map[string][]byte)
47 provenance := source
48 if source != "" {
49 for _, name := range files {
50 b, err := os.ReadFile(filepath.Join(source, name))
51 if err != nil {
52 return err
53 }
54 data[name] = b
55 }
56 } else {
57 provenance = upstream + "/-/archive/" + tag + "/xcbproto-" + tag + ".tar.gz"
58 client := http.Client{Timeout: 30 * time.Second}
59 resp, err := client.Get(provenance)
60 if err != nil {
61 return err
62 }
63 defer resp.Body.Close()
64 if resp.StatusCode != http.StatusOK {
65 return fmt.Errorf("download: %s", resp.Status)
66 }
67 gz, err := gzip.NewReader(io.LimitReader(resp.Body, 16<<20))
68 if err != nil {
69 return err
70 }
71 defer gz.Close()
72 tr := tar.NewReader(gz)
73 for {
74 h, err := tr.Next()
75 if err == io.EOF {
76 break
77 }
78 if err != nil {
79 return err
80 }
81 for _, name := range files {
82 if h.Name == "xcbproto-"+tag+"/src/"+name || name == "COPYING" && h.Name == "xcbproto-"+tag+"/COPYING" {
83 b, err := io.ReadAll(io.LimitReader(tr, 4<<20))
84 if err != nil {
85 return err
86 }
87 data[name] = b
88 }
89 }
90 }
91 }
92 m := manifest{Upstream: upstream, Tag: tag, Source: provenance}
93 for _, name := range files {
94 if len(data[name]) == 0 {
95 return fmt.Errorf("missing source %s", name)
96 }
97 }
98 for _, name := range files {
99 dest := filepath.Join("spec", "xml", name)
100 if name == "xcb.xsd" {
101 dest = "spec/xcb.xsd"
102 }
103 if name == "COPYING" {
104 dest = "LICENSES/xcb-proto-COPYING"
105 }
106 if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
107 return err
108 }
109 if err := os.WriteFile(dest, data[name], 0644); err != nil {
110 return err
111 }
112 sum := sha256.Sum256(data[name])
113 m.Files = append(m.Files, entry{filepath.ToSlash(dest), hex.EncodeToString(sum[:])})
114 }
115 b, err := json.MarshalIndent(m, "", " ")
116 if err != nil {
117 return err
118 }
119 b = append(b, '\n')
120 // Keep refresh deterministic, including when invoked repeatedly from CI.
121 old, _ := os.ReadFile("spec/manifest.json")
122 if bytes.Equal(old, b) {
123 return nil
124 }
125 return os.WriteFile("spec/manifest.json", b, 0644)
126}