diff --git a/cppython/project.py b/cppython/project.py index bd36f21..9b956eb 100644 --- a/cppython/project.py +++ b/cppython/project.py @@ -4,7 +4,6 @@ import logging from importlib import metadata -from pathlib import Path from typing import Any, Type, TypeVar from cppython_core.core import cppython_logger @@ -19,8 +18,8 @@ ) from pydantic import create_model -from cppython.schema import API, CMakePresets, ConfigurePreset, ProjectConfiguration -from cppython.utility import write_preset +from cppython.schema import API, ProjectConfiguration +from cppython.utility import write_preset, write_presets class ProjectBuilder: @@ -188,31 +187,6 @@ def download(self): else: cppython_logger.info(f"The {generator.name()} generator is already downloaded") - def _write_generator_presets(self, tool_path: Path, generator: Generator, toolchain_path: Path) -> Path: - """ - Write a generator preset. - @returns - The written directory - """ - generator_tool_path = tool_path / generator.name() - generator_tool_path.mkdir(parents=True, exist_ok=True) - - configure_preset = ConfigurePreset(name=generator.name(), hidden=True, toolchainFile=toolchain_path) - presets = CMakePresets(configurePresets=[configure_preset]) - - write_preset(generator_tool_path, presets) - - return generator_tool_path - - def _write_presets(self, tool_path: Path, names: list[str], includes: list[Path]) -> None: - """ - Write the cppython main preset - """ - - configure_preset = ConfigurePreset(name="cppython", hidden=True, inherits=names) - presets = CMakePresets(configurePresets=[configure_preset], include=includes) - - write_preset(tool_path, presets) - # API Contract def install(self) -> None: """ @@ -228,20 +202,20 @@ def install(self) -> None: tool_path = self.pyproject.tool.cppython.tool_path tool_path.mkdir(parents=True, exist_ok=True) - names = [] - includes = [] + generator_output = [] # TODO: Async for generator in self._generators: cppython_logger.info(f"Installing {generator.name()} generator") - toolchain_path = generator.install() - - directory = self._write_generator_presets(tool_path, generator, toolchain_path) - includes.append(directory) - names.append(generator.name()) + try: + toolchain_path = generator.install() + generator_output.append((generator.name(), toolchain_path)) + except Exception as exception: + cppython_logger.error(f"Generator {generator.name()} failed to install") + raise exception - self._write_presets(tool_path, names, includes) + write_presets(tool_path, generator_output) def update(self) -> None: """ @@ -256,17 +230,17 @@ def update(self) -> None: tool_path = self.pyproject.tool.cppython.tool_path tool_path.mkdir(parents=True, exist_ok=True) - names = [] - includes = [] + generator_output = [] # TODO: Async for generator in self._generators: cppython_logger.info(f"Updating {generator.name()} generator") - toolchain_path = generator.update() - - directory = self._write_generator_presets(tool_path, generator, toolchain_path) - includes.append(directory) - names.append(generator.name()) + try: + toolchain_path = generator.update() + generator_output.append((generator.name(), toolchain_path)) + except Exception as exception: + cppython_logger.error(f"Generator {generator.name()} failed to update") + raise exception - self._write_presets(tool_path, names, includes) + write_presets(tool_path, generator_output) diff --git a/cppython/schema.py b/cppython/schema.py index 56b5989..d121b93 100644 --- a/cppython/schema.py +++ b/cppython/schema.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Any, Optional -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Extra, Field, validator class Preset(BaseModel): @@ -18,10 +18,10 @@ class Preset(BaseModel): """ name: str - hidden: Optional[bool] = None + hidden: Optional[bool] inherits: list[str] = [] - displayName: Optional[str] = None - description: Optional[str] = None + displayName: Optional[str] + description: Optional[str] class ConfigurePreset(Preset): @@ -29,7 +29,11 @@ class ConfigurePreset(Preset): Partial Configure Preset specification """ - toolchainFile: Optional[Path] = None + toolchainFile: Optional[str] + + @validator("toolchainFile") + def validate_path(cls, v): + return Path(v).as_posix() class BuildPreset(Preset): @@ -37,8 +41,8 @@ class BuildPreset(Preset): Partial Build Preset specification """ - configurePreset: Optional[str] = None - inheritConfigureEnvironment: Optional[bool] = None + configurePreset: Optional[str] + inheritConfigureEnvironment: Optional[bool] class TestPreset(Preset): @@ -46,8 +50,8 @@ class TestPreset(Preset): Partial Test Preset specification """ - configurePreset: Optional[str] = None - inheritConfigureEnvironment: Optional[bool] = None + configurePreset: Optional[str] + inheritConfigureEnvironment: Optional[bool] class CMakeVersion(BaseModel, extra=Extra.forbid): @@ -67,12 +71,19 @@ class CMakePresets(BaseModel, extra=Extra.forbid): version: int = Field(default=4, const=True) cmakeMinimumRequired: CMakeVersion = CMakeVersion() # TODO: 'version' compatability validation - include: list[Path] = [] - vendor: Optional[Any] = None + include: list[str] = [] + vendor: Optional[Any] configurePresets: list[ConfigurePreset] = [] buildPresets: list[BuildPreset] = [] testPresets: list[TestPreset] = [] + @validator("include") + def validate_path(cls, v): + output = [] + for value in v: + output.append(Path(value).as_posix()) + return output + @dataclass class ProjectConfiguration: diff --git a/cppython/utility.py b/cppython/utility.py index ff9a39f..b7d601a 100644 --- a/cppython/utility.py +++ b/cppython/utility.py @@ -5,21 +5,64 @@ import json from pathlib import Path -from cppython.schema import CMakePresets +from cppython.schema import CMakePresets, ConfigurePreset -def read_preset(path: Path) -> CMakePresets: +def read_preset(name: str, path: Path) -> CMakePresets: """ Reading routing """ - preset_path = path / "CMakePresets.json" + preset_path = path / f"{name}.json" return CMakePresets.parse_file(path=preset_path) -def write_preset(path: Path, presets: CMakePresets) -> None: +def write_preset(name: str, path: Path, presets: CMakePresets) -> Path: """ Writing routine """ - with open(path / "CMakePresets.json", "w", encoding="utf8") as json_file: - json.dump(presets.dict(), json_file, ensure_ascii=False, indent=2) + file = path / f"{name}.json" + + serialized = json.loads(presets.json(exclude_none=True)) + with open(file, "w", encoding="utf8") as json_file: + json.dump(serialized, json_file, ensure_ascii=False, indent=2) + + return file + + +def write_presets(tool_path: Path, generator_output: list[tuple[str, Path]]) -> None: + """ + Write the cppython presets + """ + + def write_generator_presets(tool_path: Path, generator_name: str, toolchain_path: Path) -> Path: + """ + Write a generator preset. + @returns - The written json file + """ + generator_tool_path = tool_path / generator_name + generator_tool_path.mkdir(parents=True, exist_ok=True) + + configure_preset = ConfigurePreset(name=generator_name, hidden=True, toolchainFile=str(toolchain_path)) + presets = CMakePresets(configurePresets=[configure_preset]) + + return write_preset(generator_name, generator_tool_path, presets) + + names = [] + includes = [] + + tool_path = tool_path / "cppython" + + for generator_name, toolchain in generator_output: + + preset_file = write_generator_presets(tool_path, generator_name, toolchain) + + relative_file = preset_file.relative_to(tool_path) + + names.append(generator_name) + includes.append(str(relative_file)) + + configure_preset = ConfigurePreset(name="cppython", hidden=True, inherits=names) + presets = CMakePresets(configurePresets=[configure_preset], include=includes) + + write_preset("cppython", tool_path, presets) diff --git a/tests/unit/test_utility.py b/tests/unit/test_utility.py index 6b298e7..fde0f81 100644 --- a/tests/unit/test_utility.py +++ b/tests/unit/test_utility.py @@ -4,7 +4,7 @@ from pathlib import Path from cppython.schema import CMakePresets -from cppython.utility import read_preset, write_preset +from cppython.utility import read_preset, write_preset, write_presets class TestBuilder: @@ -12,13 +12,42 @@ class TestBuilder: TODO """ - def test_preset_read_write(self, tmpdir: Path): + def test_preset_read_write(self, tmpdir): """ TODO """ + temporary_directory = Path(tmpdir) + presets = CMakePresets() - write_preset(tmpdir, presets) - output = read_preset(tmpdir) + write_preset("test", temporary_directory, presets) + output = read_preset("test", temporary_directory) assert presets == output + + def test_presets(self, tmpdir): + """ + TODO + """ + + temporary_directory = Path(tmpdir) + + input_toolchain = temporary_directory / "input.cmake" + + with open(input_toolchain, "w", encoding="utf8") as file: + file.write("") + + generator_output = [("test", input_toolchain)] + write_presets(temporary_directory, generator_output) + + cppython_tool = temporary_directory / "cppython" + assert cppython_tool.exists() + + cppython_file = cppython_tool / "cppython.json" + assert cppython_file.exists() + + test_tool = cppython_tool / "test" + assert test_tool.exists() + + test_file = test_tool / "test.json" + assert test_file.exists()