github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/gomodules/gomodules.go (about) 1 package gomodules 2 3 import ( 4 "encoding/json" 5 "os" 6 "path" 7 "strings" 8 9 "github.com/pkg/errors" 10 11 "github.com/fossas/fossa-cli/buildtools" 12 "github.com/fossas/fossa-cli/exec" 13 "github.com/fossas/fossa-cli/pkg" 14 ) 15 16 // Resolver maps modules from import paths to their pkg.Import representation. 17 type Resolver struct { 18 pathMap map[string]pkg.Import 19 } 20 21 // module is used to unmarshal `go list -m -json all`. 22 type module struct { 23 Path string 24 Version string 25 Replace replace 26 } 27 28 type replace struct { 29 Path string 30 Version string 31 } 32 33 // Resolve calculates a package's module path from its import path, 34 // and returns the resolved pkg.Import for the module path. 35 func (r Resolver) Resolve(importpath string) (pkg.Import, error) { 36 for p := importpath; p != "." && p != "/"; p = path.Dir(p) { 37 revision, ok := r.pathMap[p] 38 if ok { 39 return revision, nil 40 } 41 } 42 return pkg.Import{}, buildtools.ErrNoRevisionForPackage 43 } 44 45 // New connects goModuleList and a parser to return a golang.Resolver. 46 func New(dir string) (Resolver, error) { 47 moduleJSON, err := goModuleList(dir) 48 if err != nil { 49 return Resolver{}, errors.Wrap(err, "Could not run go list") 50 } 51 52 resolver, err := parseModuleJSON(moduleJSON) 53 if err != nil { 54 return Resolver{}, errors.Wrap(err, "Could not parse json") 55 } 56 return resolver, nil 57 } 58 59 // Mock creates a golang.Resolver using any string input. 60 func Mock(modules string) (Resolver, error) { 61 return parseModuleJSON(modules) 62 } 63 64 // parseModuleJSON returns a golang.Resolver from the output of `go list -m -json all`. 65 // Replaced modules are handled in place and added to the pathMap. 66 func parseModuleJSON(moduleJSON string) (Resolver, error) { 67 resolver := Resolver{} 68 var modList []module 69 // The output for each module is valid JSON, but the output overall is not. 70 err := json.Unmarshal([]byte("["+strings.Replace(moduleJSON, "}\n{", "},{", -1)+"]"), &modList) 71 if err != nil { 72 return resolver, errors.Wrap(err, "Could not unmarshal JSON into module list") 73 } 74 75 normalizedModules := make(map[string]pkg.Import) 76 for _, mod := range modList { 77 importpath := mod.Path 78 79 // Handle replaced modules. 80 emptyReplace := replace{} 81 if mod.Replace != emptyReplace { 82 mod = module{ 83 Version: mod.Replace.Version, 84 Path: mod.Replace.Path, 85 } 86 } 87 88 version := extractRevision(mod.Version) 89 normalizedModules[importpath] = pkg.Import{ 90 Target: version, 91 Resolved: pkg.ID{ 92 Type: pkg.Go, 93 Name: mod.Path, 94 Revision: version, 95 }, 96 } 97 } 98 99 resolver.pathMap = normalizedModules 100 return resolver, nil 101 } 102 103 func goModuleList(path string) (string, error) { 104 cmd, _, err := exec.Which("version", os.Getenv("FOSSA_GO_CMD"), "go") 105 if err != nil { 106 return "", err 107 } 108 stdout, stderr, err := exec.Run(exec.Cmd{ 109 Name: cmd, 110 Argv: []string{"list", "-m", "-json", "all"}, 111 Dir: path, 112 }) 113 if err != nil { 114 return "", errors.Errorf("Could not run `go list -m -json all` within path %s: %s (%s)", path, strings.TrimSpace(stderr), err) 115 } 116 117 return stdout, nil 118 } 119 120 // extractRevision returns the correct revision from a gomodules version which has 121 // the form of "version-date-gitSHA" or "version". If gitSHA is present we want to 122 // return it as this means version is v0.0.0. 123 func extractRevision(version string) string { 124 split := strings.Split(version, "-") 125 if len(split) < 3 { 126 return strings.TrimSuffix(version, "+incompatible") 127 } 128 return split[2] 129 }