Skip to content

Commit

Permalink
Go mod graph and list improve (#44)
Browse files Browse the repository at this point in the history
* Add go mod graph and improve go list all

* Add go mod graph and improve go list all

* Add go mod graph and improve go list all
  • Loading branch information
sverdlov93 authored Sep 22, 2021
1 parent 42e6988 commit e10fec1
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 81 deletions.
83 changes: 55 additions & 28 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,53 +121,83 @@ func DownloadDependency(dependencyName string) error {
return errorutils.CheckError(gofrogcmd.RunCmd(goCmd))
}

// Runs 'go list -m all' command and returns map of the dependencies
// Runs 'go list -m' command and returns module name
func GetModuleNameByDir(projectDir string) (string, error) {
log.Info("Running 'go list -m' in", projectDir)
isAutoModify, err := automaticallyModifyMod()
if err != nil {
return "", err
}
var cmdArgs []string
// Since version go1.16 build commands (like go build and go list) no longer modify go.mod and go.sum by default.
if isAutoModify {
cmdArgs = []string{"list", "-m"}
} else {
cmdArgs = []string{"list", "-m", "-mod=mod"}
}
output, err := runDependenciesCmd(projectDir, cmdArgs)
if err != nil {
return "", err
}
lineOutput := strings.Split(output, "\n")
return lineOutput[0], errorutils.CheckError(err)
}

// Runs go list -f {{with .Module}}{{.Path}}:{{.Version}}{{end}} all command and returns map of the dependencies
func GetDependenciesList(projectDir string) (map[string]bool, error) {
log.Info("Running 'go list -f {{with .Module}}{{.Path}}@{{.Version}}{{end}} all' in", projectDir)

output, err := runDependenciesCmd(projectDir, []string{"list", "-f", "{{with .Module}}{{.Path}}@{{.Version}}{{end}}", "all"})
if err != nil {
return nil, err
}
return listToMap(output), errorutils.CheckError(err)
}

// Runs 'go mod graph' command and returns map that maps dependencies to their child dependencies slice
func GetDependenciesGraph(projectDir string) (map[string][]string, error) {
log.Info("Running 'go mod graph' in", projectDir)
output, err := runDependenciesCmd(projectDir, []string{"mod", "graph"})
if err != nil {
return nil, err
}
return graphToMap(output), errorutils.CheckError(err)
}

// Common function to run dependencies command for list or graph commands
func runDependenciesCmd(projectDir string, commandArgs []string) (string, error) {
var err error
if projectDir == "" {
projectDir, err = GetProjectRoot()
if err != nil {
return nil, err
return "", err
}
}

// Read and store the details of the go.mod and go.sum files,
// because they may change by the "go list" command.
// because they may change by the 'go mod graph' or 'go list' commands.
modFileContent, modFileStat, err := GetFileDetails(filepath.Join(projectDir, "go.mod"))
if err != nil {
log.Info("Dependencies were not collected for this build, since go.mod could not be found in", projectDir)
return nil, nil
return "", nil
}
sumFileContent, sumFileStat, err := GetGoSum(projectDir)
if len(sumFileContent) > 0 && sumFileStat != nil {
defer RestoreSumFile(projectDir, sumFileContent, sumFileStat)
}

log.Info("Running 'go list -m all' in", projectDir)
goCmd, err := NewCmd()
if err != nil {
return nil, err
}
isAutoModify, err := automaticallyModifyMod()
if err != nil {
return nil, err
}
// Since version go1.16 build commands (like go build and go list) no longer modify go.mod and go.sum by default.
if isAutoModify {
goCmd.Command = []string{"list", "-m", "all"}
} else {
goCmd.Command = []string{"list", "-m", "-mod=mod", "all"}
return "", err
}
goCmd.Command = commandArgs
goCmd.Dir = projectDir

err = prepareGlobalRegExp()
if err != nil {
return nil, err
return "", err
}

performPasswordMask, err := shouldMaskPassword()
if err != nil {
return nil, err
return "", err
}
var output string
var executionError error
Expand All @@ -176,24 +206,21 @@ func GetDependenciesList(projectDir string) (map[string]bool, error) {
} else {
output, _, _, executionError = gofrogcmd.RunCmdWithOutputParser(goCmd, true)
}

if len(output) != 0 {
log.Debug(output)
}

if executionError != nil {
// If the command fails, the mod stays the same, therefore, don't need to be restored.
return nil, errorutils.CheckError(executionError)
return "", errorutils.CheckError(executionError)
}

// Restore the the go.mod and go.sum files, to make sure they stay the same as before
// running the "go list" command.
// running the "go mod graph" command.
err = ioutil.WriteFile(filepath.Join(projectDir, "go.mod"), modFileContent, modFileStat.Mode())
if err != nil {
return nil, err
return "", err
}

return outputToMap(output), errorutils.CheckError(err)
return output, err
}

// Returns the root dir where the go.mod located.
Expand Down
50 changes: 21 additions & 29 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,27 @@ import (
"testing"
)

func TestOutputToMap(t *testing.T) {
content := `go: finding github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
go: finding github.com/nwaples/rardecode v0.0.0-20171029023500-e06696f847ae
go: finding github.com/pierrec/lz4 v2.0.5+incompatible
go: finding github.com/ulikunitz/xz v0.5.4
go: finding github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76
go: finding github.com/mholt/archiver v2.1.0+incompatible
go: finding rsc.io/quote v1.5.2
go: finding golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a
go: finding golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2
go: finding rsc.io/sampler v1.3.0
github.com/you/hello
github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
github.com/mholt/archiver v2.1.0+incompatible
github.com/nwaples/rardecode v0.0.0-20171029023500-e06696f847ae
github.com/pierrec/lz4 v2.0.5+incompatible
github.com/ulikunitz/xz v0.5.4
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c => golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2
golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a => /temp/tools
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
func TestListToMap(t *testing.T) {
content := `github.com/you/hello
github.com/dsnet/[email protected]
github.com/golang/[email protected]
github.com/mholt/[email protected]+incompatible
github.com/nwaples/[email protected]
github.com/pierrec/[email protected]+incompatible
github.com/ulikunitz/[email protected]
rsc.io/[email protected]
rsc.io/[email protected]
`

log.SetLogger(log.NewLogger(log.ERROR, nil))
actual := outputToMap(content)
actual := listToMap(content)
expected := map[string]bool{
"github.com/dsnet/[email protected]": true,
"github.com/golang/[email protected]": true,
"github.com/mholt/[email protected]+incompatible": true,
"github.com/nwaples/[email protected]": true,
"github.com/pierrec/[email protected]+incompatible": true,
"github.com/ulikunitz/[email protected]": true,
"golang.org/x/[email protected]": true,
"rsc.io/[email protected]": true,
"rsc.io/[email protected]": true,
}
Expand Down Expand Up @@ -140,7 +127,12 @@ func TestGetDependenciesList(t *testing.T) {
assert.NoError(t, err)
}()
originSumFileContent, _, err := GetGoSum(gomodPath)

err = fileutils.MoveFile(filepath.Join(gomodPath, "test.go.txt"), filepath.Join(gomodPath, "test.go"))
assert.NoError(t, err)
defer func() {
err := fileutils.MoveFile(filepath.Join(gomodPath, "test.go"), filepath.Join(gomodPath, "test.go.txt"))
assert.NoError(t, err)
}()
actual, err := GetDependenciesList(filepath.Join(gomodPath))
if err != nil {
t.Error(err)
Expand All @@ -154,10 +146,10 @@ func TestGetDependenciesList(t *testing.T) {
}

expected := map[string]bool{
"golang.org/x/[email protected]": true,
"golang.org/x/[email protected]": true,
"rsc.io/quote@v1.5.2": true,
"rsc.io/[email protected]": true,
"golang.org/x/[email protected]": true,
"rsc.io/[email protected]": true,
"rsc.io/sampler@v1.3.0": true,
"testGoList@": true,
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expecting: \n%v \nGot: \n%v", expected, actual)
Expand Down
5 changes: 3 additions & 2 deletions cmd/testdata/mods/testGoList/go.mod.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module example.com/hello
module testGoList

go 1.16

require rsc.io/quote v1.5.2
require golang.org/x/text v0.3.3

require golang.org/x/text v0.3.3 // indirect
8 changes: 7 additions & 1 deletion cmd/testdata/mods/testGoList/go.sum.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
10 changes: 10 additions & 0 deletions cmd/testdata/mods/testGoList/test.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package testGoList

import (
"fmt"
"rsc.io/quote"
)

func PrintHello() {
fmt.Println(quote.Hello())
}
40 changes: 19 additions & 21 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,33 +104,31 @@ func GetFileDetails(filePath string) (modFileContent []byte, modFileStat os.File
return
}

func outputToMap(output string) map[string]bool {
func listToMap(output string) map[string]bool {
lineOutput := strings.Split(output, "\n")
mapOfDeps := map[string]bool{}

for _, line := range lineOutput {
splitLine := strings.Split(line, " ")
lineLen := len(splitLine)
if lineLen == 2 {
mapOfDeps[splitLine[0]+"@"+splitLine[1]] = true
// The expected syntax : github.com/[email protected]
if len(strings.Split(line, "@")) == 2 && mapOfDeps[line] == false {
mapOfDeps[line] = true
continue
}
// In a case of a replace statement : source version => target version
// choose the target version.
if lineLen == 5 {
if splitLine[2] == "=>" {
mapOfDeps[splitLine[3]+"@"+splitLine[4]] = true
continue
}
}
// In a case of a replace statement with a local filesystem target: source version => local_target
// local target won't be added to the dependencies map.
if lineLen == 4 && splitLine[0] != "go:" {
if splitLine[2] == "=>" {
log.Info("The replacer is not pointing to a VCS version: " + splitLine[0] + ",\nThis dependency won't be added to the requested build dependencies list.")
}
}
}
return mapOfDeps
}

func graphToMap(output string) map[string][]string {
lineOutput := strings.Split(output, "\n")
mapOfDeps := map[string][]string{}
for _, line := range lineOutput {
// The expected syntax : github.com/[email protected] github.com/[email protected]
line = strings.ReplaceAll(line, "@v", ":")
splitLine := strings.Split(line, " ")
if len(splitLine) == 2 {
parent := splitLine[0]
child := splitLine[1]
mapOfDeps[parent] = append(mapOfDeps[parent], child)
}
}
return mapOfDeps
}
Expand Down

0 comments on commit e10fec1

Please sign in to comment.