// Command xtsync vendors the pinned xcb-proto sources. An explicit -source // directory supports offline refreshes; its provenance is retained in manifest. package main import ( "archive/tar" "bytes" "compress/gzip" "crypto/sha256" "encoding/hex" "encoding/json" "flag" "fmt" "io" "net/http" "os" "path/filepath" "time" ) const upstream = "https://gitlab.freedesktop.org/xorg/proto/xcbproto" const tag = "xcb-proto-1.17.0" var files = []string{"xproto.xml", "bigreq.xml", "ge.xml", "xc_misc.xml", "composite.xml", "xfixes.xml", "res.xml", "render.xml", "shape.xml", "xcb.xsd", "COPYING"} type entry struct { Path string `json:"path"` SHA256 string `json:"sha256"` } type manifest struct { Upstream string `json:"upstream"` Tag string `json:"tag"` Source string `json:"source"` Files []entry `json:"files"` } func main() { source := flag.String("source", "", "local source directory (COPYING must be included)") flag.Parse() if err := syncFiles(*source); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func syncFiles(source string) error { data := make(map[string][]byte) provenance := source if source != "" { for _, name := range files { b, err := os.ReadFile(filepath.Join(source, name)) if err != nil { return err } data[name] = b } } else { provenance = upstream + "/-/archive/" + tag + "/xcbproto-" + tag + ".tar.gz" client := http.Client{Timeout: 30 * time.Second} resp, err := client.Get(provenance) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download: %s", resp.Status) } gz, err := gzip.NewReader(io.LimitReader(resp.Body, 16<<20)) if err != nil { return err } defer gz.Close() tr := tar.NewReader(gz) for { h, err := tr.Next() if err == io.EOF { break } if err != nil { return err } for _, name := range files { if h.Name == "xcbproto-"+tag+"/src/"+name || name == "COPYING" && h.Name == "xcbproto-"+tag+"/COPYING" { b, err := io.ReadAll(io.LimitReader(tr, 4<<20)) if err != nil { return err } data[name] = b } } } } m := manifest{Upstream: upstream, Tag: tag, Source: provenance} for _, name := range files { if len(data[name]) == 0 { return fmt.Errorf("missing source %s", name) } } for _, name := range files { dest := filepath.Join("spec", "xml", name) if name == "xcb.xsd" { dest = "spec/xcb.xsd" } if name == "COPYING" { dest = "LICENSES/xcb-proto-COPYING" } if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { return err } if err := os.WriteFile(dest, data[name], 0644); err != nil { return err } sum := sha256.Sum256(data[name]) m.Files = append(m.Files, entry{filepath.ToSlash(dest), hex.EncodeToString(sum[:])}) } b, err := json.MarshalIndent(m, "", " ") if err != nil { return err } b = append(b, '\n') // Keep refresh deterministic, including when invoked repeatedly from CI. old, _ := os.ReadFile("spec/manifest.json") if bytes.Equal(old, b) { return nil } return os.WriteFile("spec/manifest.json", b, 0644) }