Skip to content

Support editable installs of Cython pxd files #516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Nov 29, 2023
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
15 changes: 10 additions & 5 deletions src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .generate import generate_file_contents

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Iterable, Sequence

from ..settings.skbuild_model import ScikitBuildSettings

Expand All @@ -50,6 +50,7 @@ def _make_editable(
reload_dir: Path | None,
settings: ScikitBuildSettings,
wheel: WheelWriter,
packages: Iterable[str],
) -> None:
modules = mapping_to_modules(mapping, libdir)
installed = libdir_to_installed(libdir)
Expand All @@ -68,9 +69,14 @@ def _make_editable(
f"_{name}_editable.py",
editable_txt.encode(),
)
# Support Cython by adding the source directory directly to the path.
# This is necessary because Cython does not support sys.meta_path for
# cimports (as of 3.0.5).
import_strings = [f"import _{name}_editable", *packages, ""]
pth_import_paths = "\n".join(import_strings)
wheel.writestr(
f"_{name}_editable.pth",
f"import _{name}_editable\n".encode(),
pth_import_paths.encode(),
)


Expand Down Expand Up @@ -341,6 +347,7 @@ def _build_wheel_impl(
) as wheel:
wheel.build(wheel_dirs)

str_pkgs = (str(Path.cwd().joinpath(p).parent.resolve()) for p in packages)
if editable and settings.editable.mode == "redirect":
reload_dir = build_dir.resolve() if settings.build_dir else None

Expand All @@ -353,15 +360,13 @@ def _build_wheel_impl(
settings=settings,
wheel=wheel,
name=normalized_name,
packages=str_pkgs,
)
elif editable and settings.editable.mode == "inplace":
if not packages:
msg = "Editable inplace mode requires at least one package"
raise AssertionError(msg)

str_pkgs = (
str(Path.cwd().joinpath(p).parent.resolve()) for p in packages
)
wheel.writestr(
f"_{normalized_name}_editable.pth",
"\n".join(str_pkgs).encode(),
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path:
)
packages = [
"build",
"cython",
"hatchling",
"pip>=23",
"pybind11",
Expand Down
21 changes: 21 additions & 0 deletions tests/packages/cython_pxd_editable/pkg1/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)

find_package(
Python
COMPONENTS Interpreter Development.Module
REQUIRED)

find_program(CYTHON "cython")

add_custom_command(
OUTPUT src/pkg1/one.c
DEPENDS src/pkg1/one.pyx
VERBATIM
COMMAND "${CYTHON}" "${CMAKE_CURRENT_SOURCE_DIR}/src/pkg1/one.pyx"
--output-file "${CMAKE_CURRENT_BINARY_DIR}/src/pkg1/one.c")

python_add_library(one MODULE "${CMAKE_CURRENT_BINARY_DIR}/src/pkg1/one.c"
WITH_SOABI)

install(TARGETS one DESTINATION pkg1/)
13 changes: 13 additions & 0 deletions tests/packages/cython_pxd_editable/pkg1/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[build-system]
build-backend = "scikit_build_core.build"
requires = [
"cython>=3.0.0",
"scikit-build-core",
]

[project]
name = "pkg1"
version = "1.0.0"

[tool.scikit-build]
wheel.packages = ["src/pkg1"]
Empty file.
1 change: 1 addition & 0 deletions tests/packages/cython_pxd_editable/pkg1/src/pkg1/one.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cdef int one()
2 changes: 2 additions & 0 deletions tests/packages/cython_pxd_editable/pkg1/src/pkg1/one.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cdef int one():
return 1
21 changes: 21 additions & 0 deletions tests/packages/cython_pxd_editable/pkg2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)

find_package(
Python
COMPONENTS Interpreter Development.Module
REQUIRED)

find_program(CYTHON "cython")

add_custom_command(
OUTPUT src/pkg2/two.c
DEPENDS src/pkg2/two.pyx
VERBATIM
COMMAND "${CYTHON}" "${CMAKE_CURRENT_SOURCE_DIR}/src/pkg2/two.pyx"
--output-file "${CMAKE_CURRENT_BINARY_DIR}/src/pkg2/two.c")

python_add_library(two MODULE "${CMAKE_CURRENT_BINARY_DIR}/src/pkg2/two.c"
WITH_SOABI)

install(TARGETS two DESTINATION pkg2/)
13 changes: 13 additions & 0 deletions tests/packages/cython_pxd_editable/pkg2/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[build-system]
build-backend = "scikit_build_core.build"
requires = [
"cython>=3.0.0",
"scikit-build-core",
]

[project]
name = "pkg2"
version = "1.0.0"

[tool.scikit-build]
wheel.packages = ["src/pkg2"]
Empty file.
4 changes: 4 additions & 0 deletions tests/packages/cython_pxd_editable/pkg2/src/pkg2/two.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pkg1.one cimport one

cdef int two():
return one() + one()
49 changes: 49 additions & 0 deletions tests/test_editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path

import pytest
from conftest import PackageInfo, process_package


@pytest.mark.compile()
Expand Down Expand Up @@ -48,3 +49,51 @@ def test_navigate_editable(isolated, isolate, package):

value = isolated.execute("import shared_pkg; shared_pkg.read_c_generated_txt()")
assert value == "Some_value_C"


@pytest.mark.compile()
@pytest.mark.configure()
@pytest.mark.integration()
@pytest.mark.parametrize(
("editable", "editable_mode"), [(False, ""), (True, "redirect"), (True, "inplace")]
)
def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated):
editable_flag = ["-e"] if editable else []

config_mode_flags = []
if editable:
config_mode_flags.append(f"--config-settings=editable.mode={editable_mode}")
if editable_mode != "inplace":
config_mode_flags.append("--config-settings=build-dir=build/{wheel_tag}")

package1 = PackageInfo(
"cython_pxd_editable/pkg1",
)
tmp_path1 = tmp_path / "pkg1"
tmp_path1.mkdir()
process_package(package1, tmp_path1, monkeypatch)

isolated.install("pip>23", "cython", "scikit-build-core[pyproject]")

isolated.install(
"-v",
*config_mode_flags,
"--no-build-isolation",
*editable_flag,
".",
)

package2 = PackageInfo(
"cython_pxd_editable/pkg2",
)
tmp_path2 = tmp_path / "pkg2"
tmp_path2.mkdir()
process_package(package2, tmp_path2, monkeypatch)

isolated.install(
"-v",
*config_mode_flags,
"--no-build-isolation",
*editable_flag,
".",
)