2121import subprocess
2222import sys
2323from pathlib import Path
24+ from urllib .parse import urlparse
2425
2526from platformio .package .version import pepver_to_semver
2627from platformio .compat import IS_WINDOWS
5960}
6061
6162
62- def has_internet_connection (host = "1.1.1.1" , port = 53 , timeout = 2 ):
63+ def has_internet_connection (timeout = 5 ):
6364 """
64- Checks if an internet connection is available (default: Cloudflare DNS server).
65- Returns True if a connection is possible, otherwise False.
65+ Checks practical internet reachability for dependency installation.
66+ 1) If HTTPS/HTTP proxy environment variable is set, test TCP connectivity to the proxy endpoint.
67+ 2) Otherwise, test direct TCP connectivity to common HTTPS endpoints (port 443).
68+
69+ Args:
70+ timeout (int): Timeout duration in seconds for the connection test.
71+
72+ Returns:
73+ True if at least one path appears reachable; otherwise False.
6674 """
67- try :
68- with socket .create_connection ((host , port ), timeout = timeout ):
75+ # 1) Test TCP connectivity to the proxy endpoint.
76+ proxy = os .getenv ("HTTPS_PROXY" ) or os .getenv ("https_proxy" ) or os .getenv ("HTTP_PROXY" ) or os .getenv ("http_proxy" )
77+ if proxy :
78+ try :
79+ u = urlparse (proxy if "://" in proxy else f"http://{ proxy } " )
80+ host = u .hostname
81+ port = u .port or (443 if u .scheme == "https" else 80 )
82+ if host and port :
83+ socket .create_connection ((host , port ), timeout = timeout ).close ()
84+ return True
85+ except Exception :
86+ # If proxy connection fails, fall back to direct connection test
87+ pass
88+
89+ # 2) Test direct TCP connectivity to common HTTPS endpoints (port 443).
90+ https_hosts = ("pypi.org" , "files.pythonhosted.org" , "github.com" )
91+ for host in https_hosts :
92+ try :
93+ socket .create_connection ((host , 443 ), timeout = timeout ).close ()
6994 return True
70- except OSError :
71- return False
95+ except Exception :
96+ continue
97+
98+ # Direct DNS:53 connection is abolished due to many false positives on enterprise networks
99+ # (add it at the end if necessary)
100+ return False
72101
73102
74103def get_executable_path (penv_dir , executable_name ):
@@ -286,18 +315,18 @@ def _get_installed_uv_packages():
286315 for p in packages :
287316 result [p ["name" ].lower ()] = pepver_to_semver (p ["version" ])
288317 else :
289- print (f"Error : uv pip list failed with exit code { result_obj .returncode } " )
318+ print (f"Warning : uv pip list failed with exit code { result_obj .returncode } " )
290319 if result_obj .stderr :
291320 print (f"Error output: { result_obj .stderr .strip ()} " )
292321
293322 except subprocess .TimeoutExpired :
294- print ("Error : uv pip list command timed out" )
323+ print ("Warning : uv pip list command timed out" )
295324 except (json .JSONDecodeError , KeyError ) as e :
296- print (f"Error : Could not parse package list: { e } " )
325+ print (f"Warning : Could not parse package list: { e } " )
297326 except FileNotFoundError :
298- print ("Error : uv command not found" )
327+ print ("Warning : uv command not found" )
299328 except Exception as e :
300- print (f"Error ! Couldn't extract the list of installed Python packages: { e } " )
329+ print (f"Warning ! Couldn't extract the list of installed Python packages: { e } " )
301330
302331 return result
303332
@@ -306,39 +335,39 @@ def _get_installed_uv_packages():
306335
307336 if packages_to_install :
308337 packages_list = []
309- package_map = {}
310338 for p in packages_to_install :
311339 spec = python_deps [p ]
312340 if spec .startswith (('http://' , 'https://' , 'git+' , 'file://' )):
313341 packages_list .append (spec )
314- package_map [spec ] = p
315342 else :
316- full_spec = f"{ p } { spec } "
317- packages_list .append (full_spec )
318- package_map [full_spec ] = p
343+ packages_list .append (f"{ p } { spec } " )
319344
320- for package_spec in packages_list :
321- cmd = [
322- penv_uv_executable , "pip" , "install" ,
323- f"--python={ python_exe } " ,
324- "--quiet" , "--upgrade" ,
325- package_spec
326- ]
327- try :
328- subprocess .check_call (
329- cmd ,
330- stdout = subprocess .DEVNULL ,
331- stderr = subprocess .STDOUT ,
332- timeout = 300
333- )
334- except subprocess .CalledProcessError as e :
335- print (f"Error: Installing package '{ package_map .get (package_spec , package_spec )} ' failed (exit code { e .returncode } )." )
336- except subprocess .TimeoutExpired :
337- print (f"Error: Installing package '{ package_map .get (package_spec , package_spec )} ' timed out." )
338- except FileNotFoundError :
339- print ("Error: uv command not found" )
340- except Exception as e :
341- print (f"Error: Installing package '{ package_map .get (package_spec , package_spec )} ': { e } ." )
345+ cmd = [
346+ penv_uv_executable , "pip" , "install" ,
347+ f"--python={ python_exe } " ,
348+ "--quiet" , "--upgrade"
349+ ] + packages_list
350+
351+ try :
352+ subprocess .check_call (
353+ cmd ,
354+ stdout = subprocess .DEVNULL ,
355+ stderr = subprocess .STDOUT ,
356+ timeout = 300
357+ )
358+
359+ except subprocess .CalledProcessError as e :
360+ print (f"Error: Failed to install Python dependencies (exit code: { e .returncode } )" )
361+ return False
362+ except subprocess .TimeoutExpired :
363+ print ("Error: Python dependencies installation timed out" )
364+ return False
365+ except FileNotFoundError :
366+ print ("Error: uv command not found" )
367+ return False
368+ except Exception as e :
369+ print (f"Error installing Python dependencies: { e } " )
370+ return False
342371
343372 return True
344373
0 commit comments