如何查看嵌套 Go 依赖项的完整依赖树

How to view full dependency tree for nested Go dependencies

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

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

对于开发人员来说,修复他们只是“去得到任何东西”似乎微不足道,但我不能在发布过程中这样做——我必须找到添加依赖项的人并要求他们修复它。

我知道有一些工具可以可视化依赖关系树和其他更复杂的构建系统,但这似乎是一个非常基本的问题:有什么方法可以查看完整的依赖关系树以了解是什么导致了构建问题?

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

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

这是 Go 正在寻找你丢失的包的路径列表。

I have no idea who is importing kafkaAvailMonitor.go

它不是 "imported",只是您的源代码的一部分并已编译。
除了它不能编译,因为它需要 github.com/Shopify/sarama/tz/breaker,它不在 GOROOTGOPATH.

不过,请检查您的直接包裹上的 go list would return,看看是否提到了 kafkaAvailMonitor

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

% 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 脚本以列出所有 依赖项。
参见 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.

: "${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__ "$@"' "[=11=]"
        fi
    } |
    sort | uniq |
    comm -23 - <(go list std | sort)
}
export -f lsdep_impl__

lsdep_impl__ "$@"

以上答案仍然没有向我显示依赖关系树,因此我花时间编写了一个脚本来完成我需要的工作 - 希望对其他人有所帮助。

上述解决方案(其他建议如 go list)的问题是它只告诉我顶层。他们不会“遍历树”。这是我得到的输出 - 这比 go build 给我的帮助更大。

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

这就是我想要得到的 - 我知道 auth 已损坏(顶部)并且断路器已损坏(底部)来自 go build 但我不知道两者之间是什么 - 我下面的脚本给了我这个输出。

.../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

我的脚本

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)

使用模块时,您可以从 go mod graph 中获得所需的内容。

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.

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

这里只想提一句,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'。