nix 构建 go 项目时“go: cannot find main module”错误源于构建上下文丢失 go.mod 目录结构,需确保 src 保留顶层目录、正确设置 subdir、vendorsha256 和 buildgomodule,且 go.work 需手动暴露工作区根。

go: cannot find main module 是 Nix 没把项目根当工作区
这不是 Go 报错,是 Nix 构建上下文丢失了 go.mod 所在目录结构。Nix 默认用 src 输出的 tarball 启动构建,而 tarball 若被 cleanSourceWith 清掉顶层目录或忽略 go.mod,Go 就完全找不到模块根。
必须确保 src 保留项目最外层目录:不要写 ./.,改用 pkgs.nix-gitignore.gitignoreSource [] ./. ;若 go.mod 在子目录(如 cmd/app/go.mod),则 buildGoModule 必须显式传入 subdir = "cmd/app"。
-
keepFiles = [ "go.mod" "go.sum" ]是cleanSourceWith的硬性要求,漏掉就必现此错 - 用
nix-build -E 'with import <nixpkgs> {}; (import ./default.nix).myApp'</nixpkgs>测试前,先手动解压result/src看目录树是否完整 - CI 中若用
actions/checkout,必须设fetch-depth: 0,否则.git缺失导致某些工具(如golangci-lint)生成的版本号变动,间接影响go.sum校验
buildGoModule 和 buildGoPackage 别选错
只要项目有 go.mod,就只能用 buildGoModule。它内置 vendor 解析、go mod download 预热、go.sum 校验,而 buildGoPackage 是为老式 GOPATH 项目设计的,会跳过 vendor/ 直接走网络拉依赖 —— 这直接破坏可复现性。
buildGoModule 要求 vendorSha256 必填(除非显式设 vendorHash = null;),值可用 nix-prefetch-dir --unpack vendor 生成;若项目含 //go:embed,也必须用 buildGoModule,否则 embed 文件不会被打包进构建上下文。
- 错误写法:
buildGoPackage { src = ...; }→ 即使能编译,也会静默忽略vendor/ - 正确写法:
buildGoModule { src = ...; vendorSha256 = "..."; } - 若需强校验
go.sum,加doCheck = true;,但得确保所有测试能在纯 Nix 环境跑通
nix develop -c "cd $(pwd) && go build" 不是偷懒,是必要操作
Nix Flakes 默认不挂载当前目录,nix develop 进入后 PWD 是 /homeless-shelter,不是你的项目路径。直接敲 go build 必然报 go: go.mod file not found。
有两种解法:临时命令用 nix develop -c "cd $(pwd) && go build -mod=vendor";长期方案是把构建逻辑写进 flake.nix 的 packages.default,用 buildGoModule 并设 subPackages 或 subdir 显式指定入口。
- 别在
devShells里只装pkgs.go,要配pkgs.gopls且版本严格匹配(如都用go_1_22),否则 LSP 报错 - 若项目启用
cgo,buildGoModule必须传pkgs.buildPackages.stdenv.cc,否则报exec: "gcc": executable file not found in $PATH -
shell.nix里禁用with import <nixpkgs> {};</nixpkgs>,改用let pkgs = import <nixpkgs> {};</nixpkgs>,避免命名空间污染
go.work 文件不被 Nix 自动识别,得手动处理
Nix 默认只认 go.mod,对 go.work 完全无感。如果项目用了多模块工作区,go run 在 nix-shell 里会报 no Go files in directory,因为 Nix 没把工作区根目录加进 src。
解决办法是:在 shell.nix 或 flake.nix 中,把整个工作区根目录作为 src,而不是只取某个子模块目录;若用 buildGoModule,还需确认 subdir 指向的是含 main 函数的模块路径,而非工作区根。
- 常见陷阱:误把
src = ./cmd/myapp当作源码路径,结果go.work在上层目录,Nix 构建时根本读不到 - 验证方法:进
nix-shell后手动执行go work use ./...,看是否成功;失败说明工作区路径没暴露给 Nix -
go.work场景下,vendorSha256应基于整个工作区的vendor/目录计算,不是单个模块的
src、每一个 subdir、每一处 vendorSha256,都是你向 Nix 明确声明“这就是我的代码疆域”的动作。漏掉任何一处,构建就变成猜谜。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











