交叉编译LoongArch架构下的Electron

1. 介绍

本文打算从环境配置,获取源代码,编译,测试,使用五个方面初步描述了 LoongArch 架构下 Electron 的交叉编译。由于chromium版本更新速度极快,且对C++新特性的应用较激进,本文档使用的 clang/llvm 落后于官方所提供的版本,存在部分编译参数不兼容的问题。

本文主要参考Electron官方文档中 构建说明构建步骤(Linux)

2. 编译环境准备

由于本地环境千差万别,Electron的编译所需依赖也比较复杂,为了不让Electron的编译大计毁于配置环境这一步,所以决定偷懒使用官方提供的docker镜像,这样可以避免编译过程中大部分环境上的问题,且不用当心依赖的版本问题,代价是需要学会几条 docker 命令。
Electron官方提供了编译环境的docker镜像:https://github.com/electron/build-images/pkgs/container/build

由于官方提供的clang/llvm 目前并不支持 LoongArch 架构,所以还得准备一套支持 LoongArch架构的编译工具。
LoongArch llvm-project源码仓库:https://github.com/loongson/llvm-project.git

2.1 安装docker

安装docker这一步,有大量优质且易懂的文章进行说明,可以参考以下链接中的安装方式,这里不再赘述。
docker安装参考:https://yeasy.gitbook.io/docker_practice/install

2.2 获取构建环境镜像

此镜像由Electron官方仓库中的 build-images 子项目提供

1
2
3
4
5
$ docker pull ghcr.io/electron/build:latest
# 将docker的用户家目录映射到本机存放数据的目录,这样方便数据交换
$ docker run -dit -P --name electron-build --mount type=bind,source=/home/loongson/docker-data,target=/home/builduser ghcr.io/electron/build:latest
# 进入docker 环境,接下来除了测试的操作,都在docker内完成
$ docker exec -it electron-build bash

接下来除了测试等操作,都编译过程都将在docker内完成

2.3 编译 clang/llvm

Electron目前默认使用clang/llvm进行编译,现在

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sudo apt update
$ sudo apt install cmake ninja-build
$ git clone https://github.com/loongson/llvm-project.git
$ cd llvm-project
$ mkdir _build && cd _build
$ cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="~/llvm_install_bin" \
../llvm -G Ninja -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_INCLUDE_TOOLS=ON \
-DLLVM_BUILD_TOOLS=ON -DLLVM_INCLUDE_UTILS=ON -DLLVM_BUILD_UTILS=ON \
-DLLVM_INCLUDE_RUNTIMES=ON -DLLVM_BUILD_RUNTIME=ON -DLLVM_BUILD_LLVM_DYLIB=ON \
-DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_ENABLE_ZLIB=ON -DLLVM_ENABLE_FFI=ON -DLLVM_ENABLE_RTTI=ON

$ ninja
$ ninja -v install

2.4 获取交叉工具链的链接器

由于系统的链接器无法链接loongarch64架构的二进制,手动制作一个交叉工具链比较麻烦,这里再次偷懒用 loongson/build-tools 里面带的交叉工具链的链接器。

1
2
3
4
$ wget https://github.com/loongson/build-tools/releases/download/2022.05.29/loongarch64-clfs-5.0-cross-tools-gcc-glibc.tar.xz
$ tar -xf loongarch64-clfs-5.0-cross-tools-gcc-glibc.tar.xz
$ sudo cp cross-tools/bin/loongarch64-unknown-linux-gnu-ld /usr/bin/loongarch64-linux-gnu-ld
$ sudo cp cross-tools/bin/loongarch64-unknown-linux-gnu-ld.bfd /usr/bin/loongarch64-linux-gnu-ld.bfd
1
2
3
4
5
6
7
8
9
10
# 查看动态库搜索路径
$ ./loongarch64-unknown-linux-gnu-ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\n
SEARCH_DIR("=/opt/cross-tools/loongarch64-unknown-linux-gnu/lib64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/opt/cross-tools/loongarch64-unknown-linux-gnu/lib")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")

完成上面一步几个配置,基本上Electron的编译环境就已经搭建好了,接下来就可以拉取代码,然后编译Electron了。

3. 拉取代码

拉取代码可以通过官方文档提供的方法来获取,也可以使用Electron官方仓库中的 build-tools (此build-tools非上面那个下载交叉工具链和clfs系统的build-tools,虽然他们名字相同,是不同的项目)子项目中的工具来完成代码的获取。
既然有更好用的工具来获取代码,当然应该果断选择使用坑更少的路。接下来将使用build-tools 工具来拉取代码。

3.1 安装 build-tools

1
2
#本地安装 build-tools
$ npm install @electron/build-tools

3.2 设置代理

Electron本身代码托管于github上,但是Electron依赖 Chromium 源码,需要一个稳定的代理才能获取到源代码(第一次拉取代码大概需要 40G 左右流量)

1
2
3
4
5
$ export ALL_PROXY="代理ip:端口"
$ export http_proxy="代理ip:端口"
$ export https_proxy="代理ip:端口"
$ git config --global http.proxy "代理ip:端口"
$ git config --global https.proxy "代理ip:端口"

3.3 拉取代码

拉取 Electron 代码使用官方提供的 build-tools 工具,这个工具默认将 GIT_CACHE_PATH 环境变量设置到用户家目录的 .git_cache 目录(–> 参考build-tools实现代码)。如果需要将git_cache 缓存放在其他目录,只需要对 GIT_CACHE_PATH 进行设置即可。
使用 git_cache 只要完整获取一次代码后,后续其他版本也可以使用这个缓存,不用每次都要去上游完整拉取代码,大大节约了时间和流量。
以下提供简单使用方法,更详细参考官方仓库README

1
2
3
4
5
6
7
8
9
10
11
# 第一次使用会去google拉取 depot_tools,需要代理,且需要较长时间
$ ./node_modules/.bin/e init release-v19.x.y --root=./Electron_v19.x.y
$ ./node_modules/.bin/e use release-v19.x.y

# 由于使用的不是官方仓库,所以这里手动clone支持LoongArch架构的源码,并切换到合适的分支
# 切换到合适的分支后,执行 sync 将会按此分支自动同步源码,并打上补丁
$ mkdir -pv ./Electron_v19.x.y/src
$ pushd Electron_v19.x.y/src
$ git clone https://github.com/loongson/electron.git -b 19-x-y-loongarch64
$ popd
$ ./node_modules/.bin/e sync -vvvv

3.4 部分报错及解决方法

  • 由于使用环境变量设置代理导致npm安装包报错
    1
    2
    3
    4
    5
    ________ running 'python3 -c import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python3", "script/lib/npx.py", "yarn@1.15.2", "install", "--frozen-lockfile"]);' in '/home/builduser/Electron_v19.x.y'
    npm ERR! code ERR_INVALID_URL
    npm ERR! Invalid URL
    npm ERR! A complete log of this run can be found in:
    npm ERR! /home/builduser/.npm/_logs/2022-06-20T09_37_12_695Z-debug-0.log
    解决方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 暂时取消代理,安装完依赖再配置依赖
    $ pushd Electron_v19.x.y/src/electron/
    $ unset http_proxy
    $ unset https_proxy
    $ python3 script/lib/npx.py yarn@1.15.2 install --frozen-lockfile
    $ popd
    # 重新设置代理,并重新执行sync获取代码,由于之前下载的代码都缓存到了.git_cache,速度会快很多
    $ export http_proxy="ip:port"
    $ export https_proxy="ip:port"
    $ ./node_modules/.bin/e sync -vvvv

4. 编译及打包 Electron

成功使用docker配置好编译环境并且获取到完整的Electron源码及依赖后,接下来将对源码进行交叉编译。这里需要有几点进行说明:

  • Electron中并非使用la64 或者loongarch64作为架构名称,而是 loong64,这个是由于v8源码进入上游时决定的。参考:https://chromium-review.googlesource.com/c/v8/v8/+/3089095
  • 目前暂时没有合适的系统(debian/ubuntu系列)支持上游最新版本的内核,导致 clang/llvm 交叉编译使用的 sysroot 当前还无法通过官方方法制作,所以暂时使用 https://github.com/loongson/build-tools 提供的 loongarch64-clfs-system-5.0.tar.bz2 (不局限于这个版本,社区版本系统高速迭代中,尽可能使用最新clfs系统0.0)
  • 交叉编译使用clang/llvm版本问题,目前 https://github.com/loongson/llvm-project 只提供了 llvm 11,Electron目前使用的版本是 llvm 15, Electron依赖的Chromium在C++新特性方面比较激进,这其中会有部分编译参数不支持和部分语法不支持的情况(类似constexpr相关)。

虽然有一些小问题,但是这些都是可以解决的,不过出现一些warning是不可避免的-_-。
接下来把工作目录切换到 Electron_v19.x.y/src 进行一些简单的配置即可开始编译了。

1
2
3
4
5
6
7
8
# 切换到编译目录
$ cd Electron_v19.x.y/src
# 将交叉编译的sysroot下载并解压到合适的位置
$ pushd build/linux
$ wget https://github.com/loongson/build-tools/releases/download/2022.05.29/loongarch64-clfs-system-5.0.tar.bz2
$ mkdir debian_bullseye_loong64-sysroot && cd debian_bullseye_loong64-sysroot
$ tar -xf ../loongarch64-clfs-system-5.0.tar.bz2
$ popd

编译配置electron/build/args/release.gn 如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cat electron/build/args/release.gn
import("all.gn")
is_component_build = false
is_official_build = false
rtc_use_h264 = proprietary_codecs
is_component_ffmpeg = true

# Build args for loong64
enable_widevine = false
ffmpeg_branding = "Chrome"
is_clang = true # 使用clang作为编译器
use_sysroot = true # 使用sysroot,否则会使用当前系统环境
is_debug = false
use_gold = false # 不支持
use_lld = false # 不支持
# 设置clang/llvm的二进制目录,最后的“/” 不能省略(之前编译llvm时设置的安装二进制目录)
clang_base_path = "/home/builduser/llvm_install_bin/"
clang_use_chrome_plugins = false
proprietary_codecs = true
# 关闭将warning当作错误显示,clang版本不一致,会出现大量warning
treat_warnings_as_errors = false
fatal_linker_warnings = false
use_gnome_keyring = false # 这个软件新版本系统中没了
host_cpu = "x64" # 设置交叉编译参数
target_cpu = "loong64" # 设置交叉编译参数
v8_target_cpu = "loong64" # 设置交叉编译参数
rtc_use_pipewire = false
dcheck_always_on = false

配置好编译参数后,开始编译

1
2
3
4
5
6
7
8
9
10
11
12
$ export LC_ALL="C.UTF-8"
# 使用gn生成配置文件
$ ./buildtools/linux64/gn gen out/Release --args="import(\"//electron/build/args/release.gn\")"
# 编译electron
$ ninja -C out/Release/ electron
# 打包electron,以下5条命令分别是生成 dist.zip, hunspell_dictionaries.zip
# mksnapshot.zip, chromedriver.zip 和 ffmpeg.zip
$ ninja -C out/Release/ electron:electron_dist_zip
$ ninja -C out/Release/ electron:hunspell_dictionaries_zip
$ ninja -C out/Release/ electron:electron_mksnapshot_zip
$ ninja -C out/Release/ electron:electron_chromedriver_zip
$ ninja -C out/Release/ electron:electron_ffmpeg_zip

编译过程到此就结束了,成功得到dist.zip, hunspell_dictionaries.zip,mksnapshot.zip, chromedriver.zip 和 ffmpeg.zip等几个包后,如何使用参考本文的第五部分Electron仓库制作和使用

5. 测试Electron

交叉编译完成后,到loongarch架构机器中,普通用户(非root用户)目录下创建如下结构形式的目录

1
2
3
4
5
6
7
8
$ mkdir -pv electron_v19.x.y/out/Release
$ cd electron_v19.x.y && git clone https://github.com/loongson/electron.git -b 19-x-y-loongarch64
$ tree electron_v19.x.y
electron_v19.x.y/
├── electron
└── out
└── Release

将dist.zip 解压到 electron_v19.x.y/out/Release/ 目录下, 然后进入electron 目录,按照如下方式安装依赖:

1
2
3
4
5
6
7
8
9
# 默认系统中的python是python3,安装测试软件
$ sudo pip3 install -g python3-dbusmock
$ sudo npm install -g yarn
$ cd electron/spec
$ yarn
$ cd ../spec-main/
$ yarn
$ cd ..
$ yarn

由于测试时自动安装的脚本存在一点问题,上面已经手动安装好了测试环境,测试时脚本会需要修改 script/spec-runner.js,需要注释掉安装依赖的两行,参照下面链接 https://github.com/electron/electron/issues/22432#issuecomment-593000264
去掉 installSpecModules 函数处理 spec 和 spec-main 目录的依赖安装。也就是上面手动进入到 spec和spec-main 里面执行yarn命令。

接下来是运行测试程序:
需要接入屏幕,远程通过ssh会 “[13035:1230/101247.042264:FATAL:ozone_platform_x11.cc(238)] Check failed: x11::Connection::Get()->Ready(). Missing X server or $DISPLAY” 报没有 X server

1
2
$ export ELECTRON_SKIP_NATIVE_MODULE_TESTS=true
$ npm run test

设置 ELECTRON_SKIP_NATIVE_MODULE_TESTS 环境变量参考配置CI测试的 arm 架构 .circleci/build_config.yml 第1126 行,此环境变量可以跳过部分导致软件crash的测试项。

linux 中由于屏幕缩放问题导致较多测试不通过,不过并不影响使用。
由于浮点数精度误差,某些依赖于精确像素测量的测试可能无法正常在Hi-DPI屏幕的设备上工作。 为了使这些测试能正常运行,请确保设备的缩放比为100%。
参考:https://www.electronjs.org/zh/docs/latest/development/testing#pixel-measurements

6. Electron仓库制作和使用

通过上述方法编译完成Electron得到一堆zip包,而安装这些包需要一个还需要一点小小的配置。

  • 需要一个存放Electron的zip包的仓库
  • 需要在安装的机器上设置一些环境变量来指向我们自己的仓库

6.1 制作一个Electron二进制仓库

在交叉编译的src目录下执行如下脚本,即可在out/目录下得到一个v19.0.6的文件夹。实际上只是重命名了生成的包,然后生成sha256sum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 重命名生成的zip包,并生成文件哈希
set -e
set -x

ELECTRON_VERSION=`cat out/Release/version`

rm -rvf out/v${ELECTRON_VERSION}
mkdir -pv out/v${ELECTRON_VERSION}

mv out/Release/dist.zip out/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-linux-loong64.zip
mv out/Release/chromedriver.zip out/v${ELECTRON_VERSION}/chromedriver-v${ELECTRON_VERSION}-linux-loong64.zip
mv out/Release/ffmpeg.zip out/v${ELECTRON_VERSION}/ffmpeg-v${ELECTRON_VERSION}-linux-loong64.zip
mv out/Release/mksnapshot.zip out/v${ELECTRON_VERSION}/mksnapshot-v${ELECTRON_VERSION}-linux-loong64.zip
mv out/Release/hunspell_dictionaries.zip out/v${ELECTRON_VERSION}/
cp electron/electron.d.ts out/v${ELECTRON_VERSION}/
cp electron/electron-api.json out/v${ELECTRON_VERSION}/

pushd out/v${ELECTRON_VERSION}
sha256sum * > SHASUMS256.txt
sed -i 's/ / */g' SHASUMS256.txt
popd

执行完上面的脚本后,将out/目录下得到的v19.0.6文件夹放到一个http服务器上即可参照下面的配置进行安装了。
也可以按照 https://registry.npmmirror.com/binary.html?path=electron/&spm=a2c6h.24755359.0.0.6d444dcchV4p85 镜像仓库的结构制作即可

6.2 安装方法

安装自己的Electron需要设置两个变量

  • ELECTRON_MIRROR: 用于指向存放二进制zip包的地址目录,最后的“/”不能去掉
  • electron_use_remote_checksums:配置使用远程的 SHASUMS256.txt 进行校验还是使用npm包中自带的进行校验(由于npm包中自带的只有官方包的哈希值,自己搭建的镜像中只能使用远程SHASUMS256.txt进行校验),该环境变量是Electron v15开始引入。

6.2.1 不设置环境变量进行安装

1
2
3
4
5
# 全局安装
$ sudo electron_use_remote_checksums=1 ELECTRON_MIRROR=http://xxx.xxx/xxx/ npm install -g electron@19.0.6

# 非全局安装
$ electron_use_remote_checksums=1 ELECTRON_MIRROR=http://xxx.xxx/xxx/ npm install electron@19.0.6

6.2.2 设置环境变量进行安装

1
2
3
$ export ELECTRON_MIRROR=http://xxx.xxx/xxx/
$ export electron_use_remote_checksums=1
$ npm install electron@19.0.6 //非全局安装

6.3 Electron使用

参照官方文档: https://www.electronjs.org/zh/docs/latest/