You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

cppython

Package Overview
Dependencies
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cppython - pypi Package Compare versions

Comparing version
0.8.1.dev1
to
0.8.1.dev10
+177
cppython/plugins/conan/builder.py
"""Construction of Conan data"""
from pathlib import Path
from string import Template
from textwrap import dedent
import libcst as cst
from pydantic import DirectoryPath
from cppython.plugins.conan.schema import ConanDependency
class RequiresTransformer(cst.CSTTransformer):
"""Transformer to add or update the `requires` attribute in a ConanFile class."""
def __init__(self, dependencies: list[ConanDependency]) -> None:
"""Initialize the transformer with a list of dependencies."""
self.dependencies = dependencies
def _create_requires_assignment(self) -> cst.Assign:
"""Create a `requires` assignment statement."""
return cst.Assign(
targets=[cst.AssignTarget(cst.Name('requires'))],
value=cst.List([
cst.Element(cst.SimpleString(f'"{dependency.requires()}"')) for dependency in self.dependencies
]),
)
def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.BaseStatement:
"""Modify the class definition to include or update 'requires'.
Args:
original_node: The original class definition.
updated_node: The updated class definition.
Returns: The modified class definition.
"""
if self._is_conanfile_class(original_node):
updated_node = self._update_requires(updated_node)
return updated_node
@staticmethod
def _is_conanfile_class(class_node: cst.ClassDef) -> bool:
"""Check if the class inherits from ConanFile.
Args:
class_node: The class definition to check.
Returns: True if the class inherits from ConanFile, False otherwise.
"""
return any((isinstance(base.value, cst.Name) and base.value.value == 'ConanFile') for base in class_node.bases)
def _update_requires(self, updated_node: cst.ClassDef) -> cst.ClassDef:
"""Update or add a 'requires' assignment in a ConanFile class definition."""
# Check if 'requires' is already defined
for body_statement_line in updated_node.body.body:
if not isinstance(body_statement_line, cst.SimpleStatementLine):
continue
assignment_statement = body_statement_line.body[0]
if not isinstance(assignment_statement, cst.Assign):
continue
for target in assignment_statement.targets:
if not isinstance(target.target, cst.Name) or target.target.value != 'requires':
continue
return self._replace_requires(updated_node, body_statement_line, assignment_statement)
# Find the last attribute assignment before methods
last_attribute = None
for body_statement_line in updated_node.body.body:
if not isinstance(body_statement_line, cst.SimpleStatementLine):
break
assignment_statement = body_statement_line.body[0]
if not isinstance(assignment_statement, cst.Assign):
break
last_attribute = body_statement_line
# Construct a new statement for the 'requires' attribute
new_statement = cst.SimpleStatementLine(
body=[self._create_requires_assignment()],
)
# Insert the new statement after the last attribute assignment
if last_attribute is not None:
new_body = list(updated_node.body.body)
index = new_body.index(last_attribute)
new_body.insert(index + 1, new_statement)
else:
new_body = [new_statement] + list(updated_node.body.body)
return updated_node.with_changes(body=updated_node.body.with_changes(body=new_body))
def _replace_requires(
self, updated_node: cst.ClassDef, body_statement_line: cst.SimpleStatementLine, assignment_statement: cst.Assign
) -> cst.ClassDef:
"""Replace the existing 'requires' assignment with a new one.
Args:
updated_node (cst.ClassDef): The class definition to update.
body_statement_line (cst.SimpleStatementLine): The body item containing the assignment.
assignment_statement (cst.Assign): The existing assignment statement.
Returns:
cst.ClassDef: The updated class definition.
"""
new_value = cst.List([
cst.Element(cst.SimpleString(f'"{dependency.requires()}"')) for dependency in self.dependencies
])
new_assignment = assignment_statement.with_changes(value=new_value)
return updated_node.with_changes(
body=updated_node.body.with_changes(
body=[new_assignment if item is body_statement_line else item for item in updated_node.body.body]
)
)
class Builder:
"""Aids in building the information needed for the Conan plugin"""
def __init__(self) -> None:
"""Initialize the builder"""
self._filename = 'conanfile.py'
@staticmethod
def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency]) -> None:
"""Creates a conanfile.py file with the necessary content."""
template_string = """
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
class MyProject(ConanFile):
name = "myproject"
version = "1.0"
settings = "os", "compiler", "build_type", "arch"
requires = ${dependencies}
generators = "CMakeDeps"
def layout(self):
cmake_layout(self)
def generate(self):
tc = CMakeToolchain(self)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()"""
template = Template(dedent(template_string))
values = {
'dependencies': [dependency.requires() for dependency in dependencies],
}
result = template.substitute(values)
with open(conan_file, 'w', encoding='utf-8') as file:
file.write(result)
def generate_conanfile(self, directory: DirectoryPath, dependencies: list[ConanDependency]) -> None:
"""Generate a conanfile.py file for the project."""
conan_file = directory / self._filename
if conan_file.exists():
source_code = conan_file.read_text(encoding='utf-8')
module = cst.parse_module(source_code)
transformer = RequiresTransformer(dependencies)
modified = module.visit(transformer)
conan_file.write_text(modified.code, encoding='utf-8')
else:
directory.mkdir(parents=True, exist_ok=True)
self._create_conanfile(conan_file, dependencies)
"""Integration tests for the conan and CMake project variation.
This module contains integration tests for projects that use conan and CMake.
The tests ensure that the projects build, configure, and execute correctly.
"""
import subprocess
from pathlib import Path
from typer.testing import CliRunner
from cppython.console.entry import app
pytest_plugins = ['tests.fixtures.example']
class TestConanCMake:
"""Test project variation of conan and CMake"""
@staticmethod
def test_simple(example_runner: CliRunner) -> None:
"""Simple project"""
result = example_runner.invoke(
app,
[
'install',
],
)
assert result.exit_code == 0, result.output
# Run the CMake configuration command
cmake_result = subprocess.run(['cmake', '--preset=default'], capture_output=True, text=True, check=False)
assert cmake_result.returncode == 0, f'CMake configuration failed: {cmake_result.stderr}'
# Verify that the build directory contains the expected files
assert (Path('build') / 'CMakeCache.txt').exists(), 'build/CMakeCache.txt not found'
@staticmethod
def test_inject(example_runner: CliRunner) -> None:
"""Inject"""
result = example_runner.invoke(
app,
[
'install',
],
)
assert result.exit_code == 0, result.output
# Run the CMake configuration command
cmake_result = subprocess.run(['cmake', '--preset=default'], capture_output=True, text=True, check=False)
assert cmake_result.returncode == 0, f'CMake configuration failed: {cmake_result.stderr}'
# Verify that the build directory contains the expected files
assert (Path('build') / 'CMakeCache.txt').exists(), 'build/CMakeCache.txt not found'
+85
-23
"""Plugin builder"""
import json
from copy import deepcopy
from pathlib import Path

@@ -13,4 +11,7 @@

def __init__(self) -> None:
"""Initialize the builder"""
@staticmethod
def write_provider_preset(provider_directory: Path, data: CMakeSyncData) -> None:
def write_provider_preset(provider_directory: Path, provider_data: CMakeSyncData) -> None:
"""Writes a provider preset from input sync data

@@ -20,16 +21,31 @@

provider_directory: The base directory to place the preset files
data: The providers synchronization data
provider_data: The providers synchronization data
"""
configure_preset = ConfigurePreset(name=data.provider_name, cacheVariables=None)
presets = CMakePresets(configurePresets=[configure_preset])
generated_configure_preset = ConfigurePreset(name=provider_data.provider_name)
json_path = provider_directory / f'{data.provider_name}.json'
# Toss in that sync data from the provider
generated_configure_preset.cacheVariables = {
'CMAKE_PROJECT_TOP_LEVEL_INCLUDES': str(provider_data.top_level_includes.as_posix()),
}
serialized = json.loads(presets.model_dump_json(exclude_none=True, by_alias=False))
with open(json_path, 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
generated_preset = CMakePresets(configurePresets=[generated_configure_preset])
provider_preset_file = provider_directory / f'{provider_data.provider_name}.json'
initial_preset = None
# If the file already exists, we need to compare it
if provider_preset_file.exists():
with open(provider_preset_file, encoding='utf-8') as file:
initial_json = file.read()
initial_preset = CMakePresets.model_validate_json(initial_json)
if generated_preset != initial_preset:
serialized = generated_preset.model_dump_json(exclude_none=True, by_alias=False, indent=4)
with open(provider_preset_file, 'w', encoding='utf8') as file:
file.write(serialized)
@staticmethod
def write_cppython_preset(
cppython_preset_directory: Path, _provider_directory: Path, _provider_data: CMakeSyncData
cppython_preset_directory: Path, provider_directory: Path, provider_data: CMakeSyncData
) -> Path:

@@ -40,2 +56,4 @@ """Write the cppython presets which inherit from the provider presets

cppython_preset_directory: The tool directory
provider_directory: The base directory containing provider presets
provider_data: The provider's synchronization data

@@ -45,15 +63,32 @@ Returns:

"""
configure_preset = ConfigurePreset(name='cppython', cacheVariables=None)
presets = CMakePresets(configurePresets=[configure_preset])
generated_configure_preset = ConfigurePreset(name='cppython', inherits=provider_data.provider_name)
generated_preset = CMakePresets(configurePresets=[generated_configure_preset])
cppython_json_path = cppython_preset_directory / 'cppython.json'
# Get the relative path to the provider preset file
provider_preset_file = provider_directory / f'{provider_data.provider_name}.json'
relative_preset = provider_preset_file.relative_to(cppython_preset_directory, walk_up=True).as_posix()
serialized = json.loads(presets.model_dump_json(exclude_none=True, by_alias=False))
with open(cppython_json_path, 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
# Set the data
generated_preset.include = [relative_preset]
return cppython_json_path
cppython_preset_file = cppython_preset_directory / 'cppython.json'
initial_preset = None
# If the file already exists, we need to compare it
if cppython_preset_file.exists():
with open(cppython_preset_file, encoding='utf-8') as file:
initial_json = file.read()
initial_preset = CMakePresets.model_validate_json(initial_json)
# Only write the file if the data has changed
if generated_preset != initial_preset:
serialized = generated_preset.model_dump_json(exclude_none=True, by_alias=False, indent=4)
with open(cppython_preset_file, 'w', encoding='utf8') as file:
file.write(serialized)
return cppython_preset_file
@staticmethod
def write_root_presets(preset_file: Path, _: Path) -> None:
def write_root_presets(preset_file: Path, cppython_preset_file: Path) -> None:
"""Read the top level json file and insert the include reference.

@@ -68,8 +103,35 @@

preset_file: Preset file to modify
cppython_preset_file: Path to the cppython preset file to include
"""
with open(preset_file, encoding='utf-8') as file:
initial_root_preset = json.load(file)
initial_root_preset = None
if (root_preset := deepcopy(initial_root_preset)) != initial_root_preset:
# If the file already exists, we need to compare it
if preset_file.exists():
with open(preset_file, encoding='utf-8') as file:
initial_json = file.read()
initial_root_preset = CMakePresets.model_validate_json(initial_json)
root_preset = initial_root_preset.model_copy(deep=True)
else:
# If the file doesn't exist, we need to default it for the user
# Forward the tool's build directory
default_configure_preset = ConfigurePreset(name='default', inherits='cppython', binaryDir='build')
root_preset = CMakePresets(configurePresets=[default_configure_preset])
# Get the relative path to the cppython preset file
preset_directory = preset_file.parent.absolute()
relative_preset = cppython_preset_file.relative_to(preset_directory, walk_up=True).as_posix()
# If the include key doesn't exist, we know we will write to disk afterwards
if not root_preset.include:
root_preset.include = []
# Only the included preset file if it doesn't exist. Implied by the above check
if str(relative_preset) not in root_preset.include:
root_preset.include.append(str(relative_preset))
# Only write the file if the data has changed
if root_preset != initial_root_preset:
with open(preset_file, 'w', encoding='utf-8') as file:
json.dump(root_preset, file, ensure_ascii=False, indent=4)
preset = root_preset.model_dump_json(exclude_none=True, indent=4)
file.write(preset)
"""Builder to help resolve cmake state"""
import json
from typing import Any
from cppython.core.schema import CorePluginData
from cppython.plugins.cmake.schema import CMakeConfiguration, CMakeData, CMakePresets
from cppython.plugins.cmake.schema import CMakeConfiguration, CMakeData

@@ -28,9 +27,2 @@

# If the user hasn't specified a preset file, we need to create one
if not modified_preset_dir.exists():
modified_preset_dir.parent.mkdir(parents=True, exist_ok=True)
with modified_preset_dir.open('w', encoding='utf-8') as file:
presets_dict = CMakePresets().model_dump_json(exclude_none=True)
json.dump(presets_dict, file, ensure_ascii=False, indent=4)
return CMakeData(preset_file=modified_preset_dir, configuration_name=parsed_data.configuration_name)

@@ -1,3 +0,8 @@

"""CMake data definitions"""
"""CMake plugin schema
This module defines the schema and data models for integrating the CMake
generator with CPPython. It includes definitions for cache variables,
configuration presets, and synchronization data.
"""
from enum import Enum, auto

@@ -14,6 +19,6 @@ from pathlib import Path

class VariableType(Enum):
"""_summary_
"""Defines the types of variables that can be used in CMake cache.
Args:
Enum: _description_
Enum: Base class for creating enumerations.
"""

@@ -31,4 +36,9 @@

class CacheVariable(CPPythonModel, extra='forbid'):
"""_summary_"""
"""Represents a variable in the CMake cache.
Attributes:
type: The type of the variable (e.g., BOOL, PATH).
value: The value of the variable, which can be a boolean or string.
"""
type: None | VariableType

@@ -42,3 +52,7 @@ value: bool | str

name: str
cacheVariables: dict[str, None | bool | str | CacheVariable] | None
inherits: Annotated[
str | list[str] | None, Field(description='The inherits field allows inheriting from other presets.')
] = None
binaryDir: Annotated[str | None, Field(description='The binary directory for the build output.')] = None
cacheVariables: dict[str, None | bool | str | CacheVariable] | None = None

@@ -52,3 +66,7 @@

configurePresets: Annotated[list[ConfigurePreset], Field(description='The list of configure presets')] = []
version: Annotated[int, Field(description='The version of the JSON schema.')] = 9
include: Annotated[
list[str] | None, Field(description='The include field allows inheriting from another preset.')
] = None
configurePresets: Annotated[list[ConfigurePreset] | None, Field(description='The list of configure presets')] = None

@@ -65,3 +83,3 @@

preset_file: FilePath
preset_file: Path
configuration_name: str

@@ -68,0 +86,0 @@

@@ -1,3 +0,8 @@

"""_summary_"""
"""Conan Provider Plugin
This module implements the Conan provider plugin for CPPython. It handles
integration with the Conan package manager, including dependency resolution,
installation, and synchronization with other tools.
"""
from pathlib import Path

@@ -13,3 +18,4 @@ from typing import Any

from cppython.plugins.cmake.schema import CMakeSyncData
from cppython.plugins.conan.resolution import resolve_conan_data
from cppython.plugins.conan.builder import Builder
from cppython.plugins.conan.resolution import resolve_conan_data, resolve_conan_dependency
from cppython.plugins.conan.schema import ConanData

@@ -33,2 +39,4 @@ from cppython.utility.exception import NotSupportedError

self.builder = Builder()
@staticmethod

@@ -66,15 +74,25 @@ def _download_file(url: str, file: Path) -> None:

"""Installs the provider"""
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)
def update(self) -> None:
"""Updates the provider"""
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)
@staticmethod
def supported_sync_type(sync_type: type[SyncData]) -> bool:
"""_summary_
"""Checks if the given sync type is supported by the Conan provider.
Args:
sync_type: _description_
sync_type: The type of synchronization data to check.
Returns:
_description_
True if the sync type is supported, False otherwise.
"""

@@ -84,9 +102,12 @@ return sync_type in CMakeGenerator.sync_types()

def sync_data(self, consumer: SyncConsumer) -> SyncData:
"""_summary_
"""Generates synchronization data for the given consumer.
Args:
consumer: _description_
consumer: The input consumer for which synchronization data is generated.
Returns:
_description_
The synchronization data object.
Raises:
NotSupportedError: If the consumer's sync type is not supported.
"""

@@ -97,3 +118,3 @@ for sync_type in consumer.sync_types():

provider_name=TypeName('conan'),
top_level_includes=self.core_data.cppython_data.tool_path / 'conan' / 'conan_provider.cmake',
top_level_includes=self.core_data.cppython_data.install_path / 'conan_provider.cmake',
)

@@ -100,0 +121,0 @@

@@ -1,9 +0,34 @@

"""_summary_"""
"""Provides functionality to resolve Conan-specific data for the CPPython project."""
from typing import Any
from packaging.requirements import Requirement
from cppython.core.exception import ConfigException
from cppython.core.schema import CorePluginData
from cppython.plugins.conan.schema import ConanData
from cppython.plugins.conan.schema import ConanData, ConanDependency
def resolve_conan_dependency(requirement: Requirement) -> ConanDependency:
"""Resolves a Conan dependency from a requirement"""
specifiers = requirement.specifier
# If the length of specifiers is greater than one, raise a configuration error
if len(specifiers) > 1:
raise ConfigException('Multiple specifiers are not supported. Please provide a single specifier.', [])
# Extract the version from the single specifier
min_version = None
if len(specifiers) == 1:
specifier = next(iter(specifiers))
if specifier.operator != '>=':
raise ConfigException(f"Unsupported specifier '{specifier.operator}'. Only '>=' is supported.", [])
min_version = specifier.version
return ConanDependency(
name=requirement.name,
version_ge=min_version,
)
def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> ConanData:

@@ -10,0 +35,0 @@ """Resolves the conan data

@@ -1,6 +0,26 @@

"""TODO"""
"""Conan plugin schema
This module defines Pydantic models used for integrating the Conan
package manager with the CPPython environment. The classes within
provide structured configuration and data needed by the Conan Provider.
"""
from cppython.core.schema import CPPythonModel
class ConanDependency(CPPythonModel):
"""Dependency information"""
name: str
version_ge: str | None = None
include_prerelease: bool | None = None
def requires(self) -> str:
"""Generate the requires attribute for Conan"""
# TODO: Implement lower and upper bounds per conan documentation
if self.version_ge:
return f'{self.name}/[>={self.version_ge}]'
return self.name
class ConanData(CPPythonModel):

@@ -7,0 +27,0 @@ """Resolved conan data"""

@@ -1,3 +0,8 @@

"""Git SCM plugin"""
"""Git SCM Plugin
This module implements the Git SCM plugin for CPPython. It provides
functionality for interacting with Git repositories, including feature
detection, version extraction, and project description retrieval.
"""
from pathlib import Path

@@ -4,0 +9,0 @@

"""The vcpkg provider implementation"""
import json
from logging import getLogger

@@ -50,9 +49,9 @@ from os import name as system_name

def supported_sync_type(sync_type: type[SyncData]) -> bool:
"""_summary_
"""Checks if the given sync type is supported by the vcpkg provider.
Args:
sync_type: _description_
sync_type: The type of synchronization data to check.
Returns:
_description_
True if the sync type is supported, False otherwise.
"""

@@ -197,5 +196,5 @@ return sync_type in CMakeGenerator.sync_types()

# Write out the manifest
serialized = json.loads(manifest.model_dump_json(exclude_none=True, by_alias=True))
serialized = manifest.model_dump_json(exclude_none=True, by_alias=True, indent=4)
with open(manifest_directory / 'vcpkg.json', 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
file.write(serialized)

@@ -229,5 +228,5 @@ executable = self.core_data.cppython_data.install_path / 'vcpkg'

# Write out the manifest
serialized = json.loads(manifest.model_dump_json(exclude_none=True, by_alias=True))
serialized = manifest.model_dump_json(exclude_none=True, by_alias=True, indent=4)
with open(manifest_directory / 'vcpkg.json', 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
file.write(serialized)

@@ -234,0 +233,0 @@ executable = self.core_data.cppython_data.install_path / 'vcpkg'

@@ -54,6 +54,6 @@ """Shared definitions for testing."""

def sync_types() -> list[type[SyncData]]:
"""_summary_
"""Returns the supported synchronization data types for the mock generator.
Returns:
_description_
A list of supported synchronization data types.
"""

@@ -60,0 +60,0 @@ return [MockSyncData]

Metadata-Version: 2.1
Name: cppython
Version: 0.8.1.dev1
Version: 0.8.1.dev10
Summary: A Python management solution for C++ dependencies

@@ -21,3 +21,8 @@ Author-Email: Synodic Software <contact@synodic.software>

Provides-Extra: pdm
Requires-Dist: pdm>=2.23.0; extra == "pdm"
Requires-Dist: pdm>=2.23.1; extra == "pdm"
Provides-Extra: cmake
Requires-Dist: cmake>=4.0.0; extra == "cmake"
Provides-Extra: conan
Requires-Dist: conan>=2.15.0; extra == "conan"
Requires-Dist: libcst>=1.7.0; extra == "conan"
Description-Content-Type: text/markdown

@@ -24,0 +29,0 @@

@@ -17,3 +17,3 @@ [project]

]
version = "0.8.1.dev1"
version = "0.8.1.dev10"

@@ -32,4 +32,11 @@ [project.license]

pdm = [
"pdm>=2.23.0",
"pdm>=2.23.1",
]
cmake = [
"cmake>=4.0.0",
]
conan = [
"conan>=2.15.0",
"libcst>=1.7.0",
]

@@ -61,3 +68,3 @@ [project.urls]

lint = [
"ruff>=0.11.4",
"ruff>=0.11.5",
"mypy>=1.15.0",

@@ -72,2 +79,5 @@ ]

[tool.pytest.ini_options]
addopts = [
"--color=yes",
]
log_cli = true

@@ -74,0 +84,0 @@ testpaths = [

@@ -1,4 +0,9 @@

"""TODO"""
"""Integration tests for the vcpkg and CMake project variation.
This module contains integration tests for projects that use vcpkg and CMake.
The tests ensure that the projects build, configure, and execute correctly.
"""
import subprocess
from pathlib import Path

@@ -30,17 +35,9 @@ import pytest

# Run the CMake configuration command
cmake_result = subprocess.run(['cmake', '--preset=default'], capture_output=True, text=True, check=False)
cmake_result = subprocess.run(
['cmake', '--preset=default', '-B', 'build'], capture_output=True, text=True, check=False
)
assert cmake_result.returncode == 0, f'CMake configuration failed: {cmake_result.stderr}'
# Run the CMake build command
build_result = subprocess.run(['cmake', '--build', 'build'], capture_output=True, text=True, check=False)
assert build_result.returncode == 0, f'CMake build failed: {build_result.stderr}'
assert 'Build finished successfully' in build_result.stdout, 'CMake build did not finish successfully'
# Execute the built program and verify the output
program_result = subprocess.run(['build/HelloWorld'], capture_output=True, text=True, check=False)
assert program_result.returncode == 0, f'Program execution failed: {program_result.stderr}'
assert 'Hello, World!' in program_result.stdout, 'Program output did not match expected output'
# Verify that the build directory contains the expected files
assert (Path('build') / 'CMakeCache.txt').exists(), 'build/CMakeCache.txt not found'
"""Unit test the provider plugin"""
import json
from pathlib import Path

@@ -108,5 +107,5 @@ from typing import Any

serialized = json.loads(presets.model_dump_json(exclude_none=True, by_alias=False))
serialized = presets.model_dump_json(exclude_none=True, by_alias=False, indent=4)
with open(root_file, 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
file.write(serialized)

@@ -144,5 +143,5 @@ data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file)

presets = CMakePresets()
serialized = json.loads(presets.model_dump_json(exclude_none=True, by_alias=False))
serialized = presets.model_dump_json(exclude_none=True, by_alias=False, indent=4)
with open(root_file, 'w', encoding='utf8') as file:
json.dump(serialized, file, ensure_ascii=False, indent=4)
file.write(serialized)

@@ -149,0 +148,0 @@ data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file)