Skip to content

Commit 55e792e

Browse files
committed
CMake Program Path
1 parent d649d4b commit 55e792e

File tree

2 files changed

+102
-20
lines changed

2 files changed

+102
-20
lines changed

cppython/plugins/conan/resolution.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Provides functionality to resolve Conan-specific data for the CPPython project."""
22

3+
import importlib
34
import logging
5+
from pathlib import Path
46
from typing import Any
57

68
from conan.api.conan_api import ConanAPI
@@ -19,13 +21,44 @@
1921
from cppython.utility.exception import ProviderConfigurationError
2022

2123

22-
def _profile_post_process(profiles: list[Profile], conan_api: ConanAPI, cache_settings: Any) -> None:
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:
2355
"""Apply profile plugin and settings processing to a list of profiles.
2456
2557
Args:
2658
profiles: List of profiles to process
2759
conan_api: The Conan API instance
2860
cache_settings: The settings configuration
61+
cmake_program: Optional path to cmake program to configure in profiles
2962
"""
3063
logger = logging.getLogger('cppython.conan')
3164

@@ -46,6 +79,15 @@ def _profile_post_process(profiles: list[Profile], conan_api: ConanAPI, cache_se
4679

4780
# Apply the full profile processing pipeline for each profile
4881
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+
4991
# Process settings to initialize processed_settings
5092
try:
5193
profile.process_settings(cache_settings)
@@ -66,15 +108,33 @@ def _profile_post_process(profiles: list[Profile], conan_api: ConanAPI, cache_se
66108
logger.debug('Configuration rebase failed for profile: %s', str(rebase_error))
67109

68110

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+
69128
def _resolve_profiles(
70-
host_profile_name: str | None, build_profile_name: str | None, conan_api: ConanAPI
129+
host_profile_name: str | None, build_profile_name: str | None, conan_api: ConanAPI, cmake_program: str | None = None
71130
) -> tuple[Profile, Profile]:
72131
"""Resolve host and build profiles, with fallback to auto-detection.
73132
74133
Args:
75134
host_profile_name: The host profile name to resolve, or None for auto-detection
76135
build_profile_name: The build profile name to resolve, or None for auto-detection
77136
conan_api: The Conan API instance
137+
cmake_program: Optional path to cmake program to configure in profiles
78138
79139
Returns:
80140
A tuple of (host_profile, build_profile)
@@ -91,6 +151,7 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
91151
logger.debug('Loading %s profile: %s', profile_type, profile_name)
92152
profile = conan_api.profiles.get_profile([profile_name])
93153
logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name)
154+
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
94155
return profile
95156
except Exception as e:
96157
logger.error('Failed to load %s profile %s: %s', profile_type, profile_name, str(e))
@@ -105,6 +166,7 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
105166
logger.debug('Loading %s profile: %s', profile_type, profile_name)
106167
profile = conan_api.profiles.get_profile([profile_name])
107168
logger.debug('Successfully loaded %s profile: %s', profile_type, profile_name)
169+
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
108170
return profile
109171
except Exception as e:
110172
logger.debug(
@@ -123,6 +185,7 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
123185

124186
profile = conan_api.profiles.get_profile([default_profile_path])
125187
logger.debug('Using default %s profile', profile_type)
188+
_apply_cmake_config_to_profile(profile, cmake_program, profile_type)
126189
return profile
127190
except Exception as e:
128191
logger.warning('Default %s profile not available, using auto-detection: %s', profile_type, str(e))
@@ -132,7 +195,7 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
132195
cache_settings = conan_api.config.settings_yml
133196

134197
# Apply profile plugin processing
135-
_profile_post_process([profile], conan_api, cache_settings)
198+
_profile_post_process([profile], conan_api, cache_settings, cmake_program)
136199

137200
logger.debug('Auto-detected %s profile with plugin processing applied', profile_type)
138201
return profile
@@ -245,8 +308,13 @@ def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> Conan
245308
# Initialize Conan API for profile resolution
246309
conan_api = ConanAPI()
247310

311+
# Try to detect cmake program path from current virtual environment
312+
cmake_program = _detect_cmake_program()
313+
248314
# Resolve profiles
249-
host_profile, build_profile = _resolve_profiles(parsed_data.host_profile, parsed_data.build_profile, conan_api)
315+
host_profile, build_profile = _resolve_profiles(
316+
parsed_data.host_profile, parsed_data.build_profile, conan_api, cmake_program
317+
)
250318

251319
return ConanData(
252320
remotes=parsed_data.remotes,

tests/unit/plugins/conan/test_resolution.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ def test_resolve_profiles_by_name(self) -> None:
280280
mock_build_profile = Mock()
281281
mock_conan_api.profiles.get_profile.side_effect = [mock_host_profile, mock_build_profile]
282282

283-
host_result, build_result = _resolve_profiles('host-profile', 'build-profile', mock_conan_api)
283+
host_result, build_result = _resolve_profiles(
284+
'host-profile', 'build-profile', mock_conan_api, cmake_program=None
285+
)
284286

285287
assert host_result == mock_host_profile
286288
assert build_result == mock_build_profile
@@ -294,7 +296,7 @@ def test_resolve_profiles_by_name_failure(self) -> None:
294296
mock_conan_api.profiles.get_profile.side_effect = Exception('Profile not found')
295297

296298
with pytest.raises(ProviderConfigurationError, match='Failed to load host profile'):
297-
_resolve_profiles('missing-profile', 'other-profile', mock_conan_api)
299+
_resolve_profiles('missing-profile', 'other-profile', mock_conan_api, cmake_program=None)
298300

299301
def test_resolve_profiles_auto_detect(self) -> None:
300302
"""Test auto-detecting profiles."""
@@ -308,7 +310,7 @@ def test_resolve_profiles_auto_detect(self) -> None:
308310
mock_conan_api.profiles.get_default_build.return_value = mock_build_default_path
309311
mock_conan_api.profiles.get_profile.side_effect = [mock_host_profile, mock_build_profile]
310312

311-
host_result, build_result = _resolve_profiles(None, None, mock_conan_api)
313+
host_result, build_result = _resolve_profiles(None, None, mock_conan_api, cmake_program=None)
312314

313315
assert host_result == mock_host_profile
314316
assert build_result == mock_build_profile
@@ -334,14 +336,14 @@ def test_resolve_profiles_fallback_to_detect(self, mock_post_process: Mock) -> N
334336
mock_conan_api.profiles.detect.side_effect = [mock_host_profile, mock_build_profile]
335337
mock_conan_api.config.settings_yml = mock_cache_settings
336338

337-
host_result, build_result = _resolve_profiles(None, None, mock_conan_api)
339+
host_result, build_result = _resolve_profiles(None, None, mock_conan_api, cmake_program=None)
338340

339341
assert host_result == mock_host_profile
340342
assert build_result == mock_build_profile
341343
assert mock_conan_api.profiles.detect.call_count == 2
342344
assert mock_post_process.call_count == 2
343-
mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings)
344-
mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings)
345+
mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings, None)
346+
mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings, None)
345347

346348
@patch('cppython.plugins.conan.resolution._profile_post_process')
347349
def test_resolve_profiles_default_fallback_to_detect(self, mock_post_process: Mock) -> None:
@@ -360,23 +362,27 @@ def test_resolve_profiles_default_fallback_to_detect(self, mock_post_process: Mo
360362
mock_conan_api.profiles.detect.side_effect = [mock_host_profile, mock_build_profile]
361363
mock_conan_api.config.settings_yml = mock_cache_settings
362364

363-
host_result, build_result = _resolve_profiles('default', 'default', mock_conan_api)
365+
host_result, build_result = _resolve_profiles('default', 'default', mock_conan_api, cmake_program=None)
364366

365367
assert host_result == mock_host_profile
366368
assert build_result == mock_build_profile
367369
assert mock_conan_api.profiles.detect.call_count == 2
368370
assert mock_post_process.call_count == 2
369-
mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings)
370-
mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings)
371+
mock_post_process.assert_any_call([mock_host_profile], mock_conan_api, mock_cache_settings, None)
372+
mock_post_process.assert_any_call([mock_build_profile], mock_conan_api, mock_cache_settings, None)
371373

372374

373375
class TestResolveConanData:
374376
"""Test Conan data resolution."""
375377

376378
@patch('cppython.plugins.conan.resolution.ConanAPI')
377379
@patch('cppython.plugins.conan.resolution._resolve_profiles')
378-
def test_resolve_conan_data_with_profiles(self, mock_resolve_profiles: Mock, mock_conan_api_class: Mock) -> None:
380+
@patch('cppython.plugins.conan.resolution._detect_cmake_program')
381+
def test_resolve_conan_data_with_profiles(
382+
self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock
383+
) -> None:
379384
"""Test resolving ConanData with profile configuration."""
385+
mock_detect_cmake.return_value = None # No cmake detected for test
380386
mock_conan_api = Mock()
381387
mock_conan_api_class.return_value = mock_conan_api
382388

@@ -395,12 +401,16 @@ def test_resolve_conan_data_with_profiles(self, mock_resolve_profiles: Mock, moc
395401
assert result.remotes == ['conancenter']
396402

397403
# Verify profile resolution was called correctly
398-
mock_resolve_profiles.assert_called_once_with('linux-x64', 'linux-gcc11', mock_conan_api)
404+
mock_resolve_profiles.assert_called_once_with('linux-x64', 'linux-gcc11', mock_conan_api, None)
399405

400406
@patch('cppython.plugins.conan.resolution.ConanAPI')
401407
@patch('cppython.plugins.conan.resolution._resolve_profiles')
402-
def test_resolve_conan_data_default_profiles(self, mock_resolve_profiles: Mock, mock_conan_api_class: Mock) -> None:
408+
@patch('cppython.plugins.conan.resolution._detect_cmake_program')
409+
def test_resolve_conan_data_default_profiles(
410+
self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock
411+
) -> None:
403412
"""Test resolving ConanData with default profile configuration."""
413+
mock_detect_cmake.return_value = None # No cmake detected for test
404414
mock_conan_api = Mock()
405415
mock_conan_api_class.return_value = mock_conan_api
406416

@@ -419,12 +429,16 @@ def test_resolve_conan_data_default_profiles(self, mock_resolve_profiles: Mock,
419429
assert result.remotes == ['conancenter'] # Default remote
420430

421431
# Verify profile resolution was called with default values
422-
mock_resolve_profiles.assert_called_once_with('default', 'default', mock_conan_api)
432+
mock_resolve_profiles.assert_called_once_with('default', 'default', mock_conan_api, None)
423433

424434
@patch('cppython.plugins.conan.resolution.ConanAPI')
425435
@patch('cppython.plugins.conan.resolution._resolve_profiles')
426-
def test_resolve_conan_data_null_profiles(self, mock_resolve_profiles: Mock, mock_conan_api_class: Mock) -> None:
436+
@patch('cppython.plugins.conan.resolution._detect_cmake_program')
437+
def test_resolve_conan_data_null_profiles(
438+
self, mock_detect_cmake: Mock, mock_resolve_profiles: Mock, mock_conan_api_class: Mock
439+
) -> None:
427440
"""Test resolving ConanData with null profile configuration."""
441+
mock_detect_cmake.return_value = None # No cmake detected for test
428442
mock_conan_api = Mock()
429443
mock_conan_api_class.return_value = mock_conan_api
430444

@@ -443,7 +457,7 @@ def test_resolve_conan_data_null_profiles(self, mock_resolve_profiles: Mock, moc
443457
assert result.remotes == []
444458

445459
# Verify profile resolution was called with None values
446-
mock_resolve_profiles.assert_called_once_with(None, None, mock_conan_api)
460+
mock_resolve_profiles.assert_called_once_with(None, None, mock_conan_api, None)
447461

448462
def test_auto_detected_profile_gets_post_processed(self, conan_mock_api: Mock):
449463
"""Test that auto-detected profiles get proper post-processing.
@@ -467,7 +481,7 @@ def test_auto_detected_profile_gets_post_processed(self, conan_mock_api: Mock):
467481
conan_mock_api.config.global_conf = Mock()
468482

469483
# Call the resolution - this should trigger auto-detection and post-processing
470-
host_profile, build_profile = _resolve_profiles(None, None, conan_mock_api)
484+
host_profile, build_profile = _resolve_profiles(None, None, conan_mock_api, cmake_program=None)
471485

472486
# Verify that process_settings was called on both profiles
473487
assert mock_profile.process_settings.call_count == EXPECTED_PROFILE_CALL_COUNT

0 commit comments

Comments
 (0)