diff --git a/hatch_rs/structs.py b/hatch_rs/structs.py index 023b92d..f135a56 100644 --- a/hatch_rs/structs.py +++ b/hatch_rs/structs.py @@ -451,6 +451,11 @@ class RustArtifactConfig(BaseModel): cargo_target_kind: CargoTargetKind | None = Field(default=None, alias="cargo-target-kind", description="Cargo target selector kind.") cargo_target: str | None = Field(default=None, alias="cargo-target", description="Cargo target selector name.") crate_type: str = Field(default="cdylib", alias="crate-type", description="Rust crate type to pass through to rustc.") + python_extension_name: str | None = Field( + default=None, + alias="python-extension-name", + description="Module stem for {python_extension_name}, when the importable name differs from the Cargo artifact name.", + ) install_scheme: InstallScheme = Field( default="package", alias="install-scheme", @@ -1029,7 +1034,8 @@ def _format_destination( import_library: str = "", ) -> Path: artifact = planned_artifact.artifact - python_extension = python_extension_name(_cargo_artifact_stem(source), abi3=self.abi3, platform=planned_artifact.resolved_target.platform) + extension_stem = artifact.python_extension_name or _cargo_artifact_stem(source) + python_extension = python_extension_name(extension_stem, abi3=self.abi3, platform=planned_artifact.resolved_target.platform) values = { "module": self.module, "target": planned_artifact.resolved_target.triple, diff --git a/hatch_rs/tests/test_structs.py b/hatch_rs/tests/test_structs.py index 0c065e1..d97f742 100644 --- a/hatch_rs/tests/test_structs.py +++ b/hatch_rs/tests/test_structs.py @@ -156,6 +156,34 @@ def test_executable_name(platform: str, expected: str): assert executable_name("mycli", platform=platform) == expected +def test_python_extension_name_overrides_cargo_artifact_stem(tmp_path): + # The cdylib is named apart from the core library it links, so the two do + # not collide in target/, but it still has to import under the module name. + plan = HatchRustBuildPlan( + module="project", + path=tmp_path, + target="x86_64-unknown-linux-gnu", + artifacts=[ + { + "name": "project_ext", + "python-extension-name": "project", + "destination": "{module}/{python_extension_name}", + } + ], + ) + plan.generate() + planned_artifact = plan._artifact_plans[0] + source = tmp_path / "target" / "x86_64-unknown-linux-gnu" / "release" / "libproject_ext.so" + source.parent.mkdir(parents=True) + source.write_bytes(b"extension") + + plan._copy_outputs(planned_artifact, build_root=tmp_path) + + copied = tmp_path / "project" / "project.so" + assert copied.read_bytes() == b"extension" + assert plan.libraries == ["project/project.so"] + + def test_build_plan_generates_cargo_invocation(tmp_path): plan = HatchRustBuildPlan(module="project", path=tmp_path, target="x86_64-apple-darwin")