Golang中如何同时使用同一依赖的不同分支版本
Golang中如何同时使用同一依赖的不同分支版本 我目前遇到了一个似乎相当独特的问题。希望有人能帮助我解决。
本质上,我正在尝试支持两个不同的项目 A 和 O,它们都是基于 E 构建的。有一个针对 E 的大型 Go 库,我们称之为 lib-E,但为了支持其他项目,它们发布了自己的库,这些库是 lib-E 的直接分支。
值得注意的是,lib-A 和 lib-O 在整个代码库中没有任何导入路径,并且 go.mod 项目定义在所有三个库中都是相同的:github.com/maintainer/lib-E。我推测这样做是为了最小化维护工作,并允许库的使用者更容易地采用即插即用的方法。
也许你现在已经开始看到我的问题了。假设我的代码库中有两个包,其中一个应该针对 lib-A 构建,另一个针对 lib-O 构建。这两个包的代码是相同的,但由于它们底层依赖的库不同,编译输出也会不同。
到目前为止,我已经尝试了几种方法,但都没有成功:
- 将两个包提取到它们自己的仓库中,每个都有自己的
go.mod,每个都包含将lib-E替换为lib-A或lib-O的replace语句。这失败了,因为我的主go.mod声明了对lib-E的直接依赖,这将覆盖模块替换。 - 将代码保留在一个仓库中,但在两个包中使用命名导入(例如
import libA "github.com/maintainer/libE"),然后在主go.mod中使用两个replace指令,分别指向libA和libO的各个仓库。这失败了,因为两个模块仍然被声明为github.com/maintainer/libE,所以我最终得到:
module declares its path as: github.com/maintainer/libE
but was required as: libA
就目前情况而言,我开始认为我试图做的事情在 Go 中是不可能的,唯一的解决方案是构建两个小二进制文件(每个都直接针对 libA 或 libO),然后在这些小的适配器二进制文件和我的主应用程序之间使用 RPC。
显然,如果可能的话,我希望避免这种大量的开销。有人能为这个问题想出更好的解决方案吗?
更多关于Golang中如何同时使用同一依赖的不同分支版本的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢你的回复,非常感谢你的意见。让我更清楚地说明一下我的动机。
看看 https://github.com/ethereum-optimism/op-geth 和 https://github.com/OffchainLabs/go-ethereum/ —— 它们都是 github.com/ethereum/go-ethereum 的分支(抱歉,我只能发布两个链接)。请注意,它们都没有修改它们的 go.mod 文件,也没有修改它们的导入路径。我只能推测,这样做是为了让任何使用者只需在他们自己的 go.mod 文件中使用 replace 关键字,就可以将其中任何一个作为原始模块的直接替代品。
我正在维护一个现有的代码库,它使用了 op-geth,现在希望通过使用 OfficialLabs 实现创建一个模块来引入对 Arbitrum 的支持。请注意,它们都指向 github.com/ethereum/go-ethereum。当只支持 op-geth 时,这不成问题,多亏了 replace。但是,当需要引入第二个使用相同导入路径的依赖项时,到目前为止这被证明是不可能的。
我已经尝试了我能想到的各种选项,比如将代码提取到单独的模块中、使用工作区、使用命名导入,我甚至不记得在这个阶段还尝试过什么。
我需要的是一个可靠的模块级重定向方法,并且不会被主模块覆盖。这在 Go 工作区中似乎是不允许的。
目前,我不确定这在 go 中是否可行,但很可能我只是没有看到显而易见的方法。
在 Go 模块系统中,确实无法直接同时使用同一模块路径的不同版本。不过,可以通过以下方法解决:
方案一:使用 replace + 不同模块路径
将分支版本发布到不同的模块路径:
// 主 go.mod
module myproject
go 1.21
require (
github.com/maintainer/lib-a v0.0.0
github.com/maintainer/lib-o v0.0.0
)
replace (
github.com/maintainer/lib-a => ./vendor/lib-a
github.com/maintainer/lib-o => ./vendor/lib-o
)
然后修改分支库的 go.mod:
// lib-a/go.mod
module github.com/maintainer/lib-a
go 1.21
// lib-o/go.mod
module github.com/maintainer/lib-o
go 1.21
方案二:使用构建标签隔离
创建带构建标签的包装器:
// +build liba
package adapter
import (
lib "github.com/maintainer/lib-e"
)
func New() lib.Interface {
return lib.New()
}
// +build libo
package adapter
import (
lib "github.com/maintainer/lib-o" // 不同的模块路径
)
func New() lib.Interface {
return lib.New()
}
方案三:使用接口抽象
定义通用接口,通过依赖注入使用不同实现:
// common/interface.go
package common
type LibInterface interface {
DoSomething() error
GetValue() string
}
// a/adapter.go
package a
import (
"github.com/maintainer/lib-a"
"myproject/common"
)
type Adapter struct {
client *lib.Client
}
func NewAdapter() common.LibInterface {
return &Adapter{
client: lib.NewClient(),
}
}
func (a *Adapter) DoSomething() error {
return a.client.DoSomething()
}
// o/adapter.go
package o
import (
"github.com/maintainer/lib-o"
"myproject/common"
)
type Adapter struct {
client *lib.Client
}
func NewAdapter() common.LibInterface {
return &Adapter{
client: lib.NewClient(),
}
}
方案四:使用工作区(Go 1.18+)
创建 go.work 文件管理多个模块:
// go.work
go 1.21
use (
./main
./lib-a-module
./lib-o-module
)
replace (
github.com/maintainer/lib-e => ./lib-a-module
)
# 构建不同版本
go work use ./lib-a-module
go build -tags liba ./cmd/a-app
go work use ./lib-o-module
go build -tags libo ./cmd/o-app
方案五:使用代理重写(高级)
设置本地代理服务器重写导入路径:
// goproxy 配置
// proxy.go
package main
import (
"net/http"
"net/http/httputil"
"strings"
)
func main() {
http.HandleFunc("/github.com/maintainer/lib-e/@v/", func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "lib-a") {
// 重定向到 lib-a
httputil.NewSingleHostReverseProxy("https://github.com/maintainer/lib-a").ServeHTTP(w, r)
} else {
// 重定向到 lib-o
httputil.NewSingleHostReverseProxy("https://github.com/maintainer/lib-o").ServeHTTP(w, r)
}
})
http.ListenAndServe(":8080", nil)
}
GOPROXY=http://localhost:8080,direct go build
最实用的方案是方案一,通过修改分支库的模块路径来区分不同版本。虽然需要维护者配合修改,但这是最符合 Go 模块设计的方式。


