Skip to content

Commit ef07739

Browse files
feat: support TOML lists in cmake.define (#921)
Adds support for specifying CMake lists in `cmake.define` using TOML lists: ``` [tool.scikit-build.cmake.define] ONE_LEVEL_LIST = [ "Foo", "Bar", "ExceptionallyLargeListEntryThatWouldOverflowTheLine", "Baz", ] NESTED_LIST = [ "Apple", "Lemon;Lime", "Banana" ] ``` Escapes semicolons in list items so as to allow nested lists. Fixes #874 --------- Co-authored-by: Henry Schreiner <[email protected]>
1 parent 682df60 commit ef07739

File tree

9 files changed

+157
-13
lines changed

9 files changed

+157
-13
lines changed

docs/configuration.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,17 +413,44 @@ You can select a different build type, such as `Debug`:
413413
414414
```
415415

416-
You can specify CMake defines:
416+
You can specify CMake defines as strings or bools:
417417

418418
````{tab} pyproject.toml
419419

420420
```toml
421421
[tool.scikit-build.cmake.define]
422-
SOME_DEFINE = "ON"
422+
SOME_DEFINE = "Foo"
423+
SOME_OPTION = true
423424
```
424425

425426
````
426427

428+
You can even specify a CMake define as a list of strings:
429+
430+
````{tab} pyproject.toml
431+
432+
```toml
433+
[tool.scikit-build.cmake.define]
434+
FOOD_GROUPS = [
435+
"Apple",
436+
"Lemon;Lime",
437+
"Banana",
438+
"Pineapple;Mango",
439+
]
440+
```
441+
442+
````
443+
444+
Semicolons inside the list elements will be escaped with a backslash (`\`) and
445+
the resulting list elements will be joined together with semicolons (`;`) before
446+
being converted to command-line arguments.
447+
448+
:::{versionchanged} 0.11
449+
450+
Support for list of strings.
451+
452+
:::
453+
427454
`````{tab} config-settings
428455
429456

src/scikit_build_core/builder/builder.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,7 @@ def configure(
245245
cmake_defines["CMAKE_OSX_ARCHITECTURES"] = ";".join(archs)
246246

247247
# Add the pre-defined or passed CMake defines
248-
cmake_defines.update(
249-
{
250-
k: ("TRUE" if v else "FALSE") if isinstance(v, bool) else v
251-
for k, v in self.settings.cmake.define.items()
252-
}
253-
)
248+
cmake_defines.update(self.settings.cmake.define)
254249

255250
self.config.configure(
256251
defines=cmake_defines,

src/scikit_build_core/resources/scikit-build.schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
},
3838
{
3939
"type": "boolean"
40+
},
41+
{
42+
"type": "array",
43+
"items": {
44+
"type": "string"
45+
}
4046
}
4147
]
4248
},
@@ -58,6 +64,12 @@
5864
},
5965
{
6066
"type": "boolean"
67+
},
68+
{
69+
"type": "array",
70+
"items": {
71+
"type": "string"
72+
}
6173
}
6274
]
6375
}

src/scikit_build_core/settings/json_schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ def convert_type(t: Any, *, normalize_keys: bool) -> dict[str, Any]:
151151
}
152152
if origin is Literal:
153153
return {"enum": list(args)}
154+
if hasattr(t, "json_schema"):
155+
return convert_type(t.json_schema, normalize_keys=normalize_keys)
154156

155157
msg = f"Cannot convert type {t} to JSON Schema"
156158
raise FailedConversionError(msg)

src/scikit_build_core/settings/skbuild_model.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"BackportSettings",
1212
"BuildSettings",
1313
"CMakeSettings",
14+
"CMakeSettingsDefine",
1415
"EditableSettings",
1516
"GenerateSettings",
1617
"InstallSettings",
@@ -27,6 +28,28 @@ def __dir__() -> List[str]:
2728
return __all__
2829

2930

31+
class CMakeSettingsDefine(str):
32+
"""
33+
A str subtype for automatically normalizing bool and list values
34+
to the CMake representation in the `cmake.define` settings key.
35+
"""
36+
37+
json_schema = Union[str, bool, List[str]]
38+
39+
def __new__(cls, raw: Union[str, bool, List[str]]) -> "CMakeSettingsDefine":
40+
def escape_semicolons(item: str) -> str:
41+
return item.replace(";", r"\;")
42+
43+
if isinstance(raw, bool):
44+
value = "TRUE" if raw else "FALSE"
45+
elif isinstance(raw, list):
46+
value = ";".join(map(escape_semicolons, raw))
47+
else:
48+
value = raw
49+
50+
return super().__new__(cls, value)
51+
52+
3053
@dataclasses.dataclass
3154
class CMakeSettings:
3255
minimum_version: Optional[Version] = None
@@ -49,7 +72,7 @@ class CMakeSettings:
4972
in config or envvar will override toml. See also ``cmake.define``.
5073
"""
5174

52-
define: Annotated[Dict[str, Union[str, bool]], "EnvVar"] = dataclasses.field(
75+
define: Annotated[Dict[str, CMakeSettingsDefine], "EnvVar"] = dataclasses.field(
5376
default_factory=dict
5477
)
5578
"""
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
project(cmake_defines LANGUAGES NONE)
3+
4+
set(ONE_LEVEL_LIST
5+
""
6+
CACHE STRING "")
7+
set(NESTED_LIST
8+
""
9+
CACHE STRING "")
10+
11+
set(out_file "${CMAKE_CURRENT_BINARY_DIR}/log.txt")
12+
file(WRITE "${out_file}" "")
13+
14+
foreach(list IN ITEMS ONE_LEVEL_LIST NESTED_LIST)
15+
list(LENGTH ${list} length)
16+
file(APPEND "${out_file}" "${list}.LENGTH = ${length}\n")
17+
foreach(item IN LISTS ${list})
18+
file(APPEND "${out_file}" "${item}\n")
19+
endforeach()
20+
endforeach()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[tool.scikit-build]
2+
cmake.version = '>=3.15'
3+
4+
[tool.scikit-build.cmake.define]
5+
ONE_LEVEL_LIST = [
6+
"Foo",
7+
"Bar",
8+
"ExceptionallyLargeListEntryThatWouldOverflowTheLine",
9+
"Baz",
10+
]
11+
NESTED_LIST = [ "Apple", "Lemon;Lime", "Banana" ]

tests/test_cmake_config.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import shutil
55
import sysconfig
66
from pathlib import Path
7+
from textwrap import dedent
78
from typing import TYPE_CHECKING
89

910
import pytest
1011
from packaging.specifiers import SpecifierSet
1112
from packaging.version import Version
1213

14+
from scikit_build_core.builder.builder import Builder
1315
from scikit_build_core.cmake import CMake, CMaker
1416
from scikit_build_core.errors import CMakeNotFoundError
17+
from scikit_build_core.settings.skbuild_read_settings import SettingsReader
1518

1619
if TYPE_CHECKING:
1720
from collections.abc import Generator
@@ -201,6 +204,41 @@ def test_cmake_paths(
201204
assert len(fp.calls) == 2
202205

203206

207+
@pytest.mark.configure
208+
def test_cmake_defines(
209+
tmp_path: Path,
210+
):
211+
source_dir = DIR / "packages" / "cmake_defines"
212+
binary_dir = tmp_path / "build"
213+
214+
config = CMaker(
215+
CMake.default_search(),
216+
source_dir=source_dir,
217+
build_dir=binary_dir,
218+
build_type="Release",
219+
)
220+
221+
reader = SettingsReader.from_file(source_dir / "pyproject.toml")
222+
223+
builder = Builder(reader.settings, config)
224+
builder.configure(defines={})
225+
226+
configure_log = Path.read_text(binary_dir / "log.txt")
227+
assert configure_log == dedent(
228+
"""\
229+
ONE_LEVEL_LIST.LENGTH = 4
230+
Foo
231+
Bar
232+
ExceptionallyLargeListEntryThatWouldOverflowTheLine
233+
Baz
234+
NESTED_LIST.LENGTH = 3
235+
Apple
236+
Lemon;Lime
237+
Banana
238+
"""
239+
)
240+
241+
204242
def test_get_cmake_via_envvar(monkeypatch: pytest.MonkeyPatch, fp):
205243
monkeypatch.setattr("shutil.which", lambda x: x)
206244
cmake_path = Path("some-prog")

tests/test_skbuild_settings.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,8 @@ def test_skbuild_settings_pyproject_toml_envvar_defines(
541541
"a": "1",
542542
"b": "2",
543543
"c": "empty",
544-
"d": False,
545-
"e": False,
544+
"d": "FALSE",
545+
"e": "FALSE",
546546
}
547547

548548
monkeypatch.setenv("DEFAULT", "3")
@@ -552,8 +552,8 @@ def test_skbuild_settings_pyproject_toml_envvar_defines(
552552
"a": "1",
553553
"b": "2",
554554
"c": "3",
555-
"d": False,
556-
"e": True,
555+
"d": "FALSE",
556+
"e": "TRUE",
557557
}
558558

559559

@@ -754,3 +754,19 @@ def test_skbuild_settings_auto_cmake_warning(
754754
Report this or (and) set manually to avoid this warning. Using 3.15 as a fall-back.
755755
""".split()
756756
)
757+
758+
759+
def test_skbuild_settings_cmake_define_list():
760+
pyproject_toml = (
761+
Path(__file__).parent / "packages" / "cmake_defines" / "pyproject.toml"
762+
)
763+
764+
config_settings: dict[str, list[str] | str] = {}
765+
766+
settings_reader = SettingsReader.from_file(pyproject_toml, config_settings)
767+
settings = settings_reader.settings
768+
769+
assert settings.cmake.define == {
770+
"NESTED_LIST": r"Apple;Lemon\;Lime;Banana",
771+
"ONE_LEVEL_LIST": "Foo;Bar;ExceptionallyLargeListEntryThatWouldOverflowTheLine;Baz",
772+
}

0 commit comments

Comments
 (0)