github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/gocmd/go.go (about)

     1  // Package gocmd provides functions for working with the Go tool. This package
     2  // is not named "go" because that name collides with a reserved keyword.
     3  package gocmd
     4  
     5  import (
     6  	"encoding/json"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/fossas/fossa-cli/exec"
    12  )
    13  
    14  // Package represents a single Go package.
    15  type Package struct {
    16  	Name       string      // Vendored import name (what would go into an `import` statement).
    17  	ImportPath string      // Fully qualified import name (including `vendor` folders).
    18  	Dir        string      // Absolute location on filesystem.
    19  	IsInternal bool        // Whether the package is an internal package.
    20  	IsStdLib   bool        // Whether the package is a part of the standard library.
    21  	Imports    []string    // Direct dependencies.
    22  	Deps       []string    // Transitive dependencies.
    23  	Error      interface{} // A package loading error, if applicable.
    24  }
    25  
    26  // Name computes the vendored import path of a package given its fully qualified
    27  // import name.
    28  func Name(importPath string) string {
    29  	sections := strings.Split(importPath, "vendor")
    30  	return sections[len(sections)-1]
    31  }
    32  
    33  // Go contains configuration information for the Go tool.
    34  type Go struct {
    35  	Cmd string
    36  	Dir string
    37  }
    38  
    39  // GoListOutput is a subset of the output of `go list`. See `go help list` for
    40  // details.
    41  type GoListOutput struct {
    42  	Name       string
    43  	ImportPath string
    44  	Dir        string
    45  	Standard   bool
    46  	Imports    []string
    47  	Deps       []string
    48  	Error      *GoListPackageError
    49  	DepsErrors []*GoListPackageError
    50  }
    51  
    52  // GoListPackageError is the PackageError struct defined in `go help list`.
    53  type GoListPackageError struct {
    54  	ImportStack []string
    55  	Pos         string
    56  	Err         string
    57  }
    58  
    59  // ListOne runs List for a single package.
    60  func (g *Go) ListOne(pkg string, flags []string) (Package, error) {
    61  	pkgs, err := g.List([]string{pkg}, flags)
    62  	if err != nil {
    63  		return Package{}, err
    64  	}
    65  	if len(pkgs) != 1 {
    66  		return Package{}, errors.New("go build target specifies multiple packages")
    67  	}
    68  	return pkgs[0], nil
    69  }
    70  
    71  // List runs `go list` to return information about packages.
    72  func (g *Go) List(pkgs, flags []string) ([]Package, error) {
    73  	// Run `go list -json $PKG` and unmarshal output.
    74  	var output []GoListOutput
    75  	argv := append(flags, pkgs...)
    76  	stdout, stderr, err := exec.Run(exec.Cmd{
    77  		Name: g.Cmd,
    78  		Argv: append([]string{"list", "-json"}, argv...),
    79  		Dir:  g.Dir,
    80  	})
    81  	if err != nil && stdout == "" {
    82  		if strings.Contains(stderr, "build constraints exclude all Go files") {
    83  			// TODO: add better documentation around this error, and rename it to be
    84  			// more useful.
    85  			return nil, errors.New("bad OS/architecture target")
    86  		}
    87  		return nil, errors.Errorf("could not run go list: %s (%s)", strings.TrimSpace(stderr), err)
    88  	}
    89  	// The output for each package is valid JSON, but the output overall is not
    90  	// valid JSON until we massage it a bit.
    91  	err = json.Unmarshal([]byte("["+strings.Replace(stdout, "}\n{", "},{", -1)+"]"), &output)
    92  	if err != nil {
    93  		return nil, errors.Wrap(err, "could not unmarshal go list output")
    94  	}
    95  
    96  	// Parse output into Packages.
    97  	var ret []Package
    98  	for _, pkg := range output {
    99  		p := Package{
   100  			Name:       pkg.Name,
   101  			ImportPath: pkg.ImportPath,
   102  			Dir:        pkg.Dir,
   103  			IsInternal: strings.Contains(pkg.ImportPath, "internal"),
   104  			IsStdLib:   pkg.Standard,
   105  			Imports:    pkg.Imports,
   106  			Deps:       pkg.Deps,
   107  		}
   108  		if pkg.DepsErrors != nil {
   109  			p.Error = pkg.DepsErrors
   110  		}
   111  		if pkg.Error != nil {
   112  			p.Error = pkg.Error
   113  		}
   114  		ret = append(ret, p)
   115  	}
   116  	return ret, nil
   117  }
   118  
   119  // Build runs `go build` for packages.
   120  func (g *Go) Build(pkgs []string) error {
   121  	_, _, err := exec.Run(exec.Cmd{
   122  		Name: g.Cmd,
   123  		Argv: append([]string{"build"}, pkgs...),
   124  	})
   125  	return err
   126  }
   127  
   128  // Clean runs `go clean` for packages.
   129  func (g *Go) Clean(pkgs []string) error {
   130  	_, _, err := exec.Run(exec.Cmd{
   131  		Name: g.Cmd,
   132  		Argv: append([]string{"clean"}, pkgs...),
   133  	})
   134  	return err
   135  }