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