From d3f5d04b309d712d6f71576ffa7270af1c540380 Mon Sep 17 00:00:00 2001 From: Jake Champion Date: Fri, 26 Aug 2022 15:59:29 +0100 Subject: [PATCH] Move to using the same cross-compiling workflow as wasmtime which enables binary compatible builds and compiles for more platforms These prebuilt binaries will eventually be used within a wizer npm package (which itself will be used within the `@fastly/js-compute` npm package). I've tested both the macos builds and they are working I don't have a windows machine to test on and I don't have an x86_64 linux machine to test on unfortunately aarch64-linux and s390x-linux are commented out due to build errors these errors are solved in a newer version of wasmtime, when wizer updates to the newer wasmtime then we should uncomment these builds --- .../binary-compatible-builds/README.md | 9 + .../binary-compatible-builds/action.yml | 10 + .../actions/binary-compatible-builds/main.js | 56 ++++++ .github/actions/install-rust/README.md | 18 ++ .github/actions/install-rust/action.yml | 12 ++ .github/actions/install-rust/main.js | 36 ++++ .github/workflows/ci.yml | 91 +-------- .github/workflows/release.yml | 187 +++++++++++------- ci/build-tarballs.sh | 63 +++--- ci/docker/aarch64-linux/Dockerfile | 7 + ci/docker/s390x-linux/Dockerfile | 7 + ci/docker/x86_64-linux/Dockerfile | 5 + 12 files changed, 322 insertions(+), 179 deletions(-) create mode 100644 .github/actions/binary-compatible-builds/README.md create mode 100644 .github/actions/binary-compatible-builds/action.yml create mode 100755 .github/actions/binary-compatible-builds/main.js create mode 100644 .github/actions/install-rust/README.md create mode 100644 .github/actions/install-rust/action.yml create mode 100644 .github/actions/install-rust/main.js create mode 100644 ci/docker/aarch64-linux/Dockerfile create mode 100644 ci/docker/s390x-linux/Dockerfile create mode 100644 ci/docker/x86_64-linux/Dockerfile diff --git a/.github/actions/binary-compatible-builds/README.md b/.github/actions/binary-compatible-builds/README.md new file mode 100644 index 0000000..8368fd4 --- /dev/null +++ b/.github/actions/binary-compatible-builds/README.md @@ -0,0 +1,9 @@ +# binary-compatible-builds + +A small (ish) action which is intended to be used and will configure builds of +Rust projects to be "more binary compatible". On Windows and macOS this +involves setting a few env vars, and on Linux this involves spinning up a CentOS +6 container which is running in the background. + +All subsequent build commands need to be wrapped in `$CENTOS` to optionally run +on `$CENTOS` on Linux to ensure builds happen inside the container. diff --git a/.github/actions/binary-compatible-builds/action.yml b/.github/actions/binary-compatible-builds/action.yml new file mode 100644 index 0000000..c2950d9 --- /dev/null +++ b/.github/actions/binary-compatible-builds/action.yml @@ -0,0 +1,10 @@ +name: 'Set up a CentOS 6 container to build releases in' +description: 'Set up a CentOS 6 container to build releases in' + +runs: + using: node12 + main: 'main.js' +inputs: + name: + required: true + description: "Name of the build" diff --git a/.github/actions/binary-compatible-builds/main.js b/.github/actions/binary-compatible-builds/main.js new file mode 100755 index 0000000..378c5c2 --- /dev/null +++ b/.github/actions/binary-compatible-builds/main.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +const child_process = require('child_process'); +const stdio = { stdio: 'inherit' }; +const fs = require('fs'); + +function set_env(name, val) { + fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) +} + +// On OSX all we need to do is configure our deployment target as old as +// possible. For now 10.9 is the limit. +if (process.platform == 'darwin') { + set_env("MACOSX_DEPLOYMENT_TARGET", "10.9"); + return; +} + +// On Windows we build against the static CRT to reduce dll dependencies +if (process.platform == 'win32') { + set_env("RUSTFLAGS", "-Ctarget-feature=+crt-static"); + return; +} + +// ... and on Linux we do fancy things with containers. We'll spawn an old +// CentOS container in the background with a super old glibc, and then we'll run +// commands in there with the `$CENTOS` env var. + +if (process.env.CENTOS !== undefined) { + const args = ['exec', '-w', process.cwd(), '-i', 'build-container']; + for (const arg of process.argv.slice(2)) { + args.push(arg); + } + child_process.execFileSync('docker', args, stdio); + return; +} + +const name = process.env.INPUT_NAME; + +child_process.execFileSync('docker', [ + 'build', + '--tag', 'build-image', + `${process.cwd()}/ci/docker/${name}` +], stdio); + +child_process.execFileSync('docker', [ + 'run', + '--detach', + '--interactive', + '--name', 'build-container', + '-v', `${process.cwd()}:${process.cwd()}`, + '-v', `${child_process.execSync('rustc --print sysroot').toString().trim()}:/rust:ro`, + 'build-image', +], stdio); + +// Use ourselves to run future commands +set_env("CENTOS", __filename); diff --git a/.github/actions/install-rust/README.md b/.github/actions/install-rust/README.md new file mode 100644 index 0000000..df8e94d --- /dev/null +++ b/.github/actions/install-rust/README.md @@ -0,0 +1,18 @@ +# install-rust + +A small github action to install `rustup` and a Rust toolchain. This is +generally expressed inline, but it was repeated enough in this repository it +seemed worthwhile to extract. + +Some gotchas: + +* Can't `--self-update` on Windows due to permission errors (a bug in Github + Actions) +* `rustup` isn't installed on macOS (a bug in Github Actions) + +When the above are fixed we should delete this action and just use this inline: + +```yml +- run: rustup update $toolchain && rustup default $toolchain + shell: bash +``` diff --git a/.github/actions/install-rust/action.yml b/.github/actions/install-rust/action.yml new file mode 100644 index 0000000..7a19659 --- /dev/null +++ b/.github/actions/install-rust/action.yml @@ -0,0 +1,12 @@ +name: 'Install Rust toolchain' +description: 'Install both `rustup` and a Rust toolchain' + +inputs: + toolchain: + description: 'Default toolchan to install' + required: false + default: 'stable' + +runs: + using: node12 + main: 'main.js' diff --git a/.github/actions/install-rust/main.js b/.github/actions/install-rust/main.js new file mode 100644 index 0000000..b144d70 --- /dev/null +++ b/.github/actions/install-rust/main.js @@ -0,0 +1,36 @@ +const child_process = require('child_process'); +const toolchain = process.env.INPUT_TOOLCHAIN; +const fs = require('fs'); + +function set_env(name, val) { + fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) +} + +// Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues +// on Windows. +if (process.platform === 'win32') { + child_process.execFileSync('rustup', ['self', 'update']); +} + +child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); +child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); +child_process.execFileSync('rustup', ['default', toolchain]); + +// Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't +// do this on nightly though since there's a fair amount of warning churn there. +if (!toolchain.startsWith('nightly')) { + set_env("RUSTFLAGS", "-D warnings"); +} + +// Save disk space by avoiding incremental compilation, and also we don't use +// any caching so incremental wouldn't help anyway. +set_env("CARGO_INCREMENTAL", "0"); + +// Turn down debuginfo from 2 to 1 to help save disk space +set_env("CARGO_PROFILE_DEV_DEBUG", "1"); +set_env("CARGO_PROFILE_TEST_DEBUG", "1"); + +if (process.platform === 'darwin') { + set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); + set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 194bba1..bb5bd43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,6 @@ name: CI -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] +on: pull_request env: CARGO_TERM_COLOR: always @@ -19,8 +15,14 @@ jobs: os: ubuntu-latest - build: x86_64-macos os: macos-latest + - build: aarch64-macos + os: macos-latest + target: aarch64-apple-darwin - build: x86_64-windows os: windows-latest + - build: x86_64-mingw + os: windows-latest + target: x86_64-pc-windows-gnu steps: - uses: actions/checkout@v2 - name: Build @@ -29,22 +31,6 @@ jobs: run: cargo test --verbose - name: Checking benches run : cargo check --benches - - name: Build release binary - run: cargo build --release --bin wizer --features="env_logger structopt" - - - name: Create dist - run: mkdir dist - - # Move binaries to dist folder - - run: cp target/release/wizer dist - if: matrix.os != 'windows-latest' && matrix.target == '' - - run: cp target/release/wizer.exe dist - if: matrix.build == 'x86_64-windows' - - - uses: actions/upload-artifact@v1 - with: - name: bins-${{ matrix.build }} - path: dist check_fuzz: runs-on: ubuntu-latest @@ -61,66 +47,3 @@ jobs: - run: rustup default stable - run: rustup component add rustfmt - run: cargo fmt --all -- --check - - - # Consumes all published artifacts from all the previous build steps, creates - # a bunch of tarballs for all of them, and then publishes the tarballs - # themselves as an artifact (for inspection) and then optionally creates - # github releases and/or tags for pushes. - publish: - name: Publish - needs: [build, rustfmt] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - # Download all the artifacts that we'll be publishing. Should keep an eye on - # the `download-artifact` repository to see if we can ever get something - # like "download all artifacts" or "download this list of artifacts" - - name: Download x86_64 macOS binaries - uses: actions/download-artifact@v1 - with: - name: bins-x86_64-macos - - name: Download x86_64 Linux binaries - uses: actions/download-artifact@v1 - with: - name: bins-x86_64-linux - - name: Download x86_64 Windows binaries - uses: actions/download-artifact@v1 - with: - name: bins-x86_64-windows - - - name: Calculate tag name - run: | - name=dev - if [[ $GITHUB_REF == refs/tags/v* ]]; then - name=${GITHUB_REF:10} - fi - echo ::set-output name=val::$name - echo TAG=$name >> $GITHUB_ENV - id: tagname - - # Assemble all the build artifacts into tarballs and zip archives. - - name: Assemble tarballs - run: | - ./ci/build-tarballs.sh x86_64-linux - ./ci/build-tarballs.sh x86_64-macos - ./ci/build-tarballs.sh x86_64-windows .exe - - # Upload all assembled tarballs as an artifact of the github action run, so - # that way even PRs can inspect the output. - - uses: actions/upload-artifact@v1 - with: - name: tarballs - path: dist - - # ... and if this was an actual push (tag or `main`) then we publish a - # new release. This'll automatically publish a tag release or update `dev` - # with this `sha` - - name: Publish Release - uses: ./.github/actions/github-release - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) - with: - files: "dist/*" - name: ${{ steps.tagname.outputs.val }} - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00cad17..01121e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,91 +1,132 @@ -# This is nearly entirely copied from -# https://github.com/fastly/Viceroy/blob/b37b89fc07ecd5845f1103380c5e4fae9cc13b30/.github/workflows/release.yml#L1 -# The changes made are to add the `--bin wizer` flag and change the filesystem paths to use wizer instead of viceroy name: Release - on: push: - tags: - - "v*.*.*" + branches: [main] + tags-ignore: [dev] + pull_request: +defaults: + run: + shell: bash + +# Cancel any in-flight jobs for the same PR/branch so there's only one active +# at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: + runs-on: ${{ matrix.os }} strategy: matrix: - rust-toolchain: [stable] - os: [ubuntu-latest, macos-11, windows-latest] - arch: [amd64, arm64] - exclude: - - os: windows-latest - arch: arm64 include: - - os: ubuntu-latest - name: linux - rust_abi: unknown-linux-gnu - - os: macos-11 - name: darwin - rust_abi: apple-darwin - - os: windows-latest - name: windows - rust_abi: pc-windows-msvc - extension: .exe - - arch: arm64 - rust_arch: aarch64 - - arch: amd64 - rust_arch: x86_64 + - build: x86_64-linux + os: ubuntu-latest + - build: x86_64-macos + os: macos-latest + - build: aarch64-macos + os: macos-latest + target: aarch64-apple-darwin + - build: x86_64-windows + os: windows-latest + - build: x86_64-mingw + os: windows-latest + target: x86_64-pc-windows-gnu + + # TODO: aarch64-linux and s390x-linux are commented out due to build errors + # these errors are solved in a newer version of wasmtime, when wizer + # updates to the newer wasmtime then we should uncomment these builds + + #src/arch/aarch64.S: Assembler messages: + #src/arch/aarch64.S:21: Error: operand 1 should be a floating-point register -- `stp lr,fp,[sp,-16]!' + #src/arch/aarch64.S:50: Error: operand 1 should be a floating-point register -- `ldp lr,fp,[sp],16' + #src/arch/aarch64.S:90: Error: bad register expression + #exit status: 1 + #- build: aarch64-linux + #os: ubuntu-latest + #target: aarch64-unknown-linux-gnu + + #/rust/lib/rustlib/s390x-unknown-linux-gnu/lib/libstd-f6c951af7877beaf.rlib(std-f6c951af7877beaf.std.e2801c51-cgu.0.rcgu.o): In function `std::sys::unix::rand::imp::getrandom_fill_bytes::hb641b796bca799e8': + #/rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library/std/src/sys/unix/rand.rs:(.text._ZN3std3sys4unix4rand19hashmap_random_keys17h949023c83f75d545E+0x30): undefined reference to `getrandom' + #/rust/lib/rustlib/s390x-unknown-linux-gnu/lib/libstd-f6c951af7877beaf.rlib(std-f6c951af7877beaf.std.e2801c51-cgu.0.rcgu.o): In function `std::sys::unix::rand::imp::getrandom::getrandom::h09b00f3fdb24c5b7': + #/rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library/std/src/sys/unix/weak.rs:176: undefined reference to `getrandom' + #/rustc/4b91a6ea7258a947e59c6522cd5898e7c0a6a88f/library/std/src/sys/unix/weak.rs:176: undefined reference to `getrandom' + #collect2: error: ld returned 1 exit status + #- build: s390x-linux + #os: ubuntu-latest + #target: s390x-unknown-linux-gnu + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: ./.github/actions/install-rust + - uses: ./.github/actions/binary-compatible-builds + with: + name: ${{ matrix.build }} + - run: | + echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV + rustup target add ${{ matrix.target }} + if: matrix.target != '' - runs-on: ${{ matrix.os }} + # Build `wizer` and executables + - run: $CENTOS cargo build --release --locked --bin wizer --all-features + + # Assemble release artifats appropriate for this platform, then upload them + # unconditionally to this workflow's files so we have a copy of them. + - run: ./ci/build-tarballs.sh "${{ matrix.build }}" "${{ matrix.target }}" + - uses: actions/upload-artifact@v1 + with: + name: bins-${{ matrix.build }} + path: dist + publish: + needs: [build] + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v2 + # Download all the artifacts that we'll be publishing. Should keep an eye on + # the `download-artifact` repository to see if we can ever get something + # like "download all artifacts" or "download this list of artifacts" + - uses: actions/download-artifact@v1 with: - submodules: true - - - name: Install latest Rust toolchain - uses: actions-rs/toolchain@v1 + name: bins-x86_64-macos + path: dist + - uses: actions/download-artifact@v1 with: - toolchain: ${{ matrix.rust-toolchain }} - target: ${{ matrix.rust_arch }}-${{ matrix.rust_abi }} - default: true - override: true - - - name: Install C cross-compilation toolchain - if: ${{ matrix.name == 'linux' && matrix.arch != 'amd64' }} - run: | - sudo apt-get update - sudo apt install -f -y gcc-${{ matrix.rust_arch }}-linux-gnu - echo CC=${{ matrix.rust_arch }}-linux-gnu-gcc >> $GITHUB_ENV - echo RUSTFLAGS='-C linker=${{ matrix.rust_arch }}-linux-gnu-gcc' >> $GITHUB_ENV - - - name: Extract tag name - uses: olegtarasov/get-tag@v2.1 - id: tagName - - - name: Build - uses: actions-rs/cargo@v1 + name: bins-aarch64-macos + path: dist + - uses: actions/download-artifact@v1 with: - command: build - args: --release --locked --bin wizer --target=${{ matrix.rust_arch }}-${{ matrix.rust_abi }} --all-features - - - name: Strip symbols (linux) - if: ${{ matrix.name == 'linux' }} - run: | - ${{ matrix.rust_arch }}-linux-gnu-strip target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/wizer${{ matrix.extension }} - - - name: Strip symbols (non-linux) - if: ${{ matrix.name != 'linux' }} - run: | - strip target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/wizer${{ matrix.extension }} + name: bins-x86_64-windows + path: dist + - uses: actions/download-artifact@v1 + with: + name: bins-x86_64-mingw + path: dist + - uses: actions/download-artifact@v1 + with: + name: bins-x86_64-linux + path: dist - - name: Package + - name: Calculate tag name run: | - cd target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release - tar czf wizer_${{ steps.tagName.outputs.tag }}_${{ matrix.name }}-${{ matrix.arch }}.tar.gz wizer${{ matrix.extension }} + name=dev + if [[ $GITHUB_REF == refs/tags/v* ]]; then + name=${GITHUB_REF:10} + fi + echo ::set-output name=val::$name + echo TAG=$name >> $GITHUB_ENV + id: tagname - - name: Release - uses: softprops/action-gh-release@v1 + # ... and if this was an actual push (tag or `main`) then we publish a + # new release. This'll automatically publish a tag release or update `dev` + # with this `sha`. Note that `continue-on-error` is set here so if this hits + # a bug we can go back and fetch and upload the release ourselves. + - run: cd .github/actions/github-release && npm install --production + - name: Publish Release + uses: ./.github/actions/github-release + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) with: - files: | - target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/wizer_${{ steps.tagName.outputs.tag }}_${{ matrix.name }}-${{ matrix.arch }}.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + files: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + name: ${{ steps.tagname.outputs.val }} + continue-on-error: true diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index 530d704..4e31640 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -1,41 +1,60 @@ #!/bin/bash -# A small shell script invoked from CI on the final Linux builder which actually -# assembles the release artifacts for a particular platform. This will take the -# binary artifacts of previous builders and create associated tarballs to -# publish to GitHub. +# A small script used for assembling release tarballs for the `wizer` +# binary. This is executed with two arguments, mostly coming from +# the CI matrix. # -# The first argument of this is the "platform" name to put into the tarball, and -# the second argument is the name of the github actions platform which is where -# we source binaries from. The final third argument is ".exe" on Windows to -# handle executable extensions right. +# The first argument is the name of the platform, used to name the release +# The second argument is the "target", if present, currently only for +# cross-compiles # -# Usage: build-tarballs.sh PLATFORM [.exe] - -# where PLATFORM is e.g. x86_64-linux, aarch64-linux, ... +# This expects the build to already be done and will assemble release artifacts +# in `dist/` set -ex platform=$1 -exe=$2 +target=$2 rm -rf tmp mkdir tmp mkdir -p dist +tag=dev +if [[ $GITHUB_REF == refs/tags/v* ]]; then + tag=${GITHUB_REF:10} +fi + +bin_pkgname=wizer-$tag-$platform + +mkdir tmp/$bin_pkgname +cp LICENSE README.md tmp/$bin_pkgname + +fmt=tar +if [ "$platform" = "x86_64-windows" ]; then + cp target/release/wizer.exe tmp/$bin_pkgname + fmt=zip +elif [ "$platform" = "x86_64-mingw" ]; then + cp target/x86_64-pc-windows-gnu/release/wizer.exe tmp/$bin_pkgname + fmt=zip +elif [ "$target" = "" ]; then + cp target/release/wizer tmp/$bin_pkgname +else + cp target/$target/release/wizer tmp/$bin_pkgname +fi + + mktarball() { dir=$1 - if [ "$exe" = "" ]; then - tar cJf dist/$dir.tar.xz -C tmp $dir + if [ "$fmt" = "tar" ]; then + # this is a bit wonky, but the goal is to use `xz` with threaded compression + # to ideally get better performance with the `-T0` flag. + tar -cvf - -C tmp $dir | xz -9 -T0 > dist/$dir.tar.xz else - (cd tmp && zip -r ../dist/$dir.zip $dir) + # Note that this runs on Windows, and it looks like GitHub Actions doesn't + # have a `zip` tool there, so we use something else + (cd tmp && 7z a ../dist/$dir.zip $dir/) fi } -# Create the main tarball of binaries -bin_pkgname=wizer-$TAG-$platform -mkdir tmp/$bin_pkgname -cp README.md tmp/$bin_pkgname -mv bins-$platform/wizer$exe tmp/$bin_pkgname -chmod +x tmp/$bin_pkgname/wizer$exe -mktarball $bin_pkgname +mktarball $bin_pkgname \ No newline at end of file diff --git a/ci/docker/aarch64-linux/Dockerfile b/ci/docker/aarch64-linux/Dockerfile new file mode 100644 index 0000000..738688b --- /dev/null +++ b/ci/docker/aarch64-linux/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:16.04 + +RUN apt-get update -y && apt-get install -y gcc gcc-aarch64-linux-gnu ca-certificates + +ENV PATH=$PATH:/rust/bin +ENV CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu +ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ No newline at end of file diff --git a/ci/docker/s390x-linux/Dockerfile b/ci/docker/s390x-linux/Dockerfile new file mode 100644 index 0000000..f25df1b --- /dev/null +++ b/ci/docker/s390x-linux/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:16.04 + +RUN apt-get update -y && apt-get install -y gcc gcc-s390x-linux-gnu ca-certificates + +ENV PATH=$PATH:/rust/bin +ENV CARGO_BUILD_TARGET=s390x-unknown-linux-gnu +ENV CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-linux-gnu-gcc \ No newline at end of file diff --git a/ci/docker/x86_64-linux/Dockerfile b/ci/docker/x86_64-linux/Dockerfile new file mode 100644 index 0000000..388eddf --- /dev/null +++ b/ci/docker/x86_64-linux/Dockerfile @@ -0,0 +1,5 @@ +FROM centos:7 + +RUN yum install -y git gcc + +ENV PATH=$PATH:/rust/bin \ No newline at end of file