11from pathlib import Path
2+ from typing import Iterable
3+ import fnmatch
24import pathlib
35import tempfile
46
911from io import BytesIO
1012import sys
1113
14+ from conan .internal .runner .output import RunnerOutput
15+
1216def ssh_info (msg , error = False ):
1317 fg = Color .BRIGHT_MAGENTA
1418 if error :
@@ -48,6 +52,8 @@ def __init__(self, conan_api, command, host_profile, build_profile, args, raw_ar
4852 self .client = SSHClient ()
4953 self .client .load_system_host_keys ()
5054 self .client .connect (hostname )
55+ self .runner_output = RunnerOutput (hostname )
56+ self .remote_conn = RemoteConnection (self .client , self .runner_output )
5157
5258
5359 def run (self , use_cache = True ):
@@ -138,14 +144,7 @@ def ensure_runner_environment(self):
138144 ssh_info (f"Expected remote conan command: { conan_cmd } " )
139145
140146 # Check if remote Conan executable exists, otherwise invoke pip inside venv
141- sftp = self .client .open_sftp ()
142- try :
143- sftp .stat (conan_cmd )
144- has_remote_conan = True
145- except FileNotFoundError :
146- has_remote_conan = False
147- finally :
148- sftp .close ()
147+ has_remote_conan = self .remote_conn .check_file_exists (conan_cmd )
149148
150149 if not has_remote_conan :
151150 _ , _stdout , _stderr = self .client .exec_command (f"{ python_command } -m venv { conan_venv } " )
@@ -183,26 +182,15 @@ def ensure_runner_environment(self):
183182 _ , _stdout , _stderr = self .client .exec_command (f"{ self .remote_conan } config home" )
184183 ssh_info (f"Remote conan config home returned: { _stdout .read ().decode ().strip ()} " )
185184 _ , _stdout , _stderr = self .client .exec_command (f"{ self .remote_conan } profile detect --force" )
186- self ._copy_profiles ()
187-
188-
189- def _copy_profiles (self ):
190- sftp = self .client .open_sftp ()
185+ self ._sync_conan_config ()
191186
192- # TODO: very questionable choices here
193- try :
194- profiles = {
195- self .args .profile_host [0 ]: self .host_profile .dumps (),
196- self .args .profile_build [0 ]: self .build_profile .dumps ()
197- }
198-
199- for name , contents in profiles .items ():
200- dest_filename = self .remote_conan_home + f"/profiles/{ name } "
201- sftp .putfo (BytesIO (contents .encode ()), dest_filename )
202- except :
203- raise ConanException ("Unable to copy profiles to remote" )
204- finally :
205- sftp .close ()
187+ def _sync_conan_config (self ):
188+ # Transfer conan config to remote
189+ self .remote_conn .put_dir (
190+ self .conan_api .config .home (),
191+ self .remote_conan_home ,
192+ exclude_patterns = ["p" , ".conan.db" , "*.pyc" , "__pycache__" , ".DS_Store" , ".git" ]
193+ )
206194
207195 def copy_working_conanfile_path (self ):
208196 resolved_path = Path (self .args .path ).resolve ()
@@ -220,6 +208,7 @@ def copy_working_conanfile_path(self):
220208 self .remote_create_dir = _stdout .read ().decode ().strip ().replace ("\\ " , '/' )
221209
222210 # Copy current folder to destination using sftp
211+ # self.remote_conn.put_dir(resolved_path.as_posix(), self.remote_create_dir)
223212 _Path = pathlib .PureWindowsPath if self .remote_is_windows else pathlib .PurePath
224213 sftp = self .client .open_sftp ()
225214 for root , dirs , files in os .walk (resolved_path .as_posix ()):
@@ -265,8 +254,67 @@ def update_local_cache(self, json_result):
265254 if stdout .channel .recv_exit_status () != 0 :
266255 raise ConanException ("Unable to save remote conan cache state" )
267256
268- sftp = self .client .open_sftp ()
269257 with tempfile .TemporaryDirectory () as tmp :
270258 local_cache_tgz = os .path .join (tmp , 'cache.tgz' )
271- sftp .get (conan_cache_tgz , local_cache_tgz )
272- package_list = self .conan_api .cache .restore (local_cache_tgz )
259+ self .remote_conn .get (conan_cache_tgz , local_cache_tgz )
260+ self .conan_api .cache .restore (local_cache_tgz )
261+
262+
263+ class RemoteConnection :
264+ def __init__ (self , client , runner_output : RunnerOutput ):
265+ from paramiko .client import SSHClient
266+ self .client : SSHClient = client
267+ self .runner_output = runner_output
268+
269+ def put (self , src : str , dst : str ) -> None :
270+ try :
271+ sftp = self .client .open_sftp ()
272+ sftp .put (src , dst )
273+ sftp .close ()
274+ except IOError as e :
275+ self .runner_output .error (f"Unable to copy { src } to { dst } :\n { e } " )
276+
277+ def put_dir (self , src : str , dst : str , exclude_patterns : Iterable [str ] = []) -> None :
278+ source_folder = Path (src )
279+ destination_folder = Path (dst )
280+ for item in source_folder .iterdir ():
281+ dest_item = (destination_folder / item .name ).as_posix ()
282+ # Check if item matches any exclude pattern
283+ if any (fnmatch .fnmatch (item .name , pattern ) for pattern in exclude_patterns ):
284+ continue
285+ if item .is_file ():
286+ self .runner_output .verbose (f"Copying file { item .as_posix ()} to { dest_item } " )
287+ self .put (item .as_posix (), dest_item )
288+ elif item .is_dir ():
289+ self .runner_output .verbose (f"Copying directory { item .as_posix ()} to { dest_item } " )
290+ self .mkdir (dest_item , ignore_existing = True )
291+ self .put_dir (item .as_posix (), dest_item , exclude_patterns )
292+
293+ def get (self , src : str , dst : str ) -> None :
294+ try :
295+ sftp = self .client .open_sftp ()
296+ sftp .get (src , dst )
297+ sftp .close ()
298+ except IOError as e :
299+ self .runner_output .error (f"Unable to copy from remote { src } to { dst } :\n { e } " )
300+
301+ def mkdir (self , folder : str , ignore_existing = False ) -> None :
302+ sftp = self .client .open_sftp ()
303+ try :
304+ sftp .mkdir (folder )
305+ except IOError :
306+ if ignore_existing :
307+ pass
308+ else :
309+ raise
310+ finally :
311+ sftp .close ()
312+
313+ def check_file_exists (self , file : str ) -> bool :
314+ try :
315+ sftp = self .client .open_sftp ()
316+ sftp .stat (file )
317+ sftp .close ()
318+ return True
319+ except FileNotFoundError :
320+ return False
0 commit comments