Skip to content

Commit 492f4dd

Browse files
committed
fix: Prevent clobbering of generated sources
Ensure that sources generated from modules with the same file name but located in different packages do not clobber each other. For example, generating sources from the following files will now work as expected: ``` <src_dir>/package1/module.pyx <src_dir>/package1/package2/module.pyx ``` This will generate the following files in the build directory: ``` <bld_dir>/package1/module.c <bld_dir>/package1/module.c.dep <bld_dir>/package1/package2/module.c <bld_dir>/package1/package2/module.c.dep ``` Additionally, source files configured outside of the source tree and now properly supported by computing the relative path and replacing "." with "_". For example, given the following project structure: ``` <src_dir>/package1/__init__.py <src_dir>/package1/module1.pyx <src_dir>/package3/__init__.py module3.pyx ``` the following will be generated: ``` <bld_dir>/package1/module1.c <bld_dir>/package1/module1.c.dep <bld_dir>/__/module3.c <bld_dir>/__/module3.c.dep ```
1 parent 11f5c30 commit 492f4dd

File tree

9 files changed

+111
-1
lines changed

9 files changed

+111
-1
lines changed

src/cython_cmake/cmake/UseCython.cmake

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,16 @@ function(Cython_compile_pyx)
135135
${CMAKE_SOURCE_DIR} ${_source_file})
136136
set(comment "Generating ${_language} source '${generated_file_relative}' from '${source_file_relative}'")
137137

138+
# Get output directory to ensure its exists
139+
get_filename_component(output_directory "${generated_file}" DIRECTORY)
140+
138141
get_source_file_property(pyx_location ${_source_file} LOCATION)
139142

140143
# Add the command to run the compiler.
141144
add_custom_command(
142145
OUTPUT ${generated_file}
146+
COMMAND
147+
${CMAKE_COMMAND} -E make_directory ${output_directory}
143148
COMMAND
144149
${_cython_command}
145150
${_language_arg}
@@ -169,7 +174,19 @@ function(Cython_compile_pyx)
169174
# cmake_path(GET _input_file STEM basename)
170175
get_filename_component(_basename "${_input_file}" NAME_WE)
171176

172-
set(${_output_var} "${CMAKE_CURRENT_BINARY_DIR}/${_basename}.${_language_extension}" PARENT_SCOPE)
177+
if(IS_ABSOLUTE ${_input_file})
178+
file(RELATIVE_PATH _input_relative ${CMAKE_CURRENT_SOURCE_DIR} ${_input_file})
179+
else()
180+
set(_input_relative ${_input_file})
181+
endif()
182+
183+
get_filename_component(_output_relative_dir "${_input_relative}" DIRECTORY)
184+
string(REPLACE "." "_" _output_relative_dir "${_output_relative_dir}")
185+
if(_output_relative_dir)
186+
set(_output_relative_dir "${_output_relative_dir}/")
187+
endif()
188+
189+
set(${_output_var} "${CMAKE_CURRENT_BINARY_DIR}/${_output_relative_dir}${_basename}.${_language_extension}" PARENT_SCOPE)
173190
endfunction()
174191

175192
set(generated_files)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
cmake_minimum_required(VERSION 3.15...3.29)
2+
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)
3+
4+
find_package(
5+
Python
6+
COMPONENTS Interpreter Development.Module
7+
REQUIRED)
8+
find_package(Cython MODULE REQUIRED VERSION 3.0)
9+
include(UseCython)
10+
11+
#-----------------------------------------------------------------------------
12+
# package1.module
13+
cython_compile_pyx(package1/module.pyx
14+
LANGUAGE C
15+
OUTPUT_VARIABLE module_c
16+
)
17+
18+
python_add_library(module1 MODULE "${module_c}" WITH_SOABI)
19+
20+
install(TARGETS module1 DESTINATION "package1")
21+
22+
#-----------------------------------------------------------------------------
23+
# package1.package2.module
24+
cython_compile_pyx(package1/package2/module.pyx
25+
LANGUAGE C
26+
OUTPUT_VARIABLE module_c
27+
)
28+
29+
python_add_library(module2 MODULE "${module_c}" WITH_SOABI)
30+
31+
install(TARGETS module2 DESTINATION "package1/package2")
32+
33+
#-----------------------------------------------------------------------------
34+
# package1.package3.module
35+
36+
file(COPY package1/package2/module.pyx DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/..)
37+
38+
cython_compile_pyx(${CMAKE_CURRENT_SOURCE_DIR}/../module.pyx
39+
LANGUAGE C
40+
CYTHON_ARGS
41+
--module-name "package1.package3.module"
42+
OUTPUT_VARIABLE module_c
43+
)
44+
45+
python_add_library(module3 MODULE "${module_c}" WITH_SOABI)
46+
47+
install(TARGETS module3 DESTINATION "package1/package3")

tests/packages/multiple_packages/package1/__init__.py

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# cython: language_level=3
2+
3+
def square(float x):
4+
return x * x

tests/packages/multiple_packages/package1/package2/__init__.py

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# cython: language_level=3
2+
3+
def square(float x):
4+
return x * x

tests/packages/multiple_packages/package1/package3/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[build-system]
2+
requires = ["scikit-build-core", "cython", "cython-cmake"]
3+
build-backend = "scikit_build_core.build"
4+
5+
[project]
6+
name = "example"
7+
version = "0.0.1"

tests/test_package.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,34 @@ def test_directive_cxx(monkeypatch, tmp_path):
127127
build_files = {x.name for x in Path("build").iterdir()}
128128
assert "simple.cxx.dep" in build_files
129129
assert "simple.cxx" in build_files
130+
131+
132+
def test_multiple_packages(monkeypatch, tmp_path):
133+
package_dir = tmp_path / "pkg5"
134+
shutil.copytree(DIR / "packages/multiple_packages", package_dir)
135+
monkeypatch.chdir(package_dir)
136+
build_dir = tmp_path / "build"
137+
138+
wheel = build_wheel(
139+
str(tmp_path), {"build-dir": str(build_dir), "wheel.license-files": []}
140+
)
141+
142+
with zipfile.ZipFile(tmp_path / wheel) as f:
143+
file_names = set(f.namelist())
144+
assert len(file_names) == 6
145+
146+
build_files = {x.name for x in build_dir.iterdir()}
147+
assert "module.c.dep" not in build_files
148+
assert "module.c" not in build_files
149+
150+
package1_build_files = {x.name for x in (build_dir / "package1").iterdir()}
151+
assert "module.c.dep" in package1_build_files
152+
assert "module.c" in package1_build_files
153+
154+
package2_build_files = {x.name for x in (build_dir / "package1/package2").iterdir()}
155+
assert "module.c.dep" in package2_build_files
156+
assert "module.c" in package2_build_files
157+
158+
package3_build_files = {x.name for x in (build_dir / "__").iterdir()}
159+
assert "module.c.dep" in package3_build_files
160+
assert "module.c" in package3_build_files

0 commit comments

Comments
 (0)