diff --git a/.github/workflows/test_development_versions.yml b/.github/workflows/test_development_versions.yml index faf9a07d5..41aa68e2b 100644 --- a/.github/workflows/test_development_versions.yml +++ b/.github/workflows/test_development_versions.yml @@ -30,7 +30,7 @@ jobs: shell: bash run: | python -m pip install --upgrade pip tox - python -m pip install ./tools/extremal-python-dependencies + python -m pip install extremal-python-dependencies==0.0.3 extremal-python-dependencies pin-dependencies \ "qiskit @ git+https://github.com/Qiskit/qiskit.git" \ "qiskit-ibm-runtime @ git+https://github.com/Qiskit/qiskit-ibm-runtime.git" \ diff --git a/.github/workflows/test_minimum_versions.yml b/.github/workflows/test_minimum_versions.yml index 0095b4963..646766ee4 100644 --- a/.github/workflows/test_minimum_versions.yml +++ b/.github/workflows/test_minimum_versions.yml @@ -28,7 +28,7 @@ jobs: shell: bash run: | python -m pip install --upgrade pip - python -m pip install ./tools/extremal-python-dependencies + python -m pip install extremal-python-dependencies==0.0.3 pip install "tox==$(extremal-python-dependencies get-tox-minversion)" extremal-python-dependencies pin-dependencies-to-minimum --inplace - name: Modify tox.ini for more thorough check diff --git a/tools/extremal-python-dependencies/README.md b/tools/extremal-python-dependencies/README.md deleted file mode 100644 index 397cb58ac..000000000 --- a/tools/extremal-python-dependencies/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# extremal-python-dependencies - -Install extremal versions of package dependencies for more robust continuous integration testing, given a package that specifies its dependencies in a `pyproject.toml` file. - -For instance, one might use this utility to install the minimum supported version of each dependency before a CI run. Ensuring all tests then pass ensures that the code is indeed compatible with the range of package versions it claims to be compatible with, helping to prevent users from encountering broken installs. - -Another way to use this tool is to install development versions of certain packages. - -This utility works with dependencies specified in a `pyproject.toml` file. It modifies `pyproject.toml`, either by sending the transformed version to stdout (the default) or by modifying in place (which may be useful in CI scripts). - -## How to use - -The following snippet modifies `pyproject.toml` in place to test with the minimum supported version of each direct dependency, under the minimum supported [tox](https://tox.wiki/) version (as specified by `minversion` in `tox.ini`). - -```sh -pip install "tox==$(extremal-python-dependencies get-tox-minversion)" -extremal-python-dependencies pin-dependencies-to-minimum --inplace -tox -epy -``` - -The following snippet modifies `pyproject.toml` in place to test with the development version of one or more dependencies: - -```sh -extremal-python-dependencies pin-dependencies \ - "qiskit @ git+https://github.com/Qiskit/qiskit.git" \ - "qiskit-ibm-runtime @ git+https://github.com/Qiskit/qiskit-ibm-runtime.git" \ - --inplace -tox -epy -``` - -Each of the above patterns can be used in a CI script. - -## Caveats - -- The minimum versions of all optional dependencies installed simultaneously must be compatible with each other. -- This tool does not set the minimum supported version of transitive dependencies. - -## Similar tools - -- [requirements-builder](https://requirements-builder.readthedocs.io/) (builds requirements from a `setup.py` file instead of a `pyproject.toml` file) diff --git a/tools/extremal-python-dependencies/extremal_python_dependencies/__init__.py b/tools/extremal-python-dependencies/extremal_python_dependencies/__init__.py deleted file mode 100644 index 75efffef4..000000000 --- a/tools/extremal-python-dependencies/extremal_python_dependencies/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# This code is a Qiskit project. - -# (C) Copyright IBM 2024. - -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/tools/extremal-python-dependencies/extremal_python_dependencies/main.py b/tools/extremal-python-dependencies/extremal_python_dependencies/main.py deleted file mode 100644 index 9f962331c..000000000 --- a/tools/extremal-python-dependencies/extremal_python_dependencies/main.py +++ /dev/null @@ -1,154 +0,0 @@ -# This code is a Qiskit project. - -# (C) Copyright IBM 2022. - -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -import re -import configparser -from typing import List - -import toml -import typer - - -# https://peps.python.org/pep-0508/#names -_name_re = re.compile(r"^([A-Z0-9][A-Z0-9._-]*[A-Z0-9])", re.IGNORECASE) - - -def mapfunc_replace(replacements: List[str]): - """Use the provided version(s) of certain packages""" - d: dict[str, str] = {} - for r in replacements: - match = _name_re.match(r) - if match is None: - raise RuntimeError(f"Replacement dependency does not match PEP 508: {r}") - name = match.group() - if name in d: - raise RuntimeError("Duplicate name") - d[name] = r - - def _mapfunc_replace(dep): - # Replace https://peps.python.org/pep-0508/#names with provided - # version, often a https://peps.python.org/pep-0440/#direct-references - match = _name_re.match(dep) - if match is None: - raise RuntimeError(f"Dependency does not match PEP 508: `{dep}`") - dep_name = match.group() - return d.get(dep_name, dep) - - return _mapfunc_replace - - -def mapfunc_minimum(dep): - """Set each dependency to its minimum version""" - for clause in dep.split(","): - if "*" in clause and "==" in clause: - raise ValueError( - "Asterisks in version specifiers are not currently supported " - "by the minimum version tests. We recommend using the " - "'compatible release' operator instead: " - "https://peps.python.org/pep-0440/#compatible-release" - ) - return re.sub(r"[>~]=", r"==", dep) - - -def inplace_map(fun, lst: list): - """In-place version of Python's `map` function""" - for i, x in enumerate(lst): - lst[i] = fun(x) - - -def process_dependencies_in_place(d: dict, mapfunc): - """Given a parsed `pyproject.toml`, process dependencies according to `mapfunc`""" - proj = d["project"] - - try: - deps = proj["dependencies"] - except KeyError: - pass # no dependencies; that's unusual, but fine. - else: - inplace_map(mapfunc, deps) - - try: - opt_deps = proj["optional-dependencies"] - except KeyError: - pass # no optional dependencies; that's fine. - else: - for dependencies_list in opt_deps.values(): - inplace_map(mapfunc, dependencies_list) - - try: - build_system = d["build-system"] - except KeyError: - pass - else: - try: - build_system_requires = build_system["requires"] - except KeyError: - pass - else: - inplace_map(mapfunc, build_system_requires) - - -app = typer.Typer() - - -@app.command() -def get_tox_minversion(): - """Extract tox minversion from `tox.ini`""" - config = configparser.ConfigParser() - config.read("tox.ini") - print(config["tox"]["minversion"]) - - -def _pin_dependencies(mapfunc, inplace: bool): - with open("pyproject.toml") as f: - d = toml.load(f) - process_dependencies_in_place(d, mapfunc) - - # Modify pyproject.toml so hatchling will allow direct references - # as dependencies. - d.setdefault("tool", {}).setdefault("hatch", {}).setdefault("metadata", {})[ - "allow-direct-references" - ] = True - - _save_pyproject_toml(d, inplace) - - -@app.command() -def pin_dependencies_to_minimum(inplace: bool = False): - """Pin all dependencies in `pyproject.toml` to their minimum versions.""" - _pin_dependencies(mapfunc_minimum, inplace) - - -@app.command() -def pin_dependencies(replacements: List[str], inplace: bool = False): - """Pin dependencies in `pyproject.toml` to the provided versions.""" - _pin_dependencies(mapfunc_replace(replacements), inplace) - - -@app.command() -def add_dependency(dependency: str, inplace: bool = False): - """Add a dependency to `pyproject.toml`.""" - with open("pyproject.toml") as f: - d = toml.load(f) - d["project"]["dependencies"].append(dependency) - _save_pyproject_toml(d, inplace) - - -def _save_pyproject_toml(d: dict, inplace: bool) -> None: - if inplace: - with open("pyproject.toml", "w") as f: - toml.dump(d, f) - else: - print(toml.dumps(d)) - - -def main(): - return app() diff --git a/tools/extremal-python-dependencies/pyproject.toml b/tools/extremal-python-dependencies/pyproject.toml deleted file mode 100644 index 0023036dc..000000000 --- a/tools/extremal-python-dependencies/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "extremal-python-dependencies" -version = "0.0.0" -description = "A utility for installing extremal versions of dependencies for more robust testing" -readme = "README.md" -license = {file = "../../LICENSE.txt"} -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "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", -] - -requires-python = ">=3.8" - -dependencies = [ - "toml==0.10.2", - "typer==0.12.3", -] - -[tool.hatch.build.targets.wheel] -only-include = [ - "extremal_python_dependencies", -] - -[project.scripts] -extremal-python-dependencies = "extremal_python_dependencies.main:main" diff --git a/tox.ini b/tox.ini index df8b06c01..d748393ef 100644 --- a/tox.ini +++ b/tox.ini @@ -13,23 +13,23 @@ commands = extras = style commands = - ruff check --fix qiskit_addon_cutting/ docs/ test/ tools/ + ruff check --fix qiskit_addon_cutting/ docs/ test/ nbqa ruff --fix docs/ - autoflake --in-place --recursive qiskit_addon_cutting/ docs/ test/ tools/ - black qiskit_addon_cutting/ docs/ test/ tools/ + autoflake --in-place --recursive qiskit_addon_cutting/ docs/ test/ + black qiskit_addon_cutting/ docs/ test/ [testenv:lint] extras = lint commands = - ruff check qiskit_addon_cutting/ docs/ test/ tools/ + ruff check qiskit_addon_cutting/ docs/ test/ nbqa ruff docs/ - autoflake --check --quiet --recursive qiskit_addon_cutting/ docs/ test/ tools/ - black --check qiskit_addon_cutting/ docs/ test/ tools/ + autoflake --check --quiet --recursive qiskit_addon_cutting/ docs/ test/ + black --check qiskit_addon_cutting/ docs/ test/ pydocstyle qiskit_addon_cutting/ mypy qiskit_addon_cutting/ reno lint - pylint -rn qiskit_addon_cutting/ test/ tools/ + pylint -rn qiskit_addon_cutting/ test/ nbqa pylint -rn docs/ [testenv:{,py-,py3-,py38-,py39-,py310-,py311-,py312-}notebook]