github.com/grailbio/base@v0.0.11/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/grailbio/base/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/grailbio/base/embedbin"
    22  	"github.com/grailbio/base/fatbin"
    23  	"github.com/grailbio/base/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  }