From 8b1b651037f8e999fb1b9f16956e8824b8b9443e Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 3 Oct 2023 15:59:42 +0100 Subject: [PATCH] Move build configuration to static configuration files This removes the vast majority of the dynamic code from our `setup.py` files, and instead uses static configuration to specify them. As part of this, we also use a single `VERSION.txt` as a single source of truth for both the `qiskit-terra` main package and the `qiskit` "metapackage". This should avoid a situation where we let the two get out of sync during a release, which can cause us severe problems for fixing the problem with respect to git tags. --- pyproject.toml | 130 ++++++++++++++++- .../passes/synthesis/high_level_synthesis.py | 4 +- qiskit/transpiler/passes/synthesis/plugin.py | 43 +++--- .../transpiler/preset_passmanagers/plugin.py | 30 ++-- qiskit/utils/optionals.py | 2 +- requirements-optional.txt | 2 +- setup.py | 138 ++---------------- tox.ini | 2 +- 8 files changed, 174 insertions(+), 177 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 172c0625d25b..aa8b651da478 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,137 @@ requires = ["setuptools", "wheel", "setuptools-rust"] build-backend = "setuptools.build_meta" +[project] +name = "qiskit" +description = "An open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives." +requires-python = ">=3.8" +license = { file = "LICENSE.txt" } +authors = [ + { name = "Qiskit Development Team", email = "hello@qiskit.org" }, +] +keywords = [ + "qiskit", + "quantum circuit", + "quantum computing", + "quantum programming language", + "quantum", + "sdk", +] +classifiers = [ + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering", +] +# These are configured in the `tool.setuptools.dynamic` table. +dynamic = ["version", "readme", "dependencies"] + +# If modifying this table, be sure to sync with `requirements-optional.txt`, +# `qiskit.utils.optionals`, and `qiskit_pkg`'s `pyproject.toml` too. +[project.optional-dependencies] +qasm3-import = [ + "qiskit-qasm3-import >= 0.1.0", +] +visualization = [ + "matplotlib >= 3.3", + "ipywidgets >= 7.3.0", + "pydot", + "Pillow >= 4.2.1", + "pylatexenc >= 1.4", + "seaborn >= 0.9.0", + "pygments >= 2.4", +] +crosstalk-pass = [ + "z3-solver >= 4.7", +] +csp-layout-pass = [ + "python-constraint >= 1.4", +] +# This will make the resolution work for installers from PyPI, but `pip install .[all]` will be +# unreliable because `qiskit-terra` will resolve to the PyPI version, so local changes in the +# optionals won't be reflected. +all = ["qiskit[qasm3-import,visualization,crosstalk-pass,csp-layout-pass]"] + +[project.urls] +Homepage = "https://qiskit.org" +Documentation = "https://qiskit.org/documentation" +"Source Code" = "https://github.com/Qiskit/qiskit" +"Bug Tracker" = "https://github.com/Qiskit/qiskit/issues" +Changelog = "https://qiskit.org/documentation/release_notes.html" + +[project.entry-points."qiskit.unitary_synthesis"] +default = "qiskit.transpiler.passes.synthesis.unitary_synthesis:DefaultUnitarySynthesis" +aqc = "qiskit.transpiler.synthesis.aqc.aqc_plugin:AQCSynthesisPlugin" +sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevSynthesis" + +[project.entry-points."qiskit.synthesis"] +"clifford.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisClifford" +"clifford.ag" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:AGSynthesisClifford" +"clifford.bm" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BMSynthesisClifford" +"clifford.greedy" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:GreedySynthesisClifford" +"clifford.layers" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerSynthesisClifford" +"clifford.lnn" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerLnnSynthesisClifford" +"linear_function.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisLinearFunction" +"linear_function.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisLinearFunction" +"linear_function.pmh" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:PMHSynthesisLinearFunction" +"permutation.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation" +"permutation.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation" +"permutation.basic" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation" +"permutation.acg" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation" + +[project.entry-points."qiskit.transpiler.init"] +default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager" + +[project.entry-points."qiskit.transpiler.translation"] +synthesis = "qiskit.transpiler.preset_passmanagers.builtin_plugins:UnitarySynthesisPassManager" +translator = "qiskit.transpiler.preset_passmanagers.builtin_plugins:BasisTranslatorPassManager" +unroller = "qiskit.transpiler.preset_passmanagers.builtin_plugins:UnrollerPassManager" + +[project.entry-points."qiskit.transpiler.routing"] +basic = "qiskit.transpiler.preset_passmanagers.builtin_plugins:BasicSwapPassManager" +lookahead = "qiskit.transpiler.preset_passmanagers.builtin_plugins:LookaheadSwapPassManager" +none = "qiskit.transpiler.preset_passmanagers.builtin_plugins:NoneRoutingPassManager" +sabre = "qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreSwapPassManager" +stochastic = "qiskit.transpiler.preset_passmanagers.builtin_plugins:StochasticSwapPassManager" + +[project.entry-points."qiskit.transpiler.optimization"] +default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:OptimizationPassManager" + +[project.entry-points."qiskit.transpiler.layout"] +default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultLayoutPassManager" +dense = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DenseLayoutPassManager" +noise_adaptive = "qiskit.transpiler.preset_passmanagers.builtin_plugins:NoiseAdaptiveLayoutPassManager" +sabre = "qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreLayoutPassManager" +trivial = "qiskit.transpiler.preset_passmanagers.builtin_plugins:TrivialLayoutPassManager" + +[project.entry-points."qiskit.transpiler.scheduling"] +alap = "qiskit.transpiler.preset_passmanagers.builtin_plugins:AlapSchedulingPassManager" +asap = "qiskit.transpiler.preset_passmanagers.builtin_plugins:AsapSchedulingPassManager" +default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultSchedulingPassManager" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.dynamic] +version = { file = "qiskit/VERSION.txt" } +readme = { file = "README.md", content-type = "text/markdown" } +dependencies = {file = "requirements.txt" } + +[tool.setuptools.packages.find] +include = ["qiskit", "qiskit.*"] + [tool.black] line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 22bcf5abb127..2843ef077378 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -74,8 +74,8 @@ class HLSConfig: hls_config = HLSConfig(permutation=[(ACGSynthesisPermutation(), {})]) hls_config = HLSConfig(permutation=[ACGSynthesisPermutation()]) - The names of the synthesis algorithms should be declared in ``entry_points`` for - ``qiskit.synthesis`` in ``setup.py``, in the form + The names of the synthesis algorithms should be declared in ``entry-points`` table for + ``qiskit.synthesis`` in ``pyproject.toml``, in the form .. The standard higher-level-objects are recommended to have a synthesis method diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 2aaf96c59eab..4154c0186430 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -130,18 +130,15 @@ def run(self, unitary, **options): The second step is to expose the :class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin` as a setuptools entry point in the package metadata. This is done by simply adding -an ``entry_points`` entry to the ``setuptools.setup`` call in the ``setup.py`` -for the plugin package with the necessary entry points under the -``qiskit.unitary_synthesis`` namespace. For example:: - - entry_points = { - 'qiskit.unitary_synthesis': [ - 'special = qiskit_plugin_pkg.module.plugin:SpecialUnitarySynthesis', - ] - }, - -(note that the entry point ``name = path`` is a single string not a Python -expression). There isn't a limit to the number of plugins a single package can +an ``entry-points`` table in ``pyproject.toml`` for the plugin package with the necessary entry +points under the ``qiskit.unitary_synthesis`` namespace. For example: + +.. code-block:: toml + + [project.entry-points."qiskit.unitary-synthesis"] + "special" = "qiskit_plugin_pkg.module.plugin:SpecialUnitarySynthesis" + +There isn't a limit to the number of plugins a single package can include as long as each plugin has a unique name. So a single package can expose multiple plugins if necessary. The name ``default`` is used by Qiskit itself and can't be used in a plugin. @@ -218,19 +215,15 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** The second step is to expose the :class:`~qiskit.transpiler.passes.synthesis.plugin.HighLevelSynthesisPlugin` as a setuptools entry point in the package metadata. This is done by adding -an ``entry_points`` entry to the ``setuptools.setup`` call in the ``setup.py`` -for the plugin package with the necessary entry points under the -``qiskit.synthesis`` namespace. For example:: - - entry_points = { - 'qiskit.synthesis': [ - 'clifford.special = qiskit_plugin_pkg.module.plugin:SpecialSynthesisClifford', - ] - }, - -(note that the entry point ``name = path`` is a single string not a Python -expression). The ``name`` consists of two parts separated by dot ".": the -name of the +an ``entry-points`` table in ``pyproject.toml`` for the plugin package with the necessary entry +points under the ``qiskit.unitary_synthesis`` namespace. For example: + +.. code-block:: toml + + [project.entry-points."qiskit.synthesis"] + "clifford.special" = "qiskit_plugin_pkg.module.plugin:SpecialSynthesisClifford" + +The ``name`` consists of two parts separated by dot ".": the name of the type of :class:`~qiskit.circuit.Operation` to which the synthesis plugin applies (``clifford``), and the name of the plugin (``special``). There isn't a limit to the number of plugins a single package can diff --git a/qiskit/transpiler/preset_passmanagers/plugin.py b/qiskit/transpiler/preset_passmanagers/plugin.py index e0fabffc9d85..e80bfd92078b 100644 --- a/qiskit/transpiler/preset_passmanagers/plugin.py +++ b/qiskit/transpiler/preset_passmanagers/plugin.py @@ -134,23 +134,19 @@ def pass_manager(self, pass_manager_config, optimization_level): The second step is to expose the :class:`~.PassManagerStagePlugin` subclass as a setuptools entry point in the package metadata. This can be done -by simply adding an ``entry_points`` entry to the ``setuptools.setup`` call in -the ``setup.py`` or the plugin package with the necessary entry points under the -appropriate namespace for the stage your plugin is for. You can see the list -of stages, entry points, and expectations from the stage in :ref:`stage_table`. -For example, continuing from the example plugin above:: - - entry_points = { - 'qiskit.transpiler.layout': [ - 'vf2 = qiskit_plugin_pkg.module.plugin:VF2LayoutPlugin', - ] - }, - -Note that the entry point ``name = path`` is a single string not a Python -expression. There isn't a limit to the number of plugins a single package can -include as long as each plugin has a unique name. So a single package can -expose multiple plugins if necessary. Refer to :ref:`stage_table` for a list -of reserved names for each stage. +an ``entry-points`` table in ``pyproject.toml`` for the plugin package with the necessary entry +points under the appropriate namespace for the stage your plugin is for. You can see the list of +stages, entry points, and expectations from the stage in :ref:`stage_table`. For example, +continuing from the example plugin above:: + +.. code-block:: toml + + [project.entry-points."qiskit.transpiler.layout"] + "vf2" = "qiskit_plugin_pkg.module.plugin:VF2LayoutPlugin" + +There isn't a limit to the number of plugins a single package can include as long as each plugin has +a unique name. So a single package can expose multiple plugins if necessary. Refer to +:ref:`stage_table` for a list of reserved names for each stage. Plugin API ========== diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index 07dc6e4376e5..cb69c6b5136c 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -212,7 +212,7 @@ """ # NOTE: If you're changing this file, sync it with `requirements-optional.txt` and potentially -# `setup.py` as well. +# `pyproject.toml` as well. import logging as _logging diff --git a/requirements-optional.txt b/requirements-optional.txt index 6afa25271ab9..031bdf584a70 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -3,7 +3,7 @@ # standard pip conventions, even though none of these are required. # # If updating this, you probably want to update `qiskit.utils.optionals` and -# maybe `setup.py` too. +# maybe `pyproject.toml` too. # Test-runner enhancements. fixtures diff --git a/setup.py b/setup.py index 55950efef410..91fb20c83cf2 100644 --- a/setup.py +++ b/setup.py @@ -13,91 +13,24 @@ "The Qiskit Terra setup file." import os -import re -from setuptools import setup, find_packages +from setuptools import setup from setuptools_rust import Binding, RustExtension +# Most of this configuration is managed by `pyproject.toml`. This only includes the extra bits to +# configure `setuptools-rust`, because we do a little dynamic trick with the debug setting, and we +# also want an explicit `setup.py` file to exist so we can manually call +# +# python setup.py build_rust --inplace --release +# +# to make optimised Rust components even for editable releases, which would otherwise be quite +# unergonomic to do otherwise. -with open("requirements.txt") as f: - REQUIREMENTS = f.read().splitlines() - -# Read long description from README. -README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md") -with open(README_PATH) as readme_file: - README = re.sub( - ".*", - "", - readme_file.read(), - flags=re.S | re.M, - ) # If RUST_DEBUG is set, force compiling in debug mode. Else, use the default behavior of whether # it's an editable installation. rust_debug = True if os.getenv("RUST_DEBUG") == "1" else None -# If modifying these optional extras, make sure to sync with `requirements-optional.txt` and -# `qiskit.utils.optionals` as well. -qasm3_import_extras = [ - "qiskit-qasm3-import>=0.1.0", -] -visualization_extras = [ - "matplotlib>=3.3", - "ipywidgets>=7.3.0", - "pydot", - "pillow>=4.2.1", - "pylatexenc>=1.4", - "seaborn>=0.9.0", - "pygments>=2.4", -] -z3_requirements = [ - "z3-solver>=4.7", -] -csp_requirements = ["python-constraint>=1.4"] - - setup( - name="qiskit", - version="1.0.0", - description="Software for developing quantum computing programs", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/Qiskit/qiskit", - author="Qiskit Development Team", - author_email="hello@qiskit.org", - license="Apache 2.0", - classifiers=[ - "Environment :: Console", - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Scientific/Engineering", - ], - keywords="qiskit sdk quantum", - packages=find_packages(exclude=["test*"]), - install_requires=REQUIREMENTS, - include_package_data=True, - python_requires=">=3.8", - extras_require={ - "qasm3-import": qasm3_import_extras, - "visualization": visualization_extras, - "crosstalk-pass": z3_requirements, - "csp-layout-pass": csp_requirements, - "all": visualization_extras + z3_requirements + csp_requirements + qasm3_import_extras, - }, - project_urls={ - "Bug Tracker": "https://github.com/Qiskit/qiskit-terra/issues", - "Documentation": "https://qiskit.org/documentation/", - "Source Code": "https://github.com/Qiskit/qiskit-terra", - }, rust_extensions=[ RustExtension( "qiskit._accelerate", @@ -113,57 +46,4 @@ ), ], options={"bdist_wheel": {"py_limited_api": "cp38"}}, - zip_safe=False, - entry_points={ - "qiskit.unitary_synthesis": [ - "default = qiskit.transpiler.passes.synthesis.unitary_synthesis:DefaultUnitarySynthesis", - "aqc = qiskit.transpiler.synthesis.aqc.aqc_plugin:AQCSynthesisPlugin", - "sk = qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevSynthesis", - ], - "qiskit.synthesis": [ - "clifford.default = qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisClifford", - "clifford.ag = qiskit.transpiler.passes.synthesis.high_level_synthesis:AGSynthesisClifford", - "clifford.bm = qiskit.transpiler.passes.synthesis.high_level_synthesis:BMSynthesisClifford", - "clifford.greedy = qiskit.transpiler.passes.synthesis.high_level_synthesis:GreedySynthesisClifford", - "clifford.layers = qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerSynthesisClifford", - "clifford.lnn = qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerLnnSynthesisClifford", - "linear_function.default = qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisLinearFunction", - "linear_function.kms = qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisLinearFunction", - "linear_function.pmh = qiskit.transpiler.passes.synthesis.high_level_synthesis:PMHSynthesisLinearFunction", - "permutation.default = qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation", - "permutation.kms = qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation", - "permutation.basic = qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation", - "permutation.acg = qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation", - ], - "qiskit.transpiler.init": [ - "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager", - ], - "qiskit.transpiler.translation": [ - "translator = qiskit.transpiler.preset_passmanagers.builtin_plugins:BasisTranslatorPassManager", - "unroller = qiskit.transpiler.preset_passmanagers.builtin_plugins:UnrollerPassManager", - "synthesis = qiskit.transpiler.preset_passmanagers.builtin_plugins:UnitarySynthesisPassManager", - ], - "qiskit.transpiler.routing": [ - "basic = qiskit.transpiler.preset_passmanagers.builtin_plugins:BasicSwapPassManager", - "stochastic = qiskit.transpiler.preset_passmanagers.builtin_plugins:StochasticSwapPassManager", - "lookahead = qiskit.transpiler.preset_passmanagers.builtin_plugins:LookaheadSwapPassManager", - "sabre = qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreSwapPassManager", - "none = qiskit.transpiler.preset_passmanagers.builtin_plugins:NoneRoutingPassManager", - ], - "qiskit.transpiler.optimization": [ - "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:OptimizationPassManager", - ], - "qiskit.transpiler.layout": [ - "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultLayoutPassManager", - "trivial = qiskit.transpiler.preset_passmanagers.builtin_plugins:TrivialLayoutPassManager", - "dense = qiskit.transpiler.preset_passmanagers.builtin_plugins:DenseLayoutPassManager", - "noise_adaptive = qiskit.transpiler.preset_passmanagers.builtin_plugins:NoiseAdaptiveLayoutPassManager", - "sabre = qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreLayoutPassManager", - ], - "qiskit.transpiler.scheduling": [ - "alap = qiskit.transpiler.preset_passmanagers.builtin_plugins:AlapSchedulingPassManager", - "asap = qiskit.transpiler.preset_passmanagers.builtin_plugins:AsapSchedulingPassManager", - "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultSchedulingPassManager", - ], - }, ) diff --git a/tox.ini b/tox.ini index df46820fc026..b790f698e034 100644 --- a/tox.ini +++ b/tox.ini @@ -41,7 +41,7 @@ allowlist_externals = git commands = ruff check qiskit test tools examples setup.py black --check {posargs} qiskit test tools examples setup.py - -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest + -git fetch -q https://github.com/Qiskit/qiskit.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --disable='invalid-name,missing-module-docstring,redefined-outer-name' --paths :(glob,top)examples/python/*.py python {toxinidir}/tools/verify_headers.py qiskit test tools examples