55installation, and synchronization with other tools.
66"""
77
8+ import subprocess
9+ from logging import getLogger
810from pathlib import Path
911from typing import Any
1012
11- from conan .api .conan_api import ConanAPI
12- from conan .api .model import ListPattern
13-
1413from cppython .core .plugin_schema .generator import SyncConsumer
1514from cppython .core .plugin_schema .provider import Provider , ProviderPluginGroupData , SupportedProviderFeatures
1615from cppython .core .schema import CorePluginData , Information , SupportedFeatures , SyncData
1918from cppython .plugins .conan .builder import Builder
2019from cppython .plugins .conan .resolution import resolve_conan_data , resolve_conan_dependency
2120from cppython .plugins .conan .schema import ConanData
22- from cppython .utility .exception import NotSupportedError , ProviderConfigurationError , ProviderInstallationError
21+ from cppython .utility .exception import NotSupportedError , ProviderInstallationError
2322from cppython .utility .utility import TypeName
2423
2524
@@ -58,43 +57,32 @@ def information() -> Information:
5857 return Information ()
5958
6059 def _install_dependencies (self , * , update : bool = False ) -> None :
61- """Install/update dependencies using Conan API .
60+ """Install/update dependencies using Conan CLI .
6261
6362 Args:
6463 update: If True, check remotes for newer versions/revisions and install those.
6564 If False, use cached versions when available.
6665 """
6766 operation = 'update' if update else 'install'
67+ logger = getLogger ('cppython.conan' )
6868
6969 try :
7070 # Setup environment and generate conanfile
71- conan_api , conanfile_path = self ._prepare_installation ()
71+ conanfile_path = self ._prepare_installation ()
7272 except Exception as e :
7373 raise ProviderInstallationError ('conan' , f'Failed to prepare { operation } environment: { e } ' , e ) from e
7474
7575 try :
76- # Load dependency graph
77- deps_graph = self ._load_dependency_graph (conan_api , conanfile_path , update )
78- except Exception as e :
79- raise ProviderInstallationError ('conan' , f'Failed to load dependency graph: { e } ' , e ) from e
80-
81- try :
82- # Install dependencies
83- self ._install_binaries (conan_api , deps_graph , update )
84- except Exception as e :
85- raise ProviderInstallationError ('conan' , f'Failed to install binary dependencies: { e } ' , e ) from e
86-
87- try :
88- # Generate consumer files
89- self ._generate_consumer_files (conan_api , deps_graph )
76+ # Install dependencies using conan install command
77+ self ._run_conan_install (conanfile_path , update , logger )
9078 except Exception as e :
91- raise ProviderInstallationError ('conan' , f'Failed to generate consumer files : { e } ' , e ) from e
79+ raise ProviderInstallationError ('conan' , f'Failed to install dependencies : { e } ' , e ) from e
9280
93- def _prepare_installation (self ) -> tuple [ ConanAPI , Path ] :
81+ def _prepare_installation (self ) -> Path :
9482 """Prepare the installation environment and generate conanfile.
9583
9684 Returns:
97- Tuple of (ConanAPI instance, conanfile path)
85+ Path to conanfile.py
9886 """
9987 # Resolve dependencies and generate conanfile.py
10088 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]:
10896 # Ensure build directory exists
10997 self .core_data .cppython_data .build_path .mkdir (parents = True , exist_ok = True )
11098
111- # Setup paths and API
112- conan_api = ConanAPI ()
99+ # Setup paths
113100 project_root = self .core_data .project_data .project_root
114101 conanfile_path = project_root / 'conanfile.py'
115102
116103 if not conanfile_path .exists ():
117104 raise FileNotFoundError ('Generated conanfile.py not found' )
118105
119- return conan_api , conanfile_path
106+ return conanfile_path
120107
121- def _load_dependency_graph (self , conan_api : ConanAPI , conanfile_path : Path , update : bool ) :
122- """Load and build the dependency graph .
108+ def _run_conan_install (self , conanfile_path : Path , update : bool , logger ) -> None :
109+ """Run conan install command .
123110
124111 Args:
125- conan_api: The Conan API instance
126112 conanfile_path: Path to the conanfile.py
127113 update: Whether to check for updates
128-
129- Returns:
130- The loaded dependency graph
114+ logger: Logger instance
131115 """
132- all_remotes = conan_api .remotes .list ()
133- profile_host , profile_build = self .data .host_profile , self .data .build_profile
134-
135- return conan_api .graph .load_graph_consumer (
136- path = str (conanfile_path ),
137- name = None ,
138- version = None ,
139- user = None ,
140- channel = None ,
141- lockfile = None ,
142- remotes = all_remotes ,
143- update = update or None ,
144- check_updates = update ,
145- is_build_require = False ,
146- profile_host = profile_host ,
147- profile_build = profile_build ,
148- )
116+ # Build conan install command
117+ command = ['conan' , 'install' , str (conanfile_path )]
149118
150- def _install_binaries ( self , conan_api : ConanAPI , deps_graph , update : bool ) -> None :
151- """Analyze and install binary dependencies.
119+ # Add build missing flag
120+ command . extend ([ '--build' , 'missing' ])
152121
153- Args:
154- conan_api: The Conan API instance
155- deps_graph: The dependency graph
156- update: Whether to check for updates
157- """
158- all_remotes = conan_api .remotes .list ()
159-
160- # Analyze binaries to determine what needs to be built/downloaded
161- conan_api .graph .analyze_binaries (
162- graph = deps_graph ,
163- build_mode = ['missing' ],
164- remotes = all_remotes ,
165- update = update or None ,
166- lockfile = None ,
167- )
122+ # 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' ])
168127
169- # Install all dependencies
170- conan_api .install .install_binaries (deps_graph = deps_graph , remotes = all_remotes )
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' ])
171132
172- def _generate_consumer_files (self , conan_api : ConanAPI , deps_graph ) -> None :
173- """Generate consumer files (CMake toolchain, deps, etc.).
133+ # Add update flag if needed
134+ if update :
135+ command .append ('--update' )
174136
175- Args:
176- conan_api: The Conan API instance
177- deps_graph: The dependency graph
178- """
179- project_root = self .core_data .project_data .project_root
137+ # Add output folder
138+ build_path = self .core_data .cppython_data .build_path
139+ command .extend (['--output-folder' , str (build_path )])
180140
181- conan_api .install .install_consumer (
182- deps_graph = deps_graph ,
183- generators = None , # Our conanfile.py template defines this
184- source_folder = str (project_root ),
185- )
141+ # Log the command being executed
142+ logger .info ('Executing conan command: %s' , ' ' .join (command ))
143+
144+ try :
145+ subprocess .run (
146+ command ,
147+ cwd = str (self .core_data .project_data .project_root ),
148+ check = True ,
149+ capture_output = True ,
150+ text = True ,
151+ )
152+ except subprocess .CalledProcessError as e :
153+ error_msg = e .stderr if e .stderr else str (e )
154+ logger .error ('Conan install failed: %s' , error_msg , exc_info = True )
155+ raise ProviderInstallationError ('conan' , error_msg , e ) from e
186156
187157 def install (self ) -> None :
188158 """Installs the provider"""
@@ -242,101 +212,90 @@ def publish(self) -> None:
242212 """Publishes the package using conan create workflow."""
243213 project_root = self .core_data .project_data .project_root
244214 conanfile_path = project_root / 'conanfile.py'
215+ logger = getLogger ('cppython.conan' )
245216
246217 if not conanfile_path .exists ():
247218 raise FileNotFoundError (f'conanfile.py not found at { conanfile_path } ' )
248219
249- conan_api = ConanAPI ()
250-
251- all_remotes = conan_api .remotes .list ()
252-
253- # Configure remotes for upload
254- configured_remotes = self ._get_configured_remotes (all_remotes )
255-
256- # Export the recipe to cache
257- ref , _ = conan_api .export .export (
258- path = str (conanfile_path ),
259- name = None ,
260- version = None ,
261- user = None ,
262- channel = None ,
263- lockfile = None ,
264- remotes = all_remotes ,
265- )
266-
267- # Build dependency graph and install
268- profile_host , profile_build = self .data .host_profile , self .data .build_profile
269- deps_graph = conan_api .graph .load_graph_consumer (
270- path = str (conanfile_path ),
271- name = None ,
272- version = None ,
273- user = None ,
274- channel = None ,
275- lockfile = None ,
276- remotes = all_remotes , # Use all remotes for dependency resolution
277- update = None ,
278- check_updates = False ,
279- is_build_require = False ,
280- profile_host = profile_host ,
281- profile_build = profile_build ,
282- )
283-
284- # Analyze and build binaries
285- conan_api .graph .analyze_binaries (
286- graph = deps_graph ,
287- build_mode = ['*' ],
288- remotes = all_remotes , # Use all remotes for dependency resolution
289- update = None ,
290- lockfile = None ,
291- )
292-
293- conan_api .install .install_binaries (deps_graph = deps_graph , remotes = all_remotes )
294-
295- if not self .data .skip_upload :
296- self ._upload_package (conan_api , ref , configured_remotes )
220+ try :
221+ # Build conan create command
222+ command = ['conan' , 'create' , str (conanfile_path )]
223+
224+ # Add build mode (build everything for publishing)
225+ command .extend (['--build' , 'missing' ])
226+
227+ # 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' ])
237+
238+ # Log the command being executed
239+ logger .info ('Executing conan create command: %s' , ' ' .join (command ))
240+
241+ # Run conan create
242+ subprocess .run (
243+ command ,
244+ cwd = str (project_root ),
245+ check = True ,
246+ capture_output = True ,
247+ text = True ,
248+ )
297249
298- def _get_configured_remotes (self , all_remotes ):
299- """Get and validate configured remotes for upload.
250+ # Upload if not skipped
251+ if not self .data .skip_upload :
252+ self ._upload_package (logger )
300253
301- Note: This only affects upload behavior. For dependency resolution,
302- we always use all available system remotes regardless of this config.
303- """
304- # If skip_upload is True, don't upload anywhere
305- if self .data .skip_upload :
306- return []
254+ except subprocess .CalledProcessError as e :
255+ error_msg = e .stderr if e .stderr else str (e )
256+ logger .error ('Conan create failed: %s' , error_msg , exc_info = True )
257+ raise ProviderInstallationError ('conan' , error_msg , e ) from e
307258
308- # If no remotes specified, upload to all available remotes
259+ def _upload_package (self , logger ) -> None :
260+ """Upload the package to configured remotes using CLI commands."""
261+ # If no remotes configured, upload to all remotes
309262 if not self .data .remotes :
310- return all_remotes
263+ # Upload to all available remotes
264+ command = ['conan' , 'upload' , '*' , '--all' , '--confirm' ]
265+ else :
266+ # Upload only to specified remotes
267+ for remote in self .data .remotes :
268+ command = ['conan' , 'upload' , '*' , '--remote' , remote , '--all' , '--confirm' ]
269+
270+ # Log the command being executed
271+ logger .info ('Executing conan upload command: %s' , ' ' .join (command ))
272+
273+ try :
274+ subprocess .run (
275+ command ,
276+ cwd = str (self .core_data .project_data .project_root ),
277+ check = True ,
278+ capture_output = True ,
279+ text = True ,
280+ )
281+ except subprocess .CalledProcessError as e :
282+ error_msg = e .stderr if e .stderr else str (e )
283+ logger .error ('Conan upload failed for remote %s: %s' , remote , error_msg , exc_info = True )
284+ raise ProviderInstallationError ('conan' , f'Upload to { remote } failed: { error_msg } ' , e ) from e
285+ return
286+
287+ # Log the command for uploading to all remotes
288+ logger .info ('Executing conan upload command: %s' , ' ' .join (command ))
311289
312- # Otherwise, upload only to specified remotes
313- configured_remotes = [remote for remote in all_remotes if remote .name in self .data .remotes ]
314-
315- if not configured_remotes :
316- available_remotes = [remote .name for remote in all_remotes ]
317- raise ProviderConfigurationError (
318- 'conan' ,
319- f'No configured remotes found. Available: { available_remotes } , Configured: { self .data .remotes } ' ,
320- 'remotes' ,
290+ try :
291+ subprocess .run (
292+ command ,
293+ cwd = str (self .core_data .project_data .project_root ),
294+ check = True ,
295+ capture_output = True ,
296+ text = True ,
321297 )
322-
323- return configured_remotes
324-
325- def _upload_package (self , conan_api , ref , configured_remotes ):
326- """Upload the package to configured remotes."""
327- ref_pattern = ListPattern (f'{ ref .name } /*' , package_id = '*' , only_recipe = False )
328- package_list = conan_api .list .select (ref_pattern )
329-
330- if not package_list .recipes :
331- raise ProviderInstallationError ('conan' , 'No packages found to upload' )
332-
333- remote = configured_remotes [0 ]
334- conan_api .upload .upload_full (
335- package_list = package_list ,
336- remote = remote ,
337- enabled_remotes = configured_remotes ,
338- check_integrity = False ,
339- force = False ,
340- metadata = None ,
341- dry_run = False ,
342- )
298+ except subprocess .CalledProcessError as e :
299+ error_msg = e .stderr if e .stderr else str (e )
300+ logger .error ('Conan upload failed: %s' , error_msg , exc_info = True )
301+ raise ProviderInstallationError ('conan' , error_msg , e ) from e
0 commit comments