Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️📦 Modernize Python packaging #208

Merged
merged 2 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
cmake_minimum_required(VERSION 3.14...3.22)
cmake_minimum_required(VERSION 3.14...3.24)

project(qfr
LANGUAGES CXX
VERSION 1.10.0
DESCRIPTION "MQT QFR - A library for Quantum Functionality Representation"
)

Expand Down
1 change: 1 addition & 0 deletions mqt/qfr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ add_library(MQT::${PROJECT_NAME}_python ALIAS ${PROJECT_NAME}_python)
# add pyqfr module
pybind11_add_module(py${PROJECT_NAME} bindings.cpp)
target_link_libraries(py${PROJECT_NAME} PUBLIC ${PROJECT_NAME} MQT::${PROJECT_NAME}_python pybind11_json)
target_compile_definitions(py${PROJECT_NAME} PRIVATE VERSION_INFO=${QFR_VERSION_INFO})
enable_lto(py${PROJECT_NAME})
5 changes: 4 additions & 1 deletion mqt/qfr/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)

namespace py = pybind11;
using namespace pybind11::literals;

Expand Down Expand Up @@ -177,7 +180,7 @@ PYBIND11_MODULE(pyqfr, m) {
"serialized_dd"_a);

#ifdef VERSION_INFO
m.attr("__version__") = VERSION_INFO;
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#else
m.attr("__version__") = "dev";
#endif
Expand Down
47 changes: 46 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
[build-system]
requires = ["setuptools", "wheel", "cmake"]
requires = [
"setuptools>=61",
"setuptools_scm[toml]>=6.4",
"ninja>=1.10; sys_platform != 'win32'",
"cmake>=3.14",
]
build-backend = "setuptools.build_meta"

[project]
name = "mqt.qfr"
description = "A tool for Quantum Functionality Representation"
readme = "README.md"
authors = [
{ name = "Lukas Burgholzer", email = "[email protected]"},
]
keywords = ["MQT", "quantum computing", "design automation"]
license = { file = "LICENSE.md" }

classifiers=[
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: C++",
"License :: OSI Approved :: MIT License",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Intended Audience :: Science/Research",
"Natural Language :: English",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
]
requires-python = ">=3.7"
dynamic = ["version"]

[project.urls]
Homepage = "https://github.com/cda-tum/qfr"
"Bug Tracker" = "https://github.com/cda-tum/qfr/issues"
Discussions = "https://github.com/cda-tum/qfr/discussions"

[tool.setuptools.packages.find]
include = ["mqt.*"]

[tool.setuptools_scm]

[tool.cibuildwheel]
build = "cp3*"
skip = "*-win32 *-musllinux_i686 *-manylinux_i686"
Expand Down
148 changes: 65 additions & 83 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,109 +1,91 @@
import os
import sys
import platform
import re
import subprocess
import sys
from contextlib import suppress
from pathlib import Path

from setuptools import setup, Extension, find_namespace_packages
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext


class CMakeExtension(Extension):
def __init__(self, name, sourcedir='', namespace=''):
def __init__(self, name, sourcedir=""):
Extension.__init__(self, name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)
self.namespace = namespace
self.sourcedir = str(Path(sourcedir).resolve())


class CMakeBuild(build_ext):
def run(self):
try:
subprocess.check_output(['cmake', '--version'])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))
def build_extension(self, ext):
from setuptools_scm import get_version

for ext in self.extensions:
self.build_extension(ext)
version = get_version(relative_to=__file__)

def build_extension(self, ext):
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.namespace+ext.name)))
# required for auto-detection of auxiliary "native" libs
if not extdir.endswith(os.path.sep):
extdir += os.path.sep

cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
'-DPYTHON_EXECUTABLE=' + sys.executable,
'-DBINDINGS=ON']

cfg = 'Debug' if self.debug else 'Release'
build_args = ['--config', cfg]

if platform.system() == "Windows":
cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
cmake_args += ['-T', 'ClangCl']
if sys.maxsize > 2 ** 32:
cmake_args += ['-A', 'x64']
build_args += ['--', '/m']
extdir = str(Path(self.get_ext_fullpath(ext.name)).parent.resolve())

cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
cfg = "Debug" if self.debug else "Release"

cmake_args = [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
f"-DQFR_VERSION_INFO={version}",
f"-DCMAKE_BUILD_TYPE={cfg}",
"-DBINDINGS=ON",
]
build_args = []

if self.compiler.compiler_type != "msvc":
if not cmake_generator:
cmake_args += ["-GNinja"]
else:
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
cpus = os.cpu_count()
if cpus is None:
cpus = 2
build_args += ['--', '-j{}'.format(cpus)]
# Single config generators are handled "normally"
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
# CMake allows an arch-in-generator style for backward compatibility
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
# Convert distutils Windows platform specifiers to CMake -A arguments
plat_to_cmake = {
"win32": "Win32",
"win-amd64": "x64",
"win-arm32": "ARM",
"win-arm64": "ARM64",
}
# Specify the arch if using MSVC generator, but only if it doesn't
# contain a backward-compatibility arch spec already in the
# generator name.
if not single_config and not contains_arch:
cmake_args += ["-A", plat_to_cmake[self.plat_name]]
# Multi-config generators have a different way to specify configs
if not single_config:
cmake_args += [f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"]
build_args += ["--config", cfg]

# cross-compile support for macOS - respect ARCHFLAGS if set
if sys.platform.startswith("darwin"):
archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
if archs:
arch_argument = "-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))
print('macOS building with: ', arch_argument, flush=True)
cmake_args += [arch_argument]
cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]

# Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level across all generators.
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ and hasattr(self, "parallel") and self.parallel:
build_args += [f"-j{self.parallel}"]

if sys.platform == "win32":
cmake_args += ["-T", "ClangCl"]

env = os.environ.copy()
env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''),
self.distribution.get_version())
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
subprocess.check_call(['cmake', '--build', '.', '--target', ext.name] + build_args, cwd=self.build_temp)
build_dir = Path(self.build_temp)
build_dir.mkdir(parents=True, exist_ok=True)
with suppress(FileNotFoundError):
Path(build_dir / "CMakeCache.txt").unlink()

subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp)
subprocess.check_call(
["cmake", "--build", ".", "--target", ext.name.split(".")[-1]] + build_args,
cwd=self.build_temp,
)

README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'README.md')
with open(README_PATH) as readme_file:
README = readme_file.read()

setup(
name='mqt.qfr',
version='1.10.0',
author='Lukas Burgholzer',
author_email='[email protected]',
description='MQT QFR - A tool for Quantum Functionality Representation',
long_description=README,
long_description_content_type="text/markdown",
license="MIT",
url="https://iic.jku.at/eda/research/quantum_dd",
ext_modules=[CMakeExtension('pyqfr', namespace='mqt.qfr.')],
cmdclass=dict(build_ext=CMakeBuild),
zip_safe=False,
packages=find_namespace_packages(include=['mqt.*']),
classifiers=[
'Development Status :: 5 - Production/Stable',
"Programming Language :: Python :: 3",
"Programming Language :: C++",
"License :: OSI Approved :: MIT License",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Intended Audience :: Science/Research",
"Natural Language :: English",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
],
keywords="mqt quantum decision_diagrams",
project_urls={
'Source': 'https://github.com/cda-tum/qfr/',
'Tracker': 'https://github.com/cda-tum/qfr/issues',
'Research': 'https://iic.jku.at/eda/research/quantum_dd',
}
ext_modules=[CMakeExtension("mqt.qfr.pyqfr")],
cmdclass={"build_ext": CMakeBuild},
)