diff --git a/src/aedifix/__init__.py b/src/aedifix/__init__.py index 3b22c3f..a7f7e23 100644 --- a/src/aedifix/__init__.py +++ b/src/aedifix/__init__.py @@ -13,6 +13,7 @@ CMakeList, CMakePath, CMaker, + CMakeSemiColonList, CMakeString, ) from .main import basic_configure @@ -28,6 +29,7 @@ "CMakeInt", "CMakeList", "CMakePath", + "CMakeSemiColonList", "CMakeString", "CMaker", "ConfigArgument", diff --git a/src/aedifix/cmake/__init__.py b/src/aedifix/cmake/__init__.py index 5704267..0f04bf3 100644 --- a/src/aedifix/cmake/__init__.py +++ b/src/aedifix/cmake/__init__.py @@ -10,6 +10,7 @@ CMakeInt, CMakeList, CMakePath, + CMakeSemiColonList, CMakeString, ) from .cmaker import CMaker @@ -21,6 +22,7 @@ "CMakeInt", "CMakeList", "CMakePath", + "CMakeSemiColonList", "CMakeString", "CMaker", ) diff --git a/src/aedifix/cmake/cmake_flags.py b/src/aedifix/cmake/cmake_flags.py index 7a670c8..dc22cff 100644 --- a/src/aedifix/cmake/cmake_flags.py +++ b/src/aedifix/cmake/cmake_flags.py @@ -237,14 +237,22 @@ def _canonicalize_cb(self) -> tuple[bool, list[str] | None]: val = [v for v in (str(x).strip() for x in val) if v] return bool(val), val - def to_command_line(self, *, quote: bool = False) -> str: + def _cmake_list_to_command_line(self, *, quote: bool, sep: str) -> str: if (val := self.value) is None: val = [] - val = " ".join(map(str, val)) + val = sep.join(map(str, val)) if quote: val = shlex_quote(val) return f"{self.prefix}{self.name}:{self.type}={val}" + def to_command_line(self, *, quote: bool = False) -> str: + return self._cmake_list_to_command_line(quote=quote, sep=" ") + + +class CMakeSemiColonList(CMakeList): + def to_command_line(self, *, quote: bool = False) -> str: + return self._cmake_list_to_command_line(quote=quote, sep=";") + class CMakeBool(CMakeFlagBase): def __init__( diff --git a/src/aedifix/packages/cuda.py b/src/aedifix/packages/cuda.py index 9ef58b4..a1b6cb0 100644 --- a/src/aedifix/packages/cuda.py +++ b/src/aedifix/packages/cuda.py @@ -10,7 +10,13 @@ from pathlib import Path from typing import TYPE_CHECKING, Final -from ..cmake import CMAKE_VARIABLE, CMakeExecutable, CMakeList, CMakePath +from ..cmake import ( + CMAKE_VARIABLE, + CMakeExecutable, + CMakeList, + CMakePath, + CMakeSemiColonList, +) from ..package import Package from ..util.argument_parser import ArgSpec, ConfigArgument @@ -143,7 +149,9 @@ class CUDA(Package): "'ampere' or 'hopper, blackwell'" ), ), - cmake_var=CMAKE_VARIABLE("CMAKE_CUDA_ARCHITECTURES", CMakeList), + cmake_var=CMAKE_VARIABLE( + "CMAKE_CUDA_ARCHITECTURES", CMakeSemiColonList + ), ) def configure(self) -> None: diff --git a/tests/cmake/test_cmake_flags.py b/tests/cmake/test_cmake_flags.py index bec59c2..b084cc2 100644 --- a/tests/cmake/test_cmake_flags.py +++ b/tests/cmake/test_cmake_flags.py @@ -18,6 +18,7 @@ CMakeInt, CMakeList, CMakePath, + CMakeSemiColonList, CMakeString, ) @@ -106,6 +107,90 @@ def test_neq(self) -> None: assert lhs != rhs_b +class TestCMakeSemiColonList: + def test_create(self) -> None: + var = CMakeSemiColonList("foo") + assert var.name == "foo" + assert var.value is None + assert var.prefix == "-D" + assert var.type == "STRING" + + var = CMakeSemiColonList("foo", value=[1, 2, 3]) + assert var.name == "foo" + assert var.value == [1, 2, 3] + assert var.prefix == "-D" + assert var.type == "STRING" + + var = CMakeSemiColonList("foo", value=(1, 2, 3), prefix="bar") + assert var.name == "foo" + assert var.value == [1, 2, 3] + assert var.prefix == "bar" + assert var.type == "STRING" + + var = CMakeSemiColonList("foo", value="foo;bar") + assert var.name == "foo" + assert var.value == ["foo", "bar"] + assert var.prefix == "-D" + assert var.type == "STRING" + + def test_create_bad(self) -> None: + # looking for "" here + with pytest.raises(TypeError, match=re.escape(rf"{int}")): + CMakeSemiColonList("foo", value=1) # type: ignore[arg-type] + + def test_canonicalize(self) -> None: + var = CMakeSemiColonList("foo") + canon = var.canonicalize() + assert canon is None + assert id(canon) != id(var) # must be distinct + assert var.name == "foo" + assert var.value is None + assert var.prefix == "-D" + assert var.type == "STRING" + + var.value = [4, 5, 6] + canon = var.canonicalize() + assert isinstance(canon, CMakeSemiColonList) + assert id(canon) != id(var) # must be distinct + assert canon.name == var.name + assert canon.value == list(map(str, var.value)) + assert id(canon.value) != id(var.value) # must be distinct + assert canon.prefix == var.prefix + assert canon.type == var.type + + @pytest.mark.parametrize( + "value", (None, [], ["1"], ["1", "2"], [34, 99, 999]) + ) + def test_to_command_line(self, value: list[str]) -> None: + val_copy = copy.deepcopy(value) + var = CMakeSemiColonList("foo", value=value) + cmd = var.to_command_line() + assert isinstance(cmd, str) + expected_str = "" if val_copy is None else ";".join(map(str, val_copy)) + assert cmd == f"-Dfoo:STRING={expected_str}" + assert var.value == val_copy + + def test_eq(self) -> None: + lhs = CMakeSemiColonList("foo", value=(1, 2, 3), prefix="bar") + rhs = CMakeSemiColonList("foo", value=(1, 2, 3), prefix="bar") + assert lhs == rhs + + def test_neq(self) -> None: + lhs = CMakeSemiColonList("foo", value=(1, 2, 3), prefix="bar") + + rhs = CMakeSemiColonList("bar", value=(1, 2, 3), prefix="bar") + assert lhs != rhs + + rhs = CMakeSemiColonList("foo", value=(1, 2, 3, 4), prefix="bar") + assert lhs != rhs + + rhs = CMakeSemiColonList("foo", value=(1, 2, 3), prefix="asdasd") + assert lhs != rhs + + rhs_b = CMakeBool("foo", value=None, prefix="asdasd") + assert lhs != rhs_b + + class TestCMakeBool: def test_create(self) -> None: var = CMakeBool("foo")