From 6326d28b64fc0ce815306a9a1fb9d8101fb7c103 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 1 Jul 2026 20:58:05 +0100 Subject: [PATCH] Bump setuptools-rust to 1.13 (#16500) This includes the final implementation of the `generated_files` logic[^1] that we had previously been monkey-patching into a pinned older version. [^1]: https://github.com/PyO3/setuptools-rust/pull/574 (cherry picked from commit a63cbcbc6d5191b2a651795d178c92cd9c1be3b4) --- pyproject.toml | 9 +-- ...bump-setuptools-rust-dd57df59e2b2ddea.yaml | 5 ++ setup.py | 81 +------------------ 3 files changed, 10 insertions(+), 85 deletions(-) create mode 100644 releasenotes/notes/bump-setuptools-rust-dd57df59e2b2ddea.yaml diff --git a/pyproject.toml b/pyproject.toml index 2fa197e9bede..01935c7d134d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,10 @@ [build-system] # This is duplicated as `dependency-groups.build` for convenience. requires = [ + # Minimum is for PEP 639 / SPDX licence metadata. "setuptools>=77.0", - # This is hard-pinned to permit the awful monkeypatching in `setup.py`. - # It should be removed when we can use a released version of - # https://github.com/PyO3/setuptools-rust/pull/574 - "setuptools-rust==1.12.0", + # Minimum is for `Extension(generated_files=...)`. + "setuptools-rust>=1.13.0", ] build-backend = "setuptools.build_meta" @@ -188,7 +187,7 @@ default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultScheduli # process. If you intended that, use `project.optional-dependencies` instead. [dependency-groups] # Sync with `build-system.requires`. -build = ["setuptools>=77.0", "setuptools-rust==1.12.0"] +build = ["setuptools>=77.0", "setuptools-rust>=1.13.0"] # We use quite tight pins on pylint and astroid because they have a habit of adding new # on-by-default lints that are harder to deal with. lint = [ diff --git a/releasenotes/notes/bump-setuptools-rust-dd57df59e2b2ddea.yaml b/releasenotes/notes/bump-setuptools-rust-dd57df59e2b2ddea.yaml new file mode 100644 index 000000000000..f12fe7e6890d --- /dev/null +++ b/releasenotes/notes/bump-setuptools-rust-dd57df59e2b2ddea.yaml @@ -0,0 +1,5 @@ +--- +build: + - | + The minimum version of the build dependency ``setuptools-rust`` was bumped to 1.13, to use the + new ``Extension(generated_files=...)`` argument. diff --git a/setup.py b/setup.py index 4df82c5114c7..35ed9675af8a 100644 --- a/setup.py +++ b/setup.py @@ -17,86 +17,6 @@ from setuptools import setup from setuptools_rust import Binding, RustExtension -# ================================================================================================== -# Warning: this section is a horrendous monkey-patching hack that re-implements a version of -# https://github.com/PyO3/setuptools-rust/pull/574 on top of `setuptools-rust==1.12.0`, which -# contains just enough functionality for our own use. - -from pathlib import Path -import json -import shutil -import functools -import setuptools_rust.build - -# This function is only called once in our build, to find the `cdylib` artifact. It's passed the -# `cargo` messages, which is what we need to locate the `OUT_DIR`; we use this as a hook point to -# intercept them and leak them out for later use. -find_cargo_artifacts_orig = setuptools_rust.build._find_cargo_artifacts -install_extension_orig = setuptools_rust.build.build_rust.install_extension -out_dir = None -generated_files = {"include": "qiskit.capi", "_ctypes.py": "qiskit.capi"} - - -@functools.wraps(find_cargo_artifacts_orig) -def find_cargo_artifacts_patch(cargo_messages, *, package_id, kinds): - global out_dir # noqa: PLW0603 (global) - we have to leak to get the monkeypatch to work. - - # Chances are that the line we're looking for will be the third laste line in the - # messages. The last is the completion report, the penultimate is generally the - # build of the final artifact. - for message in reversed(cargo_messages): - if "build-script-executed" not in message or package_id not in message: - continue - parsed = json.loads(message) - if parsed.get("package_id") == package_id: - out_dir = parsed.get("out_dir") - break - - return find_cargo_artifacts_orig(cargo_messages, package_id=package_id, kinds=kinds) - - -@functools.wraps(install_extension_orig) -def install_extension_patch(self, ext, dylib_paths): - install_extension_orig(self, ext, dylib_paths) - - # `out_dir` and `generated_files` are captured from the outer scope. - if out_dir is None: - raise RuntimeError("setup sanity check failed: the cargo `out_dir` is not set") - build_artifact_dirs = [Path(out_dir)] - - # We'll delegate the finding of the package directories to Setuptools, so we - # can be sure we're handling editable installs and other complex situations - # correctly. - build_ext = self.get_finalized_command("build_ext") - build_py = self.get_finalized_command("build_py") - - def get_package_dir(package: str) -> Path: - if self.inplace: - # If `inplace`, we have to ask `build_py` (like `build_ext` would). - return Path(build_py.get_package_dir(package)) - # ... If not, `build_ext` knows where to put the package. - return Path(build_ext.build_lib) / Path(*package.split(".")) - - for source, package in generated_files.items(): - dest = get_package_dir(package) - dest.mkdir(mode=0o755, parents=True, exist_ok=True) - for artifact_dir in build_artifact_dirs: - source_full = artifact_dir / source - dest_full = dest / source_full.name - if source_full.is_file(): - shutil.copy2(source_full, dest_full) - elif source_full.is_dir(): - shutil.copytree(source_full, dest_full, dirs_exist_ok=True) - # This tacitly makes "no match" a silent non-error. - - -setuptools_rust.build._find_cargo_artifacts = find_cargo_artifacts_patch -setuptools_rust.build.build_rust.install_extension = install_extension_patch - -# Normal service resumes below here. -# ================================================================================================== - - # 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 @@ -147,6 +67,7 @@ def get_package_dir(package: str) -> Path: binding=Binding.PyO3, debug=rust_debug, features=features, + generated_files={"include": "qiskit.capi", "_ctypes.py": "qiskit.capi"}, ) ], options={"bdist_wheel": {"py_limited_api": "cp310"}},