Golang中go.mod在依赖项涉及构建标签时如何处理

Golang中go.mod在依赖项涉及构建标签时如何处理 希望这不是一个愚蠢的问题。我一直在用 Go 开发一个解释器。其核心构建的依赖非常少。我使用构建标签来获得具有更多功能的可执行文件,这也意味着更多的依赖。例如:

# 创建一个仅包含少量依赖的版本(如 peg-parser、终端库等)
go build

# 创建一个具有更多功能和更多依赖的可执行文件
go build -tags "http,bcrypt,psql,html,json"

# 更多示例,依赖完全不同
go build -tags "gtk,sqlite"
go build -tags "raylib,sxml"

对于一个只想体验解释器核心功能的用户来说,要求所有这些(以及更多)依赖是完全没有意义的。例如,如果用户想进行 Web 开发,要求安装 gtk、raylib 等不相关的库也是没有意义的。在没有 Go 模块的情况下,你可以在构建时选择你需要的功能,并且只需要“go get”你需要的依赖。

我想将项目升级到 go.mod,但我不知道如何操作。我没有找到任何方法来定义依赖于构建标签的模块,或者以其他方式解决这个问题。如果我运行 go mod tidy,它会下载所有可能用于编译可执行文件的模块。我想我可以在编译时通过编程方式生成 go.mod,但我怀疑肯定已经有某种方法可以解决这个问题,因为这可能不是唯一需要非静态依赖列表的用例。


更多关于Golang中go.mod在依赖项涉及构建标签时如何处理的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

NobbZ:

对你来说可能只是几秒钟的等待,但对其他人来说,可能会变成几分钟甚至几小时。

这就是我问这个问题的原因。

更多关于Golang中go.mod在依赖项涉及构建标签时如何处理的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


christophberger:

用户或许可以接受多等待几秒钟

对你来说可能只是几秒钟的等待,但对其他人来说,可能会变成几分钟甚至几小时。

对你来说可能只是等待,但对其他人来说,这可能是一个显著的成本因素。

并非每个人都有高带宽,也并非每个人都支付固定费用。

你好 @refaktor

最小依赖集和完整依赖集在下载时间或体积上的差异有多大?考虑到依赖项只需下载一次,之后就会缓存在本地,用户或许可以接受多花几秒钟来下载依赖项。

如果你能将各种额外功能拆分到独立的模块中,或许可以利用 Go 1.17 新增的惰性模块加载功能。

感谢你们的回复。

差异确实很大,因为Rye(这门语言)可以包含各种可选的绑定,这些绑定也可能非常庞大,是难以编译的包,而特定用户可能永远都不需要它们。例如GTK、Raylib、Cayley(图数据库),这还只是少数几个例子。如果夸张一点说,这就像你需要为你的操作系统安装所有可能的程序,即使你只需要电子邮件和浏览器。

问题不仅仅是时间,还有平台特定的问题。一个具体的例子是,我在Ubuntu上开发,那里有我需要的所有特定Go包,但当一位使用Mac的同事尝试构建时,他在安装正确的GTK、Raylib(游戏引擎)时遇到了真正的问题,而他对此毫无兴趣,因为他只是想尝试用它做一个FizzBuzz示例。

惰性模块加载听起来很有希望,任何在编译时确定的动态模块加载都可能有所帮助。

例如,如果人们有平台特定的依赖项,他们是如何使用Go.mod的?

我不确定是否完全理解了问题的所有方面,但似乎所有真正庞大的部分都是非Go的产物(GTK、Raylib、Cayley),而它们各自的Go绑定包可能根本不大(如果我说错了请纠正我)。

如果用户在没有这些扩展的情况下编译Rye,他们就不必安装任何GTK、Raylib或Cayley,对吗?他们只需要等待Go命令下载一些不必要的绑定包,这些包会在模块缓存中占用一些空间,但不会造成阻碍。

也许你想运行一个测试,看看实际下载了多少Go代码。运行 go clean -modcache 来清除本地模块缓存,然后在你的主模块的 go.mod 文件上运行 go mod tidy,接着运行类似 du -sm $(go env GOPATH)/pkg/mod/cache 的命令来获取下载文件的大小(磁盘上)。如果这个大小看起来太大,让人不舒服,那么将项目拆分成单独的模块并利用延迟加载可能会有所帮助。(免责声明:我还没有详细检查延迟加载的行为。我的假设纯粹依赖于我之前链接的文档。)


编辑补充:

  1. Cayley 实际上是用 Go 写的。是我的错。
  2. 我非常喜欢 Rye 的理念。令人耳目一新的不同……

嗯……我没想到,如果不需要非Go的构件,你可以只安装Go绑定。目前这或许可以接受,尽管我仍然更倾向于通过几次go get就能完成的更简洁的初始安装。

我现在要测试一下,进行一次完整安装实际会下载多少内容。谢谢你的提示!

长远来看,这门语言预计会绑定越来越多的东西,所以要求从一开始就安装所有绑定(即使只是所有绑定中的Go部分)并不是一个障碍。

我想我需要制作一些构建脚本,并在编译前根据用户输入生成go.mod,这样就会失去简单的go build -flags "pgsql,httpd"……我真的很喜欢那种简洁性。而且,如果我在编译前自己生成go.mod,我也不想重复造轮子。

仔细想想,go mod tidy不像go build那样处理-flags,这很奇怪,不是吗?

谢谢你对Rye的评论 😊……用Go来写它确实是一个持续带来回报的好选择,从Go运行的所有平台、简单的交叉编译、所有的库来看都是如此,而且Go是一门非常棒/风险低的语言,所以如果你有一些“热点”代码,直接用Go重写那部分并调用它完全没有问题。而这对于一个高级程序员来说,是无法期望他用c/c++(例如)来做到的。

这是一个很好的问题,涉及到Go模块系统如何处理可选依赖的常见痛点。目前,Go的go.mod文件本身并不直接支持基于构建标签的条件依赖声明。go mod tidy会分析整个代码库(包括所有构建标签组合)并添加所有可能的依赖。

不过,有几种实践方法可以解决这个问题:

方法1:使用//go:build约束注释依赖导入

这是最标准的做法。你可以在导入语句前添加构建约束,这样go mod tidy只会为当前激活的标签解析依赖。

示例项目结构:

myapp/
├── go.mod
├── main.go
├── http_feature.go
└── gtk_feature.go

main.go (基础功能):

package main

import (
    "fmt"
    "github.com/yourname/peg-parser" // 核心依赖
)

func main() {
    fmt.Println("核心解释器")
}

http_feature.go (HTTP功能,需要时编译):

//go:build http

package main

import (
    "net/http"
    "github.com/gorilla/mux" // 仅当-tags "http"时引入
)

func init() {
    // HTTP相关初始化
}

gtk_feature.go (GTK功能,需要时编译):

//go:build gtk

package main

import (
    "github.com/gotk3/gotk3/gtk" // 仅当-tags "gtk"时引入
)

func init() {
    // GTK相关初始化
}

操作流程:

# 1. 初始状态,只有核心依赖
go mod init myapp
go mod tidy  # 只添加peg-parser

# 2. 当需要HTTP功能时,使用标签编译
go build -tags http
go mod tidy  # 此时会添加gorilla/mux

# 3. 如果要清理不需要的依赖,可以:
go mod tidy -tags="http"  # 只保留核心和http依赖

方法2:使用工具脚本管理不同配置

对于更复杂的场景,可以创建不同的构建配置文件:

build_core.sh:

#!/bin/bash
# 清理并只添加核心依赖
go mod tidy
go build -o myapp_core

build_full.sh:

#!/bin/bash
# 添加所有可能的依赖
go mod tidy -tags="http,bcrypt,psql,html,json,gtk,sqlite,raylib,sxml"
go build -tags="http,bcrypt,psql,html,json,gtk,sqlite,raylib,sxml" -o myapp_full

方法3:使用工作区(Go 1.18+)

Go工作区允许你在一个目录中管理多个模块,可以为核心功能和扩展功能创建不同的模块:

目录结构:

myproject/
├── go.work
├── core/
│   ├── go.mod
│   └── main.go
├── http-extension/
│   ├── go.mod
│   └── http.go
└── gtk-extension/
    ├── go.mod
    └── gtk.go

go.work:

go 1.18

use (
    ./core
    ./http-extension
    ./gtk-extension
)

core/go.mod:

module myapp/core

go 1.18

require github.com/yourname/peg-parser v1.0.0

http-extension/go.mod:

module myapp/http

go 1.18

require github.com/gorilla/mux v1.8.0

方法4:使用构建约束文件

创建特定标签的.go文件,这些文件只在对应标签激活时被编译:

deps_http.go:

//go:build http

package main

import _ "github.com/gorilla/mux"
import _ "github.com/lib/pq"
// HTTP相关依赖

deps_gtk.go:

//go:build gtk

package main

import _ "github.com/gotk3/gotk3/gtk"
import _ "modernc.org/sqlite"
// GTK相关依赖

在实际操作中,当用户只需要核心功能时,他们可以:

go mod tidy  # 只解析当前标签的依赖
go build     # 只构建核心功能

当需要特定功能时:

go mod tidy -tags="http,psql"
go build -tags="http,psql"

这种方法虽然需要用户理解构建标签的概念,但它保持了依赖的明确性和可管理性。go.mod文件会包含所有曾经被添加的依赖,但通过go mod tidy配合标签,可以确保只有当前需要的依赖被下载和验证。

回到顶部