diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0234545c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Enforce LF line endings for all text files tracked in this repository. +# This prevents Windows editors from introducing CRLF endings in shell scripts, +# which causes bash to embed '\r' into variable values (e.g. ARCH=$(cmd)\r). +* text=auto eol=lf + +# Explicitly mark binary files so they are never munged +*.png binary +*.jpg binary +*.ico binary diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 42f1ec28..74ed5dd3 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -91,17 +91,17 @@ jobs: strategy: matrix: include: - - BASE_IMAGE: debian:stable-slim + - BASE_IMAGE: debian:trixie-slim BUILD_IMAGE: debian_stable_base PLATFORM: linux/amd64 runner: ubuntu-latest arch: amd64 - - BASE_IMAGE: debian:stable-slim + - BASE_IMAGE: debian:trixie-slim BUILD_IMAGE: debian_stable_base PLATFORM: linux/amd64/v3 runner: ubuntu-latest arch: amd64_v3 - - BASE_IMAGE: debian:stable-slim + - BASE_IMAGE: debian:trixie-slim BUILD_IMAGE: debian_stable_base PLATFORM: linux/arm64 runner: ubuntu-24.04-arm @@ -120,54 +120,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 - - name: Inject enhanced GitHub environment variables - uses: rlespinasse/github-slug-action@v5 - - name: Load NVIDIA visible devices and driver capabilities - id: nvidia - shell: bash - run: | - case "${{ matrix.BUILD_IMAGE }}" in - cuda*) - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=all - ;; - esac - echo "visible_devices=${NVIDIA_VISIBLE_DEVICES}" | tee -a $GITHUB_OUTPUT - echo "driver_capabilities=${NVIDIA_DRIVER_CAPABILITIES}" | tee -a $GITHUB_OUTPUT - - name: Load spack version and cherry-picks - id: spack - shell: bash - run: | - source spack.sh - echo "orgrepo=${SPACK_ORGREPO}" | tee -a $GITHUB_OUTPUT - echo "version=${SPACK_VERSION}" | tee -a $GITHUB_OUTPUT - echo "sha=$(.ci/resolve_git_ref ${SPACK_ORGREPO} ${SPACK_VERSION})" | tee -a $GITHUB_OUTPUT - echo "cherrypicks=${SPACK_CHERRYPICKS//$'\n'/ }" | tee -a $GITHUB_OUTPUT - echo "cherrypicks_files=${SPACK_CHERRYPICKS_FILES//$'\n'/ }" | tee -a $GITHUB_OUTPUT - - name: Load spack-packages version and cherry-picks - id: spack-packages - shell: bash - run: | - source spack-packages.sh - echo "orgrepo=${SPACKPACKAGES_ORGREPO}" | tee -a $GITHUB_OUTPUT - echo "version=${SPACKPACKAGES_VERSION}" | tee -a $GITHUB_OUTPUT - echo "sha=$(.ci/resolve_git_ref ${SPACKPACKAGES_ORGREPO} ${SPACKPACKAGES_VERSION})" | tee -a $GITHUB_OUTPUT - echo "cherrypicks=${SPACKPACKAGES_CHERRYPICKS//$'\n'/ }" | tee -a $GITHUB_OUTPUT - echo "cherrypicks_files=${SPACKPACKAGES_CHERRYPICKS_FILES//$'\n'/ }" | tee -a $GITHUB_OUTPUT - - name: Load key4hep-spack version - id: key4hep-spack - run: | - source key4hep-spack.sh - echo "orgrepo=${KEY4HEPSPACK_ORGREPO}" | tee -a $GITHUB_OUTPUT - echo "version=${KEY4HEPSPACK_VERSION}" | tee -a $GITHUB_OUTPUT - echo "sha=$(.ci/resolve_git_ref ${KEY4HEPSPACK_ORGREPO} ${KEY4HEPSPACK_VERSION})" | tee -a $GITHUB_OUTPUT - - name: Load eic-spack version - id: eic-spack - run: | - source eic-spack.sh - echo "orgrepo=${EICSPACK_ORGREPO}" | tee -a $GITHUB_OUTPUT - echo "version=${EICSPACK_VERSION}" | tee -a $GITHUB_OUTPUT - echo "sha=$(.ci/resolve_git_ref ${EICSPACK_ORGREPO} ${EICSPACK_VERSION})" | tee -a $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v4 with: @@ -201,14 +153,6 @@ jobs: "var-cache-spack": "/var/cache/spack" } skip-extraction: ${{ steps.cache-base-mounts.outputs.cache-hit }} - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - name=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }} - tags: | - type=sha,prefix=${{ matrix.arch }}- - name: Login to GitHub Container Registry uses: docker/login-action@v4 if: ${{ env.GH_PUSH == '1' }} @@ -217,55 +161,28 @@ jobs: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} - name: Build and push - uses: docker/build-push-action@v7 - id: build - with: - file: containers/debian/Dockerfile - context: containers/debian - platforms: ${{ matrix.PLATFORM }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }},push-by-digest=true,name-canonical=true,push=true - build-args: | - BASE_IMAGE=${{ matrix.BASE_IMAGE }} - BUILD_IMAGE=${{ matrix.BUILD_IMAGE }} - NVIDIA_VISIBLE_DEVICES=${{ steps.nvidia.outputs.visible_devices }} - NVIDIA_DRIVER_CAPABILITIES=${{ steps.nvidia.outputs.driver_capabilities }} - SPACK_ORGREPO=${{ steps.spack.outputs.orgrepo }} - SPACK_VERSION=${{ steps.spack.outputs.version }} - SPACK_SHA=${{ steps.spack.outputs.sha }} - SPACK_CHERRYPICKS=${{ steps.spack.outputs.cherrypicks }} - SPACK_CHERRYPICKS_FILES=${{ steps.spack.outputs.cherrypicks_files }} - SPACKPACKAGES_ORGREPO=${{ steps.spack-packages.outputs.orgrepo }} - SPACKPACKAGES_VERSION=${{ steps.spack-packages.outputs.version }} - SPACKPACKAGES_SHA=${{ steps.spack-packages.outputs.sha }} - SPACKPACKAGES_CHERRYPICKS=${{ steps.spack-packages.outputs.cherrypicks }} - SPACKPACKAGES_CHERRYPICKS_FILES=${{ steps.spack-packages.outputs.cherrypicks_files }} - KEY4HEPSPACK_ORGREPO=${{ steps.key4hep-spack.outputs.orgrepo }} - KEY4HEPSPACK_VERSION=${{ steps.key4hep-spack.outputs.version }} - KEY4HEPSPACK_SHA=${{ steps.key4hep-spack.outputs.sha }} - EICSPACK_ORGREPO=${{ steps.eic-spack.outputs.orgrepo }} - EICSPACK_VERSION=${{ steps.eic-spack.outputs.version }} - EICSPACK_SHA=${{ steps.eic-spack.outputs.sha }} - jobs=${{ env.JOBS }} - BUILDWEEK=${{ needs.env.outputs.BUILDWEEK }} - cache-from: | - type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}-${{ env.GITHUB_REF_POINT_SLUG }}-${{ matrix.arch }} - type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}-${{ env.GITHUB_BASE_REF_SLUG }}-${{ matrix.arch }} - cache-to: type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}-${{ env.GITHUB_REF_POINT_SLUG }}-${{ matrix.arch }},mode=max + env: + BUILD_IMAGE: ${{ matrix.BUILD_IMAGE }} + BASE_IMAGE: ${{ matrix.BASE_IMAGE }} + PLATFORM: ${{ matrix.PLATFORM }} + BUILDWEEK: ${{ needs.env.outputs.BUILDWEEK }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + METADATA_FILE: /tmp/build-metadata.json + run: bash scripts/build-base.sh - name: Export digest to file - # The build-push action outputs the digest at steps.build.outputs.digest - # We write this to a file for the next job run: | + DIGEST=$(jq -r '."containerimage.digest"' /tmp/build-metadata.json) + IMAGE="${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}" mkdir -p /tmp/digests - echo "${{ steps.meta.outputs.tags }}@${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.BUILD_IMAGE }}-${{ matrix.arch }}.digest + echo "${IMAGE}@${DIGEST}" > /tmp/digests/${{ matrix.BUILD_IMAGE }}-${{ matrix.arch }}.digest - name: Check image size run: | FULLNAME="${{ matrix.BUILD_IMAGE }}" LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" LIMIT="${!LIMIT_VAR}" if [ -n "${LIMIT}" ]; then - .ci/check_image_size \ - "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@${{ steps.build.outputs.digest }}" \ + scripts/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@$(jq -r '."containerimage.digest"' /tmp/build-metadata.json)" \ "${LIMIT}" else echo "No size limit configured for ${FULLNAME}, skipping check." @@ -338,18 +255,14 @@ jobs: exit 1 fi # Get the base image name from the first digest - REGISTRY_IMAGE_TAG=$(echo $FIRST_DIGEST | cut -d'@' -f1) - REGISTRY_IMAGE=$(echo $REGISTRY_IMAGE_TAG | cut -d':' -f1) + REGISTRY_IMAGE=$(echo $FIRST_DIGEST | cut -d'@' -f1) REGISTRY=$(echo $REGISTRY_IMAGE | cut -d'/' -f1-2) IMAGE=$(echo $REGISTRY_IMAGE | cut -d'/' -f3) - TAG=$(echo $REGISTRY_IMAGE_TAG | cut -d':' -f2) echo "Registry Name: $REGISTRY" echo "Image Name: $IMAGE" - echo "Tag Name: $TAG" echo "All Digests: $ALL_DIGESTS" echo "registry=$REGISTRY" >> $GITHUB_OUTPUT echo "image=$IMAGE" >> $GITHUB_OUTPUT - echo "tag=$TAG" >> $GITHUB_OUTPUT echo "all=$ALL_DIGESTS" >> $GITHUB_OUTPUT - name: Extract Docker metadata for final tags id: meta @@ -382,7 +295,7 @@ jobs: matrix: include: - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: ci @@ -391,7 +304,7 @@ jobs: PLATFORM: linux/amd64 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: ci @@ -400,7 +313,7 @@ jobs: PLATFORM: linux/amd64/v3 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: ci @@ -409,7 +322,7 @@ jobs: PLATFORM: linux/arm64 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: xl @@ -418,7 +331,7 @@ jobs: PLATFORM: linux/amd64 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: xl @@ -427,7 +340,7 @@ jobs: PLATFORM: linux/amd64/v3 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base ENV: xl @@ -436,7 +349,7 @@ jobs: PLATFORM: linux/arm64 target: final - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default" BUILDER_IMAGE: cuda_devel RUNTIME_IMAGE: cuda_devel ENV: cuda @@ -445,7 +358,7 @@ jobs: PLATFORM: linux/amd64 target: builder_concretization_default - BUILD_IMAGE: eic_ - BUILD_TYPE: default + BUILD_TYPE: "default" BUILDER_IMAGE: cuda_devel RUNTIME_IMAGE: cuda_runtime ENV: tf @@ -462,30 +375,6 @@ jobs: dotnet: true - name: Checkout uses: actions/checkout@v7 - - name: Resolve benchmarks versions - id: benchmarks - shell: bash - run: | - echo "common_bench=$(.ci/resolve_git_ref https://github.com/eic/common_bench.git master)" | tee -a $GITHUB_OUTPUT - echo "detector_benchmarks=$(.ci/resolve_git_ref https://github.com/eic/detector_benchmarks.git master)" | tee -a $GITHUB_OUTPUT - echo "physics_benchmarks=$(.ci/resolve_git_ref https://github.com/eic/physics_benchmarks.git master)" | tee -a $GITHUB_OUTPUT - - name: Resolve campaign versions - id: campaigns - shell: bash - run: | - echo "simulation_campaign_hepmc3=$(.ci/resolve_git_ref eic/simulation_campaign_hepmc3 main)" | tee -a $GITHUB_OUTPUT - echo "job_submission_condor=$(.ci/resolve_git_ref eic/job_submission_condor main)" | tee -a $GITHUB_OUTPUT - echo "job_submission_slurm=$(.ci/resolve_git_ref eic/job_submission_slurm main)" | tee -a $GITHUB_OUTPUT - - name: Load secrets into mirrors.yaml - id: mirrors - run: | - source spack-packages.sh - export SPACKPACKAGES_VERSION - export CI_REGISTRY=ghcr.io - export CI_PROJECT_PATH=eic - cat mirrors.yaml.in | envsubst > mirrors.yaml - - name: Inject enhanced GitHub environment variables - uses: rlespinasse/github-slug-action@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v4 with: @@ -518,15 +407,6 @@ jobs: "var-cache-spack": "/var/cache/spack" } skip-extraction: ${{ steps.cache-eic-mounts.outputs.cache-hit }} - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - name=${{ env.DH_REGISTRY }}/${{ env.DH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }},enable=${{ env.DH_PUSH != 0 }} - name=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }},enable=${{ env.GH_PUSH != 0 }} - tags: | - type=sha,prefix=${{ matrix.arch }}- - name: Login to Docker Hub uses: docker/login-action@v4 if: ${{ env.DH_PUSH == '1' }} @@ -542,46 +422,31 @@ jobs: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} - name: Build and push - uses: docker/build-push-action@v7 - id: build - with: - file: containers/eic/Dockerfile - context: containers/eic - build-contexts: | - spack-environment=spack-environment - secrets: | - "CI_REGISTRY_USER=${{ secrets.GHCR_REGISTRY_USER }}" - "CI_REGISTRY_PASSWORD=${{ secrets.GHCR_REGISTRY_TOKEN }}" - "GITHUB_REGISTRY_USER=${{ secrets.GHCR_REGISTRY_USER }}" - "GITHUB_REGISTRY_TOKEN=${{ secrets.GHCR_REGISTRY_TOKEN }}" - secret-files: | - mirrors=mirrors.yaml - platforms: ${{ matrix.PLATFORM }} - target: ${{ matrix.target }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }},push-by-digest=true,name-canonical=true,push=true - build-args: | - BENCHMARK_COM_SHA=${{ steps.benchmarks.outputs.common_bench }} - BENCHMARK_DET_SHA=${{ steps.benchmarks.outputs.detector_benchmarks }} - BENCHMARK_PHY_SHA=${{ steps.benchmarks.outputs.physics_benchmarks }} - CAMPAIGNS_HEPMC3_SHA=${{ steps.campaigns.outputs.simulation_campaign_hepmc3 }} - CAMPAIGNS_CONDOR_SHA=${{ steps.campaigns.outputs.job_submission_condor }} - CAMPAIGNS_SLURM_SHA=${{ steps.campaigns.outputs.job_submission_slurm }} - DOCKER_REGISTRY=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/ - BUILDER_IMAGE=${{ matrix.BUILDER_IMAGE }} - RUNTIME_IMAGE=${{ matrix.RUNTIME_IMAGE }} - INTERNAL_TAG=${{ env.INTERNAL_TAG }} - ENV=${{ matrix.ENV }} - cache-from: | - type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}-${{ matrix.BUILD_TYPE }}-${{ env.GITHUB_REF_POINT_SLUG }}-${{ matrix.arch }} - type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}-${{ matrix.BUILD_TYPE }}-${{ env.GITHUB_BASE_REF_SLUG }}-${{ matrix.arch }} - cache-to: type=registry,ref=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/buildcache:${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}-${{ matrix.BUILD_TYPE }}-${{ env.GITHUB_REF_POINT_SLUG }}-${{ matrix.arch }},mode=max - - name: Export digest to file - # The build-push action outputs the digest at steps.build.outputs.digest - # We write this to a file for the next job + env: + BUILD_IMAGE: ${{ matrix.BUILD_IMAGE }} + ENV: ${{ matrix.ENV }} + BUILD_TYPE: ${{ matrix.BUILD_TYPE }} + BUILD_TARGET: ${{ matrix.target }} + BUILDER_IMAGE: ${{ matrix.BUILDER_IMAGE }} + RUNTIME_IMAGE: ${{ matrix.RUNTIME_IMAGE }} + PLATFORM: ${{ matrix.PLATFORM }} + CI_REGISTRY_USER: ${{ secrets.GHCR_REGISTRY_USER }} + CI_REGISTRY_PASSWORD: ${{ secrets.GHCR_REGISTRY_TOKEN }} + GITHUB_REGISTRY_USER: ${{ secrets.GHCR_REGISTRY_USER }} + GITHUB_REGISTRY_TOKEN: ${{ secrets.GHCR_REGISTRY_TOKEN }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + METADATA_FILE: /tmp/build-metadata.json + run: bash scripts/build-eic.sh + - name: Export digests to files run: | + IMAGE="${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}" mkdir -p /tmp/digests - echo "${{ steps.meta.outputs.tags }}@${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.arch }}.digest + for metadata_file in /tmp/build-metadata-*.json; do + build_type="${metadata_file#/tmp/build-metadata-}" + build_type="${build_type%.json}" + DIGEST=$(jq -r '."containerimage.digest"' "${metadata_file}") + echo "${IMAGE}@${DIGEST}" > "/tmp/digests/${{ matrix.arch }}-${build_type}.digest" + done - name: Check image size # Only check final images; concretization-only targets (cuda, tf) don't produce a full image if: ${{ matrix.target == 'final' }} @@ -590,27 +455,47 @@ jobs: LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" LIMIT="${!LIMIT_VAR}" if [ -n "${LIMIT}" ]; then - .ci/check_image_size \ - "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@${{ steps.build.outputs.digest }}" \ + metadata_file=/tmp/build-metadata-default.json + [ -f "${metadata_file}" ] || metadata_file=$(ls /tmp/build-metadata-*.json | head -1) + scripts/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@$(jq -r '."containerimage.digest"' "${metadata_file}")" \ "${LIMIT}" else echo "No size limit configured for ${FULLNAME}, skipping check." fi - - name: Upload digest as artifact + - name: Check produced build types + id: built-types + run: | + [ -f /tmp/digests/${{ matrix.arch }}-default.digest ] \ + && echo "has_default=true" >> $GITHUB_OUTPUT \ + || echo "has_default=false" >> $GITHUB_OUTPUT + [ -f /tmp/digests/${{ matrix.arch }}-nightly.digest ] \ + && echo "has_nightly=true" >> $GITHUB_OUTPUT \ + || echo "has_nightly=false" >> $GITHUB_OUTPUT + - name: Upload default digest as artifact + if: steps.built-types.outputs.has_default == 'true' + uses: actions/upload-artifact@v7 + with: + name: eic-${{ matrix.ENV }}-${{ matrix.arch }}-default-digest + path: /tmp/digests/${{ matrix.arch }}-default.digest + retention-days: 1 + - name: Upload nightly digest as artifact + if: steps.built-types.outputs.has_nightly == 'true' uses: actions/upload-artifact@v7 with: - name: eic-${{ matrix.ENV }}-${{ matrix.arch }}-digest - path: /tmp/digests/${{ matrix.arch }}.digest + name: eic-${{ matrix.ENV }}-${{ matrix.arch }}-nightly-digest + path: /tmp/digests/${{ matrix.arch }}-nightly.digest retention-days: 1 eic-manifest: - name: Push ${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }} manifest + name: Push ${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }} ${{ matrix.BUILD_TYPE }} manifest runs-on: ubuntu-latest needs: eic strategy: matrix: BUILD_IMAGE: [eic_] ENV: [ci, xl] + BUILD_TYPE: [default, nightly] steps: - name: Set up QEMU (for imagetools) uses: docker/setup-qemu-action@v4 @@ -634,7 +519,7 @@ jobs: uses: actions/download-artifact@v8 with: path: /tmp/digests - pattern: eic-${{ matrix.ENV }}-*-digest + pattern: eic-${{ matrix.ENV }}-*-${{ matrix.BUILD_TYPE }}-digest merge-multiple: true - name: Analyze digest artifacts id: digests @@ -643,7 +528,7 @@ jobs: ALL_DIGESTS="" FIRST_DIGEST="" shopt -s nullglob - for digest_file in /tmp/digests/*.digest; do + for digest_file in /tmp/digests/*-${{ matrix.BUILD_TYPE }}.digest; do DIGEST=$(cat "$digest_file") echo "Found digest: $DIGEST" if [ -z "$ALL_DIGESTS" ]; then @@ -662,18 +547,14 @@ jobs: exit 1 fi # Get the base image name from the first digest - REGISTRY_IMAGE_TAG=$(echo $FIRST_DIGEST | cut -d'@' -f1) - REGISTRY_IMAGE=$(echo $REGISTRY_IMAGE_TAG | cut -d':' -f1) + REGISTRY_IMAGE=$(echo $FIRST_DIGEST | cut -d'@' -f1) REGISTRY=$(echo $REGISTRY_IMAGE | cut -d'/' -f1-2) IMAGE=$(echo $REGISTRY_IMAGE | cut -d'/' -f3) - TAG=$(echo $REGISTRY_IMAGE_TAG | cut -d':' -f2) echo "Registry Name: $REGISTRY" echo "Image Name: $IMAGE" - echo "Tag Name: $TAG" echo "All Digests: $ALL_DIGESTS" echo "registry=$REGISTRY" >> $GITHUB_OUTPUT echo "image=$IMAGE" >> $GITHUB_OUTPUT - echo "tag=$TAG" >> $GITHUB_OUTPUT echo "all=$ALL_DIGESTS" >> $GITHUB_OUTPUT - name: Extract Docker metadata for final tags id: meta @@ -683,9 +564,10 @@ jobs: name=${{ env.DH_REGISTRY }}/${{ env.DH_REGISTRY_USER }}/${{ steps.digests.outputs.image }},enable=${{ env.DH_PUSH != 0 }} name=${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ steps.digests.outputs.image }},enable=${{ env.GH_PUSH != 0 }} tags: | - ${{ env.INTERNAL_TAG }} - type=ref,prefix=unstable-pr-,event=pr - type=match,pattern=^v(\d+\.\d+\.\d+-.*)$,group=1 + ${{ env.INTERNAL_TAG }}-${{ matrix.BUILD_TYPE }} + type=ref,prefix=unstable-pr-,event=pr,suffix=-${{ matrix.BUILD_TYPE }} + type=match,pattern=^v(\d+\.\d+\.\d+-.*)$,group=1,enable=${{ matrix.BUILD_TYPE == 'default' }} + type=match,pattern=^v(\d+\.\d+\.\d+-.*)$,group=1,suffix=-nightly,enable=${{ matrix.BUILD_TYPE == 'nightly' }} - name: Create and push manifest list run: | # Create tag arguments from the multi-line 'tags' output from the meta step @@ -703,7 +585,7 @@ jobs: runs-on: ${{ matrix.runner }} needs: eic-manifest container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -787,7 +669,7 @@ jobs: runs-on: ${{ matrix.runner }} needs: eic-manifest container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -883,7 +765,7 @@ jobs: runs-on: ${{ matrix.runner }} needs: [eic-manifest, npsim-gun] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -966,7 +848,7 @@ jobs: runs-on: ${{ matrix.runner }} needs: [eic-manifest, npsim-dis] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -1065,7 +947,7 @@ jobs: runs-on: ubuntu-latest needs: [eic-manifest, npsim-dis] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -1106,7 +988,7 @@ jobs: runs-on: ubuntu-latest needs: [eic-manifest, eicrecon-dis] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -1147,7 +1029,7 @@ jobs: runs-on: ubuntu-latest needs: [eic-manifest, npsim-gun] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} @@ -1187,7 +1069,7 @@ jobs: runs-on: ubuntu-latest needs: [eic-manifest, eicrecon-gun] container: - image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }} + image: ghcr.io/eic/eic_ci:pipeline-${{ github.run_id }}-default credentials: username: ${{ secrets.GHCR_REGISTRY_USER }} password: ${{ secrets.GHCR_REGISTRY_TOKEN }} diff --git a/.gitignore b/.gitignore index d956d2e7..90eb6140 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# build-*.sh products +mirrors.yaml + # install.sh products eic-shell local/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1d391a9f..fc5f77b5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,7 +63,9 @@ stages: - status-pending - config - base ## base OS image + - base-manifest ## create manifest tags for base images - eic ## EIC container images + - eic-manifest ## create manifest tags for EIC images - deploy ## build/deploy singularity images - benchmarks - test @@ -268,18 +270,34 @@ status:pending: artifacts: paths: - build.log + - build-*.log + - build-metadata-*.json when: always ## rules for nightly jobs .nightly: rules: - # Don't build nightly in the following cases + # For nightly-only jobs (BUILD_TYPE="nightly" exactly), skip entirely on stable branches - if: '$BUILD_TYPE == "nightly" && $CI_COMMIT_BRANCH =~ /^v[0-9]+\.[0-9]+-(alpha|beta|stable)/' ## main stable branch: vX.Y-stable when: never - if: '$BUILD_TYPE == "nightly" && $CI_COMMIT_BRANCH =~ /^v[0-9]+\.[0-9]+-[a-z]+-(alpha|beta|stable)/' ## special stable branch: vX.Y-acadia-stable (etc) when: never - if: '$BUILD_TYPE == "nightly" && $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+/' ## commit tags start with vX.Y.Z with optional suffix when: never + # For combined jobs (BUILD_TYPE != "nightly"), suppress nightly builds on stable branches + # by overriding BUILD_TYPE to "default" so the job still runs for the default image + - if: '$CI_COMMIT_BRANCH =~ /^v[0-9]+\.[0-9]+-(alpha|beta|stable)/' + when: always + variables: + BUILD_TYPE: "default" + - if: '$CI_COMMIT_BRANCH =~ /^v[0-9]+\.[0-9]+-[a-z]+-(alpha|beta|stable)/' + when: always + variables: + BUILD_TYPE: "default" + - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+/' + when: always + variables: + BUILD_TYPE: "default" # Skip containers other than eic_ci for epic or EICrecon trigger - if: '$ENV != "ci" && $CI_PIPELINE_SOURCE == "trigger" && $GITHUB_REPOSITORY != "eic/containers"' when: never @@ -319,95 +337,84 @@ base: PLATFORM: linux/amd64 extends: .build stage: base + variables: + METADATA_FILE: "build-metadata-${BUILD_IMAGE}.json" needs: - version script: - - source spack.sh ; - source spack-packages.sh ; - source key4hep-spack.sh ; - source eic-spack.sh ; - - docker buildx build --push ${BUILD_OPTIONS} - --cache-from type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}-${CI_COMMIT_REF_SLUG}-amd64 - --cache-from type=registry,ref=${GH_REGISTRY}/${GH_REGISTRY_USER}/buildcache:${BUILD_IMAGE}-${CI_COMMIT_REF_SLUG}-amd64 - --cache-from type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}-${CI_DEFAULT_BRANCH_SLUG}-amd64 - --cache-from type=registry,ref=${GH_REGISTRY}/${GH_REGISTRY_USER}/buildcache:${BUILD_IMAGE}-${CI_DEFAULT_BRANCH_SLUG}-amd64 - --cache-to type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}-${CI_COMMIT_REF_SLUG}-amd64,mode=max - --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG} - ${EXPORT_TAG:+ - ${CI_PUSH:+--tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${EXPORT_TAG}} - ${DH_PUSH:+--tag ${DH_REGISTRY}/${DH_REGISTRY_USER}/${BUILD_IMAGE}:${EXPORT_TAG}} - ${GH_PUSH:+--tag ${GH_REGISTRY}/${GH_REGISTRY_USER}/${BUILD_IMAGE}:${EXPORT_TAG}} - } - --file containers/debian/Dockerfile - --platform ${PLATFORM} - --build-arg BASE_IMAGE=${BASE_IMAGE} - --build-arg BUILD_IMAGE=${BUILD_IMAGE} - --build-arg NVIDIA_VISIBLE_DEVICES=$(case "${BUILD_IMAGE}" in cuda*) echo all ;; esac) - --build-arg NVIDIA_DRIVER_CAPABILITIES=$(case "${BUILD_IMAGE}" in cuda*) echo all ;; esac) - --build-arg SPACK_ORGREPO=${SPACK_ORGREPO} - --build-arg SPACK_VERSION=${SPACK_VERSION} - --build-arg SPACK_SHA=$(sh .ci/resolve_git_ref "${SPACK_ORGREPO}" "${SPACK_VERSION}") - --build-arg SPACK_CHERRYPICKS="${SPACK_CHERRYPICKS}" - --build-arg SPACK_CHERRYPICKS_FILES="${SPACK_CHERRYPICKS_FILES}" - --build-arg SPACKPACKAGES_ORGREPO=${SPACKPACKAGES_ORGREPO} - --build-arg SPACKPACKAGES_VERSION=${SPACKPACKAGES_VERSION} - --build-arg SPACKPACKAGES_SHA=$(sh .ci/resolve_git_ref "${SPACKPACKAGES_ORGREPO}" "${SPACKPACKAGES_VERSION}") - --build-arg SPACKPACKAGES_CHERRYPICKS="${SPACKPACKAGES_CHERRYPICKS}" - --build-arg SPACKPACKAGES_CHERRYPICKS_FILES="${SPACKPACKAGES_CHERRYPICKS_FILES}" - --build-arg KEY4HEPSPACK_ORGREPO=${KEY4HEPSPACK_ORGREPO} - --build-arg KEY4HEPSPACK_VERSION=${KEY4HEPSPACK_VERSION} - --build-arg KEY4HEPSPACK_SHA=$(sh .ci/resolve_git_ref "${KEY4HEPSPACK_ORGREPO}" "${KEY4HEPSPACK_VERSION}") - --build-arg EICSPACK_ORGREPO=${EICSPACK_ORGREPO} - --build-arg EICSPACK_VERSION=${EICSPACK_VERSION} - --build-arg EICSPACK_SHA=$(sh .ci/resolve_git_ref "${EICSPACK_ORGREPO}" "${EICSPACK_VERSION}") - --build-arg jobs=${JOBS} - --build-arg BUILDWEEK=$(date +%Yw%V) - --provenance false - containers/debian - 2>&1 | tee build.log - - LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${BUILD_IMAGE}" | tr '[:lower:]' '[:upper:]')_GIB" ; - LIMIT=$(printenv "${LIMIT_VAR}" || true) ; + - export BUILDWEEK=$(date +%Yw%V) + - apk add git bash jq + - bash scripts/build-base.sh + - | + LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${BUILD_IMAGE}" | tr '[:lower:]' '[:upper:]')_GIB" + LIMIT=$(printenv "${LIMIT_VAR}" || true) if [ -n "${LIMIT}" ]; then - .ci/check_image_size - "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" - "${LIMIT}" ; + DIGEST=$(jq -r '."containerimage.digest"' "${METADATA_FILE}") + scripts/check_image_size \ + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}@${DIGEST}" \ + "${LIMIT}" else - echo "No size limit configured for ${BUILD_IMAGE}, skipping check." ; + echo "No size limit configured for ${BUILD_IMAGE}, skipping check." fi +base-manifest: + extends: .docker + stage: base-manifest + needs: + - version + - job: base + script: + - apk add bash jq + - | + for metadata_file in build-metadata-*.json; do + BUILD_IMAGE="${metadata_file#build-metadata-}"; BUILD_IMAGE="${BUILD_IMAGE%.json}" + CI_REGISTRY_PREFIX="${CI_REGISTRY}/${CI_PROJECT_PATH}" + IMAGE_REPO="${CI_REGISTRY_PREFIX}/${BUILD_IMAGE}" + DIGEST=$(jq -r '."containerimage.digest"' "${metadata_file}") + docker buildx imagetools create --tag "${IMAGE_REPO}:${INTERNAL_TAG}" "${IMAGE_REPO}@${DIGEST}" + if [ -n "${EXPORT_TAG}" ]; then + [ -n "${CI_PUSH}" ] && docker buildx imagetools create --tag "${IMAGE_REPO}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${DH_PUSH}" ] && docker buildx imagetools create --tag "${DH_REGISTRY}/${DH_REGISTRY_USER}/${BUILD_IMAGE}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${GH_PUSH}" ] && docker buildx imagetools create --tag "${GH_REGISTRY}/${GH_REGISTRY_USER}/${BUILD_IMAGE}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + fi + done + eic: parallel: matrix: + ## Build both default and nightly sequentially in one job to reuse the shared + ## base Docker stages (default concretization+installation) from BuildKit's layer cache. - BUILD_IMAGE: eic_ ENV: - ci - cvmfs + - xl + BUILD_TYPE: "default,nightly" + BUILDER_IMAGE: debian_stable_base + RUNTIME_IMAGE: debian_stable_base + PLATFORM: linux/amd64 + ## Default-only environments (no nightly variant needed) + - BUILD_IMAGE: eic_ + ENV: - dbg - jl - prod - - xl - BUILD_TYPE: - - default + BUILD_TYPE: "default" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base PLATFORM: linux/amd64 + ## Nightly-only (no default variant; ci_without_acts is nightly-specific) - BUILD_IMAGE: eic_ ENV: - - ci - ci_without_acts - - cvmfs - - xl - BUILD_TYPE: - - nightly + BUILD_TYPE: "nightly" BUILDER_IMAGE: debian_stable_base RUNTIME_IMAGE: debian_stable_base PLATFORM: linux/amd64 # - BUILD_IMAGE: oneapi_ # ENV: # - prod -# BUILD_TYPE: -# - default -# - nightly +# BUILD_TYPE: "default,nightly" # BUILDER_IMAGE: oneapi_base # RUNTIME_IMAGE: oneapi_base # PLATFORM: linux/amd64 @@ -415,18 +422,14 @@ eic: ENV: - cuda - tf - BUILD_TYPE: - - default - - nightly + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: cuda_devel RUNTIME_IMAGE: cuda_runtime PLATFORM: linux/amd64 - BUILD_IMAGE: eic_dev_ ENV: - cuda - BUILD_TYPE: - - default - - nightly + BUILD_TYPE: "default,nightly" BUILDER_IMAGE: cuda_devel RUNTIME_IMAGE: cuda_devel PLATFORM: linux/amd64 @@ -435,94 +438,73 @@ eic: - when: always extends: .build stage: eic + variables: + ## scripts/build-eic.sh appends -.json per loop iteration; drop ${BUILD_TYPE} from here + METADATA_FILE: "build-metadata-${BUILD_IMAGE}${ENV}.json" needs: - version - - base + - base-manifest script: - - if [ "${BUILD_TYPE}" == "nightly" ] ; then IF_BUILD_NIGHTLY= ; fi - - if [ "${BUILD_TYPE}" == "default" ] ; then IF_BUILD_DEFAULT= ; fi - - apk add envsubst git - - source spack.sh ; - source spack-packages.sh ; - source key4hep-spack.sh ; - source eic-spack.sh ; - export SPACKPACKAGES_VERSION ; - cat mirrors.yaml.in | envsubst > mirrors.yaml - - set -o xtrace ; - docker buildx build --push ${BUILD_OPTIONS} - --cache-from type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}${ENV}-${BUILD_TYPE}-${CI_COMMIT_REF_SLUG}-amd64 - --cache-from type=registry,ref=${GH_REGISTRY}/${GH_REGISTRY_USER}/buildcache:${BUILD_IMAGE}${ENV}-${BUILD_TYPE}-${CI_COMMIT_REF_SLUG}-amd64 - --cache-from type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}${ENV}-${BUILD_TYPE}-${CI_DEFAULT_BRANCH_SLUG}-amd64 - --cache-from type=registry,ref=${GH_REGISTRY}/${GH_REGISTRY_USER}/buildcache:${BUILD_IMAGE}${ENV}-${BUILD_TYPE}-${CI_DEFAULT_BRANCH_SLUG}-amd64 - --cache-to type=registry,ref=${CI_REGISTRY}/${CI_PROJECT_PATH}/buildcache:${BUILD_IMAGE}${ENV}-${BUILD_TYPE}-${CI_COMMIT_REF_SLUG}-amd64,mode=max - --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${INTERNAL_TAG}-${BUILD_TYPE} - ${EXPORT_TAG:+ - ${IF_BUILD_DEFAULT+ - ${CI_PUSH:+--tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}} - ${DH_PUSH:+--tag ${DH_REGISTRY}/${DH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}} - ${GH_PUSH:+--tag ${GH_REGISTRY}/${GH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}} - } - ${IF_BUILD_DEFAULT- - ${CI_PUSH:+--tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}-${BUILD_TYPE}} - ${DH_PUSH:+--tag ${DH_REGISTRY}/${DH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}-${BUILD_TYPE}} - ${GH_PUSH:+--tag ${GH_REGISTRY}/${GH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${EXPORT_TAG}-${BUILD_TYPE}} - } - } - ${IF_BUILD_NIGHTLY+ - ${NIGHTLY:+ - ${CI_PUSH:+--tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${NIGHTLY_TAG}} - ${DH_PUSH:+--tag ${DH_REGISTRY}/${DH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${NIGHTLY_TAG}} - ${GH_PUSH:+--tag ${GH_REGISTRY}/${GH_REGISTRY_USER}/${BUILD_IMAGE}${ENV}:${NIGHTLY_TAG}} - } - } - --file containers/eic/Dockerfile - --platform ${PLATFORM} - --build-arg BENCHMARK_COM_SHA=$(sh .ci/resolve_git_ref https://github.com/eic/common_bench.git master) - --build-arg BENCHMARK_DET_SHA=$(sh .ci/resolve_git_ref https://github.com/eic/detector_benchmarks.git master) - --build-arg BENCHMARK_PHY_SHA=$(sh .ci/resolve_git_ref https://github.com/eic/physics_benchmarks.git master) - --build-arg CAMPAIGNS_HEPMC3_SHA=$(sh .ci/resolve_git_ref eic/simulation_campaign_hepmc3 main) - --build-arg CAMPAIGNS_CONDOR_SHA=$(sh .ci/resolve_git_ref eic/job_submission_condor main) - --build-arg CAMPAIGNS_SLURM_SHA=$(sh .ci/resolve_git_ref eic/job_submission_slurm main) - --build-arg DOCKER_REGISTRY=${CI_REGISTRY}/${CI_PROJECT_PATH}/ - --build-arg BUILDER_IMAGE=${BUILDER_IMAGE} - --build-arg RUNTIME_IMAGE=${RUNTIME_IMAGE} - --build-arg INTERNAL_TAG=${INTERNAL_TAG} - --build-arg EIC_CONTAINER_VERSION=${EXPORT_TAG}-${BUILD_TYPE}-$(git rev-parse HEAD) - --build-arg CI_COMMIT_SHA=${CI_COMMIT_SHA} - ${IF_BUILD_DEFAULT+ - ${EDM4EIC_VERSION:+--build-arg EDM4EIC_SHA=$(sh .ci/resolve_git_ref eic/EDM4eic ${EDM4EIC_VERSION})} - ${EICRECON_VERSION:+--build-arg EICRECON_SHA=$(sh .ci/resolve_git_ref eic/EICrecon ${EICRECON_VERSION})} - ${EPIC_VERSION:+--build-arg EPIC_SHA=$(sh .ci/resolve_git_ref eic/epic ${EPIC_VERSION})} - ${JUGGLER_VERSION:+--build-arg JUGGLER_SHA=$(sh .ci/resolve_git_ref eic/juggler ${JUGGLER_VERSION})} - } - ${IF_BUILD_NIGHTLY+ - --build-arg EDM4EIC_SHA=$(sh .ci/resolve_git_ref eic/EDM4eic ${EDM4EIC_VERSION:-main}) - --build-arg EICRECON_SHA=$(sh .ci/resolve_git_ref eic/EICrecon ${EICRECON_VERSION:-main}) - --build-arg EPIC_SHA=$(sh .ci/resolve_git_ref eic/epic ${EPIC_VERSION:-main}) - --build-arg JUGGLER_SHA=$(sh .ci/resolve_git_ref eic/juggler ${JUGGLER_VERSION:-main}) - } - --build-arg ENV=${ENV} - --build-arg jobs=${JOBS} - --build-context spack-environment=spack-environment - --secret id=mirrors,src=mirrors.yaml - --secret type=env,id=CI_REGISTRY_USER,env=CI_REGISTRY_USER - --secret type=env,id=CI_REGISTRY_PASSWORD,env=CI_REGISTRY_PASSWORD - --secret type=env,id=GITHUB_REGISTRY_USER,env=GITHUB_REGISTRY_USER - --secret type=env,id=GITHUB_REGISTRY_TOKEN,env=GITHUB_REGISTRY_TOKEN - --provenance false - containers/eic - 2>&1 | tee build.log - - FULLNAME="${BUILD_IMAGE}${ENV}" ; - LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" ; - LIMIT=$(printenv "${LIMIT_VAR}" || true) ; + - apk add git bash coreutils jq + - bash scripts/build-eic.sh + - | + FULLNAME="${BUILD_IMAGE}${ENV}" + LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" + LIMIT=$(printenv "${LIMIT_VAR}" || true) if [ -n "${LIMIT}" ]; then - .ci/check_image_size - "${CI_REGISTRY}/${CI_PROJECT_PATH}/${FULLNAME}:${INTERNAL_TAG}-${BUILD_TYPE}" - "${LIMIT}" ; + found=0 + for metadata_file in "${METADATA_FILE%.json}"-*.json; do + [ -f "${metadata_file}" ] || continue + found=1 + DIGEST=$(jq -r '."containerimage.digest"' "${metadata_file}") + scripts/check_image_size \ + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${FULLNAME}@${DIGEST}" \ + "${LIMIT}" + done + if [ "${found}" -eq 0 ]; then + echo "ERROR: No metadata files found for ${FULLNAME} (expected ${METADATA_FILE%.json}-*.json)." + exit 1 + fi else - echo "No size limit configured for ${FULLNAME}, skipping check." ; + echo "No size limit configured for ${FULLNAME}, skipping check." fi +eic-manifest: + extends: .docker + stage: eic-manifest + needs: + - version + - job: eic + script: + - apk add bash jq + - | + for metadata_file in build-metadata-*.json; do + ## Parse filename: build-metadata--.json + base="${metadata_file#build-metadata-}"; base="${base%.json}" + BUILD_TYPE="${base##*-}" + IMAGE_NAME="${base%-${BUILD_TYPE}}" + CI_REGISTRY_PREFIX="${CI_REGISTRY}/${CI_PROJECT_PATH}" + IMAGE_REPO="${CI_REGISTRY_PREFIX}/${IMAGE_NAME}" + DIGEST=$(jq -r '."containerimage.digest"' "${metadata_file}") + docker buildx imagetools create --tag "${IMAGE_REPO}:${INTERNAL_TAG}-${BUILD_TYPE}" "${IMAGE_REPO}@${DIGEST}" + if [ -n "${EXPORT_TAG}" ]; then + if [ "${BUILD_TYPE}" = "default" ]; then + [ -n "${CI_PUSH}" ] && docker buildx imagetools create --tag "${IMAGE_REPO}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${DH_PUSH}" ] && docker buildx imagetools create --tag "${DH_REGISTRY}/${DH_REGISTRY_USER}/${IMAGE_NAME}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${GH_PUSH}" ] && docker buildx imagetools create --tag "${GH_REGISTRY}/${GH_REGISTRY_USER}/${IMAGE_NAME}:${EXPORT_TAG}" "${IMAGE_REPO}@${DIGEST}" + else + [ -n "${CI_PUSH}" ] && docker buildx imagetools create --tag "${IMAGE_REPO}:${EXPORT_TAG}-${BUILD_TYPE}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${DH_PUSH}" ] && docker buildx imagetools create --tag "${DH_REGISTRY}/${DH_REGISTRY_USER}/${IMAGE_NAME}:${EXPORT_TAG}-${BUILD_TYPE}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${GH_PUSH}" ] && docker buildx imagetools create --tag "${GH_REGISTRY}/${GH_REGISTRY_USER}/${IMAGE_NAME}:${EXPORT_TAG}-${BUILD_TYPE}" "${IMAGE_REPO}@${DIGEST}" + fi + fi + if [ "${BUILD_TYPE}" = "nightly" ] && [ -n "${NIGHTLY}" ]; then + [ -n "${CI_PUSH}" ] && docker buildx imagetools create --tag "${IMAGE_REPO}:${NIGHTLY_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${DH_PUSH}" ] && docker buildx imagetools create --tag "${DH_REGISTRY}/${DH_REGISTRY_USER}/${IMAGE_NAME}:${NIGHTLY_TAG}" "${IMAGE_REPO}@${DIGEST}" + [ -n "${GH_PUSH}" ] && docker buildx imagetools create --tag "${GH_REGISTRY}/${GH_REGISTRY_USER}/${IMAGE_NAME}:${NIGHTLY_TAG}" "${IMAGE_REPO}@${DIGEST}" + fi + done + user_spack_environment: stage: benchmarks rules: @@ -532,15 +514,7 @@ user_spack_environment: - when: always needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_ - ENV: ci - BUILD_TYPE: default - BUILDER_IMAGE: debian_stable_base - RUNTIME_IMAGE: debian_stable_base - PLATFORM: linux/amd64 + - job: eic-manifest image: name: ${CI_REGISTRY_IMAGE}/eic_ci:${INTERNAL_TAG}-default script: @@ -565,15 +539,7 @@ user_spack_environment: - when: always needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_dev_ - ENV: cuda - BUILD_TYPE: default - BUILDER_IMAGE: cuda_devel - RUNTIME_IMAGE: cuda_devel - PLATFORM: linux/amd64 + - job: eic-manifest image: name: ${CI_REGISTRY_IMAGE}/eic_dev_cuda:${INTERNAL_TAG}-default @@ -608,15 +574,7 @@ eic_xl:singularity:default: BUILD_IMAGE: eic_xl needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_ - ENV: xl - BUILD_TYPE: default - BUILDER_IMAGE: debian_stable_base - RUNTIME_IMAGE: debian_stable_base - PLATFORM: linux/amd64 + - job: eic-manifest # eic-shell/install.py expects build/eic_xl.sif from the job with name 'eic_xl:singularity:nightly' eic_xl:singularity:nightly: @@ -626,15 +584,7 @@ eic_xl:singularity:nightly: BUILD_IMAGE: eic_xl needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_ - ENV: xl - BUILD_TYPE: nightly - BUILDER_IMAGE: debian_stable_base - RUNTIME_IMAGE: debian_stable_base - PLATFORM: linux/amd64 + - job: eic-manifest .benchmarks: stage: benchmarks @@ -655,15 +605,7 @@ eic_xl:singularity:nightly: BUILD_TYPE: default needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_ - ENV: ci - BUILD_TYPE: default - BUILDER_IMAGE: debian_stable_base - RUNTIME_IMAGE: debian_stable_base - PLATFORM: linux/amd64 + - job: eic-manifest .benchmarks:nightly: extends: .benchmarks @@ -671,15 +613,7 @@ eic_xl:singularity:nightly: BUILD_TYPE: nightly needs: - job: version - - job: eic - parallel: - matrix: - - BUILD_IMAGE: eic_ - ENV: ci - BUILD_TYPE: nightly - BUILDER_IMAGE: debian_stable_base - RUNTIME_IMAGE: debian_stable_base - PLATFORM: linux/amd64 + - job: eic-manifest benchmarks:geoviewer:default: extends: .benchmarks:default diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb2d78cb..703ac1c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,4 +32,4 @@ repos: - id: shellcheck types: [shell] args: [--severity=warning, -x] - files: (^[^/]+\.sh$|^\.ci/resolve_git_ref$|^\.ci/check_image_size$) + files: (^[^/]+\.sh$|^scripts/[^/]+\.sh$|^scripts/resolve_git_ref$|^scripts/check_image_size$) diff --git a/containers/eic/Dockerfile b/containers/eic/Dockerfile index 65128450..a190523c 100644 --- a/containers/eic/Dockerfile +++ b/containers/eic/Dockerfile @@ -71,10 +71,10 @@ LABEL org.opencontainers.image.title="Electron-Ion Collider build installation i RUN --mount=type=cache,target=/ccache,id=ccache-${TARGETPLATFORM} \ --mount=type=cache,target=/var/cache/spack \ --mount=type=secret,id=mirrors,target=/opt/spack/etc/spack/mirrors.yaml \ - --mount=type=secret,id=CI_REGISTRY_USER,env=CI_REGISTRY_USER \ - --mount=type=secret,id=CI_REGISTRY_PASSWORD,env=CI_REGISTRY_PASSWORD \ - --mount=type=secret,id=GITHUB_REGISTRY_USER,env=GITHUB_REGISTRY_USER \ - --mount=type=secret,id=GITHUB_REGISTRY_TOKEN,env=GITHUB_REGISTRY_TOKEN \ + --mount=type=secret,id=CI_REGISTRY_USER,env=CI_REGISTRY_USER,required=false \ + --mount=type=secret,id=CI_REGISTRY_PASSWORD,env=CI_REGISTRY_PASSWORD,required=false \ + --mount=type=secret,id=GITHUB_REGISTRY_USER,env=GITHUB_REGISTRY_USER,required=false \ + --mount=type=secret,id=GITHUB_REGISTRY_TOKEN,env=GITHUB_REGISTRY_TOKEN,required=false \ < L5 ``` -## Full Build Script +## Build Script Reference -Here's a complete script for building locally with all optimizations: +Both scripts live in `scripts/` and accept command-line flags. Many options can +also be set via environment variables, but the variable names are script-specific and do +not always exactly match the flag names (for example, `JOBS=8 bash scripts/build-base.sh`, +`LOCAL_TAG=local bash scripts/build-eic.sh`, or `LOCAL_BASE_TAG=local bash scripts/build-eic.sh`). -```bash -#!/bin/bash -set -e - -REPO_DIR="$(pwd)" -REGISTRY="ghcr.io/eic" -ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') - -# Load version information -source spack.sh -source spack-packages.sh -source key4hep-spack.sh -source eic-spack.sh - -# Helper to resolve git refs -resolve_ref() { - .ci/resolve_git_ref "$1" "$2" -} - -# Build base image -echo "=== Building debian_stable_base ===" -docker buildx build \ - -f containers/debian/Dockerfile \ - --build-arg SPACK_ORGREPO=${SPACK_ORGREPO} \ - --build-arg SPACK_VERSION=${SPACK_VERSION} \ - --build-arg SPACKPACKAGES_ORGREPO=${SPACKPACKAGES_ORGREPO} \ - --build-arg SPACKPACKAGES_VERSION=${SPACKPACKAGES_VERSION} \ - --build-arg KEY4HEPSPACK_ORGREPO=${KEY4HEPSPACK_ORGREPO} \ - --build-arg KEY4HEPSPACK_VERSION=${KEY4HEPSPACK_VERSION} \ - --build-arg EICSPACK_ORGREPO=${EICSPACK_ORGREPO} \ - --build-arg EICSPACK_VERSION=${EICSPACK_VERSION} \ - --build-arg jobs=8 \ - --cache-from type=registry,ref=${REGISTRY}/buildcache:debian_stable_base-master-${ARCH} \ - -t debian_stable_base:local \ - --load \ - containers/debian +### `scripts/build-base.sh` -# Build EIC image -echo "=== Building eic_xl ===" -docker buildx build \ - -f containers/eic/Dockerfile \ - --build-context spack-environment=spack-environment \ - --build-arg DOCKER_REGISTRY="" \ - --build-arg BUILDER_IMAGE=debian_stable_base:local \ - --build-arg RUNTIME_IMAGE=debian_stable_base:local \ - --build-arg INTERNAL_TAG="" \ - --build-arg ENV=xl \ - --cache-from type=registry,ref=${REGISTRY}/buildcache:eic_xl-default-master-${ARCH} \ - -t eic_xl:local \ - --load \ - containers/eic +Builds the base image that all EIC images depend on. + +``` +bash scripts/build-base.sh [options] -echo "=== Build complete ===" -echo "Run with: docker run -it eic_xl:local" + --image IMAGE Image to build: debian_stable_base (default), cuda_devel, cuda_runtime + --base-image IMAGE Upstream image (derived automatically from --image if omitted) + --platform PLATFORM linux/amd64 (default), linux/arm64 + --jobs N Parallel Spack build jobs (default: nproc) + --tag TAG Output image tag (default: local) +``` + +### `scripts/build-eic.sh` + +Builds an EIC software environment image. + +``` +bash scripts/build-eic.sh [options] + + --env ENV Environment: ci, xl (default), cuda, dbg, jl, prod, cvmfs, tf, ... + --build-type TYPE Comma-separated list: default, nightly, or default,nightly (default: default,nightly) + --builder-image IMG Builder base image name (default: debian_stable_base) + --runtime-image IMG Runtime base image name (default: debian_stable_base) + --platform PLATFORM linux/amd64 (default), linux/arm64 + --jobs N Parallel Spack build jobs (default: nproc) + --base-tag TAG Tag of the locally built base image to use (default: local); if not found + locally, ghcr.io/eic/:latest is used as fallback + --tag TAG Output image tag (default: local) ``` ## Build Arguments Reference -### Base Image (containers/debian/Dockerfile) +### Base image build arguments used by `scripts/build-base.sh` (`containers/debian/Dockerfile`) + +This table documents values passed by `scripts/build-base.sh`. If you invoke `docker buildx build` +directly, Dockerfile `ARG` defaults apply instead. | Argument | Description | Default | |----------|-------------|---------| -| `BASE_IMAGE` | Base Debian image | `debian:stable-slim` | +| `BASE_IMAGE` | Base Debian image passed by script | `debian:trixie-slim` for `debian_stable_base` (derived from `--image`) | | `SPACK_ORGREPO` | Spack GitHub org/repo | `spack/spack` | -| `SPACK_VERSION` | Spack version/branch | `develop` | +| `SPACK_VERSION` | Spack version/branch | (from `spack.sh`) | | `SPACK_SHA` | Specific commit SHA | (resolved from version) | -| `SPACK_CHERRYPICKS` | Space-separated cherry-pick SHAs | | -| `SPACKPACKAGES_*` | Similar args for spack-packages | | -| `KEY4HEPSPACK_*` | Similar args for key4hep-spack | | -| `EICSPACK_*` | Similar args for eic-spack | | +| `SPACK_CHERRYPICKS` | Newline-separated cherry-pick SHAs | (from `spack.sh`) | +| `SPACKPACKAGES_*` | Similar args for spack-packages | (from `spack-packages.sh`) | +| `KEY4HEPSPACK_*` | Similar args for key4hep-spack | (from `key4hep-spack.sh`) | +| `EICSPACK_*` | Similar args for eic-spack | (from `eic-spack.sh`) | | `jobs` | Parallel build jobs | `1` | ### EIC Image (containers/eic/Dockerfile) | Argument | Description | Default | |----------|-------------|---------| -| `DOCKER_REGISTRY` | Registry prefix for base images | `eicweb/` | +| `DOCKER_REGISTRY` | Registry prefix for base images | empty (local) or `ghcr.io/eic/` (GHCR fallback) | | `BUILDER_IMAGE` | Builder base image name | `debian_stable_base` | | `RUNTIME_IMAGE` | Runtime base image name | `debian_stable_base` | -| `INTERNAL_TAG` | Tag for base images | `master` | -| `ENV` | Environment type (`ci` or `xl`) | `xl` | +| `INTERNAL_TAG` | Tag for base images | value of `--base-tag` (local) or `latest` (GHCR fallback) | +| `ENV` | Environment type | `xl` | +| `SPACK_DUPLICATE_ALLOWLIST` | Pipe-separated allowed duplicates | (per ENV) | | `EDM4EIC_SHA` | Custom edm4eic commit | | | `EICRECON_SHA` | Custom eicrecon commit | | | `EPIC_SHA` | Custom epic commit | | @@ -224,14 +215,13 @@ echo "Run with: docker run -it eic_xl:local" Reduce the number of parallel jobs: ```bash -docker buildx build --build-arg jobs=2 ... +bash scripts/build-base.sh --jobs 2 ``` ### Build Takes Too Long -1. Ensure you're using registry cache (`--cache-from`) -2. Check that Spack buildcache is accessible -3. Build the CI environment first (smaller): `--build-arg ENV=ci` +1. Ensure you're on a fast network (registry cache is fetched from `ghcr.io`) +2. Build the CI environment first (smaller): `bash scripts/build-eic.sh --env ci` ### Cannot Pull from ghcr.io @@ -258,11 +248,7 @@ Using QEMU emulation (slower but works): docker run --privileged --rm tonistiigi/binfmt --install arm64 # Build for ARM64 -docker buildx build \ - --platform linux/arm64 \ - -f containers/debian/Dockerfile \ - -t debian_stable_base:local-arm64 \ - containers/debian +bash scripts/build-base.sh --platform linux/arm64 --tag local-arm64 ``` ## Next Steps diff --git a/scripts/build-base.sh b/scripts/build-base.sh new file mode 100755 index 00000000..b142ab85 --- /dev/null +++ b/scripts/build-base.sh @@ -0,0 +1,200 @@ +#!/bin/bash +# Build the EIC base container image (debian_stable_base, cuda_devel, or cuda_runtime). +# +# This script is used in GitLab CI, GitHub Actions, and for local builds. +# CI mode is detected via CI_REGISTRY (GitLab) or GITHUB_ACTIONS=true (GitHub Actions). +# +# Run `bash scripts/build-base.sh --help` for usage, options, and CI-specific details. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +cd "${REPO_DIR}" + +print_help() { + cat <&2; echo "Try 'bash scripts/build-base.sh --help' for usage." >&2; exit 1 ;; + esac +done + +## Source version files +source "${REPO_DIR}/spack.sh" +source "${REPO_DIR}/spack-packages.sh" +source "${REPO_DIR}/key4hep-spack.sh" +source "${REPO_DIR}/eic-spack.sh" + +## Convert an arbitrary git ref/branch name to a valid OCI tag component. +## Mirrors GitLab's CI_COMMIT_REF_SLUG: lowercase, non-alnum runs → '-', +## strip leading/trailing '-', truncate to 63 chars. +slugify() { + echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' | cut -c1-63 +} + +## Detect CI mode and normalise environment variables +if [ -n "${CI_REGISTRY}" ]; then + ## GitLab CI — all CI_* variables are already set by the runner + CI_MODE="gitlab" +elif [ "${GITHUB_ACTIONS}" = "true" ]; then + ## GitHub Actions — map GitHub variables to the names used below. + ## Use GITHUB_HEAD_REF for PR source branches (better cache reuse across PR + ## updates), then fall back to GITHUB_REF_NAME for push/schedule events. + ## GITHUB_BASE_REF is the PR target branch (empty on push events). DEFAULT_BRANCH + ## should be supplied by the workflow so cache keys remain stable when + ## GITHUB_BASE_REF is empty. + CI_MODE="github" + CI_REGISTRY="${GH_REGISTRY}" + CI_PROJECT_PATH="${GH_REGISTRY_USER}" + CI_COMMIT_REF_SLUG="$(slugify "${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-master}}")" + CI_DEFAULT_BRANCH_SLUG="$(slugify "${GITHUB_BASE_REF:-${DEFAULT_BRANCH:-master}}")" + INTERNAL_TAG="${INTERNAL_TAG:-pipeline-${GITHUB_RUN_ID}}" +else + CI_MODE="local" +fi + +## Derive BASE_IMAGE from BUILD_IMAGE if not provided +if [ -z "${BASE_IMAGE}" ]; then + case "${BUILD_IMAGE}" in + debian_stable_base) BASE_IMAGE="debian:trixie-slim" ;; + cuda_devel) BASE_IMAGE="nvidia/cuda:${CUDA_VERSION}-devel-${CUDA_OS}" ;; + cuda_runtime) BASE_IMAGE="nvidia/cuda:${CUDA_VERSION}-runtime-${CUDA_OS}" ;; + *) echo "Unknown BUILD_IMAGE '${BUILD_IMAGE}'; please specify --base-image" >&2; exit 1 ;; + esac +fi + +## Resolve SHAs (network calls — skipped if version is already a SHA) +echo "Resolving git SHAs..." +SPACK_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" "${SPACK_ORGREPO}" "${SPACK_VERSION}") +SPACKPACKAGES_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" "${SPACKPACKAGES_ORGREPO}" "${SPACKPACKAGES_VERSION}") +KEY4HEPSPACK_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" "${KEY4HEPSPACK_ORGREPO}" "${KEY4HEPSPACK_VERSION}") +EICSPACK_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" "${EICSPACK_ORGREPO}" "${EICSPACK_VERSION}") + +## Normalize arch string for cache tag names while preserving platform variants +## Examples: linux/amd64 -> amd64, linux/amd64/v3 -> amd64_v3, linux/arm/v7 -> arm_v7 +ARCH=$(echo "${PLATFORM}" | sed 's|^linux/||; s|/|_|g') + +## Build the docker buildx command as an array for safe quoting +build_cmd=(docker buildx build) +# shellcheck disable=SC2206 # word splitting is intentional: BUILD_OPTIONS is a space-separated list +build_cmd+=(${BUILD_OPTIONS}) + +## Derive shared registry prefix (used for image push, caching, and DOCKER_REGISTRY build-arg) +CI_REGISTRY_PREFIX="${CI_REGISTRY}/${CI_PROJECT_PATH}" +IMAGE_REPO="${CI_REGISTRY_PREFIX}/${BUILD_IMAGE}" + +## Output mode: push-by-digest in all CI modes; load locally +if [ "${CI_MODE}" != "local" ]; then + ## Push by digest; CI wrapper creates final tags via imagetools create + build_cmd+=(--output "type=image,name=${IMAGE_REPO},push-by-digest=true,name-canonical=true,push=true") + build_cmd+=(--metadata-file "${METADATA_FILE}") +else + build_cmd+=(--load) +fi + +## Cache sources: CI registry (if in CI) plus public ghcr.io/eic (GitLab and local modes) +BUILDCACHE_REPOS=() +[ "${CI_MODE}" != "local" ] && BUILDCACHE_REPOS+=("${CI_REGISTRY_PREFIX}") +[ "${CI_MODE}" != "github" ] && BUILDCACHE_REPOS+=("ghcr.io/eic") +for REPO in "${BUILDCACHE_REPOS[@]}"; do + build_cmd+=(--cache-from "type=registry,ref=${REPO}/buildcache:${BUILD_IMAGE}-${CI_COMMIT_REF_SLUG:-master}-${ARCH}") + build_cmd+=(--cache-from "type=registry,ref=${REPO}/buildcache:${BUILD_IMAGE}-${CI_DEFAULT_BRANCH_SLUG:-master}-${ARCH}") +done + +## Cache destination (CI only) +if [ "${CI_MODE}" != "local" ]; then + build_cmd+=(--cache-to "type=registry,ref=${CI_REGISTRY_PREFIX}/buildcache:${BUILD_IMAGE}-${CI_COMMIT_REF_SLUG:-master}-${ARCH},mode=max") +fi + +## Image tag (local only; CI creates tags via imagetools create after build) +if [ "${CI_MODE}" = "local" ]; then + build_cmd+=(--tag "${BUILD_IMAGE}:${LOCAL_TAG}") +fi + +## Dockerfile and platform +build_cmd+=(--file containers/debian/Dockerfile) +build_cmd+=(--platform "${PLATFORM}") + +## Build arguments +build_cmd+=(--build-arg "BASE_IMAGE=${BASE_IMAGE}") +build_cmd+=(--build-arg "BUILD_IMAGE=${BUILD_IMAGE}") +case "${BUILD_IMAGE}" in + cuda*) + build_cmd+=(--build-arg "NVIDIA_VISIBLE_DEVICES=all") + build_cmd+=(--build-arg "NVIDIA_DRIVER_CAPABILITIES=all") + ;; +esac +build_cmd+=(--build-arg "SPACK_ORGREPO=${SPACK_ORGREPO}") +build_cmd+=(--build-arg "SPACK_VERSION=${SPACK_VERSION}") +build_cmd+=(--build-arg "SPACK_SHA=${SPACK_SHA}") +build_cmd+=(--build-arg "SPACK_CHERRYPICKS=${SPACK_CHERRYPICKS}") +build_cmd+=(--build-arg "SPACK_CHERRYPICKS_FILES=${SPACK_CHERRYPICKS_FILES}") +build_cmd+=(--build-arg "SPACKPACKAGES_ORGREPO=${SPACKPACKAGES_ORGREPO}") +build_cmd+=(--build-arg "SPACKPACKAGES_VERSION=${SPACKPACKAGES_VERSION}") +build_cmd+=(--build-arg "SPACKPACKAGES_SHA=${SPACKPACKAGES_SHA}") +build_cmd+=(--build-arg "SPACKPACKAGES_CHERRYPICKS=${SPACKPACKAGES_CHERRYPICKS}") +build_cmd+=(--build-arg "SPACKPACKAGES_CHERRYPICKS_FILES=${SPACKPACKAGES_CHERRYPICKS_FILES}") +build_cmd+=(--build-arg "KEY4HEPSPACK_ORGREPO=${KEY4HEPSPACK_ORGREPO}") +build_cmd+=(--build-arg "KEY4HEPSPACK_VERSION=${KEY4HEPSPACK_VERSION}") +build_cmd+=(--build-arg "KEY4HEPSPACK_SHA=${KEY4HEPSPACK_SHA}") +build_cmd+=(--build-arg "EICSPACK_ORGREPO=${EICSPACK_ORGREPO}") +build_cmd+=(--build-arg "EICSPACK_VERSION=${EICSPACK_VERSION}") +build_cmd+=(--build-arg "EICSPACK_SHA=${EICSPACK_SHA}") +build_cmd+=(--build-arg "jobs=${JOBS}") +build_cmd+=(--build-arg "BUILDWEEK=${BUILDWEEK:-0}") + +## Suppress provenance attestation (matches CI behaviour) +build_cmd+=(--provenance false) + +## Build context +build_cmd+=(containers/debian) + +## Execute +set -o xtrace -o pipefail +"${build_cmd[@]}" 2>&1 | tee build.log diff --git a/scripts/build-eic.sh b/scripts/build-eic.sh new file mode 100755 index 00000000..4d428112 --- /dev/null +++ b/scripts/build-eic.sh @@ -0,0 +1,313 @@ +#!/bin/bash +# Build an EIC container image (eic_ci, eic_xl, eic_cuda, etc.). +# +# This script is used in GitLab CI, GitHub Actions, and for local builds. +# CI mode is detected via CI_REGISTRY (GitLab) or GITHUB_ACTIONS=true (GitHub Actions). +# +# Run `bash scripts/build-eic.sh --help` for usage, options, and CI-specific details. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +cd "${REPO_DIR}" + +print_help() { + cat <.json (default base: /tmp/build-metadata.json). +EOF +} + +## Defaults (may be overridden by env vars set from CI matrix or command-line flags) +BUILD_IMAGE="${BUILD_IMAGE:-eic_}" +ENV="${ENV:-xl}" +BUILD_TYPE="${BUILD_TYPE:-default,nightly}" +BUILDER_IMAGE="${BUILDER_IMAGE:-debian_stable_base}" +RUNTIME_IMAGE="${RUNTIME_IMAGE:-debian_stable_base}" +BUILD_TARGET="${BUILD_TARGET:-final}" +PLATFORM="${PLATFORM:-linux/amd64}" +JOBS="${JOBS:-$(getconf _NPROCESSORS_ONLN)}" +LOCAL_TAG="${LOCAL_TAG:-local}" +LOCAL_BASE_TAG="${LOCAL_BASE_TAG:-local}" +METADATA_FILE="${METADATA_FILE:-/tmp/build-metadata.json}" + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) print_help; exit 0 ;; + --env) ENV="$2"; shift 2 ;; + --build-type) BUILD_TYPE="$2"; shift 2 ;; + --builder-image) BUILDER_IMAGE="$2"; shift 2 ;; + --runtime-image) RUNTIME_IMAGE="$2"; shift 2 ;; + --target) BUILD_TARGET="$2"; shift 2 ;; + --platform) PLATFORM="$2"; shift 2 ;; + --jobs) JOBS="$2"; shift 2 ;; + --base-tag) LOCAL_BASE_TAG="$2"; shift 2 ;; + --tag) LOCAL_TAG="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; echo "Try 'bash scripts/build-eic.sh --help' for usage." >&2; exit 1 ;; + esac +done + +## Source version files (only spack-packages version is needed for mirrors.yaml) +source "${REPO_DIR}/spack-packages.sh" + +## Convert an arbitrary git ref/branch name to a valid OCI tag component. +## Mirrors GitLab's CI_COMMIT_REF_SLUG: lowercase, non-alnum runs → '-', +## strip leading/trailing '-', truncate to 63 chars. +slugify() { + echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' | cut -c1-63 +} + +## Escape a string for safe use as a sed replacement (handles \, &, and | delimiter). +sed_escape() { + printf '%s' "$1" | sed 's/[\\&|]/\\&/g' +} + +## Detect CI mode and normalise environment variables +if [ -n "${CI_REGISTRY}" ]; then + ## GitLab CI — all CI_* variables are already set by the runner + CI_MODE="gitlab" +elif [ "${GITHUB_ACTIONS}" = "true" ]; then + ## GitHub Actions — map GitHub variables to the names used below. + ## Use GITHUB_HEAD_REF for PR source branches (better cache reuse across PR + ## updates), then fall back to GITHUB_REF_NAME for push/schedule events. + ## GITHUB_BASE_REF is the PR target branch (empty on push events). DEFAULT_BRANCH + ## should be supplied by the workflow so cache keys remain stable when + ## GITHUB_BASE_REF is empty. + CI_MODE="github" + CI_REGISTRY="${GH_REGISTRY}" + CI_PROJECT_PATH="${GH_REGISTRY_USER}" + CI_COMMIT_REF_SLUG="$(slugify "${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-master}}")" + CI_DEFAULT_BRANCH_SLUG="$(slugify "${GITHUB_BASE_REF:-${DEFAULT_BRANCH:-master}}")" + CI_COMMIT_SHA="${GITHUB_SHA:-}" + INTERNAL_TAG="${INTERNAL_TAG:-pipeline-${GITHUB_RUN_ID}}" +else + CI_MODE="local" +fi + +## Generate mirrors.yaml in a temp file; the trap ensures cleanup on exit. +## sed replacement values are escaped to handle special chars (\, &, |). +MIRRORS_YAML=$(mktemp "${TMPDIR:-/tmp}/mirrors-XXXXXX.yaml") +trap 'rm -f "${MIRRORS_YAML}"' EXIT INT TERM +if [ "${CI_MODE}" != "local" ]; then + ## CI mode: expand CI_REGISTRY/CI_PROJECT_PATH variables in the template + sed -e "s|\${CI_REGISTRY}|$(sed_escape "${CI_REGISTRY}")|g" \ + -e "s|\${CI_PROJECT_PATH}|$(sed_escape "${CI_PROJECT_PATH}")|g" \ + -e "s|\${SPACKPACKAGES_VERSION}|$(sed_escape "${SPACKPACKAGES_VERSION}")|g" \ + "${REPO_DIR}/mirrors.yaml.in" > "${MIRRORS_YAML}" +else + ## Local mode: public-only mirrors (no credentials required) + cat > "${MIRRORS_YAML}" < amd64, linux/amd64/v3 -> amd64_v3, linux/arm/v7 -> arm_v7 +ARCH=$(echo "${PLATFORM}" | sed 's|linux/||; s|/|_|g') + +## Derive shared registry prefix (used for image push, caching, and DOCKER_REGISTRY build-arg) +CI_REGISTRY_PREFIX="${CI_REGISTRY}/${CI_PROJECT_PATH}" +IMAGE_REPO="${CI_REGISTRY_PREFIX}/${BUILD_IMAGE}${ENV}" + +## Validate and split the build-type list +IFS=',' read -ra BUILD_TYPES <<< "${BUILD_TYPE}" +for _bt in "${BUILD_TYPES[@]}"; do + _bt="${_bt#"${_bt%%[![:space:]]*}"}"; _bt="${_bt%"${_bt##*[![:space:]]}"}" # trim whitespace + case "${_bt}" in + default|nightly) ;; + *) echo "Unknown build type '${_bt}'; must be 'default' or 'nightly'." >&2; exit 1 ;; + esac +done + +## Enable xtrace and pipefail for the build loop +set -o xtrace -o pipefail + +## Build each type sequentially; the shared base Docker stages (default env concretization +## and installation) are reused from BuildKit's layer cache after the first build. +for build_type in "${BUILD_TYPES[@]}"; do + ## Trim whitespace from the build type + build_type="${build_type#"${build_type%%[![:space:]]*}"}"; build_type="${build_type%"${build_type##*[![:space:]]}"}" + + ## Resolve optional version overrides (nightly always resolves; default only if version set) + unset EDM4EIC_SHA EICRECON_SHA EPIC_SHA JUGGLER_SHA + if [ "${build_type}" = "nightly" ]; then + EDM4EIC_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/EDM4eic "${EDM4EIC_VERSION:-main}") + EICRECON_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/EICrecon "${EICRECON_VERSION:-main}") + EPIC_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/epic "${EPIC_VERSION:-main}") + JUGGLER_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/juggler "${JUGGLER_VERSION:-main}") + else + ## default build: only resolve if version is explicitly provided + [ -n "${EDM4EIC_VERSION}" ] && EDM4EIC_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/EDM4eic "${EDM4EIC_VERSION}") + [ -n "${EICRECON_VERSION}" ] && EICRECON_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/EICrecon "${EICRECON_VERSION}") + [ -n "${EPIC_VERSION}" ] && EPIC_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/epic "${EPIC_VERSION}") + [ -n "${JUGGLER_VERSION}" ] && JUGGLER_SHA=$(sh "${REPO_DIR}/scripts/resolve_git_ref" eic/juggler "${JUGGLER_VERSION}") + fi + + ## Build the docker buildx command as an array for safe quoting + build_cmd=(docker buildx build) + # shellcheck disable=SC2206 # word splitting is intentional: BUILD_OPTIONS is a space-separated list + build_cmd+=(${BUILD_OPTIONS}) + + ## Output mode: push-by-digest in all CI modes; load locally + if [ "${CI_MODE}" != "local" ]; then + ## Push by digest; CI wrapper creates final tags via imagetools create. + ## Always write a per-build-type metadata file so manifest jobs can identify it. + build_cmd+=(--output "type=image,name=${IMAGE_REPO},push-by-digest=true,name-canonical=true,push=true") + build_cmd+=(--metadata-file "${METADATA_FILE%.json}-${build_type}.json") + else + build_cmd+=(--load) + fi + + ## Cache sources: CI registry (if in CI) plus public ghcr.io/eic (GitLab and local modes) + CACHE_KEY="${BUILD_IMAGE}${ENV}-${build_type}" + BUILDCACHE_REPOS=() + [ "${CI_MODE}" != "local" ] && BUILDCACHE_REPOS+=("${CI_REGISTRY_PREFIX}") + [ "${CI_MODE}" != "github" ] && BUILDCACHE_REPOS+=("ghcr.io/eic") + for REPO in "${BUILDCACHE_REPOS[@]}"; do + build_cmd+=(--cache-from "type=registry,ref=${REPO}/buildcache:${CACHE_KEY}-${CI_COMMIT_REF_SLUG:-master}-${ARCH}") + build_cmd+=(--cache-from "type=registry,ref=${REPO}/buildcache:${CACHE_KEY}-${CI_DEFAULT_BRANCH_SLUG:-master}-${ARCH}") + done + + ## Cache destination (CI only) + if [ "${CI_MODE}" != "local" ]; then + build_cmd+=(--cache-to "type=registry,ref=${CI_REGISTRY_PREFIX}/buildcache:${CACHE_KEY}-${CI_COMMIT_REF_SLUG:-master}-${ARCH},mode=max") + fi + + ## Image tag (local only; CI creates tags via imagetools create after build) + if [ "${CI_MODE}" = "local" ]; then + build_cmd+=(--tag "${BUILD_IMAGE}${ENV}:${LOCAL_TAG}-${build_type}") + fi + + ## Dockerfile, target stage, and platform + build_cmd+=(--file containers/eic/Dockerfile) + [ -n "${BUILD_TARGET}" ] && build_cmd+=(--target "${BUILD_TARGET}") + build_cmd+=(--platform "${PLATFORM}") + + ## Build arguments + build_cmd+=(--build-arg "BENCHMARK_COM_SHA=${BENCHMARK_COM_SHA}") + build_cmd+=(--build-arg "BENCHMARK_DET_SHA=${BENCHMARK_DET_SHA}") + build_cmd+=(--build-arg "BENCHMARK_REC_SHA=${BENCHMARK_REC_SHA}") + build_cmd+=(--build-arg "BENCHMARK_PHY_SHA=${BENCHMARK_PHY_SHA}") + build_cmd+=(--build-arg "CAMPAIGNS_HEPMC3_SHA=${CAMPAIGNS_HEPMC3_SHA}") + build_cmd+=(--build-arg "CAMPAIGNS_CONDOR_SHA=${CAMPAIGNS_CONDOR_SHA}") + build_cmd+=(--build-arg "CAMPAIGNS_SLURM_SHA=${CAMPAIGNS_SLURM_SHA}") + + if [ "${CI_MODE}" != "local" ]; then + build_cmd+=(--build-arg "DOCKER_REGISTRY=${CI_REGISTRY_PREFIX}/") + build_cmd+=(--build-arg "INTERNAL_TAG=${INTERNAL_TAG}") + build_cmd+=(--build-arg "CI_COMMIT_SHA=${CI_COMMIT_SHA}") + else + ## Auto-detect: use locally built base images if available, otherwise pull from ghcr.io/eic/. + ## Both BUILDER_IMAGE and RUNTIME_IMAGE must exist locally to avoid a mixed local/remote build. + if docker image inspect "${BUILDER_IMAGE}:${LOCAL_BASE_TAG}" >/dev/null 2>&1 \ + && docker image inspect "${RUNTIME_IMAGE}:${LOCAL_BASE_TAG}" >/dev/null 2>&1; then + echo "Using local base images: ${BUILDER_IMAGE}:${LOCAL_BASE_TAG}, ${RUNTIME_IMAGE}:${LOCAL_BASE_TAG}" + build_cmd+=(--build-arg "DOCKER_REGISTRY=") + build_cmd+=(--build-arg "INTERNAL_TAG=${LOCAL_BASE_TAG}") + else + echo "Local base images not found (${BUILDER_IMAGE}:${LOCAL_BASE_TAG} and/or ${RUNTIME_IMAGE}:${LOCAL_BASE_TAG}); pulling from ghcr.io/eic/:latest" + build_cmd+=(--build-arg "DOCKER_REGISTRY=ghcr.io/eic/") + build_cmd+=(--build-arg "INTERNAL_TAG=latest") + fi + fi + ## EIC_CONTAINER_VERSION format is intentionally different per CI system + if [ "${CI_MODE}" = "gitlab" ]; then + build_cmd+=(--build-arg "EIC_CONTAINER_VERSION=${EXPORT_TAG}-${build_type}-$(git rev-parse HEAD)") + elif [ "${CI_MODE}" = "github" ]; then + build_cmd+=(--build-arg "EIC_CONTAINER_VERSION=github-${build_type}-${CI_COMMIT_SHA:-$(git rev-parse HEAD 2>/dev/null || echo unknown)}") + else + build_cmd+=(--build-arg "EIC_CONTAINER_VERSION=local-${build_type}-$(git rev-parse HEAD 2>/dev/null || echo unknown)") + fi + + build_cmd+=(--build-arg "BUILDER_IMAGE=${BUILDER_IMAGE}") + build_cmd+=(--build-arg "RUNTIME_IMAGE=${RUNTIME_IMAGE}") + build_cmd+=(--build-arg "ENV=${ENV}") + build_cmd+=(--build-arg "SPACK_DUPLICATE_ALLOWLIST=${SPACK_DUPLICATE_ALLOWLIST}") + build_cmd+=(--build-arg "jobs=${JOBS}") + + ## Optional version overrides + [ -n "${EDM4EIC_SHA}" ] && build_cmd+=(--build-arg "EDM4EIC_SHA=${EDM4EIC_SHA}") + [ -n "${EICRECON_SHA}" ] && build_cmd+=(--build-arg "EICRECON_SHA=${EICRECON_SHA}") + [ -n "${EPIC_SHA}" ] && build_cmd+=(--build-arg "EPIC_SHA=${EPIC_SHA}") + [ -n "${JUGGLER_SHA}" ] && build_cmd+=(--build-arg "JUGGLER_SHA=${JUGGLER_SHA}") + + ## Additional build contexts + build_cmd+=(--build-context "spack-environment=spack-environment") + + ## Secrets + build_cmd+=(--secret "id=mirrors,src=${MIRRORS_YAML}") + if [ "${CI_MODE}" != "local" ]; then + build_cmd+=(--secret "type=env,id=CI_REGISTRY_USER,env=CI_REGISTRY_USER") + build_cmd+=(--secret "type=env,id=CI_REGISTRY_PASSWORD,env=CI_REGISTRY_PASSWORD") + build_cmd+=(--secret "type=env,id=GITHUB_REGISTRY_USER,env=GITHUB_REGISTRY_USER") + build_cmd+=(--secret "type=env,id=GITHUB_REGISTRY_TOKEN,env=GITHUB_REGISTRY_TOKEN") + fi + + ## Suppress provenance attestation (matches CI behaviour) + build_cmd+=(--provenance false) + + ## Build context + build_cmd+=(containers/eic) + + ## Execute + "${build_cmd[@]}" 2>&1 | tee "build-${build_type}.log" +done diff --git a/.ci/check_image_size b/scripts/check_image_size similarity index 94% rename from .ci/check_image_size rename to scripts/check_image_size index 2935d13c..d9323a41 100755 --- a/.ci/check_image_size +++ b/scripts/check_image_size @@ -8,8 +8,8 @@ # # Requires: docker (with buildx), jq, bc # Usage: -# .ci/check_image_size ghcr.io/eic/eic_xl@sha256:abc... 10 -# .ci/check_image_size ghcr.io/eic/eic_xl:master 10 +# scripts/check_image_size ghcr.io/eic/eic_xl@sha256:abc... 10 +# scripts/check_image_size ghcr.io/eic/eic_xl:master 10 # # The caller is responsible for being logged in to the registry before calling # this script. @@ -82,7 +82,7 @@ if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then echo "" echo "To update the high-water mark after an intentional size increase:" echo " 1. Re-run this script to inspect the published image size:" - echo " .ci/check_image_size \"${IMAGE_REF}\" \"${LIMIT_GIB}\"" + echo " scripts/check_image_size \"${IMAGE_REF}\" \"${LIMIT_GIB}\"" echo " 2. Add ~15% buffer, and optionally round up to the next whole GiB" echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" exit 1 diff --git a/.ci/resolve_git_ref b/scripts/resolve_git_ref similarity index 100% rename from .ci/resolve_git_ref rename to scripts/resolve_git_ref