|
17 | 17 | from setuptools import setup |
18 | 18 | from setuptools_rust import Binding, RustExtension |
19 | 19 |
|
| 20 | +# ================================================================================================== |
| 21 | +# Warning: this section is a horrendous monkey-patching hack that re-implements a version of |
| 22 | +# https://github.com/PyO3/setuptools-rust/pull/574 on top of `setuptools-rust==1.12.0`, which |
| 23 | +# contains just enough functionality for our own use. |
| 24 | + |
| 25 | +from pathlib import Path |
| 26 | +import json |
| 27 | +import shutil |
| 28 | +import functools |
| 29 | +import setuptools_rust.build |
| 30 | + |
| 31 | +# This function is only called once in our build, to find the `cdylib` artifact. It's passed the |
| 32 | +# `cargo` messages, which is what we need to locate the `OUT_DIR`; we use this as a hook point to |
| 33 | +# intercept them and leak them out for later use. |
| 34 | +find_cargo_artifacts_orig = setuptools_rust.build._find_cargo_artifacts |
| 35 | +install_extension_orig = setuptools_rust.build.build_rust.install_extension |
| 36 | +out_dir = None |
| 37 | +generated_files = {"include": "qiskit.capi"} |
| 38 | + |
| 39 | + |
| 40 | +@functools.wraps(find_cargo_artifacts_orig) |
| 41 | +def find_cargo_artifacts_patch(cargo_messages, *, package_id, kinds): |
| 42 | + global out_dir # noqa: PLW0603 (global) - we have to leak to get the monkeypatch to work. |
| 43 | + |
| 44 | + # Chances are that the line we're looking for will be the third laste line in the |
| 45 | + # messages. The last is the completion report, the penultimate is generally the |
| 46 | + # build of the final artifact. |
| 47 | + for messsage in reversed(cargo_messages): |
| 48 | + if "build-script-executed" not in messsage or package_id not in messsage: |
| 49 | + continue |
| 50 | + parsed = json.loads(messsage) |
| 51 | + if parsed.get("package_id") == package_id: |
| 52 | + out_dir = parsed.get("out_dir") |
| 53 | + break |
| 54 | + |
| 55 | + return find_cargo_artifacts_orig(cargo_messages, package_id=package_id, kinds=kinds) |
| 56 | + |
| 57 | + |
| 58 | +@functools.wraps(install_extension_orig) |
| 59 | +def install_extension_patch(self, ext, dylib_paths): |
| 60 | + install_extension_orig(self, ext, dylib_paths) |
| 61 | + |
| 62 | + # `out_dir` and `generated_files` are captured from the outer scope. |
| 63 | + if out_dir is None: |
| 64 | + raise RuntimeError("setup sanity check failed: the cargo `out_dir` is not set") |
| 65 | + build_artifact_dirs = [Path(out_dir)] |
| 66 | + |
| 67 | + # We'll delegate the finding of the package directories to Setuptools, so we |
| 68 | + # can be sure we're handling editable installs and other complex situations |
| 69 | + # correctly. |
| 70 | + build_ext = self.get_finalized_command("build_ext") |
| 71 | + build_py = self.get_finalized_command("build_py") |
| 72 | + |
| 73 | + def get_package_dir(package: str) -> Path: |
| 74 | + if self.inplace: |
| 75 | + # If `inplace`, we have to ask `build_py` (like `build_ext` would). |
| 76 | + return Path(build_py.get_package_dir(package)) |
| 77 | + # ... If not, `build_ext` knows where to put the package. |
| 78 | + return Path(build_ext.build_lib) / Path(*package.split(".")) |
| 79 | + |
| 80 | + for source, package in generated_files.items(): |
| 81 | + dest = get_package_dir(package) |
| 82 | + dest.mkdir(mode=0o755, parents=True, exist_ok=True) |
| 83 | + for artifact_dir in build_artifact_dirs: |
| 84 | + source_full = artifact_dir / source |
| 85 | + dest_full = dest / source_full.name |
| 86 | + if source_full.is_file(): |
| 87 | + # logger.info("Copying data file from %s to %s", source_full, dest_full) |
| 88 | + shutil.copy2(source_full, dest_full) |
| 89 | + elif source_full.is_dir(): |
| 90 | + # logger.info("Copying data directory from %s to %s", source_full, dest_full) |
| 91 | + shutil.copytree(source_full, dest_full, dirs_exist_ok=True) |
| 92 | + # This tacitly makes "no match" a silent non-error. |
| 93 | + |
| 94 | + |
| 95 | +setuptools_rust.build._find_cargo_artifacts = find_cargo_artifacts_patch |
| 96 | +setuptools_rust.build.build_rust.install_extension = install_extension_patch |
| 97 | + |
| 98 | +# Normal service resumes below here. |
| 99 | +# ================================================================================================== |
| 100 | + |
| 101 | + |
20 | 102 | # Most of this configuration is managed by `pyproject.toml`. This only includes the extra bits to |
21 | 103 | # configure `setuptools-rust`, because we do a little dynamic trick with the debug setting, and we |
22 | 104 | # also want an explicit `setup.py` file to exist so we can manually call |
|
64 | 146 | binding=Binding.PyO3, |
65 | 147 | debug=rust_debug, |
66 | 148 | features=features, |
67 | | - data_files={"include": "qiskit.capi"}, |
68 | 149 | ) |
69 | 150 | ], |
70 | 151 | options={"bdist_wheel": {"py_limited_api": "cp310"}}, |
|
0 commit comments