From 32b428f49b927249329386388eab8152a1d2920a Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 5 Aug 2026 09:18:37 -0500 Subject: [PATCH] fix: compile xla before exla in CI image; make it a build pre-req MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile.ci: compile xla dep before exla — exla's extract_xla calls XLA.archive_path!/0 which requires xla to be compiled first - Fold build-ci-image into build.yaml as prerequisite job so tests always have the precompiled EXLA image available - Remove standalone build-ci-image.yaml (redundant) --- .forgejo/workflows/build-ci-image.yaml | 81 -------------------------- .forgejo/workflows/build.yaml | 61 +++++++++++++++---- Dockerfile.ci | 10 ++-- 3 files changed, 57 insertions(+), 95 deletions(-) delete mode 100644 .forgejo/workflows/build-ci-image.yaml diff --git a/.forgejo/workflows/build-ci-image.yaml b/.forgejo/workflows/build-ci-image.yaml deleted file mode 100644 index 777d325a..00000000 --- a/.forgejo/workflows/build-ci-image.yaml +++ /dev/null @@ -1,81 +0,0 @@ -name: Build CI test image - -on: - push: - branches: - - main - paths: - - 'mix.exs' - - 'mix.lock' - - 'vendor/**' - - 'Dockerfile.ci' - - '.forgejo/workflows/build-ci-image.yaml' - schedule: - - cron: '0 8 * * 1' - workflow_dispatch: - -env: - REGISTRY: git.mcintire.me - OWNER: graham - IMAGE_NAME: prop-ci - DOCKER_CLI_VERSION: '28.5.2' - BUILDX_VERSION: '0.35.0' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - build-and-push: - name: Build and push CI image - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Docker CLI and buildx - run: | - set -euo pipefail - curl -fsSL --retry 3 --retry-delay 2 \ - "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz" \ - | tar xz --strip-components=1 -C /usr/local/bin docker/docker - mkdir -p "$HOME/.docker/cli-plugins" - curl -fsSL --retry 3 --retry-delay 2 \ - -o "$HOME/.docker/cli-plugins/docker-buildx" \ - "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-amd64" - chmod +x "$HOME/.docker/cli-plugins/docker-buildx" - docker version - docker buildx version - - - name: Build and push - env: - REGISTRY_TOKEN: ${{ secrets.FORGEJO_TOKEN }} - run: | - set -euo pipefail - - IMAGE="${REGISTRY}/${OWNER}/${IMAGE_NAME}" - SHA_TAG="${GITHUB_SHA::7}" - - echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$OWNER" --password-stdin - - DOCKER_BUILDKIT=1 docker build \ - --file Dockerfile.ci \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --cache-from "${IMAGE}:latest" \ - --tag "${IMAGE}:${SHA_TAG}" \ - --tag "${IMAGE}:latest" \ - . - - docker push "${IMAGE}:${SHA_TAG}" - docker push "${IMAGE}:latest" - - { - echo "### CI image pushed" - echo "" - echo "- \`${IMAGE}:${SHA_TAG}\`" - echo "- \`${IMAGE}:latest\`" - } >> "$GITHUB_STEP_SUMMARY" - - docker image rm "${IMAGE}:${SHA_TAG}" "${IMAGE}:latest" 2>/dev/null || true - docker logout "$REGISTRY" || true diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 5a1f3da8..105cbe11 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -9,6 +9,7 @@ env: REGISTRY: git.mcintire.me OWNER: graham IMAGE_NAME: prop + CI_IMAGE_NAME: prop-ci DOCKER_CLI_VERSION: '28.5.2' BUILDX_VERSION: '0.35.0' @@ -17,9 +18,59 @@ concurrency: cancel-in-progress: true jobs: + # Build and push the CI test image first. The test step in the next job + # needs prop-ci:latest with EXLA precompiled to avoid OOM-killing g++. + build-ci-image: + name: Build CI test image + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Docker CLI and buildx + run: | + set -euo pipefail + curl -fsSL --retry 3 --retry-delay 2 \ + "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz" \ + | tar xz --strip-components=1 -C /usr/local/bin docker/docker + mkdir -p "$HOME/.docker/cli-plugins" + curl -fsSL --retry 3 --retry-delay 2 \ + -o "$HOME/.docker/cli-plugins/docker-buildx" \ + "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-amd64" + chmod +x "$HOME/.docker/cli-plugins/docker-buildx" + docker version + docker buildx version + + - name: Build and push CI image + env: + REGISTRY_TOKEN: ${{ secrets.FORGEJO_TOKEN }} + run: | + set -euo pipefail + + IMAGE="${REGISTRY}/${OWNER}/${CI_IMAGE_NAME}" + SHA_TAG="${GITHUB_SHA::7}" + + echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$OWNER" --password-stdin + + DOCKER_BUILDKIT=1 docker build \ + --file Dockerfile.ci \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --cache-from "${IMAGE}:latest" \ + --tag "${IMAGE}:${SHA_TAG}" \ + --tag "${IMAGE}:latest" \ + . + + docker push "${IMAGE}:${SHA_TAG}" + docker push "${IMAGE}:latest" + + docker image rm "${IMAGE}:${SHA_TAG}" "${IMAGE}:latest" 2>/dev/null || true + docker logout "$REGISTRY" || true + build-and-push: name: Build and Push Docker Image runs-on: ubuntu-latest + needs: build-ci-image services: postgres: @@ -40,16 +91,11 @@ jobs: uses: actions/checkout@v4 - name: Install Docker CLI and buildx - # The runner automounts the host Docker socket, so we only need the - # client. No Kaniko, no build action. run: | set -euo pipefail curl -fsSL --retry 3 --retry-delay 2 \ "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz" \ | tar xz --strip-components=1 -C /usr/local/bin docker/docker - # Docker 23+ routes `docker build` through buildx. The static tarball - # ships only the CLI, so the plugin has to be installed separately or - # DOCKER_BUILDKIT=1 fails with "buildx component is missing". mkdir -p "$HOME/.docker/cli-plugins" curl -fsSL --retry 3 --retry-delay 2 \ -o "$HOME/.docker/cli-plugins/docker-buildx" \ @@ -61,9 +107,6 @@ jobs: - name: Verify compilation run: | set -euo pipefail - # Pipe the workspace into the container via stdin instead of a volume - # mount, because the runner is itself a container whose filesystem the - # host Docker daemon cannot see. tar --exclude='_build' --exclude='deps' --exclude='.git' \ --exclude='priv/static/assets' --exclude='assets/node_modules' \ --exclude='rust' --exclude='target' \ @@ -113,8 +156,6 @@ jobs: echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$OWNER" --password-stdin - # One build, both tags. This was previously two invocations of a build - # action, i.e. the whole image was compiled twice just to add `latest`. DOCKER_BUILDKIT=1 docker build \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --cache-from "${IMAGE}:latest" \ diff --git a/Dockerfile.ci b/Dockerfile.ci index 46a2afa8..6c9dbc8f 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -38,11 +38,13 @@ COPY vendor /tmp/exla_warm/vendor RUN mkdir -p /tmp/exla_warm/config && \ printf 'import Config\nconfig :nx, :default_backend, EXLA.Backend\n' > /tmp/exla_warm/config/config.exs -# Fetch deps and compile EXLA. The cached_make compiler downloads the XLA +# Fetch deps and compile EXLA. xla must be compiled first — exla's +# extract_xla compiler calls XLA.archive_path!/0 at build time. +# mix deps.compile exla alone skips transitive deps, so we explicitly +# order xla → exla. The cached_make compiler then downloads the XLA # archive, extracts it, compiles the C++ NIF, and caches libexla.so at -# ~/.cache/xla/exla/{cache_key}/libexla.so. This layer is cached by -# Docker until mix.exs/mix.lock/vendor change. -RUN mix deps.get && mix deps.compile exla +# ~/.cache/xla/exla/{cache_key}/libexla.so. +RUN mix deps.get && mix deps.compile xla && mix deps.compile exla # Clean up the temp project — the cache in ~/.cache/xla/ persists in # the image and is what matters.