cppython
Advanced tools
| """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' |
| """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] |
+7
-2
| 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 @@ |
+13
-3
@@ -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) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
216364
7.86%115
1.77%4879
6.2%