Skip to content

Commit

Permalink
✨ ship shared C++ libraries with mqt-core Python package (#662)
Browse files Browse the repository at this point in the history
## Description

This PR adapts the `mqt.core` Python package to also ship the complete
MQT Core C++ library in a compact form so that the Python package can be
used as the single source for the C++ and the Python side in top-level
projects.
In turn, the `mqt-core-python` target is (finally) removed, which was
long overdue.
Top-level projects are now expected to rely on the mqt-core Python
package for importing circuits from Qiskit.

To minimise the growth of the mqt-core Python wheels, Python package
build will now build shared libraries as opposed to static libraries.
The resulting wheels now have a couple megabytes, which should still be
reasonable.
However, to minimise the risk of running into space issues on PyPI
further, this PR stops building emulated wheels for the `mqt-core`
Python package. We have no real signs that people are using them in any
meaningful way and they take forever to build. Hence, removing them
seams like a win-win.

Given the potential breaking nature of the discontinuation of the
`mqt-core-python` target, the next release after this release is merged
will be `v3.0`. I'd expect a couple more breaking changes to come in
over the next couple of weeks before that release though.

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.
  • Loading branch information
burgholzer authored Jan 14, 2025
2 parents fe97778 + fdb61f0 commit ce5f0ae
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 599 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CD
name: CD 🚀
on:
push:
branches: [main]
Expand All @@ -21,8 +21,8 @@ jobs:
with:
# Do not include local version information on pushes to main to facilitate TestPyPI uploads.
no-local-version: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
# Do not build emulated wheels on pushes to main to reduce runner load for CD.
build-emulated-wheels: ${{ github.ref != 'refs/heads/main' || github.event_name != 'push' }}
# Do not build emulated wheels for mqt-core to avoid issues with PyPI space limitations.
build-emulated-wheels: false

# Downloads the previously generated artifacts and deploys to TestPyPI on pushes to main
deploy-test-pypi:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
needs: change-detection
if: fromJSON(needs.change-detection.outputs.run-cd)
uses: cda-tum/mqt-workflows/.github/workflows/reusable-python-packaging.yml@v1.5
with:
# Do not build emulated wheels for mqt-core to avoid issues with PyPI space limitations.
build-emulated-wheels: false

required-checks-pass: # This job does nothing and is only used for branch protection
name: 🚦 Check
Expand Down
4 changes: 0 additions & 4 deletions cmake/CompilerOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ function(enable_project_options target_name)

option(BINDINGS "Configure for building Python bindings")
if(BINDINGS)
check_cxx_compiler_flag(-fvisibility=hidden HAS_VISIBILITY_HIDDEN)
if(HAS_VISIBILITY_HIDDEN)
target_compile_options(${target_name} INTERFACE -fvisibility=hidden)
endif()
include(CheckPIESupported)
check_pie_supported()
set_target_properties(${target_name} PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON)
Expand Down
5 changes: 5 additions & 0 deletions cmake/StandardProjectSettings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ if(ENABLE_IPO)
message(DEBUG "IPO is not supported: ${ipo_output}")
endif()
endif()

# export all symbols by default on Windows
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS
ON
CACHE BOOL "Export all symbols on Windows")
7 changes: 4 additions & 3 deletions include/mqt-core/dd/RealNumber.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#pragma once

#include "dd/DDDefinitions.hpp"
#include "mqt_core_dd_export.h"

#include <istream>
#include <limits>
Expand Down Expand Up @@ -239,11 +240,11 @@ struct RealNumber {
namespace constants {
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
/// The constant zero.
extern RealNumber zero;
MQT_CORE_DD_EXPORT extern RealNumber zero;
/// The constant one.
extern RealNumber one;
MQT_CORE_DD_EXPORT extern RealNumber one;
/// The constant sqrt(2)/2 = 1/sqrt(2).
extern RealNumber sqrt2over2;
MQT_CORE_DD_EXPORT extern RealNumber sqrt2over2;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)

/**
Expand Down
56 changes: 0 additions & 56 deletions include/mqt-core/python/qiskit/QuantumCircuit.hpp

This file was deleted.

29 changes: 22 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ qiskit = [

[project.scripts]
mqt-core-dd-compare = "mqt.core.dd.evaluation:main"
mqt-core-cli = "mqt.core.__main__:main"

[project.urls]
Homepage = "https://github.com/cda-tum/mqt-core"
Expand All @@ -68,11 +69,17 @@ ninja.version = ">=1.10"
# Setuptools-style build caching in a local directory
build-dir = "build/{wheel_tag}/{build_type}"

# Only build the Python bindings target
build.targets = ["ir"]

# Only install the Python package component
install.components = ["mqt-core_Python"]
# All the targets to build
build.targets = [
"mqt-core-ir",
"mqt-core-algorithms",
"mqt-core-circuit-optimizer",
"mqt-core-dd",
"mqt-core-zx",
"mqt-core-ds",
"mqt-core-na",
"ir",
]

metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"
sdist.include = ["src/mqt/core/_version.py"]
Expand All @@ -96,6 +103,7 @@ git-only = [
[tool.scikit-build.cmake.define]
BUILD_MQT_CORE_BINDINGS = "ON"
BUILD_MQT_CORE_TESTS = "OFF"
BUILD_SHARED_LIBS = "ON"

[[tool.scikit-build.overrides]]
if.python-version = ">=3.13"
Expand All @@ -109,6 +117,13 @@ cmake.define.DISABLE_GIL = "1"
write_to = "src/mqt/core/_version.py"


[tool.check-wheel-contents]
ignore = [
"W002", # Wheel contains duplicate files (.so symlinks)
"W004", # Module is not located at importable path (/lib/mqt-core-*.so)
]


[tool.pytest.ini_options]
minversion = "7.2"
addopts = ["-ra", "--strict-markers", "--strict-config"]
Expand Down Expand Up @@ -238,8 +253,8 @@ environment = { DEPLOY = "ON" }
environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" }

[tool.cibuildwheel.windows]
before-build = "uv pip install delvewheel>=1.7.3"
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt"
before-build = "uv pip install delvewheel>=1.9.0"
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing"
environment = { CMAKE_GENERATOR = "Ninja" }

[[tool.cibuildwheel.overrides]]
Expand Down
24 changes: 0 additions & 24 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,6 @@ add_subdirectory(zx)
# add NA library
add_subdirectory(na)

# ** Note ** The following target will soon be removed from the project. All top-level projects
# should switch to using the mqt-core Python package.
if(BINDINGS AND NOT TARGET mqt-core-python)
# add Python interface library
add_library(
${MQT_CORE_TARGET_NAME}-python ${MQT_CORE_INCLUDE_BUILD_DIR}/python/qiskit/QuantumCircuit.hpp
python/qiskit/QuantumCircuit.cpp)

# link with main project library and pybind11 libraries
target_link_libraries(
${MQT_CORE_TARGET_NAME}-python
PUBLIC MQT::CoreIR pybind11::pybind11
PRIVATE MQT::ProjectOptions MQT::ProjectWarnings)

# add MQT alias
add_library(MQT::CorePython ALIAS ${MQT_CORE_TARGET_NAME}-python)
set_target_properties(
${MQT_CORE_TARGET_NAME}-python
PROPERTIES VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
EXPORT_NAME CorePython)
list(APPEND MQT_CORE_TARGETS ${MQT_CORE_TARGET_NAME}-python)
endif()

if(BUILD_MQT_CORE_BINDINGS)
add_subdirectory(python)
endif()
Expand Down
15 changes: 15 additions & 0 deletions src/mqt/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@
from __future__ import annotations

import os
import sys
from pathlib import Path

# under Windows, make sure to add the appropriate DLL directory to the PATH
if sys.platform == "win32":

def _dll_patch() -> None:
"""Add the DLL directory to the PATH."""
import sysconfig

bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin"
os.add_dll_directory(str(bin_dir))

_dll_patch()
del _dll_patch

from typing import TYPE_CHECKING

from ._version import version as __version__
Expand Down
54 changes: 54 additions & 0 deletions src/mqt/core/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2025 Chair for Design Automation, TUM
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Command line interface for mqt-core."""

from __future__ import annotations

import argparse
import sys

from ._commands import cmake_dir, include_dir
from ._version import version as __version__


def main() -> None:
"""Entry point for the mqt-core command line interface.
This function is called when running the `mqt-core-cli` script.
.. code-block:: bash
mqt-core-cli [--version] [--include_dir] [--cmake_dir]
It provides the following command line options:
- :code:`--version`: Print the version and exit.
- :code:`--include_dir`: Print the path to the mqt-core C++ include directory.
- :code:`--cmake_dir`: Print the path to the mqt-core CMake module directory.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--version", action="version", version=__version__, help="Print version and exit.")

parser.add_argument(
"--include_dir", action="store_true", help="Print the path to the mqt-core C++ include directory."
)
parser.add_argument(
"--cmake_dir", action="store_true", help="Print the path to the mqt-core CMake module directory."
)
args = parser.parse_args()
if not sys.argv[1:]:
parser.print_help()
if args.include_dir:
print(include_dir().resolve())
if args.cmake_dir:
print(cmake_dir().resolve())


if __name__ == "__main__":
main()
51 changes: 51 additions & 0 deletions src/mqt/core/_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (c) 2025 Chair for Design Automation, TUM
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Useful commands for obtaining information about mqt-core."""

from __future__ import annotations

from importlib.metadata import PackageNotFoundError, distribution
from pathlib import Path


def include_dir() -> Path:
"""Return the path to the mqt-core include directory.
Raises:
FileNotFoundError: If the include directory is not found.
ImportError: If mqt-core is not installed.
"""
try:
dist = distribution("mqt-core")
located_include_dir = Path(dist.locate_file("mqt/core/include/mqt-core"))
if located_include_dir.exists() and located_include_dir.is_dir():
return located_include_dir
msg = "mqt-core include files not found."
raise FileNotFoundError(msg)
except PackageNotFoundError:
msg = "mqt-core not installed, installation required to access the include files."
raise ImportError(msg) from None


def cmake_dir() -> Path:
"""Return the path to the mqt-core CMake module directory.
Raises:
FileNotFoundError: If the CMake module directory is not found.
ImportError: If mqt-core is not installed.
"""
try:
dist = distribution("mqt-core")
located_cmake_dir = Path(dist.locate_file("mqt/core/share/cmake"))
if located_cmake_dir.exists() and located_cmake_dir.is_dir():
return located_cmake_dir
msg = "mqt-core CMake files not found."
raise FileNotFoundError(msg)
except PackageNotFoundError:
msg = "mqt-core not installed, installation required to access the CMake files."
raise ImportError(msg) from None
Loading

0 comments on commit ce5f0ae

Please sign in to comment.