Skip to content

Commit ccd111f

Browse files
authored
Prefer curl for downloading files over urllib (#1355)
This is especially important on macOS where `urllib` can fail to verify certificates. See https://stackoverflow.com/questions/40684543/how-to-make-python-use-ca-certificates-from-mac-os-truststore Fixes: #1207, #85, #1356, #1357, #1358
1 parent 44bf7cf commit ccd111f

File tree

2 files changed

+70
-46
lines changed

2 files changed

+70
-46
lines changed

emsdk.py

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,54 @@ def get_download_target(url, dstpath, filename_prefix=''):
670670
return file_name
671671

672672

673+
def download_with_curl(url, file_name):
674+
print("Downloading: %s from %s" % (file_name, url))
675+
if not which('curl'):
676+
exit_with_error('curl not found in PATH')
677+
# -#: show progress bar
678+
# -L: Follow HTTP 3XX redirections
679+
# -f: Fail on HTTP errors
680+
subprocess.check_call(['curl', '-#', '-f', '-L', '-o', file_name, url])
681+
682+
683+
def download_with_urllib(url, file_name):
684+
u = urlopen(url)
685+
with open(file_name, 'wb') as f:
686+
file_size = get_content_length(u)
687+
if file_size > 0:
688+
print("Downloading: %s from %s, %s Bytes" % (file_name, url, file_size))
689+
else:
690+
print("Downloading: %s from %s" % (file_name, url))
691+
692+
file_size_dl = 0
693+
# Draw a progress bar 80 chars wide (in non-TTY mode)
694+
progress_max = 80 - 4
695+
progress_shown = 0
696+
block_sz = 256 * 1024
697+
if not TTY_OUTPUT:
698+
print(' [', end='')
699+
while True:
700+
buffer = u.read(block_sz)
701+
if not buffer:
702+
break
703+
704+
file_size_dl += len(buffer)
705+
f.write(buffer)
706+
if file_size:
707+
percent = file_size_dl * 100.0 / file_size
708+
if TTY_OUTPUT:
709+
status = r" %10d [%3.02f%%]" % (file_size_dl, percent)
710+
print(status, end='\r')
711+
else:
712+
while progress_shown < progress_max * percent / 100:
713+
print('-', end='')
714+
sys.stdout.flush()
715+
progress_shown += 1
716+
if not TTY_OUTPUT:
717+
print(']')
718+
sys.stdout.flush()
719+
720+
673721
# On success, returns the filename on the disk pointing to the destination file that was produced
674722
# On failure, returns None.
675723
def download_file(url, dstpath, download_even_if_exists=False,
@@ -680,53 +728,25 @@ def download_file(url, dstpath, download_even_if_exists=False,
680728
if os.path.exists(file_name) and not download_even_if_exists:
681729
print("File '" + file_name + "' already downloaded, skipping.")
682730
return file_name
731+
732+
mkdir_p(os.path.dirname(file_name))
733+
683734
try:
684-
u = urlopen(url)
685-
mkdir_p(os.path.dirname(file_name))
686-
with open(file_name, 'wb') as f:
687-
file_size = get_content_length(u)
688-
if file_size > 0:
689-
print("Downloading: %s from %s, %s Bytes" % (file_name, url, file_size))
690-
else:
691-
print("Downloading: %s from %s" % (file_name, url))
692-
693-
file_size_dl = 0
694-
# Draw a progress bar 80 chars wide (in non-TTY mode)
695-
progress_max = 80 - 4
696-
progress_shown = 0
697-
block_sz = 256 * 1024
698-
if not TTY_OUTPUT:
699-
print(' [', end='')
700-
while True:
701-
buffer = u.read(block_sz)
702-
if not buffer:
703-
break
704-
705-
file_size_dl += len(buffer)
706-
f.write(buffer)
707-
if file_size:
708-
percent = file_size_dl * 100.0 / file_size
709-
if TTY_OUTPUT:
710-
status = r" %10d [%3.02f%%]" % (file_size_dl, percent)
711-
print(status, end='\r')
712-
else:
713-
while progress_shown < progress_max * percent / 100:
714-
print('-', end='')
715-
sys.stdout.flush()
716-
progress_shown += 1
717-
if not TTY_OUTPUT:
718-
print(']')
719-
sys.stdout.flush()
735+
# Use curl on macOS to avoid CERTIFICATE_VERIFY_FAILED issue with
736+
# python's urllib:
737+
# https://stackoverflow.com/questions/40684543/how-to-make-python-use-ca-certificates-from-mac-os-truststore
738+
# Unlike on linux or windows, curl is always available on macOS systems.
739+
if MACOS:
740+
download_with_curl(url, file_name)
741+
else:
742+
download_with_urllib(url, file_name)
720743
except Exception as e:
721-
if not silent:
722-
errlog("Error: Downloading URL '" + url + "': " + str(e))
723-
if "SSL: CERTIFICATE_VERIFY_FAILED" in str(e) or "urlopen error unknown url type: https" in str(e):
724-
errlog("Warning: Possibly SSL/TLS issue. Update or install Python SSL root certificates (2048-bit or greater) supplied in Python folder or https://pypi.org/project/certifi/ and try again.")
725-
rmfile(file_name)
744+
errlog("Error: Downloading URL '" + url + "': " + str(e))
726745
return None
727746
except KeyboardInterrupt:
728747
rmfile(file_name)
729-
exit_with_error("aborted by user, exiting")
748+
raise
749+
730750
return file_name
731751

732752

@@ -3093,4 +3113,8 @@ def print_tools(t):
30933113

30943114

30953115
if __name__ == '__main__':
3096-
sys.exit(main(sys.argv[1:]))
3116+
try:
3117+
sys.exit(main(sys.argv[1:]))
3118+
except KeyboardInterrupt:
3119+
exit_with_error('aborted by user, exiting')
3120+
sys.exit(1)

test/test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,9 @@ def test_keep_downloads(self):
270270
# install of 2.0.28, and again when we install 2.0.29, but not on the
271271
# second install of 2.0.28 because the zip should already be local.
272272
shutil.rmtree('downloads')
273-
checked_call_with_output(emsdk + ' install 2.0.28', expected='Downloading:', env=env)
274-
checked_call_with_output(emsdk + ' install 2.0.29', expected='Downloading:', env=env)
275-
checked_call_with_output(emsdk + ' install 2.0.28', expected='already downloaded, skipping', unexpected='Downloading:', env=env)
273+
checked_call_with_output(emsdk + ' install 3.1.54', expected='Downloading:', env=env)
274+
checked_call_with_output(emsdk + ' install 3.1.55', expected='Downloading:', env=env)
275+
checked_call_with_output(emsdk + ' install 3.1.54', expected='already downloaded, skipping', unexpected='Downloading:', env=env)
276276

277277

278278
if __name__ == '__main__':

0 commit comments

Comments
 (0)