From a7686977d2159b8d488d60e0045cbd817a034091 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:53:26 -0400 Subject: [PATCH 01/18] BLD: Add DLL hashes to RECORD --- ci/fix_wheels.py | 52 +++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 525aacf572cd4..4df2d2992923c 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -1,7 +1,14 @@ +""" +This file "repairs" our Windows wheels by copying the necessary DLLs for pandas to run +on a barebones Windows installation() into the wheel. + +NOTE: The paths for the DLLs are hard-coded to the location of the Visual Studio +redistributables +""" import os import shutil +import subprocess import sys -import zipfile try: if len(sys.argv) != 3: @@ -25,34 +32,25 @@ shutil.copy(wheel_path, dest_dir) # Remember to delete if process fails wheel_name = os.path.basename(wheel_path) success = True -exception = None repaired_wheel_path = os.path.join(dest_dir, wheel_name) -with zipfile.ZipFile(repaired_wheel_path, "a") as zipf: - try: - # TODO: figure out how licensing works for the redistributables - base_redist_dir = ( - f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" - f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" - f"Microsoft.VC142.CRT/" - ) - zipf.write( - os.path.join(base_redist_dir, "msvcp140.dll"), - "pandas/_libs/window/msvcp140.dll", - ) - zipf.write( - os.path.join(base_redist_dir, "concrt140.dll"), - "pandas/_libs/window/concrt140.dll", - ) - if not is_32: - zipf.write( - os.path.join(base_redist_dir, "vcruntime140_1.dll"), - "pandas/_libs/window/vcruntime140_1.dll", - ) - except Exception as e: - success = False - exception = e +# Use the wheel CLI instead of manipulating zipfiles, since the CLI will +# take care of rebuilding the hashes found in the record file +tmp_dir = os.path.join(dest_dir, "tmp") +subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path]) +base_redist_dir = ( + f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" + f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" + f"Microsoft.VC142.CRT/" +) +required_dlls = ["msvcp140.dll", "concrt140.dll"] +if not is_32: + required_dlls += "vcruntime140_1.dll" +for dll in required_dlls: + src = os.path.join(base_redist_dir, dll) + shutil.copy(src, dst) +subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"]) if not success: os.remove(repaired_wheel_path) - raise exception + sys.exit(1) print(f"Successfully repaired wheel was written to {repaired_wheel_path}") From 96e39244c2a2ae9df26b25bf36522af26628879b Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:23:53 -0400 Subject: [PATCH 02/18] Update fix_wheels.py --- ci/fix_wheels.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 4df2d2992923c..981fab3dce448 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -3,7 +3,7 @@ on a barebones Windows installation() into the wheel. NOTE: The paths for the DLLs are hard-coded to the location of the Visual Studio -redistributables +redistributables """ import os import shutil @@ -29,14 +29,17 @@ if not os.path.isdir(dest_dir): print(f"Created directory {dest_dir}") os.mkdir(dest_dir) + shutil.copy(wheel_path, dest_dir) # Remember to delete if process fails wheel_name = os.path.basename(wheel_path) success = True repaired_wheel_path = os.path.join(dest_dir, wheel_name) + +try: # Use the wheel CLI instead of manipulating zipfiles, since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") -subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path]) +subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path], check=True) base_redist_dir = ( f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" @@ -47,8 +50,8 @@ required_dlls += "vcruntime140_1.dll" for dll in required_dlls: src = os.path.join(base_redist_dir, dll) - shutil.copy(src, dst) -subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"]) + shutil.copy(src, tmp_dir) +subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"], check=True) if not success: os.remove(repaired_wheel_path) From e17f1880ac465b00690e35b21114653a412ce5a6 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:59:53 -0400 Subject: [PATCH 03/18] more fixes --- ci/fix_wheels.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 981fab3dce448..7d7d5b7973f76 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -36,24 +36,24 @@ repaired_wheel_path = os.path.join(dest_dir, wheel_name) try: -# Use the wheel CLI instead of manipulating zipfiles, since the CLI will -# take care of rebuilding the hashes found in the record file -tmp_dir = os.path.join(dest_dir, "tmp") -subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path], check=True) -base_redist_dir = ( - f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" - f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" - f"Microsoft.VC142.CRT/" -) -required_dlls = ["msvcp140.dll", "concrt140.dll"] -if not is_32: - required_dlls += "vcruntime140_1.dll" -for dll in required_dlls: - src = os.path.join(base_redist_dir, dll) - shutil.copy(src, tmp_dir) -subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"], check=True) - -if not success: + # Use the wheel CLI instead of manipulating zipfiles, since the CLI will + # take care of rebuilding the hashes found in the record file + tmp_dir = os.path.join(dest_dir, "tmp") + subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path], check=True) + base_redist_dir = ( + f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" + f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" + f"Microsoft.VC142.CRT/" + ) + required_dlls = ["msvcp140.dll", "concrt140.dll"] + if not is_32: + required_dlls += "vcruntime140_1.dll" + for dll in required_dlls: + src = os.path.join(base_redist_dir, dll) + shutil.copy(src, tmp_dir) + subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"], check=True) +except CalledProcessError: + print("Failed to add DLLS to wheel.") os.remove(repaired_wheel_path) sys.exit(1) print(f"Successfully repaired wheel was written to {repaired_wheel_path}") From 3b32226ca319abf766bdba2dda791bc6f966ff79 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 9 Apr 2023 19:15:11 -0400 Subject: [PATCH 04/18] Update fix_wheels.py --- ci/fix_wheels.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 7d7d5b7973f76..c8a8a5d929a13 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -10,6 +10,8 @@ import subprocess import sys +from subprocess import CalledProcessError + try: if len(sys.argv) != 3: raise ValueError( From 155d1d232e02f4075b24758415e7ce52369036cf Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 21:28:31 -0400 Subject: [PATCH 05/18] Update fix_wheels.py --- ci/fix_wheels.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index c8a8a5d929a13..79db60bee5379 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -9,6 +9,7 @@ import shutil import subprocess import sys +import zipfile from subprocess import CalledProcessError @@ -38,10 +39,13 @@ repaired_wheel_path = os.path.join(dest_dir, wheel_name) try: - # Use the wheel CLI instead of manipulating zipfiles, since the CLI will + # Use the wheel CLI for zipping up the wheel since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") - subprocess.run(["wheel", "unpack", f"-d {tmp_dir}", wheel_path], check=True) + with ZipFile(tmp_file, 'r') as f: + # Extracting all the members of the zip + # into a specific location. + f.extractall(path=tmp_dir) base_redist_dir = ( f"C:/Program Files (x86)/Microsoft Visual Studio/2019/" f"Enterprise/VC/Redist/MSVC/14.29.30133/{PYTHON_ARCH}/" From ce8195354a9604156f3008d5902485fbcf5aac7c Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 21:28:52 -0400 Subject: [PATCH 06/18] Update fix_wheels.py --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 79db60bee5379..48c7fda49e0f5 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -42,7 +42,7 @@ # Use the wheel CLI for zipping up the wheel since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") - with ZipFile(tmp_file, 'r') as f: + with zipfile.ZipFile(tmp_file, 'r') as f: # Extracting all the members of the zip # into a specific location. f.extractall(path=tmp_dir) From b2631af699e48d6416b2545197f7af6d05196896 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 21:44:48 -0400 Subject: [PATCH 07/18] Update fix_wheels.py --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 48c7fda49e0f5..73d61b6679426 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -42,7 +42,7 @@ # Use the wheel CLI for zipping up the wheel since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") - with zipfile.ZipFile(tmp_file, 'r') as f: + with zipfile.ZipFile(tmp_dir, 'r') as f: # Extracting all the members of the zip # into a specific location. f.extractall(path=tmp_dir) From 5d7d729b6229c813314287d19844a67bf05fd223 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 22:20:39 -0400 Subject: [PATCH 08/18] Update fix_wheels.py --- ci/fix_wheels.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 73d61b6679426..0c64808939179 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -36,13 +36,12 @@ shutil.copy(wheel_path, dest_dir) # Remember to delete if process fails wheel_name = os.path.basename(wheel_path) success = True -repaired_wheel_path = os.path.join(dest_dir, wheel_name) try: # Use the wheel CLI for zipping up the wheel since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") - with zipfile.ZipFile(tmp_dir, 'r') as f: + with zipfile.ZipFile(wheel_path, 'r') as f: # Extracting all the members of the zip # into a specific location. f.extractall(path=tmp_dir) @@ -60,6 +59,5 @@ subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"], check=True) except CalledProcessError: print("Failed to add DLLS to wheel.") - os.remove(repaired_wheel_path) sys.exit(1) print(f"Successfully repaired wheel was written to {repaired_wheel_path}") From 439fa74955ac7fc37fb082d23171e7fe72db89a1 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 22:30:46 -0400 Subject: [PATCH 09/18] syntax --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 0c64808939179..34e1d7514dbdc 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -52,7 +52,7 @@ ) required_dlls = ["msvcp140.dll", "concrt140.dll"] if not is_32: - required_dlls += "vcruntime140_1.dll" + required_dlls += ["vcruntime140_1.dll"] for dll in required_dlls: src = os.path.join(base_redist_dir, dll) shutil.copy(src, tmp_dir) From 360d6095f9804c70abb116e0618135d90fff6076 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 11 Apr 2023 22:54:26 -0400 Subject: [PATCH 10/18] try normalizing paths? --- ci/fix_wheels.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 34e1d7514dbdc..91e5f01309878 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -18,8 +18,8 @@ raise ValueError( "User must pass the path to the wheel and the destination directory." ) - wheel_path = sys.argv[1] - dest_dir = sys.argv[2] + wheel_path = os.path.abspath(sys.argv[1]) + dest_dir = os.path.abspath(sys.argv[2]) # Figure out whether we are building on 32 or 64 bit python is_32 = sys.maxsize <= 2**32 PYTHON_ARCH = "x86" if is_32 else "x64" @@ -28,7 +28,6 @@ raise ValueError( "User must pass the path to the wheel and the destination directory." ) -# Wheels are zip files if not os.path.isdir(dest_dir): print(f"Created directory {dest_dir}") os.mkdir(dest_dir) From 58971186454b5597391cfc88f9256ca073ceb53f Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:03:08 -0400 Subject: [PATCH 11/18] Update fix_wheels.py --- ci/fix_wheels.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 91e5f01309878..39459fe124f1d 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -18,8 +18,8 @@ raise ValueError( "User must pass the path to the wheel and the destination directory." ) - wheel_path = os.path.abspath(sys.argv[1]) - dest_dir = os.path.abspath(sys.argv[2]) + wheel_path = sys.argv[1] + dest_dir = sys.argv[2] # Figure out whether we are building on 32 or 64 bit python is_32 = sys.maxsize <= 2**32 PYTHON_ARCH = "x86" if is_32 else "x64" @@ -32,7 +32,6 @@ print(f"Created directory {dest_dir}") os.mkdir(dest_dir) -shutil.copy(wheel_path, dest_dir) # Remember to delete if process fails wheel_name = os.path.basename(wheel_path) success = True From a0a9651689f971ea394d521d1f3e8eb20c3451cf Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:29:36 -0400 Subject: [PATCH 12/18] Update fix_wheels.py --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 39459fe124f1d..08d7e96f9b327 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -54,7 +54,7 @@ for dll in required_dlls: src = os.path.join(base_redist_dir, dll) shutil.copy(src, tmp_dir) - subprocess.run(["wheel", "pack", tmp_dir, f"-d {dest_dir}"], check=True) + subprocess.run(["wheel", "pack", tmp_dir, "-d", "dest_dir"], check=True) except CalledProcessError: print("Failed to add DLLS to wheel.") sys.exit(1) From 499fd37b6a1954d8f98176673f41db5203e213e7 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:42:07 -0400 Subject: [PATCH 13/18] no quotes --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 08d7e96f9b327..97af0292ae431 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -54,7 +54,7 @@ for dll in required_dlls: src = os.path.join(base_redist_dir, dll) shutil.copy(src, tmp_dir) - subprocess.run(["wheel", "pack", tmp_dir, "-d", "dest_dir"], check=True) + subprocess.run(["wheel", "pack", tmp_dir, "-d", dest_dir], check=True) except CalledProcessError: print("Failed to add DLLS to wheel.") sys.exit(1) From 2a2eacdeefa57282832f8b42ffd52628abb34c2b Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 08:01:13 -0400 Subject: [PATCH 14/18] Update fix_wheels.py --- ci/fix_wheels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 97af0292ae431..8bb00241400d1 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -58,4 +58,4 @@ except CalledProcessError: print("Failed to add DLLS to wheel.") sys.exit(1) -print(f"Successfully repaired wheel was written to {repaired_wheel_path}") +print(f"Successfully repaired wheel") From 29049752612d9b10eb99d8e391bf3471d2dc09a4 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:36:42 -0400 Subject: [PATCH 15/18] Update fix_wheels.py --- ci/fix_wheels.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index 8bb00241400d1..edb7280234322 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -51,9 +51,10 @@ required_dlls = ["msvcp140.dll", "concrt140.dll"] if not is_32: required_dlls += ["vcruntime140_1.dll"] + dest_dll_dir = os.path.join(tmp_dir, "pandas/_libs/window") for dll in required_dlls: src = os.path.join(base_redist_dir, dll) - shutil.copy(src, tmp_dir) + shutil.copy(src, dest_dll_dir) subprocess.run(["wheel", "pack", tmp_dir, "-d", dest_dir], check=True) except CalledProcessError: print("Failed to add DLLS to wheel.") From eb3655f9012a1980ff0c3e89eeb8c62231bedb3d Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 12:54:18 -0400 Subject: [PATCH 16/18] pre-commit fixes --- ci/fix_wheels.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ci/fix_wheels.py b/ci/fix_wheels.py index edb7280234322..76b70fdde9ea0 100644 --- a/ci/fix_wheels.py +++ b/ci/fix_wheels.py @@ -8,11 +8,10 @@ import os import shutil import subprocess +from subprocess import CalledProcessError import sys import zipfile -from subprocess import CalledProcessError - try: if len(sys.argv) != 3: raise ValueError( @@ -39,8 +38,8 @@ # Use the wheel CLI for zipping up the wheel since the CLI will # take care of rebuilding the hashes found in the record file tmp_dir = os.path.join(dest_dir, "tmp") - with zipfile.ZipFile(wheel_path, 'r') as f: - # Extracting all the members of the zip + with zipfile.ZipFile(wheel_path, "r") as f: + # Extracting all the members of the zip # into a specific location. f.extractall(path=tmp_dir) base_redist_dir = ( @@ -59,4 +58,4 @@ except CalledProcessError: print("Failed to add DLLS to wheel.") sys.exit(1) -print(f"Successfully repaired wheel") +print("Successfully repaired wheel") From fa754195c7aa9b28127151a4d9f7c4558ec6d0db Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:10:35 -0400 Subject: [PATCH 17/18] verify fix with wheel unpack --- ci/test_wheels.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ci/test_wheels.py b/ci/test_wheels.py index f861c1cbedcad..32d668c59df0b 100644 --- a/ci/test_wheels.py +++ b/ci/test_wheels.py @@ -15,6 +15,17 @@ wheel_path = None print(f"IS_32_BIT is {is_32_bit}") print(f"Path to built wheel is {wheel_path}") + + print("Verifying file hashes in wheel RECORD file") + try: + tmp_dir = "tmp" + subprocess.run(["wheel", "unpack", wheel_path, "-d", tmp_dir], check=True) + except CalledProcessError: + print("wheel RECORD file hash verification failed.") + sys.exit(1) + finally: + shutil.rmtree(tmp_dir) + if is_32_bit: sys.exit(0) # No way to test Windows 32-bit(no docker image) if wheel_path is None: From 99d398e73d982d13331c1960de3b5abc39d89850 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 12 Apr 2023 19:10:36 -0400 Subject: [PATCH 18/18] missing import --- ci/test_wheels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/test_wheels.py b/ci/test_wheels.py index 32d668c59df0b..75675d7e4ffc3 100644 --- a/ci/test_wheels.py +++ b/ci/test_wheels.py @@ -2,6 +2,7 @@ import os import shutil import subprocess +from subprocess import CalledProcessError import sys if os.name == "nt":