继续使用分发包的同时迁移到Go模块

unguejic  于 2023-02-20  发布在  Go
关注(0)|答案(4)|浏览(105)

我有一个基于GOPATH的项目,目前在Fedora上构建如下:

sudo dnf install golang-etcd-bbolt-devel golang-x-sys-devel golang-x-text-devel
GOPATH=$HOME/go:/usr/share/gocode go build

我的项目(gonzofilter)实现了一个命令行实用程序,因此源文件位于主包中(即它们有package main声明)。
在Fedora 34及以后的版本中,Go语言似乎取消了对构建GOPATH风格项目的支持,人们不得不使用Go语言的模块:

go build
go: cannot find main module; see 'go help modules'

Go语言的这篇博文在某种程度上涵盖了我的情况(-〉“没有依赖管理器”),但它没有明确提到如何处理主包项目或发行版提供的依赖。
那么,如何迁移这样的项目呢?
我该如何让Go语言/go mod tidy/usr/share/gocode下查找我的依赖项呢?

**编辑:**准确地说:Fedora 34自带的Go语言1.16只是将GO111MODULE的默认值从auto修改为on,因此,用户仍然可以通过设置GO111MODULE=auto来恢复原来的行为。

然而,Golang的开发者们已经明确表示,他们希望在Go语言1.17中放弃对GOPATH风格项目的支持:
我们计划在Go语言1.17中放弃对GOPATH模式的支持,换句话说,Go语言1.17将忽略GO 111 MODULE,如果你的项目不是在模块感知模式下构建的,现在是时候迁移了。

**更新2023-02-19:**截至Go 1.19.5(Fedora 37),GO111MODULE=off仍然适用于构建GOPATH风格(即非模块化)的项目。

export GOPATH=$HOME/go:/usr/share/gocode GOPROXY=off GO111MODULE=off
go build helloworld.go

显然,Go团队调整了他们的GO 11 MODULE弃用计划,没有了targeting any new future release for removal, yet

8yoxcaq7

8yoxcaq71#

您可以在生成的mod文件中使用replace关键字显式定义引用本地模块。
replace packagename => /usr/share/gcode

esyap4oy

esyap4oy2#

理想情况下,发行版应该将安装的依赖项打包到与GOPROXY协议兼容的布局中,然后您就可以适当地设置GOPROXY并运行go mod tidy来使用安装的依赖项。
然而,据我所知,目前还没有发行版真正提供GOPROXY树。
下一个最好的选择是使用replace指令自己连接替换。go mod tidy已经知道在replace指令中找到的版本比其他版本更受欢迎,所以它应该满足以下要求:

go mod init github.com/gsauthof/gonzofilter

go mod edit -replace go.etcd.io/bbolt=/usr/share/gocode/src/go.etcd.io/bbolt
go mod edit -replace golang.org/x/sys=/usr/share/gocode/src/golang.org/x/sys
go mod edit -replace golang.org/x/text=/usr/share/gocode/src/golang.org/x/text

go mod tidy

但是,注意replace指令要求目标包含一个显式的go.mod文件,因此只有当您的依赖项已经包含显式的go.mod文件,或者您的发行版打包程序已经将它们作为补丁添加以支持此用例时,此指令才起作用。
请注意,使用这些replace指令,您的构建将无法随时间推移而重现或重复:如果你改变了你的依赖的发行版安装的版本,那么go build的含义也会悄悄地改变以使用那些不同的(并且可能不兼容!)版本。
因此我建议您不要这样做,而是使用go get和/或go mod tidy从上游或从公共模块代理(如proxy.golang.org)获取您想要的特定模块版本。
如果您担心上游篡改,请注意Go语言项目的go命令的official binary distribution--以及从原始源代码构建的go命令--在默认情况下会根据www.example.com上的可审计公共数据库自动验证已下载模块的校验和https://sum.golang.org;然而,Fedora发行版的go命令在默认情况下禁用校验和数据库。

l3zydbqr

l3zydbqr3#

将这样的GOPATH项目迁移到Go语言模块感知的项目的一种方法如下:
首先,手动创建具有直接依赖项的go.mod

module github.com/gsauthof/gonzofilter

go 1.15

require (
    go.etcd.io/bbolt  v1.3.5
    golang.org/x/sys  v0.0.0-20210423185535-09eb48e85fd7
    golang.org/x/text v0.3.5
)

我从当前系统中获取了最低版本:

$ rpm -q golang-etcd-bbolt-devel golang-x-sys-devel golang-x-text-devel golang-bin
golang-etcd-bbolt-devel-1.3.5-2.fc33.noarch
golang-x-sys-devel-0-0.39.20210123git9b0068b.fc33.noarch
golang-x-text-devel-0.3.5-1.fc33.noarch
golang-bin-1.15.8-1.fc33.x86_64

现在的问题是,我们不能简单地告诉模块感知的Go语言工具在它们所在的文件系统路径/usr/share/gocode/src下查找这些模块。
有一个replace指令可以添加到我们的go.mod文件中,为单个依赖项设置查找路径,例如:

replace (
    go.etcd.io/bbolt  => /usr/share/gocode/src/go.etcd.io/bbolt
    golang.org/x/sys  => /usr/share/gocode/src/golang.org/x/sys
    golang.org/x/text => /usr/share/gocode/src/golang.org/x/text
)

但是,go build仍然不使用来自/usr/share/gocode的 * 间接 * 依赖项,例如,由golang-x-text-devel依赖的包提供的依赖项。在这个例子中,go build确实找到了/usr/share/gocode/src/golang.org/x/text/go.mod,但该文件不包含任何replace指令。
因此,这失败了:

$ GOPROXY=off go build -mod=readonly
go: golang.org/x/text@v0.3.6 requires
    golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e: module lookup disabled
       by GOPROXY=off

要解决这个问题,我们必须为所有间接依赖项添加replace指令行。
当然,手动执行此操作既繁琐又容易出错。
因此,我们可以使用一个小shell一行程序自动执行此操作:
我们从go.mod文件开始:

module github.com/gsauthof/gonzofilter

go 1.15

require (
    go.etcd.io/bbolt v1.3.5
    golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7
    golang.org/x/text v0.3.6
)

replace (
//replace-this
)

然后,以下定点迭代将添加依赖项,直到生成成功:

while true; do
    GOPROXY=off go build -mod readonly 2> t
    if [ $? -eq 1 ] && grep 'module lookup disabled\|missing go.sum entry' t >/dev/null; then
        x=$(grep 'disabled\|missing' t |  tr ' \t' '\n'   | grep '@' | head -n1 | cut -d'@' -f1 )
        echo "Adding $x"
        sed -i "s@^\(//replace-this\)@\t$x => /usr/share/gocode/src/$x\n\1@" go.mod
        continue
    fi
    break
done

这将为该示例项目生成以下require指令:

replace (
    go.etcd.io/bbolt => /usr/share/gocode/src/go.etcd.io/bbolt
    golang.org/x/sys => /usr/share/gocode/src/golang.org/x/sys
    golang.org/x/text => /usr/share/gocode/src/golang.org/x/text
    golang.org/x/tools => /usr/share/gocode/src/golang.org/x/tools
    github.com/yuin/goldmark => /usr/share/gocode/src/github.com/yuin/goldmark
    golang.org/x/mod => /usr/share/gocode/src/golang.org/x/mod
    golang.org/x/net => /usr/share/gocode/src/golang.org/x/net
    golang.org/x/sync => /usr/share/gocode/src/golang.org/x/sync
    golang.org/x/xerrors => /usr/share/gocode/src/golang.org/x/xerrors
    golang.org/x/crypto => /usr/share/gocode/src/golang.org/x/crypto
    golang.org/x/term => /usr/share/gocode/src/golang.org/x/term
//replace-this
)
x759pob2

x759pob24#

另一种将Go语言指向系统级可用依赖项的方法是利用Go语言的模块供应支持:这意味着通过符号链接模块文件路径,创建一个最小的vendor/modules.txt并用-mod vendor编译。
因此,对于-mod vendor,Go build不会尝试下载任何需要的模块,而是在vendor/目录中查找它们。
人们可能会尝试创建一个从vendor/指向/usr/share/gocode/src的符号链接(所有golang-...-devel发行版都安装在/usr/share/gocode/src中),但这并不起作用,因为-mod vendor还需要另一个模块描述文件,即vendor/modules.txt
因此,除了一些更具体的符号链接,我们必须创建一个适当的modules.txt当使用vendoring功能:
首先,创建所有子级符号链接:

awk -vvd=wendor 'BEGIN {
    PROCINFO["sorted_in"]="@ind_str_asc";
    system("mkdir -p "vd)
}
func push_mod(pkg) {
    if (!seen[pkg]) {
        ARGV[ARGC++]="/usr/share/gocode/src/"pkg"/go.mod";
        seen[pkg]=1;
    }
    sub("/.+$", "", pkg);
    xs[pkg]=1;
}
/^require[^(]+$/ {
    push_mod($2);
    next
}
/^require/ {
    inb=1;
    next
}
/^\)/ {
    inb=0;
    next
}
inb { 
    push_mod($1);
}
END {
    for (i in xs) {
        print i;
        system("ln -sfn /usr/share/gocode/src/"i" "vd"/"i)
    }
}' go.mod

然后,要创建vendor/modules.txt

awk -vvd=wendor 'BEGIN {
    fn=vd"/modules.txt"
}
/^require/ {
    inb=1;
    next
}
/^\)/ {
    inb=0;
    next
}
inb {
    printf("# %s %s\n## explicit\n%s\n", $1, $2, $1) > fn 
}' go.mod

执行这些命令后,示例项目的vendor目录如下所示:

$ ls -l vendor
total 16
lrwxrwxrwx. 1 juser juser  32 2021-04-25 11:12 github.com -> /usr/share/gocode/src/github.com
lrwxrwxrwx. 1 juser juser  32 2021-04-25 11:12 go.etcd.io -> /usr/share/gocode/src/go.etcd.io
lrwxrwxrwx. 1 juser juser  32 2021-04-25 11:12 golang.org -> /usr/share/gocode/src/golang.org
-rw-r--r--. 1 juser juser 195 2021-04-25 11:13 modules.txt
$ cat vendor/modules.txt
# go.etcd.io/bbolt v1.3.5
## explicit
go.etcd.io/bbolt
# golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7
## explicit
golang.org/x/sys
# golang.org/x/text v0.3.5
## explicit
golang.org/x/text

然而go.mod是非常小的,即它不包含任何replace指令:

module github.com/gsauthof/gonzofilter

go 1.15

require (
    go.etcd.io/bbolt  v1.3.5
    golang.org/x/sys  v0.0.0-20210423185535-09eb48e85fd7
    golang.org/x/text v0.3.5
)

这就足以让

GOPROXY=off go build -mod vendor

成功。
请注意,间接依赖项(如golang.org/x/tools)未在modules.txt中列出,但是,它们仍然可以通过vendor/获得,因为在创建指向顶层目录的符号链接时,我们从所需模块开始传递遍历了所有go.mod文件。
这种模式的独特魅力在于,用户不必在go.mod文件中使用replace指令,这意味着go.mod可以保持相当通用,基本上只需要在build命令中添加-mod vendor开关即可。

相关问题