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