Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2: list dependencies from either import path or go binary #71

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7f8ce88
set up v2 folder
Bobgy Jun 19, 2021
e020f62
v2: i2: list deps in go binary
Bobgy Jun 19, 2021
55b8538
fix
Bobgy Jun 19, 2021
a571fd5
update
Bobgy Jun 20, 2021
3ea68bd
fix
Bobgy Jun 20, 2021
2eafb6e
cleanup
Bobgy Jun 20, 2021
682cc82
merge to package gocli
Bobgy Jun 20, 2021
5bd2c4a
fix unit tests
Bobgy Jun 20, 2021
b85f547
rename
Bobgy Jun 20, 2021
acfe58a
cleanup
Bobgy Jun 22, 2021
6f64046
fix
Bobgy Jun 22, 2021
f885c2c
extract more metadata from binary
Bobgy Jun 22, 2021
b9da23c
cleanup
Bobgy Jun 22, 2021
6973c71
cleanup
Bobgy Jun 22, 2021
517e811
address comments
Bobgy Jun 26, 2021
3cd4f5f
clean up
Bobgy Jun 26, 2021
0749a9a
update README with current status
Bobgy Jul 24, 2021
cee56ce
add comments clarifying what tests/modules are
Bobgy Jul 24, 2021
4f9aff3
test: simplify test module
Bobgy Jul 31, 2021
1f90f0e
address feedback: add comments & clean up temp files
Bobgy Aug 15, 2021
fff0db5
vendor go/runtime/debug
Bobgy Aug 15, 2021
89f12c3
test: add version to gocli.ExtractBinaryMetadata test
Bobgy Aug 15, 2021
8d1a5b8
modify go/runtime/debug to parse build info from go version -m comman…
Bobgy Aug 15, 2021
1fb170d
rm third_party/uw-labs/lichen
Bobgy Aug 15, 2021
7b29d8f
add example build info data
Bobgy Aug 15, 2021
6371c33
refactor: expose our own Module type that does not have a replace field
Bobgy Aug 15, 2021
2357868
add more comments
Bobgy Aug 15, 2021
2d930b0
add another approach to get go modules using packages.Visit
Bobgy Aug 30, 2021
801af76
trim +incompatible from version
Bobgy Sep 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions v2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# built binary
go-licenses

# distribution folder
dist

# MacOS common ignores
.DS_Store

# Editor
.vscode
16 changes: 16 additions & 0 deletions v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# go-licenses v2

A tool to automate license management workflow for go module project's dependencies and transitive dependencies.

## **THIS IS STILL UNDER DEVELOPMENT**

The v2 package is being developed and currently incomplete, @Bobgy is
upstreaming changes from his fork in <https://github.com/Bobgy/go-licenses/blob/main/v2>.

Tracking issue where you can find the roadmap and progress:
<https://github.com/google/go-licenses/issues/70>.

The major changes from v1 are:

* V2 only supports go modules, it can get license URL for modules without a need for you to vendor your dependencies.
* V2 does not assume each module has a single license, v2 will scan all the files for each module to find licenses.
13 changes: 13 additions & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/google/go-licenses/v2

go 1.15

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-multierror v1.1.1
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/tools v0.1.5
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
52 changes: 52 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
123 changes: 123 additions & 0 deletions v2/gocli/deps_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gocli_test

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"testing"

"github.com/google/go-licenses/v2/gocli"
"github.com/stretchr/testify/assert"
)

func TestListDeps(t *testing.T) {
var tests = []struct {
workdir string
mainModule string
modules []string
}{
{
workdir: "../tests/modules/hello01",
mainModule: "github.com/google/go-licenses/v2/tests/modules/hello01",
modules: []string{
"github.com/google/go-licenses/v2/tests/modules/hello01@(devel)",
},
},
{
workdir: "../tests/modules/cli02",
mainModule: "github.com/google/go-licenses/v2/tests/modules/cli02",
modules: []string{
"github.com/google/go-licenses/v2/tests/modules/cli02@(devel)",
"github.com/fsnotify/fsnotify@v1.4.9",
"github.com/hashicorp/hcl@v1.0.0",
"github.com/magiconair/properties@v1.8.5",
"github.com/mitchellh/go-homedir@v1.1.0",
"github.com/mitchellh/mapstructure@v1.4.1",
"github.com/pelletier/go-toml@v1.9.3",
"github.com/spf13/afero@v1.6.0",
"github.com/spf13/cast@v1.3.1",
"github.com/spf13/cobra@v1.1.3",
"github.com/spf13/jwalterweatherman@v1.1.0",
"github.com/spf13/pflag@v1.0.5",
"github.com/spf13/viper@v1.8.0",
"github.com/subosito/gotenv@v1.2.0",
"golang.org/x/sys@v0.0.0-20210510120138-977fb7262007",
"golang.org/x/text@v0.3.5",
"gopkg.in/ini.v1@v1.62.0",
"gopkg.in/yaml.v2@v2.4.0",
},
},
}
originalWorkDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
for _, tc := range tests {
os.Chdir(filepath.Join(originalWorkDir, tc.workdir))
sort.Strings(tc.modules)
normalize := func(mods []gocli.Module) []string {
res := make([]string, 0, len(mods))
for _, module := range mods {
ver := module.Version
if module.Main && ver == "" {
// Main module may not have the version, normalize as develop version.
ver = "(devel)"
}
assert.NotEmpty(t, module.Path)
assert.NotEmpty(t, ver)
res = append(res, fmt.Sprintf("%s@%s", module.Path, ver))
}
sort.Strings(res)
return res
}

t.Run(fmt.Sprintf("gocli.ExtractBinaryMetadata(%s)", tc.workdir), func(t *testing.T) {
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tempDir)
// This outputs the built binary as name "main".
binaryName := path.Join(tempDir, "main")
cmd := exec.Command("go", "build", "-o", binaryName)
_, err = cmd.Output()
// defer remove before checking error, because the file
// may be created even when there's an error.
defer os.Remove(binaryName)
if err != nil {
t.Fatalf("go build: %v", err)
}
metadata, err := gocli.ExtractBinaryMetadata(binaryName)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, tc.modules, normalize(append(metadata.Deps, metadata.Main)))
})

t.Run(fmt.Sprintf("gocli.ListDeps(%s)", tc.workdir), func(t *testing.T) {
mods, err := gocli.ListDeps(tc.mainModule)
if err != nil {
t.Fatalf("gocli.ListDeps: %v", err)
}
assert.Equal(t, tc.modules, normalize(mods), "gocli.Modules")
})
}
}
133 changes: 133 additions & 0 deletions v2/gocli/go_binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gocli

import (
"fmt"

"github.com/google/go-licenses/v2/third_party/go/runtime/debug"
)

// Module metadata extracted from binary and local go module workspace.
type BinaryMetadata struct {
// The main module used to build the binary.
// e.g. github.com//google/go-licenses/v2/tests/modules/cli02
Main Module
// Detailed metadata of all the module dependencies.
// Does not include the main module.
Deps []Module
}

// List dependencies from module metadata in a go binary.
// Modules with replace directives are returned as the replaced module instead.
//
// Prerequisites:
// * The go binary must be built with go modules without any further modifications.
// * The command must run with working directory same as to build the analyzed
// go binary, because we need the exact go modules info used to build it.
//
// Here, I am using [1] as a short term solution. It runs [4] go version -m and parses
// output. This is preferred over [2], because [2] is an alternative implemention
// for go version -m, and I expect better long term compatibility for go version -m.
//
// The parsing command output hack is still unfavorable in the long term. As
// dicussed in [3], golang community will move go version parsing into an individual
// module in golang.org/x. We can use that module instead after it is built.
//
// References of similar implementations or dicussions:
// 1. https://github.com/uw-labs/lichen/blob/be9752894a5958f6ba7be9e05dc370b7a73b58db/internal/module/extract.go#L16
// 2. https://github.com/mitchellh/golicense/blob/8c09a94a11ac73299a72a68a7b41e3a737119f91/module/module.go#L27
// 3. https://github.com/golang/go/issues/39301
// 4. https://golang.org/pkg/cmd/go/internal/version/
func ExtractBinaryMetadata(path string) (*BinaryMetadata, error) {
buildInfo, err := listModulesInBinary(path)
if err != nil {
return nil, err
}
main, deps, err := joinModulesMetadata(&buildInfo.Main, buildInfo.Deps)
if err != nil {
return nil, err
}
return &BinaryMetadata{
Main: main,
Deps: deps,
}, nil
}

func listModulesInBinary(path string) (buildinfo *debug.BuildInfo, err error) {
// TODO(Bobgy): replace with x/mod equivalent from https://github.com/golang/go/issues/39301
// when it is available.
buildinfo, err = version(path)
if err != nil {
err = fmt.Errorf("listModulesInGoBinary(path=%q): %w", path, err)
}
return buildinfo, nil
}

// joinModulesMetadata inner joins local go modules metadata with module ref
// extracted from the binary.
// The local go modules metadata is taken from calling `go list -m -json all`.
// Only those appeared in refs will be returned.
// An error is reported when we cannot find go module metadata for some refs,
// or when there's a version mismatch. These errors usually indicate your current
// working directory does not match exactly where the go binary is built.
func joinModulesMetadata(mainRef *debug.Module, refs []*debug.Module) (main Module, deps []Module, err error) {
// Note, there was an attempt to use golang.org/x/tools/go/packages for
// loading modules instead, but it fails for modules like golang.org/x/sys.
// These modules only contains sub-packages, but no source code, so it
// throws an error when using packages.Load.
// More context: https://github.com/google/go-licenses/pull/71#issuecomment-890342154
localModulesDict, err := ListModules()
Bobgy marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return main, nil, err
}
find := func(ref *debug.Module) (*Module, error) {
if ref == nil {
return nil, fmt.Errorf("ref is nil")
}
mod, ok := localModulesDict[ref.Path]
if !ok {
return nil, fmt.Errorf("Cannot find %v in current dir's go modules. Are you running this tool from the working dir to build the binary you are analyzing?", ref.Path)
}
if mod.Dir == "" {
return nil, fmt.Errorf("Module %v's local directory is empty. Did you run `go mod download`?", ref.Path)
}
ver := ref.Version
if ver == "(devel)" {
// Main module's version will be (devel). We should expect an empty version when listing the module info.
ver = ""
}
if ver != mod.Version {
return nil, fmt.Errorf("Found %v@%v in go binary, but %v is downloaded in go modules. Are you running this tool from the working dir to build the binary you are analyzing?", ref.Path, ref.Version, mod.Version)
}
return &mod, nil
}
if mainRef != nil {
found, err := find(mainRef)
if err != nil {
return main, nil, err
}
main = *found
}

for _, ref := range refs {
found, err := find(ref)
if err != nil {
return main, nil, err
}
deps = append(deps, *found)
}
return main, deps, nil
}
Loading