// Copyright 2023 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This program can be used to create a tarball from Bazel-built artifacts given their execpaths // and rootpaths. // // Design notes: Currently, building Go with Bazel on Mac can be slow. The problem is compounded // by the fact that we roll the Skia Infra repo into Skia via a go.mod update, which busts Bazel's // repository cache and causes Bazel to re-download a large number of Go modules. To mitigate // slowness on Mac, this program does not use any external dependencies. This by itself does not // necessarily make the build faster on Macs, but it unblocks the following potential optimization. // We could build this binary using a separate, minimalistic go.mod file that does not include the // Skia Infra repository. If the rules_go[1] rules don't allow multiple go.mod files, we could work // work around that limitation by shelling out to the Bazel-downloaded "go" binary from a genrule // (something like "go build -o foo foo.go"). // // [1] https://github.com/bazelbuild/rules_go package main import ( "archive/tar" "bytes" "compress/gzip" "flag" "fmt" "io" "os" "strings" ) func main() { execpathsFlag := flag.String("execpaths", "", "Space-separated list of the execpaths of files to be included in the tarball.") rootpathsFlag := flag.String("rootpaths", "", "Space-separated list of the rootpaths of files to be included in the tarball.") outputFileFlag := flag.String("output-file", "", "Filename of the tarball to create.") flag.Parse() if *execpathsFlag == "" { die("Flag --execpaths is required.\n") } if *rootpathsFlag == "" { die("Flag --rootpaths is required.\n") } if *outputFileFlag == "" { die("Flag --output-file is required.\n") } execpaths := flagToStrings(*execpathsFlag) rootpaths := flagToStrings(*rootpathsFlag) if len(execpaths) != len(rootpaths) { die("Flags --execpaths and --rootpaths were passed lists of different lenghts: %d and %d.\n", len(execpaths), len(rootpaths)) } outputFile, err := os.Create(*outputFileFlag) if err != nil { die("Could not create file %q: %s", *outputFileFlag, err) } defer outputFile.Close() gzipWriter := gzip.NewWriter(outputFile) defer gzipWriter.Close() tarWriter := tar.NewWriter(gzipWriter) defer tarWriter.Close() for i := range execpaths { // execpaths point to physical files generated by Bazel (e.g. // bazel-out/k8-linux_x64-dbg/bin/tests/some_test), whereas rootpaths are the paths that a // binary running via "bazel run" or "bazel test" expects (e.g tests/some_test). Thus, we must // map the former to the latter. // // Reference: // https://bazel.build/reference/be/make-variables#predefined_label_variables if err := addFileToTarball(tarWriter, execpaths[i], rootpaths[i]); err != nil { die("Adding file %q to tarball: %s", execpaths[i], err) } } } func flagToStrings(flag string) []string { var values []string for _, value := range strings.Split(flag, " ") { values = append(values, strings.TrimSpace(value)) } return values } func addFileToTarball(w *tar.Writer, readFromPath, saveAsPath string) error { contents, err := os.ReadFile(readFromPath) if err != nil { return err } stat, err := os.Stat(readFromPath) if err != nil { return err } header := &tar.Header{ Name: saveAsPath, Size: stat.Size(), Mode: int64(stat.Mode()), } if err := w.WriteHeader(header); err != nil { return err } _, err = io.Copy(w, bytes.NewBuffer(contents)) return err } func die(msg string, a ...interface{}) { fmt.Fprintf(os.Stderr, msg, a...) os.Exit(1) }