Skip to content
Merged
84 changes: 80 additions & 4 deletions cppython/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from cppython.data import Data, Plugins
from cppython.defaults import DefaultSCM
from cppython.utility.exception import PluginError
from cppython.utility.utility import TypeName


class Resolver:
Expand All @@ -59,14 +60,14 @@ def generate_plugins(
raw_generator_plugins = self.find_generators()
generator_plugins = self.filter_plugins(
raw_generator_plugins,
cppython_local_configuration.generator_name,
self._get_effective_generator_name(cppython_local_configuration),
'Generator',
)

raw_provider_plugins = self.find_providers()
provider_plugins = self.filter_plugins(
raw_provider_plugins,
cppython_local_configuration.provider_name,
self._get_effective_provider_name(cppython_local_configuration),
'Provider',
)

Expand All @@ -79,6 +80,74 @@ def generate_plugins(

return PluginBuildData(generator_type=generator_type, provider_type=provider_type, scm_type=scm_type)

def _get_effective_generator_name(self, config: CPPythonLocalConfiguration) -> str | None:
"""Get the effective generator name from configuration

Args:
config: The local configuration

Returns:
The generator name to use, or None for auto-detection
"""
if config.generators:
# For now, pick the first generator (in future, could support selection logic)
return list(config.generators.keys())[0]

# No generators specified, use auto-detection
return None

def _get_effective_provider_name(self, config: CPPythonLocalConfiguration) -> str | None:
"""Get the effective provider name from configuration

Args:
config: The local configuration

Returns:
The provider name to use, or None for auto-detection
"""
if config.providers:
# For now, pick the first provider (in future, could support selection logic)
return list(config.providers.keys())[0]

# No providers specified, use auto-detection
return None

def _get_effective_generator_config(
self, config: CPPythonLocalConfiguration, generator_name: str
) -> dict[str, Any]:
"""Get the effective generator configuration

Args:
config: The local configuration
generator_name: The name of the generator being used

Returns:
The configuration dict for the generator
"""
generator_type_name = TypeName(generator_name)
if config.generators and generator_type_name in config.generators:
return config.generators[generator_type_name]

# Return empty config if not found
return {}

def _get_effective_provider_config(self, config: CPPythonLocalConfiguration, provider_name: str) -> dict[str, Any]:
"""Get the effective provider configuration

Args:
config: The local configuration
provider_name: The name of the provider being used

Returns:
The configuration dict for the provider
"""
provider_type_name = TypeName(provider_name)
if config.providers and provider_type_name in config.providers:
return config.providers[provider_type_name]

# Return empty config if not found
return {}

@staticmethod
def generate_cppython_plugin_data(plugin_build_data: PluginBuildData) -> PluginCPPythonData:
"""Generates the CPPython plugin data from the resolved plugins
Expand Down Expand Up @@ -447,11 +516,18 @@ def build(
pep621_data = self._resolver.generate_pep621_data(pep621_configuration, self._project_configuration, scm)

# Create the chosen plugins
generator_config = self._resolver._get_effective_generator_config(
cppython_local_configuration, plugin_build_data.generator_type.name()
)
generator = self._resolver.create_generator(
core_data, pep621_data, cppython_local_configuration.generator, plugin_build_data.generator_type
core_data, pep621_data, generator_config, plugin_build_data.generator_type
)

provider_config = self._resolver._get_effective_provider_config(
cppython_local_configuration, plugin_build_data.provider_type.name()
)
provider = self._resolver.create_provider(
core_data, pep621_data, cppython_local_configuration.provider, plugin_build_data.provider_type
core_data, pep621_data, provider_config, plugin_build_data.provider_type
)

plugins = Plugins(generator=generator, provider=provider, scm=scm)
Expand Down
24 changes: 17 additions & 7 deletions cppython/core/resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,22 @@ def resolve_cppython(
if not modified_build_path.is_absolute():
modified_build_path = root_directory / modified_build_path

modified_provider_name = local_configuration.provider_name
modified_generator_name = local_configuration.generator_name
modified_provider_name = plugin_build_data.provider_name
modified_generator_name = plugin_build_data.generator_name

if modified_provider_name is None:
modified_provider_name = plugin_build_data.provider_name
modified_scm_name = plugin_build_data.scm_name

if modified_generator_name is None:
modified_generator_name = plugin_build_data.generator_name
# Extract provider and generator configuration data
provider_type_name = TypeName(modified_provider_name)
generator_type_name = TypeName(modified_generator_name)

modified_scm_name = plugin_build_data.scm_name
provider_data = {}
if local_configuration.providers and provider_type_name in local_configuration.providers:
provider_data = local_configuration.providers[provider_type_name]

generator_data = {}
if local_configuration.generators and generator_type_name in local_configuration.generators:
generator_data = local_configuration.generators[generator_type_name]

# Construct dependencies from the local configuration only
dependencies: list[Requirement] = []
Expand All @@ -173,6 +179,8 @@ def resolve_cppython(
generator_name=modified_generator_name,
scm_name=modified_scm_name,
dependencies=dependencies,
provider_data=provider_data,
generator_data=generator_data,
)
return cppython_data

Expand Down Expand Up @@ -200,6 +208,8 @@ def resolve_cppython_plugin(cppython_data: CPPythonData, plugin_type: type[Plugi
generator_name=cppython_data.generator_name,
scm_name=cppython_data.scm_name,
dependencies=cppython_data.dependencies,
provider_data=cppython_data.provider_data,
generator_data=cppython_data.generator_data,
)

return cast(CPPythonPluginData, plugin_data)
Expand Down
31 changes: 13 additions & 18 deletions cppython/core/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ class CPPythonData(CPPythonModel, extra='forbid'):
scm_name: TypeName
dependencies: list[Requirement]

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
@classmethod
def validate_absolute_path(cls, value: Path) -> Path:
Expand Down Expand Up @@ -302,29 +305,21 @@ class CPPythonLocalConfiguration(CPPythonModel, extra='forbid'):
),
] = Path('build')

provider: Annotated[ProviderData, Field(description="Provider plugin data associated with 'provider_name")] = (
ProviderData({})
)

provider_name: Annotated[
TypeName | None,
providers: Annotated[
dict[TypeName, ProviderData],
Field(
alias='provider-name',
description='If empty, the provider will be automatically deduced.',
description='Named provider configurations. Key is the provider name, value is the provider configuration.'
),
] = None

generator: Annotated[GeneratorData, Field(description="Generator plugin data associated with 'generator_name'")] = (
GeneratorData({})
)
] = {}

generator_name: Annotated[
TypeName | None,
generators: Annotated[
dict[TypeName, GeneratorData],
Field(
alias='generator-name',
description='If empty, the generator will be automatically deduced.',
description=(
'Named generator configurations. Key is the generator name, value is the generator configuration.'
)
),
] = None
] = {}

dependencies: Annotated[
list[str] | None,
Expand Down
2 changes: 1 addition & 1 deletion cppython/plugins/conan/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency]) ->
"""Creates a conanfile.py file with the necessary content."""
template_string = """
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan.tools.cmake import CMake, cmake_layout

class MyProject(ConanFile):
name = "myproject"
Expand Down
74 changes: 4 additions & 70 deletions cppython/plugins/conan/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import requests
from conan.api.conan_api import ConanAPI
from conan.api.model import ListPattern
from conan.internal.model.profile import Profile

from cppython.core.plugin_schema.generator import SyncConsumer
from cppython.core.plugin_schema.provider import Provider, ProviderPluginGroupData, SupportedProviderFeatures
Expand Down Expand Up @@ -109,8 +108,8 @@ def _install_dependencies(self, *, update: bool = False) -> None:
all_remotes = conan_api.remotes.list()
logger.debug('Available remotes: %s', [remote.name for remote in all_remotes])

# Get profiles with fallback to auto-detection
profile_host, profile_build = self._get_profiles(conan_api)
# Get profiles from resolved data
profile_host, profile_build = self.data.host_profile, self.data.build_profile

path = str(conanfile_path)
remotes = all_remotes
Expand Down Expand Up @@ -249,8 +248,8 @@ def publish(self) -> None:
remotes=all_remotes, # Use all remotes for dependency resolution during export
)

# Step 2: Get profiles with fallback to auto-detection
profile_host, profile_build = self._get_profiles(conan_api)
# Step 2: Get profiles from resolved data
profile_host, profile_build = self.data.host_profile, self.data.build_profile

# Step 3: Build dependency graph for the package - prepare parameters
path = str(conanfile_path)
Expand Down Expand Up @@ -305,68 +304,3 @@ def publish(self) -> None:
)
else:
raise ProviderInstallationError('conan', 'No packages found to upload')

def _apply_profile_processing(self, profiles: list[Profile], conan_api: ConanAPI, cache_settings: Any) -> 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
"""
logger = logging.getLogger('cppython.conan')

# 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')

# Process settings to initialize processed_settings
for profile in profiles:
try:
profile.process_settings(cache_settings)
except (AttributeError, Exception) as settings_error:
logger.debug('Settings processing failed for profile: %s', str(settings_error))

def _get_profiles(self, conan_api: ConanAPI) -> tuple[Profile, Profile]:
"""Get Conan profiles with fallback to auto-detection.

Args:
conan_api: The Conan API instance

Returns:
A tuple of (profile_host, profile_build) objects
"""
logger = logging.getLogger('cppython.conan')

try:
# Gather default profile paths, these can raise exceptions if not available
profile_host_path = conan_api.profiles.get_default_host()
profile_build_path = conan_api.profiles.get_default_build()

# Load the actual profile objects, can raise if data is invalid
profile_host = conan_api.profiles.get_profile([profile_host_path])
profile_build = conan_api.profiles.get_profile([profile_build_path])

logger.debug('Using existing default profiles')
return profile_host, profile_build

except Exception as e:
logger.warning('Default profiles not available, using auto-detection. Conan message: %s', str(e))

# Create auto-detected profiles
profiles = [conan_api.profiles.detect(), conan_api.profiles.detect()]
cache_settings = conan_api.config.settings_yml

# Apply profile plugin processing to both profiles
self._apply_profile_processing(profiles, conan_api, cache_settings)

logger.debug('Auto-detected profiles with plugin processing applied')
return profiles[0], profiles[1]
Loading