Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions cppython/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,10 @@ def solve(
return combos[0]

@staticmethod
def create_scm(
def create_scm[T: SCM](
core_data: CoreData,
scm_type: type[SCM],
) -> SCM:
scm_type: type[T],
) -> T:
"""Creates a source control manager from input configuration

Args:
Expand All @@ -391,17 +391,17 @@ def create_scm(
cppython_plugin_data = resolve_cppython_plugin(core_data.cppython_data, scm_type)
scm_data = resolve_scm(core_data.project_data, cppython_plugin_data)

plugin = cast(SCM, scm_type(scm_data))
plugin = scm_type(scm_data)

return plugin

def create_generator(
def create_generator[T: Generator](
self,
core_data: CoreData,
pep621_data: PEP621Data,
generator_configuration: dict[str, Any],
generator_type: type[Generator],
) -> Generator:
generator_type: type[T],
) -> T:
"""Creates a generator from input configuration

Args:
Expand All @@ -428,15 +428,15 @@ def create_generator(
cppython_data=cppython_plugin_data,
)

return cast(Generator, generator_type(generator_data, core_plugin_data, generator_configuration))
return generator_type(generator_data, core_plugin_data, generator_configuration)

def create_provider(
def create_provider[T: Provider](
self,
core_data: CoreData,
pep621_data: PEP621Data,
provider_configuration: dict[str, Any],
provider_type: type[Provider],
) -> Provider:
provider_type: type[T],
) -> T:
"""Creates Providers from input data

Args:
Expand All @@ -463,7 +463,7 @@ def create_provider(
cppython_data=cppython_plugin_data,
)

return cast(Provider, provider_type(provider_data, core_plugin_data, provider_configuration))
return provider_type(provider_data, core_plugin_data, provider_configuration)


class Builder:
Expand Down
4 changes: 2 additions & 2 deletions cppython/core/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from abc import abstractmethod
from pathlib import Path
from typing import Annotated, Any, NewType, Protocol, runtime_checkable
from typing import Annotated, Any, NewType, Protocol, Self, runtime_checkable

from packaging.requirements import Requirement
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
Expand Down Expand Up @@ -80,7 +80,7 @@ class PEP621Configuration(CPPythonModel):

@model_validator(mode='after') # type: ignore
@classmethod
def dynamic_data(cls, model: 'PEP621Configuration') -> 'PEP621Configuration':
def dynamic_data(cls, model: Self) -> Self:
"""Validates that dynamic data is represented correctly

Args:
Expand Down
13 changes: 12 additions & 1 deletion cppython/plugins/cmake/resolution.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Builder to help resolve cmake state"""

import os
from pathlib import Path
from typing import Any

from cppython.core.schema import CorePluginData
Expand All @@ -24,4 +26,13 @@ def resolve_cmake_data(data: dict[str, Any], core_data: CorePluginData) -> CMake
if not modified_preset_file.is_absolute():
modified_preset_file = root_directory / modified_preset_file

return CMakeData(preset_file=modified_preset_file, configuration_name=parsed_data.configuration_name)
# Resolve cmake binary: environment variable takes precedence over configuration
cmake_binary: Path | None = None
if env_binary := os.environ.get('CMAKE_BINARY'):
cmake_binary = Path(env_binary)
elif parsed_data.cmake_binary:
cmake_binary = parsed_data.cmake_binary

return CMakeData(
preset_file=modified_preset_file, configuration_name=parsed_data.configuration_name, cmake_binary=cmake_binary
)
8 changes: 8 additions & 0 deletions cppython/plugins/cmake/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class CMakeData(CPPythonModel):

preset_file: Path
configuration_name: str
cmake_binary: Path | None


class CMakeConfiguration(CPPythonModel):
Expand All @@ -131,3 +132,10 @@ class CMakeConfiguration(CPPythonModel):
'"default-release" will also be written'
),
] = 'default'
cmake_binary: Annotated[
Path | None,
Field(
description='Path to a specific CMake binary to use. If not specified, uses "cmake" from PATH. '
'Can be overridden via CMAKE_BINARY environment variable.'
),
] = None
7 changes: 5 additions & 2 deletions cppython/plugins/conan/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def package_info(self):

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)
"""
Expand All @@ -177,7 +176,11 @@ def export_sources(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,
) -> None:
"""Generate a conanfile.py file for the project."""
conan_file = directory / self._filename
Expand Down
37 changes: 32 additions & 5 deletions cppython/plugins/conan/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def __init__(

self._ensure_default_profiles()

# Initialize cmake_binary with system default. It may be overridden during sync.
self._cmake_binary = 'cmake'

@staticmethod
def features(directory: Path) -> SupportedFeatures:
"""Queries conan support
Expand Down Expand Up @@ -149,8 +152,11 @@ 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}'])

# Log the command being executed
logger.info('Executing conan command: conan %s', ' '.join(command_args))
# Add cmake binary configuration if specified
if self._cmake_binary and self._cmake_binary != 'cmake':
# Quote the path if it contains spaces
cmake_path = f'"{self._cmake_binary}"' if ' ' in self._cmake_binary else self._cmake_binary
command_args.extend(['-c', f'tools.cmake:cmake_program={cmake_path}'])

try:
# Use reusable Conan API instance instead of subprocess
Expand Down Expand Up @@ -200,10 +206,28 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData:
"""
for sync_type in consumer.sync_types():
if sync_type == CMakeSyncData:
return self._create_cmake_sync_data()
return self._sync_with_cmake(consumer)

raise NotSupportedError(f'Unsupported sync types: {consumer.sync_types()}')

def _sync_with_cmake(self, consumer: SyncConsumer) -> CMakeSyncData:
"""Synchronize with CMake generator and create sync data.

Args:
consumer: The CMake generator consumer

Returns:
CMakeSyncData configured for Conan integration
"""
# Extract cmake_binary from CMakeGenerator if available
if isinstance(consumer, CMakeGenerator) and not os.environ.get('CMAKE_BINARY'):
# Only override if not already set from environment variable
# Convert Path to string, or use 'cmake' if None
cmake_path = consumer.data.cmake_binary
self._cmake_binary = str(cmake_path) if cmake_path else 'cmake'

return self._create_cmake_sync_data()

def _create_cmake_sync_data(self) -> CMakeSyncData:
"""Creates CMake synchronization data with Conan toolchain configuration.

Expand Down Expand Up @@ -243,8 +267,11 @@ def publish(self) -> None:
# Add build mode (build everything for publishing)
command_args.extend(['--build', 'missing'])

# Log the command being executed
logger.info('Executing conan create command: conan %s', ' '.join(command_args))
# Add cmake binary configuration if specified
if self._cmake_binary and self._cmake_binary != 'cmake':
# Quote the path if it contains spaces
cmake_path = f'"{self._cmake_binary}"' if ' ' in self._cmake_binary else self._cmake_binary
command_args.extend(['-c', f'tools.cmake:cmake_program={cmake_path}'])

# Run conan create using reusable Conan API instance
# Change to project directory since Conan API might not handle cwd like subprocess
Expand Down
4 changes: 2 additions & 2 deletions cppython/plugins/conan/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __str__(self) -> str:
return version

@classmethod
def from_string(cls, version_str: str) -> 'ConanVersion':
def from_string(cls, version_str: str) -> ConanVersion:
"""Parse a version string into a ConanVersion."""
if '-' in version_str:
version_part, prerelease = version_str.split('-', 1)
Expand Down Expand Up @@ -219,7 +219,7 @@ def requires(self) -> str:
return result

@classmethod
def from_conan_reference(cls, reference: str) -> 'ConanDependency':
def from_conan_reference(cls, reference: str) -> ConanDependency:
"""Parse a Conan reference string into a ConanDependency.

Examples:
Expand Down
8 changes: 6 additions & 2 deletions cppython/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ def install(self) -> None:
self.logger.info('Installing project')
self.logger.info('Installing %s provider', self._data.plugins.provider.name())

# Sync before install to allow provider to access generator's resolved configuration
self._data.sync()

# Let provider handle its own exceptions for better error context
self._data.plugins.provider.install()
self._data.sync()

def update(self) -> None:
"""Updates project dependencies
Expand All @@ -88,9 +90,11 @@ def update(self) -> None:
self.logger.info('Updating project')
self.logger.info('Updating %s provider', self._data.plugins.provider.name())

# Sync before update to allow provider to access generator's resolved configuration
self._data.sync()

# Let provider handle its own exceptions for better error context
self._data.plugins.provider.update()
self._data.sync()

def publish(self) -> None:
"""Publishes the project
Expand Down
12 changes: 6 additions & 6 deletions cppython/test/pytest/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def fixture_provider_type(plugin_type: type[T]) -> type[T]:

@staticmethod
@pytest.fixture(name='generator_type', scope='session')
def fixture_generator_type(request: 'pytest.FixtureRequest') -> type[Generator]:
def fixture_generator_type(request: pytest.FixtureRequest) -> type[Generator]:
"""Provide generator variants for cross-plugin testing

Args:
Expand All @@ -207,7 +207,7 @@ def fixture_generator_type(request: 'pytest.FixtureRequest') -> type[Generator]:

@staticmethod
@pytest.fixture(name='scm_type', scope='session')
def fixture_scm_type(request: 'pytest.FixtureRequest') -> type[SCM]:
def fixture_scm_type(request: pytest.FixtureRequest) -> type[SCM]:
"""Provide SCM variants for cross-plugin testing

Args:
Expand Down Expand Up @@ -252,7 +252,7 @@ def fixture_plugin_group_data(
# Cross-plugin testing fixtures for ensuring compatibility
@staticmethod
@pytest.fixture(name='provider_type', scope='session')
def fixture_provider_type(request: 'pytest.FixtureRequest') -> type[Provider]:
def fixture_provider_type(request: pytest.FixtureRequest) -> type[Provider]:
"""Provide provider variants for cross-plugin testing

Args:
Expand All @@ -279,7 +279,7 @@ def fixture_generator_type(plugin_type: type[T]) -> type[T]:

@staticmethod
@pytest.fixture(name='scm_type', scope='session')
def fixture_scm_type(request: 'pytest.FixtureRequest') -> type[SCM]:
def fixture_scm_type(request: pytest.FixtureRequest) -> type[SCM]:
"""Provide SCM variants for cross-plugin testing

Args:
Expand Down Expand Up @@ -324,7 +324,7 @@ def fixture_plugin_group_data(
# Cross-plugin testing fixtures for ensuring compatibility
@staticmethod
@pytest.fixture(name='provider_type', scope='session')
def fixture_provider_type(request: 'pytest.FixtureRequest') -> type[Provider]:
def fixture_provider_type(request: pytest.FixtureRequest) -> type[Provider]:
"""Provide provider variants for cross-plugin testing

Args:
Expand All @@ -338,7 +338,7 @@ def fixture_provider_type(request: 'pytest.FixtureRequest') -> type[Provider]:

@staticmethod
@pytest.fixture(name='generator_type', scope='session')
def fixture_generator_type(request: 'pytest.FixtureRequest') -> type[Generator]:
def fixture_generator_type(request: pytest.FixtureRequest) -> type[Generator]:
"""Provide generator variants for cross-plugin testing

Args:
Expand Down
59 changes: 33 additions & 26 deletions examples/conan_cmake/library/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.30)
cmake_minimum_required(VERSION 4.0)

project(mathutils
VERSION 1.0.0
Expand All @@ -12,37 +12,22 @@ include(CMakePackageConfigHelpers)
# Dependencies
find_package(fmt REQUIRED)

# Library target
add_library(mathutils src/mathutils.cpp)
add_library(mathutils)
add_library(mathutils::mathutils ALIAS mathutils)

target_compile_features(mathutils PUBLIC cxx_std_17)
target_include_directories(mathutils PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
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*"
target_sources(mathutils
PUBLIC
FILE_SET CXX_MODULES FILES
src/mathutils.ixx
)

install(EXPORT mathutilsTargets
FILE mathutilsTargets.cmake
NAMESPACE mathutils::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils
)
target_compile_features(mathutils PUBLIC cxx_std_23)
target_link_libraries(mathutils PUBLIC fmt::fmt)

# Package configuration
# Generate package config files
write_basic_package_version_file(
mathutilsConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)

Expand All @@ -52,7 +37,29 @@ configure_package_config_file(
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils
)

install(FILES
install(
TARGETS mathutils
EXPORT mathutilsTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
FILE_SET CXX_MODULES
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils
)

# Create empty include directory to satisfy CMake's exported target requirements
# (module-only library with no headers, but CMake expects the directory to exist)
install(DIRECTORY DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# Export and package configuration
install(
EXPORT mathutilsTargets
FILE mathutilsTargets.cmake
NAMESPACE mathutils::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils
)

install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/mathutilsConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/mathutilsConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils
Expand Down
Loading
Loading