diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 71db5fa98..17eb94c49 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -33,7 +33,6 @@ on: - main - devel - jobs: # test only compilation succeeds (no execution) @@ -257,7 +256,19 @@ jobs: run: > wget https://developer.download.nvidia.com/compute/cuquantum/redist/cuquantum/linux-x86_64/cuquantum-linux-x86_64-24.08.0.5_cuda12-archive.tar.xz; tar -xvf cuquantum-linux-x86_64-24.08.0.5_cuda12-archive.tar.xz; - echo "CUQUANTUM_ROOT=cuquantum-linux-x86_64-24.08.0.5_cuda12-archive" >> $GITHUB_ENV + echo "CUQUANTUM_ROOT=$PWD/cuquantum-linux-x86_64-24.08.0.5_cuda12-archive" >> $GITHUB_ENV; + echo "LD_LIBRARY_PATH=$PWD/cuquantum-linux-x86_64-24.08.0.5_cuda12-archive/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV + + # One controlled Linux case checks the finder against actual SDK symbols. + # custatevecGetVersion() does not execute a GPU operation. + - name: Link and run the real cuQuantum SDK fixture + if: ${{ matrix.os == 'ubuntu-latest' && matrix.precision == 2 && matrix.omp == 'OFF' && matrix.mpi == 'OFF' && matrix.cuquantum == 'ON' && matrix.adios2 == 'OFF' && matrix.bmi2 == 'OFF' }} + run: | + cmake -S tests/packaging/cuquantum/real-sdk -B build-cuquantum-real \ + -DCUQUANTUM_ROOT="$CUQUANTUM_ROOT" \ + -DCUDAToolkit_ROOT="$CUDA_PATH" + cmake --build build-cuquantum-real --parallel 1 + ctest --test-dir build-cuquantum-real --output-on-failure # obtain ROCm for HIP acceleration on Linux - name: Install ROCm @@ -291,6 +302,11 @@ jobs: - name: Configure CMake run: > cmake -B ${{ env.build_dir }} + -DBUILD_SHARED_LIBS=${{ matrix.os == 'ubuntu-latest' && matrix.precision == 2 && matrix.omp == 'OFF' && matrix.mpi == 'OFF' && matrix.cuquantum == 'ON' && matrix.adios2 == 'OFF' && matrix.bmi2 == 'OFF' && 'OFF' || 'ON' }} + -DQUEST_ENABLE_INSTALL=${{ matrix.adios2 == 'ON' && 'OFF' || 'ON' }} + -DQUEST_ENABLE_PACKAGING=OFF + -DQUEST_BUILD_PACKAGING_TESTS=${{ matrix.adios2 == 'ON' && 'OFF' || 'ON' }} + -DQUEST_TEST_ARCHIVES=OFF -DQUEST_BUILD_EXAMPLES=ON -DQUEST_BUILD_TESTS=ON -DQUEST_FLOAT_PRECISION=${{ matrix.precision }} @@ -313,6 +329,50 @@ jobs: - name: Compile run: cmake --build ${{ env.build_dir }} --config Release --parallel 1 + # downloaded ADIOS2 is deliberately restricted to non-installable developer + # builds; every other backend validates the installed exported target + - name: Stage, relocate, and consume installed QuEST + if: ${{ matrix.adios2 == 'OFF' }} + run: ctest --test-dir ${{ env.build_dir }} -C Release -L packaging --output-on-failure + + # Keep the static real-SDK case above, and separately exercise the opt-in + # native shared install with actual CUDA/cuQuantum runtime dependencies. + - name: Install and consume native shared CUDA and cuQuantum + if: ${{ matrix.os == 'ubuntu-latest' && matrix.precision == 2 && matrix.omp == 'OFF' && matrix.mpi == 'OFF' && matrix.cuquantum == 'ON' && matrix.adios2 == 'OFF' && matrix.bmi2 == 'OFF' }} + shell: bash + run: | + for variable in ${!LD_@} ${!DYLD_@} LIBPATH SHLIB_PATH; do + unset "$variable" + done + native_root="$RUNNER_TEMP/quest-cuquantum-native" + # USE_LINK_PATH excludes SDK directories inside the producer source tree. + mkdir -p "$native_root" + cp -a --reflink=auto "$CUQUANTUM_ROOT" "$native_root/cuquantum" + cmake -S . -B "$native_root/build" \ + -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \ + -DQUEST_ENABLE_INSTALL=ON -DQUEST_ENABLE_PACKAGING=OFF \ + -DQUEST_BUILD_MIN_EXAMPLE=OFF -DQUEST_BUILD_EXAMPLES=OFF \ + -DQUEST_BUILD_TESTS=OFF -DQUEST_BUILD_PACKAGING_TESTS=OFF \ + -DQUEST_ENABLE_OMP=OFF -DQUEST_ENABLE_MPI=OFF \ + -DQUEST_ENABLE_CUDA=ON -DQUEST_ENABLE_CUQUANTUM=ON \ + -DCMAKE_CUDA_RUNTIME_LIBRARY=Shared \ + -DCMAKE_CUDA_ARCHITECTURES=${{ env.cuda_arch }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} \ + -DCUDAToolkit_ROOT="$CUDA_PATH" -DCUQUANTUM_ROOT="$native_root/cuquantum" \ + -DCMAKE_INSTALL_PREFIX="$native_root/prefix" -DCMAKE_INSTALL_LIBDIR=lib \ + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON + cmake --build "$native_root/build" --config Release --parallel 1 + cmake --install "$native_root/build" --config Release + cmake -S tests/packaging/consumer -B "$native_root/consumer" \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$native_root/prefix" \ + -DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF -DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF + cmake --build "$native_root/consumer" --config Release --parallel 1 + python3 tests/packaging/check_native_runtime.py \ + --build-dir "$native_root/build" --library "$native_root/prefix/lib/libQuEST.so" \ + --require-shared-cudart \ + --consumer "$native_root/consumer/consumer_c" \ + --consumer "$native_root/consumer/consumer_cpp" + # run all compiled isolated examples to test for link-time errors, # continuing if any fail (since some deliberately fail) - name: Run isolated examples (Windows) diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 000000000..08a66b825 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,360 @@ +# Builds, relocates, and consumes QuEST's install tree and generated packages. +# GPU functional builds remain in compile.yml; this workflow checks portable +# CPU archives, externally provided ADIOS2 exports, and native package profiles. + +name: packaging + +on: + push: + branches: + - main + - devel + - v4.3-release + pull_request: + branches: + - main + - devel + - v4.3-release + workflow_dispatch: + +permissions: + contents: read + +jobs: + binary-archives: + name: ${{ matrix.os }} ${{ matrix.shared == 'ON' && 'shared' || 'static' }} CMake ${{ matrix.cmake }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + shared: [ON, OFF] + cmake: ['3.28.x', latest] + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ matrix.cmake }} + + - name: Configure install and packaging tests + run: > + cmake -S . -B build + -DBUILD_SHARED_LIBS=${{ matrix.shared }} + -DCMAKE_BUILD_TYPE=Release + -DQUEST_ENABLE_INSTALL=ON + -DQUEST_ENABLE_PACKAGING=ON + -DQUEST_BUILD_PACKAGING_TESTS=ON + -DQUEST_BUILD_MIN_EXAMPLE=ON + -DQUEST_INSTALL_BINARIES=ON + -DQUEST_ENABLE_OMP=OFF + -DQUEST_ENABLE_NUMA=OFF + -DQUEST_ENABLE_MPI=OFF + + - name: Build QuEST + run: cmake --build build --config Release --parallel 2 + + - name: Stage, relocate, and consume the install + run: ctest --test-dir build -C Release -L packaging --output-on-failure + + - name: Create TGZ and ZIP binary archives + run: | + cpack --config build/CPackConfig.cmake -C Release -G TGZ -B binary-packages + cpack --config build/CPackConfig.cmake -C Release -G ZIP -B binary-packages + + - name: Upload binary archives + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.os }}-${{ matrix.shared }}-cmake-${{ matrix.cmake }} + path: | + binary-packages/*.tar.gz + binary-packages/*.zip + if-no-files-found: error + + source-archives: + name: Source TGZ and ZIP without Git + runs-on: ubuntu-24.04 + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Configure source packaging + run: > + cmake -S . -B build + -DCMAKE_BUILD_TYPE=Release + -DQUEST_ENABLE_INSTALL=ON + -DQUEST_ENABLE_PACKAGING=ON + -DQUEST_BUILD_MIN_EXAMPLE=ON + -DQUEST_ENABLE_OMP=OFF + -DQUEST_ENABLE_NUMA=OFF + + - name: Create source archives + run: | + cpack --config build/CPackSourceConfig.cmake -G TGZ -B source-packages + cpack --config build/CPackSourceConfig.cmake -G ZIP -B source-packages + + - name: Rebuild the TGZ outside Git + shell: bash + run: | + archive=$(find source-packages -maxdepth 1 -name '*.tar.gz' -print -quit) + test -n "$archive" + mkdir source-extract + tar -xf "$archive" -C source-extract + source_dir=$(find source-extract -mindepth 1 -maxdepth 1 -type d -print -quit) + test -n "$source_dir" + test ! -e "$source_dir/.git" + cmake -S "$source_dir" -B source-rebuild -DQUEST_ENABLE_OMP=OFF -DQUEST_ENABLE_NUMA=OFF + cmake --build source-rebuild --config Release --parallel 2 + + - name: Upload source archives + uses: actions/upload-artifact@v4 + with: + name: source-archives + path: | + source-packages/*.tar.gz + source-packages/*.zip + if-no-files-found: error + + linux-variants: + name: Linux ${{ matrix.name }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - name: custom static fp1 lib64 without NUMA + generator: Ninja + config: Release + shared: OFF + precision: 1 + omp: ON + disable_numa: TRUE + output_name: CustomQuEST + append_name: ON + prefix: /opt/quest-ci + libdir: lib64 + bindir: tools/bin + includedir: share/quest-headers + - name: fp4 Ninja Multi-Config Debug + generator: Ninja Multi-Config + config: Debug + shared: ON + precision: 4 + omp: OFF + disable_numa: FALSE + output_name: QuEST + append_name: OFF + prefix: /srv/quest-ci + libdir: lib + bindir: bin + includedir: include + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Configure variant + run: > + cmake -S . -B build-variant -G "${{ matrix.generator }}" + -DCMAKE_BUILD_TYPE=${{ matrix.config }} + -DCMAKE_INSTALL_PREFIX=${{ matrix.prefix }} + -DCMAKE_INSTALL_LIBDIR=${{ matrix.libdir }} + -DCMAKE_INSTALL_BINDIR=${{ matrix.bindir }} + -DCMAKE_INSTALL_INCLUDEDIR=${{ matrix.includedir }} + -DCMAKE_DISABLE_FIND_PACKAGE_NUMA=${{ matrix.disable_numa }} + -DBUILD_SHARED_LIBS=${{ matrix.shared }} + -DQUEST_OUTPUT_LIB_NAME=${{ matrix.output_name }} + -DQUEST_APPEND_CONFIG_TO_LIB_NAME=${{ matrix.append_name }} + -DQUEST_FLOAT_PRECISION=${{ matrix.precision }} + -DQUEST_ENABLE_OMP=${{ matrix.omp }} + -DQUEST_ENABLE_NUMA=ON + -DQUEST_ENABLE_MPI=OFF + -DQUEST_ENABLE_INSTALL=ON + -DQUEST_ENABLE_PACKAGING=ON + -DQUEST_BUILD_PACKAGING_TESTS=ON + -DQUEST_BUILD_MIN_EXAMPLE=ON + -DQUEST_INSTALL_BINARIES=ON + + - name: Build QuEST + run: cmake --build build-variant --config ${{ matrix.config }} --parallel 2 + + - name: Test variant install and archives + run: ctest --test-dir build-variant -C ${{ matrix.config }} -L packaging --output-on-failure + + adios2-exports: + name: ADIOS2 ${{ matrix.adios2 }} ${{ matrix.shared == 'ON' && 'shared' || 'static' }} export + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + adios2: [serial, mpi] + shared: [ON, OFF] + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Install external ADIOS2 + run: | + sudo apt-get update + sudo apt-get install -y libadios2-${{ matrix.adios2 }}-c++11-dev ${{ matrix.adios2 == 'mpi' && 'libopenmpi-dev openmpi-bin' || '' }} + + - name: Configure install and packaging tests + run: > + cmake -S . -B build-adios2 + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=${{ matrix.shared }} + -DQUEST_ENABLE_INSTALL=ON + -DQUEST_ENABLE_PACKAGING=OFF + -DQUEST_BUILD_PACKAGING_TESTS=ON + -DQUEST_TEST_ARCHIVES=OFF + -DQUEST_BUILD_MIN_EXAMPLE=OFF + -DQUEST_ENABLE_OMP=OFF + -DQUEST_ENABLE_NUMA=OFF + -DQUEST_ENABLE_MPI=${{ matrix.adios2 == 'mpi' && 'ON' || 'OFF' }} + -DQUEST_ENABLE_ADIOS2=ON + -DQUEST_DOWNLOAD_ADIOS2=OFF + -Dadios2_DIR=/usr/lib/x86_64-linux-gnu/cmake/adios2/${{ matrix.adios2 }} + + - name: Build QuEST + run: cmake --build build-adios2 --parallel 2 + + - name: Stage, relocate, and consume installed QuEST + run: ctest --test-dir build-adios2 -L packaging --output-on-failure + + ubuntu-deb: + name: Ubuntu 24.04 ${{ matrix.shared == 'ON' && 'shared' || 'static' }} DEB install and consumers + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + shared: [ON, OFF] + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Build stock native packages + shell: bash + run: | + docker run --rm -v "$PWD:/src" -w /src ubuntu:24.04 bash -euxo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + export OMPI_ALLOW_RUN_AS_ROOT=1 + export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + apt-get update + apt-get install -y build-essential cmake ninja-build python3 dpkg-dev fakeroot file libnuma-dev libopenmpi-dev openmpi-bin + cmake -S . -B build-deb -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SHARED_LIBS=${{ matrix.shared }} \ + -DQUEST_ENABLE_INSTALL=ON \ + -DQUEST_ENABLE_PACKAGING=ON \ + -DQUEST_BUILD_PACKAGING_TESTS=ON \ + -DQUEST_NATIVE_PACKAGE_PROFILE=ubuntu24.04 \ + -DQUEST_BUILD_MIN_EXAMPLE=ON \ + -DQUEST_INSTALL_BINARIES=ON \ + -DQUEST_ENABLE_OMP=ON \ + -DQUEST_ENABLE_NUMA=ON \ + -DQUEST_ENABLE_MPI=ON \ + -DQUEST_ENABLE_SUBCOMM=ON + cmake --build build-deb --parallel 2 + ctest --test-dir build-deb -L packaging --output-on-failure + cpack --config build-deb/CPackConfig.cmake -G DEB -B packages-deb + package_count=$(find packages-deb -maxdepth 1 -name "*.deb" -print | wc -l) + if [ "${{ matrix.shared }}" = ON ]; then test "$package_count" -eq 3; else test "$package_count" -eq 2; fi + ' + + - name: Install packages in a clean container and build consumers + shell: bash + run: | + docker run --rm -v "$PWD:/src:ro" -w /src ubuntu:24.04 bash -euxo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + export OMPI_ALLOW_RUN_AS_ROOT=1 + export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + apt-get update + apt-get install -y build-essential cmake + apt-get install -y /src/packages-deb/*.deb + cmake -S /src/tests/packaging/consumer -B /tmp/quest-consumer + cmake --build /tmp/quest-consumer --parallel 2 + ctest --test-dir /tmp/quest-consumer --output-on-failure + min_example + ' + + - name: Upload DEB packages + uses: actions/upload-artifact@v4 + with: + name: ubuntu-24.04-${{ matrix.shared }}-deb + path: packages-deb/*.deb + if-no-files-found: error + + fedora-rpm: + name: Fedora 44 ${{ matrix.shared == 'ON' && 'shared' || 'static' }} RPM install and consumers + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + shared: [ON, OFF] + + steps: + - name: Get QuEST + uses: actions/checkout@v4 + + - name: Build stock native packages + shell: bash + run: | + docker run --rm -v "$PWD:/src" -w /src fedora:44 bash -euxo pipefail -c ' + dnf install -y gcc gcc-c++ cmake ninja-build python3 rpm-build file numactl-devel openmpi-devel environment-modules + set +u + source /etc/profile.d/modules.sh + module load mpi/openmpi-x86_64 + set -u + export OMPI_ALLOW_RUN_AS_ROOT=1 + export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + cmake -S . -B build-rpm -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SHARED_LIBS=${{ matrix.shared }} \ + -DQUEST_ENABLE_INSTALL=ON \ + -DQUEST_ENABLE_PACKAGING=ON \ + -DQUEST_BUILD_PACKAGING_TESTS=ON \ + -DQUEST_NATIVE_PACKAGE_PROFILE=fedora44 \ + -DQUEST_BUILD_MIN_EXAMPLE=ON \ + -DQUEST_INSTALL_BINARIES=ON \ + -DQUEST_ENABLE_OMP=ON \ + -DQUEST_ENABLE_NUMA=ON \ + -DQUEST_ENABLE_MPI=ON \ + -DQUEST_ENABLE_SUBCOMM=ON + cmake --build build-rpm --parallel 2 + ctest --test-dir build-rpm -L packaging --output-on-failure + cpack --config build-rpm/CPackConfig.cmake -G RPM -B packages-rpm + package_count=$(find packages-rpm -maxdepth 1 -name "*.rpm" -print | wc -l) + if [ "${{ matrix.shared }}" = ON ]; then test "$package_count" -eq 3; else test "$package_count" -eq 2; fi + ' + + - name: Install packages in a clean container and build consumers + shell: bash + run: | + docker run --rm -v "$PWD:/src:ro" -w /src fedora:44 bash -euxo pipefail -c ' + dnf install -y gcc gcc-c++ cmake environment-modules /src/packages-rpm/*.rpm + set +u + source /etc/profile.d/modules.sh + module load mpi/openmpi-x86_64 + set -u + export OMPI_ALLOW_RUN_AS_ROOT=1 + export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + cmake -S /src/tests/packaging/consumer -B /tmp/quest-consumer + cmake --build /tmp/quest-consumer --parallel 2 + ctest --test-dir /tmp/quest-consumer --output-on-failure + min_example + ' + + - name: Upload RPM packages + uses: actions/upload-artifact@v4 + with: + name: fedora-44-${{ matrix.shared }}-rpm + path: packages-rpm/*.rpm + if-no-files-found: error diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b9f3c045..72697cf20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,16 @@ # ============================ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.28) +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + message(FATAL_ERROR "QuEST requires an out-of-source build. Use cmake -S . -B build.") +endif() project(QuEST - VERSION 4.2.0 + VERSION 4.3.0 DESCRIPTION "Quantum Exact Simulation Toolkit" + HOMEPAGE_URL "https://quest.qtechtheory.org" LANGUAGES CXX C ) @@ -44,9 +48,15 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) -# Maths -if (NOT WIN32) - find_library(MATH_LIBRARY m REQUIRED) +option(QUEST_ENABLE_INSTALL "Generate QuEST installation rules" ${PROJECT_IS_TOP_LEVEL}) +set(_quest_packaging_default OFF) +if(PROJECT_IS_TOP_LEVEL AND QUEST_ENABLE_INSTALL) + set(_quest_packaging_default ON) +endif() +option(QUEST_ENABLE_PACKAGING "Generate QuEST CPack packages" ${_quest_packaging_default}) +option(QUEST_BUILD_MIN_EXAMPLE "Build the minimal example" ${PROJECT_IS_TOP_LEVEL}) +if(QUEST_ENABLE_PACKAGING AND NOT QUEST_ENABLE_INSTALL) + message(FATAL_ERROR "QUEST_ENABLE_PACKAGING requires QUEST_ENABLE_INSTALL") endif() @@ -61,7 +71,7 @@ endif() # Using recipe from Kitware Blog post # https://www.kitware.com/cmake-and-the-default-build-type/ set(quest_default_build_type "Release") -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) +if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${quest_default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${quest_default_build_type}" CACHE STRING "Choose the type of build." FORCE) @@ -116,7 +126,7 @@ message(STATUS "Examples are turned ${QUEST_BUILD_EXAMPLES}. Set QUEST_BUILD_EXA # Testing option( QUEST_BUILD_TESTS - "Whether the test suite will be built alongside the QuEST library. Turned ON by default." + "Whether the test suite will be built alongside the QuEST library. Turned OFF by default." OFF ) message(STATUS "Testing is turned ${QUEST_BUILD_TESTS}. Set QUEST_BUILD_TESTS to modify.") @@ -275,7 +285,7 @@ endif() if(WIN32) # Force MSVC to export all symbols in a shared library, like GCC and clang - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + # Applied to the QuEST target below, without changing parent targets. if (QUEST_BUILD_TESTS AND BUILD_SHARED_LIBS) message(WARNING "Compiling the tests on Windows requires BUILD_SHARED_LIBS=OFF which we now force.") @@ -455,20 +465,14 @@ set_target_properties(QuEST PROPERTIES ) -# Add required C and C++ standards. -# Note the QuEST interface(s) require only C11 and C++14, -# while the source code is entirely C++ and requires C++17, -# and the tests further require C++20 (handled in tests/). -# Yet, we here specify C++17 for the source, and C11 as only -# applies to the C interface when users specify USER_SOURCE_NAMES, -# to attemptedly minimise user confusion. Users wishing to -# link QuEST with C++14 should separate compilation. -target_compile_features(QuEST - PUBLIC - c_std_11 - cxx_std_17 -) - +# Headers support C11/C++14; the implementation needs C++17. +target_compile_features(QuEST PUBLIC c_std_11 cxx_std_14 PRIVATE cxx_std_17) +set_target_properties(QuEST PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) +get_target_property(_quest_library_type QuEST TYPE) +set(QUEST_BUILT_SHARED OFF) +if(_quest_library_type STREQUAL "SHARED_LIBRARY") + set(QUEST_BUILT_SHARED ON) +endif() # Turn on all compiler warnings if (MSVC) @@ -490,206 +494,7 @@ target_compile_options(QuEST # ============================ -# OpenMP -if (QUEST_ENABLE_OMP) - - # find OpenMP, but fail gracefully... - find_package(OpenMP QUIET) - - # so that we can customise the error message on MacOS - if (NOT OpenMP_FOUND) - set(ErrorMsg "Could not find OpenMP, necessary for enabling multithreading.") - if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") - string(APPEND ErrorMsg " Try first calling \n\tbrew install libomp\nthen\n\texport OpenMP_ROOT=$(brew --prefix)/opt/libomp") - endif() - message(FATAL_ERROR ${ErrorMsg}) - endif() - - target_link_libraries(QuEST - PRIVATE - OpenMP::OpenMP_CXX - OpenMP::OpenMP_C - ) - -else() - - # suppress GCC "unknown pragma" warning when OpenMP disabled - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(QuEST PRIVATE $<$:-Wno-unknown-pragmas>) - endif() - -endif() - - -# NUMA (only relevant when multithreading) -if (QUEST_ENABLE_OMP AND QUEST_ENABLE_NUMA) - - # Find NUMA - location of NUMA headers - if (WIN32) - set(QUEST_ENABLE_NUMA 0) - message(WARNING "Building on Windows, QuEST will not be aware of numa locality") - else() - include(FindPkgConfig) - pkg_search_module(NUMA numa IMPORTED_TARGET GLOBAL) - if (${NUMA_FOUND}) - set(QUEST_ENABLE_NUMA ${NUMA_FOUND}) - target_link_libraries(QuEST PRIVATE PkgConfig::NUMA) - message(STATUS "NUMA awareness is enabled.") - else() - set(QUEST_ENABLE_NUMA 0) - message(WARNING "libnuma not found, QuEST will not be aware of numa locality") - endif() - endif() - -else() - set(QUEST_ENABLE_NUMA 0) -endif() - - -# MPI -if (QUEST_ENABLE_MPI) - find_package(MPI REQUIRED - # Component CXX is the C api usable from C++ - # NOT the deprecated C++ API - COMPONENTS CXX - ) - - target_link_libraries(QuEST - PRIVATE - MPI::MPI_CXX - ) -endif() - - -# CUDA -if (QUEST_ENABLE_CUDA) - - # make nvcc use user cxx-compiler as default host (before cuda-host is set below) - if (NOT DEFINED CMAKE_CUDA_HOST_COMPILER) - set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER}) - endif() - - enable_language(CUDA) - set(CMAKE_CUDA_STANDARD_REQUIRED ON) - set(CUDA_PROPAGATE_HOST_FLAGS OFF) - - set_property(TARGET QuEST PROPERTY CUDA_STANDARD 20) - - # force MSVC to use the modern preprocessor - if (MSVC) - target_compile_options(QuEST PRIVATE - $<$:/Zc:preprocessor> - $<$:-Xcompiler=/Zc:preprocessor> - ) - endif() - -endif() - - -# HIP -if (QUEST_ENABLE_HIP) - - # if generation fails (hip::amdhip64 not found), users can try setting - # CMAKE_MODULE_PATH to '/opt/rocm/cmake' or '/opt/rocm/hip/lib/cmake/hip' - # (suitable when shared library libamdhip64.so is located in /opt/rocm/lib/ - # or /opt/rocm/hip/lib/ respectively). We avoid setting CMAKE_MODULE_PATH - # pre-emptively since it made successful generation less likely in our tests! - # example: list(APPEND CMAKE_MODULE_PATH "/opt/rocm/cmake"). Users should - # also add '/opt/rocm/bin' or '/opt/rocm/hip/bin' to their $PATH env-var. - - enable_language(HIP) - set(CMAKE_HIP_STANDARD_REQUIRED ON) - set_property(TARGET QuEST PROPERTY HIP_STANDARD 20) - - find_package(HIP REQUIRED) - message(STATUS "Found HIP: " ${HIP_VERSION}) - - target_link_libraries(QuEST PRIVATE hip::host) - -endif() - - -# cuQuantum -if (QUEST_ENABLE_CUQUANTUM) - find_package(CUQUANTUM REQUIRED) - target_link_libraries(QuEST PRIVATE CUQUANTUM::cuStateVec) - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) -endif() - - -# Checkpointing (ADIOS2) -if (QUEST_ENABLE_ADIOS2) - - find_package(adios2 QUIET) - - # A distributed QuEST needs an MPI-enabled ADIOS2 (which provides the - # adios2::cxx_mpi target). A serial system install lacks it, so in that case we - # ignore the found package and fetch an MPI-enabled build instead of failing. - set(quest_use_found_adios2 ${adios2_FOUND}) - if (adios2_FOUND AND QUEST_ENABLE_MPI AND NOT TARGET adios2::cxx_mpi) - message(STATUS "Found ADIOS2 lacks MPI support (no adios2::cxx_mpi target); fetching an MPI-enabled build instead") - set(quest_use_found_adios2 FALSE) - endif() - - if(NOT quest_use_found_adios2 AND QUEST_DOWNLOAD_ADIOS2) - message(STATUS "fetching ADIOS2 via FetchContent") - - include(FetchContent) - FetchContent_Declare( - adios2 - GIT_REPOSITORY https://github.com/ornladios/ADIOS2.git - GIT_TAG v2.12.1 - ) - - # Match ADIOS2's MPI to QuEST's so distributed runs write per-rank slices - # into one shared file. ADIOS2's CUDA support is deliberately left OFF: - # checkpointing copies amps to host memory (syncQuregFromGpu/syncQuregToGpu) - # before any I/O, so ADIOS2 never touches device pointers. Building it with - # CUDA is unnecessary and stalls the Windows CUDA CI job. - set(ADIOS2_USE_MPI ${QUEST_ENABLE_MPI} CACHE BOOL "" FORCE) - set(ADIOS2_USE_CUDA OFF CACHE BOOL "" FORCE) - - # Forego unused facilities - set(ADIOS2_BUILD_TESTING OFF CACHE BOOL "" FORCE) - set(ADIOS2_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_SODIUM OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_Fortran OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_HDF5 OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_ZeroMQ OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_SST OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_DataMan OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_SSC OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_MHS OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_DAOS OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_MGARD OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_BZip2 OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_Blosc OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_Blosc2 OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_SZ OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_ZFP OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_PNG OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_Profiling OFF CACHE BOOL "" FORCE) - set(ADIOS2_USE_Python OFF CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(adios2) - - else() - # re-run non-QUIET so configuration fails with a clear error if the package - # somehow became unavailable between the two calls - find_package(adios2 REQUIRED) - endif() - - # In distributed builds link ADIOS2's MPI-enabled C++ interface: it defines - # ADIOS2_USE_MPI, which exposes the adios2::ADIOS(MPI_Comm) constructor used in - # qureg.cpp for collective per-rank I/O. The serial target lacks it. - if (QUEST_ENABLE_MPI) - target_link_libraries(QuEST PRIVATE adios2::cxx_mpi) - else() - target_link_libraries(QuEST PRIVATE adios2::cxx) - endif() - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) -endif() - +include(cmake/QuESTDependencies.cmake) # BMI2 (flag not necessary when unrecognised) if (QUEST_ENABLE_BMI2 AND _quest_cxx_recognises_bmi2) @@ -741,7 +546,7 @@ set(QUEST_DISABLE_DEPRECATION_WARNINGS ${QUEST_DISABLE_DEPRECATION_WARNINGS}) # add math library if (NOT MSVC) - target_link_libraries(QuEST PRIVATE ${MATH_LIBRARY}) + target_link_libraries(QuEST PRIVATE m) endif() @@ -759,56 +564,27 @@ add_subdirectory(quest) # ============================ -# min example is always built -add_executable(min_example - examples/tutorials/min_example.c -) -target_link_libraries(min_example PRIVATE QuEST::QuEST) +include(cmake/QuESTRpath.cmake) +setup_quest_rpath(QuEST "${CMAKE_INSTALL_LIBDIR}") +set(QUEST_HAVE_INSTALLABLE_EXAMPLES OFF) -if (QUEST_ENABLE_MPI AND QUEST_ENABLE_SUBCOMM) - target_link_libraries(min_example PRIVATE MPI::MPI_CXX) +if(QUEST_BUILD_MIN_EXAMPLE) + add_executable(min_example examples/tutorials/min_example.c) + target_link_libraries(min_example PRIVATE QuEST::QuEST) + setup_quest_rpath(min_example "${CMAKE_INSTALL_BINDIR}") + if(QUEST_ENABLE_INSTALL AND QUEST_INSTALL_BINARIES) + install(TARGETS min_example RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT Examples) + set(QUEST_HAVE_INSTALLABLE_EXAMPLES ON) + endif() endif() -if (QUEST_INSTALL_BINARIES) - install(TARGETS min_example - RUNTIME - DESTINATION ${CMAKE_INSTALL_BINDIR} - ) -endif () - - -# all examples optionally built -if (QUEST_BUILD_EXAMPLES) +if(QUEST_BUILD_EXAMPLES) add_subdirectory(examples) + if(QUEST_ENABLE_INSTALL AND QUEST_INSTALL_BINARIES) + set(QUEST_HAVE_INSTALLABLE_EXAMPLES ON) + endif() endif() - -## RPATH -set(BUILD_RPATH_USE_ORIGIN ON) -if(APPLE) - set(_RPATH_ORIGIN "@loader_path") -else() - set(_RPATH_ORIGIN "$ORIGIN") -endif() - -set(_INSTALL_RPATH "${_RPATH_ORIGIN}/../${CMAKE_INSTALL_LIBDIR}") -set(_BUILD_RPATH "${_RPATH_ORIGIN};$") - -# A tiny helper function so you can call it for every target -function(setup_quest_rpath tgt) - set_target_properties(${tgt} PROPERTIES - BUILD_RPATH "${_BUILD_RPATH}" - INSTALL_RPATH "${_INSTALL_RPATH}" - # keeps RPATH from being stripped when installing - INSTALL_RPATH_USE_LINK_PATH TRUE - ) -endfunction() - -setup_quest_rpath(QuEST) -setup_quest_rpath(min_example) - - - # ============================ # User source # ============================ @@ -830,11 +606,12 @@ if (USER_SOURCE_NAMES AND USER_OUTPUT_EXE_NAME) add_executable(${USER_OUTPUT_EXE_NAME} ${USER_SOURCE_NAMES}) target_link_libraries(${USER_OUTPUT_EXE_NAME} PUBLIC QuEST) - if (QUEST_INSTALL_BINARIES) - install(TARGETS ${USER_OUTPUT_EXE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + if (QUEST_ENABLE_INSTALL AND QUEST_INSTALL_BINARIES) + set(QUEST_HAVE_INSTALLABLE_EXAMPLES ON) + install(TARGETS ${USER_OUTPUT_EXE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Examples) endif() - setup_quest_rpath(${USER_OUTPUT_EXE_NAME}) + setup_quest_rpath(${USER_OUTPUT_EXE_NAME} "${CMAKE_INSTALL_BINDIR}") endif() @@ -884,66 +661,9 @@ endif() # ============================ -install(TARGETS QuEST - EXPORT QuESTTargets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - - -# Write CMake version file for QuEST -set(quest_install_config_dir "${CMAKE_INSTALL_LIBDIR}/cmake/QuEST") - - -# Write QuESTConfigVersion.cmake -write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/${QUEST_OUTPUT_LIB_NAME}ConfigVersion.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion -) - - -# Configure QuESTConfig.cmake (from template) -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/QuESTConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/${QUEST_OUTPUT_LIB_NAME}Config.cmake" - INSTALL_DESTINATION "${quest_install_config_dir}" -) - - -# Install them -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/${QUEST_OUTPUT_LIB_NAME}Config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${QUEST_OUTPUT_LIB_NAME}ConfigVersion.cmake" - DESTINATION "${quest_install_config_dir}" -) - -install(FILES - "${CMAKE_CURRENT_SOURCE_DIR}/quest/include/quest.h" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" -) - -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/quest/include/config.h" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/quest/include" -) - -install( - DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/quest/include" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/quest" - FILES_MATCHING PATTERN "*.h" - PATTERN "quest.h" EXCLUDE -) - -install( - EXPORT QuESTTargets - FILE "${QUEST_OUTPUT_LIB_NAME}Targets.cmake" - NAMESPACE QuEST:: - DESTINATION "${quest_install_config_dir}" -) - -if(PROJECT_IS_TOP_LEVEL) - include(CPack) -endif () - +if(QUEST_ENABLE_INSTALL) + include(cmake/QuESTInstall.cmake) +endif() +if(QUEST_ENABLE_PACKAGING) + include(cmake/QuESTPackaging.cmake) +endif() diff --git a/LICENCE.txt b/LICENCE.txt index 132a1f2f8..32efd108a 100644 --- a/LICENCE.txt +++ b/LICENCE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 The QuEST Authors and Contributors +Copyright (c) 2017-2026 The QuEST Authors and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9e33ea763..9c0e68f3d 100644 --- a/README.md +++ b/README.md @@ -229,23 +229,17 @@ To rocket right in, download QuEST with [git](https://git-scm.com/) at the termi git clone https://github.com/quest-kit/QuEST.git cd QuEST ``` -We recommend working in a `build` directory: +Compile the [minimum example](/examples/tutorials/min_example.c) in a separate build directory using [CMake 3.28 or newer](https://cmake.org/): ```bash -mkdir build -cd build -``` - -Compile the [minimum example](/examples/tutorials/min_example.c) using [cmake](https://cmake.org/): -```bash -cmake .. -make +cmake -S . -B build +cmake --build build ``` then run it with ```bash -./min_example +./build/min_example ``` -See the [docs](docs/README.md) for enabling acceleration and running the unit tests. +Installable builds export the canonical CMake target `QuEST::QuEST`. Downstream projects use `find_package(QuEST CONFIG REQUIRED)` and link that target; see the [CMake guide](docs/cmake.md) for installation, dependency, and packaging details. See the [docs](docs/README.md) for enabling acceleration and running the unit tests. --------------------------------- diff --git a/cmake/FindCUQUANTUM.cmake b/cmake/FindCUQUANTUM.cmake index 099306c6f..80fe4d060 100644 --- a/cmake/FindCUQUANTUM.cmake +++ b/cmake/FindCUQUANTUM.cmake @@ -1,110 +1,182 @@ #[=======================================================================[.rst: -FindCuQuantum -------------- - -Attempts to find NVIDIA's cuQuantum library. -Use CUQUANTUM_ROOT or CUQUANTUM_DIR to specify the prefix path. -@author Oliver Thomson Brown - - -Result Variables -^^^^^^^^^^^^^^^^ - -This will define the following variables: +FindCUQUANTUM +------------ +Find the shared NVIDIA cuQuantum libraries (CMake 3.28 or newer). -``CUQUANTUM_FOUND`` -True if libcuquantum is found. -``CUQUANTUM_INCLUDE_DIRS`` -Include directories needed to use cuQuantum. -``CUQUANTUM_LIBRARIES`` -Libraries needed to link to cuQuantum. -``CUQUANTUM_LIBRARY_DIRS`` -Location of libraries needed to link to cuQuantum. +@author Oliver Thomson Brown +Components are ``cuStateVec``, ``cuTensorNet`` and ``cuDensityMat``; a call +without components requests all three, required. Targets retain these names +under ``CUQUANTUM::``. ``CUQUANTUM::cuQuantum`` aggregates successfully found +components across calls. No CUDA language is needed. + +Searches use normal CMake roots, environment roots, CMAKE_PREFIX_PATH and +cross-compilation rules. An explicitly supplied ``CUQUANTUM_DIR`` is a legacy +prefix hint searched first (cached artifact overrides still take precedence). +The environment ``CUQUANTUM_DIR`` is a fallback hint, never copied into that +variable. Only shared libraries are supported, not the SDK's _static archives. + +Results: ``CUQUANTUM_FOUND``, ``CUQUANTUM__FOUND``, +``CUQUANTUM__INCLUDE_DIR``, ``CUQUANTUM__LIBRARY`` and +``CUQUANTUM__VERSION``. Versions describe individual components, +not the SDK release; package-level version requests are unsupported. +Legacy ``CUQUANTUM_INCLUDE_PATH``, ``CUQUANTUM_INCLUDE_DIRS``, +``CUQUANTUM_LIBRARIES`` and ``CUQUANTUM_LIBRARY_DIRS`` contain only successfully +resolved requested components; LIBRARIES contains imported target names. + +cuTensorNet and cuDensityMat require cuTENSOR (``CUTENSOR_ROOT`` is supported). +See https://docs.nvidia.com/cuda/cuquantum/latest/getting-started/index.html. #]=======================================================================] - include(FindPackageHandleStandardArgs) -find_package(PkgConfig QUIET) - -# CUQUANTUM_DIR is the CMake standard, but cuQuantum uses CUQUANTUM_ROOT -# so we'll check if that's defined first -# A user supplied CUQUANTUM_DIR always takes precedence -if(NOT DEFINED CUQUANTUM_DIR) - if(DEFINED ENV{CUQUANTUM_ROOT}) - set(CUQUANTUM_DIR $ENV{CUQUANTUM_ROOT}) +function(_cuquantum_artifacts component stem) + # Keep suffix restrictions local, including when a parent prefers static libs. + if(WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .dll.a) + elseif(APPLE) + set(CMAKE_FIND_LIBRARY_SUFFIXES .dylib .so) else() - set(CUQUANTUM_DIR $ENV{CUQUANTUM_DIR}) + set(CMAKE_FIND_LIBRARY_SUFFIXES .so) endif() + if(CUQUANTUM_DIR) + find_path(CUQUANTUM_${component}_INCLUDE_DIR NAMES ${stem}.h + PATHS "${CUQUANTUM_DIR}" PATH_SUFFIXES include NO_DEFAULT_PATH) + find_library(CUQUANTUM_${component}_LIBRARY NAMES ${stem} + PATHS "${CUQUANTUM_DIR}" PATH_SUFFIXES lib lib64 NO_DEFAULT_PATH) + endif() + find_path(CUQUANTUM_${component}_INCLUDE_DIR NAMES ${stem}.h + HINTS ENV CUQUANTUM_DIR PATH_SUFFIXES include) + find_library(CUQUANTUM_${component}_LIBRARY NAMES ${stem} + HINTS ENV CUQUANTUM_DIR PATH_SUFFIXES lib lib64) + mark_as_advanced(CUQUANTUM_${component}_INCLUDE_DIR CUQUANTUM_${component}_LIBRARY) + set(version "") + if(EXISTS "${CUQUANTUM_${component}_INCLUDE_DIR}/${stem}.h") + string(TOUPPER "${stem}" macro) + if(component STREQUAL "cuStateVec") + string(APPEND macro "_VER") + endif() + file(STRINGS "${CUQUANTUM_${component}_INCLUDE_DIR}/${stem}.h" lines + REGEX "^#[ \t]*define[ \t]+${macro}_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") + foreach(field IN ITEMS MAJOR MINOR PATCH) + set(${field} "") + foreach(line IN LISTS lines) + if(line MATCHES "${macro}_${field}[ \t]+([0-9]+)") + set(${field} "${CMAKE_MATCH_1}") + endif() + endforeach() + endforeach() + if(NOT MAJOR STREQUAL "" AND NOT MINOR STREQUAL "" AND NOT PATCH STREQUAL "") + set(version "${MAJOR}.${MINOR}.${PATCH}") + endif() + endif() + set(CUQUANTUM_${component}_VERSION "${version}" PARENT_SCOPE) +endfunction() + +set(_cuquantum_known cuStateVec cuTensorNet cuDensityMat) +if(NOT CUQUANTUM_FIND_COMPONENTS) + set(CUQUANTUM_FIND_COMPONENTS ${_cuquantum_known}) + foreach(_component IN LISTS _cuquantum_known) + set(CUQUANTUM_FIND_REQUIRED_${_component} TRUE) + endforeach() endif() - -if (NOT CUQUANTUM_FOUND) - # Until cuQuantum exports pkgconfig files or a CMake target - # we're going to have to do this the hard way... - - # Look for custatevec.h in an include directory below CUQUANTUM_DIR - # (or CUQUANTUM_ROOT) - find_path(CUQUANTUM_INCLUDE_PATH - NAMES - custatevec.h - cudensitymat.h - cutensornet.h - PATHS - ${CUQUANTUM_DIR}/include - ) - - set(CUQUANTUM_INCLUDE_DIRS "${CUQUANTUM_INCLUDE_PATH}") - - find_path(CUQUANTUM_LIBRARY_DIRS - NAMES - libcustatevec.so - libcudensitymat.so - libcutensornet.so - PATHS - ${CUQUANTUM_DIR}/lib - ${CUQUANTUM_DIR}/lib64 - ) - - if(CUQUANTUM_LIBRARY_DIRS) - set(CUQUANTUM_LIBRARIES "custatevec;cudensitymat;cutensornet") - endif() +set(_cuquantum_needed ${CUQUANTUM_FIND_COMPONENTS}) +if("cuDensityMat" IN_LIST _cuquantum_needed) + list(APPEND _cuquantum_needed cuTensorNet) endif() - -find_package_handle_standard_args(CUQUANTUM - REQUIRED_VARS - CUQUANTUM_INCLUDE_DIRS - CUQUANTUM_LIBRARIES - CUQUANTUM_LIBRARY_DIRS - REASON_FAILURE_MESSAGE - "Try setting CUQUANTUM_DIR or CUQUANTUM_ROOT. Current values shown below. - CUQUANTUM_DIR=${CUQUANTUM_DIR} - CUQUANTUM_ROOT=${CUQUANTUM_ROOT}" -) - -if(CUQUANTUM_FOUND AND NOT TARGET CUQUANTUM::cuQuantum) - add_library(CUQUANTUM::cuQuantum INTERFACE IMPORTED) - target_include_directories(CUQUANTUM::cuQuantum INTERFACE ${CUQUANTUM_INCLUDE_DIRS}) - target_link_libraries(CUQUANTUM::cuQuantum INTERFACE ${CUQUANTUM_LIBRARIES}) - target_link_directories(CUQUANTUM::cuQuantum INTERFACE ${CUQUANTUM_LIBRARY_DIRS}) - - if(NOT TARGET CUQUANTUM::cuStateVec) - add_library(CUQUANTUM::cuStateVec INTERFACE IMPORTED) - target_include_directories(CUQUANTUM::cuStateVec INTERFACE ${CUQUANTUM_INCLUDE_DIRS}) - target_link_directories(CUQUANTUM::cuStateVec INTERFACE ${CUQUANTUM_LIBRARY_DIRS}) - target_link_libraries(CUQUANTUM::cuStateVec INTERFACE custatevec) +# Dependencies must precede dependents, regardless of caller ordering. +set(_cuquantum_reasons "") +set(_cuquantum_has_known FALSE) +foreach(_component IN LISTS _cuquantum_needed) + set(CUQUANTUM_${_component}_FOUND FALSE) + if(_component IN_LIST _cuquantum_known) + set(_cuquantum_has_known TRUE) + else() + list(APPEND _cuquantum_reasons "Unknown component '${_component}'") + endif() +endforeach() +if(_cuquantum_has_known) + find_package(CUDAToolkit QUIET) +endif() +if("cuTensorNet" IN_LIST _cuquantum_needed) + find_package(CUTENSOR QUIET MODULE) +endif() +foreach(_component IN LISTS _cuquantum_known) + if(NOT _component IN_LIST _cuquantum_needed) + continue() endif() - - if(NOT TARGET CUQUANTUM::cuDensityMat) - add_library(CUQUANTUM::cuDensityMat INTERFACE IMPORTED) - target_include_directories(CUQUANTUM::cuDensityMat INTERFACE ${CUQUANTUM_INCLUDE_DIRS}) - target_link_directories(CUQUANTUM::cuDensityMat INTERFACE ${CUQUANTUM_LIBRARY_DIRS}) - target_link_libraries(CUQUANTUM::cuDensityMat INTERFACE cudensitymat) + string(TOLOWER "${_component}" _stem) + _cuquantum_artifacts("${_component}" "${_stem}") + set(_deps CUDA::toolkit CUDA::cublas) + if(_component STREQUAL "cuStateVec") + list(APPEND _deps CUDA::cublasLt) + elseif(_component STREQUAL "cuTensorNet") + list(APPEND _deps CUDA::cusolver CUTENSOR::cutensor) + else() + list(APPEND _deps CUDA::cusolver CUDA::cublasLt CUDA::curand CUDA::cusparse + CUTENSOR::cutensor CUQUANTUM::cuTensorNet) + if(CUDAToolkit_VERSION VERSION_GREATER_EQUAL 12) + list(APPEND _deps CUDA::nvJitLink) + endif() + endif() + set(_ready TRUE) + foreach(_dep IN LISTS _deps) + if(NOT TARGET "${_dep}") + set(_ready FALSE) + list(APPEND _cuquantum_reasons "${_component} requires ${_dep}") + endif() + endforeach() + if(NOT CUQUANTUM_${_component}_INCLUDE_DIR OR NOT CUQUANTUM_${_component}_LIBRARY + OR NOT EXISTS "${CUQUANTUM_${_component}_INCLUDE_DIR}/${_stem}.h" + OR NOT EXISTS "${CUQUANTUM_${_component}_LIBRARY}" + OR CUQUANTUM_${_component}_LIBRARY MATCHES "(_static\\.|\\.a$)") + set(_ready FALSE) + list(APPEND _cuquantum_reasons "${_component} requires its header and shared library") + endif() + if(_component STREQUAL "cuDensityMat" AND NOT CUQUANTUM_cuTensorNet_FOUND) + set(_ready FALSE) + endif() + set(CUQUANTUM_${_component}_FOUND ${_ready}) + if(_ready AND NOT TARGET CUQUANTUM::${_component}) + add_library(CUQUANTUM::${_component} UNKNOWN IMPORTED) + set_target_properties(CUQUANTUM::${_component} PROPERTIES + IMPORTED_LOCATION "${CUQUANTUM_${_component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CUQUANTUM_${_component}_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${_deps}") + endif() +endforeach() + +set(CUQUANTUM_INCLUDE_DIRS "") +set(CUQUANTUM_LIBRARIES "") +set(CUQUANTUM_LIBRARY_DIRS "") +foreach(_component IN LISTS CUQUANTUM_FIND_COMPONENTS) + if(CUQUANTUM_${_component}_FOUND) + list(APPEND CUQUANTUM_INCLUDE_DIRS "${CUQUANTUM_${_component}_INCLUDE_DIR}") + list(APPEND CUQUANTUM_LIBRARIES "CUQUANTUM::${_component}") + get_filename_component(_libdir "${CUQUANTUM_${_component}_LIBRARY}" DIRECTORY) + list(APPEND CUQUANTUM_LIBRARY_DIRS "${_libdir}") + endif() +endforeach() +list(REMOVE_DUPLICATES CUQUANTUM_INCLUDE_DIRS) +list(REMOVE_DUPLICATES CUQUANTUM_LIBRARY_DIRS) +set(CUQUANTUM_INCLUDE_PATH "${CUQUANTUM_INCLUDE_DIRS}") +set(_cuquantum_version_supported TRUE) +if(CUQUANTUM_FIND_VERSION) + set(_cuquantum_version_supported FALSE) + list(APPEND _cuquantum_reasons "No overall SDK version is available: inspect CUQUANTUM__VERSION") +endif() +list(JOIN _cuquantum_reasons ". " _cuquantum_reason) +find_package_handle_standard_args(CUQUANTUM HANDLE_COMPONENTS + REQUIRED_VARS _cuquantum_version_supported + REASON_FAILURE_MESSAGE "${_cuquantum_reason}. Set CUQUANTUM_ROOT (or legacy CUQUANTUM_DIR), CUDAToolkit_ROOT and, for tensor components, CUTENSOR_ROOT.") +if(CUQUANTUM_FOUND) + if(NOT TARGET CUQUANTUM::cuQuantum) + add_library(CUQUANTUM::cuQuantum INTERFACE IMPORTED) endif() - - if(NOT TARGET CUQUANTUM::cuTensorNet) - add_library(CUQUANTUM::cuTensorNet INTERFACE IMPORTED) - target_include_directories(CUQUANTUM::cuTensorNet INTERFACE ${CUQUANTUM_INCLUDE_DIRS}) - target_link_directories(CUQUANTUM::cuTensorNet INTERFACE ${CUQUANTUM_LIBRARY_DIRS}) - target_link_libraries(CUQUANTUM::cuTensorNet INTERFACE cutensornet) + get_target_property(_aggregate CUQUANTUM::cuQuantum INTERFACE_LINK_LIBRARIES) + if(NOT _aggregate) + set(_aggregate "") endif() + list(APPEND _aggregate ${CUQUANTUM_LIBRARIES}) + list(REMOVE_DUPLICATES _aggregate) + set_target_properties(CUQUANTUM::cuQuantum PROPERTIES INTERFACE_LINK_LIBRARIES "${_aggregate}") endif() diff --git a/cmake/FindCUTENSOR.cmake b/cmake/FindCUTENSOR.cmake new file mode 100644 index 000000000..82c782a2c --- /dev/null +++ b/cmake/FindCUTENSOR.cmake @@ -0,0 +1,44 @@ +#[=======================================================================[.rst: +FindCUTENSOR +------------ +Find the shared cuTENSOR library and header using normal CMake search rules, +including CUTENSOR_ROOT and its environment equivalent. Defines CUTENSOR_FOUND, +CUTENSOR_INCLUDE_DIR, CUTENSOR_LIBRARY and CUTENSOR::cutensor. Does not enable +CUDA or accept the SDK's static archives. +#]=======================================================================] +include(FindPackageHandleStandardArgs) +function(_cutensor_find_artifacts) + if(WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .dll.a) + elseif(APPLE) + set(CMAKE_FIND_LIBRARY_SUFFIXES .dylib .so) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .so) + endif() + find_path(CUTENSOR_INCLUDE_DIR NAMES cutensor.h PATH_SUFFIXES include) + # NVIDIA archives separate CUDA major variants beneath lib/12 or lib/13. + string(REGEX MATCH "^[0-9]+" _cuda_major "${CUDAToolkit_VERSION}") + find_library(CUTENSOR_LIBRARY NAMES cutensor + PATH_SUFFIXES "lib/${_cuda_major}" lib lib64) +endfunction() +find_package(CUDAToolkit QUIET) +_cutensor_find_artifacts() +set(_CUTENSOR_CUDA_FOUND FALSE) +if(TARGET CUDA::toolkit) + set(_CUTENSOR_CUDA_FOUND TRUE) +endif() +set(_CUTENSOR_ARTIFACTS_VALID FALSE) +if(EXISTS "${CUTENSOR_INCLUDE_DIR}/cutensor.h" AND EXISTS "${CUTENSOR_LIBRARY}" + AND NOT CUTENSOR_LIBRARY MATCHES "(_static\\.|\\.a$)") + set(_CUTENSOR_ARTIFACTS_VALID TRUE) +endif() +find_package_handle_standard_args(CUTENSOR REQUIRED_VARS + CUTENSOR_INCLUDE_DIR CUTENSOR_LIBRARY _CUTENSOR_CUDA_FOUND _CUTENSOR_ARTIFACTS_VALID) +mark_as_advanced(CUTENSOR_INCLUDE_DIR CUTENSOR_LIBRARY) +if(CUTENSOR_FOUND AND NOT TARGET CUTENSOR::cutensor) + add_library(CUTENSOR::cutensor UNKNOWN IMPORTED) + set_target_properties(CUTENSOR::cutensor PROPERTIES + IMPORTED_LOCATION "${CUTENSOR_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CUTENSOR_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "CUDA::toolkit") +endif() diff --git a/cmake/FindNUMA.cmake b/cmake/FindNUMA.cmake new file mode 100644 index 000000000..da2a486c7 --- /dev/null +++ b/cmake/FindNUMA.cmake @@ -0,0 +1,21 @@ +# Find libnuma without requiring pkg-config on the consuming machine. +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(PC_NUMA QUIET numa) +endif() +find_path(NUMA_INCLUDE_DIR NAMES numa.h HINTS ${PC_NUMA_INCLUDE_DIRS}) +find_library(NUMA_LIBRARY NAMES numa HINTS ${PC_NUMA_LIBRARY_DIRS}) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NUMA REQUIRED_VARS NUMA_INCLUDE_DIR NUMA_LIBRARY) +mark_as_advanced(NUMA_INCLUDE_DIR NUMA_LIBRARY) +if(NUMA_FOUND AND NOT TARGET NUMA::NUMA) + add_library(NUMA::NUMA UNKNOWN IMPORTED) + set_target_properties(NUMA::NUMA PROPERTIES + IMPORTED_LOCATION "${NUMA_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${NUMA_INCLUDE_DIR}") + if(NUMA_LIBRARY MATCHES "\\.a$") + set(_numa_dependencies ${PC_NUMA_STATIC_LIBRARIES}) + list(REMOVE_ITEM _numa_dependencies numa) + set_property(TARGET NUMA::NUMA PROPERTY INTERFACE_LINK_LIBRARIES "${_numa_dependencies}") + endif() +endif() diff --git a/cmake/QuESTCPackMPI.cmake.in b/cmake/QuESTCPackMPI.cmake.in new file mode 100644 index 000000000..1d552c8d4 --- /dev/null +++ b/cmake/QuESTCPackMPI.cmake.in @@ -0,0 +1,75 @@ +# Stock native profiles describe the distro's OpenMPI, not arbitrary MPI SDKs. +# Validate the selected artifact rather than whichever mpicc happens to be on PATH. +if(NOT CPACK_QUEST_ENABLE_MPI OR NOT _quest_stock_profile) + return() +endif() +set(_quest_mpi_runtime_found FALSE) +set(_quest_mpi_capabilities) +set(_quest_mpi_exclusions) +foreach(_quest_library IN LISTS CPACK_QUEST_MPI_LIBRARIES) + if(NOT _quest_library MATCHES "(^|/)libmpi[^/]*[.]so") + continue() + endif() + file(REAL_PATH "${_quest_library}" _quest_library_real) + if(CPACK_GENERATOR STREQUAL "DEB") + find_program(_quest_dpkg_query NAMES dpkg-query REQUIRED) + execute_process(COMMAND "${_quest_dpkg_query}" -S "${_quest_library_real}" + RESULT_VARIABLE _quest_query_result OUTPUT_VARIABLE _quest_owner ERROR_QUIET) + if(NOT _quest_query_result EQUAL 0 OR NOT _quest_owner MATCHES "^libopenmpi[^: ]*(:[^: ]+)?: ") + message(FATAL_ERROR "The stock Ubuntu profile requires distro OpenMPI; selected ${_quest_library_real} is not owned by an OpenMPI package. Use QUEST_NATIVE_PACKAGE_PROFILE=custom with complete dependency metadata for another MPI.") + endif() + else() + find_program(_quest_rpm_query NAMES rpm REQUIRED) + execute_process(COMMAND "${_quest_rpm_query}" -qf "${_quest_library_real}" --queryformat "%{NAME}" + RESULT_VARIABLE _quest_query_result OUTPUT_VARIABLE _quest_owner ERROR_QUIET) + if(NOT _quest_query_result EQUAL 0 OR NOT _quest_owner STREQUAL "openmpi") + message(FATAL_ERROR "The stock Fedora profile requires distro OpenMPI; selected ${_quest_library_real} is not owned by openmpi. Use QUEST_NATIVE_PACKAGE_PROFILE=custom with complete dependency metadata for another MPI.") + endif() + execute_process(COMMAND "${_quest_rpm_query}" -qf "${_quest_library_real}" --provides + RESULT_VARIABLE _quest_query_result OUTPUT_VARIABLE _quest_provides ERROR_QUIET) + if(NOT _quest_query_result EQUAL 0) + message(FATAL_ERROR "Unable to query the selected Fedora OpenMPI RPM capabilities.") + endif() + get_filename_component(_quest_stem "${_quest_library}" NAME) + string(REGEX REPLACE "[.]so.*" "" _quest_stem "${_quest_stem}") + string(REPLACE "\n" ";" _quest_provides "${_quest_provides}") + set(_quest_abi_found FALSE) + foreach(_quest_capability IN LISTS _quest_provides) + if(_quest_capability MATCHES "^${_quest_stem}[.]so[.][^ ]*[(]openmpi-[^)]+[)]$") + list(APPEND _quest_mpi_capabilities "${_quest_capability}") + set(_quest_abi_found TRUE) + endif() + endforeach() + if(NOT _quest_abi_found) + message(FATAL_ERROR "The selected Fedora OpenMPI RPM does not expose a qualified ${_quest_stem} ABI capability.") + endif() + list(APPEND _quest_mpi_exclusions "^${_quest_stem}[.]so[.].*$") + endif() + set(_quest_mpi_runtime_found TRUE) +endforeach() +if(NOT _quest_mpi_runtime_found) + message(FATAL_ERROR "Stock native MPI packages require selected distro OpenMPI shared artifacts in MPI_CXX_LIBRARIES. Use QUEST_NATIVE_PACKAGE_PROFILE=custom with complete dependency metadata for another MPI.") +endif() +if(CPACK_GENERATOR STREQUAL "RPM") + # Fedora namespaces OpenMPI capabilities. Its automatic MPI generator only + # qualifies files in MPI_HOME, while QuEST uses the normal GNU bin/lib dirs. + # Replace only the unqualified MPI requirement; leave all other scanning on. + list(REMOVE_DUPLICATES _quest_mpi_capabilities) + list(REMOVE_DUPLICATES _quest_mpi_exclusions) + list(JOIN _quest_mpi_capabilities ", " _quest_mpi_requires) + list(JOIN _quest_mpi_exclusions "|" _quest_mpi_exclude) + string(APPEND CPACK_RPM_SPEC_MORE_DEFINE + "\n%global __requires_exclude %{?__requires_exclude:%{__requires_exclude}|}${_quest_mpi_exclude}\n") + foreach(_quest_component Runtime Examples) + if(NOT _quest_component IN_LIST CPACK_QUEST_COMPONENTS) + continue() + endif() + string(TOUPPER "${_quest_component}" _quest_upper) + set(_quest_var "CPACK_RPM_${_quest_upper}_PACKAGE_REQUIRES") + if(DEFINED ${_quest_var} AND NOT "${${_quest_var}}" STREQUAL "") + string(APPEND ${_quest_var} ", ${_quest_mpi_requires}") + else() + set(${_quest_var} "${_quest_mpi_requires}") + endif() + endforeach() +endif() diff --git a/cmake/QuESTCPackOptions.cmake.in b/cmake/QuESTCPackOptions.cmake.in new file mode 100644 index 000000000..17215071b --- /dev/null +++ b/cmake/QuESTCPackOptions.cmake.in @@ -0,0 +1,168 @@ +# Evaluated once per requested generator by CPack, not during normal configuration. +if(CPACK_QUEST_USER_PROJECT_CONFIG) + include("${CPACK_QUEST_USER_PROJECT_CONFIG}") +endif() +macro(_quest_cpack_default name value) + if(NOT DEFINED ${name}) + set(${name} "${value}") + endif() +endmacro() +set(CPACK_COMPONENTS_ALL "${CPACK_QUEST_COMPONENTS}") +# Source packages retain their source name and never apply native binary policy. +if(CPACK_INSTALL_CMAKE_PROJECTS) + if(CPACK_QUEST_DEFAULT_NAME AND CPACK_PACKAGE_FILE_NAME STREQUAL CPACK_QUEST_DEFAULT_NAME) + if(CPACK_BUILD_CONFIG) + set(_quest_config "${CPACK_BUILD_CONFIG}") + elseif(CPACK_QUEST_BUILD_CONFIG) + set(_quest_config "${CPACK_QUEST_BUILD_CONFIG}") + elseif(CPACK_QUEST_MULTI_CONFIG) + message(FATAL_ERROR "Specify cpack -C for this multi-configuration build.") + else() + set(_quest_config "NoConfig") + endif() + set(CPACK_PACKAGE_FILE_NAME "${CPACK_QUEST_NAME_PREFIX}-${_quest_config}-${CPACK_QUEST_NAME_SUFFIX}") + endif() +else() + # A source tree is rooted at its archive directory, independent of install prefix. + set(CPACK_PACKAGING_INSTALL_PREFIX "/") + return() +endif() + +# Monolithic installation bypasses component selection and would bundle dependencies. +set(CPACK_MONOLITHIC_INSTALL OFF) +if(NOT CPACK_GENERATOR MATCHES "^(DEB|RPM)$") + if(CPACK_QUEST_DEFAULT_PREFIX AND CPACK_PACKAGING_INSTALL_PREFIX STREQUAL CPACK_QUEST_DEFAULT_PREFIX) + set(CPACK_PACKAGING_INSTALL_PREFIX "/") + endif() + _quest_cpack_default(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY ON) + # Component mode prevents unrelated dependency components entering archives. + set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) + set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + return() +endif() +if(NOT CPACK_QUEST_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR "QuEST native DEB/RPM profiles require Linux.") +endif() +if(NOT CPACK_QUEST_INSTALL_PREFIX STREQUAL "/usr" OR NOT CPACK_PACKAGING_INSTALL_PREFIX STREQUAL "/usr") + message(FATAL_ERROR "Native QuEST packages require configuring CMAKE_INSTALL_PREFIX=/usr and CPACK_PACKAGING_INSTALL_PREFIX=/usr.") +endif() +foreach(_quest_dir LIBDIR BINDIR INCLUDEDIR) + if(IS_ABSOLUTE "${CPACK_QUEST_INSTALL_${_quest_dir}}" OR CPACK_QUEST_INSTALL_${_quest_dir} MATCHES "(^|/)\\.\\.(/|$)") + message(FATAL_ERROR "Native QuEST packages require relative GNU installation directories (${_quest_dir}).") + endif() +endforeach() +if((CPACK_GENERATOR STREQUAL "DEB" AND CPACK_QUEST_NATIVE_PROFILE STREQUAL "ubuntu24.04") OR + (CPACK_GENERATOR STREQUAL "RPM" AND CPACK_QUEST_NATIVE_PROFILE STREQUAL "fedora44")) + set(_quest_stock_profile ON) +elseif(CPACK_QUEST_NATIVE_PROFILE STREQUAL "custom") + set(_quest_stock_profile OFF) +else() + message(FATAL_ERROR "Select a supported QUEST_NATIVE_PACKAGE_PROFILE: ubuntu24.04 for DEB, fedora44 for RPM, or custom with explicit dependency metadata.") +endif() +set(_quest_explicit_dependencies OFF) +if(NOT _quest_stock_profile OR NOT CPACK_QUEST_COMPILER_ID STREQUAL "GNU" OR + CPACK_QUEST_ENABLE_ADIOS2 OR CPACK_QUEST_ENABLE_CUDA OR CPACK_QUEST_ENABLE_HIP OR CPACK_QUEST_ENABLE_CUQUANTUM) + set(_quest_explicit_dependencies ON) +endif() +if(CPACK_GENERATOR STREQUAL "DEB") + set(_quest_prefix CPACK_DEBIAN) + set(_quest_depends PACKAGE_DEPENDS) +else() + set(_quest_prefix CPACK_RPM) + set(_quest_depends PACKAGE_REQUIRES) +endif() +if(_quest_explicit_dependencies) + foreach(_quest_component IN LISTS CPACK_QUEST_COMPONENTS) + string(TOUPPER "${_quest_component}" _quest_upper) + set(_quest_var "${_quest_prefix}_${_quest_upper}_${_quest_depends}") + if(NOT DEFINED ${_quest_var} OR "${${_quest_var}}" STREQUAL "") + message(FATAL_ERROR "This native QuEST variant requires explicit ${_quest_var} metadata for its external dependencies. Stock metadata supports GCC CPU builds only.") + endif() + endforeach() +endif() +include("${CPACK_QUEST_MPI_POLICY}") +set(CPACK_COMPONENTS_GROUPING IGNORE) + +if(CPACK_GENERATOR STREQUAL "DEB") + set(CPACK_DEB_COMPONENT_INSTALL ON) + _quest_cpack_default(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + _quest_cpack_default(CPACK_DEBIAN_PACKAGE_RELEASE "1") + _quest_cpack_default(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + _quest_cpack_default(CPACK_DEBIAN_PACKAGE_HOMEPAGE "${CPACK_PACKAGE_HOMEPAGE_URL}") + _quest_cpack_default(CPACK_DEBIAN_RUNTIME_PACKAGE_NAME libquest4) + _quest_cpack_default(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_NAME libquest-dev) + _quest_cpack_default(CPACK_DEBIAN_EXAMPLES_PACKAGE_NAME quest-examples) + _quest_cpack_default(CPACK_DEBIAN_RUNTIME_PACKAGE_SECTION libs) + _quest_cpack_default(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_SECTION libdevel) + _quest_cpack_default(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + _quest_cpack_default(CPACK_DEBIAN_RUNTIME_PACKAGE_GENERATE_SHLIBS ON) + _quest_cpack_default(CPACK_DEBIAN_RUNTIME_PACKAGE_GENERATE_SHLIBS_POLICY "=") + set(_quest_development "g++, cmake (>= 3.28)") + if(NOT CPACK_QUEST_BUILT_SHARED) + if(CPACK_QUEST_ENABLE_OMP) + string(APPEND _quest_development ", libgomp1") + endif() + if(CPACK_QUEST_ENABLE_NUMA) + string(APPEND _quest_development ", libnuma-dev") + endif() + endif() + if(CPACK_QUEST_ENABLE_MPI AND (NOT CPACK_QUEST_BUILT_SHARED OR CPACK_QUEST_ENABLE_SUBCOMM)) + string(APPEND _quest_development ", libopenmpi-dev") + endif() + _quest_cpack_default(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_DEPENDS "${_quest_development}") + set(_quest_version "${CPACK_DEBIAN_PACKAGE_VERSION}") + if(CPACK_DEBIAN_PACKAGE_RELEASE) + string(APPEND _quest_version "-${CPACK_DEBIAN_PACKAGE_RELEASE}") + endif() + if(CPACK_DEBIAN_PACKAGE_EPOCH) + set(_quest_version "${CPACK_DEBIAN_PACKAGE_EPOCH}:${_quest_version}") + endif() + set(_quest_runtime_constraint "${CPACK_DEBIAN_RUNTIME_PACKAGE_NAME} (= ${_quest_version})") +else() + set(CPACK_RPM_COMPONENT_INSTALL ON) + _quest_cpack_default(CPACK_RPM_FILE_NAME RPM-DEFAULT) + _quest_cpack_default(CPACK_RPM_PACKAGE_RELEASE "1") + _quest_cpack_default(CPACK_RPM_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + _quest_cpack_default(CPACK_RPM_PACKAGE_LICENSE MIT) + _quest_cpack_default(CPACK_RPM_PACKAGE_URL "${CPACK_PACKAGE_HOMEPAGE_URL}") + _quest_cpack_default(CPACK_RPM_RUNTIME_PACKAGE_NAME quest) + _quest_cpack_default(CPACK_RPM_DEVELOPMENT_PACKAGE_NAME quest-devel) + _quest_cpack_default(CPACK_RPM_EXAMPLES_PACKAGE_NAME quest-examples) + _quest_cpack_default(CPACK_RPM_PACKAGE_AUTOREQPROV ON) + # Standard filesystem directories belong to filesystem, never to QuEST. + _quest_cpack_default(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/lib64/cmake;/usr/lib/cmake;/usr/share/licenses") + set(_quest_development "gcc-c++, cmake >= 3.28") + if(NOT CPACK_QUEST_BUILT_SHARED) + if(CPACK_QUEST_ENABLE_OMP) + string(APPEND _quest_development ", libgomp") + endif() + if(CPACK_QUEST_ENABLE_NUMA) + string(APPEND _quest_development ", numactl-devel") + endif() + endif() + if(CPACK_QUEST_ENABLE_MPI AND (NOT CPACK_QUEST_BUILT_SHARED OR CPACK_QUEST_ENABLE_SUBCOMM)) + string(APPEND _quest_development ", openmpi-devel") + endif() + _quest_cpack_default(CPACK_RPM_DEVELOPMENT_PACKAGE_REQUIRES "${_quest_development}") + set(_quest_version "${CPACK_RPM_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}") + if(CPACK_RPM_PACKAGE_RELEASE_DIST) + string(APPEND _quest_version "%{?dist}") + endif() + if(CPACK_RPM_PACKAGE_EPOCH) + set(_quest_version "${CPACK_RPM_PACKAGE_EPOCH}:${_quest_version}") + endif() + set(_quest_runtime_constraint "${CPACK_RPM_RUNTIME_PACKAGE_NAME} = ${_quest_version}") +endif() +if(CPACK_QUEST_BUILT_SHARED) + foreach(_quest_component DEVELOPMENT EXAMPLES) + if(_quest_component STREQUAL "EXAMPLES" AND NOT CPACK_QUEST_HAVE_EXAMPLES) + continue() + endif() + set(_quest_var "${_quest_prefix}_${_quest_component}_${_quest_depends}") + if(DEFINED ${_quest_var} AND NOT "${${_quest_var}}" STREQUAL "") + string(APPEND ${_quest_var} ", ${_quest_runtime_constraint}") + else() + set(${_quest_var} "${_quest_runtime_constraint}") + endif() + endforeach() +endif() diff --git a/cmake/QuESTCPackStage.cmake.in b/cmake/QuESTCPackStage.cmake.in new file mode 100644 index 000000000..605952b2c --- /dev/null +++ b/cmake/QuESTCPackStage.cmake.in @@ -0,0 +1,8 @@ +# CMake 3.28's DEB scanner does not derive sibling component paths from RPATH. +# This hook runs after all components are staged, when CPack provides the actual +# staging directory. QuEST's own library is private to this temporary search; +# final package dependencies still come from native scanning and exact metadata. +if(CPACK_GENERATOR STREQUAL "DEB" AND CPACK_QUEST_BUILT_SHARED) + list(APPEND CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS + "${CPACK_TEMPORARY_DIRECTORY}/Runtime${CPACK_PACKAGING_INSTALL_PREFIX}/${CPACK_QUEST_INSTALL_LIBDIR}") +endif() diff --git a/cmake/QuESTCPackVerify.cmake.in b/cmake/QuESTCPackVerify.cmake.in new file mode 100644 index 000000000..4da2f2d14 --- /dev/null +++ b/cmake/QuESTCPackVerify.cmake.in @@ -0,0 +1,10 @@ +# Some CPackRPM versions return success after only a subset of rpmbuild calls +# succeed. Refuse an incomplete component set before publishing the artifacts. +if(CPACK_GENERATOR MATCHES "^(DEB|RPM)$") + list(LENGTH CPACK_QUEST_COMPONENTS _quest_expected_packages) + list(LENGTH CPACK_PACKAGE_FILES _quest_actual_packages) + if(_quest_actual_packages LESS _quest_expected_packages) + message(FATAL_ERROR + "QuEST native packaging produced ${_quest_actual_packages} files for ${_quest_expected_packages} components. Inspect the native packaging logs for failed components.") + endif() +endif() diff --git a/cmake/QuESTConfig.cmake.in b/cmake/QuESTConfig.cmake.in index 76f7ff3d6..3fbc82a80 100644 --- a/cmake/QuESTConfig.cmake.in +++ b/cmake/QuESTConfig.cmake.in @@ -2,4 +2,25 @@ # @author Luc Jaulmes (patched use of QUEST_OUTPUT_LIB_NAME) @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@QUEST_OUTPUT_LIB_NAME@Targets.cmake") +set(QuEST_FOUND TRUE) +set(QuEST_NOT_FOUND_MESSAGE "") +# Features are fixed properties of this binary, not selectable components. +check_required_components(QuEST) +if(NOT QuEST_FOUND) + set(QuEST_NOT_FOUND_MESSAGE "QuEST does not provide selectable package components") + return() +endif() +# The include boundary catches find_dependency's early return. Function scope +# confines module paths and dependency changes to PACKAGE_PREFIX_DIR. +function(_quest_find_dependencies) + list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/modules") + include(CMakeFindDependencyMacro) + include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/QuESTConfigDependencies.cmake") + set(QuEST_FOUND "${QuEST_FOUND}" PARENT_SCOPE) + set(QuEST_NOT_FOUND_MESSAGE "${QuEST_NOT_FOUND_MESSAGE}" PARENT_SCOPE) +endfunction() +_quest_find_dependencies() +if(NOT QuEST_FOUND) + return() +endif() +include("${CMAKE_CURRENT_LIST_DIR}/QuESTTargets.cmake") diff --git a/cmake/QuESTConfigDependencies.cmake.in b/cmake/QuESTConfigDependencies.cmake.in new file mode 100644 index 000000000..003a0d17b --- /dev/null +++ b/cmake/QuESTConfigDependencies.cmake.in @@ -0,0 +1,51 @@ +# All conditions describe the installed binary, not consumer options. +if(NOT @QUEST_BUILT_SHARED@ OR @QUEST_ENABLE_SUBCOMM@) + if(NOT CMAKE_CXX_COMPILER_LOADED) + set(QuEST_FOUND FALSE) + set(QuEST_NOT_FOUND_MESSAGE "This QuEST build requires CXX to be enabled. C applications can use project(... LANGUAGES C CXX).") + return() + endif() +endif() +if(@QUEST_ENABLE_MPI@ AND (NOT @QUEST_BUILT_SHARED@ OR @QUEST_ENABLE_SUBCOMM@)) + find_dependency(MPI COMPONENTS CXX) +endif() +if(NOT @QUEST_BUILT_SHARED@) + if(@QUEST_ENABLE_OMP@) + find_dependency(OpenMP COMPONENTS CXX) + endif() + if(@QUEST_ENABLE_NUMA@) + find_dependency(NUMA MODULE) + endif() + if(@QUEST_ENABLE_CUDA@) + find_dependency(CUDAToolkit) + if(NOT CUDAToolkit_VERSION_MAJOR STREQUAL "@CUDAToolkit_VERSION_MAJOR@") + set(QuEST_FOUND FALSE) + set(QuEST_NOT_FOUND_MESSAGE "QuEST requires the CUDA @CUDAToolkit_VERSION_MAJOR@ toolkit ABI used to build its static library") + return() + endif() + endif() + if(@QUEST_ENABLE_HIP@) + find_dependency(HIP) + endif() + if(@QUEST_ENABLE_CUQUANTUM@) + find_dependency(CUQUANTUM MODULE COMPONENTS cuStateVec) + set(_quest_custatevec_version "@CUQUANTUM_cuStateVec_VERSION@") + string(REGEX MATCH "^[0-9]+" _quest_custatevec_major "${_quest_custatevec_version}") + string(REGEX MATCH "^[0-9]+" _quest_found_custatevec_major "${CUQUANTUM_cuStateVec_VERSION}") + if(NOT CUQUANTUM_cuStateVec_VERSION OR + CUQUANTUM_cuStateVec_VERSION VERSION_LESS _quest_custatevec_version OR + NOT _quest_found_custatevec_major STREQUAL _quest_custatevec_major) + set(QuEST_FOUND FALSE) + set(QuEST_NOT_FOUND_MESSAGE "QuEST requires cuStateVec >= ${_quest_custatevec_version} with the same major ABI") + return() + endif() + endif() + if(@QUEST_ENABLE_ADIOS2@) + find_dependency(adios2 CONFIG COMPONENTS @_quest_adios2_components@) + if(NOT TARGET "@_quest_adios2_target@") + set(QuEST_FOUND FALSE) + set(QuEST_NOT_FOUND_MESSAGE "QuEST requires the ADIOS2 @_quest_adios2_target@ target") + return() + endif() + endif() +endif() diff --git a/cmake/QuESTDependencies.cmake b/cmake/QuESTDependencies.cmake new file mode 100644 index 000000000..ec8f3d0d3 --- /dev/null +++ b/cmake/QuESTDependencies.cmake @@ -0,0 +1,211 @@ +# Backend dependencies and target-local compilation requirements. + +# OpenMP +if (QUEST_ENABLE_OMP) + + # find OpenMP, but fail gracefully... + find_package(OpenMP QUIET COMPONENTS CXX) + + # so that we can customise the error message on MacOS + if (NOT OpenMP_FOUND) + set(ErrorMsg "Could not find OpenMP, necessary for enabling multithreading.") + if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + string(APPEND ErrorMsg " Try first calling \n\tbrew install libomp\nthen\n\texport OpenMP_ROOT=$(brew --prefix)/opt/libomp") + endif() + message(FATAL_ERROR ${ErrorMsg}) + endif() + + target_link_libraries(QuEST + PRIVATE + OpenMP::OpenMP_CXX + ) + +else() + + # suppress GCC "unknown pragma" warning when OpenMP disabled + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(QuEST PRIVATE $<$:-Wno-unknown-pragmas>) + endif() + +endif() + + +# NUMA is an optional enhancement, resolved once into the installed configuration. +if(QUEST_ENABLE_OMP AND QUEST_ENABLE_NUMA AND NOT WIN32) + find_package(NUMA QUIET) + if(NUMA_FOUND) + target_link_libraries(QuEST PRIVATE NUMA::NUMA) + else() + message(WARNING "libnuma not found, QuEST will not be aware of NUMA locality") + set(QUEST_ENABLE_NUMA OFF) + endif() +else() + set(QUEST_ENABLE_NUMA OFF) +endif() + +# MPI +if (QUEST_ENABLE_MPI) + find_package(MPI REQUIRED + # Component CXX is the C api usable from C++ + # NOT the deprecated C++ API + COMPONENTS CXX + ) + + if(QUEST_ENABLE_SUBCOMM) + target_link_libraries(QuEST PUBLIC MPI::MPI_CXX) + else() + target_link_libraries(QuEST PRIVATE MPI::MPI_CXX) + endif() +endif() + + +# CUDA +if (QUEST_ENABLE_CUDA) + + # make nvcc use user cxx-compiler as default host (before cuda-host is set below) + if (NOT DEFINED CMAKE_CUDA_HOST_COMPILER) + set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER}) + endif() + + enable_language(CUDA) + set_target_properties(QuEST PROPERTIES CUDA_STANDARD 20 + CUDA_STANDARD_REQUIRED YES CUDA_RESOLVE_DEVICE_SYMBOLS ON) + find_package(CUDAToolkit REQUIRED) + get_target_property(_quest_cuda_runtime QuEST CUDA_RUNTIME_LIBRARY) + if(NOT _quest_cuda_runtime) + if(CMAKE_CUDA_RUNTIME_LIBRARY_DEFAULT STREQUAL "SHARED") + set(_quest_cuda_runtime Shared) + else() + set(_quest_cuda_runtime Static) + endif() + set_property(TARGET QuEST PROPERTY CUDA_RUNTIME_LIBRARY "${_quest_cuda_runtime}") + endif() + target_link_libraries(QuEST PRIVATE + "$<$,STATIC>:CUDA::cudart_static>" + "$<$,SHARED>:CUDA::cudart>") + + # force MSVC to use the modern preprocessor + if (MSVC) + target_compile_options(QuEST PRIVATE + $<$:/Zc:preprocessor> + $<$:-Xcompiler=/Zc:preprocessor> + ) + endif() + +endif() + + +# HIP +if (QUEST_ENABLE_HIP) + + # if generation fails (hip::amdhip64 not found), users can try setting + # CMAKE_MODULE_PATH to '/opt/rocm/cmake' or '/opt/rocm/hip/lib/cmake/hip' + # (suitable when shared library libamdhip64.so is located in /opt/rocm/lib/ + # or /opt/rocm/hip/lib/ respectively). We avoid setting CMAKE_MODULE_PATH + # pre-emptively since it made successful generation less likely in our tests! + # example: list(APPEND CMAKE_MODULE_PATH "/opt/rocm/cmake"). Users should + # also add '/opt/rocm/bin' or '/opt/rocm/hip/bin' to their $PATH env-var. + + enable_language(HIP) + set_target_properties(QuEST PROPERTIES HIP_STANDARD 20 HIP_STANDARD_REQUIRED YES) + + find_package(HIP REQUIRED) + message(STATUS "Found HIP: " ${HIP_VERSION}) + + target_link_libraries(QuEST PRIVATE hip::host) + +endif() + + +# cuQuantum +if (QUEST_ENABLE_CUQUANTUM) + find_package(CUQUANTUM REQUIRED MODULE COMPONENTS cuStateVec) + target_link_libraries(QuEST PRIVATE CUQUANTUM::cuStateVec) +endif() + + +# Checkpointing (ADIOS2) +if (QUEST_ENABLE_ADIOS2) + + set(_quest_adios2_components CXX) + if(QUEST_ENABLE_MPI) + list(APPEND _quest_adios2_components MPI) + endif() + find_package(adios2 CONFIG QUIET COMPONENTS ${_quest_adios2_components}) + if(QUEST_ENABLE_MPI) + set(_quest_adios2_target adios2::cxx_mpi) + set(_quest_adios2_legacy_target adios2::cxx11_mpi) + else() + set(_quest_adios2_target adios2::cxx) + set(_quest_adios2_legacy_target adios2::cxx11) + endif() + # ADIOS2 2.9 (including Ubuntu 24.04) used the cxx11 target names. + if(NOT TARGET ${_quest_adios2_target} AND TARGET ${_quest_adios2_legacy_target}) + set(_quest_adios2_target "${_quest_adios2_legacy_target}") + endif() + if(NOT adios2_FOUND AND (adios2_CONFIG OR TARGET adios2::core OR TARGET adios2::cxx OR TARGET adios2::cxx_mpi)) + message(FATAL_ERROR "The external ADIOS2 configuration was found but is unusable: ${adios2_NOT_FOUND_MESSAGE}. Select a compatible ADIOS2 installation; QuEST will not fetch over partially imported targets.") + endif() + if(adios2_FOUND AND NOT TARGET ${_quest_adios2_target}) + message(FATAL_ERROR "The installed ADIOS2 package does not provide ${_quest_adios2_target}. Select a compatible external ADIOS2 installation.") + endif() + if(NOT adios2_FOUND AND QUEST_ENABLE_INSTALL) + message(FATAL_ERROR "Installable QuEST requires an external ADIOS2 package. Set adios2_DIR, or set QUEST_ENABLE_INSTALL=OFF and QUEST_ENABLE_PACKAGING=OFF for a developer build with downloaded ADIOS2.") + endif() + if(NOT adios2_FOUND AND QUEST_DOWNLOAD_ADIOS2) + message(STATUS "fetching ADIOS2 via FetchContent") + + include(FetchContent) + FetchContent_Declare( + adios2 + GIT_REPOSITORY https://github.com/ornladios/ADIOS2.git + GIT_TAG v2.12.1 + ) + + # Match ADIOS2's MPI to QuEST's so distributed runs write per-rank slices + # into one shared file. ADIOS2's CUDA support is deliberately left OFF: + # checkpointing copies amps to host memory (syncQuregFromGpu/syncQuregToGpu) + # before any I/O, so ADIOS2 never touches device pointers. Building it with + # CUDA is unnecessary and stalls the Windows CUDA CI job. + set(ADIOS2_USE_MPI ${QUEST_ENABLE_MPI} CACHE BOOL "" FORCE) + set(ADIOS2_USE_CUDA OFF CACHE BOOL "" FORCE) + + # Forego unused facilities + set(ADIOS2_BUILD_TESTING OFF CACHE BOOL "" FORCE) + set(ADIOS2_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_SODIUM OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_Fortran OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_HDF5 OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_ZeroMQ OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_SST OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_DataMan OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_SSC OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_MHS OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_DAOS OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_MGARD OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_BZip2 OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_Blosc OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_Blosc2 OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_SZ OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_ZFP OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_PNG OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_Profiling OFF CACHE BOOL "" FORCE) + set(ADIOS2_USE_Python OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(adios2) + + else() + # re-run non-QUIET so configuration fails with a clear error if the package + # somehow became unavailable between the two calls + find_package(adios2 CONFIG REQUIRED COMPONENTS ${_quest_adios2_components}) + endif() + + if(NOT TARGET ${_quest_adios2_target}) + message(FATAL_ERROR "ADIOS2 does not provide the required ${_quest_adios2_target} target") + endif() + + # In distributed builds link ADIOS2's MPI-enabled C++ interface: it defines + # ADIOS2_USE_MPI, which exposes the adios2::ADIOS(MPI_Comm) constructor used in + # qureg.cpp for collective per-rank I/O. The serial target lacks it. + target_link_libraries(QuEST PRIVATE ${_quest_adios2_target}) +endif() diff --git a/cmake/QuESTInstall.cmake b/cmake/QuESTInstall.cmake new file mode 100644 index 000000000..e3505d4a9 --- /dev/null +++ b/cmake/QuESTInstall.cmake @@ -0,0 +1,37 @@ +include(CMakePackageConfigHelpers) +set(quest_install_config_dir "${CMAKE_INSTALL_LIBDIR}/cmake/QuEST") +install(TARGETS QuEST EXPORT QuESTTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Runtime NAMELINK_COMPONENT Development + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Development + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT Runtime + FILE_SET umbrella_header DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" COMPONENT Development + FILE_SET api_headers DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" COMPONENT Development + FILE_SET config_header DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" COMPONENT Development) +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/QuESTConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" COMPATIBILITY SameMajorVersion) +configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/QuESTConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/QuESTConfig.cmake" + INSTALL_DESTINATION "${quest_install_config_dir}") +configure_file("${CMAKE_CURRENT_LIST_DIR}/QuESTConfigDependencies.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/QuESTConfigDependencies.cmake" @ONLY) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/QuESTConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/QuESTConfigDependencies.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/QuESTConfigVersion.cmake" + DESTINATION "${quest_install_config_dir}" COMPONENT Development) +install(FILES "${CMAKE_CURRENT_LIST_DIR}/FindNUMA.cmake" + "${CMAKE_CURRENT_LIST_DIR}/FindCUQUANTUM.cmake" "${CMAKE_CURRENT_LIST_DIR}/FindCUTENSOR.cmake" + DESTINATION "${quest_install_config_dir}/modules" COMPONENT Development) +install(EXPORT QuESTTargets FILE QuESTTargets.cmake NAMESPACE QuEST:: + DESTINATION "${quest_install_config_dir}" COMPONENT Development) +# Each independently installable package carries its license. +install(FILES "${PROJECT_SOURCE_DIR}/LICENCE.txt" "${PROJECT_SOURCE_DIR}/AUTHORS.txt" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/QuEST/Development" COMPONENT Development) +if(QUEST_BUILT_SHARED) + install(FILES "${PROJECT_SOURCE_DIR}/LICENCE.txt" "${PROJECT_SOURCE_DIR}/AUTHORS.txt" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/QuEST/Runtime" COMPONENT Runtime) +endif() +if(QUEST_HAVE_INSTALLABLE_EXAMPLES) + install(FILES "${PROJECT_SOURCE_DIR}/LICENCE.txt" "${PROJECT_SOURCE_DIR}/AUTHORS.txt" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/QuEST/Examples" COMPONENT Examples) +endif() diff --git a/cmake/QuESTPackaging.cmake b/cmake/QuESTPackaging.cmake new file mode 100644 index 000000000..c368aa234 --- /dev/null +++ b/cmake/QuESTPackaging.cmake @@ -0,0 +1,143 @@ +# CPack policy is separate from build and install policy. Include after install(). +include_guard(DIRECTORY) +if(NOT QUEST_ENABLE_PACKAGING) + return() +endif() +if(NOT QUEST_ENABLE_INSTALL) + message(FATAL_ERROR "QUEST_ENABLE_PACKAGING requires QUEST_ENABLE_INSTALL.") +endif() + +macro(_quest_cpack_default name value) + if(NOT DEFINED ${name}) + set(${name} "${value}") + endif() +endmacro() + +file(STRINGS "${PROJECT_SOURCE_DIR}/AUTHORS.txt" _quest_contact REGEX "^Contact:") +string(REGEX REPLACE "^Contact:[ \t]*" "" _quest_contact "${_quest_contact}") +_quest_cpack_default(CPACK_PACKAGE_NAME "QuEST") +_quest_cpack_default(CPACK_PACKAGE_VENDOR "The QuEST Authors and Contributors") +_quest_cpack_default(CPACK_PACKAGE_CONTACT "${_quest_contact}") +_quest_cpack_default(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +_quest_cpack_default(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Quantum Exact Simulation Toolkit") +_quest_cpack_default(CPACK_PACKAGE_DESCRIPTION "QuEST is a high performance simulator of quantum circuits, state vectors and density matrices.") +_quest_cpack_default(CPACK_PACKAGE_HOMEPAGE_URL "https://quest.qtechtheory.org/") +_quest_cpack_default(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENCE.txt") +_quest_cpack_default(CPACK_OUTPUT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CPackConfig.cmake") +_quest_cpack_default(CPACK_SOURCE_OUTPUT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CPackSourceConfig.cmake") +_quest_cpack_default(CPACK_GENERATOR "TGZ;ZIP") +_quest_cpack_default(CPACK_SOURCE_GENERATOR "TGZ;ZIP") +_quest_cpack_default(CPACK_SOURCE_INSTALLED_DIRECTORIES "${PROJECT_SOURCE_DIR};/") +_quest_cpack_default(CPACK_SOURCE_PACKAGE_FILE_NAME "QuEST-${PROJECT_VERSION}-Source") +_quest_cpack_default(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/packages") +if(NOT DEFINED CPACK_PACKAGING_INSTALL_PREFIX) + set(CPACK_QUEST_DEFAULT_PREFIX "${CMAKE_INSTALL_PREFIX}") + set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +endif() +_quest_cpack_default(CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR};${PROJECT_NAME};ALL;/") +# Escape generated CMake strings, including user paths with spaces or backslashes. +set(CPACK_VERBATIM_VARIABLES YES) + +set(QUEST_NATIVE_PACKAGE_PROFILE "" CACHE STRING "Native packaging profile: ubuntu24.04, fedora44, or custom") +set_property(CACHE QUEST_NATIVE_PACKAGE_PROFILE PROPERTY STRINGS "" ubuntu24.04 fedora44 custom) +set(CPACK_QUEST_NATIVE_PROFILE "${QUEST_NATIVE_PACKAGE_PROFILE}") +if(NOT CPACK_QUEST_NATIVE_PROFILE AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + cmake_host_system_information(RESULT _quest_os QUERY DISTRIB_INFO) + if(_quest_os_ID STREQUAL "ubuntu" AND _quest_os_VERSION_ID STREQUAL "24.04") + set(CPACK_QUEST_NATIVE_PROFILE ubuntu24.04) + elseif(_quest_os_ID STREQUAL "fedora" AND _quest_os_VERSION_ID STREQUAL "44") + set(CPACK_QUEST_NATIVE_PROFILE fedora44) + endif() +endif() +set(CPACK_QUEST_BUILT_SHARED "${QUEST_BUILT_SHARED}") +set(CPACK_QUEST_HAVE_EXAMPLES "${QUEST_HAVE_INSTALLABLE_EXAMPLES}") +set(CPACK_QUEST_MPI_LIBRARIES "${MPI_CXX_LIBRARIES}") +set(CPACK_QUEST_COMPILER_ID "${CMAKE_CXX_COMPILER_ID}") +set(CPACK_QUEST_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}") +set(CPACK_QUEST_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +set(CPACK_QUEST_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}") +set(CPACK_QUEST_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}") +set(CPACK_QUEST_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") +set(CPACK_QUEST_BUILD_CONFIG "${CMAKE_BUILD_TYPE}") +set(CPACK_QUEST_MULTI_CONFIG "${CMAKE_CONFIGURATION_TYPES}") +set(CPACK_QUEST_COMPONENTS Development) +if(QUEST_BUILT_SHARED) + list(PREPEND CPACK_QUEST_COMPONENTS Runtime) + set(_quest_linkage shared) +else() + set(_quest_linkage static) +endif() +if(QUEST_HAVE_INSTALLABLE_EXAMPLES) + list(APPEND CPACK_QUEST_COMPONENTS Examples) +endif() +set(_quest_backends cpu) +foreach(_quest_feature OMP NUMA MPI SUBCOMM CUDA HIP CUQUANTUM ADIOS2 BMI2 DEPRECATED_API) + set(CPACK_QUEST_ENABLE_${_quest_feature} "${QUEST_ENABLE_${_quest_feature}}") + if(QUEST_ENABLE_${_quest_feature}) + string(TOLOWER "${_quest_feature}" _quest_feature_lower) + string(APPEND _quest_backends "-${_quest_feature_lower}") + endif() +endforeach() +set(CPACK_QUEST_NAME_PREFIX "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") +set(CPACK_QUEST_NAME_SUFFIX "${_quest_linkage}-fp${QUEST_FLOAT_PRECISION}-${_quest_backends}") +# Let -C select the configuration at packaging time without losing explicit names. +if(NOT DEFINED CPACK_PACKAGE_FILE_NAME) + if(CMAKE_BUILD_TYPE) + set(_quest_default_config "${CMAKE_BUILD_TYPE}") + else() + set(_quest_default_config NoConfig) + endif() + set(CPACK_QUEST_DEFAULT_NAME "${CPACK_QUEST_NAME_PREFIX}-${_quest_default_config}-${CPACK_QUEST_NAME_SUFFIX}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_QUEST_DEFAULT_NAME}") +endif() + +# Only QuEST components are packaged, even if an external subproject installs files. +set(CPACK_COMPONENTS_ALL "${CPACK_QUEST_COMPONENTS}") +_quest_cpack_default(CPACK_COMPONENT_RUNTIME_DESCRIPTION "QuEST shared runtime library") +_quest_cpack_default(CPACK_COMPONENT_DEVELOPMENT_DESCRIPTION "QuEST headers, libraries and CMake package") +_quest_cpack_default(CPACK_COMPONENT_EXAMPLES_DESCRIPTION "QuEST example programs") + +# Default exclusions are appended to packager exclusions: never ship local build state. +set(_quest_source_ignore_patterns + "/[.]git(/|$)" "/[.]hg(/|$)" "/[.]svn(/|$)" + "/[.]worktrees(/|$)" "/[.]cache(/|$)" + "/build[^/]*(/|$)" "/cmake-build[^/]*(/|$)" "/_deps(/|$)" + "/_CPack_Packages(/|$)" "/packages(/|$)" "/CMakeFiles(/|$)" + "/CMakeCache[.]txt$" "/CMakeUserPresets[.]json$" "/CPack[^/]*[.]cmake$" + "/[^/]*[.](deb|rpm|zip|tar[.]gz)$" "/__pycache__(/|$)") +# Match entries inside this source tree, never similarly named checkout ancestors. +string(REGEX REPLACE "([][+.*()^$?\\\\|])" "[\\1]" _quest_source_regex "${PROJECT_SOURCE_DIR}") +foreach(_quest_pattern IN LISTS _quest_source_ignore_patterns) + list(APPEND CPACK_SOURCE_IGNORE_FILES "^${_quest_source_regex}(/[^/]+)*${_quest_pattern}") +endforeach() +# Detect arbitrary existing in-tree build directory names rather than assuming build/. +file(GLOB_RECURSE _quest_source_caches LIST_DIRECTORIES FALSE "${PROJECT_SOURCE_DIR}/*CMakeCache.txt") +set(_quest_exclude_dirs "${PROJECT_BINARY_DIR}" "${CPACK_PACKAGE_DIRECTORY}") +foreach(_quest_cache IN LISTS _quest_source_caches) + get_filename_component(_quest_cache_dir "${_quest_cache}" DIRECTORY) + list(APPEND _quest_exclude_dirs "${_quest_cache_dir}") +endforeach() +foreach(_quest_dir IN LISTS _quest_exclude_dirs) + if(NOT _quest_dir STREQUAL PROJECT_SOURCE_DIR) + # Bracket classes avoid backslash escaping in the generated CPack config. + string(REGEX REPLACE "([][+.*()^$?\\\\|])" "[\\1]" _quest_dir_regex "${_quest_dir}") + list(APPEND CPACK_SOURCE_IGNORE_FILES "^${_quest_dir_regex}(/|$)") + endif() +endforeach() +list(REMOVE_DUPLICATES CPACK_SOURCE_IGNORE_FILES) + +# Wrap an existing hook: packager settings are loaded before QuEST defaults/checks. +set(CPACK_QUEST_USER_PROJECT_CONFIG "${CPACK_PROJECT_CONFIG_FILE}") +configure_file("${CMAKE_CURRENT_LIST_DIR}/QuESTCPackOptions.cmake.in" + "${PROJECT_BINARY_DIR}/QuESTCPackOptions.cmake" COPYONLY) +set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/QuESTCPackOptions.cmake") +configure_file("${CMAKE_CURRENT_LIST_DIR}/QuESTCPackMPI.cmake.in" + "${PROJECT_BINARY_DIR}/QuESTCPackMPI.cmake" COPYONLY) +set(CPACK_QUEST_MPI_POLICY "${PROJECT_BINARY_DIR}/QuESTCPackMPI.cmake") +configure_file("${CMAKE_CURRENT_LIST_DIR}/QuESTCPackStage.cmake.in" + "${PROJECT_BINARY_DIR}/QuESTCPackStage.cmake" COPYONLY) +list(APPEND CPACK_PRE_BUILD_SCRIPTS "${PROJECT_BINARY_DIR}/QuESTCPackStage.cmake") +configure_file("${CMAKE_CURRENT_LIST_DIR}/QuESTCPackVerify.cmake.in" + "${PROJECT_BINARY_DIR}/QuESTCPackVerify.cmake" COPYONLY) +list(APPEND CPACK_POST_BUILD_SCRIPTS "${PROJECT_BINARY_DIR}/QuESTCPackVerify.cmake") +include(CPack) diff --git a/cmake/QuESTRpath.cmake b/cmake/QuESTRpath.cmake new file mode 100644 index 000000000..4d8ea84d5 --- /dev/null +++ b/cmake/QuESTRpath.cmake @@ -0,0 +1,22 @@ +# Each executable can live at a different depth, e.g. bin/examples/extended. +function(setup_quest_rpath target destination) + if(APPLE) + set(_origin "@loader_path") + elseif(UNIX) + set(_origin "$ORIGIN") + else() + return() + endif() + cmake_path(ABSOLUTE_PATH destination BASE_DIRECTORY "${CMAKE_INSTALL_PREFIX}" OUTPUT_VARIABLE _from) + set(_libdir "${CMAKE_INSTALL_LIBDIR}") + cmake_path(ABSOLUTE_PATH _libdir BASE_DIRECTORY "${CMAKE_INSTALL_PREFIX}" OUTPUT_VARIABLE _to) + file(RELATIVE_PATH _relative "${_from}" "${_to}") + set_property(TARGET ${target} APPEND PROPERTY INSTALL_RPATH "${_origin}/${_relative}") + # Preserve values initialized by standard CMake variables or set on the target. + foreach(_property IN ITEMS BUILD_RPATH_USE_ORIGIN INSTALL_REMOVE_ENVIRONMENT_RPATH) + get_property(_is_set TARGET ${target} PROPERTY ${_property} SET) + if(NOT _is_set) + set_property(TARGET ${target} PROPERTY ${_property} TRUE) + endif() + endforeach() +endfunction() diff --git a/docs/cmake.md b/docs/cmake.md index 223cce797..57545ec63 100644 --- a/docs/cmake.md +++ b/docs/cmake.md @@ -8,17 +8,22 @@ @author Tyson Jones (test variables) --> -Version 4 of QuEST includes reworked CMake to support library builds, CMake export, and installation. Here we detail useful variables to configure the compilation of QuEST. If using a Unix-like operating system, any of these variables can be set using the `-D` flag when invoking CMake, for example: +QuEST requires CMake 3.28 or newer. Version 4 includes CMake support for library builds, installation, exported targets, and binary and source packages. Here we detail useful variables to configure the compilation of QuEST. Set any of these cache variables with the `-D` flag when invoking CMake, for example: ``` -cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/QuEST -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DQUEST_ENABLE_OMP=ON -DQUEST_ENABLE_MPI=OFF ./ +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/QuEST -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DQUEST_ENABLE_OMP=ON -DQUEST_ENABLE_MPI=OFF ``` -Then, as detailed in [`compile.md`](compile.md), one need only move to the build directory and compile by invoking make: +Then, as detailed in [`compile.md`](compile.md), compile through CMake: ``` -cd build -make +cmake --build build +``` + +Install an install-enabled build with CMake's portable install command: + +```bash +cmake --install build --config Release ``` > [!NOTE] @@ -32,10 +37,13 @@ make | Variable | (Default) Values | Notes | | -------- | ---------------- | ----- | -| `QUEST_OUTPUT_LIB_NAME` | (`QuEST`), String | The QuEST library will be named `lib${QUEST_OUTPUT_LIB_NAME}.so`. Can be used to differentiate multiple versions of QuEST which have been compiled. | +| `QUEST_ENABLE_INSTALL` | (`ON` standalone, `OFF` as a subproject), `ON`, `OFF` | Enables QuEST installation and exported CMake package files. Installable ADIOS2 builds require an externally installed ADIOS2 package. | +| `QUEST_ENABLE_PACKAGING` | (`ON` for a standalone installable build, otherwise `OFF`), `ON`, `OFF` | Enables CPack configuration. Packaging requires installation. | +| `QUEST_OUTPUT_LIB_NAME` | (`QuEST`), String | Changes the library artifact name. The installed CMake package and target remain `QuESTConfig.cmake` and `QuEST::QuEST`. | | `QUEST_APPEND_CONFIG_TO_LIB_NAME` | (`OFF`), `ON` | When turned on `QUEST_OUTPUT_LIB_NAME` will be modified according to the other configuration options chosen. For example compiling QuEST with multithreading, distribution, and double precision with `QUEST_APPEND_CONFIG_TO_LIB_NAME` turned on creates `libQuEST-fp2+mt+mpi.so`. | | `QUEST_FLOAT_PRECISION` | (`2`), `1`, `4` | Determines which floating-point precision QuEST will use: double, single, or quad. *Note: Quad precision is not supported when also compiling for GPU.* | -| `QUEST_BUILD_EXAMPLES` | (`OFF`), `ON` | Determines whether the example programs will be built alongside QuEST. Note that `min_example` is always built. | +| `QUEST_BUILD_MIN_EXAMPLE` | (`ON` standalone, `OFF` as a subproject), `ON`, `OFF` | Determines whether the minimum example is built. | +| `QUEST_BUILD_EXAMPLES` | (`OFF`), `ON` | Determines whether the other example programs are built alongside QuEST. | | `QUEST_INSTALL_BINARIES` | (`OFF`), `ON` | Determines whether compiled binaries such as the examples will be installed as well as the QuEST library. | | `QUEST_ENABLE_OMP` | (`ON`), `OFF` | Determines whether QuEST will be built with support for parallelisation with OpenMP. | | `QUEST_ENABLE_NUMA` | (`ON`), `OFF` | Determines whether QuEST will attempt to build with NUMA awareness when OpenMP is also enabled. | @@ -46,7 +54,7 @@ make | `QUEST_ENABLE_HIP` | (`OFF`), `ON` | Determines whether QuEST will be built with support for AMD GPU acceleration. If turned on, `CMAKE_HIP_ARCHITECTURES` should probably also be set. | | `QUEST_ENABLE_BMI2` | (`OFF`), `ON` | Determines whether QuEST will be built with BMI2 intrinsics to accelerate CPU simulation of few-qubit Quregs. This is not compatible with all compilers and CPUs. **Beware** that if enabled, and the compiled QuEST executable is later run upon a different machine which lacks the BMI2 instructions, execution will crash. | | `QUEST_ENABLE_ADIOS2` | (`OFF`), `ON` | Determines whether QuEST will be built with ADIOS2 to enable checkpointing, via functions `saveQuregToFile()` and `createQuregFromFile()`. | -| `QUEST_DOWNLOAD_ADIOS2` | (`ON`), `OFF` | Determines whether to download ADIOS2 from Github, when ADIOS2 is enabled but not found. | +| `QUEST_DOWNLOAD_ADIOS2` | (`ON`), `OFF` | Determines whether to download ADIOS2 from GitHub when ADIOS2 is enabled but not found. Downloading is available only when `QUEST_ENABLE_INSTALL=OFF`; installable builds must use an external compatible ADIOS2 package. | | `QUEST_ENABLE_DEPRECATED_API` | (`OFF`), `ON` | Determines whether QuEST will be built with support for the deprecated (v3) API. ***Note**: this will generate compiler warnings and is not supported by MSVC.* | | `QUEST_DISABLE_DEPRECATION_WARNINGS` | (`OFF`), `ON` | Whether to disable the compile-time deprecation warnings when using the deprecated (v3) API. | | `USER_SOURCE_NAMES` | (Undefined), String | The source file for a user program which will be compiled alongside QuEST. `USER_OUTPUT_EXE_NAME` *must* also be defined. | @@ -62,7 +70,8 @@ make | Variable | (Default) Values | Notes | | -------- | ---------------- | ----- | | `QUEST_BUILD_TESTS` | (`OFF`), `ON` | Determines whether to additionally build QuEST's unit and integration tests. If built, tests can be run from the `build` directory with `make test`, or `ctest`, or manually launched with `./tests/tests` which enables distribution (i.e. `mpirun -np 8 ./tests/tests`) | -| `QUEST_ENABLE_DEPRECATED_API` | (`OFF`), `ON` | As described above. When enabled alongside testing, the `v3 deprecated` unit tests will additionally be compiled and can be run from within `build` via `cd tests/deprecated; ctest`, or manually launched with `./tests/deprecated/dep_tests` (enabling distribution, as above). +| `QUEST_BUILD_PACKAGING_TESTS` | (`OFF`), `ON` | Builds installation, relocation, exported-target, finder, and packaging checks. These tests do not require Catch2. Run them with `ctest --test-dir build -L packaging --output-on-failure`. | +| `QUEST_ENABLE_DEPRECATED_API` | (`OFF`), `ON` | As described above. When enabled alongside testing, the `v3 deprecated` unit tests will additionally be compiled and can be run from within `build` via `cd tests/deprecated; ctest`, or manually launched with `./tests/deprecated/dep_tests` (enabling distribution, as above). | | `QUEST_TESTS_DOWNLOAD_CATCH2` | (`ON`), `OFF` | QuEST's tests require Catch2. By default, if you don't have Catch2 installed (or CMake doesn't find it) it will be downloaded from Github and built for you. If you don't want that to happen, for example because you _do_ have Catch2 installed, set this to `OFF`. | > As of `v4.2`, macros which configure the unit tests such as `QUEST_TEST_MAX_NUM_QUBIT_PERMUTATIONS` have become environment variables specified before launch. See [`launch.md`](launch.md) @@ -80,3 +89,92 @@ make | `CMAKE_CUDA_ARCHITECTURES` | Used to set the value of `arch` when compiling for NVIDIA GPU. This is also known as the target GPU's "compute capability" and can be discovered [here](https://developer.nvidia.com/cuda-gpus). | [CMAKE_CUDA_ARCHITECTURES](https://cmake.org/cmake/help/latest/variable/CMAKE_CUDA_ARCHITECTURES.html) | | `CMAKE_HIP_ARCHITECTURES` | Used to set the HIP platform which QuEST is compiled for when compiling for AMD GPU. | [CMAKE_HIP_ARCHITECTURES](https://cmake.org/cmake/help/latest/variable/CMAKE_HIP_ARCHITECTURES.html) | | `CMAKE_RUNTIME_OUTPUT_DIRECTORY` | The output directory to which to save compiled executables, overriding the default `build` folder | [`CMAKE_RUNTIME_OUTPUT_DIRECTORY`](https://cmake.org/cmake/help/latest/variable/CMAKE_RUNTIME_OUTPUT_DIRECTORY.html). | +| `CMAKE_INSTALL_RPATH` | Additional runtime library search directories for installed targets. QuEST preserves these entries and adds its relative path to the installed QuEST library. | [CMAKE_INSTALL_RPATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH.html) | +| `CMAKE_INSTALL_RPATH_USE_LINK_PATH` | When `ON`, CMake appends linker search directories outside the project to the install RPATH. Leave this unset or `OFF` for portable archives; enable it deliberately for a native install tied to external SDK locations. | [CMAKE_INSTALL_RPATH_USE_LINK_PATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH_USE_LINK_PATH.html) | +| `CMAKE_BUILD_RPATH_USE_ORIGIN` | Initialises the target property controlling relative build-tree RPATHs on supported platforms. QuEST defaults the property to `ON` when unset and honours an explicit `OFF`. | [CMAKE_BUILD_RPATH_USE_ORIGIN](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.html) | +| `CMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH` | Initialises the target property controlling removal of toolchain-added RPATH entries during installation. QuEST defaults the property to `ON` when unset and honours an explicit `OFF`. | [CMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH.html) | + + +--------------------------- + +## Using an installed QuEST + +An install always publishes the canonical `QuESTConfig.cmake`, `QuESTConfigVersion.cmake`, and `QuESTTargets.cmake` files. `QUEST_OUTPUT_LIB_NAME` and `QUEST_APPEND_CONFIG_TO_LIB_NAME` change only the library artifact name. One QuEST configuration is supported per installation prefix. + +Downstream projects need only discover the package and link its exported target: + +```cmake +cmake_minimum_required(VERSION 3.28) +project(my_quest_program LANGUAGES C CXX) + +find_package(QuEST CONFIG REQUIRED) +add_executable(my_quest_program main.c) +target_link_libraries(my_quest_program PRIVATE QuEST::QuEST) +``` + +Enabling both C and CXX is supported for a C application and permits CMake to satisfy a static QuEST library's C++ linker requirements. The exported target requests C11 for C consumers and C++14 for C++ consumers. QuEST's C++17 implementation and GPU C++20 requirements remain private build requirements. Installed GPU packages can be consumed without enabling CUDA or HIP as project languages. + +Configure the consumer with the QuEST prefix when it is outside CMake's normal search locations: + +```bash +cmake -S consumer -B consumer-build -DCMAKE_PREFIX_PATH=/opt/QuEST +cmake --build consumer-build +``` + +The installed configuration rediscovers the dependencies required by the built library before loading `QuEST::QuEST`. It does not consult downstream `QUEST_*` settings or `BUILD_SHARED_LIBS` to reinterpret the installed binary. Static packages can therefore require development packages for enabled OpenMP, NUMA, MPI, CUDA, HIP, cuQuantum, or ADIOS2 backends. MPI is also a public requirement when QuEST was built with the subcommunicator API because its public header exposes `mpi.h`. Shared packages preserve their external runtime requirements while avoiding private SDK development requirements where the link interface does not need them. + +ADIOS2 builds select the serial or MPI C++ target to match QuEST's MPI configuration. Current ADIOS2 packages normally provide `adios2::cxx` and `adios2::cxx_mpi`; ADIOS2 2.9 packages, including Ubuntu 24.04, use the compatible legacy names `adios2::cxx11` and `adios2::cxx11_mpi`. QuEST records the selected target and requires the same interface when a static installation is consumed. + +QuEST installs its reusable cuQuantum and cuTENSOR find modules with the package. When a static built library uses cuQuantum, `QuESTConfig.cmake` makes this dependency request before loading the exported target: + +```cmake +find_package(CUQUANTUM MODULE REQUIRED COMPONENTS cuStateVec) +``` + +Consumers still call only `find_package(QuEST CONFIG REQUIRED)`. The static package records the producer's cuStateVec component version and requires an equal or newer version with the same major ABI. A shared QuEST package retains its cuQuantum runtime requirement without resolving private SDK development files during consumer configuration. + +The available cuQuantum imported targets are `CUQUANTUM::cuStateVec`, `CUQUANTUM::cuTensorNet`, and `CUQUANTUM::cuDensityMat`. Calling the finder without components requests all three. Each component's header and shared library are resolved independently; the finder does not substitute the SDK's `_static` archives. Set `CUQUANTUM_ROOT` or its environment variable to the SDK prefix and `CUDAToolkit_ROOT` to CUDA. cuTensorNet and cuDensityMat also require cuTENSOR and accept `CUTENSOR_ROOT`. An explicitly set `CUQUANTUM_DIR` remains a legacy prefix hint with precedence over these general roots. Component versions are reported separately as `CUQUANTUM__VERSION`; they are not the overall SDK release version. + +On Linux and macOS, installed QuEST targets use a relative runtime search path (`$ORIGIN` or `@loader_path`) to locate the QuEST library within the install prefix. Explicit `CMAKE_INSTALL_RPATH` entries are preserved. QuEST supplies defaults for the target properties `BUILD_RPATH_USE_ORIGIN` and `INSTALL_REMOVE_ENVIRONMENT_RPATH` only when those properties have not already been set, including through their corresponding `CMAKE_*` variables. An explicit `OFF` remains in effect. Standard CMake RPATH skip controls also remain available. + +For a native installation whose external SDKs remain in fixed locations, opt in to CMake's link-directory handling: + +```bash +cmake -S . -B build-native -DBUILD_SHARED_LIBS=ON \ + -DQUEST_ENABLE_CUDA=ON -DQUEST_ENABLE_CUQUANTUM=ON \ + -DCUDAToolkit_ROOT=/opt/cuda -DCUQUANTUM_ROOT=/opt/cuquantum \ + -DCMAKE_INSTALL_PREFIX=/opt/QuEST -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON +cmake --build build-native +cmake --install build-native +``` + +This intentionally records absolute external runtime directories in the installed shared library, allowing the loader to locate those dependencies without `LD_LIBRARY_PATH`. It does not bundle CUDA, cuQuantum, or other dependencies; those runtimes must remain installed at the recorded locations, and CUDA driver stub directories must never be used as runtime search paths. Use `CMAKE_INSTALL_RPATH` when you need to specify runtime directories explicitly. The default archive configuration keeps relative QuEST paths and does not enable `CMAKE_INSTALL_RPATH_USE_LINK_PATH`; an archive made from the opt-in native build retains its absolute SDK paths and therefore requires that deployment layout. + + +--------------------------- + +## Creating packages + +For a standalone installable build, CPack is enabled by default after the installation rules. TGZ and ZIP produce complete binary archives, and the source CPack configuration produces complete source archives: + +```bash +cmake -S . -B build -DQUEST_ENABLE_PACKAGING=ON +cmake --build build --config Release +cpack --config build/CPackConfig.cmake -G TGZ +cpack --config build/CPackConfig.cmake -G ZIP +cpack --config build/CPackSourceConfig.cmake -G TGZ +cpack --config build/CPackSourceConfig.cmake -G ZIP +``` + +Binary archive names record the QuEST version, platform, architecture, configuration, shared or static linkage, precision, and enabled backends. Packages contain only QuEST-owned files and combine the QuEST components into one complete archive. The install components are `Runtime`, `Development`, and, when installable examples were built, `Examples`. `Development` contains headers, CMake exports and find modules, static or import libraries, and linker namelinks. A shared `Development` package depends on the exact `Runtime` version; a static build has no empty runtime dependency. + +Native package profiles are available with `-DQUEST_NATIVE_PACKAGE_PROFILE=ubuntu24.04` for DEB and `-DQUEST_NATIVE_PACKAGE_PROFILE=fedora44` for RPM. Supported hosts are detected when the variable is empty; use `custom` for an explicitly described vendor environment. Native profiles install under `/usr` and use GNU installation directories. They produce `libquest4`, `libquest-dev`, and optional `quest-examples` packages on Debian, or `quest`, `quest-devel`, and optional `quest-examples` packages on Fedora. + +Fedora's stock Open MPI installation is module-based. Load it when configuring an MPI-enabled Fedora package and when running or building consumers of that package: + +```bash +source /etc/profile.d/modules.sh +module load mpi/openmpi-x86_64 +``` + +The stock profiles describe GCC, OpenMP, NUMA, and Open MPI dependencies and enable the platform's shared-library dependency scanner. For ADIOS2 or GPU variants, set complete native dependency metadata explicitly with the standard per-component CPack variables, such as `CPACK_DEBIAN_DEVELOPMENT_PACKAGE_DEPENDS`, `CPACK_DEBIAN_RUNTIME_PACKAGE_DEPENDS`, and `CPACK_DEBIAN_EXAMPLES_PACKAGE_DEPENDS`, or the corresponding `CPACK_RPM_*_PACKAGE_REQUIRES` variables. The same requirement applies to custom profiles and non-GCC native builds. diff --git a/docs/compilers.md b/docs/compilers.md index 6c4f44303..b9b06e8a0 100644 --- a/docs/compilers.md +++ b/docs/compilers.md @@ -12,6 +12,8 @@ QuEST separates compilation of the _frontend_, _backend_ and the _tests_, which This page details the specialised compilers necessary to enable specific features hardware accelerators, and lists such compilers which are known to be compatible with QuEST. +Configuring QuEST itself and its installed consumers requires CMake 3.28 or newer. + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 10278afb6..6dd10223a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,24 +20,23 @@ function(add_example direc in_fn) add_executable(${target} ${in_fn}) target_link_libraries(${target} PUBLIC QuEST) - if (QUEST_ENABLE_MPI AND QUEST_ENABLE_SUBCOMM) - target_link_libraries(${target} PRIVATE MPI::MPI_CXX) - endif() - if (QUEST_INSTALL_BINARIES) + if (QUEST_ENABLE_INSTALL AND QUEST_INSTALL_BINARIES) install( TARGETS ${target} RUNTIME DESTINATION ${out_dir} + COMPONENT Examples ) endif () set_target_properties(${target} PROPERTIES - INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}" OUTPUT_NAME "${out_fn}" ) + setup_quest_rpath(${target} "${out_dir}") + endfunction() diff --git a/quest/include/CMakeLists.txt b/quest/include/CMakeLists.txt index 43146ceb4..35bf721c5 100644 --- a/quest/include/CMakeLists.txt +++ b/quest/include/CMakeLists.txt @@ -10,3 +10,17 @@ # installing QuEST. Note that config.h must be manually created when # not compiling via CMake, e.g. when using a custom build script configure_file(config.h.in config.h @ONLY) + +# File sets preserve the historical public include layout. +set(_quest_api_headers + calculations.h channels.h debug.h decoherence.h deprecated.h environment.h + experimental.h initialisations.h matrices.h modes.h multiplication.h + operations.h paulis.h precision.h qureg.h trotterisation.h types.h wrappers.h) +list(TRANSFORM _quest_api_headers PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") +target_sources(QuEST PUBLIC + FILE_SET umbrella_header TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" FILES quest.h + FILE_SET api_headers TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}" FILES ${_quest_api_headers} + FILE_SET config_header TYPE HEADERS + BASE_DIRS "${PROJECT_BINARY_DIR}" FILES "${CMAKE_CURRENT_BINARY_DIR}/config.h") diff --git a/quest/src/core/accelerator.hpp b/quest/src/core/accelerator.hpp index fc6d002a0..b5df56664 100644 --- a/quest/src/core/accelerator.hpp +++ b/quest/src/core/accelerator.hpp @@ -333,4 +333,4 @@ void accel_statevec_initDebugState_sub(Qureg qureg); void accel_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg); -#endif // ACCELERATOR_HPP \ No newline at end of file +#endif // ACCELERATOR_HPP diff --git a/quest/src/core/bitwise.hpp b/quest/src/core/bitwise.hpp index 1c079bdfe..9576e5af5 100644 --- a/quest/src/core/bitwise.hpp +++ b/quest/src/core/bitwise.hpp @@ -219,7 +219,6 @@ INLINE qindex insertBitsWithMaskedValues(qindex number, const int* bitInds, int // there exists an overload of insertBitsWithMaskedValues() below which // additionally accepts a (seemingly) superfluous mask encoding bitInds, // and which will use a CPU intrinsic when available - // bitInds must be sorted (increasing), and mask must be zero everywhere except bitInds return mask | insertBits(number, bitInds, numBits, 0); } @@ -460,4 +459,4 @@ INLINE void setToBitsOfInteger(int* bits, qindex number, int numBits) { -#endif // BITWISE_HPP \ No newline at end of file +#endif // BITWISE_HPP diff --git a/quest/src/cpu/cpu_subroutines.cpp b/quest/src/cpu/cpu_subroutines.cpp index c1ba9b3aa..34da687f8 100644 --- a/quest/src/cpu/cpu_subroutines.cpp +++ b/quest/src/cpu/cpu_subroutines.cpp @@ -2648,7 +2648,6 @@ void cpu_densmatr_multiQubitProjector_sub(Qureg qureg, ConstList64 qubits, Const } - /* * STATE INITIALISATION */ diff --git a/quest/src/cpu/cpu_subroutines.hpp b/quest/src/cpu/cpu_subroutines.hpp index f8773fc1c..c104728f6 100644 --- a/quest/src/cpu/cpu_subroutines.hpp +++ b/quest/src/cpu/cpu_subroutines.hpp @@ -202,4 +202,4 @@ void cpu_statevec_initDebugState_sub(Qureg qureg); void cpu_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg); -#endif // CPU_SUBROUTINES_HPP \ No newline at end of file +#endif // CPU_SUBROUTINES_HPP diff --git a/quest/src/gpu/gpu_subroutines.hpp b/quest/src/gpu/gpu_subroutines.hpp index 9c6a3013f..8fc23d5ae 100644 --- a/quest/src/gpu/gpu_subroutines.hpp +++ b/quest/src/gpu/gpu_subroutines.hpp @@ -198,4 +198,4 @@ void gpu_statevec_initDebugState_sub(Qureg qureg); void gpu_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg); -#endif // GPU_SUBROUTINES_HPP \ No newline at end of file +#endif // GPU_SUBROUTINES_HPP diff --git a/quest/src/gpu/gpu_thrust.cuh b/quest/src/gpu/gpu_thrust.cuh index ae226545c..8df3f4f44 100644 --- a/quest/src/gpu/gpu_thrust.cuh +++ b/quest/src/gpu/gpu_thrust.cuh @@ -516,7 +516,7 @@ struct functor_projectStateVec { { } __host__ __device__ gpu_qcomp operator()(qindex n, gpu_qcomp amp) { - + // return amp scaled by zero or renorm, depending on whether n has projected substate qreal fac = renorm * ((n & qubitMask) == outcomeMask); return fac * amp; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4d5050e51..44bb814a7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,9 +7,6 @@ add_executable(tests target_link_libraries(tests PRIVATE QuEST::QuEST Catch2::Catch2) target_compile_features(tests PUBLIC cxx_std_20) -if (QUEST_ENABLE_MPI AND QUEST_ENABLE_SUBCOMM) - target_link_libraries(tests PRIVATE MPI::MPI_CXX) -endif() # extend the MSVC max object size if (MSVC) diff --git a/tests/unit/experimental.cpp b/tests/unit/experimental.cpp index 67cc9e6ab..56b50426f 100644 --- a/tests/unit/experimental.cpp +++ b/tests/unit/experimental.cpp @@ -30,7 +30,6 @@ using Catch::Matchers::ContainsSubstring; * UTILITIES */ - #define TEST_CATEGORY \ LABEL_UNIT_TAG "[experimental]" diff --git a/utils/docs/Doxyfile b/utils/docs/Doxyfile index 9432ff058..f65245601 100644 --- a/utils/docs/Doxyfile +++ b/utils/docs/Doxyfile @@ -51,7 +51,7 @@ PROJECT_NAME = "The Quantum Exact Simulation Toolkit" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "v4.2.0" +PROJECT_NUMBER = "v4.3.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a