github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/golang/build.go (about)

     1  // Copyright 2015-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package golang is an API to the Go compiler.
     6  package golang
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"go/build"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  )
    17  
    18  type Environ struct {
    19  	build.Context
    20  }
    21  
    22  // Default is the default build environment comprised of the default GOPATH,
    23  // GOROOT, GOOS, GOARCH, and CGO_ENABLED values.
    24  func Default() Environ {
    25  	return Environ{Context: build.Default}
    26  }
    27  
    28  // PackageByPath retrieves information about a package by its file system path.
    29  //
    30  // `path` is assumed to be the directory containing the package.
    31  func (c Environ) PackageByPath(path string) (*build.Package, error) {
    32  	abs, err := filepath.Abs(path)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	return c.Context.ImportDir(abs, 0)
    37  }
    38  
    39  // Package retrieves information about a package by its Go import path.
    40  func (c Environ) Package(importPath string) (*build.Package, error) {
    41  	return c.Context.Import(importPath, "", 0)
    42  }
    43  
    44  // ListPackage matches a subset of the JSON output of the `go list -json`
    45  // command.
    46  //
    47  // See `go help list` for the full structure.
    48  //
    49  // This currently contains an incomplete list of dependencies.
    50  type ListPackage struct {
    51  	Dir        string
    52  	Deps       []string
    53  	GoFiles    []string
    54  	SFiles     []string
    55  	HFiles     []string
    56  	Goroot     bool
    57  	Root       string
    58  	ImportPath string
    59  }
    60  
    61  // GoCmd runs a go command in the environment.
    62  func (c Environ) GoCmd(args ...string) *exec.Cmd {
    63  	cmd := exec.Command(filepath.Join(c.GOROOT, "bin", "go"), args...)
    64  	cmd.Env = append(os.Environ(), c.Env()...)
    65  	return cmd
    66  }
    67  
    68  // Version returns the Go version string that runtime.Version would return for
    69  // the Go compiler in this environ.
    70  func (c Environ) Version() (string, error) {
    71  	cmd := c.GoCmd("version")
    72  	v, err := cmd.CombinedOutput()
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  	s := strings.Fields(string(v))
    77  	if len(s) < 3 {
    78  		return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v))
    79  	}
    80  	return s[2], nil
    81  }
    82  
    83  // Deps lists all dependencies of the package given by `importPath`.
    84  func (c Environ) Deps(importPath string) (*ListPackage, error) {
    85  	// The output of this is almost the same as build.Import, except for
    86  	// the dependencies.
    87  	cmd := c.GoCmd("list", "-json", importPath)
    88  	out, err := cmd.CombinedOutput()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	var p ListPackage
    94  	if err := json.Unmarshal(out, &p); err != nil {
    95  		return nil, err
    96  	}
    97  	return &p, nil
    98  }
    99  
   100  func (c Environ) Env() []string {
   101  	var env []string
   102  	if c.GOARCH != "" {
   103  		env = append(env, fmt.Sprintf("GOARCH=%s", c.GOARCH))
   104  	}
   105  	if c.GOOS != "" {
   106  		env = append(env, fmt.Sprintf("GOOS=%s", c.GOOS))
   107  	}
   108  	if c.GOROOT != "" {
   109  		env = append(env, fmt.Sprintf("GOROOT=%s", c.GOROOT))
   110  	}
   111  	if c.GOPATH != "" {
   112  		env = append(env, fmt.Sprintf("GOPATH=%s", c.GOPATH))
   113  	}
   114  	var cgo int8
   115  	if c.CgoEnabled {
   116  		cgo = 1
   117  	}
   118  	env = append(env, fmt.Sprintf("CGO_ENABLED=%d", cgo))
   119  	return env
   120  }
   121  
   122  func (c Environ) String() string {
   123  	return strings.Join(c.Env(), " ")
   124  }
   125  
   126  // Optional arguments to Environ.Build.
   127  type BuildOpts struct {
   128  	// NoStrip builds an unstripped binary.
   129  	NoStrip bool
   130  	// ExtraArgs to `go build`.
   131  	ExtraArgs []string
   132  }
   133  
   134  // Build compiles the package given by `importPath`, writing the build object
   135  // to `binaryPath`.
   136  func (c Environ) Build(importPath string, binaryPath string, opts BuildOpts) error {
   137  	p, err := c.Package(importPath)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	return c.BuildDir(p.Dir, binaryPath, opts)
   143  }
   144  
   145  // BuildDir compiles the package in the directory `dirPath`, writing the build
   146  // object to `binaryPath`.
   147  func (c Environ) BuildDir(dirPath string, binaryPath string, opts BuildOpts) error {
   148  	args := []string{
   149  		"build",
   150  		"-a", // Force rebuilding of packages.
   151  		"-o", binaryPath,
   152  		"-installsuffix", "uroot",
   153  		"-gcflags=all=-l", // Disable "function inlining" to get a smaller binary
   154  	}
   155  	if !opts.NoStrip {
   156  		args = append(args, `-ldflags=-s -w`) // Strip all symbols.
   157  	}
   158  	if len(c.BuildTags) > 0 {
   159  		args = append(args, []string{"-tags", strings.Join(c.BuildTags, " ")}...)
   160  	}
   161  	if opts.ExtraArgs != nil {
   162  		args = append(args, opts.ExtraArgs...)
   163  	}
   164  	// We always set the working directory, so this is always '.'.
   165  	args = append(args, ".")
   166  
   167  	cmd := c.GoCmd(args...)
   168  	cmd.Dir = dirPath
   169  
   170  	if o, err := cmd.CombinedOutput(); err != nil {
   171  		return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err)
   172  	}
   173  	return nil
   174  }