Skip to content

Commit e8f8795

Browse files
committed
Merge remote-tracking branch 'origin/main' into cudaq-src
2 parents ad0039c + d6ce116 commit e8f8795

File tree

7 files changed

+139
-25
lines changed

7 files changed

+139
-25
lines changed

.github/workflows/ci_macos.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,13 @@ jobs:
249249

250250
- name: Validate wheel
251251
run: |
252-
# Full validation: core tests, examples, snippets, backends
253-
# GPU/MPI tests are automatically skipped on macOS (CPU-only)
252+
# Validates wheel: core tests, examples, snippets, backends.
253+
# GPU/remote targets are skipped (no CUDA on macOS runners).
254+
# TODO: When adding macOS to publishing.yml, align the validation
255+
# approach with python_wheels.yml (which uses
256+
# explicit find exclusions for platform/backends/dynamics paths,
257+
# and does not use validate_pycudaq.sh for snippets/examples
258+
# currently used in python_wheels.yml).
254259
bash scripts/validate_pycudaq.sh \
255260
-v ${{ needs.wheel.outputs.cudaq_version }} \
256261
-i dist \

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ SET(CMAKE_SKIP_INSTALL_RPATH FALSE)
216216
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
217217
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
218218
if(APPLE)
219-
SET(CMAKE_INSTALL_RPATH "@executable_path;@executable_path/lib;@executable_path/lib/plugins;@executable_path/../lib;@executable_path/../lib/plugins")
219+
SET(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/lib;@loader_path/lib/plugins;@loader_path/../lib;@loader_path/../lib/plugins;@executable_path;@executable_path/lib;@executable_path/lib/plugins;@executable_path/../lib;@executable_path/../lib/plugins")
220220
else()
221221
SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/lib:$ORIGIN/lib/plugins:$ORIGIN/../lib:$ORIGIN/../lib/plugins")
222222
endif()

python/runtime/cudaq/domains/plugins/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
message(STATUS "Building cudaq-pyscf plugin.")
1010
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/plugins)
11-
SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
11+
if(APPLE)
12+
SET(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/..")
13+
else()
14+
SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
15+
endif()
1216
add_library(cudaq-pyscf SHARED PySCFDriver.cpp)
1317

1418
if (SKBUILD)

python/utils/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
# the terms of the Apache License 2.0 which accompanies this distribution. #
77
# ============================================================================ #
88
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
9-
SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
9+
if(APPLE)
10+
SET(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/..")
11+
else()
12+
SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
13+
endif()
1014

1115
add_library(cudaq-py-utils SHARED LinkedLibraryHolder.cpp)
1216
target_link_libraries(cudaq-py-utils PRIVATE

runtime/cudaq/distributed/builtin/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
if (MPI_CXX_FOUND)
1010
message(STATUS "Building default MPI Comm plugin")
1111
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/plugins)
12-
set(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
12+
if(APPLE)
13+
set(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/..")
14+
else()
15+
set(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..")
16+
endif()
1317
# IMPORTANT: Don't change this lib name without updating the getMpiPlugin function
1418
set(LIBRARY_NAME cudaq-comm-plugin)
1519
add_library(${LIBRARY_NAME} SHARED mpi_comm_impl.cpp)

scripts/build_wheel.sh

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -340,16 +340,15 @@ if [ "$platform" = "Darwin" ]; then
340340
exit 1
341341
fi
342342

343-
# delocate repairs the wheel in place or to wheelhouse/
344-
# Use --ignore-missing because internal libs reference each other via @rpath
345-
# and delocate can't resolve them (they're all packaged together)
343+
# delocate repairs the wheel and copies it to wheelhouse/.
344+
# With @loader_path rpaths, delocate can resolve inter-library
345+
# references.
346346
mkdir -p wheelhouse
347-
delocate_args="--ignore-missing -w wheelhouse"
348347
if $verbose; then
349-
echo " Command: delocate-wheel -v $delocate_args $wheel_file"
350-
delocate-wheel -v $delocate_args "$wheel_file"
348+
echo " Command: delocate-wheel -v -w wheelhouse $wheel_file"
349+
delocate-wheel -v -w wheelhouse "$wheel_file"
351350
else
352-
delocate-wheel $delocate_args "$wheel_file"
351+
delocate-wheel -w wheelhouse "$wheel_file"
353352
fi
354353

355354
# Move repaired wheel to output

scripts/validate_pycudaq.sh

Lines changed: 110 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# -i <packages_dir>: Directory containing wheel files (--find-links)
2121
# -p <python_version>: Python version (default: 3.11)
2222
# -q: Quick test mode (only run core tests)
23+
# -F: Force fresh venv (macOS only; delete and recreate instead of reusing)
2324
#
2425
# Examples:
2526
# # From repo root with auto-detection:
@@ -35,19 +36,33 @@
3536
# COPY python/README.md.in /tmp/README.md
3637
#
3738
# Note: To run target tests, set the necessary API keys (IONQ_API_KEY, etc.)
39+
#
40+
# TODO: Unify wheel validation around this script. Currently:
41+
# - ci_macos.yml uses this script (auto-detects from repo, runs everything)
42+
# - publishing.yml uses this script (selective dir copy to /tmp, no targets/)
43+
# - python_wheels.yml does NOT use this script; it runs pytest, snippets,
44+
# and examples directly with explicit `find` exclusions in Docker containers.
45+
# The goal is to have all wheel validation run through this script, replacing the
46+
# inline find/pytest commands in python_wheels.yml. Use -q for quick
47+
# (core pytest only, suitable for per-PR CI) and full mode for publishing.
48+
# This avoids duplicating skip logic between the script and workflow files.
3849

3950
__optind__=$OPTIND
4051
OPTIND=1
4152
python_version=3.11
4253
quick_test=false
43-
while getopts ":c:f:i:p:qv:" opt; do
54+
fresh_venv=false
55+
while getopts ":c:f:Fi:p:qv:" opt; do
4456
case $opt in
4557
c)
4658
cuda_version_conda="$OPTARG"
4759
;;
4860
f)
4961
root_folder="$OPTARG"
5062
;;
63+
F)
64+
fresh_venv=true
65+
;;
5166
p)
5267
python_version="$OPTARG"
5368
;;
@@ -68,6 +83,26 @@ while getopts ":c:f:i:p:qv:" opt; do
6883
done
6984
OPTIND=$__optind__
7085

86+
# Sanitize environment: unset variables that could leak build-tree or
87+
# system-installed CUDA-Q libraries into the validation environment.
88+
# Without this, DYLD_LIBRARY_PATH from a prior build step can cause the
89+
# wheel to load libraries from _skbuild/ instead of its own bundled copies,
90+
# masking packaging bugs.
91+
SANITIZE_VARS="
92+
DYLD_LIBRARY_PATH
93+
DYLD_FALLBACK_LIBRARY_PATH
94+
LD_LIBRARY_PATH
95+
CUDAQ_INSTALL_PREFIX
96+
CUDA_QUANTUM_PATH
97+
PYTHONPATH
98+
"
99+
100+
for var in $SANITIZE_VARS; do
101+
unset "$var"
102+
done
103+
104+
echo "Environment sanitized (unset: $SANITIZE_VARS)"
105+
71106
# Auto-detect repo structure if -f not provided
72107
if [ -z "$root_folder" ]; then
73108
# Try to find repo root
@@ -89,13 +124,16 @@ if [ -z "$root_folder" ]; then
89124
rm -rf "${staging_dir:?}"
90125
mkdir -p "$staging_dir"
91126

92-
# Symlink test files to staging (mirrors CI copy structure)
93-
ln -sf "$readme_src" "$staging_dir/README.md"
94-
ln -sf "$repo_root/python/tests" "$staging_dir/tests"
95-
ln -sf "$repo_root/docs/sphinx/examples/python" "$staging_dir/examples"
96-
ln -sf "$repo_root/docs/sphinx/snippets/python" "$staging_dir/snippets"
127+
# Copy test files to staging (mirrors CI copy structure).
128+
# Use cp -r instead of symlinks for robustness: find(1) may not
129+
# follow initial symlinks in all environments, and CI runners may
130+
# have different filesystem semantics.
131+
cp -f "$readme_src" "$staging_dir/README.md"
132+
cp -r "$repo_root/python/tests" "$staging_dir/tests"
133+
cp -r "$repo_root/docs/sphinx/examples/python" "$staging_dir/examples"
134+
cp -r "$repo_root/docs/sphinx/snippets/python" "$staging_dir/snippets"
97135
if [ -d "$repo_root/docs/sphinx/targets/python" ]; then
98-
ln -sf "$repo_root/docs/sphinx/targets/python" "$staging_dir/targets"
136+
cp -r "$repo_root/docs/sphinx/targets/python" "$staging_dir/targets"
99137
fi
100138

101139
root_folder="$staging_dir"
@@ -111,13 +149,41 @@ fi
111149

112150
echo "Using test root folder: $root_folder"
113151

114-
# Detect platform
152+
# Detect platform and GPU availability
115153
is_macos=false
154+
has_cuda=false
116155
if [ "$(uname)" = "Darwin" ]; then
117156
is_macos=true
118157
echo "macOS detected: running CPU-only validation"
158+
else
159+
if [ -x "$(command -v nvidia-smi)" ] && nvidia-smi &>/dev/null; then
160+
has_cuda=true
161+
fi
162+
fi
163+
if ! $has_cuda; then
164+
echo "No CUDA GPU detected: GPU-dependent tests will be skipped"
119165
fi
120166

167+
# Check if a Python file requires a GPU target that is not available.
168+
# Returns 0 (true) if the file should be skipped, 1 (false) otherwise.
169+
requires_unavailable_gpu_target() {
170+
local file="$1"
171+
if $has_cuda; then
172+
return 1
173+
fi
174+
local targets
175+
targets=$(awk -F'"' '/cudaq\.set_target/ {print $2}' "$file")
176+
for t in $targets; do
177+
case "$t" in
178+
nvidia|nvidia-fp64|nvidia-mgpu|dynamics|tensornet|remote-mqpu)
179+
echo "Skipping $file (requires GPU target '$t')"
180+
return 0
181+
;;
182+
esac
183+
done
184+
return 1
185+
}
186+
121187
# Check that the `cuda_version_conda` is a full version string like "12.8.0" (Linux only)
122188
if ! $is_macos && ! [[ $cuda_version_conda =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
123189
echo -e "\e[01;31mThe cuda_version_conda (-c) must be a full version string like '12.8.0'. Provided: '${cuda_version_conda}'.\e[0m" >&2
@@ -142,8 +208,13 @@ if $is_macos; then
142208
# macOS: use venv (simpler, no conda ToS issues, no MPI needed for CPU-only)
143209
venv_dir="$HOME/.venv/cudaq-validation"
144210

211+
if $fresh_venv && [ -d "$venv_dir" ]; then
212+
echo "Removing existing venv at $venv_dir"
213+
rm -rf "$venv_dir"
214+
fi
215+
145216
if [ -d "$venv_dir" ]; then
146-
echo "Reusing existing venv at $venv_dir (delete it to start fresh)"
217+
echo "Reusing existing venv at $venv_dir (use -F to start fresh)"
147218
else
148219
echo "Creating venv at $venv_dir"
149220
python3 -m venv "$venv_dir"
@@ -205,6 +276,13 @@ if ! $is_macos; then
205276
fi
206277
status_sum=0
207278

279+
# Run all tests from a temp directory so the repo tree (cwd, _skbuild/,
280+
# etc.) cannot accidentally be found via sys.path or dyld search paths
281+
# to ensure the wheel is installed in isolation.
282+
test_workdir=$(mktemp -d)
283+
echo "Running tests from isolated directory: $test_workdir"
284+
cd "$test_workdir"
285+
208286
# Verify that the necessary GPU targets are installed and usable (Linux only)
209287
if $is_macos; then
210288
echo "Skipping GPU target verification on macOS (CPU-only)"
@@ -284,6 +362,9 @@ fi
284362

285363
# Run snippets in docs
286364
for ex in $(find "$root_folder/snippets" -name '*.py'); do
365+
if requires_unavailable_gpu_target "$ex"; then
366+
continue
367+
fi
287368
echo "Executing $ex"
288369
python3 "$ex"
289370
if [ ! $? -eq 0 ]; then
@@ -294,13 +375,13 @@ done
294375

295376
# Run examples
296377
for ex in $(find "$root_folder/examples" -name '*.py'); do
378+
if requires_unavailable_gpu_target "$ex"; then
379+
continue
380+
fi
297381
skip_example=false
298-
# Extract target names from cudaq.set_target("...") calls (awk splits on quotes, prints field 2)
299382
explicit_targets=$(awk -F'"' '/cudaq\.set_target/ {print $2}' "$ex")
300383
for t in $explicit_targets; do
301384
if [ "$t" == "quera" ] || [ "$t" == "braket" ]; then
302-
# Skipped because GitHub does not have the necessary authentication token
303-
# to submit a (paid) job to Amazon Braket (includes QuEra).
304385
echo -e "\e[01;31mWarning: Explicitly set target braket or quera in $ex; skipping validation due to paid submission.\e[0m" >&2
305386
skip_example=true
306387
elif [ "$t" == "pasqal" ] && [ -z "${PASQAL_PASSWORD}" ]; then
@@ -318,9 +399,19 @@ for ex in $(find "$root_folder/examples" -name '*.py'); do
318399
fi
319400
done
320401

402+
snippet_count=$(find "$root_folder/snippets" -name '*.py' 2>/dev/null | wc -l)
403+
example_count=$(find "$root_folder/examples" -name '*.py' 2>/dev/null | wc -l)
404+
if [ "$snippet_count" -eq 0 ] && [ "$example_count" -eq 0 ]; then
405+
echo -e "\e[01;31mNo snippets or examples found in $root_folder. Check staging setup.\e[0m" >&2
406+
status_sum=$((status_sum + 1))
407+
fi
408+
321409
# Run target tests if target folder exists.
322410
if [ -d "$root_folder/targets" ]; then
323411
for ex in $(find "$root_folder/targets" -name '*.py'); do
412+
if requires_unavailable_gpu_target "$ex"; then
413+
continue
414+
fi
324415
skip_example=false
325416
# Extract target names from cudaq.set_target("...") calls (awk splits on quotes, prints field 2)
326417
explicit_targets=$(awk -F'"' '/cudaq\.set_target/ {print $2}' "$ex")
@@ -345,6 +436,13 @@ if [ -d "$root_folder/targets" ]; then
345436
elif [ "$t" == "ionq" ] && [ -z "${IONQ_API_KEY}" ]; then
346437
echo -e "\e[01;31mWarning: Explicitly set target ionq in $ex; skipping validation due to missing API key.\e[0m" >&2
347438
skip_example=true
439+
elif [ "$t" == "quantum_machines" ] || [ "$t" == "quantinuum" ] || \
440+
[ "$t" == "orca" ] || [ "$t" == "orca-photonics" ] || \
441+
[ "$t" == "iqm" ] || [ "$t" == "infleqtion" ] || [ "$t" == "anyon" ]; then
442+
# These targets require remote backends that are not available
443+
# in CI or local dev without explicit setup.
444+
echo "Skipping $ex (remote target '$t' not available)"
445+
skip_example=true
348446
fi
349447
done
350448
if ! $skip_example; then

0 commit comments

Comments
 (0)