File indexing completed on 2026-11-11 09:31:28
0001
0002 set -u
0003 set -e
0004 set -o pipefail
0005
0006 SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
0007
0008 export SPACK_COLOR=always
0009
0010
0011 function pinned_version() {
0012 sed -n "s/^$1=//p" "${SCRIPT_DIR}/versions.env"
0013 }
0014
0015 function start_section() {
0016 local section_name="$1"
0017 if [ -n "${GITHUB_ACTIONS:-}" ]; then
0018 echo "::group::${section_name}"
0019 else
0020 echo "${section_name}"
0021 fi
0022 }
0023
0024 function end_section() {
0025 if [ -n "${GITHUB_ACTIONS:-}" ]; then
0026 echo "::endgroup::"
0027 fi
0028 }
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043 TRANSIENT_ERROR_PATTERNS=(
0044
0045 "No binary for"
0046 "no binary available"
0047 "NoBinaryFoundError"
0048 "cannot install.*cache-only"
0049 "Failed to install.*due to.*FetchError"
0050 "Failed to fetch"
0051 "fetch failed"
0052 "FetchError"
0053 "curl.*(error|failed|timed out|Could not resolve)"
0054 "Connection reset"
0055 "Connection refused"
0056 "Connection timed out"
0057 "Could not resolve host"
0058 "Temporary failure in name resolution"
0059 "Network is unreachable"
0060 "Read timed out"
0061 "read timeout"
0062 "timed out"
0063 "[Tt]imeout"
0064 "TLS handshake"
0065 "EOF occurred"
0066 "Server disconnected"
0067 "Remote end closed connection"
0068 "toomanyrequests"
0069 "Too Many Requests"
0070 "rate limit"
0071
0072
0073
0074
0075 "temporarily limiting"
0076 "unauthenticated download"
0077 "Please retry later"
0078 "HTTP Error 429"
0079 "HTTP Error 5[0-9][0-9]"
0080 "50[0-9] (Internal Server Error|Bad Gateway|Service Unavailable|Gateway Time-out)"
0081 "unable to access.*github.com"
0082 "RPC failed"
0083 "early EOF"
0084 )
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097 function retry_transient() {
0098 local max_attempts=${DEP_RETRY_MAX_ATTEMPTS:-6}
0099 local delay=${DEP_RETRY_BASE_DELAY:-20}
0100 local attempt=1
0101 local log status pat matched jitter sleep_for
0102 log=$(mktemp)
0103 while true; do
0104 echo "attempt ${attempt}/${max_attempts}: $*"
0105 status=0
0106
0107 "$@" 2>&1 | tee "${log}" || status=${PIPESTATUS[0]}
0108 if [ "${status}" -eq 0 ]; then
0109 rm -f "${log}"
0110 return 0
0111 fi
0112
0113 matched=""
0114 for pat in "${TRANSIENT_ERROR_PATTERNS[@]}"; do
0115 if grep -qiE -- "${pat}" "${log}"; then
0116 matched="${pat}"
0117 break
0118 fi
0119 done
0120 rm -f "${log}"
0121
0122 if [ -z "${matched}" ]; then
0123 echo "Command failed (exit ${status}) with no transient-error signature; not retrying"
0124 return "${status}"
0125 fi
0126 if [ "${attempt}" -ge "${max_attempts}" ]; then
0127 echo "Command still failing after ${max_attempts} attempts (last transient signature: '${matched}')"
0128 return "${status}"
0129 fi
0130
0131 jitter=$(( (RANDOM % (delay / 2 + 1)) - delay / 4 ))
0132 sleep_for=$(( delay + jitter ))
0133 [ "${sleep_for}" -lt 1 ] && sleep_for=1
0134 echo "Transient failure detected (matched '${matched}'); retrying in ${sleep_for}s"
0135 sleep "${sleep_for}"
0136 attempt=$((attempt + 1))
0137 delay=$((delay * 2))
0138 done
0139 }
0140
0141
0142 while getopts "c:t:d:e:s:F:fh" opt; do
0143 case ${opt} in
0144 c )
0145 compiler=$OPTARG
0146 ;;
0147 F )
0148 flavor=$OPTARG
0149 ;;
0150 t )
0151 tag=$OPTARG
0152 ;;
0153 d )
0154 destination=$OPTARG
0155 ;;
0156 e )
0157 env_file=$OPTARG
0158 ;;
0159 s )
0160 cxx_std=$OPTARG
0161 ;;
0162 f )
0163 full_install=true
0164 ;;
0165 h )
0166 echo "Usage: $0 [-c compiler] [-t tag] [-d destination] -e env_file [-h]"
0167 echo "Options:"
0168 echo " -c <compiler> Specify compiler (defaults to CXX env var)"
0169 echo " -t <tag> Specify dependency tag (defaults to DEPENDENCY_TAG env var, then versions.env)"
0170 echo " -d <destination> Specify install destination (defaults based on CI environment)"
0171 echo " -e <env_file> Specify environment file to output environments to"
0172 echo " -s <cxx_std> C++ standard for lockfile selection (e.g. 20, 23). Defaults to CXXSTD env var or 23."
0173 echo " -F <flavor> Accelerator flavor (e.g. cuda13, rocm7). Defaults to FLAVOR env var or the host stack."
0174 echo " -f Full dependency installation. Includes Geant4 datasets and Python packages."
0175 echo " -h Show this help message"
0176 exit 0
0177 ;;
0178 \? )
0179 echo "Invalid option: -$OPTARG" 1>&2
0180 exit 1
0181 ;;
0182 : )
0183 echo "Option -$OPTARG requires an argument" 1>&2
0184 exit 1
0185 ;;
0186 esac
0187 done
0188
0189 script_start=$(date +%s.%N)
0190
0191
0192 checkpoint() {
0193 local label=$1
0194 local now
0195 now=$(date +%s.%N)
0196 local elapsed
0197 elapsed=$(echo "$now - ${last_time:-$script_start}" | bc)
0198 printf "[%s] %.3f s\n" "$label" "$elapsed"
0199 last_time=$now
0200 }
0201
0202
0203 if [ -z "${compiler:-}" ]; then
0204 compiler="${CXX:-default}"
0205 fi
0206
0207 if [ -z "${tag:-}" ]; then
0208 tag="${DEPENDENCY_TAG:-$(pinned_version DEPENDENCY_TAG)}"
0209 if [ -z "${tag:-}" ]; then
0210 echo "No tag specified via -t, DEPENDENCY_TAG or ${SCRIPT_DIR}/versions.env"
0211 exit 1
0212 fi
0213 fi
0214
0215 if [ -z "${destination:-}" ]; then
0216 if [ -n "${GITHUB_ACTIONS:-}" ]; then
0217 destination="${GITHUB_WORKSPACE}/dependencies"
0218 elif [ -n "${GITLAB_CI:-}" ]; then
0219 destination="${CI_PROJECT_DIR}/dependencies"
0220 else
0221 echo "No destination specified via -d and not running in CI"
0222 exit 1
0223 fi
0224 fi
0225
0226 if [ -z "${env_file:-}" ]; then
0227 echo "No environment file specified via -e"
0228 exit 1
0229 fi
0230
0231 if [ -z "${cxx_std:-}" ]; then
0232 cxx_std="${CXXSTD:-23}"
0233 fi
0234
0235
0236 if [ -z "${flavor:-}" ]; then
0237 flavor="${FLAVOR:-}"
0238 fi
0239 if [ "${flavor}" == "host" ]; then
0240 flavor=""
0241 fi
0242
0243 checkpoint "Create environment file $(realpath "$env_file")"
0244 echo "" > "$env_file"
0245 export env_file
0246
0247 function set_env {
0248 key="$1"
0249 value="$2"
0250
0251 echo "=> ${key}=${value}"
0252
0253 echo "export ${key}=${value}" >> "$env_file"
0254 }
0255
0256
0257
0258 checkpoint "Starting setup script"
0259
0260 mkdir -p "${destination}"
0261
0262
0263
0264
0265 destination="$(cd "${destination}" && pwd)"
0266
0267 echo "Install tag: $tag"
0268 echo "Install destination: $destination"
0269
0270 if [ -n "${GITLAB_CI:-}" ]; then
0271 _spack_folder=${CI_PROJECT_DIR}/spack
0272 else
0273 _spack_folder=${PWD}/spack
0274 fi
0275
0276 start_section "Authenticate github.com fetches"
0277
0278
0279 "${SCRIPT_DIR}/github_auth.sh"
0280 end_section
0281
0282 start_section "Install spack if not already installed"
0283 if ! command -v spack &> /dev/null; then
0284 "${SCRIPT_DIR}/setup_spack.sh" "${_spack_folder}"
0285 source "${_spack_folder}/share/spack/setup-env.sh"
0286 fi
0287 checkpoint "Spack install complete"
0288
0289 _spack_repo_version=${SPACK_REPO_VERSION:-$(pinned_version SPACK_REPO_VERSION)}
0290 _spack_repo_directory="$(realpath "$(spack location --repo builtin)/../../../")"
0291
0292 echo "Ensure builtin repo is synced to commit ${_spack_repo_version}"
0293
0294 git config --global --add safe.directory "${_spack_repo_directory}"
0295 retry_transient spack repo update builtin --commit "${_spack_repo_version}"
0296 checkpoint "Spack repository updated"
0297
0298 end_section
0299
0300 if [ -n "${GITLAB_CI:-}" ]; then
0301
0302 mkdir -p ${CI_PROJECT_DIR}/.spack
0303 ln -s ${CI_PROJECT_DIR}/.spack ${HOME}/.spack
0304 fi
0305
0306
0307
0308 if [ -n "${CI:-}" ]; then
0309 start_section "Add buildcache mirror"
0310 mirror_name="acts-spack-buildcache"
0311 mirror_url="oci://ghcr.io/acts-project/spack-buildcache"
0312 if [ -n "${GITLAB_CI:-}" ]; then
0313
0314 mirror_url="oci://registry.cern.ch/ghcr.io/acts-project/spack-buildcache"
0315 fi
0316
0317
0318 if ! spack mirror list | grep -q ${mirror_name}; then
0319 echo "Adding buildcache ${mirror_name}"
0320 spack mirror add ${mirror_name} ${mirror_url} --unsigned
0321 fi
0322
0323
0324
0325 if [ -n "${GITHUB_TOKEN:-}" ] && [[ "${mirror_url}" == oci://ghcr.io/* ]]; then
0326 echo "Setting GHCR credentials on ${mirror_name}"
0327 spack mirror set \
0328 --oci-username "${GITHUB_ACTOR:-x-access-token}" \
0329 --oci-password-variable GITHUB_TOKEN \
0330 "${mirror_name}"
0331 fi
0332
0333
0334 spack mirror list
0335 spack config get mirrors
0336 end_section
0337
0338 start_section "Add ACTS package repository"
0339 if ! spack repo list | grep -q "acts"; then
0340 echo "Adding ACTS package repository from ci-dependencies"
0341 retry_transient spack repo add https://github.com/acts-project/ci-dependencies.git --path spack_repo/acts
0342 fi
0343 echo "Updating ACTS package repository to tag ${tag}"
0344 retry_transient spack repo update acts --tag "${tag}"
0345 end_section
0346
0347 start_section "Locate OpenGL"
0348 "${SCRIPT_DIR}/opengl.sh"
0349 checkpoint "OpenGL location complete"
0350 end_section
0351 fi
0352
0353 start_section "Get spack lock file"
0354 arch=$(spack arch --family)
0355
0356 env_dir="${destination}/env"
0357 view_dir="${destination}/view"
0358 venv_dir="${destination}/venv"
0359 mkdir -p ${env_dir}
0360
0361 lock_file_path="${destination}/spack.lock"
0362 cmd=(
0363 "${SCRIPT_DIR}/select_lockfile.py"
0364 "--tag" "${tag}"
0365 "--arch" "${arch}"
0366 "--cxx" "${cxx_std}"
0367 "--output" "${lock_file_path}"
0368 )
0369
0370 if [ "${compiler}" != "default" ]; then
0371 cmd+=("--compiler-binary" "${compiler}")
0372 fi
0373
0374 if [ -n "${flavor}" ]; then
0375 cmd+=("--flavor" "${flavor}")
0376 fi
0377
0378 "${cmd[@]}"
0379
0380 checkpoint "Lock file prepared"
0381
0382 end_section
0383
0384
0385
0386 start_section "Create spack environment"
0387 spack env create -d "${env_dir}" "${lock_file_path}" --with-view "$view_dir"
0388
0389
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409 view_exclude_config="$(mktemp)"
0410 cat > "$view_exclude_config" <<YAML
0411 view:
0412 default:
0413 root: ${view_dir}
0414 exclude:
0415 - libiconv
0416 YAML
0417 spack -e "${env_dir}" config add -f "$view_exclude_config"
0418 rm -f "$view_exclude_config"
0419 checkpoint "Spack environment created"
0420 spack -e "${env_dir}" spec -l
0421 checkpoint "Spack spec complete"
0422 spack -e "${env_dir}" find
0423 checkpoint "Spack find complete"
0424 end_section
0425
0426 start_section "Install spack packages"
0427
0428
0429
0430
0431
0432
0433 retry_transient spack -e "${env_dir}" install --fail-fast --use-buildcache only --concurrent-packages 10
0434 checkpoint "Spack install complete"
0435 end_section
0436
0437 start_section "Ensure uv is available"
0438 if ! command -v uv &> /dev/null ; then
0439 echo "uv not found, installing uv"
0440 curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${destination}/uv" sh
0441 export PATH="${destination}/uv:${PATH}"
0442 checkpoint "uv installation complete"
0443 fi
0444 end_section
0445
0446 start_section "Patch up Geant4 data directory"
0447 if [ "${full_install:-false}" == "true" ]; then
0448 uv run "$SCRIPT_DIR/download_geant4_datasets.py" -j8 --config "${view_dir}/bin/geant4-config"
0449 checkpoint "Geant4 datasets download complete"
0450 fi
0451 geant4_dir=$(spack -e "${env_dir}" location -i geant4)
0452
0453
0454
0455
0456
0457 mkdir -p "${geant4_dir}/share/Geant4/data"
0458 [ -e "${view_dir}/share/Geant4/data" ] ||
0459 ln -s "${geant4_dir}/share/Geant4/data" "${view_dir}/share/Geant4/data"
0460 end_section
0461
0462 start_section "Prepare python environment"
0463 "${view_dir}/bin/python3" -m venv --system-site-packages "$venv_dir"
0464
0465
0466
0467
0468
0469
0470
0471 spack_python_excludes="${venv_dir}/spack-python-excludes.txt"
0472 "${venv_dir}/bin/python3" -I -c '
0473 from importlib.metadata import distributions
0474 print("\n".join(sorted({dist.metadata["Name"] for dist in distributions()})))
0475 ' > "$spack_python_excludes"
0476 retry_transient uv pip install --python "${venv_dir}/bin/python3" --excludes "$spack_python_excludes" pyyaml jinja2
0477 "${venv_dir}/bin/python3" -c 'import yaml, jinja2'
0478 if [ "${full_install:-false}" == "true" ]; then
0479 retry_transient uv pip install --python "${venv_dir}/bin/python3" --excludes "$spack_python_excludes" \
0480 -r "${SCRIPT_DIR}/../../Python/Examples/tests/requirements.txt" histcmp==0.12.0 matplotlib pytest-md-report
0481 fi
0482 checkpoint "Python environment prepared"
0483 end_section
0484
0485 start_section "Set environment variables"
0486 set_env PATH "${venv_dir}/bin:${view_dir}/bin/:${PATH}"
0487 set_env ACTS_SPACK_PYTHON_EXCLUDES "$spack_python_excludes"
0488
0489
0490
0491
0492 set_env LD_LIBRARY_PATH "${venv_dir}/lib:${view_dir}/lib:${view_dir}/lib64:${view_dir}/lib/root"
0493 set_env DYLD_LIBRARY_PATH "${venv_dir}/lib:${view_dir}/lib:${view_dir}/lib64:${view_dir}/lib/root"
0494
0495
0496
0497 cmake_prefix_path="${venv_dir}:${view_dir}"
0498 for cuda_cmake_dir in "${view_dir}"/targets/*/lib/cmake; do
0499 if [ -d "${cuda_cmake_dir}" ]; then
0500 cmake_prefix_path="${cmake_prefix_path}:${cuda_cmake_dir}"
0501 fi
0502 done
0503 set_env CMAKE_PREFIX_PATH "${cmake_prefix_path}"
0504 set_env ROOT_SETUP_SCRIPT "${view_dir}/bin/thisroot.sh"
0505 set_env ROOT_INCLUDE_PATH "${view_dir}/include"
0506
0507 set_env PKG_CONFIG_PATH ""
0508 set_env pythonLocation ""
0509 set_env Python_ROOT_DIR ""
0510 set_env Python2_ROOT_DIR ""
0511 set_env Python3_ROOT_DIR ""
0512 end_section
0513
0514 checkpoint "Setup script complete"