From 725327d76ba747bea03d2deaa261f3b9aa632371 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Sun, 25 Oct 2026 11:25:32 +0100 Subject: [PATCH] Build - Switch to UI for build --- .../test-stack-reusable-workflow.yml | 25 ++++----- Dockerfile | 52 +++++++++++-------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test-stack-reusable-workflow.yml b/.github/workflows/test-stack-reusable-workflow.yml index c4c6f3e42..08c9186d6 100644 --- a/.github/workflows/test-stack-reusable-workflow.yml +++ b/.github/workflows/test-stack-reusable-workflow.yml @@ -28,21 +28,9 @@ jobs: with: persist-credentials: false - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v7 - with: - python-version: ${{ env.PYTHON_VERSION }} + # No setup-python / host pip cache here, everything is installed inside the Docker build - - name: Cache pip packages - uses: actions/cache@v6 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-py${{ env.PYTHON_VERSION }}-${{ hashFiles('requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip-py${{ env.PYTHON_VERSION }}- - ${{ runner.os }}-pip- - - - name: Get current date for cache key + - name: Get current date for the daily dependency refresh id: date run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT @@ -57,10 +45,15 @@ jobs: build-args: | PYTHON_VERSION=${{ env.PYTHON_VERSION }} LOGGER_LEVEL=TRACE + DEPS_REFRESH_DATE=${{ steps.date.outputs.date }} tags: test-changedetectionio load: true - cache-from: type=gha,scope=build-${{ github.ref_name }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('requirements.txt', 'Dockerfile') }}-${{ steps.date.outputs.date }} - cache-to: type=gha,mode=max,scope=build-${{ github.ref_name }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('requirements.txt', 'Dockerfile') }}-${{ steps.date.outputs.date }} + # The date is a build-arg rather than part of the scope, so only the deps layer refreshes daily. + # A PR's first push (and anything with changed requirements/Dockerfile) falls back to master's cache. + cache-from: | + type=gha,scope=build-${{ github.ref_name }}-py${{ env.PYTHON_VERSION }} + type=gha,scope=build-master-py${{ env.PYTHON_VERSION }} + cache-to: type=gha,mode=max,scope=build-${{ github.ref_name }}-py${{ env.PYTHON_VERSION }} - name: Verify build run: | diff --git a/Dockerfile b/Dockerfile index 99cd0de0c..64f764921 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,15 @@ WORKDIR /install COPY requirements.txt /requirements.txt -# Use cache mounts and multiple wheel sources for faster ARM builds -ENV PIP_CACHE_DIR=/tmp/pip-cache +# uv resolves and downloads in parallel, pip did ~170 serial index round-trips (~130s cold on amd64). +# Installed via pip rather than the ghcr.io/astral-sh/uv image because that image doesn't cover arm/v7. +RUN pip install --no-cache-dir uv~=0.9 +# UV_LINK_MODE=copy: the cache mount is a different filesystem from /dependencies, hardlinking would fail +# UV_COMPILE_BYTECODE=1: pip writes .pyc at install time and uv doesn't, otherwise every container pays at startup +ENV UV_CACHE_DIR=/tmp/uv-cache \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + UV_PYTHON=/usr/local/bin/python3 # Help Rust find OpenSSL for cryptography package compilation on ARM ENV PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig" ENV PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 @@ -35,44 +42,47 @@ ENV OPENSSL_INCLUDE_DIR="/usr/include/openssl" # Additional environment variables for cryptography Rust build ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 -# --only-binary=litellm: never compile litellm's Rust extension from the sdist. Under QEMU that takes +# Changing this re-resolves the unpinned requirements without invalidating the apt layers above (CI passes the date) +ARG DEPS_REFRESH_DATE='' +ARG TARGETPLATFORM + +# --only-binary litellm: never compile litellm's Rust extension from the sdist. Under QEMU that takes # hours and then fails, this makes a platform without a wheel fail in seconds instead (see requirements.txt) -RUN --mount=type=cache,id=pip,sharing=locked,target=/tmp/pip-cache \ - pip install \ - --prefer-binary \ - --only-binary=litellm \ - --extra-index-url https://www.piwheels.org/simple \ - --extra-index-url https://pypi.anaconda.org/ARM-software/simple \ - --cache-dir=/tmp/pip-cache \ +# piwheels / ARM-software only carry ARM wheels, on amd64 they're just an extra lookup per package. +# unsafe-best-match keeps pip's behaviour of picking the best version across all indexes, +# uv's default is to stop at the first index that has the package at all. +RUN --mount=type=cache,id=uv,sharing=locked,target=/tmp/uv-cache \ + case "$TARGETPLATFORM" in \ + linux/arm*) EXTRA_INDEXES="--extra-index-url https://www.piwheels.org/simple --extra-index-url https://pypi.anaconda.org/ARM-software/simple" ;; \ + *) EXTRA_INDEXES="" ;; \ + esac && \ + uv pip install \ + --index-strategy unsafe-best-match \ + $EXTRA_INDEXES \ + --only-binary litellm \ --target=/dependencies \ -r /requirements.txt # Playwright is an alternative to Selenium # Excluded this package from requirements.txt to prevent arm/v6 and arm/v7 builds from failing # https://github.com/dgtlmoon/changedetection.io/pull/1067 also musl/alpine (not supported) -RUN --mount=type=cache,id=pip,sharing=locked,target=/tmp/pip-cache \ - pip install \ - --prefer-binary \ - --cache-dir=/tmp/pip-cache \ +RUN --mount=type=cache,id=uv,sharing=locked,target=/tmp/uv-cache \ + uv pip install \ --target=/dependencies \ playwright~=1.56.0 \ || echo "WARN: Failed to install Playwright. The application can still run, but the Playwright option will be disabled." # OpenCV is optional for fast image comparison (pixelmatch is the fallback) # Skip on arm/v7 and arm/v8 where builds take weeks - excluded from requirements.txt -ARG TARGETPLATFORM -RUN --mount=type=cache,id=pip,sharing=locked,target=/tmp/pip-cache \ +RUN --mount=type=cache,id=uv,sharing=locked,target=/tmp/uv-cache \ case "$TARGETPLATFORM" in \ linux/arm/v7|linux/arm/v8) \ echo "INFO: Skipping OpenCV on $TARGETPLATFORM (build takes too long), using pixelmatch fallback" \ ;; \ *) \ - pip install \ - --prefer-binary \ - --extra-index-url https://www.piwheels.org/simple \ - --cache-dir=/tmp/pip-cache \ + uv pip install \ --target=/dependencies \ - opencv-python-headless>=4.8.0.76 \ + "opencv-python-headless>=4.8.0.76" \ || echo "WARN: OpenCV install failed, will use pixelmatch fallback" \ ;; \ esac