github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/cmd/gofat/gofat.go (about) 1 // Copyright 2019 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // Command gofat is a simple utility to make fat binaries in the 6 // fatbin format (see github.com/Schaudge/grailbase/fatbin). 7 package main 8 9 import ( 10 "bytes" 11 "flag" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "path" 18 "runtime" 19 "strings" 20 21 "github.com/Schaudge/grailbase/embedbin" 22 "github.com/Schaudge/grailbase/fatbin" 23 "github.com/Schaudge/grailbase/log" 24 ) 25 26 func main() { 27 log.AddFlags() 28 log.SetFlags(0) 29 log.SetPrefix("gofat: ") 30 flag.Usage = func() { 31 fmt.Fprintf(os.Stderr, `usage: 32 gofat build build a fatbin binary 33 gofat info show fatbin binary information 34 gofat embed build an embedbin binary 35 `) 36 flag.PrintDefaults() 37 os.Exit(2) 38 } 39 flag.Parse() 40 if flag.NArg() == 0 { 41 flag.Usage() 42 } 43 44 cmd, args := flag.Arg(0), flag.Args()[1:] 45 switch cmd { 46 default: 47 fmt.Fprintf(os.Stderr, "unknown command %s\n", cmd) 48 flag.Usage() 49 case "info": 50 info(args) 51 case "build": 52 build(args) 53 case "embed": 54 embed(args) 55 } 56 } 57 58 func info(args []string) { 59 if len(args) == 0 { 60 fmt.Fprintf(os.Stderr, "usage: gofat info binaries...\n") 61 os.Exit(2) 62 } 63 for _, filename := range args { 64 f, err := os.Open(filename) 65 must(err) 66 info, err := f.Stat() 67 must(err) 68 r, err := fatbin.OpenFile(f, info.Size()) 69 must(err) 70 fmt.Println(filename, r.GOOS()+"/"+r.GOARCH(), info.Size()) 71 for _, info := range r.List() { 72 fmt.Print("\t", info.Goos, "/", info.Goarch, " ", info.Size, "\n") 73 } 74 must(f.Close()) 75 } 76 } 77 78 func build(args []string) { 79 var ( 80 flags = flag.NewFlagSet("build", flag.ExitOnError) 81 goarches = flags.String("goarches", "amd64", "list of GOARCH values to build") 82 gooses = flags.String("gooses", "darwin,linux", "list of GOOS values to build") 83 out = flags.String("o", "", "build output path") 84 ) 85 flags.Usage = func() { 86 fmt.Fprintf(os.Stderr, "usage: gofat build [-o output] [packages]\n") 87 flags.PrintDefaults() 88 os.Exit(2) 89 } 90 91 must(flags.Parse(args)) 92 if *out == "" { 93 cmd := exec.Command("go", append([]string{"list"}, flags.Args()...)...) 94 cmd.Stderr = os.Stderr 95 listout, err := cmd.Output() 96 must(err) 97 *out = path.Base(string(bytes.TrimSpace(listout))) 98 } 99 100 cmd := exec.Command("go", append([]string{"build", "-o", *out}, flags.Args()...)...) 101 cmd.Stderr = os.Stderr 102 must(cmd.Run()) 103 104 f, err := os.OpenFile(*out, os.O_WRONLY|os.O_APPEND, 0777) 105 must(err) 106 info, err := f.Stat() 107 must(err) 108 fat := fatbin.NewWriter(f, info.Size(), runtime.GOOS, runtime.GOARCH) 109 110 for _, goarch := range strings.Split(*goarches, ",") { 111 for _, goos := range strings.Split(*gooses, ",") { 112 if goarch == runtime.GOARCH && goos == runtime.GOOS { 113 continue 114 } 115 outfile, err := ioutil.TempFile("", *out) 116 must(err) 117 name := outfile.Name() 118 outfile.Close() 119 cmd := exec.Command("go", "build", "-o", name) 120 cmd.Stderr = os.Stderr 121 cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch) 122 must(cmd.Run()) 123 124 outfile, err = os.Open(name) 125 must(err) 126 w, err := fat.Create(goos, goarch) 127 must(err) 128 _, err = io.Copy(w, outfile) 129 must(err) 130 must(os.Remove(name)) 131 must(outfile.Close()) 132 log.Print("append ", goos, "/", goarch) 133 } 134 } 135 must(fat.Close()) 136 must(f.Close()) 137 } 138 139 func embed(args []string) { 140 var ( 141 flags = flag.NewFlagSet("embed", flag.ExitOnError) 142 out = flags.String("o", "", "build output path") 143 ) 144 flags.Usage = func() { 145 fmt.Fprintf(os.Stderr, "usage: gofat embed [-o output] [name1:path1 [name2:path2 ...]]\n") 146 flags.PrintDefaults() 147 os.Exit(2) 148 } 149 150 must(flags.Parse(args)) 151 args = flags.Args() 152 if len(args) == 0 { 153 log.Fatal("missing path to input binary") 154 } 155 inputPath, args := args[0], args[1:] 156 157 paths := map[string]string{} 158 var names []string 159 for _, arg := range args { 160 parts := strings.SplitN(arg, ":", 2) 161 if len(parts) != 2 { 162 log.Fatalf("malformed argument: %s", arg) 163 } 164 name, path := parts[0], parts[1] 165 if _, ok := paths[name]; ok { 166 log.Fatalf("duplicate name: %s", name) 167 } 168 paths[name] = path 169 names = append(names, name) 170 } 171 172 var outF *os.File 173 var err error 174 if *out != "" { 175 outF, err = os.OpenFile(*out, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 176 must(err) 177 var inF *os.File 178 inF, err = os.Open(inputPath) 179 must(err) 180 _, err = io.Copy(outF, inF) 181 must(err) 182 must(inF.Close()) 183 } else { 184 outF, err = os.OpenFile(inputPath, os.O_RDWR|os.O_APPEND, 0777) 185 must(err) 186 } 187 188 ew, err := embedbin.NewFileWriter(outF) 189 must(err) 190 for _, name := range names { 191 embedF, err := os.Open(paths[name]) 192 must(err) 193 embedW, err := ew.Create(name) 194 must(err) 195 _, err = io.Copy(embedW, embedF) 196 must(err) 197 must(embedF.Close()) 198 } 199 must(ew.Close()) 200 must(outF.Close()) 201 } 202 203 func must(err error) { 204 if err != nil { 205 log.Fatal(err) 206 } 207 }