How to view full dependency tree for nested Go dependencies

huangapple go评论80阅读模式
英文:

How to view full dependency tree for nested Go dependencies

问题

我正在尝试调试我们的 CI 中的以下构建错误:“A 依赖于无法构建的 B,因为它依赖于 C。”我正在构建我的数据服务,它并不直接依赖于 kafkaAvailMonitor.go,这使得错误难以追踪。换句话说:

> 数据(我正在构建的)依赖于(?),而(?)依赖于 kafkaAvailMonitor.go

对于开发人员来说,修复这个问题可能很简单,他们只需执行“go get whatever”,但作为发布过程的一部分,我不能这样做——我必须找到添加了该依赖项的人并要求他们修复它。

我知道有工具可以可视化依赖树和其他更复杂的构建系统,但这似乎是一个相当基本的问题:有没有办法查看完整的依赖树以查找导致构建问题的原因?

go build -a -v

../../../msgq/kafkaAvailMonitor.go:8:2: cannot find package 
  “github.com/Shopify/sarama/tz/breaker” in any of:
  /usr/lib/go-1.6/src/github.com/Shopify/sarama/tz/breaker(来自 $GOROOT)
  /home/jenkins/go/src/github.com/Shopify/sarama/tz/breaker(来自 $GOPATH)
  /home/jenkins/vendor-library/src/github.com/Shopify/sarama/tz/breaker
  /home/jenkins/go/src/github.com/Shopify/sarama/tz/breaker
  /home/jenkins/vendor-library/src/github.com/Shopify/sarama/tz/breaker
英文:

I'm trying to debug the following build error in our CI where "A depends on B which can't build because it depends on C." I'm building my data service which doesn't directly depend on kafkaAvailMonitor.go which makes this error hard to trace. In other words:

> data (what I'm building) depends on (?) which depends on
> kafkaAvailMonitor.go

It may seem trivial to fix for a developer they just do "go get whatever" but I can't do that as part of the release process - I have to find the person that added the dependency and ask them to fix it.

I'm aware that there are tools to visualize the dependency tree and other more sophisticated build systems, but this seems like a pretty basic issue: is there any way I can view the full dependency tree to see what's causing the build issue?

go build -a -v

../../../msgq/kafkaAvailMonitor.go:8:2: cannot find package 
  "github.com/Shopify/sarama/tz/breaker" in any of:
  /usr/lib/go-1.6/src/github.com/Shopify/sarama/tz/breaker (from $GOROOT)
  /home/jenkins/go/src/github.com/Shopify/sarama/tz/breaker (from $GOPATH)
  /home/jenkins/vendor-library/src/github.com/Shopify/sarama/tz/breaker
  /home/jenkins/go/src/github.com/Shopify/sarama/tz/breaker
  /home/jenkins/vendor-library/src/github.com/Shopify/sarama/tz/breaker

答案1

得分: 46

使用模块时,您可以通过go mod graph命令获取所需的内容。

用法:go mod graph

Graph命令以文本形式打印模块需求图(应用替换)。输出中的每一行都有两个以空格分隔的字段:一个模块和它的一个依赖。每个模块都被标识为形式为path@version的字符串,除了主模块,它没有@version后缀。

也就是说,对于原始问题,运行go mod graph | grep github.com/Shopify/sarama,然后更仔细地查看左侧的每个条目。

英文:

When using modules you may be able to get what you need from go mod graph.

<pre>
usage: go mod graph

Graph prints the module requirement graph (with replacements applied)
in text form. Each line in the output has two space-separated fields: a module
and one of its requirements. Each module is identified as a string of the form
path@version, except for the main module, which has no @version suffix.
</pre>

I.e., for the original question, run go mod graph | grep github.com/Shopify/sarama then look more closely at each entry on the left-hand side.

答案2

得分: 22

如果以下内容不是堆栈跟踪,那它是什么?

这是Go正在查找缺失包的路径列表。

我不知道谁在导入kafkaAvailMonitor.go。

它没有被“导入”,只是你的源代码的一部分并被编译。但是它无法编译,因为它需要github.com/Shopify/sarama/tz/breaker,而这个包不在GOROOTGOPATH中。

不过,你可以检查一下go list在你的直接包上的返回结果,看看是否提到了kafkaAvailMonitor

go list可以显示你的包直接依赖的包,或者它的完整的传递依赖关系集合。

% go list -f '{{ .Imports }}' github.com/davecheney/profile
[io/ioutil log os os/signal path/filepath runtime runtime/pprof]
% go list -f '{{ .Deps }}' github.com/davecheney/profile
[bufio bytes errors fmt io io/ioutil log math os os/signal path/filepath reflect run

然后,你可以编写脚本使用go list来列出所有依赖项。例如,可以参考Noel Cower (nilium)这个bash脚本

#!/usr/bin/env bash
# Usage: lsdep [PACKAGE...]
#
# Example (list github.com/foo/bar and package dir deps [the . argument])
# $ lsdep github.com/foo/bar .
#
# By default, this will list dependencies (imports), test imports, and test
# dependencies (imports made by test imports).  You can recurse further by
# setting TESTIMPORTS to an integer greater than one, or to skip test
# dependencies, set TESTIMPORTS to 0 or a negative integer.

: "${TESTIMPORTS:=1}"

lsdep_impl__ () {
    local txtestimps='{{range $v := .TestImports}}{{print . "\n"}}{{end}}'
    local txdeps='{{range $v := .Deps}}{{print . "\n"}}{{end}}'

    {
        go list -f "${txtestimps}${txdeps}" "$@"
        if [[ -n "${TESTIMPORTS}" ]] && [[ "${TESTIMPORTS:-1}" -gt 0 ]]
        then
            go list -f "${txtestimps}" "$@" |
            sort | uniq |
            comm -23 - <(go list std | sort) |
                TESTIMPORTS=$((TESTIMPORTS - 1)) xargs bash -c 'lsdep_impl__ "$@"' "$0"
        fi
    } |
    sort | uniq |
    comm -23 - <(go list std | sort)
}
export -f lsdep_impl__

lsdep_impl__ "$@"
英文:

> if the following isn't a stack trace what is it?

It is the list of path where Go is looking for your missing package.

> I have no idea who is importing kafkaAvailMonitor.go

It is not "imported", just part of your sources and compiled.
Except it cannot compile, because it needs github.com/Shopify/sarama/tz/breaker, which is not in GOROOT or GOPATH.

Still, check what go list would return on your direct package, to see if kafkaAvailMonitor is mentioned.

> go list can show both the packages that your package directly depends, or its complete set of transitive dependencies.

% go list -f &#39;{{ .Imports }}&#39; github.com/davecheney/profile
[io/ioutil log os os/signal path/filepath runtime runtime/pprof]
% go list -f &#39;{{ .Deps }}&#39; github.com/davecheney/profile
[bufio bytes errors fmt io io/ioutil log math os os/signal path/filepath reflect run

You can then script go list in order to list all dependencies.
See this bash script for instance, by Noel Cower (nilium)

#!/usr/bin/env bash
# Usage: lsdep [PACKAGE...]
#
# Example (list github.com/foo/bar and package dir deps [the . argument])
# $ lsdep github.com/foo/bar .
#
# By default, this will list dependencies (imports), test imports, and test
# dependencies (imports made by test imports).  You can recurse further by
# setting TESTIMPORTS to an integer greater than one, or to skip test
# dependencies, set TESTIMPORTS to 0 or a negative integer.

: &quot;${TESTIMPORTS:=1}&quot;

lsdep_impl__ () {
	local txtestimps=&#39;{{range $v := .TestImports}}{{print . &quot;\n&quot;}}{{end}}&#39;
	local txdeps=&#39;{{range $v := .Deps}}{{print . &quot;\n&quot;}}{{end}}&#39;

	{
		go list -f &quot;${txtestimps}${txdeps}&quot; &quot;$@&quot;
		if [[ -n &quot;${TESTIMPORTS}&quot; ]] &amp;&amp; [[ &quot;${TESTIMPORTS:-1}&quot; -gt 0 ]]
		then
			go list -f &quot;${txtestimps}&quot; &quot;$@&quot; |
			sort | uniq |
			comm -23 - &lt;(go list std | sort) |
				TESTIMPORTS=$((TESTIMPORTS - 1)) xargs bash -c &#39;lsdep_impl__ &quot;$@&quot;&#39; &quot;$0&quot;
		fi
	} |
	sort | uniq |
	comm -23 - &lt;(go list std | sort)
}
export -f lsdep_impl__

lsdep_impl__ &quot;$@&quot;

答案3

得分: 9

我只想在这里提一下,go mod why也可以帮助你。无论如何,你不能获取和显示整个依赖树。但是你可以追溯一个子依赖的单个分支,直到其根依赖。

示例:

$ go mod why github.com/childdep
# github.com/childdep
github.com/arepo.git/service
github.com/arepo.git/service.test
github.com/anotherrepo.git/mocks
github.com/childdep

这意味着,你最终在anotherrepo.git/mocks中导入了childdep

英文:

I just want to mention here that go mod why can also help. Anyway you cannot get and display the whole tree. But you can trace back one single branch of a child dependency until its parent root.

Example:

$ go mod why github.com/childdep
# github.com/childdep
github.com/arepo.git/service
github.com/arepo.git/service.test
github.com/anotherrepo.git/mocks
github.com/childdep

That means, you have imported 'childdep' finally in 'anotherrepo.git/mocks'.

答案4

得分: 7

可以尝试使用这个链接:https://github.com/vc60er/deptree

 redis git:(master) go mod graph | deptree -d 3
package: github.com/go-redis/redis/v9
dependence tree:

┌── github.com/cespare/xxhash/v2@v2.1.2
├── github.com/dgryski/go-rendezvous@v0.0.0-20200823014737-9f7001d12a5f
├── github.com/fsnotify/fsnotify@v1.4.9
│    └── golang.org/x/sys@v0.0.0-20191005200804-aed5e4c7ecf9
├── github.com/nxadm/tail@v1.4.8
│    ├── github.com/fsnotify/fsnotify@v1.4.9
│    │    └── golang.org/x/sys@v0.0.0-20191005200804-aed5e4c7ecf9
│    └── gopkg.in/tomb.v1@v1.0.0-20141024135613-dd632973f1e7
├── github.com/onsi/ginkgo@v1.16.5
│    ├── github.com/go-task/slim-sprig@v0.0.0-20210107165309-348f09dbbbc0
│    │    ├── github.com/davecgh/go-spew@v1.1.1
│    │    └── github.com/stretchr/testify@v1.5.1
│    │         └── ...
英文:

can try this https://github.com/vc60er/deptree

 redis git:(master) go mod graph | deptree -d 3
package: github.com/go-redis/redis/v9
dependence tree:

┌── github.com/cespare/xxhash/v2@v2.1.2
├── github.com/dgryski/go-rendezvous@v0.0.0-20200823014737-9f7001d12a5f
├── github.com/fsnotify/fsnotify@v1.4.9
│    └── golang.org/x/sys@v0.0.0-20191005200804-aed5e4c7ecf9
├── github.com/nxadm/tail@v1.4.8
│    ├── github.com/fsnotify/fsnotify@v1.4.9
│    │    └── golang.org/x/sys@v0.0.0-20191005200804-aed5e4c7ecf9
│    └── gopkg.in/tomb.v1@v1.0.0-20141024135613-dd632973f1e7
├── github.com/onsi/ginkgo@v1.16.5
│    ├── github.com/go-task/slim-sprig@v0.0.0-20210107165309-348f09dbbbc0
│    │    ├── github.com/davecgh/go-spew@v1.1.1
│    │    └── github.com/stretchr/testify@v1.5.1
│    │         └── ...

答案5

得分: 2

上面的答案仍然没有显示给我一个依赖树,所以我花时间编写了一个Python脚本来完成我需要的功能 - 希望能帮助其他人。

上面的解决方案(像go list提出的其他解决方案)的问题在于它只告诉我顶层。它们不会“遍历树”。这是我得到的输出 - 它对我来说并没有比go build给我的更多帮助。

.../npd/auth/
   .../mon/mlog
   .../auth/service

这是我想要的结果 - 我知道auth是有问题的(顶部),breaker也是有问题的(底部),但我不知道中间有什么 - 我下面的脚本给我这个输出。

.../npd/auth/
	.../npd/auth/service				
		.../npd/auth/resource
			.../npd/auth/storage
			   .../npd/middleware
				  .../npd/metrics/persist
					.../npd/kafka
						.../vendor-library/src/github.com/Shopify/sarama
							.../vendor-library/src/github.com/Shopify/sarama/vz/breaker

我的Python脚本:


    import subprocess
    import os
    
    folder_locations=['.../go/src','.../vendor-library/src']
    
    def getImports(_cwd):
        #When the commands were combined they overflowed the bugger and I couldn't find a workaround
        cmd1 = ["go", "list", "-f", " {{.ImportPath}}","./..."]
        cmd2 = ["go", "list", "-f", " {{.Imports}}","./..."]
    
        process = subprocess.Popen(' '.join(cmd1), cwd=_cwd,shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
    
        out1, err = process.communicate()
    
        process = subprocess.Popen(' '.join(cmd2), cwd=_cwd,shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
    
        out2, err = process.communicate()
        out2clean=str(out2).replace("b'","'").replace('[','').replace(']','').replace("'","'")
    
        return str(out1).split('\n'),out2clean.split('\n')
    
    def getFullPath(rel_path):
        for i in folder_locations:
            if os.path.exists(i+'/'+rel_path):
                return i+'/'+rel_path
        return None
    
    def getNextImports(start,depth):
    
        depth=depth+1
        indent = '\t'*(depth+1)
    
        for i,val in enumerate(start.keys()):
    
            if depth==1:
                print (val)
    
            out1,out2=getImports(val)
    
            noDeps=True
            for j in out2[i].split(' '):
                noDeps=False
        
                _cwd2=getFullPath(j)
                new_tree = {_cwd2:[]}
                not_exists = (not _cwd2 in alltmp)
        
                if not_exists:
                    print(indent+_cwd2)
                    start[val].append(new_tree)
                    getNextImports(new_tree,depth)
                    alltmp.append(_cwd2)
    
            if noDeps:
                print(indent+'No deps')
    
    _cwd = '/Users/.../npd/auth'
    
    alltmp=[]
    start_root={_cwd:[]}
    getNextImports(start_root,0)
英文:

The above answer still doesn't show me a dependency tree so I've taken the time to write a Python script to do what I need - hopefully that helps other people.

The issue with the above solution (the others proposed like go list) is that it only tells me the top level. They don't "traverse the tree." This is the output I get - which doesn't help any more than what go build gives me.

.../npd/auth/
   .../mon/mlog
   .../auth/service

This is what I'm trying to get - I know that auth is broken (top) and that breaker is broken (bottom) from go build but I have no idea what's in between - my script below gives me this output.

.../npd/auth/
	.../npd/auth/service				
		.../npd/auth/resource
			.../npd/auth/storage
			   .../npd/middleware
				  .../npd/metrics/persist
					.../npd/kafka
						.../vendor-library/src/github.com/Shopify/sarama
							.../vendor-library/src/github.com/Shopify/sarama/vz/breaker

My Python script:

<!-- language: python -->

import subprocess
import os
folder_locations=[&#39;.../go/src&#39;,&#39;.../vendor-library/src&#39;]
def getImports(_cwd):
#When the commands were combined they overflowed the bugger and I couldn&#39;t find a workaround
cmd1 = [&quot;go&quot;, &quot;list&quot;, &quot;-f&quot;, &quot; {{.ImportPath}}&quot;,&quot;./...&quot;]
cmd2 = [&quot;go&quot;, &quot;list&quot;, &quot;-f&quot;, &quot; {{.Imports}}&quot;,&quot;./...&quot;]
process = subprocess.Popen(&#39; &#39;.join(cmd1), cwd=_cwd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out1, err = process.communicate()
process = subprocess.Popen(&#39; &#39;.join(cmd2), cwd=_cwd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out2, err = process.communicate()
out2clean=str(out2).replace(&quot;b&#39;&quot;,&#39;&#39;).replace(&#39;[&#39;,&#39;&#39;).replace(&#39;]&#39;,&#39;&#39;).replace(&quot;&#39;&quot;,&#39;&#39;)
return str(out1).split(&#39;\\n&#39;),out2clean.split(&#39;\\n&#39;)
def getFullPath(rel_path):
for i in folder_locations:
if os.path.exists(i+&#39;/&#39;+rel_path):
return i+&#39;/&#39;+rel_path
return None
def getNextImports(start,depth):
depth=depth+1
indent = &#39;\t&#39;*(depth+1)
for i,val in enumerate(start.keys()):
if depth==1:
print (val)
out1,out2=getImports(val)
noDeps=True
for j in out2[i].split(&#39; &#39;):
noDeps=False
_cwd2=getFullPath(j)
new_tree = {_cwd2:[]}
not_exists = (not _cwd2 in alltmp)
if not_exists:
print(indent+_cwd2)
start[val].append(new_tree)
getNextImports(new_tree,depth)
alltmp.append(_cwd2)
if noDeps:
print(indent+&#39;No deps&#39;)
_cwd = &#39;/Users/.../npd/auth&#39;
alltmp=[]
start_root={_cwd:[]}
getNextImports(start_root,0)

huangapple
  • 本文由 发表于 2017年6月13日 20:07:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/44521071.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定