github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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  func (c Environ) goCmd(args ...string) *exec.Cmd {
    62  	cmd := exec.Command(filepath.Join(c.GOROOT, "bin", "go"), args...)
    63  	cmd.Env = append(os.Environ(), c.Env()...)
    64  	return cmd
    65  }
    66  
    67  // Version returns the Go version string that runtime.Version would return for
    68  // the Go compiler in this environ.
    69  func (c Environ) Version() (string, error) {
    70  	cmd := c.goCmd("version")
    71  	v, err := cmd.CombinedOutput()
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	s := strings.Fields(string(v))
    76  	if len(s) < 3 {
    77  		return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v))
    78  	}
    79  	return s[2], nil
    80  }
    81  
    82  // Deps lists all dependencies of the package given by `importPath`.
    83  func (c Environ) Deps(importPath string) (*ListPackage, error) {
    84  	// The output of this is almost the same as build.Import, except for
    85  	// the dependencies.
    86  	cmd := c.goCmd("list", "-json", importPath)
    87  	out, err := cmd.CombinedOutput()
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	var p ListPackage
    93  	if err := json.Unmarshal(out, &p); err != nil {
    94  		return nil, err
    95  	}
    96  	return &p, nil
    97  }
    98  
    99  func (c Environ) Env() []string {
   100  	var env []string
   101  	if c.GOARCH != "" {
   102  		env = append(env, fmt.Sprintf("GOARCH=%s", c.GOARCH))
   103  	}
   104  	if c.GOOS != "" {
   105  		env = append(env, fmt.Sprintf("GOOS=%s", c.GOOS))
   106  	}
   107  	if c.GOROOT != "" {
   108  		env = append(env, fmt.Sprintf("GOROOT=%s", c.GOROOT))
   109  	}
   110  	if c.GOPATH != "" {
   111  		env = append(env, fmt.Sprintf("GOPATH=%s", c.GOPATH))
   112  	}
   113  	var cgo int8
   114  	if c.CgoEnabled {
   115  		cgo = 1
   116  	}
   117  	env = append(env, fmt.Sprintf("CGO_ENABLED=%d", cgo))
   118  	return env
   119  }
   120  
   121  func (c Environ) String() string {
   122  	return strings.Join(c.Env(), " ")
   123  }
   124  
   125  // Optional arguments to Environ.Build.
   126  type BuildOpts struct {
   127  	// ExtraArgs to `go build`.
   128  	ExtraArgs []string
   129  }
   130  
   131  // Build compiles the package given by `importPath`, writing the build object
   132  // to `binaryPath`.
   133  func (c Environ) Build(importPath string, binaryPath string, opts BuildOpts) error {
   134  	p, err := c.Package(importPath)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	return c.BuildDir(p.Dir, binaryPath, opts)
   140  }
   141  
   142  // BuildDir compiles the package in the directory `dirPath`, writing the build
   143  // object to `binaryPath`.
   144  func (c Environ) BuildDir(dirPath string, binaryPath string, opts BuildOpts) error {
   145  	args := []string{
   146  		"build",
   147  		"-a", // Force rebuilding of packages.
   148  		"-o", binaryPath,
   149  		"-installsuffix", "uroot",
   150  		"-gcflags=all=-l",   // Disable "function inlining" to get a smaller binary
   151  		"-ldflags", "-s -w", // Strip all symbols.
   152  	}
   153  	if len(c.BuildTags) > 0 {
   154  		args = append(args, []string{"-tags", strings.Join(c.BuildTags, " ")}...)
   155  	}
   156  	if opts.ExtraArgs != nil {
   157  		args = append(args, opts.ExtraArgs...)
   158  	}
   159  	// We always set the working directory, so this is always '.'.
   160  	args = append(args, ".")
   161  
   162  	cmd := c.goCmd(args...)
   163  	cmd.Dir = dirPath
   164  
   165  	if o, err := cmd.CombinedOutput(); err != nil {
   166  		return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err)
   167  	}
   168  	return nil
   169  }