在 Haskell 开发中,使用 Nix 进行构建能带来极大的便利,尤其是涉及跨平台交叉编译时。本文记录了将一个基于 haskell.nix 的项目(haskell-periodic)配置为支持 Windows (mingwW64) 交叉编译的全过程。
1. 调整构建脚本 (Makefile)
原始的 Makefile 1 主要是针对 Linux (musl) 环境设计的,缺少对 Windows 可执行文件后缀(.exe)和特定工具链的支持。
为了支持 Windows 交叉编译,我们需要在 Makefile 中增加对 mingwW64 平台的分支判断,主要修改点如下:
- 定义 Strip 工具 :Linux 下通常使用 standard
strip,但在交叉编译 Windows 版本时,必须使用对应的x86_64-w64-mingw32-strip。 - 处理文件后缀 :Windows 可执行文件需要
.exe后缀,而 Linux 不需要。 - 修改输出目标:构建目标路径需要动态追加这个后缀。
修改后的 Makefile 片段:
Makefile
makefile
# ... 之前的定义 ...
ifeq ($(PLATFORM),mingwW64)
# Windows 平台特定配置
STRIP = x86_64-w64-mingw32-strip
COMPILER ?= ghc9122
EXT = .exe
else
# ... 其他平台配置 ...
endif
# ...
# 针对每个组件,确保输出包含后缀 $(EXT)
periodic:
PKG=periodic-client-exe make dist/$(PLATFORM)/$@$(EXT)
# ... 其他组件类似 ...
通过运行 make PLATFORM=mingwW64,构建系统现在可以正确识别目标平台并尝试生成 .exe 文件。
2. 遭遇链接错误:UCRT 与 MSVCRT 冲突
在调整完 Makefile 并尝试构建后,我们在链接阶段(Linking)遇到了如下错误:
Plaintext
vbnet
multiple definition of `__getmainargs'
multiple definition of `_onexit'
multiple definition of `_amsg_exit'
...
问题分析:
这是一个经典的 Windows C 运行时冲突问题。现代的 GHC 和 Nix 工具链倾向于使用 UCRT (Universal C Runtime),但某些依赖或 GHC 内部组件可能引入了旧版的 MSVCRT (Microsoft Visual C Runtime)。当链接器同时遇到这两套库的符号定义时,就会报"多重定义"错误。
3. 修正 Nix 配置 (default.nix)
为了解决这个问题,我们需要通过 GHC 传递参数给链接器,告诉它允许符号的多重定义(让链接器自动选择其中一个)。
我们需要修改 default.nix 2222,利用 haskell.nix 提供的模块系统 (modules) 注入特定的 configureFlags。
修改后的 default.nix 关键部分:
Nix
ini
# ... 头部引入保持不变 ...
in pkgs.haskell-nix.cabalProject {
# ... src, index-state 等配置 ...
modules = [(
{pkgs, ...}: {
enableProfiling = enableProfiling;
# --- Windows 交叉编译修复 ---
# 针对 Windows 平台,添加链接器参数以解决 UCRT/MSVCRT 冲突
packages.periodic-client-exe.configureFlags = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isWindows [
"--ghc-option=-optl-Wl,--allow-multiple-definition"
];
# 如果 Server 端也需要在 Windows 构建,同样应用该修复
packages.periodic-server.configureFlags = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isMusl [
# ... 保留原有的 Linux/Musl 链接库配置 ...
"--ghc-option=-optl=-lssl"
# ...
] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isWindows [
"--ghc-option=-optl-Wl,--allow-multiple-definition"
];
})];
}
这里使用了 pkgs.lib.optionals pkgs.stdenv.hostPlatform.isWindows 来确保这些标志仅在编译 Windows 版本时生效,不会影响 Linux 版本的构建。
4. 最终结果
完成上述修改后,再次执行构建命令:
Bash
ini
make PLATFORM=mingwW64
构建过程顺利通过链接阶段,成功在 dist/mingwW64/ 目录下生成了 Windows 可执行文件(如 periodic.exe)。通过这种方式,我们利用 Nix 强大的交叉编译能力,在不离开 Linux 开发环境的情况下,优雅地交付了 Windows 二进制文件。