diff --git a/setuptools_rust/build.py b/setuptools_rust/build.py index f01cf5f3..fa6d58d1 100644 --- a/setuptools_rust/build.py +++ b/setuptools_rust/build.py @@ -140,7 +140,16 @@ def build_extension( debug = self._is_debug_build(ext) use_cargo_crate_type = _check_cargo_supports_crate_type_option(ext.env) - package_id = ext.metadata(quiet=quiet)["resolve"]["root"] + metadata = ext.metadata("--no-deps", quiet=quiet) + manifest_path = Path(ext.path).resolve() + package_id = next( + ( + package["id"] + for package in metadata["packages"] + if Path(package["manifest_path"]).resolve() == manifest_path + ), + None, + ) if package_id is None: raise FileError( f"manifest for Rust extention `{ext.name}` at path `{ext.path}` is a virtual manifest (a workspace root without a package).\n\n" @@ -929,8 +938,12 @@ def _replace_cross_target_dir(path: str, ext: RustExtension, *, quiet: bool) -> dockerfile; invoking `cargo metadata` we can work out the correct local target directory. """ - cross_target_dir = ext._metadata(cargo="cross", quiet=quiet)["target_directory"] - local_target_dir = ext._metadata(cargo="cargo", quiet=quiet)["target_directory"] + cross_target_dir = ext._metadata("cross", "--no-deps", quiet=quiet)[ + "target_directory" + ] + local_target_dir = ext._metadata("cargo", "--no-deps", quiet=quiet)[ + "target_directory" + ] return path.replace(cross_target_dir, local_target_dir) diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index ab66f6d3..a3e6f189 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -7,7 +7,7 @@ import warnings from collections.abc import Sequence from enum import IntEnum, auto -from functools import lru_cache +from pathlib import Path from typing import ( TYPE_CHECKING, Any, @@ -194,11 +194,17 @@ def __init__( DeprecationWarning, ) + self._metadata_cache: dict[tuple[str, ...], CargoMetadata] = {} + def get_lib_name(self, *, quiet: bool) -> str: """Parse Cargo.toml to get the name of the shared library.""" - metadata = self.metadata(quiet=quiet) - root_key = metadata["resolve"]["root"] - [pkg] = [p for p in metadata["packages"] if p["id"] == root_key] + metadata = self.metadata("--no-deps", quiet=quiet) + manifest_path = Path(self.path).resolve() + (pkg,) = ( + p + for p in metadata["packages"] + if Path(p["manifest_path"]).resolve() == manifest_path + ) name = pkg["targets"][0]["name"] assert isinstance(name, str) return re.sub(r"[./\\-]", "_", name) @@ -253,16 +259,16 @@ def install_script(self, module_name: str, exe_path: str) -> None: with open(file, "w") as f: f.write(_SCRIPT_TEMPLATE.format(executable=repr(executable))) - def metadata(self, *, quiet: bool) -> CargoMetadata: + def metadata(self, *args: str, quiet: bool) -> CargoMetadata: """Returns cargo metadata for this extension package. - Cached - will only execute cargo on first invocation. + Cached - will only execute cargo on first invocation for each + combination of *args. """ - return self._metadata(os.environ.get("CARGO", "cargo"), quiet) + return self._metadata(os.environ.get("CARGO", "cargo"), *args, quiet=quiet) - @lru_cache # noqa: B019 - def _metadata(self, cargo: str, quiet: bool) -> CargoMetadata: + def _metadata(self, cargo: str, *args: str, quiet: bool) -> CargoMetadata: metadata_command = [ cargo, "metadata", @@ -270,10 +276,16 @@ def _metadata(self, cargo: str, quiet: bool) -> CargoMetadata: self.path, "--format-version", "1", + *args, ] if self.cargo_manifest_args: metadata_command.extend(self.cargo_manifest_args) + command = tuple(metadata_command) + + if (cached := self._metadata_cache.get(command)) is not None: + return cached + try: # If quiet, capture stderr and only show it on exceptions # If not quiet, let stderr be inherited @@ -284,7 +296,9 @@ def _metadata(self, cargo: str, quiet: bool) -> CargoMetadata: except subprocess.CalledProcessError as e: raise SetupError(format_called_process_error(e)) try: - return cast(CargoMetadata, json.loads(payload)) + metadata = cast(CargoMetadata, json.loads(payload)) + self._metadata_cache[command] = metadata + return metadata except json.decoder.JSONDecodeError as e: raise SetupError( f"""