为龙芯笔记本交叉编译 Rust

在龙芯笔记本上安装好 Gentoo 之后,接下来我要在它上面安装 Rust。主要的原因是我习惯使用的一些命令,比如 fdrgbat 都需要使用 Rust 编译。

Rust 对 MIPS 提供 Tier 3 级别的支持,并且对 mips64el-unknown-linux-gnuabi64 的 target 提供 host 支持,可以在上面运行 Rust 编译器。因此我们可以直接在龙芯笔记本上安装 Rust,然后在本地编译需要的软件。

不过,直接安装 dev-lang/rust-bin 是不行的,因为 Rust 默认使用 mips64r2 架构进行编译,而龙芯 2F 芯片是基于 mips3 架构的,因此不能直接运行,需要交叉编译 dev-lang/rust 的二进制包。

1. 交叉编译 Rust 编译器

目前 Wiki 对于这部分内容的说明已经过时了,根据今年 2 月份 869a67e 的提交,目前可以直接使用 crossdev 交叉编译 Rust 编译器。

首先在宿主机上安装 Rust。

1
2
3
4
5
## 如果只需要交叉编译 Rust 编译器,直接使用 dev-lang/rust-bin 即可
## 当然也可以使用 dev-lang/rust,不过编译安装需要的时间比较长
##(和编译 GCC 的时间差不多)
## 如果需要交叉编译 Rust 程序,则必须安装 dev-lang/rust
emerge -avq dev-lang/rust-bin

根据 ebuild 文件的说明,在进行交叉编译的时候,需要启用 system-bootstrap 的 USE flag,并禁用 system-llvm,还需要启用宿主机的 LLVM target。

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/package.use/rust
1
2
dev-lang/rust system-bootstrap -system-llvm
dev-lang/rust LLVM_TARGETS: X86

接下来要把目标架构从 mips64r2 改为 mips3,可以使用 patch:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/patches/dev-lang/rust/mips3.patchlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs b/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs
index 515473fbabc..fa3c6875964 100644
--- a/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs
+++ b/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs
@@ -15,8 +15,8 @@ pub fn target() -> Target {
options: TargetOptions {
abi: "abi64".into(),
// NOTE(mips64r2) matches C toolchain
- cpu: "mips64r2".into(),
- features: "+mips64r2,+xgot".into(),
+ cpu: "mips3".into(),
+ features: "+mips3,+xgot".into(),
max_atomic_width: Some(64),
mcount: "_mcount".into(),

也可以使用 env 文件:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/dev-lang/rust
1
2
3
4
function post_src_unpack() {
sed -i -e "s/mips64r2/mips3/" \
"${S}"/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs || die
}

另外还有一点,在编译 Rust 的时候,有一步会有 -DCMAKE_ASM_COMPILER=/usr/bin/mips64el-linux-gnuabi64-gcc 的参数,没有找到原因,不过解决方法很简单,补一个软连接就可以了。

1
ln -sv /usr/bin/mips64el-unknown-linux-gnuabi64-gcc /usr/local/bin/mips64el-linux-gnuabi64-gcc

接下来直接安装即可。

1
mips64el-unknown-linux-gnuabi64-emerge -av dev-lang/rust

这里面有两点需要注意:

  1. sys-libs/glibc 不得使用 -march=loongson2f 编译,否则会提示链接错误;
  2. 此时 /usr/mips64el-unknown-linux-gnuabi64/etc/portage/make.conf 中的 CFLAGS 要还原最原始配置(crossdev 安装时默认的即可),不能有 -march-mtune-mabi 还有 -Wa,... 的选项。

可以通过 package.env 进行配置:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/embedded
1
2
CFLAGS="-O2 -pipe -fomit-frame-pointer"
CXXFLAGS="${CFLAGS}"
/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/mips3
1
2
CFLAGS="-O2 -march=mips3 -mtune=loongson2f -mabi=64 -Wa,-mfix-loongson2f-nop -pipe"
CXXFLAGS="${CFLAGS}"
/usr/mips64el-unknown-linux-gnuabi64/etc/portage/package.env/rust
1
2
sys-libs/glibc mips3
dev-lang/rust embedded

然后把编译好的二进制包复制到龙芯笔记本上

1
2
3
ssh yeeloong 'mkdir -p /var/cache/binpkgs/dev-lang/rust'
scp /usr/mips64el-unknown-linux-gnuabi64/var/cache/binpkgs/dev-lang/rust/* \
yeeloong:/var/cache/binpkgs/dev-lang/rust

在龙芯笔记本上:

1
2
3
4
## 保持编译时的 USE flag
echo 'dev-lang/rust system-bootstrap -system-llvm' >> /etc/portage/package.use/rust
echo 'dev-lang/rust LLVM_TARGETS: X86' >> /etc/portage/package.use/rust
emerge -avqk dev-lang/rust

接下来就可以安装常用的几个软件了:

1
2
3
4
5
6
7
8
9
10
cat <<-EOF >> /etc/portage/package.accept_keywords/rust
sys-apps/bat * ~*
dev-libs/libgit2 * ~*
sys-apps/fd * ~*
sys-apps/lsd * ~*
sys-apps/ripgrep * ~*
dev-util/tailspin * ~*
EOF

emerge -avq bat fd ripgrep tailspin lsd

需要注意的是,在安装 lsd 的时候,CFALGS 的设置不能是 -march=loongson2f,否则会导致编译失败。
使用 -march=mips3 可以正常编译。

这样编译得到的 lsd 执行文件,虽然显示是 mips64r2 架构的:

1
2
$ file /usr/bin/lsd
/usr/bin/lsd: ELF 64-bit LSB pie executable, MIPS, MIPS64 rel2 version 1 (SYSV), dynamically linked, interpreter /lib64/ld.so.1, for GNU/Linux 3.2.0, stripped

但是可以正常运行。

2. 交叉编译用 Rust 写的软件

上面的方法比较简单,但是由于 Rust 编译的速度本身就比较慢,再加上弱鸡的龙芯 CPU,基本上编译一个软件都得一小时起步:

1
2
3
4
5
6
$ qlop -v bat fd ripgrep tailspin lsd
2024-07-12T12:04:23 >>> sys-apps/bat-0.24.0-r1: 2:30:33
2024-07-12T15:22:19 >>> sys-apps/ripgrep-14.1.0-r1: 1:10:49
2024-07-12T16:58:42 >>> sys-apps/fd-10.1.0: 1:01:34
2024-07-12T18:41:51 >>> dev-util/tailspin-3.0.1: 2:05:39
2024-07-13T11:48:53 >>> sys-apps/lsd-1.1.2-r1: 1:54:52

因此,也可以选择直接在宿主机上交叉编译所需要的软件,不过相对来说比较麻烦,需要处理的问题比较多。

根据 Bug 679878 的说明,目前直接通过 crossdev 直接编译 Rust 编写的软件还有一些问题没有解决。实测也是如此,大部分软件都需要一些 hacking 才能成功交叉编译安装。

首先在宿主机上安装 rust:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 这是因为之前已经安装了 `rust-bin` 和 `llvm`,否则不选也可以。
echo 'dev-lang/rust system-bootstrap system-llvm' >> /etc/portage/package.use/rust

## 启用 MIPS 交叉编译。
echo 'dev-lang/rust LLVM_TARGETS: Mips' >> /etc/portage/package.use/rust

## 进行 Rust 编程所需要的一些工具
echo 'dev-lang/rust rust-analyzer rustfmt clippy rust-src' >> /etc/portage/package.use/rust

## 把前面的 mips3 补丁复制过来
mkdir -p /etc/portage/patches/dev-lang/rust/
cp /usr/mips64el-unknown-linux-gnuabi64/etc/portage/patches/dev-lang/rust/mips3.patch \
/etc/portage/patches/dev-lang/rust/mips3.patch
## 或者
mkdir -p /etc/portage/env/dev-lang/
cp /usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/dev-lang/rust \
/etc/portage/env/dev-lang/rust

emerge -avq rust

然后安装 rust-std。首先需要将 sys-devel/rust-std 链接到 crossdev 的仓库中:

1
2
ln -sv /var/db/repos/gentoo/sys-devel/rust-std \
/var/db/repos/crossdev/cross-mips64el-unknown-linux-gnuabi64

跟前面类似,这里也要使用 mips3 编译 rust-std,而且需要在编译时禁止下载 LLVM:

/etc/portage/env/cross-mips64el-unknown-linux-gnuabi64/rust-std
1
2
3
4
5
6
7
8
9
10
11
function post_src_unpack() {
sed -i -e "s/mips64r2/mips3/" \
"${S}"/compiler/rustc_target/src/spec/targets/mips64el_unknown_linux_gnuabi64.rs || die
}

function post_src_configure() {
sed -i -e "1i\
[llvm]\n\
download-ci-llvm = false" \
"${S}"/config.toml || die
}

然后就可以安装了。

1
emerge -avq cross-mips64el-unknown-linux-gnuabi64/rust-std

接下来,我们就可以交叉编译所需要的软件了。

2.1. tailspin

直接安装即可。

2.2. lsd、fd

lsd、fd 都可以成功编译,不过需要禁用 lto:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/sys-apps/{lsd,fd}
1
2
3
function post_src_prepare() {
sed -i -e '/^lto/s:true:false:' Cargo.toml || die
}

而且 lsd 不能使用 -march=loongson2f 编译,要改为使用 -march=mips3 才可以:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/package.env/lsd
1
sys-apps/lsd mips3

2.3. git-delta

git-delta 需要禁用 RUSTONIG_DYNAMIC_LIBONIG:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/sys-apps/git-delta
1
2
3
function post_src_configure() {
export RUSTONIG_SYSTEM_LIBONIG=0
}

另外,它和 lsd 一样,也不能用 -march=loongson2f 编译:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/package.env/git-delta
1
sys-apps/git-delta mips3

2.4. bat

bat 需要同时禁用 lto 和 RUSTONIG_DYNAMIC_LIBONIG:

/usr/mips64el-unknown-linux-gnuabi64/etc/portage/env/sys-apps/bat
1
2
3
4
5
6
7
function post_src_prepare() {
sed -i -e '/^lto/s:true:false:' Cargo.toml || die
}

function post_src_configure() {
export RUSTONIG_SYSTEM_LIBONIG=0
}

2.5. ripgrep

ripgrep 需要使用 QEMU User Mode Emulation,因为在安装的时候直接运行了 rg 命令。

按照 Wiki 中的指示,使用 USE=static-usrQEMU_USER_TARGETS:mips64el 安装好 qemu 之后,启用 binfmt 服务:

1
2
3
4
5
## 配置 QEMU_LD_PREFIX
# mkdir /usr/gnemul
# ln -sv /usr/mips64el-unknown-linux-gnuabi64 /usr/gnemul/qemu-mips64el
## 启动 binfmt 服务
# rc-service qemu-binfmt start

也可以直接 使用 wrapper,然后手动配置 binfmt:

1
echo ':qemu-mips64el:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00:\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/local/bin/qemu-wrapper-hard-loongson-n64:OC' > /proc/sys/fs/binfmt_misc/register

最后对比一下在宿主上交叉编译所需要的时间:

1
2
3
4
5
6
7
2024-08-06T18:26:40 >>> sys-apps/lsd-1.1.2-r1: 33s
2024-08-06T18:34:39 >>> sys-apps/fd-10.1.0: 25s
2024-08-06T18:40:08 >>> sys-apps/ripgrep-14.1.0-r1: 20s
2024-08-06T19:50:18 >>> sys-apps/bat-0.24.0-r1: 49s
2024-08-06T20:16:21 >>> dev-util/tailspin-3.0.1: 23s
2024-08-06T20:19:06 >>> dev-util/git-delta-0.16.5: 31s
2024-08-17T13:24:54 >>> sys-apps/ripgrep-all-0.10.6: 59s