xref: /aosp_15_r20/external/skia/tools/testrunners/common/make_tarball/make_tarball.go (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1// Copyright 2023 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This program can be used to create a tarball from Bazel-built artifacts given their execpaths
6// and rootpaths.
7//
8// Design notes: Currently, building Go with Bazel on Mac can be slow. The problem is compounded
9// by the fact that we roll the Skia Infra repo into Skia via a go.mod update, which busts Bazel's
10// repository cache and causes Bazel to re-download a large number of Go modules. To mitigate
11// slowness on Mac, this program does not use any external dependencies. This by itself does not
12// necessarily make the build faster on Macs, but it unblocks the following potential optimization.
13// We could build this binary using a separate, minimalistic go.mod file that does not include the
14// Skia Infra repository. If the rules_go[1] rules don't allow multiple go.mod files, we could work
15// work around that limitation by shelling out to the Bazel-downloaded "go" binary from a genrule
16// (something like "go build -o foo foo.go").
17//
18// [1] https://github.com/bazelbuild/rules_go
19
20package main
21
22import (
23	"archive/tar"
24	"bytes"
25	"compress/gzip"
26	"flag"
27	"fmt"
28	"io"
29	"os"
30	"strings"
31)
32
33func main() {
34	execpathsFlag := flag.String("execpaths", "", "Space-separated list of the execpaths of files to be included in the tarball.")
35	rootpathsFlag := flag.String("rootpaths", "", "Space-separated list of the rootpaths of files to be included in the tarball.")
36	outputFileFlag := flag.String("output-file", "", "Filename of the tarball to create.")
37	flag.Parse()
38
39	if *execpathsFlag == "" {
40		die("Flag --execpaths is required.\n")
41	}
42	if *rootpathsFlag == "" {
43		die("Flag --rootpaths is required.\n")
44	}
45	if *outputFileFlag == "" {
46		die("Flag --output-file is required.\n")
47	}
48
49	execpaths := flagToStrings(*execpathsFlag)
50	rootpaths := flagToStrings(*rootpathsFlag)
51
52	if len(execpaths) != len(rootpaths) {
53		die("Flags --execpaths and --rootpaths were passed lists of different lenghts: %d and %d.\n", len(execpaths), len(rootpaths))
54	}
55
56	outputFile, err := os.Create(*outputFileFlag)
57	if err != nil {
58		die("Could not create file %q: %s", *outputFileFlag, err)
59	}
60	defer outputFile.Close()
61
62	gzipWriter := gzip.NewWriter(outputFile)
63	defer gzipWriter.Close()
64
65	tarWriter := tar.NewWriter(gzipWriter)
66	defer tarWriter.Close()
67
68	for i := range execpaths {
69		// execpaths point to physical files generated by Bazel (e.g.
70		// bazel-out/k8-linux_x64-dbg/bin/tests/some_test), whereas rootpaths are the paths that a
71		// binary running via "bazel run" or "bazel test" expects (e.g tests/some_test). Thus, we must
72		// map the former to the latter.
73		//
74		// Reference:
75		// https://bazel.build/reference/be/make-variables#predefined_label_variables
76		if err := addFileToTarball(tarWriter, execpaths[i], rootpaths[i]); err != nil {
77			die("Adding file %q to tarball: %s", execpaths[i], err)
78		}
79	}
80}
81
82func flagToStrings(flag string) []string {
83	var values []string
84	for _, value := range strings.Split(flag, " ") {
85		values = append(values, strings.TrimSpace(value))
86	}
87	return values
88}
89
90func addFileToTarball(w *tar.Writer, readFromPath, saveAsPath string) error {
91	contents, err := os.ReadFile(readFromPath)
92	if err != nil {
93		return err
94	}
95
96	stat, err := os.Stat(readFromPath)
97	if err != nil {
98		return err
99	}
100
101	header := &tar.Header{
102		Name: saveAsPath,
103		Size: stat.Size(),
104		Mode: int64(stat.Mode()),
105	}
106	if err := w.WriteHeader(header); err != nil {
107		return err
108	}
109
110	_, err = io.Copy(w, bytes.NewBuffer(contents))
111	return err
112}
113
114func die(msg string, a ...interface{}) {
115	fmt.Fprintf(os.Stderr, msg, a...)
116	os.Exit(1)
117}
118