Skip to content
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
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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 = [
Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/bump-setuptools-rust-dd57df59e2b2ddea.yaml
Original file line number Diff line number Diff line change
@@ -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.
81 changes: 1 addition & 80 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"}},
Expand Down