diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3ad92f9..15054cd 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,13 +3,17 @@ on: release: types: [published] workflow_dispatch: + pull_request: + paths: + - .github/workflows/cd.yml jobs: python-packaging: name: 🐍 Packaging - uses: cda-tum/mqt-core/.github/workflows/reusable-python-packaging.yml@v2.2.2 + uses: cda-tum/mqt-workflows/.github/workflows/reusable-python-packaging.yml@v1.1.4 with: setup-z3: true + z3-version: 4.12.6 # 4.13.0 has incorrectly tagged manylinux wheels deploy: if: github.event_name == 'release' && github.event.action == 'published' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3f3441..df925e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,15 +14,13 @@ concurrency: jobs: change-detection: name: 🔍 Change - uses: cda-tum/mqt-core/.github/workflows/reusable-change-detection.yml@v2.2.2 + uses: cda-tum/mqt-workflows/.github/workflows/reusable-change-detection.yml@v1.1.4 cpp-tests: name: 🇨‌ Test needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-tests) - uses: cda-tum/mqt-core/.github/workflows/reusable-cpp-ci.yml@v2.2.2 - secrets: - token: ${{ secrets.CODECOV_TOKEN }} + uses: cda-tum/mqt-workflows/.github/workflows/reusable-cpp-ci.yml@v1.1.4 with: setup-z3: true @@ -30,7 +28,7 @@ jobs: name: 🇨‌ Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) - uses: cda-tum/mqt-core/.github/workflows/reusable-cpp-linter.yml@v2.2.2 + uses: cda-tum/mqt-workflows/.github/workflows/reusable-cpp-linter.yml@v1.1.4 with: setup-z3: true @@ -38,7 +36,7 @@ jobs: name: 📝 CodeQL needs: change-detection if: fromJSON(needs.change-detection.outputs.run-code-ql) - uses: cda-tum/mqt-core/.github/workflows/reusable-code-ql.yml@main + uses: cda-tum/mqt-workflows/.github/workflows/reusable-code-ql.yml@v1.1.4 with: setup-z3: true diff --git a/.github/workflows/update-mqt-core.yml b/.github/workflows/update-mqt-core.yml new file mode 100644 index 0000000..6ed788f --- /dev/null +++ b/.github/workflows/update-mqt-core.yml @@ -0,0 +1,26 @@ +name: Update MQT Core +on: + schedule: + # run once a month on the first day of the month at 00:00 UTC + - cron: "0 0 1 * *" + workflow_dispatch: + inputs: + update-to-head: + description: "Update to the latest commit on the default branch" + type: boolean + required: false + default: false + pull_request: + paths: + - .github/workflows/update-mqt-core.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + update-mqt-core: + name: ⬆️ Update MQT Core + uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.1.4 + with: + update-to-head: ${{ fromJSON(github.event.inputs.update-to-head) || false }} diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 0bd135b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "extern/qfr"] - path = extern/mqt-core - url = https://github.com/cda-tum/mqt-core.git - branch = main diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8119fd..55b1d3c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ ci: repos: # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -46,7 +46,7 @@ repos: # Python linting using ruff - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.5.0 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -56,14 +56,14 @@ repos: # Also run Black on examples in the documentation - repo: https://github.com/adamchainz/blacken-docs - rev: 1.16.0 + rev: 1.18.0 hooks: - id: blacken-docs additional_dependencies: [black==23.*] # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.10.1 hooks: - id: mypy files: ^(src/mqt) @@ -73,14 +73,14 @@ repos: # Check for spelling - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell args: ["-L", "wille,linz,applys,lits", "--skip", "*.ipynb"] # Clang-format the C++ part of the code base automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.2 + rev: v18.1.8 hooks: - id: clang-format types_or: [c++, c, cuda] @@ -91,8 +91,6 @@ repos: hooks: - id: cmake-format additional_dependencies: [pyyaml] - - id: cmake-lint - additional_dependencies: [pyyaml] # Format configuration files with prettier - repo: https://github.com/pre-commit/mirrors-prettier diff --git a/CMakeLists.txt b/CMakeLists.txt index e2cc55d..36bd105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,20 +6,9 @@ project( LANGUAGES CXX DESCRIPTION "A Tool for Utilizing SAT in Quantum Computing") -# check whether the submodule ``modulename`` is correctly cloned in the ``/extern`` directory. -macro(CHECK_SUBMODULE_PRESENT modulename) - if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/${modulename}/CMakeLists.txt") - message( - FATAL_ERROR - "${modulename} submodule not cloned properly. \ - Please run `git submodule update --init --recursive` \ - from the main project directory") - endif() -endmacro() - -check_submodule_present(mqt-core) - option(BUILD_MQT_QUSAT_BINDINGS "Build the MQT QUSAT Python bindings" OFF) +option(BUILD_MQT_QUSAT_TESTS "Also build tests for the MQT QUSAT project" ON) + if(BUILD_MQT_QUSAT_BINDINGS) # ensure that the BINDINGS option is set set(BINDINGS @@ -43,14 +32,12 @@ endif() # Add path for custom modules list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -# search for Z3 -find_package(Z3 REQUIRED) +include(cmake/ExternalDependencies.cmake) # add main library code add_subdirectory(src) # add test code -option(BUILD_MQT_QUSAT_TESTS "Also build tests for the MQT QUSAT project" ON) if(BUILD_MQT_QUSAT_TESTS) enable_testing() include(GoogleTest) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake new file mode 100644 index 0000000..9c8a420 --- /dev/null +++ b/cmake/ExternalDependencies.cmake @@ -0,0 +1,89 @@ +# Declare all external dependencies and make sure that they are available. + +include(FetchContent) +set(FETCH_PACKAGES "") + +# search for Z3 +find_package(Z3 REQUIRED) + +if(BUILD_MQT_QUSAT_BINDINGS) + if(NOT SKBUILD) + # Manually detect the installed pybind11 package. + execute_process( + COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE pybind11_DIR) + + # Add the detected directory to the CMake prefix path. + list(APPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") + endif() + + # add pybind11 library + find_package(pybind11 CONFIG REQUIRED) +endif() + +# cmake-format: off +set(MQT_CORE_VERSION 2.5.1 + CACHE STRING "MQT Core version") +set(MQT_CORE_REV "35e06ca3067ca3cf36bda1f0c38edf5bd7456fb6" + CACHE STRING "MQT Core identifier (tag, branch or commit hash)") +set(MQT_CORE_REPO_OWNER "cda-tum" + CACHE STRING "MQT Core repository owner (change when using a fork)") +# cmake-format: on +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + FetchContent_Declare( + mqt-core + GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/mqt-core.git + GIT_TAG ${MQT_CORE_REV} + FIND_PACKAGE_ARGS ${MQT_CORE_VERSION}) + list(APPEND FETCH_PACKAGES mqt-core) +else() + find_package(mqt-core ${MQT_CORE_VERSION} QUIET) + if(NOT mqt-core_FOUND) + FetchContent_Declare( + mqt-core + GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/mqt-core.git + GIT_TAG ${MQT_CORE_REV}) + list(APPEND FETCH_PACKAGES mqt-core) + endif() +endif() + +if(BUILD_MQT_QUSAT_TESTS) + set(gtest_force_shared_crt + ON + CACHE BOOL "" FORCE) + set(GTEST_VERSION + 1.14.0 + CACHE STRING "Google Test version") + set(GTEST_URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.tar.gz) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + FetchContent_Declare(googletest URL ${GTEST_URL} FIND_PACKAGE_ARGS ${GTEST_VERSION} NAMES GTest) + list(APPEND FETCH_PACKAGES googletest) + else() + find_package(googletest ${GTEST_VERSION} QUIET NAMES GTest) + if(NOT googletest_FOUND) + FetchContent_Declare(googletest URL ${GTEST_URL}) + list(APPEND FETCH_PACKAGES googletest) + endif() + endif() +endif() + +if(BUILD_MQT_QUSAT_BINDINGS) + # add pybind11_json library + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + FetchContent_Declare( + pybind11_json + GIT_REPOSITORY https://github.com/pybind/pybind11_json + FIND_PACKAGE_ARGS) + list(APPEND FETCH_PACKAGES pybind11_json) + else() + find_package(pybind11_json QUIET) + if(NOT pybind11_json_FOUND) + FetchContent_Declare(pybind11_json GIT_REPOSITORY https://github.com/pybind/pybind11_json) + list(APPEND FETCH_PACKAGES pybind11_json) + endif() + endif() +endif() + +# Make all declared dependencies available. +FetchContent_MakeAvailable(${FETCH_PACKAGES}) diff --git a/extern/mqt-core b/extern/mqt-core deleted file mode 160000 index 87f97a3..0000000 --- a/extern/mqt-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 87f97a38f9bcc61131bd891f3789ff18f2aaefc7 diff --git a/pyproject.toml b/pyproject.toml index 309c3e0..831e686 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["scikit-build-core>=0.6.1", "setuptools-scm>=7", "pybind11>=2.11"] +requires = ["scikit-build-core>=0.8.1", "setuptools-scm>=7", "pybind11>=2.12"] build-backend = "scikit_build_core.build" [project] @@ -46,24 +46,23 @@ Research = "https://www.cda.cit.tum.de/research/quantum" [tool.scikit-build] # Protect the configuration against future changes in scikit-build-core -minimum-version = "0.6.1" +minimum-version = "0.8.1" -# Set the target to build -cmake.targets = ["pyqusat"] +# Set the wheel install directory +wheel.install-dir = "mqt/qusat" # Set required CMake and Ninja versions -cmake.minimum-version = "3.19" -ninja.minimum-version = "1.10" +cmake.version = ">=3.19" +ninja.version = ">=1.10" # Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}" -# Build stable ABI wheels for CPython 3.12+ -wheel.py-api = "cp312" - # Explicitly set the package directory wheel.packages = ["src/mqt"] +install.components = ["mqt-qusat_Python"] + metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" sdist.include = ["src/mqt/qusat/_version.py"] sdist.exclude = [ @@ -221,20 +220,21 @@ build = "cp3*" skip = "*-musllinux*" archs = "auto64" test-command = "python -c \"from mqt import qusat\"" -test-skip = "cp312-*" # Qiskit Terra does not support Python 3.12 yet -build-frontend = "build" +test-skip = "cp38-macosx_arm64" +build-frontend = "build[uv]" [tool.cibuildwheel.linux] environment = { Z3_ROOT="/opt/python/cp311-cp311/lib/python3.11/site-packages/z3", DEPLOY="ON" } -before-all = "/opt/python/cp311-cp311/bin/pip install z3-solver==4.11.2" +before-all = "/opt/python/cp311-cp311/bin/pip install z3-solver==4.12.6" repair-wheel-command = [ "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/python/cp311-cp311/lib/python3.11/site-packages/z3/lib", "auditwheel repair -w {dest_dir} {wheel}", ] [tool.cibuildwheel.macos] -environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } [tool.cibuildwheel.windows] -before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -v -w {dest_dir} {wheel}" +before-build = "pip install delvewheel>=1.4.0" +repair-wheel-command = "delvewheel repair -v -w {dest_dir} {wheel} --namespace-pkg mqt" +environment = { CMAKE_GENERATOR = "Ninja" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3930d4d..ae158fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,3 @@ -# add MQT::Core target -set(BUILD_MQT_CORE_TESTS - OFF - CACHE BOOL "Build MQT Core tests") -add_subdirectory("${PROJECT_SOURCE_DIR}/extern/mqt-core" "extern/mqt-core" EXCLUDE_FROM_ALL) # main project library add_library(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/include/SatEncoder.hpp ${PROJECT_SOURCE_DIR}/include/Statistics.hpp SatEncoder.cpp) @@ -12,7 +7,10 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include) # link to the MQT::Core libraries -target_link_libraries(${PROJECT_NAME} PUBLIC MQT::Core) +target_link_libraries( + ${PROJECT_NAME} + PUBLIC MQT::Core nlohmann_json::nlohmann_json + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) # add z3 SMT solver target_link_libraries(${PROJECT_NAME} PUBLIC z3::z3lib) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 53364a9..92dad8c 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -1,44 +1,26 @@ -if(NOT SKBUILD) - message( - NOTICE - "\ - This CMake file is meant to be executed using 'scikit-build'. Running - it directly will almost certainly not produce the desired result. If - you are a user trying to install this package, please use the command - below, which will install all necessary build dependencies, compile - the package in an isolated environment, and then install it. - ===================================================================== - $ pip install . - ===================================================================== - If you are a software developer, and this is your own package, then - it is usually much more efficient to install the build dependencies - in your environment once and use the following command that avoids - a costly creation of a new virtual environment at every compilation: - ===================================================================== - $ pip install 'scikit-build-core[pyproject]' setuptools_scm pybind11 - $ pip install --no-build-isolation -ve . - ===================================================================== - You may optionally add -Ceditable.rebuild=true to auto-rebuild when - the package is imported. Otherwise, you need to re-run the above - after editing C++ files.") +if(APPLE) + set(BASEPOINT @loader_path) +else() + set(BASEPOINT $ORIGIN) endif() +list(APPEND CMAKE_INSTALL_RPATH ${BASEPOINT} ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -if(NOT SKBUILD) - # Manually detect the installed pybind11 package and import it into CMake. - execute_process( - COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE pybind11_DIR) - list(APPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") -endif() - -# Import pybind11 through CMake's find_package mechanism -find_package(pybind11 CONFIG REQUIRED) - -# We are now ready to compile the actual extension module -pybind11_add_module(py${PROJECT_NAME} bindings.cpp) +pybind11_add_module( + py${PROJECT_NAME} + # Prefer thin LTO if available + THIN_LTO + # Optimize the bindings for size + OPT_SIZE + # Source code goes here + bindings.cpp) target_compile_definitions(py${PROJECT_NAME} PRIVATE Z3_FOUND) -target_link_libraries(py${PROJECT_NAME} PRIVATE ${PROJECT_NAME} MQT::CorePython) +target_link_libraries(py${PROJECT_NAME} PRIVATE ${PROJECT_NAME} MQT::CorePython MQT::ProjectOptions + MQT::ProjectWarnings) # Install directive for scikit-build-core -install(TARGETS py${PROJECT_NAME} LIBRARY DESTINATION mqt/qusat) +install( + TARGETS py${PROJECT_NAME} + DESTINATION . + COMPONENT mqt-${PROJECT_NAME}_Python) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 65de093..a1001ca 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,11 +1 @@ -if(NOT TARGET gtest OR NOT TARGET gmock) - # Prevent overriding the parent project's compiler/linker settings on Windows - set(gtest_force_shared_crt # cmake-lint: disable=C0103 - ON - CACHE BOOL "" FORCE) - add_subdirectory("${PROJECT_SOURCE_DIR}/extern/mqt-core/extern/googletest" - "extern/mqt-core/extern/googletest" EXCLUDE_FROM_ALL) - set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES FOLDER extern) -endif() - package_add_test(${PROJECT_NAME}_test ${PROJECT_NAME} test_satencoder.cpp) diff --git a/test/test_satencoder.cpp b/test/test_satencoder.cpp index f58df01..934b1a8 100644 --- a/test/test_satencoder.cpp +++ b/test/test_satencoder.cpp @@ -8,6 +8,7 @@ #endif #include +#include #include #include