From 0d0b333f4014cc9433acba15fd1a08c2a7986716 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 Apr 2023 11:38:26 +0100 Subject: [PATCH] Add support for Linux arm64 (#446) --- .github/workflows/build_and_deploy.yaml | 4 +- .github/workflows/build_and_test.yaml | 17 +++-- .github/workflows/build_linux.yaml | 88 +++++++++++++++++++++---- dockerbuild/Dockerfile | 2 +- dockerbuild/aarch64/.cargo/config.toml | 3 + dockerbuild/aarch64/.env | 11 ++++ hak/keytar/build.ts | 2 +- hak/matrix-seshat/build.ts | 10 +-- hak/matrix-seshat/check.ts | 4 +- 9 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 dockerbuild/aarch64/.cargo/config.toml create mode 100644 dockerbuild/aarch64/.env diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index 5f39167..4d4104b 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -97,6 +97,7 @@ jobs: name: Linux (sqlcipher system) uses: ./.github/workflows/build_linux.yaml with: + arch: amd64 config: element.io/${{ inputs.mode || 'nightly' }} sqlcipher: system version: ${{ needs.prepare.outputs.linux-version }} @@ -108,6 +109,7 @@ jobs: name: Linux (sqlcipher static) uses: ./.github/workflows/build_linux.yaml with: + arch: amd64 deploy-mode: true config: element.io/${{ inputs.mode || 'nightly' }} sqlcipher: static @@ -159,4 +161,4 @@ jobs: uses: ./.github/workflows/reprepro.yaml secrets: inherit with: - artifact-name: linux-sqlcipher-system + artifact-name: linux-amd64-sqlcipher-system diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index c68afbc..dc364cd 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -73,15 +73,22 @@ jobs: needs: - fetch - linux_docker - name: Linux + name: "Linux (${{ matrix.arch }}) (sqlcipher: ${{ matrix.sqlcipher }})" uses: ./.github/workflows/build_linux.yaml strategy: matrix: sqlcipher: [system, static] + arch: [amd64, arm64] + exclude: + # FIXME: This combination yields a broken Seshat at this time + # Errors at launch with `undefined symbol: PKCS5_PBKDF2_HMAC + - arch: arm64 + sqlcipher: static with: config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }} sqlcipher: ${{ matrix.sqlcipher }} docker-image: ${{ needs.linux_docker.outputs.docker-image }} + arch: ${{ matrix.arch }} macos: needs: fetch @@ -101,14 +108,14 @@ jobs: artifact: macos executable: "./dist/mac-universal/Element.app/Contents/MacOS/Element" prepare_cmd: "find ./dist/mac-universal/Element.app -type f | perl -lne 'print if -B' | tr '\\n' '\\0' | xargs -0 -n1 chmod 755" - - name: "Linux (sqlcipher: system)" + - name: "Linux (amd64) (sqlcipher: system)" os: ubuntu - artifact: linux-sqlcipher-system + artifact: linux-amd64-sqlcipher-system executable: "element-desktop" prepare_cmd: "sudo apt install ./dist/*.deb" - - name: "Linux (sqlcipher: static)" + - name: "Linux (amd64) (sqlcipher: static)" os: ubuntu - artifact: linux-sqlcipher-static + artifact: linux-amd64-sqlcipher-static executable: "element-desktop" prepare_cmd: "sudo apt install ./dist/*.deb" - name: Windows (x86) diff --git a/.github/workflows/build_linux.yaml b/.github/workflows/build_linux.yaml index c1bf446..866e2cf 100644 --- a/.github/workflows/build_linux.yaml +++ b/.github/workflows/build_linux.yaml @@ -4,6 +4,10 @@ on: workflow_call: inputs: + arch: + type: string + required: true + description: "The architecture to build for, one of 'amd64' | 'arm64'" config: type: string required: true @@ -33,6 +37,24 @@ jobs: run: shell: bash steps: + - uses: kanga333/variable-mapper@master + id: config + with: + key: "${{ inputs.arch }}" + export_to: output + map: | + { + "amd64": { + "target": "x86_64-unknown-linux-gnu", + "arch": "x86-64" + }, + "arm64": { + "target": "aarch64-unknown-linux-gnu", + "arch": "aarch64", + "build-args": "--arm64" + } + } + - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 @@ -43,7 +65,7 @@ jobs: id: cache uses: actions/cache@v3 with: - key: ${{ runner.os }}-${{ inputs.docker-image || github.ref_name }}-${{ inputs.sqlcipher }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }} + key: ${{ runner.os }}-${{ inputs.docker-image || github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }} path: | ./.hak @@ -58,11 +80,30 @@ jobs: - name: Install Deps run: "yarn install --frozen-lockfile" + - name: Prepare for static sqlcipher build + if: inputs.sqlcipher == 'static' + run: | + echo "SQLCIPHER_STATIC=1" >> $GITHUB_ENV + + # Ideally the docker image would be ready for cross-compilation but libsqlcipher-dev is not Multi-Arch compatible + # https://unix.stackexchange.com/a/349359 + - name: Prepare for cross compilation + if: steps.cache.outputs.cache-hit != 'true' && inputs.arch == 'arm64' + run: | + set -x + sed -i 's/deb http/deb [arch=amd64] http/g' /etc/apt/sources.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main multiverse restricted universe" | tee -a /etc/apt/sources.list + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main multiverse restricted universe" | tee -a /etc/apt/sources.list + dpkg --add-architecture arm64 + apt-get -qq update + apt-get -qq install --no-install-recommends crossbuild-essential-arm64 libsqlcipher-dev:arm64 libssl-dev:arm64 libsecret-1-dev:arm64 libgnome-keyring-dev:arm64 + rustup target add aarch64-unknown-linux-gnu + mv dockerbuild/aarch64/.cargo . + cat dockerbuild/aarch64/.env >> $GITHUB_ENV + - name: Build Natives if: steps.cache.outputs.cache-hit != 'true' - run: "yarn build:native" - env: - SQLCIPHER_STATIC: ${{ inputs.sqlcipher == 'static' && '1' || '' }} + run: "yarn build:native --target ${{ steps.config.outputs.target }}" - name: "[Nightly] Resolve version" id: nightly @@ -77,12 +118,14 @@ jobs: echo "config-args=--deb-changelog changelog.Debian" >> $GITHUB_OUTPUT fi - cp "$DIR/control.template" debcontrol + cat "$DIR/control.template" | grep -v "Architecture: " > debcontrol + echo "Architecture: $ARCHITECTURE" >> debcontrol VERSION=${INPUT_VERSION:-$(cat package.json | jq -r .version)} echo "Version: $VERSION" >> debcontrol env: DIR: ${{ inputs.config }} INPUT_VERSION: ${{ inputs.version }} + ARCHITECTURE: ${{ inputs.arch }} - name: Build App run: | @@ -90,20 +133,37 @@ jobs: ${{ steps.nightly.outputs.config-args }} \ ${{ steps.debian.outputs.config-args }} \ --deb-custom-control=debcontrol - yarn build --publish never -l --config electron-builder.json + yarn build --publish never -l --config electron-builder.json ${{ steps.config.outputs.build-args }} - - name: Check ldd + - name: Check native libraries run: | - ldd dist/linux-unpacked/resources/app.asar.unpacked/node_modules/matrix-seshat/native/index.node + set -x + shopt -s globstar + + FILES=$(file dist/**/*.node) + echo "$FILES" + + if [ grep -v "$ARCH" ]; then + exit 1 + fi + + LIBS=$(readelf -d dist/**/*.node | grep NEEDED) + echo "$LIBS" + if [ "$SQLCIPHER_STATIC" == "1" ]; then - ldd dist/linux-unpacked/resources/app.asar.unpacked/node_modules/matrix-seshat/native/index.node | grep -v libsqlcipher.so.0 - ldd dist/linux-unpacked/resources/app.asar.unpacked/node_modules/matrix-seshat/native/index.node | grep libcrypto.so.1.1 + if grep -q "libsqlcipher.so.0" <<< "$LIBS" ; then + exit 2 + fi else - ldd dist/linux-unpacked/resources/app.asar.unpacked/node_modules/matrix-seshat/native/index.node | grep libsqlcipher.so.0 - ldd dist/linux-unpacked/resources/app.asar.unpacked/node_modules/matrix-seshat/native/index.node | grep -v libcrypto.so.1.1 + if grep -q "libcrypto.so.1.1" <<< "$LIBS" ; then + exit 3 + fi + if ! grep -q "libsqlcipher.so.0" <<< "$LIBS" ; then + exit 4 + fi fi env: - SQLCIPHER_STATIC: ${{ inputs.sqlcipher == 'static' && '1' || '' }} + ARCH: ${{ steps.config.outputs.arch }} - name: Stash deb package if: inputs.deploy-mode @@ -136,6 +196,6 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: ${{ inputs.deploy-mode && 'packages.element.io' || format('linux-sqlcipher-{0}', inputs.sqlcipher) }} + name: ${{ inputs.deploy-mode && 'packages.element.io' || format('linux-{0}-sqlcipher-{1}', inputs.arch, inputs.sqlcipher) }} path: dist retention-days: 1 diff --git a/dockerbuild/Dockerfile b/dockerbuild/Dockerfile index bc4875d..98e098e 100644 --- a/dockerbuild/Dockerfile +++ b/dockerbuild/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get -qq update && apt-get -qq dist-upgrade && \ libsecret-1-dev libgnome-keyring-dev \ libopenjp2-tools \ # Used by github actions \ - jq grep \ + jq grep file \ # Used by seshat (when not SQLCIPHER_STATIC) \ libsqlcipher-dev && \ # git-lfs diff --git a/dockerbuild/aarch64/.cargo/config.toml b/dockerbuild/aarch64/.cargo/config.toml new file mode 100644 index 0000000..3d05042 --- /dev/null +++ b/dockerbuild/aarch64/.cargo/config.toml @@ -0,0 +1,3 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" +rustflags = ["-L/usr/lib/aarch64-linux-gnu"] diff --git a/dockerbuild/aarch64/.env b/dockerbuild/aarch64/.env new file mode 100644 index 0000000..1e7b603 --- /dev/null +++ b/dockerbuild/aarch64/.env @@ -0,0 +1,11 @@ +AS=/usr/bin/aarch64-linux-gnu-as +STRIP=/usr/bin/aarch64-linux-gnu-strip +AR=/usr/bin/aarch64-linux-gnu-ar +CC=/usr/bin/aarch64-linux-gnu-gcc +CPP=/usr/bin/aarch64-linux-gnu-cpp +CXX=/usr/bin/aarch64-linux-gnu-g++ +LD=/usr/bin/aarch64-linux-gnu-ld +FC=/usr/bin/aarch64-linux-gnu-gfortran +PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig +CFLAGS=-L/usr/lib/aarch64-linux-gnu +RUSTFLAGS=-L/usr/lib/aarch64-linux-gnu diff --git a/hak/keytar/build.ts b/hak/keytar/build.ts index c0433c3..ec7c8b5 100644 --- a/hak/keytar/build.ts +++ b/hak/keytar/build.ts @@ -27,7 +27,7 @@ export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: Dependency await new Promise((resolve, reject) => { const proc = childProcess.spawn( path.join(moduleInfo.nodeModuleBinDir, "node-gyp" + (hakEnv.isWin() ? ".cmd" : "")), - ["rebuild"], + ["rebuild", "--arch", hakEnv.getTargetArch()], { cwd: moduleInfo.moduleBuildDir, env, diff --git a/hak/matrix-seshat/build.ts b/hak/matrix-seshat/build.ts index 5a41d89..2753110 100644 --- a/hak/matrix-seshat/build.ts +++ b/hak/matrix-seshat/build.ts @@ -205,14 +205,15 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): P const cflags = ["-DSQLITE_HAS_CODEC"]; - if (!hakEnv.isHost()) { + // If the caller has specified CFLAGS then we shouldn't specify target + // as their compiler may be incompatible (gcc) + if (!hakEnv.isHost() && !process.env.CFLAGS) { // `clang` uses more logical option naming. cflags.push(`--target=${hakEnv.getTargetId()}`); } - if (cflags.length) { - args.push(`CFLAGS=${cflags.join(" ")}`); - } + if (process.env.CFLAGS) cflags.unshift(process.env.CFLAGS); + args.push(`CFLAGS=${cflags.join(" ")}`); const ldflags: string[] = []; @@ -222,6 +223,7 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): P } if (ldflags.length) { + if (process.env.LDFLAGS) ldflags.unshift(process.env.LDFLAGS); args.push(`LDFLAGS=${ldflags.join(" ")}`); } diff --git a/hak/matrix-seshat/check.ts b/hak/matrix-seshat/check.ts index 4725246..a4405e6 100644 --- a/hak/matrix-seshat/check.ts +++ b/hak/matrix-seshat/check.ts @@ -70,7 +70,7 @@ export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom await new Promise((resolve, reject) => { const rustc = childProcess.execFile( "rustc", - ["--target", hakEnv.getTargetId(), "-o", "tmp", "-"], + ["--target", hakEnv.getTargetId(), "--emit=obj", "-o", "tmp", "-"], (err, out) => { if (err) { reject( @@ -86,6 +86,8 @@ export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom }, ); rustc.stdin!.write("fn main() {}"); + rustc.stdout!.pipe(process.stdout); + rustc.stderr!.pipe(process.stderr); rustc.stdin!.end(); }); }