From 2ce8d66cea282f61eb1a8bf11d27284ff284ce27 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 08:49:53 -0400 Subject: [PATCH 01/68] Consumer Example --- examples/conan_cmake/library/CMakeLists.txt | 21 ++++++++++++++ .../library/include/mathutils/mathutils.h | 29 +++++++++++++++++++ examples/conan_cmake/library/pyproject.toml | 25 ++++++++++++++++ .../conan_cmake/library/src/mathutils.cpp | 27 +++++++++++++++++ .../library_consumer/CMakeLists.txt | 11 +++++++ .../conan_cmake/library_consumer/main.cpp | 10 +++++++ .../library_consumer/pyproject.toml | 25 ++++++++++++++++ 7 files changed, 148 insertions(+) create mode 100644 examples/conan_cmake/library/CMakeLists.txt create mode 100644 examples/conan_cmake/library/include/mathutils/mathutils.h create mode 100644 examples/conan_cmake/library/pyproject.toml create mode 100644 examples/conan_cmake/library/src/mathutils.cpp create mode 100644 examples/conan_cmake/library_consumer/CMakeLists.txt create mode 100644 examples/conan_cmake/library_consumer/main.cpp create mode 100644 examples/conan_cmake/library_consumer/pyproject.toml diff --git a/examples/conan_cmake/library/CMakeLists.txt b/examples/conan_cmake/library/CMakeLists.txt new file mode 100644 index 0000000..835fac5 --- /dev/null +++ b/examples/conan_cmake/library/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.24) + +project(mathutils VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(fmt REQUIRED) + +# Create the library +add_library(mathutils src/mathutils.cpp) + +# Set up include directories +target_include_directories(mathutils + PUBLIC + $ + $ +) + +# Link dependencies +target_link_libraries(mathutils PUBLIC fmt::fmt) \ No newline at end of file diff --git a/examples/conan_cmake/library/include/mathutils/mathutils.h b/examples/conan_cmake/library/include/mathutils/mathutils.h new file mode 100644 index 0000000..63ef016 --- /dev/null +++ b/examples/conan_cmake/library/include/mathutils/mathutils.h @@ -0,0 +1,29 @@ +#pragma once + +namespace mathutils +{ + /** + * Add two numbers and return the result with formatted output + * @param a First number + * @param b Second number + * @return Sum of a and b + */ + double add(double a, double b); + + /** + * Multiply two numbers and return the result with formatted output + * @param a First number + * @param b Second number + * @return Product of a and b + */ + double multiply(double a, double b); + + /** + * Print a formatted calculation result + * @param operation The operation performed + * @param a First operand + * @param b Second operand + * @param result The result of the operation + */ + void print_result(const char *operation, double a, double b, double result); +} diff --git a/examples/conan_cmake/library/pyproject.toml b/examples/conan_cmake/library/pyproject.toml new file mode 100644 index 0000000..db4ea39 --- /dev/null +++ b/examples/conan_cmake/library/pyproject.toml @@ -0,0 +1,25 @@ +[project] +description = "A simple C++ library example using conan with CPPython" +name = "mathutils" +version = "1.0.0" + +license = { text = "MIT" } + +authors = [{ name = "Synodic Software", email = "contact@synodic.software" }] + +requires-python = ">=3.13" + +dependencies = ["cppython[conan, cmake, git]>=0.9.0"] + + +[tool.cppython] +install-path = "install" + +dependencies = ["fmt>=11.2.0"] + +[tool.cppython.generators.cmake] + +[tool.cppython.providers.conan] + +[tool.pdm] +distribution = false diff --git a/examples/conan_cmake/library/src/mathutils.cpp b/examples/conan_cmake/library/src/mathutils.cpp new file mode 100644 index 0000000..c801724 --- /dev/null +++ b/examples/conan_cmake/library/src/mathutils.cpp @@ -0,0 +1,27 @@ +#include "mathutils/mathutils.h" +#include +#include + +namespace mathutils +{ + double add(double a, double b) + { + double result = a + b; + print_result("addition", a, b, result); + return result; + } + + double multiply(double a, double b) + { + double result = a * b; + print_result("multiplication", a, b, result); + return result; + } + + void print_result(const char *operation, double a, double b, double result) + { + fmt::print(fg(fmt::terminal_color::green), + "MathUtils {}: {} + {} = {}\n", + operation, a, b, result); + } +} diff --git a/examples/conan_cmake/library_consumer/CMakeLists.txt b/examples/conan_cmake/library_consumer/CMakeLists.txt new file mode 100644 index 0000000..455b719 --- /dev/null +++ b/examples/conan_cmake/library_consumer/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.24) + +project(MathUtilsConsumer LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(mathutils REQUIRED) + +add_executable(consumer main.cpp) +target_link_libraries(consumer PRIVATE mathutils::mathutils) diff --git a/examples/conan_cmake/library_consumer/main.cpp b/examples/conan_cmake/library_consumer/main.cpp new file mode 100644 index 0000000..8a38781 --- /dev/null +++ b/examples/conan_cmake/library_consumer/main.cpp @@ -0,0 +1,10 @@ +#include + +int main() +{ + // Test the mathutils library + double result1 = mathutils::add(5.0, 3.0); + double result2 = mathutils::multiply(4.0, 2.5); + + return 0; +} diff --git a/examples/conan_cmake/library_consumer/pyproject.toml b/examples/conan_cmake/library_consumer/pyproject.toml new file mode 100644 index 0000000..d1f4d1e --- /dev/null +++ b/examples/conan_cmake/library_consumer/pyproject.toml @@ -0,0 +1,25 @@ +[project] +description = "A test consumer for the mathutils library" +name = "mathutils-consumer" +version = "1.0.0" + +license = { text = "MIT" } + +authors = [{ name = "Synodic Software", email = "contact@synodic.software" }] + +requires-python = ">=3.13" + +dependencies = ["cppython[conan, cmake, git]>=0.9.0"] + + +[tool.cppython] +install-path = "install" + +dependencies = ["mathutils==1.0.0"] + +[tool.cppython.generators.cmake] + +[tool.cppython.providers.conan] + +[tool.pdm] +distribution = false From 9d3fffde015ec57e1a4e588fb7bd90bbccbc328d Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 08:53:43 -0400 Subject: [PATCH 02/68] Update Examples Test --- .../integration/examples/test_conan_cmake.py | 141 +++++++++++++++--- 1 file changed, 119 insertions(+), 22 deletions(-) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index f62f342..82adaf8 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -4,6 +4,8 @@ The tests ensure that the projects build, configure, and execute correctly. """ +import os +import shutil import subprocess from pathlib import Path from tomllib import loads @@ -21,41 +23,136 @@ class TestConanCMake: """Test project variation of conan and CMake""" @staticmethod - def test_simple(example_runner: CliRunner) -> None: - """Simple project""" - # Create project configuration + def _create_project(skip_upload: bool = True) -> Project: + """Create a project instance with common configuration.""" project_root = Path.cwd() - project_configuration = ProjectConfiguration(project_root=project_root, version=None, verbosity=2, debug=True) - - # Create console interface + config = ProjectConfiguration(project_root=project_root, version=None, verbosity=2, debug=True) interface = ConsoleInterface() - # Load pyproject.toml data pyproject_path = project_root / 'pyproject.toml' pyproject_data = loads(pyproject_path.read_text(encoding='utf-8')) - # Create and use the project directly - project = Project(project_configuration, interface, pyproject_data) + if skip_upload: + TestConanCMake._ensure_conan_config(pyproject_data) + pyproject_data['tool']['cppython']['providers']['conan']['skip_upload'] = True - # Call install directly to get structured results - project.install() + return Project(config, interface, pyproject_data) - # Run the CMake configuration command + @staticmethod + def _run_cmake_configure() -> None: + """Run CMake configuration and assert success.""" result = subprocess.run(['cmake', '--preset=default'], capture_output=True, text=True, check=False) + assert result.returncode == 0, f'CMake configuration failed: {result.stderr}' + + @staticmethod + def _run_cmake_build() -> None: + """Run CMake build and assert success.""" + result = subprocess.run(['cmake', '--build', 'build'], capture_output=True, text=True, check=False) + assert result.returncode == 0, f'CMake build failed: {result.stderr}' + + @staticmethod + def _verify_build_artifacts() -> Path: + """Verify basic build artifacts exist and return build path.""" + build_path = Path('build').absolute() + assert (build_path / 'CMakeCache.txt').exists(), f'CMakeCache.txt not found in {build_path}' + return build_path + + @staticmethod + def _ensure_conan_config(pyproject_data: dict) -> None: + """Helper method to ensure Conan configuration exists in pyproject data""" + if 'tool' not in pyproject_data: + pyproject_data['tool'] = {} + if 'cppython' not in pyproject_data['tool']: + pyproject_data['tool']['cppython'] = {} + if 'providers' not in pyproject_data['tool']['cppython']: + pyproject_data['tool']['cppython']['providers'] = {} + if 'conan' not in pyproject_data['tool']['cppython']['providers']: + pyproject_data['tool']['cppython']['providers']['conan'] = {} + + @staticmethod + def test_simple(example_runner: CliRunner) -> None: + """Simple project""" + # Create project and install dependencies + project = TestConanCMake._create_project(skip_upload=False) + project.install() - assert result.returncode == 0, f'Cmake failed: {result.stderr}' + # Configure and verify build + TestConanCMake._run_cmake_configure() + TestConanCMake._verify_build_artifacts() - path = Path('build').absolute() + # Test publishing with skip_upload enabled + publish_project = TestConanCMake._create_project(skip_upload=True) + publish_project.publish() - # Verify that the build directory contains the expected files - assert (path / 'CMakeCache.txt').exists(), f'{path / "CMakeCache.txt"} not found' + @staticmethod + def test_library(example_runner: CliRunner) -> None: + """Test library creation and packaging workflow""" + # Create project and install dependencies + project = TestConanCMake._create_project(skip_upload=False) + project.install() - # --- Setup for Publish with modified config --- - # Modify the in-memory representation of the pyproject data - pyproject_data['tool']['cppython']['providers']['conan']['skip_upload'] = True + # Configure, build, and verify + TestConanCMake._run_cmake_configure() + TestConanCMake._run_cmake_build() + build_path = TestConanCMake._verify_build_artifacts() - # Create a new project instance with the modified configuration for the 'publish' step - publish_project = Project(project_configuration, interface, pyproject_data) + # Verify library files exist (platform-specific) + lib_files = list(build_path.glob('**/libmathutils.*')) + list(build_path.glob('**/mathutils.lib')) + assert len(lib_files) > 0, f'No library files found in {build_path}' - # Publish the project to the local cache + # Package the library to local cache + publish_project = TestConanCMake._create_project(skip_upload=True) publish_project.publish() + + @staticmethod + def _publish_library_to_cache() -> None: + """Helper method to publish the library to local Conan cache""" + examples_root = Path(__file__).parent.parent.parent.parent / 'examples' + library_source = examples_root / 'conan_cmake' / 'library' + library_temp = Path('temp_library') + + # Copy library to temp location + shutil.copytree(library_source, library_temp) + + # Change to library directory and publish it + original_cwd = Path.cwd() + try: + os.chdir(library_temp) + + # Create and configure library project + lib_project = TestConanCMake._create_project(skip_upload=True) + lib_project.install() + + # Build and publish library + TestConanCMake._run_cmake_configure() + TestConanCMake._run_cmake_build() + lib_project.publish() + + finally: + os.chdir(original_cwd) + + @staticmethod + def test_library_consumer(example_runner: CliRunner) -> None: + """Test that a consumer can use the published library""" + # First, publish the library to the local cache + TestConanCMake._publish_library_to_cache() + + # Create consumer project and install dependencies + consumer_project = TestConanCMake._create_project(skip_upload=False) + consumer_project.install() + + # Configure and build the consumer + TestConanCMake._run_cmake_configure() + TestConanCMake._run_cmake_build() + build_path = TestConanCMake._verify_build_artifacts() + + # Verify the executable was created and works + exe_files = list(build_path.glob('**/consumer*')) + assert len(exe_files) > 0, f'No consumer executable found in {build_path}' + + # Run the consumer to verify it works + exe_path = next((f for f in exe_files if f.suffix == '.exe' or f.is_file()), None) + if exe_path: + result = subprocess.run([str(exe_path)], capture_output=True, text=True, check=False) + assert result.returncode == 0, f'Consumer execution failed: {result.stderr}' + assert 'MathUtils' in result.stdout, f'Expected MathUtils output not found: {result.stdout}' From 6ef3b542cb43f37404a3c2fba605430c8f178dd5 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 09:02:32 -0400 Subject: [PATCH 03/68] Template Name and Version --- cppython/plugins/conan/builder.py | 27 ++++++++++++++++++++------- cppython/plugins/conan/plugin.py | 7 ++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 2be317d..1317f41 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -121,15 +121,15 @@ def __init__(self) -> None: self._filename = 'conanfile.py' @staticmethod - def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency]) -> None: + def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], name: str, version: str) -> None: """Creates a conanfile.py file with the necessary content.""" template_string = """ from conan import ConanFile from conan.tools.cmake import CMake, cmake_layout - class MyProject(ConanFile): - name = "myproject" - version = "1.0" + class AutoPackage(ConanFile): + name = "${name}" + version = "${version}" settings = "os", "compiler", "build_type", "arch" requires = ${dependencies} generators = "CMakeDeps" @@ -137,14 +137,25 @@ class MyProject(ConanFile): def layout(self): cmake_layout(self) + def generate(self): + deps = CMakeDeps(self) + deps.generate() + def build(self): cmake = CMake(self) cmake.configure() - cmake.build()""" + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + """ template = Template(dedent(template_string)) values = { + 'name': name, + 'version': version, 'dependencies': [dependency.requires() for dependency in dependencies], } @@ -153,7 +164,9 @@ def build(self): with open(conan_file, 'w', encoding='utf-8') as file: file.write(result) - def generate_conanfile(self, directory: DirectoryPath, dependencies: list[ConanDependency]) -> None: + def generate_conanfile( + self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str + ) -> None: """Generate a conanfile.py file for the project.""" conan_file = directory / self._filename @@ -167,4 +180,4 @@ def generate_conanfile(self, directory: DirectoryPath, dependencies: list[ConanD conan_file.write_text(modified.code, encoding='utf-8') else: directory.mkdir(parents=True, exist_ok=True) - self._create_conanfile(conan_file, dependencies) + self._create_conanfile(conan_file, dependencies, name, version) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 0e4ebd8..441a52d 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -110,7 +110,12 @@ def _prepare_installation(self) -> tuple[ConanAPI, Path]: """ # Resolve dependencies and generate conanfile.py 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.builder.generate_conanfile( + self.core_data.project_data.project_root, + resolved_dependencies, + self.core_data.pep621_data.name, + self.core_data.pep621_data.version, + ) # Ensure build directory exists self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True) From 92848534c8ba1f8f8b7bd2b5ec19001c709f9550 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 09:14:32 -0400 Subject: [PATCH 04/68] Generator Definition Fix --- cppython/plugins/conan/builder.py | 3 +-- cppython/plugins/conan/plugin.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 1317f41..2632715 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -125,14 +125,13 @@ def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], nam """Creates a conanfile.py file with the necessary content.""" template_string = """ from conan import ConanFile - from conan.tools.cmake import CMake, cmake_layout + from conan.tools.cmake import CMake, CMakeDeps, cmake_layout class AutoPackage(ConanFile): name = "${name}" version = "${version}" settings = "os", "compiler", "build_type", "arch" requires = ${dependencies} - generators = "CMakeDeps" def layout(self): cmake_layout(self) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 441a52d..3012389 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -192,7 +192,7 @@ def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None: conan_api.install.install_consumer( deps_graph=deps_graph, - generators=['CMakeToolchain', 'CMakeDeps'], + generators=[], # Our conanfile.py template defines this source_folder=str(project_root), output_folder=str(self.core_data.cppython_data.build_path), ) From 4ccb5ba960a3c42a66d9e412b8d812fba0280efe Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 10:01:29 -0400 Subject: [PATCH 05/68] Update Example CMake Installation --- examples/conan_cmake/library/CMakeLists.txt | 60 +++++++++++++++---- .../library/cmake/mathutilsConfig.cmake.in | 8 +++ 2 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 examples/conan_cmake/library/cmake/mathutilsConfig.cmake.in diff --git a/examples/conan_cmake/library/CMakeLists.txt b/examples/conan_cmake/library/CMakeLists.txt index 835fac5..eb23c20 100644 --- a/examples/conan_cmake/library/CMakeLists.txt +++ b/examples/conan_cmake/library/CMakeLists.txt @@ -1,21 +1,59 @@ -cmake_minimum_required(VERSION 3.24) +cmake_minimum_required(VERSION 3.30) -project(mathutils VERSION 1.0.0 LANGUAGES CXX) +project(mathutils + VERSION 1.0.0 + DESCRIPTION "A modern math utilities library" + LANGUAGES CXX +) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +# Dependencies find_package(fmt REQUIRED) -# Create the library +# Library target add_library(mathutils src/mathutils.cpp) +add_library(mathutils::mathutils ALIAS mathutils) -# Set up include directories -target_include_directories(mathutils - PUBLIC +target_compile_features(mathutils PUBLIC cxx_std_17) +target_include_directories(mathutils PUBLIC $ - $ + $ +) +target_link_libraries(mathutils PUBLIC fmt::fmt) + +# Installation +install(TARGETS mathutils + EXPORT mathutilsTargets + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h*" +) + +install(EXPORT mathutilsTargets + FILE mathutilsTargets.cmake + NAMESPACE mathutils:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils +) + +# Package configuration +write_basic_package_version_file( + mathutilsConfigVersion.cmake + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file( + cmake/mathutilsConfig.cmake.in + mathutilsConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils ) -# Link dependencies -target_link_libraries(mathutils PUBLIC fmt::fmt) \ No newline at end of file +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/mathutilsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/mathutilsConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils +) \ No newline at end of file diff --git a/examples/conan_cmake/library/cmake/mathutilsConfig.cmake.in b/examples/conan_cmake/library/cmake/mathutilsConfig.cmake.in new file mode 100644 index 0000000..cfe24c1 --- /dev/null +++ b/examples/conan_cmake/library/cmake/mathutilsConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(fmt REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/mathutilsTargets.cmake") + +check_required_components(mathutils) From 17976658912b60100424f3fa33a8e9d0caccee1b Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 10:01:58 -0400 Subject: [PATCH 06/68] Add Missing Publish Sync --- cppython/project.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cppython/project.py b/cppython/project.py index 0ac3c9e..1cab7ef 100644 --- a/cppython/project.py +++ b/cppython/project.py @@ -98,5 +98,14 @@ def publish(self) -> None: Raises: Exception: Provider-specific exception """ + if not self._enabled: + self.logger.info('Skipping publish because the project is not enabled') + return + + self.logger.info('Publishing project') + + # Ensure sync is performed before publishing to generate necessary files + self._data.sync() + # Let provider handle its own exceptions for better error context self._data.plugins.provider.publish() From 37ee6205b42bfe0297c6f92f4586f9ca38ef2582 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 10:02:46 -0400 Subject: [PATCH 07/68] Update main.cpp --- examples/conan_cmake/library_consumer/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/conan_cmake/library_consumer/main.cpp b/examples/conan_cmake/library_consumer/main.cpp index 8a38781..2ba064a 100644 --- a/examples/conan_cmake/library_consumer/main.cpp +++ b/examples/conan_cmake/library_consumer/main.cpp @@ -1,10 +1,14 @@ #include +#include int main() { // Test the mathutils library + std::cout << "Testing MathUtils library..." << std::endl; + double result1 = mathutils::add(5.0, 3.0); double result2 = mathutils::multiply(4.0, 2.5); + std::cout << "MathUtils tests completed successfully!" << std::endl; return 0; } From e9b4de0f9fbf0cdf15c3addbd2db0fb9895b9231 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 20 Jul 2025 10:02:51 -0400 Subject: [PATCH 08/68] Update test_conan_cmake.py --- tests/integration/examples/test_conan_cmake.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index 82adaf8..4d7ef38 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -111,6 +111,10 @@ def _publish_library_to_cache() -> None: library_source = examples_root / 'conan_cmake' / 'library' library_temp = Path('temp_library') + # Clean up any existing temp directory first + if library_temp.exists(): + shutil.rmtree(library_temp) + # Copy library to temp location shutil.copytree(library_source, library_temp) @@ -130,6 +134,9 @@ def _publish_library_to_cache() -> None: finally: os.chdir(original_cwd) + # Clean up temp directory + if library_temp.exists(): + shutil.rmtree(library_temp) @staticmethod def test_library_consumer(example_runner: CliRunner) -> None: From 08d5b3324640bc9bf2278052f6026c740e3f5383 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:08:15 -0400 Subject: [PATCH 09/68] CMake Toolchain Sync --- cppython/plugins/cmake/builder.py | 18 +++++++--- cppython/plugins/cmake/schema.py | 12 ++++++- cppython/plugins/conan/plugin.py | 59 +++++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 659a79b..2ccbd5f 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -2,7 +2,7 @@ from pathlib import Path -from cppython.plugins.cmake.schema import CMakeData, CMakePresets, CMakeSyncData, ConfigurePreset +from cppython.plugins.cmake.schema import CacheVariable, CMakeData, CMakePresets, CMakeSyncData, ConfigurePreset class Builder: @@ -21,10 +21,18 @@ def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: """ generated_configure_preset = ConfigurePreset(name=provider_data.provider_name, hidden=True) - # 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()), - } + # Handle both top_level_includes and toolchain options + cache_variables: dict[str, str | bool | CacheVariable | None] = {} + + if provider_data.top_level_includes: + cache_variables['CMAKE_PROJECT_TOP_LEVEL_INCLUDES'] = str(provider_data.top_level_includes.as_posix()) + + if provider_data.toolchain: + # Use the toolchainFile field for better integration + generated_configure_preset.toolchainFile = str(provider_data.toolchain.as_posix()) + + if cache_variables: + generated_configure_preset.cacheVariables = cache_variables return CMakePresets(configurePresets=[generated_configure_preset]) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index eac1ed6..f357729 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -58,6 +58,10 @@ class ConfigurePreset(CPPythonModel, extra='allow'): str | None, Field(description='The path to the output binary directory.'), ] = None + toolchainFile: Annotated[ + FilePath | None, + Field(description='Path to the toolchain file.'), + ] = None cacheVariables: dict[str, None | bool | str | CacheVariable] | None = None @@ -77,7 +81,13 @@ class CMakePresets(CPPythonModel, extra='allow'): class CMakeSyncData(SyncData): """The CMake sync data""" - top_level_includes: FilePath + top_level_includes: FilePath | None = None + toolchain: FilePath | None = None + + def model_post_init(self, __context) -> None: + """Validate that at least one of top_level_includes or toolchain is provided.""" + if not self.top_level_includes and not self.toolchain: + raise ValueError("Either 'top_level_includes' or 'toolchain' must be provided") class CMakeData(CPPythonModel): diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 3012389..3473c71 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import Any -import requests from conan.api.conan_api import ConanAPI from conan.api.model import ListPattern @@ -27,8 +26,6 @@ class ConanProvider(Provider): """Conan Provider""" - _provider_url = 'https://raw.githubusercontent.com/conan-io/cmake-conan/refs/heads/develop2/conan_provider.cmake' - def __init__( self, group_data: ProviderPluginGroupData, core_data: CorePluginData, configuration_data: dict[str, Any] ) -> None: @@ -39,15 +36,6 @@ def __init__( self.builder = Builder() - @staticmethod - def _download_file(url: str, file: Path) -> None: - """Replaces the given file with the contents of the url""" - file.parent.mkdir(parents=True, exist_ok=True) - - with open(file, 'wb') as out_file: - content = requests.get(url, stream=True).content - out_file.write(content) - @staticmethod def features(directory: Path) -> SupportedFeatures: """Queries conan support @@ -197,6 +185,13 @@ def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None: output_folder=str(self.core_data.cppython_data.build_path), ) + # Rename the generated toolchain file so our wrapper can include it + original_toolchain = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake' + renamed_toolchain = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake.real' + + if original_toolchain.exists() and not renamed_toolchain.exists(): + original_toolchain.rename(renamed_toolchain) + def install(self) -> None: """Installs the provider""" self._install_dependencies(update=False) @@ -231,17 +226,51 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: """ for sync_type in consumer.sync_types(): if sync_type == CMakeSyncData: + # Use the CMakeToolchain file directly as the toolchain + toolchain_path = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake' + + # Create the directory structure if it doesn't exist + toolchain_path.parent.mkdir(parents=True, exist_ok=True) + + # Always create a minimal toolchain file that includes dependencies when they exist + toolchain_content = f'''# Conan CMake integration file +# This file is managed by CPPython and integrates Conan dependencies with CMake + +# Set the build directory for reference +set(CONAN_BUILD_DIR "{self.core_data.cppython_data.build_path.as_posix()}") + +# Include CMakeDeps generated dependency files if they exist +file(GLOB CONAN_DEPS_FILES "${{CONAN_BUILD_DIR}}/*-config.cmake") +foreach(DEPS_FILE ${{CONAN_DEPS_FILES}}) + include("${{DEPS_FILE}}") +endforeach() + +# Include any conan-generated toolchain files +if(EXISTS "${{CONAN_BUILD_DIR}}/conan_toolchain.cmake.real") + include("${{CONAN_BUILD_DIR}}/conan_toolchain.cmake.real") +else() + message(STATUS "Conan dependencies not installed yet - run conan install to install dependencies") +endif() +''' + + toolchain_path.write_text(toolchain_content) + return CMakeSyncData( provider_name=TypeName('conan'), - top_level_includes=self.core_data.cppython_data.install_path / 'conan_provider.cmake', + toolchain=toolchain_path, ) raise NotSupportedError(f'Unsupported sync types: {consumer.sync_types()}') @classmethod async def download_tooling(cls, directory: Path) -> None: - """Downloads the conan provider file""" - cls._download_file(cls._provider_url, directory / 'conan_provider.cmake') + """Download external tooling required by the Conan provider. + + Since we're using CMakeToolchain generator instead of cmake-conan provider, + no external tooling needs to be downloaded. + """ + # No external tooling required when using CMakeToolchain + pass def publish(self) -> None: """Publishes the package using conan create workflow.""" From aa935bbdba261632efb373236f28b1b251085c31 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:08:30 -0400 Subject: [PATCH 10/68] Use CMakeToolchain Generator --- cppython/plugins/conan/builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 2632715..3623126 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -125,7 +125,7 @@ def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], nam """Creates a conanfile.py file with the necessary content.""" template_string = """ from conan import ConanFile - from conan.tools.cmake import CMake, CMakeDeps, cmake_layout + from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout class AutoPackage(ConanFile): name = "${name}" @@ -139,6 +139,8 @@ def layout(self): def generate(self): deps = CMakeDeps(self) deps.generate() + tc = CMakeToolchain(self) + tc.generate() def build(self): cmake = CMake(self) From fa9a13658f314305b01f78d05470cf3ca6163c12 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:10:48 -0400 Subject: [PATCH 11/68] Update resolution.py --- cppython/plugins/conan/resolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppython/plugins/conan/resolution.py b/cppython/plugins/conan/resolution.py index 4f5f868..bd11006 100644 --- a/cppython/plugins/conan/resolution.py +++ b/cppython/plugins/conan/resolution.py @@ -188,7 +188,7 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile: _apply_cmake_config_to_profile(profile, cmake_program, profile_type) return profile except Exception as e: - logger.warning('Default %s profile not available, using auto-detection: %s', profile_type, str(e)) + logger.debug('Default %s profile not available, using auto-detection: %s', profile_type, str(e)) # Create auto-detected profile profile = conan_api.profiles.detect() From 28191be67679da15268d732679a1b34e93c875a2 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:28:56 -0400 Subject: [PATCH 12/68] Remove Fill --- cppython/plugins/conan/plugin.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 3473c71..bea3f89 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -232,29 +232,6 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: # Create the directory structure if it doesn't exist toolchain_path.parent.mkdir(parents=True, exist_ok=True) - # Always create a minimal toolchain file that includes dependencies when they exist - toolchain_content = f'''# Conan CMake integration file -# This file is managed by CPPython and integrates Conan dependencies with CMake - -# Set the build directory for reference -set(CONAN_BUILD_DIR "{self.core_data.cppython_data.build_path.as_posix()}") - -# Include CMakeDeps generated dependency files if they exist -file(GLOB CONAN_DEPS_FILES "${{CONAN_BUILD_DIR}}/*-config.cmake") -foreach(DEPS_FILE ${{CONAN_DEPS_FILES}}) - include("${{DEPS_FILE}}") -endforeach() - -# Include any conan-generated toolchain files -if(EXISTS "${{CONAN_BUILD_DIR}}/conan_toolchain.cmake.real") - include("${{CONAN_BUILD_DIR}}/conan_toolchain.cmake.real") -else() - message(STATUS "Conan dependencies not installed yet - run conan install to install dependencies") -endif() -''' - - toolchain_path.write_text(toolchain_content) - return CMakeSyncData( provider_name=TypeName('conan'), toolchain=toolchain_path, From 3bc2769ac96ff8cb8c385f3d67eae69eb9daa67f Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:29:29 -0400 Subject: [PATCH 13/68] Update plugin.py --- cppython/plugins/conan/plugin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index bea3f89..c47fcf9 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -229,9 +229,6 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: # Use the CMakeToolchain file directly as the toolchain toolchain_path = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake' - # Create the directory structure if it doesn't exist - toolchain_path.parent.mkdir(parents=True, exist_ok=True) - return CMakeSyncData( provider_name=TypeName('conan'), toolchain=toolchain_path, From f758ab4bed9739b2087707cd6d5e6521974a267b Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 05:51:38 -0400 Subject: [PATCH 14/68] Update schema.py --- cppython/plugins/cmake/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index f357729..ab6293a 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -82,7 +82,7 @@ class CMakeSyncData(SyncData): """The CMake sync data""" top_level_includes: FilePath | None = None - toolchain: FilePath | None = None + toolchain: Path | None = None # We don't resolve the toolchain until after we set it def model_post_init(self, __context) -> None: """Validate that at least one of top_level_includes or toolchain is provided.""" From 87f42b1b3c9c0a77aafd12d4e9d972fa298fab9e Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 06:12:01 -0400 Subject: [PATCH 15/68] Update schema.py --- cppython/plugins/cmake/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index ab6293a..96c0df5 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -59,7 +59,7 @@ class ConfigurePreset(CPPythonModel, extra='allow'): Field(description='The path to the output binary directory.'), ] = None toolchainFile: Annotated[ - FilePath | None, + Path | None, Field(description='Path to the toolchain file.'), ] = None cacheVariables: dict[str, None | bool | str | CacheVariable] | None = None From 489cfac59208e37207c2ab46ae351ca23f0d42c6 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 06:27:35 -0400 Subject: [PATCH 16/68] Update builder.py --- cppython/plugins/cmake/builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 2ccbd5f..710447e 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -29,7 +29,7 @@ def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: if provider_data.toolchain: # Use the toolchainFile field for better integration - generated_configure_preset.toolchainFile = str(provider_data.toolchain.as_posix()) + generated_configure_preset.toolchainFile = provider_data.toolchain if cache_variables: generated_configure_preset.cacheVariables = cache_variables From 8d2da03caffbedc08f6293ad3846511a56a30f9f Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 22 Jul 2025 23:59:44 -0400 Subject: [PATCH 17/68] Update Chore --- pdm.lock | 22 +++++++++++----------- pyproject.toml | 33 +++++++-------------------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/pdm.lock b/pdm.lock index aff0232..023d380 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "cmake", "conan", "git", "lint", "pdm", "pytest", "release", "test"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:bbd888727dfc7101af521ae5aadf9644ae7879f21fb1d1b3a5f804257e8cdd4c" +content_hash = "sha256:0c5f1c805fb3e33b257813f660c85a4e91ad3f80e38ad519b83127c1a148dfee" [[metadata.targets]] requires_python = ">=3.13" @@ -588,19 +588,19 @@ files = [ [[package]] name = "pyrefly" -version = "0.24.2" +version = "0.25.0" requires_python = ">=3.8" summary = "A fast Python type checker written in Rust" files = [ - {file = "pyrefly-0.24.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7e6bd1b88ec53b3f1ce2ece844016d7e7f0848a77022857a7fa6674a49abcc13"}, - {file = "pyrefly-0.24.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:83aa9013f2299dfc8ce11adec30a63be71528484c45e603375efe7496cb0538e"}, - {file = "pyrefly-0.24.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bf1689032b78f8f653244cd323ee1e06a0efb6192c4d7a415d1e85aedd37905"}, - {file = "pyrefly-0.24.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8404b804a5a1bc4a54cc8e58bceacdf49d7221531843c068547241d8f476af24"}, - {file = "pyrefly-0.24.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d09f166a46e43655ea812611887ca16a0c54386296f4c9333f3f5fc7236709"}, - {file = "pyrefly-0.24.2-py3-none-win32.whl", hash = "sha256:6c602df48dcfa3240f9076c7d1e9cf9dc2d94c90ee5b4c6745f3734125a2cf3a"}, - {file = "pyrefly-0.24.2-py3-none-win_amd64.whl", hash = "sha256:9ed4690716eb47077082d4e99624e0a1165b9ac93300c8d823f42cae12ec1ef4"}, - {file = "pyrefly-0.24.2-py3-none-win_arm64.whl", hash = "sha256:96ba49c02f374d716b8674409aa653093dad5263cf4e429a1d5ec603064db715"}, - {file = "pyrefly-0.24.2.tar.gz", hash = "sha256:671b9933c2a3f646983de68bc0422736f7ce364c4f645f742559423b0b9b5150"}, + {file = "pyrefly-0.25.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:14e791b2509b9abbcb6d9922b3bc15fecafc7e69d84b6c859cd17656dd9fd146"}, + {file = "pyrefly-0.25.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c3d46dbbcf8f3d1284eda3cee5c9047692b8f44457324c587a742f5bd832b67"}, + {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90e9ffa15e10b9f322d7c266480504dd7ac298f8b9d2bb830c1cf2de2a6a3031"}, + {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8faaa6cbc3e4a77e1a4de01a9aaa8d39ac0e1c23be25f00025664bb7530688"}, + {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91837045f7e8406cd361510312fff23bca07ddb0cad538dbfbd77c509199cf1e"}, + {file = "pyrefly-0.25.0-py3-none-win32.whl", hash = "sha256:5528738fb763f720a4ebba0fe420d15e3a377ca69da0e2d5a804c6581a9ca19e"}, + {file = "pyrefly-0.25.0-py3-none-win_amd64.whl", hash = "sha256:36cb043567dac1d940e527fb5dfd2bb155022cbcd80056a76ba485ed32489c74"}, + {file = "pyrefly-0.25.0-py3-none-win_arm64.whl", hash = "sha256:b13ca14f5f582ad57a5f959487f498dd1d82b75d7bf2cb0db162a6f67404c6af"}, + {file = "pyrefly-0.25.0.tar.gz", hash = "sha256:dbc124f31fe4397649fb2a6191f08c99af8ddd3cd2ba182ba95b0c5731575b87"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 405cd79..29bc2bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,23 +20,11 @@ dependencies = [ ] [project.optional-dependencies] -pytest = [ - "pytest>=8.4.1", - "pytest-mock>=3.14.1", -] -git = [ - "dulwich>=0.23.2", -] -pdm = [ - "pdm>=2.25.4", -] -cmake = [ - "cmake>=4.0.3", -] -conan = [ - "conan>=2.18.1", - "libcst>=1.8.2", -] +pytest = ["pytest>=8.4.1", "pytest-mock>=3.14.1"] +git = ["dulwich>=0.23.2"] +pdm = ["pdm>=2.25.4"] +cmake = ["cmake>=4.0.3"] +conan = ["conan>=2.18.1", "libcst>=1.8.2"] [project.urls] homepage = "https://github.com/Synodic-Software/CPPython" @@ -59,15 +47,8 @@ cppython = "cppython.plugins.pdm.plugin:CPPythonPlugin" cppython = "cppython.test.pytest.fixtures" [dependency-groups] -lint = [ - "ruff>=0.12.4", - "pyrefly>=0.24.2", -] -test = [ - "pytest>=8.4.1", - "pytest-cov>=6.2.1", - "pytest-mock>=3.14.1", -] +lint = ["ruff>=0.12.4", "pyrefly>=0.25.0"] +test = ["pytest>=8.4.1", "pytest-cov>=6.2.1", "pytest-mock>=3.14.1"] [project.scripts] cppython = "cppython.console.entry:app" From cf1ef5ca61a720e772615e0667376dacfe538339 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Wed, 23 Jul 2025 01:06:52 -0400 Subject: [PATCH 18/68] Remove Unused Config --- cppython/plugins/conan/plugin.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index c47fcf9..92b5c75 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -180,17 +180,11 @@ def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None: conan_api.install.install_consumer( deps_graph=deps_graph, - generators=[], # Our conanfile.py template defines this + generators=None, # Our conanfile.py template defines this source_folder=str(project_root), output_folder=str(self.core_data.cppython_data.build_path), ) - - # Rename the generated toolchain file so our wrapper can include it - original_toolchain = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake' - renamed_toolchain = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake.real' - - if original_toolchain.exists() and not renamed_toolchain.exists(): - original_toolchain.rename(renamed_toolchain) + conan_api.install. def install(self) -> None: """Installs the provider""" From c9d585bf3dbf4aaf73da599cf7426e08ee5f2b69 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Wed, 23 Jul 2025 02:05:06 -0400 Subject: [PATCH 19/68] Fix Cmake Path Issue --- cppython/plugins/cmake/builder.py | 2 +- cppython/plugins/cmake/schema.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 710447e..26f46d1 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -29,7 +29,7 @@ def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: if provider_data.toolchain: # Use the toolchainFile field for better integration - generated_configure_preset.toolchainFile = provider_data.toolchain + generated_configure_preset.toolchainFile = provider_data.toolchain.as_posix() if cache_variables: generated_configure_preset.cacheVariables = cache_variables diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index 96c0df5..c58bd0c 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -59,7 +59,7 @@ class ConfigurePreset(CPPythonModel, extra='allow'): Field(description='The path to the output binary directory.'), ] = None toolchainFile: Annotated[ - Path | None, + str | Path | None, Field(description='Path to the toolchain file.'), ] = None cacheVariables: dict[str, None | bool | str | CacheVariable] | None = None From 582116c76f6fc1db81ef92b44c4bfd24c34af3fc Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Wed, 23 Jul 2025 02:05:14 -0400 Subject: [PATCH 20/68] Fix Toolchain Location --- cppython/plugins/conan/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 92b5c75..10ad418 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -182,9 +182,7 @@ def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None: deps_graph=deps_graph, generators=None, # Our conanfile.py template defines this source_folder=str(project_root), - output_folder=str(self.core_data.cppython_data.build_path), ) - conan_api.install. def install(self) -> None: """Installs the provider""" @@ -221,7 +219,7 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: for sync_type in consumer.sync_types(): if sync_type == CMakeSyncData: # Use the CMakeToolchain file directly as the toolchain - toolchain_path = self.core_data.cppython_data.build_path / 'conan_toolchain.cmake' + toolchain_path = self.core_data.cppython_data.build_path / 'generators' / 'conan_toolchain.cmake' return CMakeSyncData( provider_name=TypeName('conan'), From e738ad532161e896697c65ceb0dab15af18a3f7c Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Thu, 24 Jul 2025 00:52:41 -0400 Subject: [PATCH 21/68] Update settings.json --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index caac882..30db100 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,6 @@ "source.organizeImports": "explicit" }, "editor.defaultFormatter": "charliermarsh.ruff" - } + }, + "cmake.ignoreCMakeListsMissing": true } \ No newline at end of file From 830828dbf9a62a6e795bff8ae675a66fa4b9ed56 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 26 Jul 2025 00:04:27 -0400 Subject: [PATCH 22/68] Update Chore --- pdm.lock | 84 +++++++++++++++++++++++++------------------------- pyproject.toml | 4 +-- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/pdm.lock b/pdm.lock index 023d380..a23eb6b 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "cmake", "conan", "git", "lint", "pdm", "pytest", "release", "test"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:0c5f1c805fb3e33b257813f660c85a4e91ad3f80e38ad519b83127c1a148dfee" +content_hash = "sha256:7790fe7a9becc9139aeb447c8ba496f2d6480d44f30f6151fb15d9dec5d90246" [[metadata.targets]] requires_python = ">=3.13" @@ -122,22 +122,22 @@ files = [ [[package]] name = "conan" -version = "2.18.1" +version = "2.19.0" requires_python = ">=3.6" -summary = "" +summary = "Conan C/C++ package manager" dependencies = [ - "colorama", - "distro; platform_system == \"FreeBSD\" or sys_platform == \"linux\"", - "fasteners", - "jinja2", - "patch-ng", - "python-dateutil", - "pyyaml", - "requests", - "urllib3", + "Jinja2<4.0.0,>=3.0", + "PyYAML<7.0,>=6.0", + "colorama<0.5.0,>=0.4.3", + "distro<=1.8.0,>=1.4.0; platform_system == \"Linux\" or platform_system == \"FreeBSD\"", + "fasteners>=0.15", + "patch-ng<1.19,>=1.18.0", + "python-dateutil<3,>=2.8.0", + "requests<3.0.0,>=2.25", + "urllib3<2.1,>=1.26.6", ] files = [ - {file = "conan-2.18.1.tar.gz", hash = "sha256:5d8e9fac7614de9297933f65de8f17db14851a871cebc962f4856b7c294f43c5"}, + {file = "conan-2.19.0.tar.gz", hash = "sha256:6f66530678daa2d28ca09c53598e5c04a9d6cad80212d50f9cedcc9c7d3d7f30"}, ] [[package]] @@ -588,19 +588,19 @@ files = [ [[package]] name = "pyrefly" -version = "0.25.0" +version = "0.25.1" requires_python = ">=3.8" summary = "A fast Python type checker written in Rust" files = [ - {file = "pyrefly-0.25.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:14e791b2509b9abbcb6d9922b3bc15fecafc7e69d84b6c859cd17656dd9fd146"}, - {file = "pyrefly-0.25.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c3d46dbbcf8f3d1284eda3cee5c9047692b8f44457324c587a742f5bd832b67"}, - {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90e9ffa15e10b9f322d7c266480504dd7ac298f8b9d2bb830c1cf2de2a6a3031"}, - {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8faaa6cbc3e4a77e1a4de01a9aaa8d39ac0e1c23be25f00025664bb7530688"}, - {file = "pyrefly-0.25.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91837045f7e8406cd361510312fff23bca07ddb0cad538dbfbd77c509199cf1e"}, - {file = "pyrefly-0.25.0-py3-none-win32.whl", hash = "sha256:5528738fb763f720a4ebba0fe420d15e3a377ca69da0e2d5a804c6581a9ca19e"}, - {file = "pyrefly-0.25.0-py3-none-win_amd64.whl", hash = "sha256:36cb043567dac1d940e527fb5dfd2bb155022cbcd80056a76ba485ed32489c74"}, - {file = "pyrefly-0.25.0-py3-none-win_arm64.whl", hash = "sha256:b13ca14f5f582ad57a5f959487f498dd1d82b75d7bf2cb0db162a6f67404c6af"}, - {file = "pyrefly-0.25.0.tar.gz", hash = "sha256:dbc124f31fe4397649fb2a6191f08c99af8ddd3cd2ba182ba95b0c5731575b87"}, + {file = "pyrefly-0.25.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:65374450bb590b715b3d5ee5fb1d9053b01cc5843df24b7f9727ce244ba412d7"}, + {file = "pyrefly-0.25.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8127b83f850f7c3b78413bd6626d2496c2af763d68716bf0ada9f0752dc550fb"}, + {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c84a7846651067d7d782835ade6eed66e855dbd1c63925cd4490d226d92d4a42"}, + {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41ca0aff5f74103bf33f8d86725d7dec9ade410721f3071d661e2393220a053c"}, + {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9909532b49c591338838078229cb49389c024c29f836f92028fee78ce62db3e2"}, + {file = "pyrefly-0.25.1-py3-none-win32.whl", hash = "sha256:86a37b106a0f32cf0f0d82af5e49c41e6618c82896f109094c8dad436f17d7d7"}, + {file = "pyrefly-0.25.1-py3-none-win_amd64.whl", hash = "sha256:8a5a3432a0fabebc8a651cc0ffde22001e39a1e80cd2337b1d6a839b771c4c71"}, + {file = "pyrefly-0.25.1-py3-none-win_arm64.whl", hash = "sha256:e5093e979b31f10099bc48cc737d36fb69cbb84cd062df4dc122ff55c22712e2"}, + {file = "pyrefly-0.25.1.tar.gz", hash = "sha256:9525fffb280ff7ffdd3fb5d2658aa1385bb3ef3687e3d0abbe2e48baa2d72fa1"}, ] [[package]] @@ -750,28 +750,28 @@ files = [ [[package]] name = "ruff" -version = "0.12.4" +version = "0.12.5" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." files = [ - {file = "ruff-0.12.4-py3-none-linux_armv6l.whl", hash = "sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a"}, - {file = "ruff-0.12.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442"}, - {file = "ruff-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045"}, - {file = "ruff-0.12.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57"}, - {file = "ruff-0.12.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184"}, - {file = "ruff-0.12.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb"}, - {file = "ruff-0.12.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1"}, - {file = "ruff-0.12.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b"}, - {file = "ruff-0.12.4-py3-none-win32.whl", hash = "sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93"}, - {file = "ruff-0.12.4-py3-none-win_amd64.whl", hash = "sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a"}, - {file = "ruff-0.12.4-py3-none-win_arm64.whl", hash = "sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e"}, - {file = "ruff-0.12.4.tar.gz", hash = "sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873"}, + {file = "ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92"}, + {file = "ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a"}, + {file = "ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a"}, + {file = "ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a"}, + {file = "ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5"}, + {file = "ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293"}, + {file = "ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb"}, + {file = "ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb"}, + {file = "ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9"}, + {file = "ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5"}, + {file = "ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805"}, + {file = "ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 29bc2bd..17bd8b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ pytest = ["pytest>=8.4.1", "pytest-mock>=3.14.1"] git = ["dulwich>=0.23.2"] pdm = ["pdm>=2.25.4"] cmake = ["cmake>=4.0.3"] -conan = ["conan>=2.18.1", "libcst>=1.8.2"] +conan = ["conan>=2.19.0", "libcst>=1.8.2"] [project.urls] homepage = "https://github.com/Synodic-Software/CPPython" @@ -47,7 +47,7 @@ cppython = "cppython.plugins.pdm.plugin:CPPythonPlugin" cppython = "cppython.test.pytest.fixtures" [dependency-groups] -lint = ["ruff>=0.12.4", "pyrefly>=0.25.0"] +lint = ["ruff>=0.12.5", "pyrefly>=0.25.1"] test = ["pytest>=8.4.1", "pytest-cov>=6.2.1", "pytest-mock>=3.14.1"] [project.scripts] From 5c781a2ef423431511fb8a9061b9714d54f39b14 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 26 Jul 2025 20:00:22 -0400 Subject: [PATCH 23/68] Remove Duplicate Test --- .../integration/examples/test_conan_cmake.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index 4d7ef38..87f75a0 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -84,26 +84,6 @@ def test_simple(example_runner: CliRunner) -> None: publish_project = TestConanCMake._create_project(skip_upload=True) publish_project.publish() - @staticmethod - def test_library(example_runner: CliRunner) -> None: - """Test library creation and packaging workflow""" - # Create project and install dependencies - project = TestConanCMake._create_project(skip_upload=False) - project.install() - - # Configure, build, and verify - TestConanCMake._run_cmake_configure() - TestConanCMake._run_cmake_build() - build_path = TestConanCMake._verify_build_artifacts() - - # Verify library files exist (platform-specific) - lib_files = list(build_path.glob('**/libmathutils.*')) + list(build_path.glob('**/mathutils.lib')) - assert len(lib_files) > 0, f'No library files found in {build_path}' - - # Package the library to local cache - publish_project = TestConanCMake._create_project(skip_upload=True) - publish_project.publish() - @staticmethod def _publish_library_to_cache() -> None: """Helper method to publish the library to local Conan cache""" From 57c72eb3157aedaf9fdacb3b5bdd5f2c60bd59f4 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 27 Jul 2025 01:10:51 -0400 Subject: [PATCH 24/68] Revert "Remove Duplicate Test" This reverts commit 5c781a2ef423431511fb8a9061b9714d54f39b14. --- .../integration/examples/test_conan_cmake.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index 87f75a0..4d7ef38 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -84,6 +84,26 @@ def test_simple(example_runner: CliRunner) -> None: publish_project = TestConanCMake._create_project(skip_upload=True) publish_project.publish() + @staticmethod + def test_library(example_runner: CliRunner) -> None: + """Test library creation and packaging workflow""" + # Create project and install dependencies + project = TestConanCMake._create_project(skip_upload=False) + project.install() + + # Configure, build, and verify + TestConanCMake._run_cmake_configure() + TestConanCMake._run_cmake_build() + build_path = TestConanCMake._verify_build_artifacts() + + # Verify library files exist (platform-specific) + lib_files = list(build_path.glob('**/libmathutils.*')) + list(build_path.glob('**/mathutils.lib')) + assert len(lib_files) > 0, f'No library files found in {build_path}' + + # Package the library to local cache + publish_project = TestConanCMake._create_project(skip_upload=True) + publish_project.publish() + @staticmethod def _publish_library_to_cache() -> None: """Helper method to publish the library to local Conan cache""" From 464cdb823dba2989bda6911d18a03e55c17f52f2 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 27 Jul 2025 01:16:09 -0400 Subject: [PATCH 25/68] Remove Duplicate Test --- .../integration/examples/test_conan_cmake.py | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index 4d7ef38..8b5634d 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -4,8 +4,6 @@ The tests ensure that the projects build, configure, and execute correctly. """ -import os -import shutil import subprocess from pathlib import Path from tomllib import loads @@ -103,63 +101,3 @@ def test_library(example_runner: CliRunner) -> None: # Package the library to local cache publish_project = TestConanCMake._create_project(skip_upload=True) publish_project.publish() - - @staticmethod - def _publish_library_to_cache() -> None: - """Helper method to publish the library to local Conan cache""" - examples_root = Path(__file__).parent.parent.parent.parent / 'examples' - library_source = examples_root / 'conan_cmake' / 'library' - library_temp = Path('temp_library') - - # Clean up any existing temp directory first - if library_temp.exists(): - shutil.rmtree(library_temp) - - # Copy library to temp location - shutil.copytree(library_source, library_temp) - - # Change to library directory and publish it - original_cwd = Path.cwd() - try: - os.chdir(library_temp) - - # Create and configure library project - lib_project = TestConanCMake._create_project(skip_upload=True) - lib_project.install() - - # Build and publish library - TestConanCMake._run_cmake_configure() - TestConanCMake._run_cmake_build() - lib_project.publish() - - finally: - os.chdir(original_cwd) - # Clean up temp directory - if library_temp.exists(): - shutil.rmtree(library_temp) - - @staticmethod - def test_library_consumer(example_runner: CliRunner) -> None: - """Test that a consumer can use the published library""" - # First, publish the library to the local cache - TestConanCMake._publish_library_to_cache() - - # Create consumer project and install dependencies - consumer_project = TestConanCMake._create_project(skip_upload=False) - consumer_project.install() - - # Configure and build the consumer - TestConanCMake._run_cmake_configure() - TestConanCMake._run_cmake_build() - build_path = TestConanCMake._verify_build_artifacts() - - # Verify the executable was created and works - exe_files = list(build_path.glob('**/consumer*')) - assert len(exe_files) > 0, f'No consumer executable found in {build_path}' - - # Run the consumer to verify it works - exe_path = next((f for f in exe_files if f.suffix == '.exe' or f.is_file()), None) - if exe_path: - result = subprocess.run([str(exe_path)], capture_output=True, text=True, check=False) - assert result.returncode == 0, f'Consumer execution failed: {result.stderr}' - assert 'MathUtils' in result.stdout, f'Expected MathUtils output not found: {result.stdout}' From 8985b63bd44e5d86a88fea10ee06c196209242c5 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 27 Jul 2025 01:25:34 -0400 Subject: [PATCH 26/68] Move Conan Test Example Files --- .../test_package}/CMakeLists.txt | 0 .../library/test_package/conanfile.py | 40 +++++++++++++++++++ .../test_package}/main.cpp | 0 .../library_consumer/pyproject.toml | 25 ------------ 4 files changed, 40 insertions(+), 25 deletions(-) rename examples/conan_cmake/{library_consumer => library/test_package}/CMakeLists.txt (100%) create mode 100644 examples/conan_cmake/library/test_package/conanfile.py rename examples/conan_cmake/{library_consumer => library/test_package}/main.cpp (100%) delete mode 100644 examples/conan_cmake/library_consumer/pyproject.toml diff --git a/examples/conan_cmake/library_consumer/CMakeLists.txt b/examples/conan_cmake/library/test_package/CMakeLists.txt similarity index 100% rename from examples/conan_cmake/library_consumer/CMakeLists.txt rename to examples/conan_cmake/library/test_package/CMakeLists.txt diff --git a/examples/conan_cmake/library/test_package/conanfile.py b/examples/conan_cmake/library/test_package/conanfile.py new file mode 100644 index 0000000..57e15ff --- /dev/null +++ b/examples/conan_cmake/library/test_package/conanfile.py @@ -0,0 +1,40 @@ +"""Test package for mathutils library.""" + +import os + +from conan import ConanFile +from conan.tools.build import can_run +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout + + +class MathUtilsTestConan(ConanFile): + """Test package for mathutils library.""" + + settings = 'os', 'compiler', 'build_type', 'arch' + + def requirements(self): + """Add the tested package as a requirement.""" + self.requires(self.tested_reference_str) + + def layout(self): + """Set the CMake layout.""" + cmake_layout(self) + + def generate(self): + """Generate CMake dependencies and toolchain.""" + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + """Build the test package.""" + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + """Run the test.""" + if can_run(self): + cmd = os.path.join(self.cpp.build.bindir, 'consumer') + self.run(cmd, env='conanrun') diff --git a/examples/conan_cmake/library_consumer/main.cpp b/examples/conan_cmake/library/test_package/main.cpp similarity index 100% rename from examples/conan_cmake/library_consumer/main.cpp rename to examples/conan_cmake/library/test_package/main.cpp diff --git a/examples/conan_cmake/library_consumer/pyproject.toml b/examples/conan_cmake/library_consumer/pyproject.toml deleted file mode 100644 index d1f4d1e..0000000 --- a/examples/conan_cmake/library_consumer/pyproject.toml +++ /dev/null @@ -1,25 +0,0 @@ -[project] -description = "A test consumer for the mathutils library" -name = "mathutils-consumer" -version = "1.0.0" - -license = { text = "MIT" } - -authors = [{ name = "Synodic Software", email = "contact@synodic.software" }] - -requires-python = ">=3.13" - -dependencies = ["cppython[conan, cmake, git]>=0.9.0"] - - -[tool.cppython] -install-path = "install" - -dependencies = ["mathutils==1.0.0"] - -[tool.cppython.generators.cmake] - -[tool.cppython.providers.conan] - -[tool.pdm] -distribution = false From 750d51f1823762e0c6645d205ec3bb10fa9de355 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Thu, 31 Jul 2025 15:51:27 -0400 Subject: [PATCH 27/68] Use Subprocess Calls --- cppython/plugins/conan/plugin.py | 295 ++++++++++------------ tests/fixtures/conan.py | 14 +- tests/unit/plugins/conan/test_install.py | 103 +++++--- tests/unit/plugins/conan/test_publish.py | 296 ++++++++++------------- tests/unit/plugins/conan/test_update.py | 55 +++++ 5 files changed, 394 insertions(+), 369 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 10ad418..6f35d28 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -5,12 +5,11 @@ installation, and synchronization with other tools. """ +import subprocess +from logging import getLogger from pathlib import Path from typing import Any -from conan.api.conan_api import ConanAPI -from conan.api.model import ListPattern - from cppython.core.plugin_schema.generator import SyncConsumer from cppython.core.plugin_schema.provider import Provider, ProviderPluginGroupData, SupportedProviderFeatures from cppython.core.schema import CorePluginData, Information, SupportedFeatures, SyncData @@ -19,7 +18,7 @@ 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 -from cppython.utility.exception import NotSupportedError, ProviderConfigurationError, ProviderInstallationError +from cppython.utility.exception import NotSupportedError, ProviderInstallationError from cppython.utility.utility import TypeName @@ -58,43 +57,32 @@ def information() -> Information: return Information() def _install_dependencies(self, *, update: bool = False) -> None: - """Install/update dependencies using Conan API. + """Install/update dependencies using Conan CLI. Args: update: If True, check remotes for newer versions/revisions and install those. If False, use cached versions when available. """ operation = 'update' if update else 'install' + logger = getLogger('cppython.conan') try: # Setup environment and generate conanfile - conan_api, conanfile_path = self._prepare_installation() + conanfile_path = self._prepare_installation() except Exception as e: raise ProviderInstallationError('conan', f'Failed to prepare {operation} environment: {e}', e) from e try: - # Load dependency graph - deps_graph = self._load_dependency_graph(conan_api, conanfile_path, update) - except Exception as e: - raise ProviderInstallationError('conan', f'Failed to load dependency graph: {e}', e) from e - - try: - # Install dependencies - self._install_binaries(conan_api, deps_graph, update) - except Exception as e: - raise ProviderInstallationError('conan', f'Failed to install binary dependencies: {e}', e) from e - - try: - # Generate consumer files - self._generate_consumer_files(conan_api, deps_graph) + # Install dependencies using conan install command + self._run_conan_install(conanfile_path, update, logger) except Exception as e: - raise ProviderInstallationError('conan', f'Failed to generate consumer files: {e}', e) from e + raise ProviderInstallationError('conan', f'Failed to install dependencies: {e}', e) from e - def _prepare_installation(self) -> tuple[ConanAPI, Path]: + def _prepare_installation(self) -> Path: """Prepare the installation environment and generate conanfile. Returns: - Tuple of (ConanAPI instance, conanfile path) + Path to conanfile.py """ # Resolve dependencies and generate conanfile.py resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies] @@ -108,81 +96,63 @@ def _prepare_installation(self) -> tuple[ConanAPI, Path]: # Ensure build directory exists self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True) - # Setup paths and API - conan_api = ConanAPI() + # Setup paths project_root = self.core_data.project_data.project_root conanfile_path = project_root / 'conanfile.py' if not conanfile_path.exists(): raise FileNotFoundError('Generated conanfile.py not found') - return conan_api, conanfile_path + return conanfile_path - def _load_dependency_graph(self, conan_api: ConanAPI, conanfile_path: Path, update: bool): - """Load and build the dependency graph. + def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None: + """Run conan install command. Args: - conan_api: The Conan API instance conanfile_path: Path to the conanfile.py update: Whether to check for updates - - Returns: - The loaded dependency graph + logger: Logger instance """ - all_remotes = conan_api.remotes.list() - profile_host, profile_build = self.data.host_profile, self.data.build_profile - - return conan_api.graph.load_graph_consumer( - path=str(conanfile_path), - name=None, - version=None, - user=None, - channel=None, - lockfile=None, - remotes=all_remotes, - update=update or None, - check_updates=update, - is_build_require=False, - profile_host=profile_host, - profile_build=profile_build, - ) + # Build conan install command + command = ['conan', 'install', str(conanfile_path)] - def _install_binaries(self, conan_api: ConanAPI, deps_graph, update: bool) -> None: - """Analyze and install binary dependencies. + # Add build missing flag + command.extend(['--build', 'missing']) - Args: - conan_api: The Conan API instance - deps_graph: The dependency graph - update: Whether to check for updates - """ - all_remotes = conan_api.remotes.list() - - # Analyze binaries to determine what needs to be built/downloaded - conan_api.graph.analyze_binaries( - graph=deps_graph, - build_mode=['missing'], - remotes=all_remotes, - update=update or None, - lockfile=None, - ) + # Add profiles if specified + if self.data.host_profile and str(self.data.host_profile) != 'default': + command.extend(['--profile:host', str(self.data.host_profile)]) + else: + command.extend(['--profile:host', 'default']) - # Install all dependencies - conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes) + if self.data.build_profile and str(self.data.build_profile) != 'default': + command.extend(['--profile:build', str(self.data.build_profile)]) + else: + command.extend(['--profile:build', 'default']) - def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None: - """Generate consumer files (CMake toolchain, deps, etc.). + # Add update flag if needed + if update: + command.append('--update') - Args: - conan_api: The Conan API instance - deps_graph: The dependency graph - """ - project_root = self.core_data.project_data.project_root + # Add output folder + build_path = self.core_data.cppython_data.build_path + command.extend(['--output-folder', str(build_path)]) - conan_api.install.install_consumer( - deps_graph=deps_graph, - generators=None, # Our conanfile.py template defines this - source_folder=str(project_root), - ) + # Log the command being executed + logger.info('Executing conan command: %s', ' '.join(command)) + + try: + subprocess.run( + command, + cwd=str(self.core_data.project_data.project_root), + check=True, + capture_output=True, + text=True, + ) + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else str(e) + logger.error('Conan install failed: %s', error_msg, exc_info=True) + raise ProviderInstallationError('conan', error_msg, e) from e def install(self) -> None: """Installs the provider""" @@ -242,101 +212,90 @@ def publish(self) -> None: """Publishes the package using conan create workflow.""" project_root = self.core_data.project_data.project_root conanfile_path = project_root / 'conanfile.py' + logger = getLogger('cppython.conan') if not conanfile_path.exists(): raise FileNotFoundError(f'conanfile.py not found at {conanfile_path}') - conan_api = ConanAPI() - - all_remotes = conan_api.remotes.list() - - # Configure remotes for upload - configured_remotes = self._get_configured_remotes(all_remotes) - - # Export the recipe to cache - ref, _ = conan_api.export.export( - path=str(conanfile_path), - name=None, - version=None, - user=None, - channel=None, - lockfile=None, - remotes=all_remotes, - ) - - # Build dependency graph and install - profile_host, profile_build = self.data.host_profile, self.data.build_profile - deps_graph = conan_api.graph.load_graph_consumer( - path=str(conanfile_path), - name=None, - version=None, - user=None, - channel=None, - lockfile=None, - remotes=all_remotes, # Use all remotes for dependency resolution - update=None, - check_updates=False, - is_build_require=False, - profile_host=profile_host, - profile_build=profile_build, - ) - - # Analyze and build binaries - conan_api.graph.analyze_binaries( - graph=deps_graph, - build_mode=['*'], - remotes=all_remotes, # Use all remotes for dependency resolution - update=None, - lockfile=None, - ) - - conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes) - - if not self.data.skip_upload: - self._upload_package(conan_api, ref, configured_remotes) + try: + # Build conan create command + command = ['conan', 'create', str(conanfile_path)] + + # Add build mode (build everything for publishing) + command.extend(['--build', 'missing']) + + # Add profiles + if self.data.host_profile and str(self.data.host_profile) != 'default': + command.extend(['--profile:host', str(self.data.host_profile)]) + else: + command.extend(['--profile:host', 'default']) + + if self.data.build_profile and str(self.data.build_profile) != 'default': + command.extend(['--profile:build', str(self.data.build_profile)]) + else: + command.extend(['--profile:build', 'default']) + + # Log the command being executed + logger.info('Executing conan create command: %s', ' '.join(command)) + + # Run conan create + subprocess.run( + command, + cwd=str(project_root), + check=True, + capture_output=True, + text=True, + ) - def _get_configured_remotes(self, all_remotes): - """Get and validate configured remotes for upload. + # Upload if not skipped + if not self.data.skip_upload: + self._upload_package(logger) - Note: This only affects upload behavior. For dependency resolution, - we always use all available system remotes regardless of this config. - """ - # If skip_upload is True, don't upload anywhere - if self.data.skip_upload: - return [] + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else str(e) + logger.error('Conan create failed: %s', error_msg, exc_info=True) + raise ProviderInstallationError('conan', error_msg, e) from e - # If no remotes specified, upload to all available remotes + def _upload_package(self, logger) -> None: + """Upload the package to configured remotes using CLI commands.""" + # If no remotes configured, upload to all remotes if not self.data.remotes: - return all_remotes + # Upload to all available remotes + command = ['conan', 'upload', '*', '--all', '--confirm'] + else: + # Upload only to specified remotes + for remote in self.data.remotes: + command = ['conan', 'upload', '*', '--remote', remote, '--all', '--confirm'] + + # Log the command being executed + logger.info('Executing conan upload command: %s', ' '.join(command)) + + try: + subprocess.run( + command, + cwd=str(self.core_data.project_data.project_root), + check=True, + capture_output=True, + text=True, + ) + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else str(e) + logger.error('Conan upload failed for remote %s: %s', remote, error_msg, exc_info=True) + raise ProviderInstallationError('conan', f'Upload to {remote} failed: {error_msg}', e) from e + return + + # Log the command for uploading to all remotes + logger.info('Executing conan upload command: %s', ' '.join(command)) - # Otherwise, upload only to specified remotes - configured_remotes = [remote for remote in all_remotes if remote.name in self.data.remotes] - - if not configured_remotes: - available_remotes = [remote.name for remote in all_remotes] - raise ProviderConfigurationError( - 'conan', - f'No configured remotes found. Available: {available_remotes}, Configured: {self.data.remotes}', - 'remotes', + try: + subprocess.run( + command, + cwd=str(self.core_data.project_data.project_root), + check=True, + capture_output=True, + text=True, ) - - return configured_remotes - - def _upload_package(self, conan_api, ref, configured_remotes): - """Upload the package to configured remotes.""" - ref_pattern = ListPattern(f'{ref.name}/*', package_id='*', only_recipe=False) - package_list = conan_api.list.select(ref_pattern) - - if not package_list.recipes: - raise ProviderInstallationError('conan', 'No packages found to upload') - - remote = configured_remotes[0] - conan_api.upload.upload_full( - package_list=package_list, - remote=remote, - enabled_remotes=configured_remotes, - check_integrity=False, - force=False, - metadata=None, - dry_run=False, - ) + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else str(e) + logger.error('Conan upload failed: %s', error_msg, exc_info=True) + raise ProviderInstallationError('conan', error_msg, e) from e diff --git a/tests/fixtures/conan.py b/tests/fixtures/conan.py index f13eb0d..1138157 100644 --- a/tests/fixtures/conan.py +++ b/tests/fixtures/conan.py @@ -166,14 +166,12 @@ def fixture_conan_mock_dependencies() -> list[Requirement]: @pytest.fixture(name='conan_setup_mocks') def fixture_conan_setup_mocks( plugin: ConanProvider, - conan_mock_api: Mock, mocker: MockerFixture, ) -> dict[str, Mock]: """Sets up all mocks for testing install/update operations Args: plugin: The plugin instance - conan_mock_api: Mock ConanAPI instance mocker: Pytest mocker fixture Returns: @@ -185,8 +183,9 @@ def fixture_conan_setup_mocks( # Set the builder attribute on the plugin plugin.builder = mock_builder # type: ignore[attr-defined] - # Mock ConanAPI constructor - mock_conan_api_constructor = mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api) + # Mock subprocess.run to simulate successful command execution + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.return_value = mocker.Mock(returncode=0) # Mock resolve_conan_dependency def mock_resolve(requirement: Requirement) -> ConanDependency: @@ -196,8 +195,13 @@ def mock_resolve(requirement: Requirement) -> ConanDependency: 'cppython.plugins.conan.plugin.resolve_conan_dependency', side_effect=mock_resolve ) + # Mock getLogger to avoid logging setup issues + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) + return { 'builder': mock_builder, - 'conan_api_constructor': mock_conan_api_constructor, + 'subprocess_run': mock_subprocess_run, 'resolve_conan_dependency': mock_resolve_conan_dependency, + 'logger': mock_logger, } diff --git a/tests/unit/plugins/conan/test_install.py b/tests/unit/plugins/conan/test_install.py index d90c982..4e91ad4 100644 --- a/tests/unit/plugins/conan/test_install.py +++ b/tests/unit/plugins/conan/test_install.py @@ -1,5 +1,6 @@ """Unit tests for the conan plugin install functionality""" +import subprocess from pathlib import Path from typing import Any from unittest.mock import Mock @@ -13,10 +14,6 @@ from cppython.test.pytest.mixins import ProviderPluginTestMixin from cppython.utility.exception import ProviderInstallationError -# Constants for test assertions -EXPECTED_PROFILE_CALLS = 2 -EXPECTED_GET_PROFILE_CALLS = 2 - # Use shared fixtures pytest_plugins = ['tests.fixtures.conan'] @@ -82,24 +79,30 @@ def test_with_dependencies( # Verify build path was created assert plugin.core_data.cppython_data.build_path.exists() - # Verify ConanAPI constructor was called - conan_setup_mocks['conan_api_constructor'].assert_called_once() + # Verify subprocess.run was called with conan install command + conan_setup_mocks['subprocess_run'].assert_called_once() + call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] + assert call_args[0] == 'conan' + assert call_args[1] == 'install' + assert '--build' in call_args + assert 'missing' in call_args + assert '--profile:host' in call_args + assert '--profile:build' in call_args + assert '--output-folder' in call_args def test_conan_command_failure( self, plugin: ConanProvider, conan_temp_conanfile: Path, conan_mock_dependencies: list[Requirement], - conan_mock_api: Mock, mocker: MockerFixture, ) -> None: - """Test install method when conan API operations fail + """Test install method when conan CLI command fails Args: plugin: The plugin instance conan_temp_conanfile: Path to temporary conanfile.py conan_mock_dependencies: List of mock dependencies - conan_mock_api: Mock ConanAPI instance mocker: Pytest mocker fixture """ # Mock builder @@ -107,66 +110,98 @@ def test_conan_command_failure( mock_builder.generate_conanfile = mocker.Mock() plugin.builder = mock_builder # type: ignore[attr-defined] - # Configure the API mock to fail on graph loading - conan_mock_api.graph.load_graph_consumer.side_effect = Exception('Conan API error: package not found') - - # Mock ConanAPI constructor to return our configured mock - mock_conan_api_constructor = mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api) - # Mock resolve_conan_dependency def mock_resolve(requirement: Requirement) -> ConanDependency: return ConanDependency(name=requirement.name) mocker.patch('cppython.plugins.conan.plugin.resolve_conan_dependency', side_effect=mock_resolve) + # Mock subprocess.run to simulate command failure + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + 1, ['conan', 'install'], stderr='Conan CLI error: package not found' + ) + # Add a dependency plugin.core_data.cppython_data.dependencies = [conan_mock_dependencies[0]] # Execute and verify exception is raised with pytest.raises( ProviderInstallationError, - match='Failed to load dependency graph: Conan API error: package not found', + match='Failed to install dependencies:.*Conan CLI error: package not found', ): plugin.install() # Verify builder was still called mock_builder.generate_conanfile.assert_called_once() - # Verify Conan API was attempted - mock_conan_api_constructor.assert_called_once() + # Verify subprocess.run was attempted + mock_subprocess_run.assert_called_once() - def test_with_default_profiles( + def test_with_update_flag( self, plugin: ConanProvider, conan_temp_conanfile: Path, conan_mock_dependencies: list[Requirement], conan_setup_mocks: dict[str, Mock], - conan_mock_api: Mock, ) -> None: - """Test install method uses pre-resolved profiles from plugin construction + """Test install method passes update flag to CLI when update=True Args: plugin: The plugin instance conan_temp_conanfile: Path to temporary conanfile.py conan_mock_dependencies: List of mock dependencies conan_setup_mocks: Dictionary containing all mocks - conan_mock_api: Mock ConanAPI instance """ # Setup dependencies plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - # Execute - should use the profiles resolved during plugin construction - plugin.install() + # Execute update instead of install + plugin.update() + + # Verify subprocess.run was called with --update flag + conan_setup_mocks['subprocess_run'].assert_called_once() + call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] + assert '--update' in call_args - # Verify that the API was used for installation - conan_setup_mocks['conan_api_constructor'].assert_called_once() + def test_with_custom_profiles( + self, + plugin: ConanProvider, + conan_temp_conanfile: Path, + conan_mock_dependencies: list[Requirement], + conan_setup_mocks: dict[str, Mock], + mocker: MockerFixture, + ) -> None: + """Test install method uses custom profiles when specified - # Verify the rest of the process continued with resolved profiles - conan_mock_api.graph.load_graph_consumer.assert_called_once() - conan_mock_api.install.install_binaries.assert_called_once() - conan_mock_api.install.install_consumer.assert_called_once() + Args: + plugin: The plugin instance + conan_temp_conanfile: Path to temporary conanfile.py + conan_mock_dependencies: List of mock dependencies + conan_setup_mocks: Dictionary containing all mocks + mocker: Pytest mocker fixture + """ + # Mock custom profiles + custom_host_profile = mocker.Mock() + custom_build_profile = mocker.Mock() + + # Override profiles in the plugin data + plugin.data.host_profile = custom_host_profile + plugin.data.build_profile = custom_build_profile + + # Setup dependencies + plugin.core_data.cppython_data.dependencies = conan_mock_dependencies + + # Execute + plugin.install() - # Verify that the resolved profiles were used in the graph loading - call_args = conan_mock_api.graph.load_graph_consumer.call_args - assert call_args.kwargs['profile_host'] == plugin.data.host_profile - assert call_args.kwargs['profile_build'] == plugin.data.build_profile + # Verify subprocess.run was called with custom profiles + conan_setup_mocks['subprocess_run'].assert_called_once() + call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] + + # Find profile arguments + profile_host_idx = call_args.index('--profile:host') + profile_build_idx = call_args.index('--profile:build') + + assert call_args[profile_host_idx + 1] == str(custom_host_profile) + assert call_args[profile_build_idx + 1] == str(custom_build_profile) diff --git a/tests/unit/plugins/conan/test_publish.py b/tests/unit/plugins/conan/test_publish.py index 163eeba..9d986b5 100644 --- a/tests/unit/plugins/conan/test_publish.py +++ b/tests/unit/plugins/conan/test_publish.py @@ -1,20 +1,21 @@ """Unit tests for the conan plugin publish functionality""" +import subprocess from typing import Any -from unittest.mock import MagicMock, Mock import pytest from pytest_mock import MockerFixture from cppython.plugins.conan.plugin import ConanProvider from cppython.test.pytest.mixins import ProviderPluginTestMixin -from cppython.utility.exception import ProviderConfigurationError, ProviderInstallationError +from cppython.utility.exception import ProviderInstallationError # Use shared fixtures pytest_plugins = ['tests.fixtures.conan'] # Constants for test assertions -EXPECTED_PROFILE_CALLS = 2 +EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD = 2 +EXPECTED_SUBPROCESS_CALLS_SINGLE = 1 class TestConanPublish(ProviderPluginTestMixin[ConanProvider]): @@ -43,246 +44,217 @@ def fixture_plugin_type() -> type[ConanProvider]: return ConanProvider def test_skip_upload( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish with skip_upload=True only exports and builds locally + """Test that publish with skip_upload=True only runs conan create without upload Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ # Set plugin to skip upload mode plugin.data.skip_upload = True - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) + # Mock subprocess.run + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.return_value = mocker.Mock(returncode=0) - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) # Execute publish plugin.publish() - # Verify export was called - conan_mock_api_publish.export.export.assert_called_once() - - # Verify graph loading and analysis - conan_mock_api_publish.graph.load_graph_consumer.assert_called_once() - conan_mock_api_publish.graph.analyze_binaries.assert_called_once_with( - graph=mock_graph, - build_mode=['*'], - remotes=conan_mock_api_publish.remotes.list(), - update=None, - lockfile=None, - ) - - # Verify install was called - conan_mock_api_publish.install.install_binaries.assert_called_once_with( - deps_graph=mock_graph, remotes=conan_mock_api_publish.remotes.list() - ) + # Verify subprocess.run was called once for conan create + mock_subprocess_run.assert_called_once() + call_args = mock_subprocess_run.call_args[0][0] + assert call_args[0] == 'conan' + assert call_args[1] == 'create' + assert '--build' in call_args + assert 'missing' in call_args - # Verify upload was NOT called for local mode - conan_mock_api_publish.upload.upload_full.assert_not_called() + # Verify no upload commands were executed (since skip_upload=True) + assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_SINGLE def test_with_upload( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish with remotes=['conancenter'] exports, builds, and uploads + """Test that publish with remotes=['conancenter'] runs conan create and upload Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ # Set plugin to upload mode plugin.data.remotes = ['conancenter'] + plugin.data.skip_upload = False - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) + # Mock subprocess.run + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.return_value = mocker.Mock(returncode=0) - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) # Execute publish plugin.publish() - # Verify all steps were called - conan_mock_api_publish.export.export.assert_called_once() - conan_mock_api_publish.graph.load_graph_consumer.assert_called_once() - conan_mock_api_publish.graph.analyze_binaries.assert_called_once() - conan_mock_api_publish.install.install_binaries.assert_called_once() + # Verify subprocess.run was called twice (create + upload) + assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD - # Verify upload was called - conan_mock_api_publish.list.select.assert_called_once() - conan_mock_api_publish.upload.upload_full.assert_called_once() + # Check first call - conan create + create_call_args = mock_subprocess_run.call_args_list[0][0][0] + assert create_call_args[0] == 'conan' + assert create_call_args[1] == 'create' - def test_no_remotes_configured( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish raises error when no remotes are configured for upload - - Args: - plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to upload mode - plugin.data.remotes = ['conancenter'] + # Check second call - conan upload + upload_call_args = mock_subprocess_run.call_args_list[1][0][0] + assert upload_call_args[0] == 'conan' + assert upload_call_args[1] == 'upload' + assert '--remote' in upload_call_args + assert 'conancenter' in upload_call_args - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) - - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph - - # Mock no remotes configured - conan_mock_api_publish.remotes.list.return_value = [] - - # Execute publish and expect ProviderConfigurationError - with pytest.raises(ProviderConfigurationError, match='No configured remotes found'): - plugin.publish() - - def test_no_packages_found( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + def test_upload_to_all_remotes( + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish raises error when no packages are found to upload + """Test that publish with empty remotes list uploads to all available remotes Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ - # Set plugin to upload mode - plugin.data.remotes = ['conancenter'] + # Set plugin to upload to all remotes + plugin.data.remotes = [] + plugin.data.skip_upload = False - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) + # Mock subprocess.run + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.return_value = mocker.Mock(returncode=0) - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - # Mock empty package list - mock_select_result = mocker.Mock() - mock_select_result.recipes = [] - conan_mock_api_publish.list.select.return_value = mock_select_result + # Execute publish + plugin.publish() - # Execute publish and expect ProviderInstallationError - with pytest.raises(ProviderInstallationError, match='No packages found to upload'): - plugin.publish() + # Verify subprocess.run was called twice (create + upload) + assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD - def test_with_default_profiles( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + # Check second call - conan upload to all remotes + upload_call_args = mock_subprocess_run.call_args_list[1][0][0] + assert upload_call_args[0] == 'conan' + assert upload_call_args[1] == 'upload' + assert '--all' in upload_call_args + assert '--confirm' in upload_call_args + + def test_conan_create_failure( + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish uses pre-resolved profiles from plugin construction + """Test that publish raises error when conan create fails Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ - # Set plugin to skip upload mode + # Set plugin to skip upload mode for simpler test plugin.data.skip_upload = True - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) - - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph + # Mock subprocess.run to fail + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + 1, ['conan', 'create'], stderr='Conan create failed: missing dependency' + ) - # Execute publish - plugin.publish() + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - # Verify that the resolved profiles were used in the graph loading - conan_mock_api_publish.graph.load_graph_consumer.assert_called_once() - call_args = conan_mock_api_publish.graph.load_graph_consumer.call_args - assert call_args.kwargs['profile_host'] == plugin.data.host_profile - assert call_args.kwargs['profile_build'] == plugin.data.build_profile + # Execute publish and expect ProviderInstallationError + with pytest.raises(ProviderInstallationError, match='Conan create failed: missing dependency'): + plugin.publish() - def test_upload_parameters( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + def test_conan_upload_failure( + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish upload is called with correct parameters + """Test that publish raises error when conan upload fails Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ # Set plugin to upload mode plugin.data.remotes = ['conancenter'] + plugin.data.skip_upload = False + + # Mock subprocess.run + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + + # First call (create) succeeds, second call (upload) fails + def subprocess_side_effect(*args, **kwargs): + if 'create' in args[0]: + return mocker.Mock(returncode=0) + elif 'upload' in args[0]: + raise subprocess.CalledProcessError( + 1, ['conan', 'upload'], stderr='Upload failed: authentication error' + ) + + mock_subprocess_run.side_effect = subprocess_side_effect + + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) - - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph - - # Mock remotes and package list - mock_remote = MagicMock() - mock_remote.name = 'conancenter' - remotes = [mock_remote] - conan_mock_api_publish.remotes.list.return_value = remotes - - mock_package_list = MagicMock() - mock_package_list.recipes = ['test_package/1.0@user/channel'] - conan_mock_api_publish.list.select.return_value = mock_package_list - - # Execute publish - plugin.publish() - - # Verify upload_full was called with correct parameters - conan_mock_api_publish.upload.upload_full.assert_called_once_with( - package_list=mock_package_list, - remote=mock_remote, - enabled_remotes=remotes, - check_integrity=False, - force=False, - metadata=None, - dry_run=False, - ) + # Execute publish and expect ProviderInstallationError + with pytest.raises(ProviderInstallationError, match='Upload to conancenter failed'): + plugin.publish() - def test_list_pattern_creation( - self, plugin: ConanProvider, conan_mock_api_publish: Mock, conan_temp_conanfile: None, mocker: MockerFixture + def test_with_custom_profiles( + self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture ) -> None: - """Test that publish creates correct ListPattern for package selection + """Test that publish uses custom profiles when specified Args: plugin: The plugin instance - conan_mock_api_publish: Mock ConanAPI for publish operations conan_temp_conanfile: Fixture to create conanfile.py mocker: Pytest mocker fixture """ - # Set plugin to upload mode - plugin.data.remotes = ['conancenter'] + # Set plugin to skip upload mode for simpler test + plugin.data.skip_upload = True - # Mock the necessary imports and API creation - mocker.patch('cppython.plugins.conan.plugin.ConanAPI', return_value=conan_mock_api_publish) - mock_list_pattern = mocker.patch('cppython.plugins.conan.plugin.ListPattern') + # Mock custom profiles + custom_host_profile = mocker.Mock() + custom_build_profile = mocker.Mock() + + # Override profiles in the plugin data + plugin.data.host_profile = custom_host_profile + plugin.data.build_profile = custom_build_profile - # Mock the dependencies graph - mock_graph = mocker.Mock() - conan_mock_api_publish.graph.load_graph_consumer.return_value = mock_graph + # Mock subprocess.run + mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') + mock_subprocess_run.return_value = mocker.Mock(returncode=0) + + # Mock getLogger + mock_logger = mocker.Mock() + mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) # Execute publish plugin.publish() - # Get the ref from the export call to verify ListPattern creation - # The export call returns (ref, conanfile) - we need the ref.name - export_return = conan_mock_api_publish.export.export.return_value - ref = export_return[0] # First element of the tuple - - # Verify ListPattern was created with correct reference pattern - mock_list_pattern.assert_called_once_with(f'{ref.name}/*', package_id='*', only_recipe=False) + # Verify subprocess.run was called with custom profiles + mock_subprocess_run.assert_called_once() + call_args = mock_subprocess_run.call_args[0][0] + + # Find profile arguments + profile_host_idx = call_args.index('--profile:host') + profile_build_idx = call_args.index('--profile:build') + + assert call_args[profile_host_idx + 1] == str(custom_host_profile) + assert call_args[profile_build_idx + 1] == str(custom_build_profile) diff --git a/tests/unit/plugins/conan/test_update.py b/tests/unit/plugins/conan/test_update.py index b39e780..d91d798 100644 --- a/tests/unit/plugins/conan/test_update.py +++ b/tests/unit/plugins/conan/test_update.py @@ -5,9 +5,12 @@ install() and update() now share the same underlying implementation. """ +from pathlib import Path from typing import Any +from unittest.mock import Mock import pytest +from packaging.requirements import Requirement from cppython.plugins.conan.plugin import ConanProvider from cppython.test.pytest.mixins import ProviderPluginTestMixin @@ -37,3 +40,55 @@ def fixture_plugin_type() -> type[ConanProvider]: The type of the Provider """ return ConanProvider + + def test_update_includes_update_flag( + self, + plugin: ConanProvider, + conan_temp_conanfile: Path, + conan_mock_dependencies: list[Requirement], + conan_setup_mocks: dict[str, Mock], + ) -> None: + """Test that update() method includes --update flag in conan command + + Args: + plugin: The plugin instance + conan_temp_conanfile: Path to temporary conanfile.py + conan_mock_dependencies: List of mock dependencies + conan_setup_mocks: Dictionary containing all mocks + """ + # Setup dependencies + plugin.core_data.cppython_data.dependencies = conan_mock_dependencies + + # Execute update + plugin.update() + + # Verify subprocess.run was called with --update flag + conan_setup_mocks['subprocess_run'].assert_called_once() + call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] + assert '--update' in call_args + + def test_install_does_not_include_update_flag( + self, + plugin: ConanProvider, + conan_temp_conanfile: Path, + conan_mock_dependencies: list[Requirement], + conan_setup_mocks: dict[str, Mock], + ) -> None: + """Test that install() method does not include --update flag in conan command + + Args: + plugin: The plugin instance + conan_temp_conanfile: Path to temporary conanfile.py + conan_mock_dependencies: List of mock dependencies + conan_setup_mocks: Dictionary containing all mocks + """ + # Setup dependencies + plugin.core_data.cppython_data.dependencies = conan_mock_dependencies + + # Execute install + plugin.install() + + # Verify subprocess.run was called without --update flag + conan_setup_mocks['subprocess_run'].assert_called_once() + call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] + assert '--update' not in call_args From 75ad3120c128c034a3a1faadf2a34b2c14b64bed Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 12:36:57 -0400 Subject: [PATCH 28/68] Remove Outdated Tests --- tests/unit/plugins/conan/test_install.py | 169 ------------ tests/unit/plugins/conan/test_publish.py | 223 --------------- tests/unit/plugins/conan/test_resolution.py | 291 -------------------- tests/unit/plugins/conan/test_update.py | 55 ---- 4 files changed, 738 deletions(-) diff --git a/tests/unit/plugins/conan/test_install.py b/tests/unit/plugins/conan/test_install.py index 4e91ad4..74fd89f 100644 --- a/tests/unit/plugins/conan/test_install.py +++ b/tests/unit/plugins/conan/test_install.py @@ -1,18 +1,11 @@ """Unit tests for the conan plugin install functionality""" -import subprocess -from pathlib import Path from typing import Any -from unittest.mock import Mock import pytest -from packaging.requirements import Requirement -from pytest_mock import MockerFixture from cppython.plugins.conan.plugin import ConanProvider -from cppython.plugins.conan.schema import ConanDependency from cppython.test.pytest.mixins import ProviderPluginTestMixin -from cppython.utility.exception import ProviderInstallationError # Use shared fixtures pytest_plugins = ['tests.fixtures.conan'] @@ -43,165 +36,3 @@ def fixture_plugin_type() -> type[ConanProvider]: The type of the Provider """ return ConanProvider - - def test_with_dependencies( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - conan_setup_mocks: dict[str, Mock], - ) -> None: - """Test install method with dependencies and existing conanfile - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - conan_setup_mocks: Dictionary containing all mocks - """ - # Setup dependencies - plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - - # Execute - plugin.install() - - # Verify builder was called - conan_setup_mocks['builder'].generate_conanfile.assert_called_once() - assert ( - conan_setup_mocks['builder'].generate_conanfile.call_args[0][0] - == plugin.core_data.project_data.project_root - ) - assert len(conan_setup_mocks['builder'].generate_conanfile.call_args[0][1]) == EXPECTED_DEPENDENCY_COUNT - - # Verify dependency resolution was called - assert conan_setup_mocks['resolve_conan_dependency'].call_count == EXPECTED_DEPENDENCY_COUNT - - # Verify build path was created - assert plugin.core_data.cppython_data.build_path.exists() - - # Verify subprocess.run was called with conan install command - conan_setup_mocks['subprocess_run'].assert_called_once() - call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] - assert call_args[0] == 'conan' - assert call_args[1] == 'install' - assert '--build' in call_args - assert 'missing' in call_args - assert '--profile:host' in call_args - assert '--profile:build' in call_args - assert '--output-folder' in call_args - - def test_conan_command_failure( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - mocker: MockerFixture, - ) -> None: - """Test install method when conan CLI command fails - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - mocker: Pytest mocker fixture - """ - # Mock builder - mock_builder = mocker.Mock() - mock_builder.generate_conanfile = mocker.Mock() - plugin.builder = mock_builder # type: ignore[attr-defined] - - # Mock resolve_conan_dependency - def mock_resolve(requirement: Requirement) -> ConanDependency: - return ConanDependency(name=requirement.name) - - mocker.patch('cppython.plugins.conan.plugin.resolve_conan_dependency', side_effect=mock_resolve) - - # Mock subprocess.run to simulate command failure - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - 1, ['conan', 'install'], stderr='Conan CLI error: package not found' - ) - - # Add a dependency - plugin.core_data.cppython_data.dependencies = [conan_mock_dependencies[0]] - - # Execute and verify exception is raised - with pytest.raises( - ProviderInstallationError, - match='Failed to install dependencies:.*Conan CLI error: package not found', - ): - plugin.install() - - # Verify builder was still called - mock_builder.generate_conanfile.assert_called_once() - - # Verify subprocess.run was attempted - mock_subprocess_run.assert_called_once() - - def test_with_update_flag( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - conan_setup_mocks: dict[str, Mock], - ) -> None: - """Test install method passes update flag to CLI when update=True - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - conan_setup_mocks: Dictionary containing all mocks - """ - # Setup dependencies - plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - - # Execute update instead of install - plugin.update() - - # Verify subprocess.run was called with --update flag - conan_setup_mocks['subprocess_run'].assert_called_once() - call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] - assert '--update' in call_args - - def test_with_custom_profiles( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - conan_setup_mocks: dict[str, Mock], - mocker: MockerFixture, - ) -> None: - """Test install method uses custom profiles when specified - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - conan_setup_mocks: Dictionary containing all mocks - mocker: Pytest mocker fixture - """ - # Mock custom profiles - custom_host_profile = mocker.Mock() - custom_build_profile = mocker.Mock() - - # Override profiles in the plugin data - plugin.data.host_profile = custom_host_profile - plugin.data.build_profile = custom_build_profile - - # Setup dependencies - plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - - # Execute - plugin.install() - - # Verify subprocess.run was called with custom profiles - conan_setup_mocks['subprocess_run'].assert_called_once() - call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] - - # Find profile arguments - profile_host_idx = call_args.index('--profile:host') - profile_build_idx = call_args.index('--profile:build') - - assert call_args[profile_host_idx + 1] == str(custom_host_profile) - assert call_args[profile_build_idx + 1] == str(custom_build_profile) diff --git a/tests/unit/plugins/conan/test_publish.py b/tests/unit/plugins/conan/test_publish.py index 9d986b5..c1bd760 100644 --- a/tests/unit/plugins/conan/test_publish.py +++ b/tests/unit/plugins/conan/test_publish.py @@ -1,22 +1,15 @@ """Unit tests for the conan plugin publish functionality""" -import subprocess from typing import Any import pytest -from pytest_mock import MockerFixture from cppython.plugins.conan.plugin import ConanProvider from cppython.test.pytest.mixins import ProviderPluginTestMixin -from cppython.utility.exception import ProviderInstallationError # Use shared fixtures pytest_plugins = ['tests.fixtures.conan'] -# Constants for test assertions -EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD = 2 -EXPECTED_SUBPROCESS_CALLS_SINGLE = 1 - class TestConanPublish(ProviderPluginTestMixin[ConanProvider]): """Tests for the Conan provider publish functionality""" @@ -42,219 +35,3 @@ def fixture_plugin_type() -> type[ConanProvider]: The type of the Provider """ return ConanProvider - - def test_skip_upload( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish with skip_upload=True only runs conan create without upload - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to skip upload mode - plugin.data.skip_upload = True - - # Mock subprocess.run - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.return_value = mocker.Mock(returncode=0) - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish - plugin.publish() - - # Verify subprocess.run was called once for conan create - mock_subprocess_run.assert_called_once() - call_args = mock_subprocess_run.call_args[0][0] - assert call_args[0] == 'conan' - assert call_args[1] == 'create' - assert '--build' in call_args - assert 'missing' in call_args - - # Verify no upload commands were executed (since skip_upload=True) - assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_SINGLE - - def test_with_upload( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish with remotes=['conancenter'] runs conan create and upload - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to upload mode - plugin.data.remotes = ['conancenter'] - plugin.data.skip_upload = False - - # Mock subprocess.run - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.return_value = mocker.Mock(returncode=0) - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish - plugin.publish() - - # Verify subprocess.run was called twice (create + upload) - assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD - - # Check first call - conan create - create_call_args = mock_subprocess_run.call_args_list[0][0][0] - assert create_call_args[0] == 'conan' - assert create_call_args[1] == 'create' - - # Check second call - conan upload - upload_call_args = mock_subprocess_run.call_args_list[1][0][0] - assert upload_call_args[0] == 'conan' - assert upload_call_args[1] == 'upload' - assert '--remote' in upload_call_args - assert 'conancenter' in upload_call_args - - def test_upload_to_all_remotes( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish with empty remotes list uploads to all available remotes - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to upload to all remotes - plugin.data.remotes = [] - plugin.data.skip_upload = False - - # Mock subprocess.run - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.return_value = mocker.Mock(returncode=0) - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish - plugin.publish() - - # Verify subprocess.run was called twice (create + upload) - assert mock_subprocess_run.call_count == EXPECTED_SUBPROCESS_CALLS_WITH_UPLOAD - - # Check second call - conan upload to all remotes - upload_call_args = mock_subprocess_run.call_args_list[1][0][0] - assert upload_call_args[0] == 'conan' - assert upload_call_args[1] == 'upload' - assert '--all' in upload_call_args - assert '--confirm' in upload_call_args - - def test_conan_create_failure( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish raises error when conan create fails - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to skip upload mode for simpler test - plugin.data.skip_upload = True - - # Mock subprocess.run to fail - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - 1, ['conan', 'create'], stderr='Conan create failed: missing dependency' - ) - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish and expect ProviderInstallationError - with pytest.raises(ProviderInstallationError, match='Conan create failed: missing dependency'): - plugin.publish() - - def test_conan_upload_failure( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish raises error when conan upload fails - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to upload mode - plugin.data.remotes = ['conancenter'] - plugin.data.skip_upload = False - - # Mock subprocess.run - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - - # First call (create) succeeds, second call (upload) fails - def subprocess_side_effect(*args, **kwargs): - if 'create' in args[0]: - return mocker.Mock(returncode=0) - elif 'upload' in args[0]: - raise subprocess.CalledProcessError( - 1, ['conan', 'upload'], stderr='Upload failed: authentication error' - ) - - mock_subprocess_run.side_effect = subprocess_side_effect - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish and expect ProviderInstallationError - with pytest.raises(ProviderInstallationError, match='Upload to conancenter failed'): - plugin.publish() - - def test_with_custom_profiles( - self, plugin: ConanProvider, conan_temp_conanfile: None, mocker: MockerFixture - ) -> None: - """Test that publish uses custom profiles when specified - - Args: - plugin: The plugin instance - conan_temp_conanfile: Fixture to create conanfile.py - mocker: Pytest mocker fixture - """ - # Set plugin to skip upload mode for simpler test - plugin.data.skip_upload = True - - # Mock custom profiles - custom_host_profile = mocker.Mock() - custom_build_profile = mocker.Mock() - - # Override profiles in the plugin data - plugin.data.host_profile = custom_host_profile - plugin.data.build_profile = custom_build_profile - - # Mock subprocess.run - mock_subprocess_run = mocker.patch('cppython.plugins.conan.plugin.subprocess.run') - mock_subprocess_run.return_value = mocker.Mock(returncode=0) - - # Mock getLogger - mock_logger = mocker.Mock() - mocker.patch('cppython.plugins.conan.plugin.getLogger', return_value=mock_logger) - - # Execute publish - plugin.publish() - - # Verify subprocess.run was called with custom profiles - mock_subprocess_run.assert_called_once() - call_args = mock_subprocess_run.call_args[0][0] - - # Find profile arguments - profile_host_idx = call_args.index('--profile:host') - profile_build_idx = call_args.index('--profile:build') - - assert call_args[profile_host_idx + 1] == str(custom_host_profile) - assert call_args[profile_build_idx + 1] == str(custom_build_profile) diff --git a/tests/unit/plugins/conan/test_resolution.py b/tests/unit/plugins/conan/test_resolution.py index a9034fe..891042d 100644 --- a/tests/unit/plugins/conan/test_resolution.py +++ b/tests/unit/plugins/conan/test_resolution.py @@ -4,19 +4,13 @@ from unittest.mock import Mock, patch import pytest -from conan.internal.model.profile import Profile from packaging.requirements import Requirement from cppython.core.exception import ConfigException -from cppython.core.schema import CorePluginData from cppython.plugins.conan.resolution import ( - _profile_post_process, - _resolve_profiles, - resolve_conan_data, resolve_conan_dependency, ) from cppython.plugins.conan.schema import ( - ConanData, ConanDependency, ConanRevision, ConanUserChannel, @@ -205,293 +199,8 @@ def test_from_reference_full(self) -> None: assert dependency.revision is not None assert dependency.revision.revision == 'abc123' - -class TestProfileProcessing: - """Test profile processing functionality.""" - - def test_success(self) -> None: - """Test successful profile processing.""" - mock_conan_api = Mock() - mock_profile = Mock() - mock_cache_settings = Mock() - mock_plugin = Mock() - - mock_conan_api.profiles._load_profile_plugin.return_value = mock_plugin - profiles = [mock_profile] - - _profile_post_process(profiles, mock_conan_api, mock_cache_settings) - - mock_plugin.assert_called_once_with(mock_profile) - mock_profile.process_settings.assert_called_once_with(mock_cache_settings) - - def test_no_plugin(self) -> None: - """Test profile processing when no plugin is available.""" - mock_conan_api = Mock() - mock_profile = Mock() - mock_cache_settings = Mock() - - mock_conan_api.profiles._load_profile_plugin.return_value = None - profiles = [mock_profile] - - _profile_post_process(profiles, mock_conan_api, mock_cache_settings) - - mock_profile.process_settings.assert_called_once_with(mock_cache_settings) - - def test_plugin_failure(self, caplog: pytest.LogCaptureFixture) -> None: - """Test profile processing when plugin fails.""" - mock_conan_api = Mock() - mock_profile = Mock() - mock_cache_settings = Mock() - mock_plugin = Mock() - - mock_conan_api.profiles._load_profile_plugin.return_value = mock_plugin - mock_plugin.side_effect = Exception('Plugin failed') - profiles = [mock_profile] - - with caplog.at_level(logging.WARNING): - _profile_post_process(profiles, mock_conan_api, mock_cache_settings) - - assert 'Profile plugin failed for profile' in caplog.text - mock_profile.process_settings.assert_called_once_with(mock_cache_settings) - - def test_settings_failure(self, caplog: pytest.LogCaptureFixture) -> None: - """Test profile processing when settings processing fails.""" - mock_conan_api = Mock() - mock_profile = Mock() - mock_cache_settings = Mock() - - mock_conan_api.profiles._load_profile_plugin.return_value = None - mock_profile.process_settings.side_effect = Exception('Settings failed') - profiles = [mock_profile] - - with caplog.at_level(logging.DEBUG): - _profile_post_process(profiles, mock_conan_api, mock_cache_settings) - - assert 'Settings processing failed for profile' in caplog.text - - class TestResolveProfiles: """Test profile resolution functionality.""" - def test_by_name(self) -> None: - """Test resolving profiles by name.""" - mock_conan_api = Mock() - mock_host_profile = Mock() - mock_build_profile = Mock() - mock_conan_api.profiles.get_profile.side_effect = [mock_host_profile, mock_build_profile] - - host_result, build_result = _resolve_profiles( - 'host-profile', 'build-profile', mock_conan_api, cmake_program=None - ) - - assert host_result == mock_host_profile - assert build_result == mock_build_profile - assert mock_conan_api.profiles.get_profile.call_count == EXPECTED_PROFILE_CALL_COUNT - mock_conan_api.profiles.get_profile.assert_any_call(['host-profile']) - mock_conan_api.profiles.get_profile.assert_any_call(['build-profile']) - - def test_by_name_failure(self) -> None: - """Test resolving profiles by name when host profile fails.""" - mock_conan_api = Mock() - mock_conan_api.profiles.get_profile.side_effect = Exception('Profile not found') - - with pytest.raises(ProviderConfigurationError, match='Failed to load host profile'): - _resolve_profiles('missing-profile', 'other-profile', mock_conan_api, cmake_program=None) - - def test_auto_detect(self) -> None: - """Test auto-detecting profiles.""" - mock_conan_api = Mock() - mock_host_profile = Mock() - mock_build_profile = Mock() - mock_host_default_path = 'host-default' - mock_build_default_path = 'build-default' - - mock_conan_api.profiles.get_default_host.return_value = mock_host_default_path - mock_conan_api.profiles.get_default_build.return_value = mock_build_default_path - mock_conan_api.profiles.get_profile.side_effect = [mock_host_profile, mock_build_profile] - - host_result, build_result = _resolve_profiles(None, None, mock_conan_api, cmake_program=None) - - assert host_result == mock_host_profile - assert build_result == mock_build_profile - mock_conan_api.profiles.get_default_host.assert_called_once() - mock_conan_api.profiles.get_default_build.assert_called_once() - mock_conan_api.profiles.get_profile.assert_any_call([mock_host_default_path]) - mock_conan_api.profiles.get_profile.assert_any_call([mock_build_default_path]) - - @patch('cppython.plugins.conan.resolution._profile_post_process') - def test_fallback_to_detect(self, mock_post_process: Mock) -> None: - """Test falling back to profile detection when defaults fail.""" - mock_conan_api = Mock() - mock_host_profile = Mock() - mock_build_profile = Mock() - mock_cache_settings = Mock() - - # Mock the default profile methods to fail - mock_conan_api.profiles.get_default_host.side_effect = Exception('No default profile') - mock_conan_api.profiles.get_default_build.side_effect = Exception('No default profile') - mock_conan_api.profiles.get_profile.side_effect = Exception('Profile not found') - - # Mock detect to succeed - mock_conan_api.profiles.detect.side_effect = [mock_host_profile, mock_build_profile] - mock_conan_api.config.settings_yml = mock_cache_settings - - host_result, build_result = _resolve_profiles(None, None, mock_conan_api, cmake_program=None) - - assert host_result == mock_host_profile - assert build_result == mock_build_profile - assert mock_conan_api.profiles.detect.call_count == EXPECTED_PROFILE_CALL_COUNT - assert mock_post_process.call_count == EXPECTED_PROFILE_CALL_COUNT - mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings, None) - mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings, None) - - @patch('cppython.plugins.conan.resolution._profile_post_process') - def test_default_fallback_to_detect(self, mock_post_process: Mock) -> None: - """Test falling back to profile detection when default profile fails.""" - mock_conan_api = Mock() - mock_host_profile = Mock() - mock_build_profile = Mock() - mock_cache_settings = Mock() - - # Mock the default profile to fail (this simulates the "default" profile not existing) - mock_conan_api.profiles.get_profile.side_effect = Exception('Profile not found') - mock_conan_api.profiles.get_default_host.side_effect = Exception('No default profile') - mock_conan_api.profiles.get_default_build.side_effect = Exception('No default profile') - - # Mock detect to succeed - mock_conan_api.profiles.detect.side_effect = [mock_host_profile, mock_build_profile] - mock_conan_api.config.settings_yml = mock_cache_settings - - host_result, build_result = _resolve_profiles('default', 'default', mock_conan_api, cmake_program=None) - - assert host_result == mock_host_profile - assert build_result == mock_build_profile - assert mock_conan_api.profiles.detect.call_count == EXPECTED_PROFILE_CALL_COUNT - assert mock_post_process.call_count == EXPECTED_PROFILE_CALL_COUNT - mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings, None) - mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings, None) - - class TestResolveConanData: """Test Conan data resolution.""" - - @patch('cppython.plugins.conan.resolution.ConanAPI') - @patch('cppython.plugins.conan.resolution._resolve_profiles') - @patch('cppython.plugins.conan.resolution._detect_cmake_program') - def test_with_profiles( - self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock - ) -> None: - """Test resolving ConanData with profile configuration.""" - mock_detect_cmake.return_value = None # No cmake detected for test - mock_conan_api = Mock() - mock_conan_api_class.return_value = mock_conan_api - - mock_host_profile = Mock(spec=Profile) - mock_build_profile = Mock(spec=Profile) - mock_resolve_profiles.return_value = (mock_host_profile, mock_build_profile) - - data = {'host_profile': 'linux-x64', 'build_profile': 'linux-gcc11', 'remotes': ['conancenter']} - core_data = Mock(spec=CorePluginData) - - result = resolve_conan_data(data, core_data) - - assert isinstance(result, ConanData) - assert result.host_profile == mock_host_profile - assert result.build_profile == mock_build_profile - assert result.remotes == ['conancenter'] - - # Verify profile resolution was called correctly - mock_resolve_profiles.assert_called_once_with('linux-x64', 'linux-gcc11', mock_conan_api, None) - - @patch('cppython.plugins.conan.resolution.ConanAPI') - @patch('cppython.plugins.conan.resolution._resolve_profiles') - @patch('cppython.plugins.conan.resolution._detect_cmake_program') - def test_default_profiles( - self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock - ) -> None: - """Test resolving ConanData with default profile configuration.""" - mock_detect_cmake.return_value = None # No cmake detected for test - mock_conan_api = Mock() - mock_conan_api_class.return_value = mock_conan_api - - mock_host_profile = Mock(spec=Profile) - mock_build_profile = Mock(spec=Profile) - mock_resolve_profiles.return_value = (mock_host_profile, mock_build_profile) - - data = {} # Empty data should use defaults - core_data = Mock(spec=CorePluginData) - - result = resolve_conan_data(data, core_data) - - assert isinstance(result, ConanData) - assert result.host_profile == mock_host_profile - assert result.build_profile == mock_build_profile - assert result.remotes == ['conancenter'] # Default remote - - # Verify profile resolution was called with default values - mock_resolve_profiles.assert_called_once_with('default', 'default', mock_conan_api, None) - - @patch('cppython.plugins.conan.resolution.ConanAPI') - @patch('cppython.plugins.conan.resolution._resolve_profiles') - @patch('cppython.plugins.conan.resolution._detect_cmake_program') - def test_null_profiles( - self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock - ) -> None: - """Test resolving ConanData with null profile configuration.""" - mock_detect_cmake.return_value = None # No cmake detected for test - mock_conan_api = Mock() - mock_conan_api_class.return_value = mock_conan_api - - mock_host_profile = Mock(spec=Profile) - mock_build_profile = Mock(spec=Profile) - mock_resolve_profiles.return_value = (mock_host_profile, mock_build_profile) - - data = {'host_profile': None, 'build_profile': None, 'remotes': [], 'skip_upload': False} - core_data = Mock(spec=CorePluginData) - - result = resolve_conan_data(data, core_data) - - assert isinstance(result, ConanData) - assert result.host_profile == mock_host_profile - assert result.build_profile == mock_build_profile - assert result.remotes == [] - assert result.skip_upload is False - - # Verify profile resolution was called with None values - mock_resolve_profiles.assert_called_once_with(None, None, mock_conan_api, None) - - @patch('cppython.plugins.conan.resolution.ConanAPI') - @patch('cppython.plugins.conan.resolution._profile_post_process') - def test_auto_detected_profile_processing(self, mock_post_process: Mock, mock_conan_api_class: Mock): - """Test that auto-detected profiles get proper post-processing. - - Args: - mock_post_process: Mock for _profile_post_process function - mock_conan_api_class: Mock for ConanAPI class - """ - mock_conan_api = Mock() - mock_conan_api_class.return_value = mock_conan_api - - # Configure the mock to simulate no default profiles - mock_conan_api.profiles.get_default_host.side_effect = Exception('No default profile') - mock_conan_api.profiles.get_default_build.side_effect = Exception('No default profile') - - # Create a profile that simulates auto-detection - mock_profile = Mock() - mock_profile.settings = {'os': 'Windows', 'arch': 'x86_64'} - mock_profile.process_settings = Mock() - mock_profile.conf = Mock() - mock_profile.conf.validate = Mock() - mock_profile.conf.rebase_conf_definition = Mock() - - mock_conan_api.profiles.detect.return_value = mock_profile - mock_conan_api.config.global_conf = Mock() - - # Call the resolution - this should trigger auto-detection and post-processing - host_profile, build_profile = _resolve_profiles(None, None, mock_conan_api, cmake_program=None) - - # Verify that auto-detection was called for both profiles - assert mock_conan_api.profiles.detect.call_count == EXPECTED_PROFILE_CALL_COUNT - - # Verify that post-processing was called for both profiles - assert mock_post_process.call_count == EXPECTED_PROFILE_CALL_COUNT diff --git a/tests/unit/plugins/conan/test_update.py b/tests/unit/plugins/conan/test_update.py index d91d798..b39e780 100644 --- a/tests/unit/plugins/conan/test_update.py +++ b/tests/unit/plugins/conan/test_update.py @@ -5,12 +5,9 @@ install() and update() now share the same underlying implementation. """ -from pathlib import Path from typing import Any -from unittest.mock import Mock import pytest -from packaging.requirements import Requirement from cppython.plugins.conan.plugin import ConanProvider from cppython.test.pytest.mixins import ProviderPluginTestMixin @@ -40,55 +37,3 @@ def fixture_plugin_type() -> type[ConanProvider]: The type of the Provider """ return ConanProvider - - def test_update_includes_update_flag( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - conan_setup_mocks: dict[str, Mock], - ) -> None: - """Test that update() method includes --update flag in conan command - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - conan_setup_mocks: Dictionary containing all mocks - """ - # Setup dependencies - plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - - # Execute update - plugin.update() - - # Verify subprocess.run was called with --update flag - conan_setup_mocks['subprocess_run'].assert_called_once() - call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] - assert '--update' in call_args - - def test_install_does_not_include_update_flag( - self, - plugin: ConanProvider, - conan_temp_conanfile: Path, - conan_mock_dependencies: list[Requirement], - conan_setup_mocks: dict[str, Mock], - ) -> None: - """Test that install() method does not include --update flag in conan command - - Args: - plugin: The plugin instance - conan_temp_conanfile: Path to temporary conanfile.py - conan_mock_dependencies: List of mock dependencies - conan_setup_mocks: Dictionary containing all mocks - """ - # Setup dependencies - plugin.core_data.cppython_data.dependencies = conan_mock_dependencies - - # Execute install - plugin.install() - - # Verify subprocess.run was called without --update flag - conan_setup_mocks['subprocess_run'].assert_called_once() - call_args = conan_setup_mocks['subprocess_run'].call_args[0][0] - assert '--update' not in call_args From cf288b4285485e48f6fa240f44525e1e6d77dc9f Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 12:47:58 -0400 Subject: [PATCH 29/68] Add Presets File Write --- cppython/plugins/conan/builder.py | 10 +- cppython/plugins/conan/plugin.py | 23 +-- cppython/plugins/conan/resolution.py | 208 +-------------------------- cppython/plugins/conan/schema.py | 26 ++-- 4 files changed, 27 insertions(+), 240 deletions(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 3623126..17c178d 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -121,7 +121,9 @@ def __init__(self) -> None: self._filename = 'conanfile.py' @staticmethod - def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], name: str, version: str) -> None: + def _create_conanfile( + conan_file: Path, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path + ) -> None: """Creates a conanfile.py file with the necessary content.""" template_string = """ from conan import ConanFile @@ -140,6 +142,7 @@ def generate(self): deps = CMakeDeps(self) deps.generate() tc = CMakeToolchain(self) + tc.user_presets_path = "${preset_file}" tc.generate() def build(self): @@ -158,6 +161,7 @@ def package(self): 'name': name, 'version': version, 'dependencies': [dependency.requires() for dependency in dependencies], + 'preset_file': str(preset_file), } result = template.substitute(values) @@ -166,7 +170,7 @@ def package(self): file.write(result) def generate_conanfile( - self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str + self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path ) -> None: """Generate a conanfile.py file for the project.""" conan_file = directory / self._filename @@ -181,4 +185,4 @@ def generate_conanfile( conan_file.write_text(modified.code, encoding='utf-8') else: directory.mkdir(parents=True, exist_ok=True) - self._create_conanfile(conan_file, dependencies, name, version) + self._create_conanfile(conan_file, dependencies, name, version, preset_file) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 6f35d28..c0ea15d 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -91,6 +91,7 @@ def _prepare_installation(self) -> Path: resolved_dependencies, self.core_data.pep621_data.name, self.core_data.pep621_data.version, + self.core_data.cppython_data.tool_path / 'ConanPresets.json', ) # Ensure build directory exists @@ -120,15 +121,8 @@ def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None command.extend(['--build', 'missing']) # Add profiles if specified - if self.data.host_profile and str(self.data.host_profile) != 'default': - command.extend(['--profile:host', str(self.data.host_profile)]) - else: - command.extend(['--profile:host', 'default']) - - if self.data.build_profile and str(self.data.build_profile) != 'default': - command.extend(['--profile:build', str(self.data.build_profile)]) - else: - command.extend(['--profile:build', 'default']) + command.extend(['--profile:host', 'default']) + command.extend(['--profile:build', 'default']) # Add update flag if needed if update: @@ -225,15 +219,8 @@ def publish(self) -> None: command.extend(['--build', 'missing']) # Add profiles - if self.data.host_profile and str(self.data.host_profile) != 'default': - command.extend(['--profile:host', str(self.data.host_profile)]) - else: - command.extend(['--profile:host', 'default']) - - if self.data.build_profile and str(self.data.build_profile) != 'default': - command.extend(['--profile:build', str(self.data.build_profile)]) - else: - command.extend(['--profile:build', 'default']) + command.extend(['--profile:host', 'default']) + command.extend(['--profile:build', 'default']) # Log the command being executed logger.info('Executing conan create command: %s', ' '.join(command)) diff --git a/cppython/plugins/conan/resolution.py b/cppython/plugins/conan/resolution.py index bd11006..3c98b37 100644 --- a/cppython/plugins/conan/resolution.py +++ b/cppython/plugins/conan/resolution.py @@ -1,12 +1,8 @@ """Provides functionality to resolve Conan-specific data for the CPPython project.""" -import importlib -import logging from pathlib import Path from typing import Any -from conan.api.conan_api import ConanAPI -from conan.internal.model.profile import Profile from packaging.requirements import Requirement from cppython.core.exception import ConfigException @@ -18,193 +14,6 @@ ConanVersion, ConanVersionRange, ) -from cppython.utility.exception import ProviderConfigurationError - - -def _detect_cmake_program() -> str | None: - """Detect CMake program path from the cmake module if available. - - Returns: - Path to cmake executable, or None if not found - """ - try: - # Try to import cmake module and get its executable path - # Note: cmake is an optional dependency, so we import it conditionally - cmake = importlib.import_module('cmake') - - cmake_bin_dir = Path(cmake.CMAKE_BIN_DIR) - - # Try common cmake executable names (pathlib handles platform differences) - for cmake_name in ['cmake.exe', 'cmake']: - cmake_exe = cmake_bin_dir / cmake_name - if cmake_exe.exists(): - return str(cmake_exe) - - return None - except ImportError: - # cmake module not available - return None - except (AttributeError, Exception): - # If cmake module doesn't have expected attributes - return None - - -def _profile_post_process( - profiles: list[Profile], conan_api: ConanAPI, cache_settings: Any, cmake_program: str | None = None -) -> None: - """Apply profile plugin and settings processing to a list of profiles. - - Args: - profiles: List of profiles to process - conan_api: The Conan API instance - cache_settings: The settings configuration - cmake_program: Optional path to cmake program to configure in profiles - """ - logger = logging.getLogger('cppython.conan') - - # Get global configuration - global_conf = conan_api.config.global_conf - - # Apply profile plugin processing - try: - profile_plugin = conan_api.profiles._load_profile_plugin() - if profile_plugin is not None: - for profile in profiles: - try: - profile_plugin(profile) - except Exception as plugin_error: - logger.warning('Profile plugin failed for profile: %s', str(plugin_error)) - except (AttributeError, Exception): - logger.debug('Profile plugin not available or failed to load') - - # Apply the full profile processing pipeline for each profile - for profile in profiles: - # Set cmake program configuration if provided - if cmake_program is not None: - try: - # Set the tools.cmake:cmake_program configuration in the profile - profile.conf.update('tools.cmake:cmake_program', cmake_program) - logger.debug('Set tools.cmake:cmake_program=%s in profile', cmake_program) - except (AttributeError, Exception) as cmake_error: - logger.debug('Failed to set cmake program configuration: %s', str(cmake_error)) - - # Process settings to initialize processed_settings - try: - profile.process_settings(cache_settings) - except (AttributeError, Exception) as settings_error: - logger.debug('Settings processing failed for profile: %s', str(settings_error)) - - # Validate configuration - try: - profile.conf.validate() - except (AttributeError, Exception) as conf_error: - logger.debug('Configuration validation failed for profile: %s', str(conf_error)) - - # Apply global configuration to the profile - try: - if global_conf is not None: - profile.conf.rebase_conf_definition(global_conf) - except (AttributeError, Exception) as rebase_error: - logger.debug('Configuration rebase failed for profile: %s', str(rebase_error)) - - -def _apply_cmake_config_to_profile(profile: Profile, cmake_program: str | None, profile_type: str) -> None: - """Apply cmake program configuration to a profile. - - Args: - profile: The profile to configure - cmake_program: Path to cmake program to configure - profile_type: Type of profile (for logging) - """ - if cmake_program is not None: - logger = logging.getLogger('cppython.conan') - try: - profile.conf.update('tools.cmake:cmake_program', cmake_program) - logger.debug('Set tools.cmake:cmake_program=%s in %s profile', cmake_program, profile_type) - except (AttributeError, Exception) as cmake_error: - logger.debug('Failed to set cmake program in %s profile: %s', profile_type, str(cmake_error)) - - -def _resolve_profiles( - host_profile_name: str | None, build_profile_name: str | None, conan_api: ConanAPI, cmake_program: str | None = None -) -> tuple[Profile, Profile]: - """Resolve host and build profiles, with fallback to auto-detection. - - Args: - host_profile_name: The host profile name to resolve, or None for auto-detection - build_profile_name: The build profile name to resolve, or None for auto-detection - conan_api: The Conan API instance - cmake_program: Optional path to cmake program to configure in profiles - - Returns: - A tuple of (host_profile, build_profile) - """ - logger = logging.getLogger('cppython.conan') - - def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile: - """Helper to resolve a single profile.""" - profile_type = 'host' if is_host else 'build' - - if profile_name is not None and profile_name != 'default': - # Explicitly specified profile name (not the default) - fail if not found - try: - logger.debug('Loading %s profile: %s', profile_type, profile_name) - profile = conan_api.profiles.get_profile([profile_name]) - logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name) - _apply_cmake_config_to_profile(profile, cmake_program, profile_type) - return profile - except Exception as e: - logger.error('Failed to load %s profile %s: %s', profile_type, profile_name, str(e)) - raise ProviderConfigurationError( - 'conan', - f'Failed to load {profile_type} profile {profile_name}: {str(e)}', - f'{profile_type}_profile', - ) from e - elif profile_name == 'default': - # Try to load default profile, but fall back to auto-detection if it fails - try: - logger.debug('Loading %s profile: %s', profile_type, profile_name) - profile = conan_api.profiles.get_profile([profile_name]) - logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name) - _apply_cmake_config_to_profile(profile, cmake_program, profile_type) - return profile - except Exception as e: - logger.debug( - 'Failed to load %s profile %s: %s. Falling back to auto-detection.', - profile_type, - profile_name, - str(e), - ) - # Fall back to auto-detection - - try: - if is_host: - default_profile_path = conan_api.profiles.get_default_host() - else: - default_profile_path = conan_api.profiles.get_default_build() - - profile = conan_api.profiles.get_profile([default_profile_path]) - logger.debug('Using default %s profile', profile_type) - _apply_cmake_config_to_profile(profile, cmake_program, profile_type) - return profile - except Exception as e: - logger.debug('Default %s profile not available, using auto-detection: %s', profile_type, str(e)) - - # Create auto-detected profile - profile = conan_api.profiles.detect() - cache_settings = conan_api.config.settings_yml - - # Apply profile plugin processing - _profile_post_process([profile], conan_api, cache_settings, cmake_program) - - logger.debug('Auto-detected %s profile with plugin processing applied', profile_type) - return profile - - # Resolve both profiles - host_profile = _resolve_profile(host_profile_name, is_host=True) - build_profile = _resolve_profile(build_profile_name, is_host=False) - - return host_profile, build_profile def _handle_single_specifier(name: str, specifier) -> ConanDependency: @@ -257,7 +66,7 @@ def resolve_conan_dependency(requirement: Requirement) -> ConanDependency: return _handle_single_specifier(requirement.name, next(iter(specifiers))) # Handle multiple specifiers - convert to Conan range syntax - range_parts = [] + range_parts: list[str] = [] # Define order for operators to ensure consistent output operator_order = ['>=', '>', '<=', '<', '!='] @@ -305,20 +114,13 @@ def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> Conan """ parsed_data = ConanConfiguration(**data) - # Initialize Conan API for profile resolution - conan_api = ConanAPI() + profile_dir = Path(parsed_data.profile_dir) - # Try to detect cmake program path from current virtual environment - cmake_program = _detect_cmake_program() - - # Resolve profiles - host_profile, build_profile = _resolve_profiles( - parsed_data.host_profile, parsed_data.build_profile, conan_api, cmake_program - ) + if not profile_dir.is_absolute(): + profile_dir = core_data.cppython_data.tool_path / profile_dir return ConanData( remotes=parsed_data.remotes, skip_upload=parsed_data.skip_upload, - host_profile=host_profile, - build_profile=build_profile, + profile_dir=profile_dir, ) diff --git a/cppython/plugins/conan/schema.py b/cppython/plugins/conan/schema.py index 19d2e73..a8f348c 100644 --- a/cppython/plugins/conan/schema.py +++ b/cppython/plugins/conan/schema.py @@ -1,3 +1,5 @@ +from pathlib import Path + """Conan plugin schema This module defines Pydantic models used for integrating the Conan @@ -8,7 +10,6 @@ import re from typing import Annotated -from conan.internal.model.profile import Profile from pydantic import Field, field_validator from cppython.core.schema import CPPythonModel @@ -294,8 +295,7 @@ class ConanData(CPPythonModel): remotes: list[str] skip_upload: bool - host_profile: Profile - build_profile: Profile + profile_dir: Path class ConanConfiguration(CPPythonModel): @@ -307,19 +307,13 @@ class ConanConfiguration(CPPythonModel): ] = ['conancenter'] skip_upload: Annotated[ bool, - Field(description='If true, skip uploading packages during publish (local-only mode).'), + Field(description='If true, skip uploading packages to a remote during publishing.'), ] = False - host_profile: Annotated[ - str | None, - Field( - description='Conan host profile defining the target platform where the built software will run. ' - 'Used for cross-compilation scenarios.' - ), - ] = 'default' - build_profile: Annotated[ - str | None, + profile_dir: Annotated[ + str, Field( - description='Conan build profile defining the platform where the compilation process executes. ' - 'Typically matches the development machine.' + description='Directory containing Conan profiles. Profiles will be looked up relative to this directory. ' + 'If profiles do not exist in this directory, Conan will fall back to default profiles.' + "If a relative path is provided, it will be resolved relative to the tool's working directory." ), - ] = 'default' + ] = 'profiles' From 3d104ca35c2d3472197953c48f3362beeb10a896 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 13:05:45 -0400 Subject: [PATCH 30/68] Fix Imports --- cppython/plugins/conan/plugin.py | 113 +++++++++++++++---------------- cppython/plugins/conan/schema.py | 3 +- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index c0ea15d..dfe49d7 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -5,11 +5,14 @@ installation, and synchronization with other tools. """ -import subprocess +import os from logging import getLogger from pathlib import Path from typing import Any +from conan.api.conan_api import ConanAPI +from conan.cli.cli import Cli + from cppython.core.plugin_schema.generator import SyncConsumer from cppython.core.plugin_schema.provider import Provider, ProviderPluginGroupData, SupportedProviderFeatures from cppython.core.schema import CorePluginData, Information, SupportedFeatures, SyncData @@ -34,6 +37,11 @@ def __init__( self.data: ConanData = resolve_conan_data(configuration_data, core_data) self.builder = Builder() + # Initialize ConanAPI once and reuse it + self._conan_api = ConanAPI() + # Initialize CLI for command API to work properly + self._cli = Cli(self._conan_api) + self._cli.add_commands() @staticmethod def features(directory: Path) -> SupportedFeatures: @@ -107,44 +115,45 @@ def _prepare_installation(self) -> Path: return conanfile_path def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None: - """Run conan install command. + """Run conan install command using Conan API. Args: conanfile_path: Path to the conanfile.py update: Whether to check for updates logger: Logger instance """ - # Build conan install command - command = ['conan', 'install', str(conanfile_path)] + # Build conan install command arguments + command_args = ['install', str(conanfile_path)] # Add build missing flag - command.extend(['--build', 'missing']) + command_args.extend(['--build', 'missing']) # Add profiles if specified - command.extend(['--profile:host', 'default']) - command.extend(['--profile:build', 'default']) + command_args.extend(['--profile:host', 'default']) + command_args.extend(['--profile:build', 'default']) # Add update flag if needed if update: - command.append('--update') + command_args.append('--update') # Add output folder build_path = self.core_data.cppython_data.build_path - command.extend(['--output-folder', str(build_path)]) + command_args.extend(['--output-folder', str(build_path)]) # Log the command being executed - logger.info('Executing conan command: %s', ' '.join(command)) + logger.info('Executing conan command: conan %s', ' '.join(command_args)) try: - subprocess.run( - command, - cwd=str(self.core_data.project_data.project_root), - check=True, - capture_output=True, - text=True, - ) - except subprocess.CalledProcessError as e: - error_msg = e.stderr if e.stderr else str(e) + # Use reusable Conan API instance instead of subprocess + # Change to project directory since Conan API might not handle cwd like subprocess + original_cwd = os.getcwd() + try: + os.chdir(str(self.core_data.project_data.project_root)) + self._conan_api.command.run(command_args) + finally: + os.chdir(original_cwd) + except Exception as e: + error_msg = str(e) logger.error('Conan install failed: %s', error_msg, exc_info=True) raise ProviderInstallationError('conan', error_msg, e) from e @@ -212,77 +221,65 @@ def publish(self) -> None: raise FileNotFoundError(f'conanfile.py not found at {conanfile_path}') try: - # Build conan create command - command = ['conan', 'create', str(conanfile_path)] + # Build conan create command arguments + command_args = ['create', str(conanfile_path)] # Add build mode (build everything for publishing) - command.extend(['--build', 'missing']) + command_args.extend(['--build', 'missing']) # Add profiles - command.extend(['--profile:host', 'default']) - command.extend(['--profile:build', 'default']) + command_args.extend(['--profile:host', 'default']) + command_args.extend(['--profile:build', 'default']) # Log the command being executed - logger.info('Executing conan create command: %s', ' '.join(command)) + logger.info('Executing conan create command: conan %s', ' '.join(command_args)) - # Run conan create - subprocess.run( - command, - cwd=str(project_root), - check=True, - capture_output=True, - text=True, - ) + # Run conan create using reusable Conan API instance + # Change to project directory since Conan API might not handle cwd like subprocess + original_cwd = os.getcwd() + try: + os.chdir(str(project_root)) + self._conan_api.command.run(command_args) + finally: + os.chdir(original_cwd) # Upload if not skipped if not self.data.skip_upload: self._upload_package(logger) - except subprocess.CalledProcessError as e: - error_msg = e.stderr if e.stderr else str(e) + except Exception as e: + error_msg = str(e) logger.error('Conan create failed: %s', error_msg, exc_info=True) raise ProviderInstallationError('conan', error_msg, e) from e def _upload_package(self, logger) -> None: - """Upload the package to configured remotes using CLI commands.""" + """Upload the package to configured remotes using Conan API.""" # If no remotes configured, upload to all remotes if not self.data.remotes: # Upload to all available remotes - command = ['conan', 'upload', '*', '--all', '--confirm'] + command_args = ['upload', '*', '--all', '--confirm'] else: # Upload only to specified remotes for remote in self.data.remotes: - command = ['conan', 'upload', '*', '--remote', remote, '--all', '--confirm'] + command_args = ['upload', '*', '--remote', remote, '--all', '--confirm'] # Log the command being executed - logger.info('Executing conan upload command: %s', ' '.join(command)) + logger.info('Executing conan upload command: conan %s', ' '.join(command_args)) try: - subprocess.run( - command, - cwd=str(self.core_data.project_data.project_root), - check=True, - capture_output=True, - text=True, - ) - except subprocess.CalledProcessError as e: - error_msg = e.stderr if e.stderr else str(e) + self._conan_api.command.run(command_args) + except Exception as e: + error_msg = str(e) logger.error('Conan upload failed for remote %s: %s', remote, error_msg, exc_info=True) raise ProviderInstallationError('conan', f'Upload to {remote} failed: {error_msg}', e) from e return # Log the command for uploading to all remotes - logger.info('Executing conan upload command: %s', ' '.join(command)) + logger.info('Executing conan upload command: conan %s', ' '.join(command_args)) try: - subprocess.run( - command, - cwd=str(self.core_data.project_data.project_root), - check=True, - capture_output=True, - text=True, - ) - except subprocess.CalledProcessError as e: - error_msg = e.stderr if e.stderr else str(e) + self._conan_api.command.run(command_args) + except Exception as e: + error_msg = str(e) logger.error('Conan upload failed: %s', error_msg, exc_info=True) raise ProviderInstallationError('conan', error_msg, e) from e diff --git a/cppython/plugins/conan/schema.py b/cppython/plugins/conan/schema.py index a8f348c..43b136e 100644 --- a/cppython/plugins/conan/schema.py +++ b/cppython/plugins/conan/schema.py @@ -1,5 +1,3 @@ -from pathlib import Path - """Conan plugin schema This module defines Pydantic models used for integrating the Conan @@ -8,6 +6,7 @@ """ import re +from pathlib import Path from typing import Annotated from pydantic import Field, field_validator From f3dd734ab524303ffb77eb9e713f4d9cbd9d2f7f Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 14:12:53 -0400 Subject: [PATCH 31/68] Add Build Presets --- cppython/plugins/cmake/schema.py | 34 +++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index c58bd0c..8f746a0 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -65,17 +65,36 @@ class ConfigurePreset(CPPythonModel, extra='allow'): cacheVariables: dict[str, None | bool | str | CacheVariable] | None = None -class CMakePresets(CPPythonModel, extra='allow'): - """The schema for the CMakePresets and CMakeUserPresets files. +class BuildPreset(CPPythonModel, extra='allow'): + """Partial Build Preset specification for CMake build presets""" - The only information needed is the configure preset list for cache variable injection - """ + name: str + hidden: Annotated[bool | None, Field(description='If true, the preset is hidden and cannot be used directly.')] = ( + None + ) + + inherits: Annotated[ + str | list[str] | None, Field(description='The inherits field allows inheriting from other presets.') + ] = None + configurePreset: Annotated[ + str | None, + Field(description='The name of a configure preset to associate with this build preset.'), + ] = None + configuration: Annotated[ + str | None, + Field(description='Build configuration. Equivalent to --config on the command line.'), + ] = None + + +class CMakePresets(CPPythonModel, extra='allow'): + """The schema for the CMakePresets and CMakeUserPresets files.""" 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 + buildPresets: Annotated[list[BuildPreset] | None, Field(description='The list of build presets')] = None class CMakeSyncData(SyncData): @@ -108,5 +127,10 @@ class CMakeConfiguration(CPPythonModel): ), ] = Path('CMakePresets.json') configuration_name: Annotated[ - str, Field(description='The CMake configuration preset to look for and override inside the given `preset_file`') + str, + Field( + description='The CMake configuration preset to look for and override inside the given `preset_file`. ' + 'Additional configurations will be added using this option as the base. For example, given "default", ' + '"default-release" will also be written' + ), ] = 'default' From b3cb14b9eee90e71e4ca82c8a4d556313d22ee03 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 14:39:24 -0400 Subject: [PATCH 32/68] Write Plugin Documentation --- docs/antora.yml | 5 +- docs/modules/ROOT/pages/index.adoc | 17 ++++- docs/modules/plugins/nav.adoc | 5 ++ .../plugins/pages/cmake-configuration.adoc | 14 ++++ docs/modules/plugins/pages/cmake-presets.adoc | 72 +++++++++++++++++++ docs/modules/plugins/pages/cmake.adoc | 7 ++ docs/modules/plugins/pages/index.adoc | 47 ++++++++++++ docs/modules/tests/nav.adoc | 6 +- 8 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 docs/modules/plugins/nav.adoc create mode 100644 docs/modules/plugins/pages/cmake-configuration.adoc create mode 100644 docs/modules/plugins/pages/cmake-presets.adoc create mode 100644 docs/modules/plugins/pages/cmake.adoc create mode 100644 docs/modules/plugins/pages/index.adoc diff --git a/docs/antora.yml b/docs/antora.yml index 3334c3c..7487323 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -2,5 +2,6 @@ name: cppython version: 0.1.0 title: CPPython nav: - - modules/ROOT/nav.adoc - - modules/tests/nav.adoc + - modules/ROOT/nav.adoc + - modules/plugins/nav.adoc + - modules/tests/nav.adoc diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index c3caa44..c89ed3d 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -1 +1,16 @@ -= Documentation for CPPython \ No newline at end of file += CPPython Documentation + +CPPython is a C++ project management tool that integrates with various build systems, package managers, and development tools through a flexible plugin architecture. + +== Getting Started + +- xref:configuration.adoc[Configuration Guide] - Learn how to configure CPPython for your project +- xref:plugins:index.adoc[Plugin System] - Understand the plugin architecture and available plugins + +== Plugin Documentation + +=== Build System Integration + +- xref:plugins:cmake.adoc[CMake Generator Plugin] - Complete integration with CMake build system + * xref:plugins:cmake-presets.adoc[CMake Presets System] - Advanced preset management + * xref:plugins:cmake-configuration.adoc[Configuration Options] - Detailed configuration reference \ No newline at end of file diff --git a/docs/modules/plugins/nav.adoc b/docs/modules/plugins/nav.adoc new file mode 100644 index 0000000..6e40db1 --- /dev/null +++ b/docs/modules/plugins/nav.adoc @@ -0,0 +1,5 @@ +.Plugins +* xref:index.adoc[Plugin System Overview] +* xref:cmake.adoc[CMake Generator Plugin] +** xref:cmake-presets.adoc[CMake Presets System] +** xref:cmake-configuration.adoc[Configuration Options] diff --git a/docs/modules/plugins/pages/cmake-configuration.adoc b/docs/modules/plugins/pages/cmake-configuration.adoc new file mode 100644 index 0000000..c238e1f --- /dev/null +++ b/docs/modules/plugins/pages/cmake-configuration.adoc @@ -0,0 +1,14 @@ += CMake Configuration Options + +This page provides a comprehensive reference for configuring the CMake Generator Plugin in CPPython. + +== Configuration Location + +CMake plugin configuration is specified in your project's `pyproject.toml` file under the `[tool.cppython.generators.cmake]` section: + +[source,toml] +---- +[tool.cppython.generators.cmake] +preset_file = "CMakePresets.json" +configuration_name = "default" +---- \ No newline at end of file diff --git a/docs/modules/plugins/pages/cmake-presets.adoc b/docs/modules/plugins/pages/cmake-presets.adoc new file mode 100644 index 0000000..f3dbc37 --- /dev/null +++ b/docs/modules/plugins/pages/cmake-presets.adoc @@ -0,0 +1,72 @@ += CMake Presets System + +The CMake Presets System in CPPython provides a sophisticated three-tier architecture for managing CMake configurations across different build scenarios, dependency providers, and user customizations. + +== Architecture Overview + +The preset system is designed around three distinct layers: + +[source,text] +---- +User CMakePresets.json + ↓ (includes) +CPPython Presets (.cppython/cppython.json) + ↓ (includes) +Provider Presets (.cppython/providers/*.json) +---- + +Each layer serves a specific purpose and maintains clear separation of concerns. + +== Provider Preset Layer + +The provider layer contains presets generated by dependency management tools like Conan or vcpkg. These presets include tool-specific configurations and are completely managed by CPPython. + +=== Structure + +Each provider generates **4 configure presets**: + +`{provider}-base`:: +Hidden preset containing common configuration shared by all other presets. Includes toolchain files and top-level includes. + +`{provider}-default`:: +Hidden preset inheriting from base. No specific build type set, making it suitable for multi-config generators. + +`{provider}-release`:: +Hidden preset inheriting from base with `CMAKE_BUILD_TYPE=Release` for single-config generators. + +`{provider}-debug`:: +Hidden preset inheriting from base with `CMAKE_BUILD_TYPE=Debug` for single-config generators. + + +== CPPython Preset Layer + +The CPPython layer standardizes provider configurations and creates both configure and build presets. This layer serves as the primary interface between provider-specific settings and user configurations. + +=== Configure Presets + +The CPPython layer generates **3 configure presets**: + +`default`:: +Public preset inheriting from `{provider}-default`. Designed for multi-config generators. + +`release`:: +Public preset inheriting from `{provider}-release`. Optimized for single-config Release builds. + +`debug`:: +Public preset inheriting from `{provider}-debug`. Optimized for single-config Debug builds. + +=== Build Presets + +The CPPython layer generates **4 build presets** to handle both single-config and multi-config generators: + +`multi-release`:: +Uses the `default` configure preset with `configuration: "Release"`. For multi-config generators. + +`multi-debug`:: +Uses the `default` configure preset with `configuration: "Debug"`. For multi-config generators. + +`release`:: +Uses the `release` configure preset with `configuration: "Release"`. For single-config generators. + +`debug`:: +Uses the `debug` configure preset with `configuration: "Debug"`. For single-config generators. \ No newline at end of file diff --git a/docs/modules/plugins/pages/cmake.adoc b/docs/modules/plugins/pages/cmake.adoc new file mode 100644 index 0000000..121aa13 --- /dev/null +++ b/docs/modules/plugins/pages/cmake.adoc @@ -0,0 +1,7 @@ += CMake Generator Plugin + +**TODO** + +== Overview + +**TODO** \ No newline at end of file diff --git a/docs/modules/plugins/pages/index.adoc b/docs/modules/plugins/pages/index.adoc new file mode 100644 index 0000000..0a76536 --- /dev/null +++ b/docs/modules/plugins/pages/index.adoc @@ -0,0 +1,47 @@ += Plugin System Overview + +CPPython uses a plugin-based architecture to support different build systems, package managers, and source control systems. The plugin system is designed to be extensible and modular, allowing for easy integration of new tools and workflows. + +== Plugin Types + +CPPython supports three main types of plugins: + +=== Generator Plugins + +Generator plugins integrate with build systems like CMake, providing functionality to generate build files and manage build configurations. + +Currently supported generators: +- xref:cmake.adoc[CMake Generator Plugin] + +=== Provider Plugins + +Provider plugins integrate with package managers and dependency providers, handling dependency resolution and environment setup. + +Currently supported providers: +- Conan +- vcpkg + +=== Source Control Manager (SCM) Plugins + +SCM plugins integrate with version control systems to manage project metadata and versioning. + +Currently supported SCM systems: +- Git + +== Plugin Architecture + +The plugin system follows a consistent pattern where each plugin type implements specific interfaces: + +- **Generator Interface**: Handles build system integration +- **Provider Interface**: Manages dependency resolution +- **SCM Interface**: Provides version control integration + +Each plugin can define: +- Configuration schemas using Pydantic models +- Synchronization data structures +- Build and resolution logic +- File generation and management + +== Configuration + +Plugins are configured through the `pyproject.toml` file in the `[tool.cppython]` section. Each plugin type has its own configuration section with specific options and defaults. \ No newline at end of file diff --git a/docs/modules/tests/nav.adoc b/docs/modules/tests/nav.adoc index 24495cf..a8cd685 100644 --- a/docs/modules/tests/nav.adoc +++ b/docs/modules/tests/nav.adoc @@ -1,3 +1,3 @@ -.CPPython -* xref:index.adoc[] -* xref:fixtures.adoc[] \ No newline at end of file +.Testing +* xref:index.adoc[Testing Overview] +* xref:fixtures.adoc[Test Fixtures] \ No newline at end of file From 15eb1e074a5c18898d1f49914fdd5a0564f77664 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 14:47:14 -0400 Subject: [PATCH 33/68] Write Additional Presets --- cppython/plugins/cmake/builder.py | 255 +++++++++++++++++++---- tests/unit/plugins/cmake/test_presets.py | 2 +- 2 files changed, 213 insertions(+), 44 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 26f46d1..4bb6fc4 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -2,7 +2,14 @@ from pathlib import Path -from cppython.plugins.cmake.schema import CacheVariable, CMakeData, CMakePresets, CMakeSyncData, ConfigurePreset +from cppython.plugins.cmake.schema import ( + BuildPreset, + CacheVariable, + CMakeData, + CMakePresets, + CMakeSyncData, + ConfigurePreset, +) class Builder: @@ -16,10 +23,10 @@ def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: """Generates a provider preset from input sync data Args: - provider_directory: The base directory to place the preset files provider_data: The providers synchronization data """ - generated_configure_preset = ConfigurePreset(name=provider_data.provider_name, hidden=True) + # Base hidden preset with common configuration + base_preset = ConfigurePreset(name=f'{provider_data.provider_name}-base', hidden=True) # Handle both top_level_includes and toolchain options cache_variables: dict[str, str | bool | CacheVariable | None] = {} @@ -29,12 +36,31 @@ def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: if provider_data.toolchain: # Use the toolchainFile field for better integration - generated_configure_preset.toolchainFile = provider_data.toolchain.as_posix() + base_preset.toolchainFile = provider_data.toolchain.as_posix() if cache_variables: - generated_configure_preset.cacheVariables = cache_variables + base_preset.cacheVariables = cache_variables + + # Create specific configuration presets + default_preset = ConfigurePreset( + name=f'{provider_data.provider_name}-default', hidden=True, inherits=f'{provider_data.provider_name}-base' + ) + + release_preset = ConfigurePreset( + name=f'{provider_data.provider_name}-release', + hidden=True, + inherits=f'{provider_data.provider_name}-base', + cacheVariables={'CMAKE_BUILD_TYPE': 'Release'}, + ) - return CMakePresets(configurePresets=[generated_configure_preset]) + debug_preset = ConfigurePreset( + name=f'{provider_data.provider_name}-debug', + hidden=True, + inherits=f'{provider_data.provider_name}-base', + cacheVariables={'CMAKE_BUILD_TYPE': 'Debug'}, + ) + + return CMakePresets(configurePresets=[base_preset, default_preset, release_preset, debug_preset]) @staticmethod def write_provider_preset(provider_directory: Path, provider_data: CMakeSyncData) -> None: @@ -75,8 +101,50 @@ def generate_cppython_preset( Returns: A CMakePresets object """ - generated_configure_preset = ConfigurePreset(name='cppython', inherits=provider_data.provider_name, hidden=True) - generated_preset = CMakePresets(configurePresets=[generated_configure_preset]) + # Configure presets similar to Conan structure + default_configure = ConfigurePreset( + name='default', inherits=f'{provider_data.provider_name}-default', hidden=False + ) + + release_configure = ConfigurePreset( + name='release', inherits=f'{provider_data.provider_name}-release', hidden=False + ) + + debug_configure = ConfigurePreset(name='debug', inherits=f'{provider_data.provider_name}-debug', hidden=False) + + # Build presets for multi-config and single-config generators + multi_release_build = BuildPreset( + name='multi-release', + configurePreset='default', + configuration='Release', + inherits=f'{provider_data.provider_name}-release', + ) + + multi_debug_build = BuildPreset( + name='multi-debug', + configurePreset='default', + configuration='Debug', + inherits=f'{provider_data.provider_name}-debug', + ) + + release_build = BuildPreset( + name='release', + configurePreset='release', + configuration='Release', + inherits=f'{provider_data.provider_name}-release', + ) + + debug_build = BuildPreset( + name='debug', + configurePreset='debug', + configuration='Debug', + inherits=f'{provider_data.provider_name}-debug', + ) + + generated_preset = CMakePresets( + configurePresets=[default_configure, release_configure, debug_configure], + buildPresets=[multi_release_build, multi_debug_build, release_build, debug_build], + ) # Get the relative path to the provider preset file provider_preset_file = provider_directory / f'{provider_data.provider_name}.json' @@ -122,66 +190,167 @@ def write_cppython_preset( return cppython_preset_file @staticmethod - def generate_root_preset( - preset_file: Path, cppython_preset_file: Path, cmake_data: CMakeData, build_directory: Path - ) -> CMakePresets: - """Generates the top level root preset with the include reference. + def _create_user_presets(cmake_data: CMakeData, build_directory: Path) -> tuple[ConfigurePreset, list[BuildPreset]]: + """Create user configure and build presets. Args: - preset_file: Preset file to modify - cppython_preset_file: Path to the cppython preset file to include cmake_data: The CMake data to use build_directory: The build directory to use Returns: - A CMakePresets object + A tuple containing the configure preset and list of build presets """ - default_configure_preset = ConfigurePreset( + user_configure_preset = ConfigurePreset( name=cmake_data.configuration_name, - inherits='cppython', + inherits='default', # Inherit from cppython's default preset binaryDir=build_directory.as_posix(), - cacheVariables={ - 'CMAKE_BUILD_TYPE': 'Release' # Ensure compatibility for single-config and multi-config generators - }, ) - if preset_file.exists(): - with open(preset_file, encoding='utf-8') as file: - initial_json = file.read() - root_preset = CMakePresets.model_validate_json(initial_json) - - if root_preset.configurePresets is None: - root_preset.configurePresets = [default_configure_preset] - - # Set defaults - preset = next((p for p in root_preset.configurePresets if p.name == default_configure_preset.name), None) - if preset: - # If the name matches, we need to verify it inherits from cppython - if preset.inherits is None: - preset.inherits = 'cppython' - elif isinstance(preset.inherits, str) and preset.inherits != 'cppython': - preset.inherits = [preset.inherits, 'cppython'] - elif isinstance(preset.inherits, list) and 'cppython' not in preset.inherits: - preset.inherits.append('cppython') + user_build_presets = [ + BuildPreset( + name=f'{cmake_data.configuration_name}-multi-release', + configurePreset=cmake_data.configuration_name, + configuration='Release', + ), + BuildPreset( + name=f'{cmake_data.configuration_name}-multi-debug', + configurePreset=cmake_data.configuration_name, + configuration='Debug', + ), + ] + + return user_configure_preset, user_build_presets + + @staticmethod + def _load_existing_preset(preset_file: Path) -> CMakePresets | None: + """Load existing preset file if it exists. + + Args: + preset_file: Path to the preset file + + Returns: + CMakePresets object if file exists, None otherwise + """ + if not preset_file.exists(): + return None + + with open(preset_file, encoding='utf-8') as file: + initial_json = file.read() + return CMakePresets.model_validate_json(initial_json) + + @staticmethod + def _update_configure_preset(existing_preset: ConfigurePreset, build_directory: Path) -> None: + """Update an existing configure preset to ensure proper inheritance and binary directory. + + Args: + existing_preset: The preset to update + build_directory: The build directory to use + """ + # Update existing preset to ensure it inherits from 'default' + if existing_preset.inherits is None: + existing_preset.inherits = 'default' + elif isinstance(existing_preset.inherits, str) and existing_preset.inherits != 'default': + existing_preset.inherits = ['default', existing_preset.inherits] + elif isinstance(existing_preset.inherits, list) and 'default' not in existing_preset.inherits: + existing_preset.inherits.insert(0, 'default') + + # Update binary directory if not set + if not existing_preset.binaryDir: + existing_preset.binaryDir = build_directory.as_posix() + + @staticmethod + def _handle_configure_presets( + root_preset: CMakePresets, user_configure_preset: ConfigurePreset, build_directory: Path + ) -> None: + """Handle configure presets in the root preset. + + Args: + root_preset: The root preset to modify + user_configure_preset: The user's configure preset + build_directory: The build directory to use + """ + if root_preset.configurePresets is None: + root_preset.configurePresets = [user_configure_preset] + else: + # Update or add the user's configure preset + existing_preset = next( + (p for p in root_preset.configurePresets if p.name == user_configure_preset.name), None + ) + if existing_preset: + Builder._update_configure_preset(existing_preset, build_directory) else: - root_preset.configurePresets.append(default_configure_preset) + root_preset.configurePresets.append(user_configure_preset) + + @staticmethod + def _handle_build_presets(root_preset: CMakePresets, user_build_presets: list[BuildPreset]) -> None: + """Handle build presets in the root preset. + Args: + root_preset: The root preset to modify + user_build_presets: The user's build presets to add + """ + if root_preset.buildPresets is None: + root_preset.buildPresets = user_build_presets.copy() else: - # If the file doesn't exist, we need to default it for the user - root_preset = CMakePresets(configurePresets=[default_configure_preset]) + # Add build presets if they don't exist + for build_preset in user_build_presets: + existing = next((p for p in root_preset.buildPresets if p.name == build_preset.name), None) + if not existing: + root_preset.buildPresets.append(build_preset) + @staticmethod + def _handle_includes(root_preset: CMakePresets, preset_file: Path, cppython_preset_file: Path) -> None: + """Handle include paths in the root preset. + + Args: + root_preset: The root preset to modify + preset_file: Path to the preset file + cppython_preset_file: Path to the cppython preset file to include + """ # 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 + # Handle includes 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)) + @staticmethod + def generate_root_preset( + preset_file: Path, cppython_preset_file: Path, cmake_data: CMakeData, build_directory: Path + ) -> CMakePresets: + """Generates the top level root preset with the include reference. + + Args: + preset_file: Preset file to modify + cppython_preset_file: Path to the cppython preset file to include + cmake_data: The CMake data to use + build_directory: The build directory to use + + Returns: + A CMakePresets object + """ + # Create user presets + user_configure_preset, user_build_presets = Builder._create_user_presets(cmake_data, build_directory) + + # Load existing preset or create new one + root_preset = Builder._load_existing_preset(preset_file) + if root_preset is None: + root_preset = CMakePresets( + configurePresets=[user_configure_preset], + buildPresets=user_build_presets, + ) + else: + # Handle existing preset + Builder._handle_configure_presets(root_preset, user_configure_preset, build_directory) + Builder._handle_build_presets(root_preset, user_build_presets) + + # Handle includes + Builder._handle_includes(root_preset, preset_file, cppython_preset_file) + return root_preset @staticmethod diff --git a/tests/unit/plugins/cmake/test_presets.py b/tests/unit/plugins/cmake/test_presets.py index 253592b..98f113a 100644 --- a/tests/unit/plugins/cmake/test_presets.py +++ b/tests/unit/plugins/cmake/test_presets.py @@ -27,7 +27,7 @@ def test_generate_root_preset_new(project_data: ProjectData) -> None: assert any(p.name == 'test-configuration' for p in result.configurePresets) preset = next(p for p in result.configurePresets if p.name == 'test-configuration') - assert preset.inherits == 'cppython' + assert preset.inherits == 'default' @staticmethod def test_generate_root_preset_existing(project_data: ProjectData) -> None: From 8e43a78705925095c38e9941388d5712c5e0839a Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 14:50:32 -0400 Subject: [PATCH 34/68] Ignore Lint Errors Will fix later when Pyrefly updates --- cppython/plugins/cmake/builder.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 4bb6fc4..2ee7e5e 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -248,15 +248,15 @@ def _update_configure_preset(existing_preset: ConfigurePreset, build_directory: """ # Update existing preset to ensure it inherits from 'default' if existing_preset.inherits is None: - existing_preset.inherits = 'default' + existing_preset.inherits = 'default' # type: ignore[misc] elif isinstance(existing_preset.inherits, str) and existing_preset.inherits != 'default': - existing_preset.inherits = ['default', existing_preset.inherits] + existing_preset.inherits = ['default', existing_preset.inherits] # type: ignore[misc] elif isinstance(existing_preset.inherits, list) and 'default' not in existing_preset.inherits: existing_preset.inherits.insert(0, 'default') # Update binary directory if not set if not existing_preset.binaryDir: - existing_preset.binaryDir = build_directory.as_posix() + existing_preset.binaryDir = build_directory.as_posix() # type: ignore[misc] @staticmethod def _handle_configure_presets( @@ -270,7 +270,7 @@ def _handle_configure_presets( build_directory: The build directory to use """ if root_preset.configurePresets is None: - root_preset.configurePresets = [user_configure_preset] + root_preset.configurePresets = [user_configure_preset] # type: ignore[misc] else: # Update or add the user's configure preset existing_preset = next( @@ -290,7 +290,7 @@ def _handle_build_presets(root_preset: CMakePresets, user_build_presets: list[Bu user_build_presets: The user's build presets to add """ if root_preset.buildPresets is None: - root_preset.buildPresets = user_build_presets.copy() + root_preset.buildPresets = user_build_presets.copy() # type: ignore[misc] else: # Add build presets if they don't exist for build_preset in user_build_presets: @@ -313,7 +313,7 @@ def _handle_includes(root_preset: CMakePresets, preset_file: Path, cppython_pres # Handle includes if not root_preset.include: - root_preset.include = [] + root_preset.include = [] # type: ignore[misc] if str(relative_preset) not in root_preset.include: root_preset.include.append(str(relative_preset)) From 0202fe667d90fb6f17e586330293a87efac282a9 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 15:00:27 -0400 Subject: [PATCH 35/68] Update lib Name --- tests/unit/plugins/conan/test_resolution.py | 34 ++++++++++----------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/unit/plugins/conan/test_resolution.py b/tests/unit/plugins/conan/test_resolution.py index 891042d..7f5889e 100644 --- a/tests/unit/plugins/conan/test_resolution.py +++ b/tests/unit/plugins/conan/test_resolution.py @@ -1,8 +1,5 @@ """Unit tests for Conan resolution functionality.""" -import logging -from unittest.mock import Mock, patch - import pytest from packaging.requirements import Requirement @@ -17,7 +14,6 @@ ConanVersion, ConanVersionRange, ) -from cppython.utility.exception import ProviderConfigurationError # Constants for test validation EXPECTED_PROFILE_CALL_COUNT = 2 @@ -130,46 +126,46 @@ def test_requires_no_version(self) -> None: def test_with_user_channel(self) -> None: """Test that ConanDependency handles user/channel correctly.""" dependency = ConanDependency( - name='mylib', + name='example', version=ConanVersion.from_string('1.0.0'), user_channel=ConanUserChannel(user='myuser', channel='stable'), ) - assert dependency.requires() == 'mylib/1.0.0@myuser/stable' + assert dependency.requires() == 'example/1.0.0@myuser/stable' def test_with_revision(self) -> None: """Test that ConanDependency handles revisions correctly.""" dependency = ConanDependency( - name='mylib', version=ConanVersion.from_string('1.0.0'), revision=ConanRevision(revision='abc123') + name='example', version=ConanVersion.from_string('1.0.0'), revision=ConanRevision(revision='abc123') ) - assert dependency.requires() == 'mylib/1.0.0#abc123' + assert dependency.requires() == 'example/1.0.0#abc123' def test_full_reference(self) -> None: """Test that ConanDependency handles full references correctly.""" dependency = ConanDependency( - name='mylib', + name='example', version=ConanVersion.from_string('1.0.0'), user_channel=ConanUserChannel(user='myuser', channel='stable'), revision=ConanRevision(revision='abc123'), ) - assert dependency.requires() == 'mylib/1.0.0@myuser/stable#abc123' + assert dependency.requires() == 'example/1.0.0@myuser/stable#abc123' def test_from_reference_simple(self) -> None: """Test parsing a simple package name.""" - dependency = ConanDependency.from_conan_reference('mylib') + dependency = ConanDependency.from_conan_reference('example') - assert dependency.name == 'mylib' + assert dependency.name == 'example' assert dependency.version is None assert dependency.user_channel is None assert dependency.revision is None def test_from_reference_with_version(self) -> None: """Test parsing a package with version.""" - dependency = ConanDependency.from_conan_reference('mylib/1.0.0') + dependency = ConanDependency.from_conan_reference('example/1.0.0') - assert dependency.name == 'mylib' + assert dependency.name == 'example' assert dependency.version is not None assert str(dependency.version) == '1.0.0' assert dependency.user_channel is None @@ -177,9 +173,9 @@ def test_from_reference_with_version(self) -> None: def test_from_reference_with_version_range(self) -> None: """Test parsing a package with version range.""" - dependency = ConanDependency.from_conan_reference('mylib/[>=1.0 <2.0]') + dependency = ConanDependency.from_conan_reference('example/[>=1.0 <2.0]') - assert dependency.name == 'mylib' + assert dependency.name == 'example' assert dependency.version is None assert dependency.version_range is not None assert dependency.version_range.expression == '>=1.0 <2.0' @@ -188,9 +184,9 @@ def test_from_reference_with_version_range(self) -> None: def test_from_reference_full(self) -> None: """Test parsing a full Conan reference.""" - dependency = ConanDependency.from_conan_reference('mylib/1.0.0@myuser/stable#abc123') + dependency = ConanDependency.from_conan_reference('example/1.0.0@myuser/stable#abc123') - assert dependency.name == 'mylib' + assert dependency.name == 'example' assert dependency.version is not None assert str(dependency.version) == '1.0.0' assert dependency.user_channel is not None @@ -199,8 +195,10 @@ def test_from_reference_full(self) -> None: assert dependency.revision is not None assert dependency.revision.revision == 'abc123' + class TestResolveProfiles: """Test profile resolution functionality.""" + class TestResolveConanData: """Test Conan data resolution.""" From 39620fb1b2444edba644e3a441159e1b1986b678 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 16:53:35 -0400 Subject: [PATCH 36/68] Add Log Colouring --- cppython/builder.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cppython/builder.py b/cppython/builder.py index 25fd451..efc07ba 100644 --- a/cppython/builder.py +++ b/cppython/builder.py @@ -1,11 +1,15 @@ """Defines the data and routines for building a CPPython project type""" import logging +import os from importlib.metadata import entry_points from inspect import getmodule from logging import Logger from typing import Any, cast +from rich.console import Console +from rich.logging import RichHandler + from cppython.core.plugin_schema.generator import Generator from cppython.core.plugin_schema.provider import Provider from cppython.core.plugin_schema.scm import SCM, SupportedSCMFeatures @@ -471,8 +475,28 @@ def __init__(self, project_configuration: ProjectConfiguration, logger: Logger) self._project_configuration = project_configuration self._logger = logger - # Add default output stream - self._logger.addHandler(logging.StreamHandler()) + # Informal standard to check for color + force_color = os.getenv('FORCE_COLOR', '1') != '0' + + console = Console( + force_terminal=force_color, + color_system='auto', + width=120, + legacy_windows=False, + no_color=False, + ) + + rich_handler = RichHandler( + console=console, + rich_tracebacks=True, + show_time=False, + show_path=False, + markup=True, + show_level=False, + enable_link_path=False, + ) + + self._logger.addHandler(rich_handler) self._logger.setLevel(Builder.levels[project_configuration.verbosity]) self._logger.info('Logging setup complete') From ca5e4eb148c3d2c615a76f93c00849a2df60e899 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 16:54:43 -0400 Subject: [PATCH 37/68] Remove Profile Handling --- cppython/plugins/conan/plugin.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index dfe49d7..d104e06 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -128,10 +128,6 @@ def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None # Add build missing flag command_args.extend(['--build', 'missing']) - # Add profiles if specified - command_args.extend(['--profile:host', 'default']) - command_args.extend(['--profile:build', 'default']) - # Add update flag if needed if update: command_args.append('--update') @@ -227,10 +223,6 @@ def publish(self) -> None: # Add build mode (build everything for publishing) command_args.extend(['--build', 'missing']) - # Add profiles - command_args.extend(['--profile:host', 'default']) - command_args.extend(['--profile:build', 'default']) - # Log the command being executed logger.info('Executing conan create command: conan %s', ' '.join(command_args)) From 2411ee74f362b139b01a4f8fa54ff07dd9d1f4d1 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 2 Aug 2025 21:32:15 -0400 Subject: [PATCH 38/68] Profile Default Fallback --- cppython/plugins/conan/builder.py | 2 +- cppython/plugins/conan/plugin.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 17c178d..e8512ed 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -161,7 +161,7 @@ def package(self): 'name': name, 'version': version, 'dependencies': [dependency.requires() for dependency in dependencies], - 'preset_file': str(preset_file), + 'preset_file': preset_file.as_posix(), # Cross-platform path with forward slashes } result = template.substitute(values) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index d104e06..4f5590b 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -43,6 +43,8 @@ def __init__( self._cli = Cli(self._conan_api) self._cli.add_commands() + self._ensure_default_profiles() + @staticmethod def features(directory: Path) -> SupportedFeatures: """Queries conan support @@ -114,6 +116,15 @@ def _prepare_installation(self) -> Path: return conanfile_path + def _ensure_default_profiles(self) -> None: + """Ensure default Conan profiles exist, creating them if necessary.""" + try: + self._conan_api.profiles.get_default_host() + self._conan_api.profiles.get_default_build() + except Exception: + # If profiles don't exist, create them using profile detect + self._conan_api.command.run(['profile', 'detect']) + def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None: """Run conan install command using Conan API. From a71454437c3726321ad2afa0f2024322fb4f82f2 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 5 Aug 2025 04:11:16 -0400 Subject: [PATCH 39/68] Presets Instead of Details --- cppython/plugins/cmake/builder.py | 167 +++++++++--------------------- cppython/plugins/cmake/plugin.py | 6 +- cppython/plugins/cmake/schema.py | 18 ++-- cppython/plugins/conan/plugin.py | 23 ++-- cppython/plugins/vcpkg/plugin.py | 79 +++++++++++++- 5 files changed, 154 insertions(+), 139 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 2ee7e5e..ab0fdd3 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -4,7 +4,6 @@ from cppython.plugins.cmake.schema import ( BuildPreset, - CacheVariable, CMakeData, CMakePresets, CMakeSyncData, @@ -18,136 +17,70 @@ class Builder: def __init__(self) -> None: """Initialize the builder""" - @staticmethod - def generate_provider_preset(provider_data: CMakeSyncData) -> CMakePresets: - """Generates a provider preset from input sync data - - Args: - provider_data: The providers synchronization data - """ - # Base hidden preset with common configuration - base_preset = ConfigurePreset(name=f'{provider_data.provider_name}-base', hidden=True) - - # Handle both top_level_includes and toolchain options - cache_variables: dict[str, str | bool | CacheVariable | None] = {} - - if provider_data.top_level_includes: - cache_variables['CMAKE_PROJECT_TOP_LEVEL_INCLUDES'] = str(provider_data.top_level_includes.as_posix()) - - if provider_data.toolchain: - # Use the toolchainFile field for better integration - base_preset.toolchainFile = provider_data.toolchain.as_posix() - - if cache_variables: - base_preset.cacheVariables = cache_variables - - # Create specific configuration presets - default_preset = ConfigurePreset( - name=f'{provider_data.provider_name}-default', hidden=True, inherits=f'{provider_data.provider_name}-base' - ) - - release_preset = ConfigurePreset( - name=f'{provider_data.provider_name}-release', - hidden=True, - inherits=f'{provider_data.provider_name}-base', - cacheVariables={'CMAKE_BUILD_TYPE': 'Release'}, - ) - - debug_preset = ConfigurePreset( - name=f'{provider_data.provider_name}-debug', - hidden=True, - inherits=f'{provider_data.provider_name}-base', - cacheVariables={'CMAKE_BUILD_TYPE': 'Debug'}, - ) - - return CMakePresets(configurePresets=[base_preset, default_preset, release_preset, debug_preset]) - - @staticmethod - def write_provider_preset(provider_directory: Path, provider_data: CMakeSyncData) -> None: - """Writes a provider preset from input sync data - - Args: - provider_directory: The base directory to place the preset files - provider_data: The providers synchronization data - """ - generated_preset = Builder.generate_provider_preset(provider_data) - - 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 generate_cppython_preset( - cppython_preset_directory: Path, provider_directory: Path, provider_data: CMakeSyncData + cppython_preset_directory: Path, provider_preset_file: Path, provider_data: CMakeSyncData ) -> CMakePresets: """Generates the cppython preset which inherits from the provider presets Args: cppython_preset_directory: The tool directory - provider_directory: The base directory containing provider presets + provider_preset_file: Path to the provider's preset file provider_data: The provider's synchronization data Returns: A CMakePresets object """ - # Configure presets similar to Conan structure - default_configure = ConfigurePreset( - name='default', inherits=f'{provider_data.provider_name}-default', hidden=False - ) - - release_configure = ConfigurePreset( - name='release', inherits=f'{provider_data.provider_name}-release', hidden=False - ) - - debug_configure = ConfigurePreset(name='debug', inherits=f'{provider_data.provider_name}-debug', hidden=False) - - # Build presets for multi-config and single-config generators - multi_release_build = BuildPreset( - name='multi-release', - configurePreset='default', - configuration='Release', - inherits=f'{provider_data.provider_name}-release', - ) - - multi_debug_build = BuildPreset( - name='multi-debug', - configurePreset='default', - configuration='Debug', - inherits=f'{provider_data.provider_name}-debug', - ) - - release_build = BuildPreset( - name='release', - configurePreset='release', - configuration='Release', - inherits=f'{provider_data.provider_name}-release', - ) - - debug_build = BuildPreset( - name='debug', - configurePreset='debug', - configuration='Debug', - inherits=f'{provider_data.provider_name}-debug', - ) + configure_presets = [] + + preset_name = 'cppython-default' + parent_preset_name = f'{provider_data.provider_name}-default' + + # Create a default preset that inherits from provider's default preset + default_configure = ConfigurePreset(name=preset_name, inherits=parent_preset_name, hidden=True) + configure_presets.append(default_configure) + + # Create presets for each configuration + for config in provider_data.configurations: + config_name = config.lower() + preset_name = f'cppython-{config_name}' + parent_preset_name = f'{provider_data.provider_name}-{config_name}' + preset = ConfigurePreset(name=preset_name, inherits=parent_preset_name, hidden=True) + configure_presets.append(preset) + + build_presets = [] + + # Multi-config build presets using the default configure preset. + # Important: Do not use a configure preset here, the user will do that in their own presets. + for config in provider_data.configurations: + config_name = config.lower() + preset_name = f'cppython-multi-{config_name}' + parent_preset_name = f'{provider_data.provider_name}-multi-{config_name}' + multi_build_preset = BuildPreset( + name=preset_name, + configuration=config, + inherits=parent_preset_name, + ) + build_presets.append(multi_build_preset) + + # Single-config build presets using the config-specific configure presets + # Important: Do not use a configure preset here, the user will do that in their own presets. + for config in provider_data.configurations: + config_name = config.lower() + parent_config_name = f'{provider_data.provider_name}-{config_name}' + single_build_preset = BuildPreset( + name=config_name, + configuration=config, + inherits=parent_config_name, + ) + build_presets.append(single_build_preset) generated_preset = CMakePresets( - configurePresets=[default_configure, release_configure, debug_configure], - buildPresets=[multi_release_build, multi_debug_build, release_build, debug_build], + configurePresets=configure_presets, + buildPresets=build_presets, ) # 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() # Set the data @@ -156,20 +89,20 @@ def generate_cppython_preset( @staticmethod def write_cppython_preset( - cppython_preset_directory: Path, provider_directory: Path, provider_data: CMakeSyncData + cppython_preset_directory: Path, provider_preset_file: Path, provider_data: CMakeSyncData ) -> Path: """Write the cppython presets which inherit from the provider presets Args: cppython_preset_directory: The tool directory - provider_directory: The base directory containing provider presets + provider_preset_file: Path to the provider's preset file provider_data: The provider's synchronization data Returns: A file path to the written data """ generated_preset = Builder.generate_cppython_preset( - cppython_preset_directory, provider_directory, provider_data + cppython_preset_directory, provider_preset_file, provider_data ) cppython_preset_file = cppython_preset_directory / 'cppython.json' diff --git a/cppython/plugins/cmake/plugin.py b/cppython/plugins/cmake/plugin.py index 052ef7a..495ddd9 100644 --- a/cppython/plugins/cmake/plugin.py +++ b/cppython/plugins/cmake/plugin.py @@ -63,12 +63,10 @@ def sync(self, sync_data: SyncData) -> None: match sync_data: case CMakeSyncData(): self._cppython_preset_directory.mkdir(parents=True, exist_ok=True) - self._provider_directory.mkdir(parents=True, exist_ok=True) - - self.builder.write_provider_preset(self._provider_directory, sync_data) + # Provider now provides the preset file path, we don't generate it cppython_preset_file = self.builder.write_cppython_preset( - self._cppython_preset_directory, self._provider_directory, sync_data + self._cppython_preset_directory, sync_data.preset_file, sync_data ) self.builder.write_root_presets( diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index 8f746a0..06b8623 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -100,13 +100,17 @@ class CMakePresets(CPPythonModel, extra='allow'): class CMakeSyncData(SyncData): """The CMake sync data""" - top_level_includes: FilePath | None = None - toolchain: Path | None = None # We don't resolve the toolchain until after we set it - - def model_post_init(self, __context) -> None: - """Validate that at least one of top_level_includes or toolchain is provided.""" - if not self.top_level_includes and not self.toolchain: - raise ValueError("Either 'top_level_includes' or 'toolchain' must be provided") + preset_file: Annotated[FilePath, Field(description='Path to the CMakePresets.json file generated by the provider')] + configurations: Annotated[ + list[str], + Field( + description='List of configurations to generate presets for. Preset names are generated via ' + 'the `provider_name` field with an appended suffix.' + ), + ] = [ + 'Release', + 'Debug', + ] class CMakeData(CPPythonModel): diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 4f5590b..ba563e7 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -198,16 +198,25 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: """ for sync_type in consumer.sync_types(): if sync_type == CMakeSyncData: - # Use the CMakeToolchain file directly as the toolchain - toolchain_path = self.core_data.cppython_data.build_path / 'generators' / 'conan_toolchain.cmake' - - return CMakeSyncData( - provider_name=TypeName('conan'), - toolchain=toolchain_path, - ) + return self._create_cmake_sync_data() raise NotSupportedError(f'Unsupported sync types: {consumer.sync_types()}') + def _create_cmake_sync_data(self) -> CMakeSyncData: + """Creates CMake synchronization data with Conan toolchain configuration. + + Returns: + CMakeSyncData configured for Conan integration + """ + # Conan's CMakeToolchain generator automatically creates preset files + # The preset file will be created by Conan in the build directory + conan_preset_path = self.core_data.cppython_data.build_path / 'CMakePresets.json' + + return CMakeSyncData( + provider_name=TypeName('conan'), + preset_file=conan_preset_path, + ) + @classmethod async def download_tooling(cls, directory: Path) -> None: """Download external tooling required by the Conan provider. diff --git a/cppython/plugins/vcpkg/plugin.py b/cppython/plugins/vcpkg/plugin.py index 0652220..dc73ceb 100644 --- a/cppython/plugins/vcpkg/plugin.py +++ b/cppython/plugins/vcpkg/plugin.py @@ -1,5 +1,6 @@ """The vcpkg provider implementation""" +import json import subprocess from logging import getLogger from os import name as system_name @@ -110,13 +111,83 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData: """ for sync_type in consumer.sync_types(): if sync_type == CMakeSyncData: - return CMakeSyncData( - provider_name=TypeName('vcpkg'), - top_level_includes=self.core_data.cppython_data.install_path / 'scripts/buildsystems/vcpkg.cmake', - ) + return self._create_cmake_sync_data() raise NotSupportedError('OOF') + def _create_cmake_sync_data(self) -> CMakeSyncData: + """Creates CMake synchronization data with vcpkg configuration. + + Returns: + CMakeSyncData configured for vcpkg integration + """ + # Providers are now responsible for generating their own preset file + # Create provider sync data with vcpkg configuration + provider_preset_path = self.core_data.cppython_data.tool_path / 'providers' / 'vcpkg.json' + provider_preset_path.parent.mkdir(parents=True, exist_ok=True) + + # Create CMakeSyncData with vcpkg configuration + vcpkg_cmake_path = self.core_data.cppython_data.install_path / 'scripts/buildsystems/vcpkg.cmake' + + # Create a custom provider preset with vcpkg configuration + provider_preset = { + 'version': 9, + 'configurePresets': [ + { + 'name': 'vcpkg-base', + 'hidden': True, + 'cacheVariables': {'CMAKE_PROJECT_TOP_LEVEL_INCLUDES': str(vcpkg_cmake_path.as_posix())}, + }, + { + 'name': 'vcpkg-release', + 'hidden': True, + 'inherits': 'vcpkg-base', + 'cacheVariables': {'CMAKE_BUILD_TYPE': 'Release'}, + }, + { + 'name': 'vcpkg-debug', + 'hidden': True, + 'inherits': 'vcpkg-base', + 'cacheVariables': {'CMAKE_BUILD_TYPE': 'Debug'}, + }, + ], + 'buildPresets': [ + { + 'name': 'vcpkg-multi-release', + 'configurePreset': 'vcpkg-base', + 'configuration': 'Release', + 'hidden': True, + }, + { + 'name': 'vcpkg-multi-debug', + 'configurePreset': 'vcpkg-base', + 'configuration': 'Debug', + 'hidden': True, + }, + { + 'name': 'vcpkg-release', + 'configurePreset': 'vcpkg-release', + 'configuration': 'Release', + 'hidden': True, + }, + { + 'name': 'vcpkg-debug', + 'configurePreset': 'vcpkg-debug', + 'configuration': 'Debug', + 'hidden': True, + }, + ], + } + + # Write the preset file + with open(provider_preset_path, 'w', encoding='utf-8') as f: + json.dump(provider_preset, f, indent=4) + + return CMakeSyncData( + provider_name=TypeName('vcpkg'), + preset_file=provider_preset_path, + ) + @classmethod def tooling_downloaded(cls, path: Path) -> bool: """Returns whether the provider tooling needs to be downloaded From 3ea5fa6f916c79365173d5302b830924df5d11ff Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 5 Aug 2025 04:18:47 -0400 Subject: [PATCH 40/68] Update CMake Preset Tests --- tests/unit/plugins/cmake/test_presets.py | 92 +++++++++++++++--------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/tests/unit/plugins/cmake/test_presets.py b/tests/unit/plugins/cmake/test_presets.py index 98f113a..e0dd6bc 100644 --- a/tests/unit/plugins/cmake/test_presets.py +++ b/tests/unit/plugins/cmake/test_presets.py @@ -1,5 +1,6 @@ """Tests for CMakePresets""" +import json from pathlib import Path from cppython.core.schema import ProjectData @@ -53,22 +54,6 @@ def test_generate_root_preset_existing(project_data: ProjectData) -> None: class TestWrites: """Tests for writing the CMakePresets class""" - @staticmethod - def test_provider_write(tmp_path: Path) -> None: - """Verifies that the provider preset writing works as intended - - Args: - tmp_path: The input path the use - """ - builder = Builder() - - includes_file = tmp_path / 'includes.cmake' - with includes_file.open('w', encoding='utf-8') as file: - file.write('example contents') - - data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file) - builder.write_provider_preset(tmp_path, data) - @staticmethod def test_cppython_write(tmp_path: Path) -> None: """Verifies that the cppython preset writing works as intended @@ -78,17 +63,48 @@ def test_cppython_write(tmp_path: Path) -> None: """ builder = Builder() - provider_directory = tmp_path / 'providers' - provider_directory.mkdir(parents=True, exist_ok=True) - - includes_file = provider_directory / 'includes.cmake' - with includes_file.open('w', encoding='utf-8') as file: - file.write('example contents') - - data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file) - builder.write_provider_preset(provider_directory, data) - - builder.write_cppython_preset(tmp_path, provider_directory, data) + # Create a mock provider preset file + provider_preset_file = tmp_path / 'provider.json' + provider_preset = { + 'version': 9, + 'configurePresets': [ + {'name': 'test-provider-base', 'hidden': True}, + {'name': 'test-provider-release', 'hidden': True, 'inherits': 'test-provider-base'}, + {'name': 'test-provider-debug', 'hidden': True, 'inherits': 'test-provider-base'}, + ], + 'buildPresets': [ + { + 'name': 'test-provider-multi-release', + 'configurePreset': 'test-provider-base', + 'configuration': 'Release', + 'hidden': True, + }, + { + 'name': 'test-provider-multi-debug', + 'configurePreset': 'test-provider-base', + 'configuration': 'Debug', + 'hidden': True, + }, + { + 'name': 'test-provider-release', + 'configurePreset': 'test-provider-release', + 'configuration': 'Release', + 'hidden': True, + }, + { + 'name': 'test-provider-debug', + 'configurePreset': 'test-provider-debug', + 'configuration': 'Debug', + 'hidden': True, + }, + ], + } + + with provider_preset_file.open('w', encoding='utf-8') as file: + json.dump(provider_preset, file, indent=4) + + data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) + builder.write_cppython_preset(tmp_path, provider_preset_file, data) @staticmethod def test_root_write(project_data: ProjectData) -> None: @@ -116,10 +132,15 @@ def test_root_write(project_data: ProjectData) -> None: with open(root_file, 'w', encoding='utf8') as file: file.write(serialized) - data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file) - builder.write_provider_preset(provider_directory, data) + # Create a mock provider preset file + provider_preset_file = provider_directory / 'CMakePresets.json' + provider_preset_data = {'version': 3, 'configurePresets': [{'name': 'test-provider-base', 'hidden': True}]} + with provider_preset_file.open('w') as f: + json.dump(provider_preset_data, f) - cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_directory, data) + data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) + + cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_preset_file, data) build_directory = project_data.project_root / 'build' builder.write_root_presets( @@ -157,10 +178,15 @@ def test_relative_root_write(project_data: ProjectData) -> None: with open(root_file, 'w', encoding='utf8') as file: file.write(serialized) - data = CMakeSyncData(provider_name=TypeName('test-provider'), top_level_includes=includes_file) - builder.write_provider_preset(provider_directory, data) + # Create a mock provider preset file + provider_preset_file = provider_directory / 'CMakePresets.json' + provider_preset_data = {'version': 3, 'configurePresets': [{'name': 'test-provider-base', 'hidden': True}]} + with provider_preset_file.open('w') as f: + json.dump(provider_preset_data, f) + + data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) - cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_directory, data) + cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_preset_file, data) build_directory = project_data.project_root / 'build' builder.write_root_presets( From f162e7e5f5ab35915dfaf5248a50c10598b3351d Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 5 Aug 2025 05:35:56 -0400 Subject: [PATCH 41/68] Update --- cppython/plugins/cmake/schema.py | 3 +-- cppython/plugins/conan/plugin.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index 06b8623..71a5a87 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -10,7 +10,6 @@ from typing import Annotated from pydantic import Field -from pydantic.types import FilePath from cppython.core.schema import CPPythonModel, SyncData @@ -100,7 +99,7 @@ class CMakePresets(CPPythonModel, extra='allow'): class CMakeSyncData(SyncData): """The CMake sync data""" - preset_file: Annotated[FilePath, Field(description='Path to the CMakePresets.json file generated by the provider')] + preset_file: Annotated[Path, Field(description='Path to the CMakePresets.json file generated by the provider.')] configurations: Annotated[ list[str], Field( diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index ba563e7..3ed1107 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -208,9 +208,9 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: Returns: CMakeSyncData configured for Conan integration """ - # Conan's CMakeToolchain generator automatically creates preset files - # The preset file will be created by Conan in the build directory - conan_preset_path = self.core_data.cppython_data.build_path / 'CMakePresets.json' + # Conan's CMakeToolchain generator creates preset files at the configured user_presets_path + # This should match the path configured in the conanfile template + conan_preset_path = self.core_data.cppython_data.tool_path / 'ConanPresets.json' return CMakeSyncData( provider_name=TypeName('conan'), From e213be092b76b7e904e9028331ea6f9c4671637c Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 5 Aug 2025 06:03:30 -0400 Subject: [PATCH 42/68] Update plugin.py --- cppython/plugins/conan/plugin.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 3ed1107..0343608 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -6,7 +6,7 @@ """ import os -from logging import getLogger +from logging import Logger, getLogger from pathlib import Path from typing import Any @@ -83,8 +83,10 @@ def _install_dependencies(self, *, update: bool = False) -> None: raise ProviderInstallationError('conan', f'Failed to prepare {operation} environment: {e}', e) from e try: - # Install dependencies using conan install command - self._run_conan_install(conanfile_path, update, logger) + build_types = ['Release', 'Debug'] + for build_type in build_types: + logger.info('Installing dependencies for build type: %s', build_type) + self._run_conan_install(conanfile_path, update, logger, build_type) except Exception as e: raise ProviderInstallationError('conan', f'Failed to install dependencies: {e}', e) from e @@ -125,12 +127,13 @@ def _ensure_default_profiles(self) -> None: # If profiles don't exist, create them using profile detect self._conan_api.command.run(['profile', 'detect']) - def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None: - """Run conan install command using Conan API. + def _run_conan_install(self, conanfile_path: Path, update: bool, build_type: str, logger: Logger) -> None: + """Run conan install command using Conan API with optional build type. Args: conanfile_path: Path to the conanfile.py update: Whether to check for updates + build_type: Build type (Release, Debug, etc.) or None for default logger: Logger instance """ # Build conan install command arguments @@ -143,6 +146,10 @@ def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None if update: command_args.append('--update') + # Add build type setting if specified + if build_type: + command_args.extend(['-s', f'build_type={build_type}']) + # Add output folder build_path = self.core_data.cppython_data.build_path command_args.extend(['--output-folder', str(build_path)]) From b14c5be998ec97220025c50c4479689d41757a2c Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 12 Aug 2025 11:31:57 -0700 Subject: [PATCH 43/68] Fix Parameter --- cppython/plugins/conan/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 0343608..932b3ae 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -86,7 +86,7 @@ def _install_dependencies(self, *, update: bool = False) -> None: build_types = ['Release', 'Debug'] for build_type in build_types: logger.info('Installing dependencies for build type: %s', build_type) - self._run_conan_install(conanfile_path, update, logger, build_type) + self._run_conan_install(conanfile_path, update, build_type, logger) except Exception as e: raise ProviderInstallationError('conan', f'Failed to install dependencies: {e}', e) from e From 8956551f9508b5f8a136063944e6d8f7b2e253b8 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 12 Aug 2025 11:33:26 -0700 Subject: [PATCH 44/68] Skip Conan Tests --- tests/integration/examples/test_conan_cmake.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index 8b5634d..dcb3d23 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -8,6 +8,7 @@ from pathlib import Path from tomllib import loads +import pytest from typer.testing import CliRunner from cppython.console.schema import ConsoleInterface @@ -17,6 +18,7 @@ pytest_plugins = ['tests.fixtures.example', 'tests.fixtures.conan'] +@pytest.mark.skip(reason='TODO') class TestConanCMake: """Test project variation of conan and CMake""" From 23a068e9bfcc1186fb23a5a45cff43833c2ca6f1 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Tue, 12 Aug 2025 12:37:33 -0700 Subject: [PATCH 45/68] Update test_vcpkg_cmake.py --- tests/integration/examples/test_vcpkg_cmake.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/examples/test_vcpkg_cmake.py b/tests/integration/examples/test_vcpkg_cmake.py index f7486a3..a1a3ad7 100644 --- a/tests/integration/examples/test_vcpkg_cmake.py +++ b/tests/integration/examples/test_vcpkg_cmake.py @@ -7,7 +7,6 @@ import subprocess from pathlib import Path -import pytest from typer.testing import CliRunner pytest_plugins = ['tests.fixtures.example'] @@ -17,7 +16,6 @@ class TestVcpkgCMake: """Test project variation of vcpkg and CMake""" @staticmethod - @pytest.mark.skip(reason='TODO') def test_simple(example_runner: CliRunner) -> None: """Simple project""" # By nature of running the test, we require PDM to develop the project and so it will be installed From 47e5b943b0bf42ef005145e00bad37a50eb28ad1 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Thu, 14 Aug 2025 14:07:33 -0700 Subject: [PATCH 46/68] Remove Preset Generation --- cppython/plugins/vcpkg/plugin.py | 60 -------------------------------- 1 file changed, 60 deletions(-) diff --git a/cppython/plugins/vcpkg/plugin.py b/cppython/plugins/vcpkg/plugin.py index dc73ceb..f7e79cb 100644 --- a/cppython/plugins/vcpkg/plugin.py +++ b/cppython/plugins/vcpkg/plugin.py @@ -1,6 +1,5 @@ """The vcpkg provider implementation""" -import json import subprocess from logging import getLogger from os import name as system_name @@ -121,68 +120,9 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: Returns: CMakeSyncData configured for vcpkg integration """ - # Providers are now responsible for generating their own preset file - # Create provider sync data with vcpkg configuration - provider_preset_path = self.core_data.cppython_data.tool_path / 'providers' / 'vcpkg.json' - provider_preset_path.parent.mkdir(parents=True, exist_ok=True) - # Create CMakeSyncData with vcpkg configuration vcpkg_cmake_path = self.core_data.cppython_data.install_path / 'scripts/buildsystems/vcpkg.cmake' - # Create a custom provider preset with vcpkg configuration - provider_preset = { - 'version': 9, - 'configurePresets': [ - { - 'name': 'vcpkg-base', - 'hidden': True, - 'cacheVariables': {'CMAKE_PROJECT_TOP_LEVEL_INCLUDES': str(vcpkg_cmake_path.as_posix())}, - }, - { - 'name': 'vcpkg-release', - 'hidden': True, - 'inherits': 'vcpkg-base', - 'cacheVariables': {'CMAKE_BUILD_TYPE': 'Release'}, - }, - { - 'name': 'vcpkg-debug', - 'hidden': True, - 'inherits': 'vcpkg-base', - 'cacheVariables': {'CMAKE_BUILD_TYPE': 'Debug'}, - }, - ], - 'buildPresets': [ - { - 'name': 'vcpkg-multi-release', - 'configurePreset': 'vcpkg-base', - 'configuration': 'Release', - 'hidden': True, - }, - { - 'name': 'vcpkg-multi-debug', - 'configurePreset': 'vcpkg-base', - 'configuration': 'Debug', - 'hidden': True, - }, - { - 'name': 'vcpkg-release', - 'configurePreset': 'vcpkg-release', - 'configuration': 'Release', - 'hidden': True, - }, - { - 'name': 'vcpkg-debug', - 'configurePreset': 'vcpkg-debug', - 'configuration': 'Debug', - 'hidden': True, - }, - ], - } - - # Write the preset file - with open(provider_preset_path, 'w', encoding='utf-8') as f: - json.dump(provider_preset, f, indent=4) - return CMakeSyncData( provider_name=TypeName('vcpkg'), preset_file=provider_preset_path, From bd73f82bd9de01168cd48655e6610e315b672c65 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Thu, 14 Aug 2025 14:39:26 -0700 Subject: [PATCH 47/68] once again change the cmake sync format sad --- cppython/plugins/cmake/schema.py | 11 +---------- cppython/plugins/vcpkg/plugin.py | 2 +- tests/integration/examples/test_conan_cmake.py | 2 -- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index 71a5a87..75c5c9d 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -100,16 +100,7 @@ class CMakeSyncData(SyncData): """The CMake sync data""" preset_file: Annotated[Path, Field(description='Path to the CMakePresets.json file generated by the provider.')] - configurations: Annotated[ - list[str], - Field( - description='List of configurations to generate presets for. Preset names are generated via ' - 'the `provider_name` field with an appended suffix.' - ), - ] = [ - 'Release', - 'Debug', - ] + toolchain: Path | None = None class CMakeData(CPPythonModel): diff --git a/cppython/plugins/vcpkg/plugin.py b/cppython/plugins/vcpkg/plugin.py index f7e79cb..c6ca36f 100644 --- a/cppython/plugins/vcpkg/plugin.py +++ b/cppython/plugins/vcpkg/plugin.py @@ -125,7 +125,7 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: return CMakeSyncData( provider_name=TypeName('vcpkg'), - preset_file=provider_preset_path, + toolchain=vcpkg_cmake_path, ) @classmethod diff --git a/tests/integration/examples/test_conan_cmake.py b/tests/integration/examples/test_conan_cmake.py index dcb3d23..8b5634d 100644 --- a/tests/integration/examples/test_conan_cmake.py +++ b/tests/integration/examples/test_conan_cmake.py @@ -8,7 +8,6 @@ from pathlib import Path from tomllib import loads -import pytest from typer.testing import CliRunner from cppython.console.schema import ConsoleInterface @@ -18,7 +17,6 @@ pytest_plugins = ['tests.fixtures.example', 'tests.fixtures.conan'] -@pytest.mark.skip(reason='TODO') class TestConanCMake: """Test project variation of conan and CMake""" From d03edb688eef4355e22372cf475601d350706f88 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:07:26 -0400 Subject: [PATCH 48/68] Remove Preset Injection --- cppython/plugins/conan/builder.py | 11 ++++------- cppython/plugins/conan/plugin.py | 5 ++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index e8512ed..0e8905b 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -121,9 +121,7 @@ def __init__(self) -> None: self._filename = 'conanfile.py' @staticmethod - def _create_conanfile( - conan_file: Path, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path - ) -> None: + def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], name: str, version: str) -> None: """Creates a conanfile.py file with the necessary content.""" template_string = """ from conan import ConanFile @@ -142,7 +140,7 @@ def generate(self): deps = CMakeDeps(self) deps.generate() tc = CMakeToolchain(self) - tc.user_presets_path = "${preset_file}" + tc.user_presets_path = None tc.generate() def build(self): @@ -161,7 +159,6 @@ def package(self): 'name': name, 'version': version, 'dependencies': [dependency.requires() for dependency in dependencies], - 'preset_file': preset_file.as_posix(), # Cross-platform path with forward slashes } result = template.substitute(values) @@ -170,7 +167,7 @@ def package(self): file.write(result) def generate_conanfile( - self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path + self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str ) -> None: """Generate a conanfile.py file for the project.""" conan_file = directory / self._filename @@ -185,4 +182,4 @@ def generate_conanfile( conan_file.write_text(modified.code, encoding='utf-8') else: directory.mkdir(parents=True, exist_ok=True) - self._create_conanfile(conan_file, dependencies, name, version, preset_file) + self._create_conanfile(conan_file, dependencies, name, version) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 932b3ae..0400b5c 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -103,7 +103,6 @@ def _prepare_installation(self) -> Path: resolved_dependencies, self.core_data.pep621_data.name, self.core_data.pep621_data.version, - self.core_data.cppython_data.tool_path / 'ConanPresets.json', ) # Ensure build directory exists @@ -217,11 +216,11 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: """ # Conan's CMakeToolchain generator creates preset files at the configured user_presets_path # This should match the path configured in the conanfile template - conan_preset_path = self.core_data.cppython_data.tool_path / 'ConanPresets.json' + conan_toolchain_path = self.core_data.cppython_data.tool_path / 'ConanPresets.json' return CMakeSyncData( provider_name=TypeName('conan'), - preset_file=conan_preset_path, + toolchain=conan_toolchain_path, ) @classmethod From 30599c9a47d8d877122eff1e18738db9f52d25ca Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:12:41 -0400 Subject: [PATCH 49/68] Update Chore --- pdm.lock | 199 +++++++++++++++++++++++++------------------------ pyproject.toml | 12 +-- 2 files changed, 108 insertions(+), 103 deletions(-) diff --git a/pdm.lock b/pdm.lock index a23eb6b..6bf58ff 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "cmake", "conan", "git", "lint", "pdm", "pytest", "release", "test"] strategy = [] lock_version = "4.5.0" -content_hash = "sha256:7790fe7a9becc9139aeb447c8ba496f2d6480d44f30f6151fb15d9dec5d90246" +content_hash = "sha256:492bd92e62664c52af0dd980a4fb4adca223d46f544bcc4774cada07a524efeb" [[metadata.targets]] requires_python = ">=3.13" @@ -86,29 +86,29 @@ files = [ [[package]] name = "cmake" -version = "4.0.3" -requires_python = ">=3.7" -summary = "" -files = [ - {file = "cmake-4.0.3-py3-none-macosx_10_10_universal2.whl", hash = "sha256:f2adfb459747025f40f9d3bdd1f3a485d43e866c0c4eb66373d1fcd666b13e4a"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:04c40c92fdcaa96c66a5731b5b3fbbdf87da99cc68fdd30ff30b90c34d222986"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d41b83d061bcc375a7a5f2942ba523a7563368d296d91260f9d8a53a10f5e5e5"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:434f84fdf1e21578974876b8414dc47afeaea62027d9adc37a943a6bb08eb053"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beec48371a4b906fe398758ded5df57fc16e9bb14fd34244d9d66ee35862fb9f"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47dc28bee6cfb4de00c7cf7e87d565b5c86eb4088da81b60a49e214fcdd4ffda"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e10fdc972b3211915b65cc89e8cd24e1a26c9bd684ee71c3f369fb488f2c4388"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d840e780c48c5df1330879d50615176896e8e6eee554507d21ce8e2f1a5f0ff8"}, - {file = "cmake-4.0.3-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:6ef63bbabcbe3b89c1d80547913b6caceaad57987a27e7afc79ebc88ecd829e4"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:67103f2bcce8f57b8705ba8e353f18fdc3684a346eee97dc5f94d11575a424c6"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:880a1e1ae26d440d7e4f604fecbf839728ca7b096c870f2e7359855cc4828532"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:c403b660bbff1fd4d7f1c5d9e015ea27566e49ca9461e260c9758f2fd4e5e813"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:2a66ecdd4c3238484cb0c377d689c086a9b8b533e25329f73d21bd1c38f1ae86"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:004e58b1a1a384c2ca799c9c41ac4ed86ac3b80129462992c43c1121f8729ffd"}, - {file = "cmake-4.0.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:133dbc33f995cb97a4456d83d67fa0a7a798f53f979454359140588baa928f43"}, - {file = "cmake-4.0.3-py3-none-win32.whl", hash = "sha256:3e07bdd14e69ea67d1e67a4f5225ac2fd91ee9e349c440143cdddd7368be1f46"}, - {file = "cmake-4.0.3-py3-none-win_amd64.whl", hash = "sha256:9a349ff2b4a7c63c896061676bc0f4e6994f373d54314d79ba3608ee7fa75442"}, - {file = "cmake-4.0.3-py3-none-win_arm64.whl", hash = "sha256:94a52e67b264a51089907c9e74ca5a9e2f3e65c57c457e0f40f02629a0de74d8"}, - {file = "cmake-4.0.3.tar.gz", hash = "sha256:215732f09ea8a7088fe1ab46bbd61669437217278d709fd3851bf8211e8c59e3"}, +version = "4.1.0" +requires_python = ">=3.8" +summary = "CMake is an open-source, cross-platform family of tools designed to build, test and package software" +files = [ + {file = "cmake-4.1.0-py3-none-macosx_10_10_universal2.whl", hash = "sha256:69df62445b22d78c2002c22edeb0e85590ae788e477d222fb2ae82c871c33090"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4e3a30a4f72a8a6d8d593dc289e791f1d84352c1f629543ac8e22c62dbadb20a"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0e2fea746d746f52aa52b8498777ff665a0627d9b136bec4ae0465c38b75e799"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5a28a87601fa5e775017bf4f5836e8e75091d08f3e5aac411256754ba54fe5c4"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2a8790473afbb895b8e684e479f26773e4fc5c86845e3438e8488d38de9db807"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dab375932f5962e078da8cf76ca228c21bf4bea9ddeb1308e2b35797fa30f784"}, + {file = "cmake-4.1.0-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:f2eaa6f0a25e31fe09fb0b7f40fbf208eea5f1313093ff441ecfff7dc1b80adf"}, + {file = "cmake-4.1.0-py3-none-manylinux_2_35_riscv64.whl", hash = "sha256:3ee38de00cad0501c7dd2b94591522381e3ef9c8468094f037a17ed9e478ef13"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2d9f14b7d58e447865c111b3b90945b150724876866f5801c80970151718f710"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:574448a03acdf34c55a7c66485e7a8260709e8386e9145708e18e2abe5fc337b"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8c2538fb557b9edd74d48c189fcde42a55ad7e2c39e04254f8c5d248ca1af4c"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:7c7999c5a1d5a3a66adacc61056765557ed253dc7b8e9deab5cae546f4f9361c"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:e77ac2554a7b8a94745add465413e3266b714766e9a5d22ac8e5b36a900a1136"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:d54e68d5439193265fd7211671420601f6a672b8ca220f19e6c72238b41a84c2"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c6bd346fe4d9c205310ef9a6e09ced7e610915fa982d7b649f9b12caa6fa0605"}, + {file = "cmake-4.1.0-py3-none-win32.whl", hash = "sha256:7219b7e85ed03a98af89371b9dee762e236ad94e8a09ce141070e6ac6415756f"}, + {file = "cmake-4.1.0-py3-none-win_amd64.whl", hash = "sha256:76e8e7d80a1a9bb5c7ec13ec8da961a8c5a997247f86a08b29f0c2946290c461"}, + {file = "cmake-4.1.0-py3-none-win_arm64.whl", hash = "sha256:8d39bbfee7c181e992875cd390fc6d51a317c9374656b332021a67bb40c0b07f"}, + {file = "cmake-4.1.0.tar.gz", hash = "sha256:bacdd21aebdf9a42e5631cfb365beb8221783fcd27c4e04f7db8b79c43fb12df"}, ] [[package]] @@ -122,7 +122,7 @@ files = [ [[package]] name = "conan" -version = "2.19.0" +version = "2.19.1" requires_python = ">=3.6" summary = "Conan C/C++ package manager" dependencies = [ @@ -137,7 +137,7 @@ dependencies = [ "urllib3<2.1,>=1.26.6", ] files = [ - {file = "conan-2.19.0.tar.gz", hash = "sha256:6f66530678daa2d28ca09c53598e5c04a9d6cad80212d50f9cedcc9c7d3d7f30"}, + {file = "conan-2.19.1.tar.gz", hash = "sha256:bf334867b81bcb73e5be31afe26a0f207017719298ad1f0f64762867caa9a971"}, ] [[package]] @@ -203,20 +203,21 @@ files = [ [[package]] name = "dulwich" -version = "0.23.2" +version = "0.24.1" requires_python = ">=3.9" -summary = "" +summary = "Python Git Library" dependencies = [ - "urllib3", + "typing-extensions>=4.0; python_version < \"3.11\"", + "urllib3>=1.25", ] files = [ - {file = "dulwich-0.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e44dec7e36bc035da0ec3df6c1564810699e319ba41b71d17750dd7452e1b2fc"}, - {file = "dulwich-0.23.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:398ba1c0e1581071cdcb38a681e0ff1e046aa8f31bad3bc368266f499c4ddf9e"}, - {file = "dulwich-0.23.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:432c6eeac5edf97ff7090fbac7cda708167ee90e5afa78652d252e87e397f425"}, - {file = "dulwich-0.23.2-cp313-cp313-win32.whl", hash = "sha256:8555980e8509d7f76e80de58d1eb7bd2c1c317942b7a3c9c113d81dfc287f4c0"}, - {file = "dulwich-0.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:2b042dca31de4d4a0e88e4dbe20afe804a640c8882eec0de5093bffb34b75370"}, - {file = "dulwich-0.23.2-py3-none-any.whl", hash = "sha256:0b0439d309cf808f7955f74776981d9ac9dc1ec715aa39798de9b22bb95ac163"}, - {file = "dulwich-0.23.2.tar.gz", hash = "sha256:a152ebb0e95bc0f23768be563f80ff1e719bf5c4f5c2696be4fa8ab625a39879"}, + {file = "dulwich-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a11ec69fc6604228804ddfc32c85b22bc627eca4cf4ff3f27dbe822e6f29477"}, + {file = "dulwich-0.24.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a9800df7238b586b4c38c00432776781bc889cf02d756dcfb8dc0ecb8fc47a33"}, + {file = "dulwich-0.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3baab4a01aff890e2e6551ccbd33eb2a44173c897f0f027ad3aeab0fb057ec44"}, + {file = "dulwich-0.24.1-cp313-cp313-win32.whl", hash = "sha256:b39689aa4d143ba1fb0a687a4eb93d2e630d2c8f940aaa6c6911e9c8dca16e6a"}, + {file = "dulwich-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:8fca9b863b939b52c5f759d292499f0d21a7bf7f8cbb9fdeb8cdd9511c5bc973"}, + {file = "dulwich-0.24.1-py3-none-any.whl", hash = "sha256:57cc0dc5a21059698ffa4ed9a7272f1040ec48535193df84b0ee6b16bf615676"}, + {file = "dulwich-0.24.1.tar.gz", hash = "sha256:e19fd864f10f02bb834bb86167d92dcca1c228451b04458761fc13dabd447758"}, ] [[package]] @@ -474,36 +475,38 @@ files = [ [[package]] name = "pdm" -version = "2.25.4" +version = "2.25.6" requires_python = ">=3.9" -summary = "" +summary = "A modern Python package and dependency manager supporting the latest PEP standards" dependencies = [ "blinker", - "certifi", - "dep-logic", - "filelock", - "findpython", - "hishel", - "httpcore", - "httpx[socks]", - "id", - "installer", - "packaging", - "pbs-installer", + "certifi>=2024.8.30", + "dep-logic>=0.5", + "filelock>=3.13", + "findpython<1.0.0a0,>=0.7.0", + "hishel>=0.0.32", + "httpcore>=1.0.6", + "httpx[socks]<1,>0.20", + "id>=1.5.0", + "importlib-metadata>=3.6; python_version < \"3.10\"", + "installer<0.8,>=0.7", + "packaging>22.0", + "pbs-installer>=2025.6.6", "platformdirs", "pyproject-hooks", - "python-dotenv", - "resolvelib", - "rich", - "shellingham", - "tomlkit", - "truststore", - "unearth", - "virtualenv", + "python-dotenv>=0.15", + "resolvelib>=1.1", + "rich>=12.3.0", + "shellingham>=1.3.2", + "tomli>=1.1.0; python_version < \"3.11\"", + "tomlkit<1,>=0.11.1", + "truststore>=0.10.4; python_version >= \"3.10\"", + "unearth>=0.17.5", + "virtualenv>=20", ] files = [ - {file = "pdm-2.25.4-py3-none-any.whl", hash = "sha256:3efab7367cb5d9d6e4ef9db6130e4f5620c247343c8e95e18bd0d76b201ff7da"}, - {file = "pdm-2.25.4.tar.gz", hash = "sha256:bd655d789429928d6e27ff6693c19c82bc81aa75ba51d7b1c6102d039c8f211c"}, + {file = "pdm-2.25.6-py3-none-any.whl", hash = "sha256:5f18326edb40cb3d179f96583be1253ee31cf9160cc7ca4299839eaebd079f2a"}, + {file = "pdm-2.25.6.tar.gz", hash = "sha256:46693c26dde87bdeffecf18eb852ea55434c9b6b2aec42edef237f4ac595763c"}, ] [[package]] @@ -588,19 +591,19 @@ files = [ [[package]] name = "pyrefly" -version = "0.25.1" +version = "0.28.1" requires_python = ">=3.8" summary = "A fast Python type checker written in Rust" files = [ - {file = "pyrefly-0.25.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:65374450bb590b715b3d5ee5fb1d9053b01cc5843df24b7f9727ce244ba412d7"}, - {file = "pyrefly-0.25.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8127b83f850f7c3b78413bd6626d2496c2af763d68716bf0ada9f0752dc550fb"}, - {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c84a7846651067d7d782835ade6eed66e855dbd1c63925cd4490d226d92d4a42"}, - {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41ca0aff5f74103bf33f8d86725d7dec9ade410721f3071d661e2393220a053c"}, - {file = "pyrefly-0.25.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9909532b49c591338838078229cb49389c024c29f836f92028fee78ce62db3e2"}, - {file = "pyrefly-0.25.1-py3-none-win32.whl", hash = "sha256:86a37b106a0f32cf0f0d82af5e49c41e6618c82896f109094c8dad436f17d7d7"}, - {file = "pyrefly-0.25.1-py3-none-win_amd64.whl", hash = "sha256:8a5a3432a0fabebc8a651cc0ffde22001e39a1e80cd2337b1d6a839b771c4c71"}, - {file = "pyrefly-0.25.1-py3-none-win_arm64.whl", hash = "sha256:e5093e979b31f10099bc48cc737d36fb69cbb84cd062df4dc122ff55c22712e2"}, - {file = "pyrefly-0.25.1.tar.gz", hash = "sha256:9525fffb280ff7ffdd3fb5d2658aa1385bb3ef3687e3d0abbe2e48baa2d72fa1"}, + {file = "pyrefly-0.28.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a4abd5218f43c25c00571fc498f85892b434d2361882a9e38ca7bb0ccb949bff"}, + {file = "pyrefly-0.28.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b0ef6859ceca146f41be152e3bc783248cac7425f904a67bb9d3b130210e03c"}, + {file = "pyrefly-0.28.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ac5fa3dcf83d51f9a7524a391f3614e8227fa4d22f4c053437f91e763d1fa6"}, + {file = "pyrefly-0.28.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:428e8a174891a14d9e7364e20073162d66c7a0e2575dc5433e2f8228a0fe94ca"}, + {file = "pyrefly-0.28.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a5f66c43fc2e4676307b539d1ed085744a5625c579365cfc889cb9faf9ef8d0"}, + {file = "pyrefly-0.28.1-py3-none-win32.whl", hash = "sha256:ccf2e7d1253de03940953aeb8746c189435899620d113cb05114e8d2175892e4"}, + {file = "pyrefly-0.28.1-py3-none-win_amd64.whl", hash = "sha256:cb973dc1fc3c128f9d674f943eca9eea6d4ed272a329836efc9d6e5c16ebe12a"}, + {file = "pyrefly-0.28.1-py3-none-win_arm64.whl", hash = "sha256:b3aa87f12555dda76b60aa101466ad5fde54a53f20c5112b02ea2eaaf0d6bfe9"}, + {file = "pyrefly-0.28.1.tar.gz", hash = "sha256:9ebc67e4a2e3d33c78f1962e7b2a16cd9b4415ce22fcf7a290b741ed9f3b7535"}, ] [[package]] @@ -750,28 +753,29 @@ files = [ [[package]] name = "ruff" -version = "0.12.5" +version = "0.12.9" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." files = [ - {file = "ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92"}, - {file = "ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a"}, - {file = "ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb"}, - {file = "ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9"}, - {file = "ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5"}, - {file = "ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805"}, - {file = "ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e"}, + {file = "ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e"}, + {file = "ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f"}, + {file = "ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340"}, + {file = "ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66"}, + {file = "ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7"}, + {file = "ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93"}, + {file = "ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908"}, + {file = "ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089"}, + {file = "ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a"}, ] [[package]] @@ -821,11 +825,12 @@ files = [ [[package]] name = "truststore" -version = "0.10.1" -summary = "" +version = "0.10.4" +requires_python = ">=3.10" +summary = "Verify certificates using native system trust stores" files = [ - {file = "truststore-0.10.1-py3-none-any.whl", hash = "sha256:b64e6025a409a43ebdd2807b0c41c8bff49ea7ae6550b5087ac6df6619352d4c"}, - {file = "truststore-0.10.1.tar.gz", hash = "sha256:eda021616b59021812e800fa0a071e51b266721bef3ce092db8a699e21c63539"}, + {file = "truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981"}, + {file = "truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301"}, ] [[package]] @@ -846,15 +851,15 @@ files = [ [[package]] name = "types-requests" -version = "2.32.4.20250611" +version = "2.32.4.20250809" requires_python = ">=3.9" -summary = "" +summary = "Typing stubs for requests" dependencies = [ - "urllib3", + "urllib3>=2", ] files = [ - {file = "types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072"}, - {file = "types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826"}, + {file = "types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}, + {file = "types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 17bd8b5..a9f08b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,15 +16,15 @@ dependencies = [ "pydantic>=2.11.7", "packaging>=25.0", "requests>=2.32.4", - "types-requests>=2.32.4.20250611", + "types-requests>=2.32.4.20250809", ] [project.optional-dependencies] pytest = ["pytest>=8.4.1", "pytest-mock>=3.14.1"] -git = ["dulwich>=0.23.2"] -pdm = ["pdm>=2.25.4"] -cmake = ["cmake>=4.0.3"] -conan = ["conan>=2.19.0", "libcst>=1.8.2"] +git = ["dulwich>=0.24.1"] +pdm = ["pdm>=2.25.6"] +cmake = ["cmake>=4.1.0"] +conan = ["conan>=2.19.1", "libcst>=1.8.2"] [project.urls] homepage = "https://github.com/Synodic-Software/CPPython" @@ -47,7 +47,7 @@ cppython = "cppython.plugins.pdm.plugin:CPPythonPlugin" cppython = "cppython.test.pytest.fixtures" [dependency-groups] -lint = ["ruff>=0.12.5", "pyrefly>=0.25.1"] +lint = ["ruff>=0.12.9", "pyrefly>=0.28.1"] test = ["pytest>=8.4.1", "pytest-cov>=6.2.1", "pytest-mock>=3.14.1"] [project.scripts] From fbdb01fe40a4f8a90027c9d48ecb2e7f66167aa0 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:17:06 -0400 Subject: [PATCH 50/68] Update schema.py --- cppython/plugins/cmake/schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index 75c5c9d..b098ff2 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -99,7 +99,6 @@ class CMakePresets(CPPythonModel, extra='allow'): class CMakeSyncData(SyncData): """The CMake sync data""" - preset_file: Annotated[Path, Field(description='Path to the CMakePresets.json file generated by the provider.')] toolchain: Path | None = None From d39189b0ad6fad03d107d0609be682d4d151bea9 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:30:52 -0400 Subject: [PATCH 51/68] Update plugin.py --- cppython/plugins/conan/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 0400b5c..4a87122 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -214,9 +214,8 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: Returns: CMakeSyncData configured for Conan integration """ - # Conan's CMakeToolchain generator creates preset files at the configured user_presets_path - # This should match the path configured in the conanfile template - conan_toolchain_path = self.core_data.cppython_data.tool_path / 'ConanPresets.json' + # TODO: Use the actual toolchain path conan writes to + conan_toolchain_path = self.core_data.cppython_data.tool_path return CMakeSyncData( provider_name=TypeName('conan'), From 0dacefc28f51ec09bfe9b0bdb21d15721ce32681 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:37:53 -0400 Subject: [PATCH 52/68] Remove Preset Writes --- cppython/plugins/cmake/builder.py | 41 +---------------- cppython/plugins/cmake/schema.py | 2 +- cppython/plugins/conan/plugin.py | 2 +- tests/unit/plugins/cmake/test_presets.py | 57 +----------------------- 4 files changed, 6 insertions(+), 96 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index ab0fdd3..e230934 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -33,51 +33,14 @@ def generate_cppython_preset( """ configure_presets = [] - preset_name = 'cppython-default' - parent_preset_name = f'{provider_data.provider_name}-default' + preset_name = 'cppython' # Create a default preset that inherits from provider's default preset - default_configure = ConfigurePreset(name=preset_name, inherits=parent_preset_name, hidden=True) + default_configure = ConfigurePreset(name=preset_name, hidden=True, toolchainFile=provider_data.toolchain_file) configure_presets.append(default_configure) - # Create presets for each configuration - for config in provider_data.configurations: - config_name = config.lower() - preset_name = f'cppython-{config_name}' - parent_preset_name = f'{provider_data.provider_name}-{config_name}' - preset = ConfigurePreset(name=preset_name, inherits=parent_preset_name, hidden=True) - configure_presets.append(preset) - - build_presets = [] - - # Multi-config build presets using the default configure preset. - # Important: Do not use a configure preset here, the user will do that in their own presets. - for config in provider_data.configurations: - config_name = config.lower() - preset_name = f'cppython-multi-{config_name}' - parent_preset_name = f'{provider_data.provider_name}-multi-{config_name}' - multi_build_preset = BuildPreset( - name=preset_name, - configuration=config, - inherits=parent_preset_name, - ) - build_presets.append(multi_build_preset) - - # Single-config build presets using the config-specific configure presets - # Important: Do not use a configure preset here, the user will do that in their own presets. - for config in provider_data.configurations: - config_name = config.lower() - parent_config_name = f'{provider_data.provider_name}-{config_name}' - single_build_preset = BuildPreset( - name=config_name, - configuration=config, - inherits=parent_config_name, - ) - build_presets.append(single_build_preset) - generated_preset = CMakePresets( configurePresets=configure_presets, - buildPresets=build_presets, ) # Get the relative path to the provider preset file diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index b098ff2..e8e44e6 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -99,7 +99,7 @@ class CMakePresets(CPPythonModel, extra='allow'): class CMakeSyncData(SyncData): """The CMake sync data""" - toolchain: Path | None = None + toolchain_file: Path | None = None class CMakeData(CPPythonModel): diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 4a87122..7df826c 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -219,7 +219,7 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: return CMakeSyncData( provider_name=TypeName('conan'), - toolchain=conan_toolchain_path, + toolchain_file=conan_toolchain_path, ) @classmethod diff --git a/tests/unit/plugins/cmake/test_presets.py b/tests/unit/plugins/cmake/test_presets.py index e0dd6bc..4c5995f 100644 --- a/tests/unit/plugins/cmake/test_presets.py +++ b/tests/unit/plugins/cmake/test_presets.py @@ -1,7 +1,6 @@ """Tests for CMakePresets""" import json -from pathlib import Path from cppython.core.schema import ProjectData from cppython.plugins.cmake.builder import Builder @@ -54,58 +53,6 @@ def test_generate_root_preset_existing(project_data: ProjectData) -> None: class TestWrites: """Tests for writing the CMakePresets class""" - @staticmethod - def test_cppython_write(tmp_path: Path) -> None: - """Verifies that the cppython preset writing works as intended - - Args: - tmp_path: The input path the use - """ - builder = Builder() - - # Create a mock provider preset file - provider_preset_file = tmp_path / 'provider.json' - provider_preset = { - 'version': 9, - 'configurePresets': [ - {'name': 'test-provider-base', 'hidden': True}, - {'name': 'test-provider-release', 'hidden': True, 'inherits': 'test-provider-base'}, - {'name': 'test-provider-debug', 'hidden': True, 'inherits': 'test-provider-base'}, - ], - 'buildPresets': [ - { - 'name': 'test-provider-multi-release', - 'configurePreset': 'test-provider-base', - 'configuration': 'Release', - 'hidden': True, - }, - { - 'name': 'test-provider-multi-debug', - 'configurePreset': 'test-provider-base', - 'configuration': 'Debug', - 'hidden': True, - }, - { - 'name': 'test-provider-release', - 'configurePreset': 'test-provider-release', - 'configuration': 'Release', - 'hidden': True, - }, - { - 'name': 'test-provider-debug', - 'configurePreset': 'test-provider-debug', - 'configuration': 'Debug', - 'hidden': True, - }, - ], - } - - with provider_preset_file.open('w', encoding='utf-8') as file: - json.dump(provider_preset, file, indent=4) - - data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) - builder.write_cppython_preset(tmp_path, provider_preset_file, data) - @staticmethod def test_root_write(project_data: ProjectData) -> None: """Verifies that the root preset writing works as intended @@ -138,7 +85,7 @@ def test_root_write(project_data: ProjectData) -> None: with provider_preset_file.open('w') as f: json.dump(provider_preset_data, f) - data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) + data = CMakeSyncData(provider_name=TypeName('test-provider')) cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_preset_file, data) @@ -184,7 +131,7 @@ def test_relative_root_write(project_data: ProjectData) -> None: with provider_preset_file.open('w') as f: json.dump(provider_preset_data, f) - data = CMakeSyncData(provider_name=TypeName('test-provider'), preset_file=provider_preset_file) + data = CMakeSyncData(provider_name=TypeName('test-provider')) cppython_preset_file = builder.write_cppython_preset(cppython_preset_directory, provider_preset_file, data) From ebfe39c7ceec9ad2f9f5464bc377ea0b4c4e2567 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Fri, 15 Aug 2025 06:47:33 -0400 Subject: [PATCH 53/68] Update plugin.py --- cppython/plugins/cmake/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cppython/plugins/cmake/plugin.py b/cppython/plugins/cmake/plugin.py index 495ddd9..e008a70 100644 --- a/cppython/plugins/cmake/plugin.py +++ b/cppython/plugins/cmake/plugin.py @@ -64,9 +64,10 @@ def sync(self, sync_data: SyncData) -> None: case CMakeSyncData(): self._cppython_preset_directory.mkdir(parents=True, exist_ok=True) - # Provider now provides the preset file path, we don't generate it + cppython_preset_file = self._cppython_preset_directory / 'CPPython.json' + cppython_preset_file = self.builder.write_cppython_preset( - self._cppython_preset_directory, sync_data.preset_file, sync_data + self._cppython_preset_directory, cppython_preset_file, sync_data ) self.builder.write_root_presets( From 99178740d1e2b0578458979a12ea0c9010f6a7f8 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 12:08:33 -0400 Subject: [PATCH 54/68] Relative `binaryDir` --- cppython/plugins/cmake/builder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index e230934..83b3365 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -98,7 +98,7 @@ def _create_user_presets(cmake_data: CMakeData, build_directory: Path) -> tuple[ """ user_configure_preset = ConfigurePreset( name=cmake_data.configuration_name, - inherits='default', # Inherit from cppython's default preset + inherits='cppython', binaryDir=build_directory.as_posix(), ) @@ -273,6 +273,9 @@ def write_root_presets( initial_json = file.read() initial_root_preset = CMakePresets.model_validate_json(initial_json) + # Ensure that the build_directory is relative to the preset_file + build_directory = build_directory.relative_to(preset_file.parent) + root_preset = Builder.generate_root_preset(preset_file, cppython_preset_file, cmake_data, build_directory) # Only write the file if the data has changed From a4bec4b1fba99056bda37c36978cf1be570b7ed9 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 13:05:02 -0400 Subject: [PATCH 55/68] Add Description Slots --- cppython/plugins/cmake/schema.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cppython/plugins/cmake/schema.py b/cppython/plugins/cmake/schema.py index e8e44e6..6571f5b 100644 --- a/cppython/plugins/cmake/schema.py +++ b/cppython/plugins/cmake/schema.py @@ -46,6 +46,8 @@ class ConfigurePreset(CPPythonModel, extra='allow'): """Partial Configure Preset specification to allow cache variable injection""" name: str + description: Annotated[str | None, Field(description='A human-readable description of the preset.')] = None + hidden: Annotated[bool | None, Field(description='If true, the preset is hidden and cannot be used directly.')] = ( None ) @@ -68,6 +70,8 @@ class BuildPreset(CPPythonModel, extra='allow'): """Partial Build Preset specification for CMake build presets""" name: str + description: Annotated[str | None, Field(description='A human-readable description of the preset.')] = None + hidden: Annotated[bool | None, Field(description='If true, the preset is hidden and cannot be used directly.')] = ( None ) From 8159b1a64d4084bf4713670d7173c74215311026 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 14:43:04 -0400 Subject: [PATCH 56/68] Use Conan Toolchain Location --- cppython/plugins/conan/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index 7df826c..b99d3fb 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -214,8 +214,7 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: Returns: CMakeSyncData configured for Conan integration """ - # TODO: Use the actual toolchain path conan writes to - conan_toolchain_path = self.core_data.cppython_data.tool_path + conan_toolchain_path = self.core_data.cppython_data.build_path / 'generators' / 'conan_toolchain.cmake' return CMakeSyncData( provider_name=TypeName('conan'), From 521e15edce8f452ec1e7d52531dbfd20e12cbc2d Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 14:43:16 -0400 Subject: [PATCH 57/68] Update Default Presets --- cppython/plugins/cmake/builder.py | 124 +++++++++++++++++++----------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 83b3365..8561fde 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -36,7 +36,12 @@ def generate_cppython_preset( preset_name = 'cppython' # Create a default preset that inherits from provider's default preset - default_configure = ConfigurePreset(name=preset_name, hidden=True, toolchainFile=provider_data.toolchain_file) + default_configure = ConfigurePreset( + name=preset_name, + hidden=True, + description='Injected configuration preset for CPPython', + toolchainFile=provider_data.toolchain_file, + ) configure_presets.append(default_configure) generated_preset = CMakePresets( @@ -86,8 +91,10 @@ def write_cppython_preset( return cppython_preset_file @staticmethod - def _create_user_presets(cmake_data: CMakeData, build_directory: Path) -> tuple[ConfigurePreset, list[BuildPreset]]: - """Create user configure and build presets. + def _create_presets( + cmake_data: CMakeData, build_directory: Path + ) -> tuple[list[ConfigurePreset], list[BuildPreset]]: + """Create the default configure and build presets for the user. Args: cmake_data: The CMake data to use @@ -96,26 +103,63 @@ def _create_user_presets(cmake_data: CMakeData, build_directory: Path) -> tuple[ Returns: A tuple containing the configure preset and list of build presets """ - user_configure_preset = ConfigurePreset( - name=cmake_data.configuration_name, - inherits='cppython', - binaryDir=build_directory.as_posix(), + user_configure_presets: list[ConfigurePreset] = [] + user_build_presets: list[BuildPreset] = [] + + name = cmake_data.configuration_name + release_name = name + '-release' + debug_name = name + '-debug' + + user_configure_presets.append( + ConfigurePreset( + name=name, + description='All multi-configuration generators should inherit from this preset', + hidden=True, + inherits='cppython', + binaryDir='${sourceDir}/' + build_directory.as_posix(), + cacheVariables={'CMAKE_CONFIGURATION_TYPES': 'Debug;Release'}, + ) + ) + + user_configure_presets.append( + ConfigurePreset( + name=release_name, + description='All single-configuration generators should inherit from this preset', + hidden=True, + inherits=name, + cacheVariables={'CMAKE_BUILD_TYPE': 'Release'}, + ) ) - user_build_presets = [ + user_configure_presets.append( + ConfigurePreset( + name=debug_name, + description='All single-configuration generators should inherit from this preset', + hidden=True, + inherits=name, + cacheVariables={'CMAKE_BUILD_TYPE': 'Debug'}, + ) + ) + + user_build_presets.append( BuildPreset( - name=f'{cmake_data.configuration_name}-multi-release', - configurePreset=cmake_data.configuration_name, - configuration='Release', - ), + name=release_name, + description='An example build preset for release', + hidden=True, + configurePreset=release_name, + ) + ) + + user_build_presets.append( BuildPreset( - name=f'{cmake_data.configuration_name}-multi-debug', - configurePreset=cmake_data.configuration_name, - configuration='Debug', - ), - ] + name=debug_name, + description='An example build preset for debug', + hidden=True, + configurePreset=debug_name, + ) + ) - return user_configure_preset, user_build_presets + return user_configure_presets, user_build_presets @staticmethod def _load_existing_preset(preset_file: Path) -> CMakePresets | None: @@ -152,39 +196,36 @@ def _update_configure_preset(existing_preset: ConfigurePreset, build_directory: # Update binary directory if not set if not existing_preset.binaryDir: - existing_preset.binaryDir = build_directory.as_posix() # type: ignore[misc] + existing_preset.binaryDir = '${sourceDir}/' + build_directory.as_posix() # type: ignore[misc] @staticmethod - def _handle_configure_presets( - root_preset: CMakePresets, user_configure_preset: ConfigurePreset, build_directory: Path + def _modify_presets( + root_preset: CMakePresets, + user_configure_presets: list[ConfigurePreset], + user_build_presets: list[BuildPreset], + build_directory: Path, ) -> None: - """Handle configure presets in the root preset. + """Handle presets in the root preset. Args: root_preset: The root preset to modify - user_configure_preset: The user's configure preset + user_configure_presets: The user's configure presets + user_build_presets: The user's build presets build_directory: The build directory to use """ if root_preset.configurePresets is None: - root_preset.configurePresets = [user_configure_preset] # type: ignore[misc] + root_preset.configurePresets = user_configure_presets.copy() # type: ignore[misc] else: # Update or add the user's configure preset - existing_preset = next( - (p for p in root_preset.configurePresets if p.name == user_configure_preset.name), None - ) + for user_configure_preset in user_configure_presets: + existing_preset = next( + (p for p in root_preset.configurePresets if p.name == user_configure_preset.name), None + ) if existing_preset: Builder._update_configure_preset(existing_preset, build_directory) else: root_preset.configurePresets.append(user_configure_preset) - @staticmethod - def _handle_build_presets(root_preset: CMakePresets, user_build_presets: list[BuildPreset]) -> None: - """Handle build presets in the root preset. - - Args: - root_preset: The root preset to modify - user_build_presets: The user's build presets to add - """ if root_preset.buildPresets is None: root_preset.buildPresets = user_build_presets.copy() # type: ignore[misc] else: @@ -195,7 +236,7 @@ def _handle_build_presets(root_preset: CMakePresets, user_build_presets: list[Bu root_preset.buildPresets.append(build_preset) @staticmethod - def _handle_includes(root_preset: CMakePresets, preset_file: Path, cppython_preset_file: Path) -> None: + def _modify_includes(root_preset: CMakePresets, preset_file: Path, cppython_preset_file: Path) -> None: """Handle include paths in the root preset. Args: @@ -230,22 +271,19 @@ def generate_root_preset( A CMakePresets object """ # Create user presets - user_configure_preset, user_build_presets = Builder._create_user_presets(cmake_data, build_directory) + user_configure_presets, user_build_presets = Builder._create_presets(cmake_data, build_directory) # Load existing preset or create new one root_preset = Builder._load_existing_preset(preset_file) if root_preset is None: root_preset = CMakePresets( - configurePresets=[user_configure_preset], + configurePresets=user_configure_presets, buildPresets=user_build_presets, ) else: - # Handle existing preset - Builder._handle_configure_presets(root_preset, user_configure_preset, build_directory) - Builder._handle_build_presets(root_preset, user_build_presets) + Builder._modify_presets(root_preset, user_configure_presets, user_build_presets, build_directory) - # Handle includes - Builder._handle_includes(root_preset, preset_file, cppython_preset_file) + Builder._modify_includes(root_preset, preset_file, cppython_preset_file) return root_preset From bebd85d86fd071aba84eae803d28d07c41ecfec2 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 14:48:34 -0400 Subject: [PATCH 58/68] Remove CPPython Preset Includes --- cppython/plugins/cmake/builder.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 8561fde..69b86bb 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -40,19 +40,17 @@ def generate_cppython_preset( name=preset_name, hidden=True, description='Injected configuration preset for CPPython', - toolchainFile=provider_data.toolchain_file, ) + + if provider_data.toolchain_file: + default_configure.toolchainFile = provider_data.toolchain_file.as_posix() + configure_presets.append(default_configure) generated_preset = CMakePresets( configurePresets=configure_presets, ) - # Get the relative path to the provider preset file - relative_preset = provider_preset_file.relative_to(cppython_preset_directory, walk_up=True).as_posix() - - # Set the data - generated_preset.include = [relative_preset] return generated_preset @staticmethod @@ -114,7 +112,6 @@ def _create_presets( ConfigurePreset( name=name, description='All multi-configuration generators should inherit from this preset', - hidden=True, inherits='cppython', binaryDir='${sourceDir}/' + build_directory.as_posix(), cacheVariables={'CMAKE_CONFIGURATION_TYPES': 'Debug;Release'}, @@ -125,7 +122,6 @@ def _create_presets( ConfigurePreset( name=release_name, description='All single-configuration generators should inherit from this preset', - hidden=True, inherits=name, cacheVariables={'CMAKE_BUILD_TYPE': 'Release'}, ) @@ -135,7 +131,6 @@ def _create_presets( ConfigurePreset( name=debug_name, description='All single-configuration generators should inherit from this preset', - hidden=True, inherits=name, cacheVariables={'CMAKE_BUILD_TYPE': 'Debug'}, ) @@ -145,7 +140,6 @@ def _create_presets( BuildPreset( name=release_name, description='An example build preset for release', - hidden=True, configurePreset=release_name, ) ) @@ -154,7 +148,6 @@ def _create_presets( BuildPreset( name=debug_name, description='An example build preset for debug', - hidden=True, configurePreset=debug_name, ) ) From 1b167e4e70a4072ed444b34e3cefed1d4e8109ff Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 16:05:03 -0400 Subject: [PATCH 59/68] Move to src directory --- cppython/plugins/conan/builder.py | 4 ++++ examples/conan_cmake/simple/CMakeLists.txt | 2 +- examples/conan_cmake/simple/{ => src}/main.cpp | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename examples/conan_cmake/simple/{ => src}/main.cpp (100%) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index 0e8905b..b6ce19d 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -151,6 +151,10 @@ def build(self): def package(self): cmake = CMake(self) cmake.install() + + def export_sources(self): + self.copy("CMakeLists.txt") + self.copy("src/*") """ template = Template(dedent(template_string)) diff --git a/examples/conan_cmake/simple/CMakeLists.txt b/examples/conan_cmake/simple/CMakeLists.txt index 7cf1ecc..e728aa4 100644 --- a/examples/conan_cmake/simple/CMakeLists.txt +++ b/examples/conan_cmake/simple/CMakeLists.txt @@ -6,5 +6,5 @@ set(CMAKE_CXX_STANDARD 14) find_package(fmt REQUIRED) -add_executable(main main.cpp) +add_executable(main src/main.cpp) target_link_libraries(main PRIVATE fmt::fmt) \ No newline at end of file diff --git a/examples/conan_cmake/simple/main.cpp b/examples/conan_cmake/simple/src/main.cpp similarity index 100% rename from examples/conan_cmake/simple/main.cpp rename to examples/conan_cmake/simple/src/main.cpp From ec79e2bd4c69996f84dfc353c8d80fb44ff733c4 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 16:05:13 -0400 Subject: [PATCH 60/68] Remove OF --- cppython/plugins/conan/plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cppython/plugins/conan/plugin.py b/cppython/plugins/conan/plugin.py index b99d3fb..42abcdd 100644 --- a/cppython/plugins/conan/plugin.py +++ b/cppython/plugins/conan/plugin.py @@ -149,10 +149,6 @@ def _run_conan_install(self, conanfile_path: Path, update: bool, build_type: str if build_type: command_args.extend(['-s', f'build_type={build_type}']) - # Add output folder - build_path = self.core_data.cppython_data.build_path - command_args.extend(['--output-folder', str(build_path)]) - # Log the command being executed logger.info('Executing conan command: conan %s', ' '.join(command_args)) From a60fd8292266abe86b7cd25a24070970bdc70173 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 16:09:45 -0400 Subject: [PATCH 61/68] Fix Sources --- cppython/plugins/conan/builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index b6ce19d..e3284de 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -126,6 +126,7 @@ def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], nam template_string = """ from conan import ConanFile from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout + from conan.tools.files import copy class AutoPackage(ConanFile): name = "${name}" @@ -153,8 +154,8 @@ def package(self): cmake.install() def export_sources(self): - self.copy("CMakeLists.txt") - self.copy("src/*") + copy(self, "CMakeLists.txt", src=self.recipe_folder, dst=self.export_sources_folder) + copy(self, "src/*", src=self.recipe_folder, dst=self.export_sources_folder) """ template = Template(dedent(template_string)) From 184d9fd3c570db6bf8af4f9dce11374874249b8e Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 16:42:28 -0400 Subject: [PATCH 62/68] Update Conanfile --- cppython/plugins/conan/builder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cppython/plugins/conan/builder.py b/cppython/plugins/conan/builder.py index e3284de..9ae94e8 100644 --- a/cppython/plugins/conan/builder.py +++ b/cppython/plugins/conan/builder.py @@ -153,9 +153,14 @@ def package(self): cmake = CMake(self) cmake.install() + def package_info(self): + self.cpp_info.libs = ["${name}"] + def export_sources(self): copy(self, "CMakeLists.txt", src=self.recipe_folder, dst=self.export_sources_folder) + copy(self, "include/*", src=self.recipe_folder, dst=self.export_sources_folder) copy(self, "src/*", src=self.recipe_folder, dst=self.export_sources_folder) + copy(self, "cmake/*", src=self.recipe_folder, dst=self.export_sources_folder) """ template = Template(dedent(template_string)) From 815ec8c4a5065df5f39653b70fa27cbefec18ba6 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 16:46:15 -0400 Subject: [PATCH 63/68] Update `vcpkg` Subprocess Logging --- cppython/plugins/vcpkg/plugin.py | 62 ++- examples/vcpkg_cmake/simple/CMakeLists.txt | 11 +- examples/vcpkg_cmake/simple/helloworld.cpp | 7 - examples/vcpkg_cmake/simple/pdm.lock | 393 ++++++++++++++++++ examples/vcpkg_cmake/simple/pyproject.toml | 2 +- examples/vcpkg_cmake/simple/src/main.cpp | 7 + tests/fixtures/vcpkg.py | 1 + .../integration/examples/test_vcpkg_cmake.py | 42 +- 8 files changed, 491 insertions(+), 34 deletions(-) delete mode 100644 examples/vcpkg_cmake/simple/helloworld.cpp create mode 100644 examples/vcpkg_cmake/simple/pdm.lock create mode 100644 examples/vcpkg_cmake/simple/src/main.cpp create mode 100644 tests/fixtures/vcpkg.py diff --git a/cppython/plugins/vcpkg/plugin.py b/cppython/plugins/vcpkg/plugin.py index c6ca36f..2fb0236 100644 --- a/cppython/plugins/vcpkg/plugin.py +++ b/cppython/plugins/vcpkg/plugin.py @@ -32,6 +32,39 @@ def __init__( self.core_data: CorePluginData = core_data self.data: VcpkgData = resolve_vcpkg_data(configuration_data, core_data) + @staticmethod + def _handle_subprocess_error( + logger_instance, operation: str, error: subprocess.CalledProcessError, exception_class: type + ) -> None: + """Handles subprocess errors with comprehensive error message formatting. + + Args: + logger_instance: The logger instance to use for error logging + operation: Description of the operation that failed (e.g., 'install', 'clone') + error: The CalledProcessError exception + exception_class: The exception class to raise + + Raises: + The specified exception_class with the formatted error message + """ + # Capture both stdout and stderr for better error reporting + stdout_msg = error.stdout.strip() if error.stdout else '' + stderr_msg = error.stderr.strip() if error.stderr else '' + + # Combine both outputs for comprehensive error message + error_parts = [] + if stderr_msg: + error_parts.append(f'stderr: {stderr_msg}') + if stdout_msg: + error_parts.append(f'stdout: {stdout_msg}') + + if not error_parts: + error_parts.append(f'Command failed with exit code {error.returncode}') + + error_msg = ' | '.join(error_parts) + logger_instance.error('Unable to %s: %s', operation, error_msg, exc_info=True) + raise exception_class('vcpkg', operation, error_msg, error) from error + @staticmethod def features(directory: Path) -> SupportedFeatures: """Queries vcpkg support @@ -82,6 +115,7 @@ def _update_provider(cls, path: Path) -> None: shell=True, check=True, capture_output=True, + text=True, ) elif system_name == 'posix': subprocess.run( @@ -90,11 +124,10 @@ def _update_provider(cls, path: Path) -> None: shell=True, check=True, capture_output=True, + text=True, ) except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if e.stderr else str(e) - logger.error('Unable to bootstrap the vcpkg repository: %s', error_msg, exc_info=True) - raise ProviderToolingError('vcpkg', 'bootstrap', error_msg, e) from e + cls._handle_subprocess_error(logger, 'bootstrap the vcpkg repository', e, ProviderToolingError) def sync_data(self, consumer: SyncConsumer) -> SyncData: """Gathers a data object for the given generator @@ -125,7 +158,7 @@ def _create_cmake_sync_data(self) -> CMakeSyncData: return CMakeSyncData( provider_name=TypeName('vcpkg'), - toolchain=vcpkg_cmake_path, + toolchain_file=vcpkg_cmake_path, ) @classmethod @@ -169,17 +202,17 @@ async def download_tooling(cls, directory: Path) -> None: cwd=directory, check=True, capture_output=True, + text=True, ) subprocess.run( ['git', 'pull'], cwd=directory, check=True, capture_output=True, + text=True, ) except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if e.stderr else str(e) - logger.error('Unable to update the vcpkg repository: %s', error_msg, exc_info=True) - raise ProviderToolingError('vcpkg', 'update', error_msg, e) from e + cls._handle_subprocess_error(logger, 'update the vcpkg repository', e, ProviderToolingError) else: try: logger.debug("Cloning the vcpkg repository to '%s'", directory.absolute()) @@ -190,12 +223,11 @@ async def download_tooling(cls, directory: Path) -> None: cwd=directory, check=True, capture_output=True, + text=True, ) except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if e.stderr else str(e) - logger.error('Unable to clone the vcpkg repository: %s', error_msg, exc_info=True) - raise ProviderToolingError('vcpkg', 'clone', error_msg, e) from e + cls._handle_subprocess_error(logger, 'clone the vcpkg repository', e, ProviderToolingError) cls._update_provider(directory) @@ -220,11 +252,10 @@ def install(self) -> None: cwd=str(build_path), check=True, capture_output=True, + text=True, ) except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if e.stderr else str(e) - logger.error('Unable to install project dependencies: %s', error_msg, exc_info=True) - raise ProviderInstallationError('vcpkg', error_msg, e) from e + self._handle_subprocess_error(logger, 'install project dependencies', e, ProviderInstallationError) def update(self) -> None: """Called when dependencies need to be updated and written to the lock file.""" @@ -248,11 +279,10 @@ def update(self) -> None: cwd=str(build_path), check=True, capture_output=True, + text=True, ) except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if e.stderr else str(e) - logger.error('Unable to update project dependencies: %s', error_msg, exc_info=True) - raise ProviderInstallationError('vcpkg', error_msg, e) from e + self._handle_subprocess_error(logger, 'update project dependencies', e, ProviderInstallationError) def publish(self) -> None: """Called when the project needs to be published. diff --git a/examples/vcpkg_cmake/simple/CMakeLists.txt b/examples/vcpkg_cmake/simple/CMakeLists.txt index 5526812..39fa2d1 100644 --- a/examples/vcpkg_cmake/simple/CMakeLists.txt +++ b/examples/vcpkg_cmake/simple/CMakeLists.txt @@ -1,9 +1,10 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.24) -project(HelloWorld) +project(FormatOutput LANGUAGES CXX C) -find_package(fmt CONFIG REQUIRED) +set(CMAKE_CXX_STANDARD 14) -add_executable(HelloWorld helloworld.cpp) +find_package(fmt REQUIRED) -target_link_libraries(HelloWorld PRIVATE fmt::fmt) \ No newline at end of file +add_executable(main src/main.cpp) +target_link_libraries(main PRIVATE fmt::fmt) \ No newline at end of file diff --git a/examples/vcpkg_cmake/simple/helloworld.cpp b/examples/vcpkg_cmake/simple/helloworld.cpp deleted file mode 100644 index 82c4e7a..0000000 --- a/examples/vcpkg_cmake/simple/helloworld.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main() -{ - fmt::print("Hello World!\n"); - return 0; -} \ No newline at end of file diff --git a/examples/vcpkg_cmake/simple/pdm.lock b/examples/vcpkg_cmake/simple/pdm.lock new file mode 100644 index 0000000..2f4a85d --- /dev/null +++ b/examples/vcpkg_cmake/simple/pdm.lock @@ -0,0 +1,393 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:b2622b10976126fce19eb70004ec43a9b5bb0319222fb13824ff58cfcd81c256" + +[[metadata.targets]] +requires_python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +requires_python = ">=3.7" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +requires_python = ">=3.7" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default"] +files = [ + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "click" +version = "8.2.1" +requires_python = ">=3.10" +summary = "Composable command line interface toolkit" +groups = ["default"] +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[[package]] +name = "cmake" +version = "4.1.0" +requires_python = ">=3.8" +summary = "CMake is an open-source, cross-platform family of tools designed to build, test and package software" +groups = ["default"] +files = [ + {file = "cmake-4.1.0-py3-none-macosx_10_10_universal2.whl", hash = "sha256:69df62445b22d78c2002c22edeb0e85590ae788e477d222fb2ae82c871c33090"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4e3a30a4f72a8a6d8d593dc289e791f1d84352c1f629543ac8e22c62dbadb20a"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0e2fea746d746f52aa52b8498777ff665a0627d9b136bec4ae0465c38b75e799"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5a28a87601fa5e775017bf4f5836e8e75091d08f3e5aac411256754ba54fe5c4"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2a8790473afbb895b8e684e479f26773e4fc5c86845e3438e8488d38de9db807"}, + {file = "cmake-4.1.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dab375932f5962e078da8cf76ca228c21bf4bea9ddeb1308e2b35797fa30f784"}, + {file = "cmake-4.1.0-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:f2eaa6f0a25e31fe09fb0b7f40fbf208eea5f1313093ff441ecfff7dc1b80adf"}, + {file = "cmake-4.1.0-py3-none-manylinux_2_35_riscv64.whl", hash = "sha256:3ee38de00cad0501c7dd2b94591522381e3ef9c8468094f037a17ed9e478ef13"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2d9f14b7d58e447865c111b3b90945b150724876866f5801c80970151718f710"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:574448a03acdf34c55a7c66485e7a8260709e8386e9145708e18e2abe5fc337b"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8c2538fb557b9edd74d48c189fcde42a55ad7e2c39e04254f8c5d248ca1af4c"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:7c7999c5a1d5a3a66adacc61056765557ed253dc7b8e9deab5cae546f4f9361c"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:e77ac2554a7b8a94745add465413e3266b714766e9a5d22ac8e5b36a900a1136"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:d54e68d5439193265fd7211671420601f6a672b8ca220f19e6c72238b41a84c2"}, + {file = "cmake-4.1.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c6bd346fe4d9c205310ef9a6e09ced7e610915fa982d7b649f9b12caa6fa0605"}, + {file = "cmake-4.1.0-py3-none-win32.whl", hash = "sha256:7219b7e85ed03a98af89371b9dee762e236ad94e8a09ce141070e6ac6415756f"}, + {file = "cmake-4.1.0-py3-none-win_amd64.whl", hash = "sha256:76e8e7d80a1a9bb5c7ec13ec8da961a8c5a997247f86a08b29f0c2946290c461"}, + {file = "cmake-4.1.0-py3-none-win_arm64.whl", hash = "sha256:8d39bbfee7c181e992875cd390fc6d51a317c9374656b332021a67bb40c0b07f"}, + {file = "cmake-4.1.0.tar.gz", hash = "sha256:bacdd21aebdf9a42e5631cfb365beb8221783fcd27c4e04f7db8b79c43fb12df"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["default"] +marker = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cppython" +version = "0.9.6" +requires_python = ">=3.13" +summary = "A Python management solution for C++ dependencies" +groups = ["default"] +dependencies = [ + "packaging>=25.0", + "pydantic>=2.11.7", + "requests>=2.32.4", + "typer>=0.16.0", + "types-requests>=2.32.4.20250611", +] +files = [ + {file = "cppython-0.9.6-py3-none-any.whl", hash = "sha256:9e95545a5bb38b02f86991a4d3873d61764c044adcdeca05d8d9a2a55a885a1f"}, + {file = "cppython-0.9.6.tar.gz", hash = "sha256:313729995ba1952d8a8e7fca23f3e5be6031a5bfefede747627407ca68d6ea6c"}, +] + +[[package]] +name = "cppython" +version = "0.9.6" +extras = ["cmake", "git", "vcpkg"] +requires_python = ">=3.13" +summary = "A Python management solution for C++ dependencies" +groups = ["default"] +dependencies = [ + "cmake>=4.0.3", + "cppython==0.9.6", + "dulwich>=0.23.2", +] +files = [ + {file = "cppython-0.9.6-py3-none-any.whl", hash = "sha256:9e95545a5bb38b02f86991a4d3873d61764c044adcdeca05d8d9a2a55a885a1f"}, + {file = "cppython-0.9.6.tar.gz", hash = "sha256:313729995ba1952d8a8e7fca23f3e5be6031a5bfefede747627407ca68d6ea6c"}, +] + +[[package]] +name = "dulwich" +version = "0.24.1" +requires_python = ">=3.9" +summary = "Python Git Library" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0; python_version < \"3.11\"", + "urllib3>=1.25", +] +files = [ + {file = "dulwich-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a11ec69fc6604228804ddfc32c85b22bc627eca4cf4ff3f27dbe822e6f29477"}, + {file = "dulwich-0.24.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a9800df7238b586b4c38c00432776781bc889cf02d756dcfb8dc0ecb8fc47a33"}, + {file = "dulwich-0.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3baab4a01aff890e2e6551ccbd33eb2a44173c897f0f027ad3aeab0fb057ec44"}, + {file = "dulwich-0.24.1-cp313-cp313-win32.whl", hash = "sha256:b39689aa4d143ba1fb0a687a4eb93d2e630d2c8f940aaa6c6911e9c8dca16e6a"}, + {file = "dulwich-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:8fca9b863b939b52c5f759d292499f0d21a7bf7f8cbb9fdeb8cdd9511c5bc973"}, + {file = "dulwich-0.24.1-py3-none-any.whl", hash = "sha256:57cc0dc5a21059698ffa4ed9a7272f1040ec48535193df84b0ee6b16bf615676"}, + {file = "dulwich-0.24.1.tar.gz", hash = "sha256:e19fd864f10f02bb834bb86167d92dcca1c228451b04458761fc13dabd447758"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +requires_python = ">=3.10" +summary = "Python port of markdown-it. Markdown parsing, done right!" +groups = ["default"] +dependencies = [ + "mdurl~=0.1", +] +files = [ + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" +groups = ["default"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "packaging" +version = "25.0" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["default"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +requires_python = ">=3.9" +summary = "Data validation using Python type hints" +groups = ["default"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.33.2", + "typing-extensions>=4.12.2", + "typing-inspection>=0.4.0", +] +files = [ + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +requires_python = ">=3.9" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["default"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["default"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[[package]] +name = "requests" +version = "2.32.4" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, +] + +[[package]] +name = "rich" +version = "14.1.0" +requires_python = ">=3.8.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +groups = ["default"] +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", +] +files = [ + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +requires_python = ">=3.7" +summary = "Tool to Detect Surrounding Shell" +groups = ["default"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "typer" +version = "0.16.0" +requires_python = ">=3.7" +summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." +groups = ["default"] +dependencies = [ + "click>=8.0.0", + "rich>=10.11.0", + "shellingham>=1.3.0", + "typing-extensions>=3.7.4.3", +] +files = [ + {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, + {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250809" +requires_python = ">=3.9" +summary = "Typing stubs for requests" +groups = ["default"] +dependencies = [ + "urllib3>=2", +] +files = [ + {file = "types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}, + {file = "types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +requires_python = ">=3.9" +summary = "Backported and Experimental Type Hints for Python 3.9+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +requires_python = ">=3.9" +summary = "Runtime typing introspection tools" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.12.0", +] +files = [ + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +requires_python = ">=3.9" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] diff --git a/examples/vcpkg_cmake/simple/pyproject.toml b/examples/vcpkg_cmake/simple/pyproject.toml index be94ba0..78c8428 100644 --- a/examples/vcpkg_cmake/simple/pyproject.toml +++ b/examples/vcpkg_cmake/simple/pyproject.toml @@ -15,7 +15,7 @@ dependencies = ["cppython[vcpkg, cmake, git]>=0.9.0"] [tool.cppython] install-path = "install" -dependencies = ["fmt>=11.0.2"] +dependencies = ["fmt>=11.2.0"] [tool.cppython.generators.cmake] diff --git a/examples/vcpkg_cmake/simple/src/main.cpp b/examples/vcpkg_cmake/simple/src/main.cpp new file mode 100644 index 0000000..4de3567 --- /dev/null +++ b/examples/vcpkg_cmake/simple/src/main.cpp @@ -0,0 +1,7 @@ +#include "fmt/color.h" + +int main() +{ + fmt::print(fg(fmt::terminal_color::cyan), "Hello fmt {}!\n", FMT_VERSION); + return 0; +} \ No newline at end of file diff --git a/tests/fixtures/vcpkg.py b/tests/fixtures/vcpkg.py new file mode 100644 index 0000000..b59fc28 --- /dev/null +++ b/tests/fixtures/vcpkg.py @@ -0,0 +1 @@ +"""Shared fixtures for VCPkg plugin tests""" diff --git a/tests/integration/examples/test_vcpkg_cmake.py b/tests/integration/examples/test_vcpkg_cmake.py index a1a3ad7..877aa04 100644 --- a/tests/integration/examples/test_vcpkg_cmake.py +++ b/tests/integration/examples/test_vcpkg_cmake.py @@ -6,22 +6,54 @@ import subprocess from pathlib import Path +from tomllib import loads from typer.testing import CliRunner -pytest_plugins = ['tests.fixtures.example'] +from cppython.console.schema import ConsoleInterface +from cppython.core.schema import ProjectConfiguration +from cppython.project import Project + +pytest_plugins = ['tests.fixtures.example', 'tests.fixtures.vcpkg'] class TestVcpkgCMake: """Test project variation of vcpkg and CMake""" + @staticmethod + def _create_project(skip_upload: bool = True) -> Project: + """Create a project instance with common configuration.""" + project_root = Path.cwd() + config = ProjectConfiguration(project_root=project_root, version=None, verbosity=2, debug=True) + interface = ConsoleInterface() + + pyproject_path = project_root / 'pyproject.toml' + pyproject_data = loads(pyproject_path.read_text(encoding='utf-8')) + + if skip_upload: + TestVcpkgCMake._ensure_vcpkg_config(pyproject_data) + pyproject_data['tool']['cppython']['providers']['vcpkg']['skip_upload'] = True + + return Project(config, interface, pyproject_data) + + @staticmethod + def _ensure_vcpkg_config(pyproject_data: dict) -> None: + """Helper method to ensure Vcpkg configuration exists in pyproject data""" + if 'tool' not in pyproject_data: + pyproject_data['tool'] = {} + if 'cppython' not in pyproject_data['tool']: + pyproject_data['tool']['cppython'] = {} + if 'providers' not in pyproject_data['tool']['cppython']: + pyproject_data['tool']['cppython']['providers'] = {} + if 'vcpkg' not in pyproject_data['tool']['cppython']['providers']: + pyproject_data['tool']['cppython']['providers']['vcpkg'] = {} + @staticmethod def test_simple(example_runner: CliRunner) -> None: """Simple project""" - # By nature of running the test, we require PDM to develop the project and so it will be installed - result = subprocess.run(['pdm', 'install'], capture_output=True, text=True, check=False) - - assert result.returncode == 0, f'PDM install failed: {result.stderr}' + # Create project and install dependencies + project = TestVcpkgCMake._create_project(skip_upload=False) + project.install() # Run the CMake configuration command result = subprocess.run(['cmake', '--preset=default'], capture_output=True, text=True, check=False) From 2a0f06ceaadb6f04157e36a53f49244e3c960258 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 17:55:29 -0400 Subject: [PATCH 64/68] Update test_vcpkg_cmake.py --- tests/integration/examples/test_vcpkg_cmake.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/examples/test_vcpkg_cmake.py b/tests/integration/examples/test_vcpkg_cmake.py index 877aa04..2488e63 100644 --- a/tests/integration/examples/test_vcpkg_cmake.py +++ b/tests/integration/examples/test_vcpkg_cmake.py @@ -8,6 +8,7 @@ from pathlib import Path from tomllib import loads +import pytest from typer.testing import CliRunner from cppython.console.schema import ConsoleInterface @@ -17,6 +18,7 @@ pytest_plugins = ['tests.fixtures.example', 'tests.fixtures.vcpkg'] +@pytest.mark.skip(reason='Address file locks.') class TestVcpkgCMake: """Test project variation of vcpkg and CMake""" From de30a25e1d6d9ece5d1c69a5e36564664f8ab0a1 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 18:42:39 -0400 Subject: [PATCH 65/68] Fix Tests --- cppython/plugins/cmake/builder.py | 24 ++++++++++++------------ tests/unit/plugins/cmake/test_presets.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cppython/plugins/cmake/builder.py b/cppython/plugins/cmake/builder.py index 69b86bb..b264cd7 100644 --- a/cppython/plugins/cmake/builder.py +++ b/cppython/plugins/cmake/builder.py @@ -179,13 +179,13 @@ def _update_configure_preset(existing_preset: ConfigurePreset, build_directory: existing_preset: The preset to update build_directory: The build directory to use """ - # Update existing preset to ensure it inherits from 'default' + # Update existing preset to ensure it inherits from 'cppython' if existing_preset.inherits is None: - existing_preset.inherits = 'default' # type: ignore[misc] - elif isinstance(existing_preset.inherits, str) and existing_preset.inherits != 'default': - existing_preset.inherits = ['default', existing_preset.inherits] # type: ignore[misc] - elif isinstance(existing_preset.inherits, list) and 'default' not in existing_preset.inherits: - existing_preset.inherits.insert(0, 'default') + existing_preset.inherits = 'cppython' # type: ignore[misc] + elif isinstance(existing_preset.inherits, str) and existing_preset.inherits != 'cppython': + existing_preset.inherits = ['cppython', existing_preset.inherits] # type: ignore[misc] + elif isinstance(existing_preset.inherits, list) and 'cppython' not in existing_preset.inherits: + existing_preset.inherits.insert(0, 'cppython') # Update binary directory if not set if not existing_preset.binaryDir: @@ -214,10 +214,10 @@ def _modify_presets( existing_preset = next( (p for p in root_preset.configurePresets if p.name == user_configure_preset.name), None ) - if existing_preset: - Builder._update_configure_preset(existing_preset, build_directory) - else: - root_preset.configurePresets.append(user_configure_preset) + if existing_preset: + Builder._update_configure_preset(existing_preset, build_directory) + else: + root_preset.configurePresets.append(user_configure_preset) if root_preset.buildPresets is None: root_preset.buildPresets = user_build_presets.copy() # type: ignore[misc] @@ -304,8 +304,8 @@ def write_root_presets( initial_json = file.read() initial_root_preset = CMakePresets.model_validate_json(initial_json) - # Ensure that the build_directory is relative to the preset_file - build_directory = build_directory.relative_to(preset_file.parent) + # Ensure that the build_directory is relative to the preset_file, allowing upward traversal + build_directory = build_directory.relative_to(preset_file.parent, walk_up=True) root_preset = Builder.generate_root_preset(preset_file, cppython_preset_file, cmake_data, build_directory) diff --git a/tests/unit/plugins/cmake/test_presets.py b/tests/unit/plugins/cmake/test_presets.py index 4c5995f..5b0949f 100644 --- a/tests/unit/plugins/cmake/test_presets.py +++ b/tests/unit/plugins/cmake/test_presets.py @@ -27,7 +27,7 @@ def test_generate_root_preset_new(project_data: ProjectData) -> None: assert any(p.name == 'test-configuration' for p in result.configurePresets) preset = next(p for p in result.configurePresets if p.name == 'test-configuration') - assert preset.inherits == 'default' + assert preset.inherits == 'cppython' @staticmethod def test_generate_root_preset_existing(project_data: ProjectData) -> None: From a92ff62c5e320122528c39649a8bd82d11c4501e Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sat, 16 Aug 2025 19:07:47 -0400 Subject: [PATCH 66/68] Exclude Linting Examples --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a9f08b5..5678dd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,9 @@ quote-style = "single" [tool.coverage.report] skip_empty = true +[tool.pyrefly] +project-excludes = ["examples"] + [tool.pdm] plugins = ["-e file:///${PROJECT_ROOT}"] From 5a14bf3139ab154934996535a921b2d2c096aa1d Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 17 Aug 2025 03:15:53 -0400 Subject: [PATCH 67/68] Remove Outdated Type Ignores --- cppython/core/schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cppython/core/schema.py b/cppython/core/schema.py index 7b43589..3678c65 100644 --- a/cppython/core/schema.py +++ b/cppython/core/schema.py @@ -44,7 +44,7 @@ class ProjectConfiguration(CPPythonModel, extra='forbid'): bool, Field(description='Debug mode. Additional processing will happen to expose more debug information') ] = False - @field_validator('verbosity') # type: ignore + @field_validator('verbosity') @classmethod def min_max(cls, value: int) -> int: """Validator that clamps the input value @@ -121,7 +121,7 @@ class CPPythonData(CPPythonModel, extra='forbid'): provider_data: Annotated[dict[str, Any], Field(description='Resolved provider configuration data')] generator_data: Annotated[dict[str, Any], Field(description='Resolved generator configuration data')] - @field_validator('configuration_path', 'install_path', 'tool_path', 'build_path') # type: ignore + @field_validator('configuration_path', 'install_path', 'tool_path', 'build_path') @classmethod def validate_absolute_path(cls, value: Path) -> Path: """Enforce the input is an absolute path From aa209437973747e5472c5dc1b41237344cc82448 Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 17 Aug 2025 03:22:03 -0400 Subject: [PATCH 68/68] Add Debug Log --- cppython/builder.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cppython/builder.py b/cppython/builder.py index efc07ba..fb0faf5 100644 --- a/cppython/builder.py +++ b/cppython/builder.py @@ -5,6 +5,7 @@ from importlib.metadata import entry_points from inspect import getmodule from logging import Logger +from pprint import pformat from typing import Any, cast from rich.console import Console @@ -556,4 +557,6 @@ def build( plugins = Plugins(generator=generator, provider=provider, scm=scm) + self._logger.debug('Project data:\n%s', pformat(dict(core_data))) + return Data(core_data, plugins, self._logger)