diff --git a/.github/workflows/docker_build_tpls.yml b/.github/workflows/docker_build_tpls.yml index c5abfea8..1bb5e9c4 100644 --- a/.github/workflows/docker_build_tpls.yml +++ b/.github/workflows/docker_build_tpls.yml @@ -173,6 +173,14 @@ jobs: SPACK_BUILD_JOBS: 8 SPEC: "+rocm ~uncrustify ~openmp ~pygeosx ~docs ~trilinos ~petsc amdgpu_target=gfx942 %%amdclang-19 ^caliper~papi~gotcha~sampler~libunwind~libdw" + # Built natively. The published image carries the macOS prefix; it is + # not a Linux container produced by docker-build.sh. + - name: macOS 26 - Apple Clang (arm64) + RUNS_ON: macos-26 + DOCKER_REPOSITORY: geosx/macos-26-apple-clang + SPEC: "~pygeosx ~docs ~openmp %c,cxx=apple-clang %fortran=gcc@16.2.0" + SPACK_BUILD_JOBS: 2 + steps: - name: Checkout uses: actions/checkout@v7 @@ -182,7 +190,7 @@ jobs: - name: Set up Docker Buildx id: buildx_hosted - if: matrix.RUNS_ON != 'streak2' + if: matrix.RUNS_ON != 'streak2' && matrix.RUNS_ON != 'macos-26' uses: docker/setup-buildx-action@v4 with: cleanup: true @@ -200,6 +208,7 @@ jobs: - name: Compose DOCKER_BASE_IMAGE id: base + if: matrix.RUNS_ON != 'macos-26' run: | # Rows that do not build on a docker_base_images image (e.g. ROCm) # pin their base image directly. @@ -264,9 +273,111 @@ jobs: DOCKER_BUILDER: ${{ steps.buildx_streak2.outputs.name || steps.buildx_hosted.outputs.name }} DOCKER_LOAD: 1 DOCKER_NETWORK: ${{ matrix.RUNS_ON == 'streak2' && 'host' || '' }} + if: matrix.RUNS_ON != 'macos-26' run: bash -x ./scripts/docker-build.sh + - name: Set up Python for Uberenv + if: matrix.RUNS_ON == 'macos-26' + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Set up build environment + if: matrix.RUNS_ON == 'macos-26' + env: + HOMEBREW_NO_ANALYTICS: 1 + run: | + python3 -m pip install clingo + short_sha=${GITHUB_SHA:0:7} + docker_tag="${{ github.event.number }}-${{ github.run_number }}" + echo "SPACK_PYTHON=$(command -v python3)" >> "$GITHUB_ENV" + echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV" + echo "GEOS_TPL_DIR=/opt/GEOS/GEOS_TPL-${docker_tag}-${short_sha}" >> "$GITHUB_ENV" + echo "SPACK_CONFIG=$RUNNER_TEMP/geosx-macos-spack.yaml" >> "$GITHUB_ENV" + sudo mkdir -p /opt/GEOS + sudo chown "$(id -un)" /opt/GEOS + + - name: Test dependency setup + if: matrix.RUNS_ON == 'macos-26' + run: bash scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash + + - name: Prepare dependencies + if: matrix.RUNS_ON == 'macos-26' + env: + HOMEBREW_NO_ANALYTICS: 1 + run: | + unset HOMEBREW_NO_INSTALL_UPGRADE HOMEBREW_NO_INSTALL_CLEANUP + brew update + python3 - <<'PY' + import json + import subprocess + + with open('scripts/spack_configs/macOS/homebrew-manifest.json') as stream: + formulae = [entry['name'] for entry in json.load(stream)['formulae']] + installed = subprocess.check_output(['brew', 'list', '--formula'], text=True).split() + if 'mpich' in installed and 'open-mpi' in formulae: + formulae.remove('open-mpi') + subprocess.run(['brew', 'install', '--formula', *formulae], check=True) + PY + bash scripts/setupMacOS-TPL-deps.bash --spack-config-out "$SPACK_CONFIG" \ + 2>&1 | tee "$RUNNER_TEMP/macos-setup.log" + + - name: Build TPLs + if: matrix.RUNS_ON == 'macos-26' + env: + HOMEBREW_NO_ANALYTICS: 1 + SPEC: ${{ matrix.SPEC }} + SPACK_BUILD_JOBS: ${{ matrix.SPACK_BUILD_JOBS }} + run: | + python3 scripts/uberenv/uberenv.py \ + --project-json="$GITHUB_WORKSPACE/.uberenv_config.json" \ + --spack-env-file="$SPACK_CONFIG" \ + --prefix="$GEOS_TPL_DIR" \ + --spec="$SPEC" \ + -j "$SPACK_BUILD_JOBS" 2>&1 | tee "$RUNNER_TEMP/macos-build.log" + + - name: Prepare image publish + if: matrix.RUNS_ON == 'macos-26' + run: | + set -euo pipefail + curl -fsSL -o "$RUNNER_TEMP/crane.tgz" \ + https://github.com/google/go-containerregistry/releases/download/v0.20.6/go-containerregistry_Darwin_arm64.tar.gz + tar -xzf "$RUNNER_TEMP/crane.tgz" -C "$RUNNER_TEMP" crane + "$RUNNER_TEMP/crane" version + + - name: Package TPL image + if: matrix.RUNS_ON == 'macos-26' + env: + DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} + DOCKER_TAG: ${{ github.event.number }}-${{ github.run_number }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + set -euo pipefail + host_config=$(find "$GITHUB_WORKSPACE" -maxdepth 1 -name '*.cmake' ! -name 'lvarray-*' | head -n 1) + test -n "$host_config" + rm -rf \ + "$GEOS_TPL_DIR/bin" \ + "$GEOS_TPL_DIR/build_stage" \ + "$GEOS_TPL_DIR/builtin_spack_packages_repo" \ + "$GEOS_TPL_DIR/misc_cache" \ + "$GEOS_TPL_DIR/spack" \ + "$GEOS_TPL_DIR/spack_env" \ + "$GEOS_TPL_DIR/.spack-db" + tpl_name=$(basename "$GEOS_TPL_DIR") + stage="$RUNNER_TEMP/image-root" + mkdir -p "$stage/opt/GEOS" + mv "$GEOS_TPL_DIR" "$stage/opt/GEOS/$tpl_name" + cp "$host_config" "$stage/spack-generated.cmake" + "$RUNNER_TEMP/crane" auth login index.docker.io \ + -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" + image="${DOCKER_REPOSITORY}:${DOCKER_TAG}" + ref=$("$RUNNER_TEMP/crane" append --platform linux/arm64 \ + -f <(tar -C "$stage" -cf - .) -t "$image") + "$RUNNER_TEMP/crane" mutate "$ref" \ + -e "GEOS_TPL_DIR=/opt/GEOS/${tpl_name}" -t "$image" - name: Login to DockerHub + if: matrix.RUNS_ON != 'macos-26' uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} @@ -274,6 +385,7 @@ jobs: logout: ${{ matrix.RUNS_ON != 'streak2' }} - name: Push docker image + if: matrix.RUNS_ON != 'macos-26' env: DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} DOCKER_TAG: ${{ github.event.number }}-${{ github.run_number }} @@ -292,10 +404,23 @@ jobs: echo "No local image to remove: ${image}" fi - # Convenience job - passes when all docker images are built. + - name: Upload build logs and host configs + if: always() && matrix.RUNS_ON == 'macos-26' + uses: actions/upload-artifact@v7 + with: + name: macos-arm64-build + path: | + ${{ runner.temp }}/macos-*.log + ${{ runner.temp }}/geosx-macos-spack.yaml + ${{ runner.temp }}/image-root/spack-generated.cmake + + # Convenience job - passes when every build_images row, including macOS, succeeds. check_that_all_images_built: runs-on: ubuntu-22.04 needs: [build_images] + if: always() steps: - name: Success - run: "true" + run: | + echo "build_images: ${{ needs.build_images.result }}" + test "${{ needs.build_images.result }}" = success diff --git a/docker/Stanford/Dockerfile b/docker/Stanford/Dockerfile index 3ff7ff4e..5646859d 100644 --- a/docker/Stanford/Dockerfile +++ b/docker/Stanford/Dockerfile @@ -9,7 +9,7 @@ FROM $DOCKER_ROOT_IMAGE as tpl_toolchain_intersect_geosx_toolchain ARG SRC_DIR ARG INSTALL_DIR -ENV GEOSX_TPL_DIR=$INSTALL_DIR +ENV GEOS_TPL_DIR=$INSTALL_DIR RUN yum install -y \ ca-certificates \ @@ -62,27 +62,27 @@ ARG SPEC # Have to create install directory first for uberenv # -k flag is to ignore SSL errors RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ - mkdir -p ${GEOSX_TPL_DIR} && \ + mkdir -p ${GEOS_TPL_DIR} && \ source /oak/stanford/groups/tchelepi/geos-sherlock/pythonVenv/geosxEnv/bin/activate && \ ./scripts/uberenv/uberenv.py \ # spack spec defined per image --spec "${SPEC}" \ --spack-env-file=${SRC_DIR}/docker/stanford-spack.yaml \ --project-json=.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -k && \ # Remove host-config generated for LvArray rm lvarray* && \ # Rename and copy spack-generated host-config to root directory cp *.cmake /spack-generated.cmake && \ # Remove extraneous spack files - cd ${GEOSX_TPL_DIR} && \ + cd ${GEOS_TPL_DIR} && \ rm -rf bin/ build_stage/ misc_cache/ spack/ spack_env/ .spack-db/ FROM tpl_toolchain_intersect_geosx_toolchain AS geosx_toolchain ARG SRC_DIR -COPY --from=tpl_toolchain $GEOSX_TPL_DIR $GEOSX_TPL_DIR +COPY --from=tpl_toolchain $GEOS_TPL_DIR $GEOS_TPL_DIR # Extract the generated host-config COPY --from=tpl_toolchain /spack-generated.cmake / diff --git a/docker/TotalEnergies/Dockerfile b/docker/TotalEnergies/Dockerfile index 084bcae1..c715ddc9 100644 --- a/docker/TotalEnergies/Dockerfile +++ b/docker/TotalEnergies/Dockerfile @@ -9,7 +9,7 @@ FROM $DOCKER_ROOT_IMAGE as tpl_toolchain_intersect_geosx_toolchain ARG SRC_DIR ARG INSTALL_DIR -ENV GEOSX_TPL_DIR=$INSTALL_DIR +ENV GEOS_TPL_DIR=$INSTALL_DIR RUN yum install --disablerepo=intel-mkl-repo -y \ ca-certificates \ @@ -51,26 +51,26 @@ ARG SPEC # Have to create install directory first for uberenv # -k flag is to ignore SSL errors RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ - mkdir -p ${GEOSX_TPL_DIR} && \ + mkdir -p ${GEOS_TPL_DIR} && \ ./scripts/uberenv/uberenv.py \ # spack spec defined per image --spec "${SPEC}" \ --spack-env-file=${SRC_DIR}/docker/total-spack.yaml \ --project-json=.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -k && \ # Remove host-config generated for LvArray rm lvarray* && \ # Rename and copy spack-generated host-config to root directory cp *.cmake /spack-generated.cmake && \ # Remove extraneous spack files - cd ${GEOSX_TPL_DIR} && \ + cd ${GEOS_TPL_DIR} && \ rm -rf bin/ build_stage/ misc_cache/ spack/ spack_env/ .spack-db/ FROM tpl_toolchain_intersect_geosx_toolchain AS geosx_toolchain ARG SRC_DIR -COPY --from=tpl_toolchain $GEOSX_TPL_DIR $GEOSX_TPL_DIR +COPY --from=tpl_toolchain $GEOS_TPL_DIR $GEOS_TPL_DIR # Extract the generated host-config COPY --from=tpl_toolchain /spack-generated.cmake / diff --git a/docker/configure-tpl.sh b/docker/configure-tpl.sh index cd75b527..6788b82b 100755 --- a/docker/configure-tpl.sh +++ b/docker/configure-tpl.sh @@ -10,8 +10,8 @@ if [ -z "$BLD_DIR" ]; then exit 1 fi -if [ -z "$GEOSX_TPL_DIR" ]; then - echo "Environment variable \"GEOSX_TPL_DIR\" is undefined." +if [ -z "$GEOS_TPL_DIR" ]; then + echo "Environment variable \"GEOS_TPL_DIR\" is undefined." exit 1 fi @@ -24,6 +24,6 @@ python3 $SRC_DIR/scripts/config-build.py \ --hostconfig $SRC_DIR/$HOST_CONFIG \ --buildtype Release \ --buildpath $BLD_DIR \ ---installpath $GEOSX_TPL_DIR \ +--installpath $GEOS_TPL_DIR \ -DNUM_PROC=$(nproc) \ $* diff --git a/docker/tpl-rockylinux.Dockerfile b/docker/tpl-rockylinux.Dockerfile index 856435c1..5f9b73b2 100644 --- a/docker/tpl-rockylinux.Dockerfile +++ b/docker/tpl-rockylinux.Dockerfile @@ -18,7 +18,7 @@ FROM ${DOCKER_BASE_IMAGE} AS tpl_toolchain_intersect_geosx_toolchain ARG SRC_DIR ARG INSTALL_DIR -ENV GEOSX_TPL_DIR=$INSTALL_DIR +ENV GEOS_TPL_DIR=$INSTALL_DIR # Packages needed both for the TPL build and for the downstream GEOS build. # Some Rocky 8 vs 9 differences are handled by the base image already @@ -114,7 +114,7 @@ RUN dnf -y install \ # expected external compiler paths before Spack starts so missing compilers fail # directly instead of being discovered or built by Spack. RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ - mkdir -p ${GEOSX_TPL_DIR} && \ + mkdir -p ${GEOS_TPL_DIR} && \ GEOSX_SPEC="${SPEC}" && \ if [ -z "${GEOSX_SPEC}" ] || [ "${GEOSX_SPEC}" = "undefined" ]; then \ echo "ERROR: SPEC build-arg must be supplied" >&2 ; \ @@ -149,7 +149,7 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ --spec '${GEOSX_SPEC}' \ --spack-env-file=${GEOSX_SPACK_ENV_FILE} \ --project-json=${SRC_DIR}/.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -j ${SPACK_BUILD_JOBS} \ -k " ; \ else \ @@ -157,19 +157,19 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ --spec "${GEOSX_SPEC}" \ --spack-env-file=${GEOSX_SPACK_ENV_FILE} \ --project-json=${SRC_DIR}/.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -j ${SPACK_BUILD_JOBS} \ -k ; \ fi && \ rm -f lvarray* && \ cp *.cmake /spack-generated.cmake && \ - cd ${GEOSX_TPL_DIR} && \ + cd ${GEOS_TPL_DIR} && \ rm -rf bin/ build_stage/ builtin_spack_packages_repo/ misc_cache/ spack/ spack_env/ .spack-db/ # ----- Final GEOS-build image ----- FROM tpl_toolchain_intersect_geosx_toolchain AS geosx_toolchain ARG SRC_DIR -COPY --from=tpl_toolchain $GEOSX_TPL_DIR $GEOSX_TPL_DIR +COPY --from=tpl_toolchain $GEOS_TPL_DIR $GEOS_TPL_DIR COPY --from=tpl_toolchain /spack-generated.cmake / RUN dnf -y install \ diff --git a/docker/tpl-ubuntu-hip.Dockerfile b/docker/tpl-ubuntu-hip.Dockerfile index 10879e0d..b7783b4e 100644 --- a/docker/tpl-ubuntu-hip.Dockerfile +++ b/docker/tpl-ubuntu-hip.Dockerfile @@ -28,9 +28,9 @@ ENV OPENSSL_FORCE_FIPS_MODE=0 \ OPENSSL_CONF=/etc/ssl/openssl-non-fips.cnf # Install directory provided as a docker build argument; forwarded via ENV -# (GEOSX_TPL_DIR is part of the image contract consumed by GEOS). +# (GEOS_TPL_DIR is part of the image contract consumed by GEOS). ARG INSTALL_DIR -ENV GEOSX_TPL_DIR=$INSTALL_DIR +ENV GEOS_TPL_DIR=$INSTALL_DIR # ROCm parameters ARG AMDGPU_TARGET=gfx942 @@ -166,7 +166,7 @@ RUN apt-get update && \ # for hours and spack is otherwise silent, which makes a stalled build # indistinguishable from a slow one in CI logs. RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ - mkdir -p ${GEOSX_TPL_DIR} && \ + mkdir -p ${GEOS_TPL_DIR} && \ GEOSX_SPEC="${SPEC}" && \ if [ -z "${GEOSX_SPEC}" ] || [ "${GEOSX_SPEC}" = "undefined" ]; then \ echo "ERROR: SPEC build-arg must be supplied" >&2 ; \ @@ -176,12 +176,12 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ ( while true; do \ sleep 60; \ echo "[heartbeat] $(date -Iseconds) uberenv/spack still running"; \ - find ${GEOSX_TPL_DIR}/build_stage -maxdepth 2 -mindepth 2 -type d -printf '%T@ %p\n' 2>/dev/null | \ + find ${GEOS_TPL_DIR}/build_stage -maxdepth 2 -mindepth 2 -type d -printf '%T@ %p\n' 2>/dev/null | \ sort -nr | head -n 3 | \ while read -r _ path; do \ echo "[heartbeat] recent stage dir: ${path}"; \ done; \ - find ${GEOSX_TPL_DIR}/build_stage -maxdepth 4 \( -name spack-build-out.txt -o -name spack-build-env.txt -o -name spack-configure-args.txt \) -printf '%T@ %p\n' 2>/dev/null | \ + find ${GEOS_TPL_DIR}/build_stage -maxdepth 4 \( -name spack-build-out.txt -o -name spack-build-env.txt -o -name spack-configure-args.txt \) -printf '%T@ %p\n' 2>/dev/null | \ sort -nr | head -n 3 | \ while read -r _ path; do \ echo "[heartbeat] recent stage file: ${path}"; \ @@ -199,7 +199,7 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ --spec "${GEOSX_SPEC}" \ --spack-env-file=${SRC_DIR}/docker/spack-rocm.yaml \ --project-json=${SRC_DIR}/.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -j ${SPACK_BUILD_JOBS} \ -k; \ rc=$?; \ @@ -209,7 +209,7 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ } && \ rm -f lvarray* && \ cp *.cmake /spack-generated.cmake && \ - cd ${GEOSX_TPL_DIR} && \ + cd ${GEOS_TPL_DIR} && \ rm -rf bin/ build_stage/ builtin_spack_packages_repo/ misc_cache/ spack/ spack_env/ .spack-db/ # ----- Final GEOS-build image ----- @@ -228,7 +228,7 @@ ARG ROCM_VERSION # it is actually needed. ENV OPENSSL_CONF="" -COPY --from=tpl_toolchain $GEOSX_TPL_DIR $GEOSX_TPL_DIR +COPY --from=tpl_toolchain $GEOS_TPL_DIR $GEOS_TPL_DIR # Extract the generated host-config COPY --from=tpl_toolchain /spack-generated.cmake / diff --git a/docker/tpl-ubuntu.Dockerfile b/docker/tpl-ubuntu.Dockerfile index 542fc29b..e40aaa68 100644 --- a/docker/tpl-ubuntu.Dockerfile +++ b/docker/tpl-ubuntu.Dockerfile @@ -31,9 +31,9 @@ ENV OPENSSL_FORCE_FIPS_MODE=0 \ OPENSSL_CONF=/etc/ssl/openssl-non-fips.cnf # Install directory provided as a docker build argument; forwarded via ENV -# (GEOSX_TPL_DIR is part of the image contract consumed by GEOS). +# (GEOS_TPL_DIR is part of the image contract consumed by GEOS). ARG INSTALL_DIR -ENV GEOSX_TPL_DIR=$INSTALL_DIR +ENV GEOS_TPL_DIR=$INSTALL_DIR # Packages needed both for the TPL build and for the downstream GEOS build. # We avoid reinstalling anything already present in the base image (compiler, @@ -162,7 +162,7 @@ RUN apt-get update && \ # Run uberenv. The SPEC is supplied by the matrix because the spack toolchain # tag depends on the compiler+version baked into the base image. RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ - mkdir -p ${GEOSX_TPL_DIR} && \ + mkdir -p ${GEOS_TPL_DIR} && \ GEOSX_SPEC="${SPEC}" && \ if [ -z "${GEOSX_SPEC}" ] || [ "${GEOSX_SPEC}" = "undefined" ]; then \ echo "ERROR: SPEC build-arg must be supplied" >&2 ; \ @@ -179,12 +179,12 @@ RUN --mount=src=.,dst=$SRC_DIR,readwrite cd ${SRC_DIR} && \ --spec "${GEOSX_SPEC}" \ --spack-env-file=${GEOSX_SPACK_ENV_FILE} \ --project-json=${SRC_DIR}/.uberenv_config.json \ - --prefix ${GEOSX_TPL_DIR} \ + --prefix ${GEOS_TPL_DIR} \ -j ${SPACK_BUILD_JOBS} \ -k && \ rm -f lvarray* && \ cp *.cmake /spack-generated.cmake && \ - cd ${GEOSX_TPL_DIR} && \ + cd ${GEOS_TPL_DIR} && \ rm -rf bin/ build_stage/ builtin_spack_packages_repo/ misc_cache/ spack/ spack_env/ .spack-db/ # ----- Final GEOS-build image ----- @@ -199,7 +199,7 @@ ARG SRC_DIR # it is actually needed. ENV OPENSSL_CONF="" -COPY --from=tpl_toolchain $GEOSX_TPL_DIR $GEOSX_TPL_DIR +COPY --from=tpl_toolchain $GEOS_TPL_DIR $GEOS_TPL_DIR COPY --from=tpl_toolchain /spack-generated.cmake / RUN apt-get update && \ diff --git a/scripts/setupMacOS-TPL-deps.bash b/scripts/setupMacOS-TPL-deps.bash index cf2e3fd6..27d9732f 100755 --- a/scripts/setupMacOS-TPL-deps.bash +++ b/scripts/setupMacOS-TPL-deps.bash @@ -54,11 +54,12 @@ usage() Usage: scripts/setupMacOS-TPL-deps.bash [options] Validate the Homebrew dependency set recorded in the checked-in manifest. Exact -pins and minimum-version policies are applied as declared. The manifest's host -and platform versions describe the qualification host; they are not exact -host-version gates. By default, missing formulas are installed only after the -complete preflight succeeds. Exact-pinned formula drift is never upgraded or -downgraded. +version pins and minimum-version policies are applied as declared. The +manifest's host and platform versions describe the qualification host; they are +not exact host-version gates. A rewritten Homebrew formula file with the same +version is reported and accepted. By default, missing formulas are installed +only after the complete preflight succeeds. Exact-pinned version drift is never +upgraded or downgraded. Options: --check-only Validate without installing anything. @@ -546,10 +547,16 @@ formula_metadata_matches() record_error "Formula '${name}' metadata drift: expected version '${expected_version}', found '${actual_version}'" return 1 fi - if [[ "${actual_sha}" != "${expected_sha}" ]]; then - record_error "Formula '${name}' source drift: expected checksum '${expected_sha}', found '${actual_sha}'" + # ruby_source_checksum hashes the Homebrew formula file. Core rewrites + # that file for bottle rebuilds without changing the installed version. + # A missing checksum is incomplete metadata, not that rewrite. + if [[ -z "${actual_sha}" ]]; then + record_error "Formula '${name}' is missing ruby_source_checksum" return 1 fi + if [[ "${actual_sha}" != "${expected_sha}" ]]; then + echo "INFO: Formula '${name}' formula file checksum differs from the qualification snapshot '${expected_sha}'; version '${actual_version}' still matches" + fi ;; minimum) if ! version_at_least "${actual_version}" "${minimum_version}"; then @@ -727,9 +734,8 @@ preflight_formulae() i=$((i + 1)) continue fi - # Check source identity for exact-pinned formulas even when already - # installed. This makes stale API caches and silently rewritten formulas - # visible drift. + # Check the live formula version even when the formula is already + # installed. A rewritten formula file with the same version is informational. formula_metadata_matches "${i}" || true validate_formula_installation "${i}" true || true i=$((i + 1)) diff --git a/scripts/spack_configs/macOS/README.md b/scripts/spack_configs/macOS/README.md index 14af9b0b..73aec7fe 100644 --- a/scripts/spack_configs/macOS/README.md +++ b/scripts/spack_configs/macOS/README.md @@ -8,9 +8,11 @@ built by Spack. The setup is fail-closed for the dependency set. It does not install Homebrew, update Homebrew, upgrade or downgrade an installed formula, or silently accept -a changed exact-pinned formula definition. The macOS, SDK, Apple Clang, and +a changed exact-pinned formula version. The macOS, SDK, Apple Clang, and Homebrew versions recorded in the manifest identify the qualification host; -they are reported for traceability but are not exact host-version gates. +they are reported for traceability but are not exact host-version gates. A +rewritten Homebrew formula file with the same version is also reported and +accepted. ## One-time prerequisite @@ -23,7 +25,7 @@ eval "$(brew shellenv)" The dependency script discovers Homebrew through `PATH` (and also checks the standard Apple Silicon and Intel install paths). It validates exact versions -and source checksums for the exact-pinned formulas in the manifest. No GEOS tap +for the exact-pinned formulas in the manifest. No GEOS tap or other Homebrew tap is required. CMake only has to satisfy the project minimum of `3.24`; an installed `mpich` or `open-mpi` satisfies the MPI requirement. @@ -74,8 +76,8 @@ Installation occurs only when all of the following preflight checks pass: - the host is Darwin/arm64 and the Apple Command Line Tools are available; - CMake is at least `3.24`, and either `mpich` or `open-mpi` is available; -- Homebrew reports the exact stable formula version, formula revision, and Ruby - source checksum for the exact-pinned formulas in the manifest; +- Homebrew reports the exact stable formula version and formula revision for + the exact-pinned formulas in the manifest; - every formula already installed has the exact receipt version and prefix; - every required executable, header, and library from an installed formula is present. @@ -87,8 +89,9 @@ metadata, receipts, prefixes, and required paths. A partial or changed installation is an error. The script exports `HOMEBREW_NO_AUTO_UPDATE=1`, so the formula metadata visible -to the invoked Homebrew is authoritative for that run. A stale local API cache -is reported as drift instead of being mistaken for the tested formula set. +to the invoked Homebrew is authoritative for that run. A version that differs +from an exact pin is rejected. A formula-file checksum that differs while the +version still matches is printed and does not fail the run. The generated Spack environment selects the installed MPI provider and its concrete version. It also selects `/usr/bin/ar` and @@ -113,25 +116,28 @@ upgrade`, an unreviewed downgrade, or `--force` linking. Qualify the newer dependency set with a clean TPL build and update the manifest and macOS Spack configuration together. -The checked-in exact formula pins and source checksums come from the official -Homebrew formula API snapshot dated 2026-09-04 and are selected for +The checked-in exact formula versions and source checksums come from the official +Homebrew formula API snapshot dated 2026-09-23 and are selected for qualification. They are not yet described as qualified until a clean TPL build and its smoke tests pass. The manifest records the exact host used to select them for traceability, but macOS patch/build revisions, Apple Clang build revisions, Apple Clang versions, CMake versions at or above `3.24`, MPI provider -choice, and Homebrew executable patch releases are informational rather than -support gates. Homebrew-managed transitive dependencies are not separate Spack -externals; the post-install executable and link-library checks are the local -compatibility guard for this boundary. +choice, Homebrew executable patch releases, and later rewrites of a formula +file that keep the same version are informational rather than support gates. +Homebrew-managed transitive dependencies are not separate Spack externals; the +post-install executable and link-library checks are the local compatibility +guard for this boundary. ## Updating the manifest Treat a manifest change as a toolchain change: 1. obtain exact-pinned formula versions (`stable`, plus `_` when the - formula revision is nonzero) and `ruby_source_checksum.sha256` from - `brew info --json=v2` or the official formula API; keep CMake at or above - `3.24` and keep either `mpich` or `open-mpi` available; + formula revision is nonzero) from `brew info --json=v2` or the official + formula API when that version changes; record `ruby_source_checksum.sha256` + for the qualification snapshot, but do not treat a later formula-file + rewrite as a failure; keep CMake at or above `3.24` and keep either `mpich` + or `open-mpi` available; 2. update the matching external version and prefix in the macOS Spack environment; 3. run `scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash`; diff --git a/scripts/spack_configs/macOS/homebrew-manifest.json b/scripts/spack_configs/macOS/homebrew-manifest.json index c1dfddcc..331747ae 100644 --- a/scripts/spack_configs/macOS/homebrew-manifest.json +++ b/scripts/spack_configs/macOS/homebrew-manifest.json @@ -2,7 +2,7 @@ "schema_version": 1, "source": { "description": "Official Homebrew formula API metadata and version policies selected for GEOS macOS TPL qualification", - "as_of": "2026-09-04" + "as_of": "2026-09-23" }, "supported_platform": { "os": "Darwin", @@ -23,7 +23,7 @@ { "name": "gcc", "brew_version": "16.2.0", - "formula_sha256": "0683955ef01d30162abfcfff3fe9b0e73eebba9a5f9920fb53cb94bc8cdc3f43", + "formula_sha256": "f09c8035d0874fb1d8a2d7ca3464f8108273c4c532cec582d7b9f340aa544aa3", "prefix": "/opt/homebrew/opt/gcc", "spack_package": "gcc", "spack_version": "16.2.0", @@ -34,7 +34,7 @@ { "name": "openblas", "brew_version": "0.3.34", - "formula_sha256": "b6c9d393f4c2a6ebe1b66354decd9d7da8ce66952f9e760454fd601689e2d89a", + "formula_sha256": "a9df5fa47015b4814c0ea8616127e248e48cc5a1136c1f33b13f5cdbe2da454e", "prefix": "/opt/homebrew/opt/openblas", "spack_package": "openblas", "spack_version": "0.3.34", @@ -63,7 +63,7 @@ "version_policy": "minimum", "minimum_version": "3.24", "brew_version": "4.4.3", - "formula_sha256": "55fab0cd335d245c13704744585837a4e593aad16335097fc554df712e2dba1b", + "formula_sha256": "b6640b40b96c513cfe79cb15f43c4b6de9b08386104bb9096c965ad02fd1f87f", "prefix": "/opt/homebrew/opt/cmake", "spack_package": "cmake", "spack_version": "4.4.3", @@ -73,8 +73,8 @@ }, { "name": "readline", - "brew_version": "8.3.3", - "formula_sha256": "660d4099f7dcd652c78f530672ffaf01103a00b3ee72aca4059f10c1038b1228", + "brew_version": "8.3.6", + "formula_sha256": "ead60941ba0f81334499b208e26382eefda15eb1b9614c417d70f1d827dc2e64", "prefix": "/opt/homebrew/opt/readline", "spack_package": "readline", "spack_version": "8.3", @@ -86,7 +86,7 @@ { "name": "m4", "brew_version": "1.4.21", - "formula_sha256": "79b4221c141d51a12b59824d0dd69e59c90ee582ca9451150ea973278e19fabf", + "formula_sha256": "4f77735ee9b10e73b569387e86f8c456c3d50d253d70325543dba1d6aba24675", "prefix": "/opt/homebrew/opt/m4", "spack_package": "m4", "spack_version": "1.4.21", @@ -96,11 +96,11 @@ }, { "name": "pkgconf", - "brew_version": "3.0.6", - "formula_sha256": "53cae4d107ccc45a50359ea12428bddec0ecd0ef1fe8283a3ace27cd497ef4e0", + "brew_version": "3.0.7", + "formula_sha256": "aa4787b4c8af3925054fb55ae38c9ce16cea5325ce5788e2d1c6cec514f30f5c", "prefix": "/opt/homebrew/opt/pkgconf", "spack_package": "pkgconf", - "spack_version": "3.0.6", + "spack_version": "3.0.7", "required_paths": [ "bin/pkg-config", "bin/pkgconf" @@ -109,7 +109,7 @@ { "name": "autoconf", "brew_version": "2.73", - "formula_sha256": "036d4f18fa1b9072705af36ebc9c8d02700853c0149e04feeb0fc2232aecab96", + "formula_sha256": "cab75402095f9a65be6a5d3fc11a2f7a6a7fdf04189abee37bfc1c6e5adff499", "prefix": "/opt/homebrew/opt/autoconf", "spack_package": "autoconf", "spack_version": "2.73", @@ -119,8 +119,8 @@ }, { "name": "automake", - "brew_version": "1.18.1_1", - "formula_sha256": "187e1f49c7831094765bc2630eef07270d5080670f67b3b4a610391cb73bd233", + "brew_version": "1.19", + "formula_sha256": "706ee509ee3d3a4bc05999b0695f0998cddedeb7d534699bf721ebb0ee4ec487", "prefix": "/opt/homebrew/opt/automake", "spack_package": "automake", "spack_version": "1.18.1", @@ -131,7 +131,7 @@ { "name": "libtool", "brew_version": "2.6.2", - "formula_sha256": "1b21318c46a9bba74d7013aef9d76ee0144f1b747aceecaa7ba92438462c3a79", + "formula_sha256": "5513e09178e3dccafd9cbb3e24c1af5c4aee5d40769256a3963ef8b234c35a39", "prefix": "/opt/homebrew/opt/libtool", "spack_package": "libtool", "spack_version": "2.6.2", @@ -143,7 +143,7 @@ { "name": "gettext", "brew_version": "1.0", - "formula_sha256": "bd0322f7114c68dd627c974ec16ceaa6295bf68eac55995ee2dfb59ce6fefd51", + "formula_sha256": "9c90f7cf5a45c828e2a66d9339b25b781302cf49316e939d029e6079e1b9d916", "prefix": "/opt/homebrew/opt/gettext", "spack_package": "gettext", "spack_version": "1.0", @@ -155,7 +155,7 @@ { "name": "binutils", "brew_version": "2.47", - "formula_sha256": "f1fd62bf787bec32ca4f0bf872372bddd6432f0db24b5c5d6c68fb0b340d047e", + "formula_sha256": "8f4e28410aec61265d8c13578741a617423f9289fdb0ef1dbff8368eceb1bff7", "prefix": "/opt/homebrew/opt/binutils", "spack_package": "binutils", "spack_version": "2.47", @@ -166,7 +166,7 @@ { "name": "python@3.14", "brew_version": "3.14.7", - "formula_sha256": "e7e3a023de8b88f5a7e2e09c861e9e15fb0d0a2d78b6bb619a1b27e7da155b9a", + "formula_sha256": "0165e2f65d6b237155d0fb089a6b27259fd85e0fa7582c6ee293df587346cfee", "prefix": "/opt/homebrew/opt/python@3.14", "spack_package": "python", "spack_version": "3.14.7", @@ -178,7 +178,7 @@ "name": "perl", "version_policy": "any", "brew_version": "5.44.0", - "formula_sha256": "19d8fe283a3699d99d4a81d521b2cf5317f5e8ad728319138518b2e803e70dcb", + "formula_sha256": "8549ff64704f61aa09cf20f19861111db8f28798c1ef2129fb31ec481447effa", "prefix": "/opt/homebrew/opt/perl", "spack_package": "perl", "spack_version": "5.44.0", diff --git a/scripts/spack_configs/macOS/spack.yaml b/scripts/spack_configs/macOS/spack.yaml index 1c577927..2147d91b 100644 --- a/scripts/spack_configs/macOS/spack.yaml +++ b/scripts/spack_configs/macOS/spack.yaml @@ -80,7 +80,7 @@ spack: require: "openblas@=0.3.34" pkgconfig: buildable: false - require: "pkgconf@=3.0.6" + require: "pkgconf@=3.0.7" zlib-api: require: "zlib@=1.3.2" @@ -150,7 +150,7 @@ spack: pkgconf: buildable: false externals: - - spec: "pkgconf@=3.0.6 platform=darwin target=aarch64" + - spec: "pkgconf@=3.0.7 platform=darwin target=aarch64" prefix: /opt/homebrew/opt/pkgconf autoconf: diff --git a/scripts/spack_packages/packages/geosx/package.py b/scripts/spack_packages/packages/geosx/package.py index e895b710..50d9b964 100644 --- a/scripts/spack_packages/packages/geosx/package.py +++ b/scripts/spack_packages/packages/geosx/package.py @@ -134,6 +134,7 @@ class Geosx(CMakePackage, CudaPackage, ROCmPackage): depends_on('chai+cuda~separable_compilation cuda_arch={0}'.format(sm_), when='cuda_arch={0}'.format(sm_)) depends_on('camp+cuda cuda_arch={0}'.format(sm_), when='cuda_arch={0}'.format(sm_)) depends_on('hypre+cuda cuda_arch={0}'.format(sm_), when='cuda_arch={0}'.format(sm_)) + depends_on('hypredrive+cuda cuda_arch={0}'.format(sm_), when='+hypredrive cuda_arch={0}'.format(sm_)) with when('+rocm'): for gfx_ in ROCmPackage.amdgpu_targets: @@ -142,6 +143,7 @@ class Geosx(CMakePackage, CudaPackage, ROCmPackage): depends_on(f"chai+rocm~separable_compilation amdgpu_target={gfx_}", when=f"amdgpu_target={gfx_}") depends_on(f"camp+rocm amdgpu_target={gfx_}", when=f"amdgpu_target={gfx_}") depends_on(f"hypre+rocm amdgpu_target={gfx_}", when=f"amdgpu_target={gfx_}") + depends_on(f"hypredrive+rocm amdgpu_target={gfx_}", when=f"+hypredrive amdgpu_target={gfx_}") # # IO @@ -199,6 +201,8 @@ class Geosx(CMakePackage, CudaPackage, ROCmPackage): depends_on("hypredrive +pic", when="~shared") depends_on("hypredrive +shared", when="+shared") depends_on("hypredrive +caliper", when="+caliper") + depends_on("hypredrive +cuda", when="+cuda") + depends_on("hypredrive +rocm", when="+rocm") depends_on('petsc@3.19.4~hdf5~hypre+int64', when='+petsc') depends_on('petsc+ptscotch', when='+petsc+scotch') @@ -354,7 +358,8 @@ def geos_hostconfig(self, spec, prefix, py_site_pkgs_dir=None): cfg.write(cmake_cache_string("CMAKE_CXX_FLAGS", cxxflags)) release_flags = "-O3 -DNDEBUG" - if "clang" in self.compiler.cxx: + # Apple Clang accepts neither -mtune=native nor a useful -march=native. + if "clang" in self.compiler.cxx and "apple-clang" not in self.compiler.cxx: release_flags += " -march=native -mtune=native" cfg.write(cmake_cache_string("CMAKE_CXX_FLAGS_RELEASE", release_flags)) reldebinf_flags = "-O2 -g -DNDEBUG" diff --git a/scripts/spack_packages/packages/hypredrive/package.py b/scripts/spack_packages/packages/hypredrive/package.py index d4dd280d..38e39558 100644 --- a/scripts/spack_packages/packages/hypredrive/package.py +++ b/scripts/spack_packages/packages/hypredrive/package.py @@ -51,19 +51,19 @@ class Hypredrive(CMakePackage, CudaPackage, ROCmPackage): depends_on("cmake@3.23:", type="build") depends_on("mpi") - depends_on("hypre@develop +mpi") - depends_on("hypre@develop +shared", when="+shared") - depends_on("hypre@develop +pic~shared", when="+pic~shared") - depends_on("hypre@develop +caliper", when="+caliper") - depends_on("hypre@develop +cuda", when="+cuda") - depends_on("hypre@develop +rocm", when="+rocm") - depends_on("hypre@develop +sycl", when="+sycl") - depends_on("hypre@develop +superlu-dist", when="+superlu-dist") + depends_on("hypre +mpi") + depends_on("hypre +shared", when="+shared") + depends_on("hypre +pic~shared", when="+pic~shared") + depends_on("hypre +caliper", when="+caliper") + depends_on("hypre +cuda", when="+cuda") + depends_on("hypre +rocm", when="+rocm") + depends_on("hypre +sycl", when="+sycl") + depends_on("hypre +superlu-dist", when="+superlu-dist") for feature in ("fortran", "matlab", "julia", "superlu-dist"): - depends_on("hypre@develop precision=double", when="+{0}".format(feature)) + depends_on("hypre precision=double", when="+{0}".format(feature)) for feature in ("matlab", "julia"): - depends_on("hypre@develop~complex", when="+{0}".format(feature)) + depends_on("hypre~complex", when="+{0}".format(feature)) requires( "%c,cxx=oneapi", @@ -73,7 +73,7 @@ class Hypredrive(CMakePackage, CudaPackage, ROCmPackage): for arch in CudaPackage.cuda_arch_values: depends_on( - "hypre@develop+cuda cuda_arch={0}".format(arch), when="+cuda cuda_arch={0}".format(arch) + "hypre+cuda cuda_arch={0}".format(arch), when="+cuda cuda_arch={0}".format(arch) ) depends_on( "superlu-dist@9.2.1:+cuda cuda_arch={0}".format(arch), @@ -82,7 +82,7 @@ class Hypredrive(CMakePackage, CudaPackage, ROCmPackage): for target in ROCmPackage.amdgpu_targets: depends_on( - "hypre@develop+rocm amdgpu_target={0}".format(target), + "hypre+rocm amdgpu_target={0}".format(target), when="+rocm amdgpu_target={0}".format(target), ) depends_on( diff --git a/scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash b/scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash index b3a40479..09e33a5f 100755 --- a/scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash +++ b/scripts/tests/macos_homebrew/test_setupMacOS_TPL_deps.bash @@ -518,8 +518,8 @@ create_fake_tools write_manifest test_committed_manifest -# Host releases newer than the recorded qualification host are accepted. The -# Homebrew formula versions and source checksums remain exact. +# Host releases newer than the recorded qualification host are accepted. Exact +# formula versions remain gates. Formula-file checksum rewrites do not. reset_state expect_success "qualification-host macOS/SDK versions accept patch drift" run_setup --check-only @@ -695,7 +695,9 @@ expect_failure "formula version metadata drift is rejected" run_setup reset_state set_candidate beta 2.0 1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -expect_failure "formula source checksum drift is rejected" run_setup +expect_success "formula file checksum drift does not fail the build" run_setup +assert_file_contains "checksum drift is reported" \ + "${LAST_OUTPUT}" "formula file checksum differs from the qualification snapshot" reset_state rm -f "${FAKE_PREFIX}/opt/beta/bin/beta-tool"