Skip to content

Commit cf288b4

Browse files
committed
Add Presets File Write
1 parent 75ad312 commit cf288b4

File tree

4 files changed

+27
-240
lines changed

4 files changed

+27
-240
lines changed

cppython/plugins/conan/builder.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def __init__(self) -> None:
121121
self._filename = 'conanfile.py'
122122

123123
@staticmethod
124-
def _create_conanfile(conan_file: Path, dependencies: list[ConanDependency], name: str, version: str) -> None:
124+
def _create_conanfile(
125+
conan_file: Path, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path
126+
) -> None:
125127
"""Creates a conanfile.py file with the necessary content."""
126128
template_string = """
127129
from conan import ConanFile
@@ -140,6 +142,7 @@ def generate(self):
140142
deps = CMakeDeps(self)
141143
deps.generate()
142144
tc = CMakeToolchain(self)
145+
tc.user_presets_path = "${preset_file}"
143146
tc.generate()
144147
145148
def build(self):
@@ -158,6 +161,7 @@ def package(self):
158161
'name': name,
159162
'version': version,
160163
'dependencies': [dependency.requires() for dependency in dependencies],
164+
'preset_file': str(preset_file),
161165
}
162166

163167
result = template.substitute(values)
@@ -166,7 +170,7 @@ def package(self):
166170
file.write(result)
167171

168172
def generate_conanfile(
169-
self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str
173+
self, directory: DirectoryPath, dependencies: list[ConanDependency], name: str, version: str, preset_file: Path
170174
) -> None:
171175
"""Generate a conanfile.py file for the project."""
172176
conan_file = directory / self._filename
@@ -181,4 +185,4 @@ def generate_conanfile(
181185
conan_file.write_text(modified.code, encoding='utf-8')
182186
else:
183187
directory.mkdir(parents=True, exist_ok=True)
184-
self._create_conanfile(conan_file, dependencies, name, version)
188+
self._create_conanfile(conan_file, dependencies, name, version, preset_file)

cppython/plugins/conan/plugin.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def _prepare_installation(self) -> Path:
9191
resolved_dependencies,
9292
self.core_data.pep621_data.name,
9393
self.core_data.pep621_data.version,
94+
self.core_data.cppython_data.tool_path / 'ConanPresets.json',
9495
)
9596

9697
# Ensure build directory exists
@@ -120,15 +121,8 @@ def _run_conan_install(self, conanfile_path: Path, update: bool, logger) -> None
120121
command.extend(['--build', 'missing'])
121122

122123
# Add profiles if specified
123-
if self.data.host_profile and str(self.data.host_profile) != 'default':
124-
command.extend(['--profile:host', str(self.data.host_profile)])
125-
else:
126-
command.extend(['--profile:host', 'default'])
127-
128-
if self.data.build_profile and str(self.data.build_profile) != 'default':
129-
command.extend(['--profile:build', str(self.data.build_profile)])
130-
else:
131-
command.extend(['--profile:build', 'default'])
124+
command.extend(['--profile:host', 'default'])
125+
command.extend(['--profile:build', 'default'])
132126

133127
# Add update flag if needed
134128
if update:
@@ -225,15 +219,8 @@ def publish(self) -> None:
225219
command.extend(['--build', 'missing'])
226220

227221
# Add profiles
228-
if self.data.host_profile and str(self.data.host_profile) != 'default':
229-
command.extend(['--profile:host', str(self.data.host_profile)])
230-
else:
231-
command.extend(['--profile:host', 'default'])
232-
233-
if self.data.build_profile and str(self.data.build_profile) != 'default':
234-
command.extend(['--profile:build', str(self.data.build_profile)])
235-
else:
236-
command.extend(['--profile:build', 'default'])
222+
command.extend(['--profile:host', 'default'])
223+
command.extend(['--profile:build', 'default'])
237224

238225
# Log the command being executed
239226
logger.info('Executing conan create command: %s', ' '.join(command))
Lines changed: 5 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
"""Provides functionality to resolve Conan-specific data for the CPPython project."""
22

3-
import importlib
4-
import logging
53
from pathlib import Path
64
from typing import Any
75

8-
from conan.api.conan_api import ConanAPI
9-
from conan.internal.model.profile import Profile
106
from packaging.requirements import Requirement
117

128
from cppython.core.exception import ConfigException
@@ -18,193 +14,6 @@
1814
ConanVersion,
1915
ConanVersionRange,
2016
)
21-
from cppython.utility.exception import ProviderConfigurationError
22-
23-
24-
def _detect_cmake_program() -> str | None:
25-
"""Detect CMake program path from the cmake module if available.
26-
27-
Returns:
28-
Path to cmake executable, or None if not found
29-
"""
30-
try:
31-
# Try to import cmake module and get its executable path
32-
# Note: cmake is an optional dependency, so we import it conditionally
33-
cmake = importlib.import_module('cmake')
34-
35-
cmake_bin_dir = Path(cmake.CMAKE_BIN_DIR)
36-
37-
# Try common cmake executable names (pathlib handles platform differences)
38-
for cmake_name in ['cmake.exe', 'cmake']:
39-
cmake_exe = cmake_bin_dir / cmake_name
40-
if cmake_exe.exists():
41-
return str(cmake_exe)
42-
43-
return None
44-
except ImportError:
45-
# cmake module not available
46-
return None
47-
except (AttributeError, Exception):
48-
# If cmake module doesn't have expected attributes
49-
return None
50-
51-
52-
def _profile_post_process(
53-
profiles: list[Profile], conan_api: ConanAPI, cache_settings: Any, cmake_program: str | None = None
54-
) -> None:
55-
"""Apply profile plugin and settings processing to a list of profiles.
56-
57-
Args:
58-
profiles: List of profiles to process
59-
conan_api: The Conan API instance
60-
cache_settings: The settings configuration
61-
cmake_program: Optional path to cmake program to configure in profiles
62-
"""
63-
logger = logging.getLogger('cppython.conan')
64-
65-
# Get global configuration
66-
global_conf = conan_api.config.global_conf
67-
68-
# Apply profile plugin processing
69-
try:
70-
profile_plugin = conan_api.profiles._load_profile_plugin()
71-
if profile_plugin is not None:
72-
for profile in profiles:
73-
try:
74-
profile_plugin(profile)
75-
except Exception as plugin_error:
76-
logger.warning('Profile plugin failed for profile: %s', str(plugin_error))
77-
except (AttributeError, Exception):
78-
logger.debug('Profile plugin not available or failed to load')
79-
80-
# Apply the full profile processing pipeline for each profile
81-
for profile in profiles:
82-
# Set cmake program configuration if provided
83-
if cmake_program is not None:
84-
try:
85-
# Set the tools.cmake:cmake_program configuration in the profile
86-
profile.conf.update('tools.cmake:cmake_program', cmake_program)
87-
logger.debug('Set tools.cmake:cmake_program=%s in profile', cmake_program)
88-
except (AttributeError, Exception) as cmake_error:
89-
logger.debug('Failed to set cmake program configuration: %s', str(cmake_error))
90-
91-
# Process settings to initialize processed_settings
92-
try:
93-
profile.process_settings(cache_settings)
94-
except (AttributeError, Exception) as settings_error:
95-
logger.debug('Settings processing failed for profile: %s', str(settings_error))
96-
97-
# Validate configuration
98-
try:
99-
profile.conf.validate()
100-
except (AttributeError, Exception) as conf_error:
101-
logger.debug('Configuration validation failed for profile: %s', str(conf_error))
102-
103-
# Apply global configuration to the profile
104-
try:
105-
if global_conf is not None:
106-
profile.conf.rebase_conf_definition(global_conf)
107-
except (AttributeError, Exception) as rebase_error:
108-
logger.debug('Configuration rebase failed for profile: %s', str(rebase_error))
109-
110-
111-
def _apply_cmake_config_to_profile(profile: Profile, cmake_program: str | None, profile_type: str) -> None:
112-
"""Apply cmake program configuration to a profile.
113-
114-
Args:
115-
profile: The profile to configure
116-
cmake_program: Path to cmake program to configure
117-
profile_type: Type of profile (for logging)
118-
"""
119-
if cmake_program is not None:
120-
logger = logging.getLogger('cppython.conan')
121-
try:
122-
profile.conf.update('tools.cmake:cmake_program', cmake_program)
123-
logger.debug('Set tools.cmake:cmake_program=%s in %s profile', cmake_program, profile_type)
124-
except (AttributeError, Exception) as cmake_error:
125-
logger.debug('Failed to set cmake program in %s profile: %s', profile_type, str(cmake_error))
126-
127-
128-
def _resolve_profiles(
129-
host_profile_name: str | None, build_profile_name: str | None, conan_api: ConanAPI, cmake_program: str | None = None
130-
) -> tuple[Profile, Profile]:
131-
"""Resolve host and build profiles, with fallback to auto-detection.
132-
133-
Args:
134-
host_profile_name: The host profile name to resolve, or None for auto-detection
135-
build_profile_name: The build profile name to resolve, or None for auto-detection
136-
conan_api: The Conan API instance
137-
cmake_program: Optional path to cmake program to configure in profiles
138-
139-
Returns:
140-
A tuple of (host_profile, build_profile)
141-
"""
142-
logger = logging.getLogger('cppython.conan')
143-
144-
def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
145-
"""Helper to resolve a single profile."""
146-
profile_type = 'host' if is_host else 'build'
147-
148-
if profile_name is not None and profile_name != 'default':
149-
# Explicitly specified profile name (not the default) - fail if not found
150-
try:
151-
logger.debug('Loading %s profile: %s', profile_type, profile_name)
152-
profile = conan_api.profiles.get_profile([profile_name])
153-
logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name)
154-
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
155-
return profile
156-
except Exception as e:
157-
logger.error('Failed to load %s profile %s: %s', profile_type, profile_name, str(e))
158-
raise ProviderConfigurationError(
159-
'conan',
160-
f'Failed to load {profile_type} profile {profile_name}: {str(e)}',
161-
f'{profile_type}_profile',
162-
) from e
163-
elif profile_name == 'default':
164-
# Try to load default profile, but fall back to auto-detection if it fails
165-
try:
166-
logger.debug('Loading %s profile: %s', profile_type, profile_name)
167-
profile = conan_api.profiles.get_profile([profile_name])
168-
logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name)
169-
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
170-
return profile
171-
except Exception as e:
172-
logger.debug(
173-
'Failed to load %s profile %s: %s. Falling back to auto-detection.',
174-
profile_type,
175-
profile_name,
176-
str(e),
177-
)
178-
# Fall back to auto-detection
179-
180-
try:
181-
if is_host:
182-
default_profile_path = conan_api.profiles.get_default_host()
183-
else:
184-
default_profile_path = conan_api.profiles.get_default_build()
185-
186-
profile = conan_api.profiles.get_profile([default_profile_path])
187-
logger.debug('Using default %s profile', profile_type)
188-
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
189-
return profile
190-
except Exception as e:
191-
logger.debug('Default %s profile not available, using auto-detection: %s', profile_type, str(e))
192-
193-
# Create auto-detected profile
194-
profile = conan_api.profiles.detect()
195-
cache_settings = conan_api.config.settings_yml
196-
197-
# Apply profile plugin processing
198-
_profile_post_process([profile], conan_api, cache_settings, cmake_program)
199-
200-
logger.debug('Auto-detected %s profile with plugin processing applied', profile_type)
201-
return profile
202-
203-
# Resolve both profiles
204-
host_profile = _resolve_profile(host_profile_name, is_host=True)
205-
build_profile = _resolve_profile(build_profile_name, is_host=False)
206-
207-
return host_profile, build_profile
20817

20918

21019
def _handle_single_specifier(name: str, specifier) -> ConanDependency:
@@ -257,7 +66,7 @@ def resolve_conan_dependency(requirement: Requirement) -> ConanDependency:
25766
return _handle_single_specifier(requirement.name, next(iter(specifiers)))
25867

25968
# Handle multiple specifiers - convert to Conan range syntax
260-
range_parts = []
69+
range_parts: list[str] = []
26170

26271
# Define order for operators to ensure consistent output
26372
operator_order = ['>=', '>', '<=', '<', '!=']
@@ -305,20 +114,13 @@ def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> Conan
305114
"""
306115
parsed_data = ConanConfiguration(**data)
307116

308-
# Initialize Conan API for profile resolution
309-
conan_api = ConanAPI()
117+
profile_dir = Path(parsed_data.profile_dir)
310118

311-
# Try to detect cmake program path from current virtual environment
312-
cmake_program = _detect_cmake_program()
313-
314-
# Resolve profiles
315-
host_profile, build_profile = _resolve_profiles(
316-
parsed_data.host_profile, parsed_data.build_profile, conan_api, cmake_program
317-
)
119+
if not profile_dir.is_absolute():
120+
profile_dir = core_data.cppython_data.tool_path / profile_dir
318121

319122
return ConanData(
320123
remotes=parsed_data.remotes,
321124
skip_upload=parsed_data.skip_upload,
322-
host_profile=host_profile,
323-
build_profile=build_profile,
125+
profile_dir=profile_dir,
324126
)

cppython/plugins/conan/schema.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pathlib import Path
2+
13
"""Conan plugin schema
24
35
This module defines Pydantic models used for integrating the Conan
@@ -8,7 +10,6 @@
810
import re
911
from typing import Annotated
1012

11-
from conan.internal.model.profile import Profile
1213
from pydantic import Field, field_validator
1314

1415
from cppython.core.schema import CPPythonModel
@@ -294,8 +295,7 @@ class ConanData(CPPythonModel):
294295

295296
remotes: list[str]
296297
skip_upload: bool
297-
host_profile: Profile
298-
build_profile: Profile
298+
profile_dir: Path
299299

300300

301301
class ConanConfiguration(CPPythonModel):
@@ -307,19 +307,13 @@ class ConanConfiguration(CPPythonModel):
307307
] = ['conancenter']
308308
skip_upload: Annotated[
309309
bool,
310-
Field(description='If true, skip uploading packages during publish (local-only mode).'),
310+
Field(description='If true, skip uploading packages to a remote during publishing.'),
311311
] = False
312-
host_profile: Annotated[
313-
str | None,
314-
Field(
315-
description='Conan host profile defining the target platform where the built software will run. '
316-
'Used for cross-compilation scenarios.'
317-
),
318-
] = 'default'
319-
build_profile: Annotated[
320-
str | None,
312+
profile_dir: Annotated[
313+
str,
321314
Field(
322-
description='Conan build profile defining the platform where the compilation process executes. '
323-
'Typically matches the development machine.'
315+
description='Directory containing Conan profiles. Profiles will be looked up relative to this directory. '
316+
'If profiles do not exist in this directory, Conan will fall back to default profiles.'
317+
"If a relative path is provided, it will be resolved relative to the tool's working directory."
324318
),
325-
] = 'default'
319+
] = 'profiles'

0 commit comments

Comments
 (0)