@@ -40,11 +40,11 @@ def getInternalFusionPythonInstillationFolder() -> str | os.PathLike[str]:
4040 return folder
4141
4242
43- def executeCommand (* args : str ) -> subprocess .CompletedProcess [str ]:
43+ def executeCommand (* args : str , check : bool = True ) -> subprocess .CompletedProcess [str ]:
4444 logger .debug (f"Running Command -> { ' ' .join (args )} " )
4545 try :
4646 result : subprocess .CompletedProcess [str ] = subprocess .run (
47- args , shell = False , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True , check = True
47+ args , shell = False , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True , check = check
4848 )
4949 logger .debug (f"Command Output:\n { result .stdout } " )
5050 return result
@@ -55,6 +55,40 @@ def executeCommand(*args: str) -> subprocess.CompletedProcess[str]:
5555 raise error
5656
5757
58+ def pipInstalled (pythonExecutablePath : str ) -> bool :
59+ """Check whether pip is importable by the given Fusion python interpreter."""
60+ # Use check=False so the expected "pip missing" case does not log an error.
61+ return executeCommand (pythonExecutablePath , "-m" , "pip" , "--version" , check = False ).returncode == 0
62+
63+
64+ def ensurePipInstalled (pythonFolder : str | os .PathLike [str ], pythonExecutablePath : str ) -> None :
65+ """Bootstrap pip into Fusion's bundled python if it is missing.
66+
67+ Fusion's runtime does not reliably ship pip: it is absent by default on macOS, and every
68+ Fusion update lands in a fresh 'webdeploy' folder whose python may also lack it on Windows.
69+ Prefer the stdlib 'ensurepip' (no network required); fall back to downloading 'get-pip.py'.
70+ """
71+ if pipInstalled (pythonExecutablePath ):
72+ return
73+
74+ logger .info ("pip not found in Fusion python, bootstrapping..." )
75+
76+ # ensurepip ships with the CPython standard library and needs no internet access.
77+ try :
78+ executeCommand (pythonExecutablePath , "-m" , "ensurepip" , "--upgrade" )
79+ if pipInstalled (pythonExecutablePath ):
80+ return
81+ except subprocess .CalledProcessError :
82+ logger .warning ("ensurepip unavailable, falling back to get-pip.py" )
83+
84+ # Fallback: download and run get-pip.py (curl ships with both modern Windows and macOS).
85+ pipInstallScriptPath = os .path .join (pythonFolder , "get-pip.py" )
86+ if not os .path .exists (pipInstallScriptPath ):
87+ executeCommand ("curl" , "-fsSL" , "https://bootstrap.pypa.io/get-pip.py" , "-o" , pipInstallScriptPath )
88+
89+ executeCommand (pythonExecutablePath , pipInstallScriptPath )
90+
91+
5892def getInstalledPipPackages (pythonExecutablePath : str ) -> dict [str , str ]:
5993 result : str = executeCommand (pythonExecutablePath , "-m" , "pip" , "freeze" ).stdout
6094 # We don't need to check against packages with a specific hash as those are not required by Synthesis.
@@ -92,17 +126,11 @@ def resolveDependencies() -> bool | None:
92126 progressBar .reset ()
93127 progressBar .show ("Synthesis" , f"Installing dependencies..." , 0 , len (PIP_DEPENDENCY_VERSION_MAP ) * 2 + 2 , 0 )
94128
95- # Install pip manually on macos as it is not included by default? Really?
96- if SYSTEM == "Darwin" and not os .path .exists (os .path .join (pythonFolder , "pip" )):
97- pipInstallScriptPath = os .path .join (pythonFolder , "get-pip.py" )
98- if not os .path .exists (pipInstallScriptPath ):
99- executeCommand ("curl" , "https://bootstrap.pypa.io/get-pip.py" , "-o" , pipInstallScriptPath )
100- progressBar .message = "Downloading PIP Installer..."
101-
102- progressBar .progressValue += 1
103- progressBar .message = "Installing PIP..."
104- executeCommand (pythonExecutablePath , pipInstallScriptPath )
105- progressBar .progressValue += 1
129+ # Fusion's bundled python does not reliably ship pip (missing by default on macOS, and on
130+ # fresh Windows webdeploy installs after a Fusion update), so bootstrap it if it is absent.
131+ progressBar .message = "Installing PIP..."
132+ ensurePipInstalled (pythonFolder , pythonExecutablePath )
133+ progressBar .progressValue += 2
106134
107135 installedPackages = getInstalledPipPackages (pythonExecutablePath )
108136 if packagesOutOfDate (installedPackages ):
0 commit comments