Gtk Rust交叉编译(一)
引子
事情的起因是我某天想写一个软件,于是又在看诸多用户界面的框架。然后就看到了GTK和Rust这一对组合。 正好这是我一直以来想学的语言以及我一直以来想学的框架, 所以我便跟着Gtk的官方教程搭配Rust练习了一下。
虽然gtk-rs有一个非常方便的模板可以发布到Flatpak上,但是我平时主要用的还是Windows,因此我便花时间在看如何编译到Windows。
这篇博文对应的便是我的第一阶段成果,即不考虑GTK的资源文件的情况下,如何把gtk-rs文件编译成exe文件。 之后可能还会有第二阶段,来研究如何包含GTK的资源文件。
注意:
- 这篇博客仅限于在Linux开发交叉编译到Windows上。如果本身就在Windows上利用MSYS2开发或编译,则无需折腾。即便需要交叉编程,如果不是自己想折腾,特别是工程使用,建议参考现有项目。
- 这条路能不能走通我还不确定。
- 以下假设项目名称为mygtk。
- 我对于编译领域了解较少,如有错误,欢迎指出。
- 以下针对Linux x64架构编译到Windows x64架构上。如需针对32位,请自行对其中的“x86_64”、“mingw64”等处进行替换。
- 此处未提供GTK程序。可参考最简单的空窗口。
Rust的交叉编译
Rust有着非常良好的交叉编译的支持。对于一个普通的Rust项目,仅需要根据官方教程,
添加工具链即可。以Linux交叉编译到Windows为例,我只需要
rustup target add x86-64-pc-windows-gnu
(因为我是基于GNU的工具链编译的,而非MSVC)来添加工具链。
当然,除了Rust本身的工具链,我们也需要对应平台的链接器等编译工具。如果我们直接cargo build --target=x86_64-pc-windows-gnu
,会出现linker 'x86_64-w64-mingw32-gcc' not found
的错误。因此我们需要安装这个包来获得Mingw的编译工具。
之后我们即可运行cargo build --target=x86_64-pc-windows-gnu
,生成的exe文件会在target/86_64-pc-windows-gnu/
下。
Pkgconf的抱怨
但是当我们直接拿一个Gtk-rs的程序来编译后,会发现编译失败,同时看到如下的报错信息:
warning: pkg-config has not been configured to support cross-compilation.
经过翻阅文档,可以大致确定是因为缺少平台特定的头文件。于是通过查看mingw-w64-gcc
所包含的文件,可以看出,大量文件被放置于/usr/x86_64-w64-mingw32
下。因此我们只需要向pkg-config指出这个地址即可。
运行PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-w64-mingw32/ cargo build --target=x86_64-pc-windows-gnu
即可。
从MSYS2搞到头文件
但是我们很快就会发现,编译器又抱怨了。它在抱怨链接器找不到库。
可以发现我们缺少mingw-gtk4的相关文件。那我们可以从哪里搞到呢?
通过查看GTK的官方文档,可以发现如要在Windows上编译,最好的方法便是使用MSYS2。它里面提供了我们需要的包。
那如何在Linux里使用呢?
可以看到,MSYS2使用了pacman作为包管理系统。因此我们只需要用ArchLinux为底,加入MSYS2的仓库,制作Docker文件即可。
几个注意事项:
- 我们用的是脚本,因此没有交互,所以对于pacman的参数都要加上
--noconfirm
避免程序问我们yes/no
。 - 添加仓库签名比较麻烦(我懒),因此为了跳过,在每个仓库定义下都要添加
SigLevel = Optional TrustAll
或者在[option]
下添加SigLevel = Never
。 - 之前已经提到过了pkgconf的一个环境参数,这里我们需要设置另一个环境参数,让它能够找到.pc文件。
Dockerfile(其中有部分环境参数是为了使用国内镜像。我没有设置crates的镜像,请在项目下设置):
FROM docker.io/archlinux:latest
# 添加镜像
RUN sed -i '1iServer = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch' /etc/pacman.d/mirrorlist
# 添加MSYS2仓库
RUN echo -e "\nSigLevel = Never" >> /etc/pacman.conf
RUN echo -e "\n[mingw64]\nInclude = /etc/pacman.d/mirrorlist.mingw" >> /etc/pacman.conf
RUN echo -e "\n[msys]\nInclude = /etc/pacman.d/mirrorlist.msys" >> /etc/pacman.conf
RUN echo -e "Server = https://mirrors.sjtug.sjtu.edu.cn/msys2/msys/\$arch/" > /etc/pacman.d/mirrorlist.msys
RUN echo -e "Server = https://mirrors.sjtug.sjtu.edu.cn/msys2/mingw/\$repo" > /etc/pacman.d/mirrorlist.mingw
RUN pacman -Sy --noconfirm
# 安装包
RUN pacman -S --noconfirm gcc pkgconf rustup mingw-w64-gcc mingw-w64-x86_64-gtk4
# 设置 rust
ENV RUSTUP_DIST_SERVER=https://mirrors.sjtug.sjtu.edu.cn/rust-static
ENV RUSTUP_UPDATE_ROOT=https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup
RUN rustup install stable
RUN rustup target add x86_64-pc-windows-gnu
# 设置容器参数
RUN mkdir /app
WORKDIR /app
ENV PKG_CONFIG_SYSROOT_DIR=/mingw64
ENV PKG_CONFIG_PATH=/mingw64/lib/pkgconfig
CMD cargo build --target=x86_64-pc-windows-gnu
之后打包它: buildah bud -t cargo
(友情提示,这个容器大小可能超过6个G,且编译时间较长)。
我们利用这个容器来编译我们的仓库(假设项目路径为./mygtk
):podman run -v $PWD/mygtk:/app cargo
。
然后我们便可以在gtk/target/x86_64-pc-windows-gnu/debug/
下找到mygtk.exe
。
动态链接库
到这里还没完。当我们把exe文件复制到Windows操作系统上时会发现,它提示缺少DLL。那么我们可以从哪里搞到DLL呢?
显而易见,我们之前用的msys2里安装的包就包含了我们所需的各类DLL。它们都在/mingw64/bin下。
为了找到这些DLL,我们需要一个工具:mingw_ldd。这是一个python库,可以列出所有依赖的DLL。通过pip安装后,我们只需要运行mingw-ldd mygtk.exe --dll-lookup-dirs /mingw64/bin | grep -o '/mingw64/bin.*$' | xargs cp -t .
就可以列出DLL;用grep裁剪保留我们需要的路径;用xargs把参数合并起来传给cp。当然,前提是我们将它添加至之前的容器中。
可以看到,我们目前的编译包括几步:正常的cargo编译、查询并复制DLL。因此不妨使用脚本来完成。
抛砖引玉:
cargo build --target=x86_64-pc-windows-gnu
mkdir dist || true
cp target/x86_64-pc-windows-gnu/debug/mygtk.exe dist/
mingw-ldd dist/mygtk.exe --dll-lookup-dirs /mingw64/bin | grep -o '/mingw64/bin.*$' | xargs cp -t dist
完整的代码我放到了仓库上,可以参考。
总结
到此为止,我们至少做到了可以在Windows上运行我们在Linux上交叉编译出来的程序。
但是我们还有很多问题没有解决。 比如说,右上角图标一个都显示不出;我们通过双击exe来运行时会带出一个黑框框;我们还不支持程序设置。这些问题将在未来的第二篇中解决。(咕咕咕)(好吧,其实是这条路能不能走通我还不确定)