From 19b0efcb0e731b201c6daea3619f39db3f3acdb6 Mon Sep 17 00:00:00 2001 From: Marek Rusinowski Date: Sun, 20 Oct 2024 00:27:56 +0200 Subject: [PATCH] Add new Docker based build system (#1730) The new system is primarily targeting efficiency: - Smaller and quicker to build Docker build environment images - Storage of Docker images in registry instead of cache that expires - Leverages much more performant namespace.so GitHub actions runners - Each step is optimized for parallelism to leverage new runners - Heavy cross branch, more persistent, caching of compilation objects The system is usable on CI but also provides scripts to be able to run containerized build locally. --- .github/workflows/docker-images-build.yml | 43 +++++ .github/workflows/engine-build.yml | 172 ++++++++++++++++++ docker-build-v2/README.md | 89 +++++++++ docker-build-v2/amd64-linux/Dockerfile | 56 ++++++ docker-build-v2/amd64-linux/ccache.conf | 2 + docker-build-v2/amd64-linux/toolchain.cmake | 7 + docker-build-v2/amd64-windows/Dockerfile | 28 +++ docker-build-v2/amd64-windows/ccache.conf | 2 + docker-build-v2/amd64-windows/toolchain.cmake | 10 + docker-build-v2/build.sh | 68 +++++++ docker-build-v2/scripts/compile.sh | 14 ++ docker-build-v2/scripts/configure.sh | 11 ++ docker-build-v2/scripts/package.sh | 23 +++ docker-build-v2/scripts/split-debug-info.sh | 26 +++ 14 files changed, 551 insertions(+) create mode 100644 .github/workflows/docker-images-build.yml create mode 100644 .github/workflows/engine-build.yml create mode 100644 docker-build-v2/README.md create mode 100644 docker-build-v2/amd64-linux/Dockerfile create mode 100644 docker-build-v2/amd64-linux/ccache.conf create mode 100644 docker-build-v2/amd64-linux/toolchain.cmake create mode 100644 docker-build-v2/amd64-windows/Dockerfile create mode 100644 docker-build-v2/amd64-windows/ccache.conf create mode 100644 docker-build-v2/amd64-windows/toolchain.cmake create mode 100755 docker-build-v2/build.sh create mode 100755 docker-build-v2/scripts/compile.sh create mode 100755 docker-build-v2/scripts/configure.sh create mode 100755 docker-build-v2/scripts/package.sh create mode 100755 docker-build-v2/scripts/split-debug-info.sh diff --git a/.github/workflows/docker-images-build.yml b/.github/workflows/docker-images-build.yml new file mode 100644 index 0000000000..59839eb25e --- /dev/null +++ b/.github/workflows/docker-images-build.yml @@ -0,0 +1,43 @@ +# Builds docker images used by engine-build.yml +name: Docker Images Build +on: + workflow_dispatch: + push: + branches: + - master + paths: + - 'docker-build-v2/amd64-linux/**' + - 'docker-build-v2/amd64-windows/**' + pull_request: + paths: + - 'docker-build-v2/amd64-linux/**' + - 'docker-build-v2/amd64-windows/**' +jobs: + build-images: + if: github.repository == 'beyond-all-reason/spring' || github.event_name == 'workflow_dispatch' + name: Build ${{ matrix.system }} docker image + runs-on: ubuntu-latest + strategy: + matrix: + system: + - amd64-linux + - amd64-windows + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: docker-build-v2/${{ matrix.system }} + push: ${{ github.event_name != 'pull_request' }} + tags: ghcr.io/${{ github.repository_owner }}/recoil-build-${{ matrix.system }}:latest diff --git a/.github/workflows/engine-build.yml b/.github/workflows/engine-build.yml new file mode 100644 index 0000000000..399c7a09fe --- /dev/null +++ b/.github/workflows/engine-build.yml @@ -0,0 +1,172 @@ +# Engine CI workflow that compiles engine using build-docker-v2 scripts with +# https://namespace.so/ GitHub Actions Runners. +# +# The worklow has multiple layers of caching for ccache compilation objects: +# +# - Local cache on runner's machine SSD disk, very efficient but prone to +# cache misses when workflow can't be scheduled next to the storage +# - Remote, higher latency, S3 based storage using Cloudflare R2 bucket +# - For pull requests only: GitHub Actions Cache archive build by mounting +# overlay filesystem on top of two other read-only caches (because pull +# requests can't have write access for security reasons) to reduce build +# times for subsequent pushes to the same PR branch. +# +# Because ccache can't use S3 based storage directly, we are leveraging +# bazel-remote project that supports it, and ccache supports storing artifacts +# in bazel remote caching HTTP layout. + +name: Build Engine v2 +on: + workflow_dispatch: + inputs: + recache: + description: "Recache ccache build objects" + type: boolean + default: false + # TODO: Uncomment after it's merged and docker images are availabe + # pull_request: + # paths-ignore: + # - 'doc/**' + # push: + # branches: + # - master + # # TODO: add release branches once their naming is finalized + # paths-ignore: + # - 'doc/**' +jobs: + build-engine: + if: github.repository == 'beyond-all-reason/spring' || github.event_name == 'workflow_dispatch' + strategy: + matrix: + system: + - amd64-linux + - amd64-windows + runs-on: + # Run on 8 core, 16GiB ubuntu 24.04 machine. + - nscloud-ubuntu-24.04-amd64-8x16-with-cache + # Mount named cache volume `engine-${{ matrix.system }}`. + - nscloud-cache-tag-engine-${{ matrix.system }} + # The cache volume should be 20GiB (minimal size). + - nscloud-cache-size-20gb + # Also cache the git checkouts in datacenter local namespace git mirror + # to speed them up. + - nscloud-git-mirror-5gb + # Also cache docker pulls in volume so images don't have to be pulled + # and unpacked every time. + - nscloud-exp-container-image-cache + steps: + - name: Checkout code + uses: namespacelabs/nscloud-checkout-action@v5 + with: + # We fetch full history because engine automatic version naming uses + # that. + fetch-depth: 0 + submodules: recursive + dissociate: true + path: src + - name: Setup ccache cache + uses: namespacelabs/nscloud-cache-action@v1 + with: + path: | + bazel-remote-data + tools + - name: Restore pull request's bazel remote cache overlay + id: pr-cache-restore + if: github.event_name == 'pull_request' + uses: actions/cache/restore@v4 + with: + path: bazel-remote-data-overlay.tar + key: pr-bazel-remote-data-${{ matrix.system }}-${{ github.run_id }} + restore-keys: pr-bazel-remote-data-${{ matrix.system }}- + - name: Mount bazel remote overlay + id: mount-overlay + if: github.event_name == 'pull_request' + run: | + sudo apt-get install --yes fuse-overlayfs + sudo tar --acls --xattrs --xattrs-include='*' -xf bazel-remote-data-overlay.tar || mkdir bazel-remote-data-overlay + mkdir -p overlay-workdir bazel-remote-data-merged + sudo fuse-overlayfs -o lowerdir=bazel-remote-data,upperdir=bazel-remote-data-overlay,workdir=overlay-workdir bazel-remote-data-merged + - name: Start remote ccache fetcher + uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1.0.7 + env: + BAZEL_REMOTE_S3_ENDPOINT: ${{ vars.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com + BAZEL_REMOTE_S3_BUCKET: ${{ vars.R2_BUCKET_BUILD_CACHE }} + BAZEL_REMOTE_S3_ACCESS_KEY_ID: ${{ github.event_name == 'pull_request' && vars.R2_RO_ACCESS_KEY_ID || vars.R2_ACCESS_KEY_ID }} + BAZEL_REMOTE_S3_SECRET_ACCESS_KEY: ${{ github.event_name == 'pull_request' && vars.R2_RO_ACCESS_KEY_SECRET || secrets.R2_ACCESS_KEY_SECRET }} + with: + run: | + if ! sha256sum --status -c <<< "8679a76074b1408a95d2b3ec0f5b1a6d0c20500cfc24c3a87ef08c1b60200f8c tools/bazel-remote"; then + curl -L https://github.com/buchgr/bazel-remote/releases/download/v2.4.4/bazel-remote-2.4.4-linux-x86_64 -o bazel-remote + chmod +x bazel-remote + mv bazel-remote tools/bazel-remote + fi + + cat > remote_ccache.conf < /dev/null)" ]]; then + image=ghcr.io/beyond-all-reason/recoil-build-amd64-$OS:latest + docker pull $image > /dev/null +fi + +docker run -it --rm \ + -v /etc/passwd:/etc/passwd:ro \ + -v /etc/group:/etc/group:ro \ + --user=$(id -u):$(id -g) \ + -v $(pwd):/build/src:ro \ + -v $(pwd)/.cache/ccache-$OS:/build/cache:rw \ + -v $(pwd)/build-$OS:/build/out:rw \ + -e CONFIGURE \ + -e COMPILE \ + $image \ + bash -c ' +set -e +echo "$@" +cd /build/src/docker-build-v2/scripts +$CONFIGURE && ./configure.sh "$@" +$COMPILE && ./compile.sh +' -- "$@" diff --git a/docker-build-v2/scripts/compile.sh b/docker-build-v2/scripts/compile.sh new file mode 100755 index 0000000000..685928070a --- /dev/null +++ b/docker-build-v2/scripts/compile.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +cmake --build /build/out +cmake --install /build/out + +# Manually remove the lib, include and share directories coming +# from the GNUInstallDirs. Unfortunately it's not possible to +# easily disable in CMake all installation targets exported when +# using add_subdirectory so we drop them from installation manually. +# This is done to heavily reduce the resulting install size. +cd /build/out/install +rm -rf lib include share diff --git a/docker-build-v2/scripts/configure.sh b/docker-build-v2/scripts/configure.sh new file mode 100755 index 0000000000..f44da06948 --- /dev/null +++ b/docker-build-v2/scripts/configure.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +cmake --fresh -S /build/src -B /build/out \ + -DCMAKE_INSTALL_PREFIX:PATH=/build/out/install \ + -DAI_EXCLUDE_REGEX="^CppTestAI$" \ + -DUSERDOCS_PLAIN=ON \ + -DCMAKE_BUILD_TYPE=RELWITHDEBINFO \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" \ + -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" \ + -G Ninja \ + "$@" diff --git a/docker-build-v2/scripts/package.sh b/docker-build-v2/scripts/package.sh new file mode 100755 index 0000000000..903477ccf4 --- /dev/null +++ b/docker-build-v2/scripts/package.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e -u -o pipefail + +cd /build/src + +branch=$(git rev-parse --abbrev-ref HEAD) +tag_name="{$branch}$(git describe --abbrev=7)_$ENGINE_PLATFORM" +bin_name=spring_bar_$tag_name-minimal-portable.7z +dbg_name=spring_bar_$tag_name-minimal-symbols.tar.zst + +cd /build/out/install + +# Compute md5 hashes of all files in archive. We additionally gzip it as gzip adds +# checksum to the list itself. To validate just `zcat files.md5.gz | md5sum -c -` +find . -type f ! -name '*.dbg' ! -name files.md5.gz -exec md5sum {} \; | gzip > files.md5.gz + +rm -f /build/artifacts/$bin_name /build/artifacts/$dbg_name + +# Trigger compression of main binaries and debug info concurrently +7z a -t7z -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on /build/artifacts/$bin_name ./* -xr\!*.dbg & +tar cvf - $(find ./ -name '*.dbg') | zstd -T0 > /build/artifacts/$dbg_name & +wait diff --git a/docker-build-v2/scripts/split-debug-info.sh b/docker-build-v2/scripts/split-debug-info.sh new file mode 100755 index 0000000000..3b84b9e515 --- /dev/null +++ b/docker-build-v2/scripts/split-debug-info.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e -u -o pipefail + +cd /build/out/install + +function split_debug { + set -e -u -o pipefail + + file="$1" + if ! objdump -h "$file" | grep -q ".debug_" || objdump -h "$file" | grep -q .gnu_debuglink; then + echo "skipping $file" + return + fi + echo "stripping $file" + filename="$(basename "$file")" + debugfile="$(dirname "$file")/${filename%.*}.dbg" + objcopy --only-keep-debug ${file} ${debugfile} + strip --strip-debug --strip-unneeded ${file} + objcopy --add-gnu-debuglink=${debugfile} ${file} +} +export -f split_debug + +# We split debug info from all binaries in parallel using xargs -P0 +find \( -regex '.*\.\(dll\|so\|exe\)' -o -type f ! -name '*.dbg' -executable \) -print0 \ + | xargs -0 -P0 -n1 bash -c 'split_debug "$0"'