Skip to content

Commit a7b332c

Browse files
committed
feat: deduce C/CXX if possible
Signed-off-by: Henry Schreiner <[email protected]>
1 parent bfa2dcf commit a7b332c

File tree

2 files changed

+98
-8
lines changed

2 files changed

+98
-8
lines changed

src/cython_cmake/cmake/UseCython.cmake

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,30 +73,54 @@
7373
#=============================================================================
7474

7575
if(CMAKE_VERSION VERSION_LESS "3.20")
76-
message(SEND_ERROR "CMake 3.20 required")
76+
message(FATAL_ERROR "CMake 3.20 required")
7777
endif()
7878

7979

80-
function(Cython_compile_pyx INPUT)
80+
function(Cython_compile_pyx)
8181
cmake_parse_arguments(
82-
PARSE_ARGV 1
82+
PARSE_ARGV 0
8383
CYTHON
8484
""
8585
"OUTPUT;LANGUAGE;OUTPUT_VARIABLE"
8686
"CYTHON_ARGS"
8787
)
88+
set(ALL_INPUT ${CYTHON_UNPARSED_ARGUMENTS})
89+
list(LENGTH ALL_INPUT INPUT_LENGTH)
90+
if(NOT INPUT_LENGTH EQUAL 1)
91+
message(FATAL_ERROR "One and only one input file must be specified, got '${ALL_INPUT}'")
92+
endif()
93+
list(GET ALL_INPUT 0 INPUT)
8894

89-
# Set target language (required)
95+
# Set target language
9096
if(NOT CYTHON_LANGUAGE)
91-
message(SEND_ERROR "cython_compile_pyx LANGUAGE keyword is required")
97+
get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES)
98+
99+
if("C" IN_LIST _langauges AND "CXX" IN_LIST _languages)
100+
# Try to compute language. Returns falsy if not found.
101+
_cython_compute_language(CYTHON_LANGUAGE ${INPUT})
102+
message(STATUS "${CYTHON_LANGUAGE}")
103+
elseif("C" IN_LIST _languages)
104+
# If only C is enabled globally, assume C
105+
set(CYTHON_LANGUAGE C)
106+
elseif("CXX" IN_LIST _languages)
107+
# Likewise for CXX
108+
set(CYTHON_LANGUAGE "CXX")
109+
else()
110+
message(FATAL_ERROR "LANGUAGE keyword required if neither C nor CXX enabled globally")
111+
endif()
112+
endif()
113+
114+
if(NOT CYTHON_LANGUAGE)
115+
message(FATAL_ERROR "LANGUAGE keyword or `# distutils: language=...` required if C and CXX are enabled globally")
92116
elseif(CYTHON_LANGUAGE STREQUAL C)
93117
set(language_arg "")
94118
set(langauge_ext ".c")
95119
elseif(CYTHON_LANGUAGE STREQUAL CXX)
96120
set(language_arg "--cplus")
97121
set(langauge_ext ".cxx")
98122
else()
99-
message(SEND_ERROR "cython_compile_pyx LANGUAGE must be one of C or CXX")
123+
message(FATAL_ERROR "cython_compile_pyx LANGUAGE must be one of C or CXX")
100124
endif()
101125

102126
# Place the cython files in the current binary dir if no path given
@@ -135,7 +159,7 @@ function(Cython_compile_pyx INPUT)
135159
--depfile
136160
"${INPUT}"
137161
--output-file "${CYTHON_OUTPUT}"
138-
DEPENDS
162+
MAIN_DEPENDENCY
139163
"${INPUT}"
140164
DEPFILE
141165
"${depfile_path}"
@@ -145,3 +169,12 @@ function(Cython_compile_pyx INPUT)
145169
)
146170

147171
endfunction()
172+
173+
function(_cython_compute_language OUTPUT_VARIABLE FILENAME)
174+
file(READ "${FILENAME}" FILE_CONTENT)
175+
set(REGEX_PATTERN [=[^[[:space:]]*#[[:space:]]*distutils:.*language[[:space:]]*=[[:space:]]*(c\\+\\+|c)]=])
176+
string(REGEX MATCH "${REGEX_PATTERN}" MATCH_RESULT "${FILE_CONTENT}")
177+
string(TOUPPER "${MATCH_RESULT}" LANGUAGE_NAME)
178+
string(REPLACE "+" "X" LANGUAGE_NAME "${LANGUAGE_NAME}")
179+
set(${OUTPUT_VARIABLE} ${LANGUAGE_NAME} PARENT_SCOPE)
180+
endfunction()

tests/test_package.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import importlib.metadata
4+
import shutil
45
import zipfile
56
from pathlib import Path
67

@@ -15,7 +16,7 @@ def test_version():
1516
assert importlib.metadata.version("cython_cmake") == m.__version__
1617

1718

18-
def test_scikit_build_core(monkeypatch, tmp_path):
19+
def test_simple(monkeypatch, tmp_path):
1920
monkeypatch.chdir(DIR / "packages/simple")
2021
build_dir = tmp_path / "build"
2122

@@ -30,3 +31,59 @@ def test_scikit_build_core(monkeypatch, tmp_path):
3031
build_files = {x.name for x in build_dir.iterdir()}
3132
assert "simple.c.dep" in build_files
3233
assert "simple.c" in build_files
34+
35+
36+
def test_implicit_cxx(monkeypatch, tmp_path):
37+
package_dir = tmp_path / "pkg2"
38+
shutil.copytree(DIR / "packages/simple", package_dir)
39+
monkeypatch.chdir(package_dir)
40+
41+
cmakelists = Path("CMakeLists.txt")
42+
txt = (
43+
cmakelists.read_text()
44+
.replace("LANGUAGE C", "")
45+
.replace("LANGUAGES C", "LANGUAGES CXX")
46+
)
47+
cmakelists.write_text(txt)
48+
49+
wheel = build_wheel(
50+
str(tmp_path), {"build-dir": "build", "wheel.license-files": []}
51+
)
52+
53+
with zipfile.ZipFile(tmp_path / wheel) as f:
54+
file_names = set(f.namelist())
55+
assert len(file_names) == 4
56+
57+
build_files = {x.name for x in Path("build").iterdir()}
58+
assert "simple.cxx.dep" in build_files
59+
assert "simple.cxx" in build_files
60+
61+
62+
def test_directive_cxx(monkeypatch, tmp_path):
63+
package_dir = tmp_path / "pkg3"
64+
shutil.copytree(DIR / "packages/simple", package_dir)
65+
monkeypatch.chdir(package_dir)
66+
67+
cmakelists = Path("CMakeLists.txt")
68+
txt = (
69+
cmakelists.read_text()
70+
.replace("LANGUAGE C", "")
71+
.replace("LANGUAGES C", "LANGUAGES CXX")
72+
)
73+
cmakelists.write_text(txt)
74+
75+
simple = Path("simple.pyx")
76+
txt = simple.read_text()
77+
simple.write_text(f"# distutils: language=c++\n{txt}")
78+
79+
wheel = build_wheel(
80+
str(tmp_path), {"build-dir": "build", "wheel.license-files": []}
81+
)
82+
83+
with zipfile.ZipFile(tmp_path / wheel) as f:
84+
file_names = set(f.namelist())
85+
assert len(file_names) == 4
86+
87+
build_files = {x.name for x in Path("build").iterdir()}
88+
assert "simple.cxx.dep" in build_files
89+
assert "simple.cxx" in build_files

0 commit comments

Comments
 (0)