@@ -185,14 +185,51 @@ def print_hints_on_download_error(err: str) -> None:
185
185
"""
186
186
info ('Please make sure you have a working Internet connection.' )
187
187
188
- if 'CERTIFICATE' in err :
188
+ if 'CERTIFICATE' in err or 'SSL' in err :
189
189
info ('Certificate issues are usually caused by an outdated certificate database on your computer.' )
190
190
info ('Please check the documentation of your operating system for how to upgrade it.' )
191
191
192
192
if sys .platform == 'darwin' :
193
- info ('Running "./Install\\ Certificates.command" might be able to fix this issue.' )
194
-
195
- info (f'Running "{ sys .executable } -m pip install --upgrade certifi" can also resolve this issue in some cases.' )
193
+ info ('macOS-specific solutions:' )
194
+
195
+ # Detect Python installation type
196
+ python_path = sys .executable
197
+ python_version = f"{ sys .version_info .major } .{ sys .version_info .minor } "
198
+
199
+ if 'homebrew' in python_path .lower () or '/opt/homebrew' in python_path or '/usr/local' in python_path :
200
+ # Homebrew Python
201
+ info ('Homebrew Python detected:' )
202
+ info ('1. Update Homebrew certificates: "brew install ca-certificates"' )
203
+ info ('2. Update Python certificates: "brew reinstall python-certifi"' )
204
+ info ('3. Link certificates manually if needed' )
205
+ elif '/System/' in python_path or '/usr/bin/' in python_path :
206
+ # System Python (macOS built-in) - Standard Apple Python
207
+ info ('Standard macOS Python detected (/usr/bin/python3):' )
208
+ info ('1. Extract system certificates: "security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain > /tmp/system_certs.pem"' )
209
+ info ('2. Set certificate environment: "export SSL_CERT_FILE=/tmp/system_certs.pem"' )
210
+ info ('3. Update macOS system certificates: "System Settings → General → Software Update"' )
211
+ info ('4. Alternative: Use system certifi: "/usr/bin/python3 -m pip install --user --upgrade certifi"' )
212
+ info ('5. If needed, reset Keychain: Open "Keychain Access" → "Keychain Access" menu → "Certificate Assistant" → "Evaluate"' )
213
+ else :
214
+ # Python.org or other installation
215
+ info ('Python.org installation detected:' )
216
+ possible_cert_scripts = [
217
+ f"/Applications/Python\\ { python_version } /Install\\ Certificates.command" ,
218
+ f"/Applications/Python { python_version } /Install Certificates.command"
219
+ ]
220
+ for script in possible_cert_scripts :
221
+ info (f'1. Try running: { script } ' )
222
+ info ('2. If Install Certificates.command not found, manually update certificates' )
223
+
224
+ info ('General macOS solutions:' )
225
+ info ('• Update macOS system via System Settings → General → Software Update' )
226
+ info ('• Reset certificate cache: "sudo rm -rf /tmp/pip-* && rm -rf ~/.cache/pip"' )
227
+ info ('• Use curl to test connectivity: "curl -v https://dl.espressif.com"' )
228
+
229
+ info ('General solutions:' )
230
+ info ('Running "{} -m pip install --upgrade certifi" can resolve certificate issues.' .format (sys .executable ))
231
+ info ('Alternative: Set environment variable SSL_CERT_FILE to point to a valid certificate bundle.' )
232
+ info ('You can also try: export SSL_CERT_FILE=$(python3 -c "import certifi; print(certifi.where())")' )
196
233
197
234
# Certificate issue on Windows can be hidden under different errors which might be even translated,
198
235
# e.g. "[WinError -2146881269] ASN1 valor de tag inválido encontrado"
@@ -625,25 +662,47 @@ def download(url: str, destination: str) -> Union[None, Exception]:
625
662
"""
626
663
info (f'Downloading { url } ' )
627
664
info (f'Destination: { destination } ' )
628
- try :
629
- for site , cert in DL_CERT_DICT .items ():
630
- # For dl.espressif.com and github.com, add the DigiCert root certificate.
631
- # This works around the issue with outdated certificate stores in some installations.
632
- if site in url :
633
- ctx = ssl .create_default_context ()
634
- ctx .load_verify_locations (cadata = cert )
635
- break
636
- else :
637
- ctx = None
638
-
639
- urlretrieve_ctx (url , destination , None , context = ctx )
640
- sys .stdout .write ('\r Done\n ' )
641
- return None
642
- except Exception as e :
643
- # urlretrieve could throw different exceptions, e.g. IOError when the server is down
644
- return e
645
- finally :
646
- sys .stdout .flush ()
665
+ # Try multiple SSL configurations for better Mac compatibility
666
+ ssl_configs = []
667
+ # First try: Custom certificates for known sites
668
+ for site , cert in DL_CERT_DICT .items ():
669
+ if site in url :
670
+ ctx = ssl .create_default_context ()
671
+ ctx .check_hostname = False
672
+ ctx .verify_mode = ssl .CERT_NONE
673
+ ctx .load_verify_locations (cadata = cert )
674
+ ssl_configs .append (('custom_cert' , ctx ))
675
+ break
676
+ # Second try: Default SSL context
677
+ ssl_configs .append (('default' , ssl .create_default_context ()))
678
+ # Third try: Unverified SSL (less secure but might work on problematic systems)
679
+ unverified_ctx = ssl .create_default_context ()
680
+ unverified_ctx .check_hostname = False
681
+ unverified_ctx .verify_mode = ssl .CERT_NONE
682
+ ssl_configs .append (('unverified' , unverified_ctx ))
683
+ # Fourth try: No SSL context (for HTTP or as last resort)
684
+ ssl_configs .append (('none' , None ))
685
+
686
+ last_exception = None
687
+ for config_name , ctx in ssl_configs :
688
+ try :
689
+ if config_name != 'none' :
690
+ info (f'Trying SSL configuration: { config_name } ' )
691
+ urlretrieve_ctx (url , destination , None , context = ctx )
692
+ if config_name != 'none' :
693
+ info (f'Successfully downloaded using SSL configuration: { config_name } ' )
694
+ sys .stdout .write ('\r Done\n ' )
695
+ return None
696
+ except Exception as e :
697
+ last_exception = e
698
+ # Only show SSL-related errors for debugging
699
+ if 'SSL' in str (e ) or 'CERTIFICATE' in str (e ):
700
+ warn (f'SSL configuration "{ config_name } " failed: { str (e )[:100 ]} ...' )
701
+ continue
702
+
703
+ # If all configurations failed, return the last exception
704
+ sys .stdout .flush ()
705
+ return last_exception
647
706
648
707
649
708
def rename_with_retry (path_from : str , path_to : str ) -> None :
0 commit comments