前情提要

这是接着上一篇来的,上一篇说到,我们用别人已经编译好的一个支持 armv6 的 .NET 7.0.10 的 docker 镜像成功的构建出了能在树莓派 1B+ 上跑的 ASF。

但马上 ASF 5.5 之后要开始使用 .NET 8 了啊,那怎么办呢?

我又想到上一篇里说到的那篇在 issue 里的回复,这篇回复简要的写了一个可以交叉构建的环境是如何编译的。

想了想总不能以后总等别人做镜像吧,于是自己根据这篇回复来开始着手编译一下最新的 .NET 8.0.0 环境。

编译 armv6 交叉构建环境

事后诸葛亮

允许我事后诸葛亮一下,这个过程真的挺痛苦的,那篇笔记总共就写了寥寥数行,虽然最终跑完了才发现写的基本上没什么问题。

但更痛苦的是,在我编译完之后发现,那个之前搞 .NET 7.0 的老哥,开了一个 dotnet-armv6 的 docker 镜像,里面就是已经弄好了的 .NET 8.0。

但好在,现在如果这老哥停止支持了,我还能从零构建一个能用的镜像,想了想也挺有意义的,所以才决定把这个折腾过程也写成文字材料,以供备用。

正式开始

因为主要是根据那篇回复来搞定的,所以我打算步骤也写的大致一致,方便对照参考。

但回复里原先的顺序其实有点乱,逻辑上来说确实是这个顺序,但执行起来我觉得还是稍稍整理一下为好。

准备工作

一台 Linux 主机,需要预装 docker,这个我就不赘述了。

下载 dotnet/runtime 仓库(我也很好奇,为什么不是 sdk 仓库?):

git clone https://github.com/dotnet/runtime --recursive
cd runtime
git checkout -b buildarmv6 v8.0.3

这么下载的原因一个是可以下载 submodule,另一个就是可以 git reset --hard 直接取消自己的更改。

第一步:编译 Runtime

使用指定的 docker 镜像来编译需要的内容,这里 ubuntu-20.04-cross-armv6-raspbian-10-latest 这个标签是在 微软的 dotnet docker 标签库 里找的。

sudo docker run -e ROOTFS_DIR=/crossrootfs/armv6 -w $(pwd) --platform linux/amd64 -v $(pwd):$(pwd) -it mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-cross-armv6-raspbian-10-latest ./build.sh --ci --restore --build --arch armv6 --os linux --configuration Debug /p:CrossBuild=true /p:ArchiveTests=true -s mono+libs+host+packs
cd ..

这段一般运行的时候都不会有什么问题,一般出错都是版本号或者代码的问题,可以多尝试两遍,如果还有问题那建议稍微后退一个小版本,比如我一开始用的 main 分支,可版本号是 9.0.0,这导致我编译的时候出现非常多问题。

编译完后,去 runtime/artifacts/packages/Debug/Shipping 目录下看看 dotnet-runtime 开头的文件的完整文件名是什么,之后要用,可能因为用的是 release 代码的原因,我这里跑出来的是 dotnet-runtime-8.0.0-linux-armv6.tar.gz,文件名里没有 ci 字样。

第二步:拉取并修改 AspNetCore 的部分内容

这一步要操作的内容比较多,因为 AspNetCore 确实编译起来很多问题。

  1. 先拉取代码
git clone https://github.com/dotnet/aspnetcore --recursive
cd aspnetcore
git checkout -b buildarmv6 v8.0.3
  1. 下载完后,根据回复提供的 gist 的内容,修改 aspnetcore/eng/Versions.props 文件,将 <ValidateBaseline>true</ValidateBaseline> 改为 <ValidateBaseline>false</ValidateBaseline>

  2. 根据 gist,修改 aspnetcore/src/Framework/App.Runtime/src/Microsoft.AspNetCore.App.Runtime.csproj 文件。

这里要更改的地方有三处:

(1). <DotNetRuntimeDownloadPath>$(DotNetAssetRootUrl)/$(DotNetRuntimeArchiveFileName)</DotNetRuntimeDownloadPath>

(2). 572 行左右的 <DownloadFile> 的 Uris 属性,修改为 $(DotNetRuntimeDownloadPath),和上一处关联起来,默认是从互联网拉取代码的,所以需要额外改掉这里。

(3). 可选,<DotNetRuntimeArchiveFileName> 这个标签的内容,如果你上一步的文件名有 ci 字样的话,可以将其改成 <DotNetRuntimeArchiveFileName>dotnet-runtime-8.0.0-ci-$(TargetRuntimeIdentifier)$(ArchiveExtension)</DotNetRuntimeArchiveFileName>,没有就不用。

  1. 修改 aspnetcore/eng/build.sh,在开头部分加上如下代码以在容器内安装较新的 nodejs,后面编译的时候要用。(别装 20,用的依赖最高只支持 19)
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 42D5A192B819C5DA
apt-get update
apt-get install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=18
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get install nodejs -y

第三步:编译 AspNetCore

这个 AspNetCore 确实跑起来毛病多多,我自己试的时候主要就是卡在这一步上了。

因为要用到目录的绝对路径,所以暂且说一下我的用户目录是 /workspace/sffxzzp,这样便于修改时理解。

这一步耗时比较长,一般错一次就白跑十几分钟,等的很累,主要时间都耗在等上了,所以之前的步骤尽量别出错。

命令里的 DotNetAssetRootUrl 就是第一步看文件名的那个位置,确保一致,另外 file:// 不能少,否则没法正常从本地拉取第一步的结果。

还有就是别忘了用 -v 参数映射你的 runtime 目录进去,不然也是无法正常拉取的,因为那篇回复的这里只给了后半段,所以前半段我就复制了第一步的代码,结果忘记映射,我就说怎么总是拉取不到文件。

运行:

sudo docker run -e ROOTFS_DIR=/crossrootfs/armv6 -w $(pwd) --platform linux/amd64 -v $(pwd):$(pwd) -v /workspace/sffxzzp/runtime:/workspace/sffxzzp/runtime -it mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-cross-armv6-raspbian-10-latest eng/build.sh --ci --pack -c Debug --arch armv6 --os-name linux /p:DotNetAssetRootUrl=file:///workspace/sffxzzp/runtime/artifacts/packages/Debug/Shipping
cd ..

2024.3.31 补充:

一般来说,编译会出现 SignalR 版本不对的错误,需要搜索 link:../signalr 并且修改 package.json 以及 yarn.lock 文件,将版本号改成 @microsoft/signalr 在 npm 上的最新版本号,并且注释掉 .npmrc 文件的全部内容,以保证正常编译。

然后直接删除 --frozen-lockfile 参数,理论上就可以一遍跑完结果了。

运行一遍之后可能会报错,尝试运行第二遍,然后可能会提示因为有 --frozen-lockfile 参数,yarn 无法正常更新 lockfile。

遇到之后首先需要修改 aspnetcore/.npmrc,注释掉或者删掉全部内容,微软的 registry 会有鉴权问题。

然后需要修改 aspnetcore/eng/targets/Npm.Common.targets 文件,删除全部的 --frozen-lockfile 参数。

再重新编译一次,这次应该就能正常跑通了。

到这,已经基本上宣告成功了,后面就是一些简单的步骤了。

第四步:安装 dotnet-sdk 并进行一些小修改

2024.3.31 补充:

这一步可以不做,SDK 安装之后可能会版本不对,直接进行下一步打包为 Docker 镜像,再使用 Docker 镜像即可。

其实可以直接用微软那个 dotnet-install.sh 脚本,但问题是 sdk 里还要稍稍修改一下,所以直接用二进制包,将其安装在 dotnet 目录。

运行:

mkdir dotnet
cd dotnet
wget https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz
tar -zxvf dotnet-sdk-8.0.100-linux-x64.tar.gz
rm dotnet-sdk-8.0.100-linux-x64.tar.gz
cd ..
sudo ln -s /workspace/sffxzzp/dotnet/dotnet /usr/local/bin
sed -i 's/linux-s390x/linux-armv6;linux-s390x/g' /workspace/sffxzzp/dotnet/sdk/8.0.100/Microsoft.NETCoreSdk.BundledVersions.props

最后一行批量替换其实是原回复的第 6 步,而且手动哪里有 sed 替换一步到位爽呢。

第五步 :创建本地 NuGet 仓库

这一步是将之前两次痛苦编译的 nupkg 文件复制到一个文件夹,并将之添加为已安装的 dotnet-sdk 的本地 NuGet 仓库,这样后续在编译 ASF 或者其他文件的时候,遇到 linux-armv6 就会先从本地仓库中拉取,而不是尝试远程拉取(远程都没有,肯定会编译失败)。

运行:

mkdir nugetrepo
cp ./runtime/artifacts/packages/Debug/Shipping/*.nupkg ./nugetrepo/
cp ./aspnetcore/artifacts/packages/Debug/Shipping/*.nupkg ./nugetrepo/
dotnet nuget add source /workspace/sffxzzp/nugetrepo/ -n localrepo

2024.3.31 补充:

可以删除后缀带 symbol 的文件以缩小体积。

至此,环境配置完成,可以直接 docker publish -r linux-armv6 进行编译了。

可选:将环境打包为 Docker 镜像

如果我不光想本地自己用,我还想打包成 docker 镜像给别人用呢?

很简单,因为需要的只是编译好的 nupkg 文件,所以直接用对应版本的 dotnet 镜像稍做修改就能搞定啦。

先创建一个 Dockerfile,内容为:

FROM mcr.microsoft.com/dotnet/sdk:8.0.100-1

WORKDIR /root
COPY ./nugetrepolite/ /usr/share/dotnet/nugetrepo

RUN sed -i 's/linux-s390x/linux-armv6;linux-s390x/g' /usr/share/dotnet/sdk/8.0.100/Microsoft.NETCoreSdk.BundledVersions.props
RUN dotnet nuget add source /usr/share/dotnet/nugetrepo/ -n localrepo
RUN rm -rf /tmp/*

CMD "/bin/bash"

原理很简单,就是将编译好的 nupkg 复制进 docker 镜像,然后对 Microsoft.NETCoreSdk.BundledVersions.props 进行修改。

那问题又来了,镜像弄好了,别人应该怎么用呢?且看后面编译 ASF 部分。

编译 ASF

上一篇文章其实已经大致写了应该如何使用已经弄好的 docker 镜像来编译。

但经过这一次的痛苦编译之后,我觉得上次写复杂了,所以这次写一个更简易一点的。

很简单,就两步。

当然这一次直接用 ASF 的 github 仓库最新提交,因为这次我们的环境已经是 8.0 啦。

第一步:拉取代码并修改 cc.sh

拉取代码:

git clone https://github.com/JustArchiNET/ArchiSteamFarm --recursive --depth=1
cd ArchiSteamFarm

cc.sh 头部加上以下内容以在一开始就安装 nodejs,这样方便我们一步到位的编译 ASF-ui:

apt update
apt install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
apt update
apt install nodejs -y
git config --global safe.directory '*'

然后修改两个 FLAGS 的内容:

# 删掉 -p:UseAppHost=false 以使用 --self-contained 参数
DOTNET_FLAGS="-c $CONFIGURATION -f $TARGET_FRAMEWORK -p:ContinuousIntegrationBuild=true --nologo"
# -r 后指定目标平台,而不是使用当前机器的平台,另外 --self-contained 会在编译打包时带上运行库,否则树莓派无法运行
PUBLISH_FLAGS="-r linux-armv6 --self-contained"

第二步:编译

运行:

docker run -w $(pwd) -v $(pwd):$(pwd) -it --rm taphome/dotnet-armv6:v8.0.0 ./cc.sh

简单吧,确实简单,我怎么就没想到用 docker 的 volume 呢?

当然,编译完,打包复制之前建议先 chown 和 chmod 搞一下文件权限问题,不再赘述。

今天就写这么多,摸了。