11from pathlib import Path
22import pathlib
33import tempfile
4+ from typing import Iterable
45
56from conan .api .output import Color , ConanOutput
67from conan .errors import ConanException
910from io import BytesIO
1011import sys
1112
13+ from conan .internal .runner .output import RunnerOutput
14+
1215def ssh_info (msg , error = False ):
1316 fg = Color .BRIGHT_MAGENTA
1417 if error :
@@ -48,6 +51,8 @@ def __init__(self, conan_api, command, host_profile, build_profile, args, raw_ar
4851 self .client = SSHClient ()
4952 self .client .load_system_host_keys ()
5053 self .client .connect (hostname )
54+ self .runner_output = RunnerOutput (hostname )
55+ self .remote_conn = RemoteConnection (self .client , self .runner_output )
5156
5257
5358 def run (self , use_cache = True ):
@@ -138,14 +143,7 @@ def ensure_runner_environment(self):
138143 ssh_info (f"Expected remote conan command: { conan_cmd } " )
139144
140145 # 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 ()
146+ has_remote_conan = self .remote_conn .check_file_exists (conan_cmd )
149147
150148 if not has_remote_conan :
151149 _ , _stdout , _stderr = self .client .exec_command (f"{ python_command } -m venv { conan_venv } " )
@@ -183,26 +181,15 @@ def ensure_runner_environment(self):
183181 _ , _stdout , _stderr = self .client .exec_command (f"{ self .remote_conan } config home" )
184182 ssh_info (f"Remote conan config home returned: { _stdout .read ().decode ().strip ()} " )
185183 _ , _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 ()
184+ self ._sync_conan_config ()
191185
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 ()
186+ def _sync_conan_config (self ):
187+ # Transfer conan config to remote
188+ self .remote_conn .put_dir (
189+ self .conan_api .config .home (),
190+ self .remote_conan_home ,
191+ exclude_dir = ["__pycache__" , "p" ],
192+ )
206193
207194 def copy_working_conanfile_path (self ):
208195 resolved_path = Path (self .args .path ).resolve ()
@@ -220,6 +207,7 @@ def copy_working_conanfile_path(self):
220207 self .remote_create_dir = _stdout .read ().decode ().strip ().replace ("\\ " , '/' )
221208
222209 # Copy current folder to destination using sftp
210+ # self.remote_conn.put_dir(resolved_path.as_posix(), self.remote_create_dir)
223211 _Path = pathlib .PureWindowsPath if self .remote_is_windows else pathlib .PurePath
224212 sftp = self .client .open_sftp ()
225213 for root , dirs , files in os .walk (resolved_path .as_posix ()):
@@ -265,8 +253,64 @@ def update_local_cache(self, json_result):
265253 if stdout .channel .recv_exit_status () != 0 :
266254 raise ConanException ("Unable to save remote conan cache state" )
267255
268- sftp = self .client .open_sftp ()
269256 with tempfile .TemporaryDirectory () as tmp :
270257 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 )
258+ self .remote_conn .get (conan_cache_tgz , local_cache_tgz )
259+ self .conan_api .cache .restore (local_cache_tgz )
260+
261+
262+ class RemoteConnection :
263+ def __init__ (self , client , runner_output : RunnerOutput ):
264+ from paramiko .client import SSHClient
265+ self .client : SSHClient = client
266+ self .runner_output = runner_output
267+
268+ def put (self , src : str , dst : str ) -> None :
269+ try :
270+ sftp = self .client .open_sftp ()
271+ sftp .put (src , dst )
272+ sftp .close ()
273+ except IOError as e :
274+ self .runner_output .error (f"Unable to copy { src } to { dst } :\n { e } " )
275+
276+ def put_dir (self , src : str , dst : str , exclude_dir : Iterable [str ] = []) -> None :
277+ source_folder = Path (src )
278+ destination_folder = Path (dst )
279+ for item in source_folder .iterdir ():
280+ dest_item = (destination_folder / item .name ).as_posix ()
281+ if item .is_file ():
282+ self .runner_output .status (f"Copying file { item .as_posix ()} to { dest_item } " )
283+ self .put (item .as_posix (), dest_item )
284+ elif item .is_dir () and item .name not in exclude_dir :
285+ self .runner_output .status (f"Copying directory { src } to { dst } " )
286+ self .mkdir (dest_item , ignore_existing = True )
287+ self .put_dir (item .as_posix (), dest_item , exclude_dir )
288+
289+ def get (self , src : str , dst : str ) -> None :
290+ try :
291+ sftp = self .client .open_sftp ()
292+ sftp .get (src , dst )
293+ sftp .close ()
294+ except IOError as e :
295+ self .runner_output .error (f"Unable to copy from remote { src } to { dst } :\n { e } " )
296+
297+ def mkdir (self , folder : str , ignore_existing = False ) -> None :
298+ sftp = self .client .open_sftp ()
299+ try :
300+ sftp .mkdir (folder )
301+ except IOError :
302+ if ignore_existing :
303+ pass
304+ else :
305+ raise
306+ finally :
307+ sftp .close ()
308+
309+ def check_file_exists (self , file : str ) -> bool :
310+ try :
311+ sftp = self .client .open_sftp ()
312+ sftp .stat (file )
313+ sftp .close ()
314+ return True
315+ except FileNotFoundError :
316+ return False
0 commit comments