Skip to content

Commit 0f6b349

Browse files
committed
feat: more robust pip installation and upgrade
1 parent 7039a1a commit 0f6b349

File tree

6 files changed

+108
-62
lines changed

6 files changed

+108
-62
lines changed

dist/node12/setup-cpp.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/node12/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/node16/setup-cpp.js

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/node16/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/python/python.ts

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable require-atomic-updates */
12
import { addPath } from "../utils/env/addEnv"
23
import { setupAptPack } from "../utils/setup/setupAptPack"
34
import { setupPacmanPack } from "../utils/setup/setupPacmanPack"
@@ -41,6 +42,7 @@ export async function setupPythonViaSystem(
4142
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4243
_arch: string
4344
): Promise<InstallationInfo> {
45+
let installInfo: InstallationInfo
4446
switch (process.platform) {
4547
case "win32": {
4648
if (setupDir) {
@@ -56,81 +58,123 @@ export async function setupPythonViaSystem(
5658
const pythonSetupDir = dirname(pythonBinPath)
5759
/** The directory which the tool is installed to */
5860
await addPath(pythonSetupDir)
59-
return { installDir: pythonSetupDir, binDir: pythonSetupDir }
61+
installInfo = { installDir: pythonSetupDir, binDir: pythonSetupDir }
62+
break
6063
}
6164
case "darwin": {
62-
return setupBrewPack("python3", version)
65+
installInfo = await setupBrewPack("python3", version)
66+
break
6367
}
6468
case "linux": {
65-
let installInfo: InstallationInfo
6669
if (isArch()) {
6770
installInfo = await setupPacmanPack("python", version)
68-
await setupPacmanPack("python-pip")
6971
} else if (hasDnf()) {
7072
installInfo = setupDnfPack("python3", version)
71-
setupDnfPack("python3-pip")
7273
} else if (isUbuntu()) {
73-
installInfo = await setupAptPack([{ name: "python3", version }, { name: "python3-pip" }])
74+
installInfo = await setupAptPack([{ name: "python3", version }])
7475
} else {
7576
throw new Error("Unsupported linux distributions")
7677
}
77-
return installInfo
78+
break
7879
}
7980
default: {
8081
throw new Error("Unsupported platform")
8182
}
8283
}
84+
await findOrSetupPip((await findPython())!)
85+
return installInfo
8386
}
8487

85-
let setupPythonAndPipTried = false
86-
8788
/// setup python and pip if needed
88-
export async function setupPythonAndPip(): Promise<string> {
89-
let foundPython: string
89+
export async function findOrSetupPythonAndPip(): Promise<string> {
90+
const foundPython = await findOrSetupPython()
91+
const foundPip = await findOrSetupPip(foundPython)
92+
if (foundPip === undefined) {
93+
throw new Error("pip was not installed correctly")
94+
}
95+
setupWheel(foundPython)
96+
return foundPython
97+
}
9098

91-
// install python
99+
let setupPythonTried = false
100+
101+
async function findPython() {
92102
if (which.sync("python3", { nothrow: true }) !== null) {
93-
foundPython = "python3"
103+
return "python3"
94104
} else if (which.sync("python", { nothrow: true }) !== null && (await isBinUptoDate("python", "3.0.0"))) {
95-
foundPython = "python"
96-
} else {
97-
info("python3 was not found. Installing python")
98-
await setupPython(getVersion("python", undefined), "", process.arch)
99-
// try again
100-
if (setupPythonAndPipTried) {
101-
throw new Error("Failed to install python")
102-
}
103-
setupPythonAndPipTried = true
104-
return setupPythonAndPip() // recurse
105+
return "python"
106+
}
107+
return undefined
108+
}
109+
110+
async function findOrSetupPython() {
111+
const maybeFoundPython = await findPython()
112+
if (maybeFoundPython !== undefined) {
113+
return maybeFoundPython
105114
}
106115

107-
assert(typeof foundPython === "string")
116+
if (setupPythonTried) {
117+
throw new Error("Failed to install python")
118+
}
119+
setupPythonTried = true
108120

109-
await setupPip(foundPython)
121+
// install python
122+
info("python3 was not found. Installing python")
123+
await setupPython(getVersion("python", undefined), "", process.arch)
124+
return findOrSetupPython() // recurse
125+
}
110126

111-
// install wheel (required for Conan, Meson, etc.)
112-
execaSync(foundPython, ["-m", "pip", "install", "-U", "wheel"], { stdio: "inherit" })
127+
async function findOrSetupPip(foundPython: string) {
128+
const maybePip = await findPip()
113129

114-
return foundPython
130+
if (maybePip === undefined) {
131+
// install pip if not installed
132+
info("pip was not found. Installing pip")
133+
await setupPip(foundPython)
134+
return findPip() // recurse to check if pip is on PATH and up-to-date
135+
}
136+
137+
return maybePip
138+
}
139+
140+
async function findPip() {
141+
for (const pip of ["pip3", "pip"]) {
142+
if (
143+
which.sync(pip, { nothrow: true }) !== null &&
144+
// eslint-disable-next-line no-await-in-loop
145+
(await isBinUptoDate(pip, DefaultVersions.pip!))
146+
) {
147+
return pip
148+
}
149+
}
150+
return undefined
115151
}
116152

117153
async function setupPip(foundPython: string) {
118-
const mayBePip = unique(["pip3", "pip"])
154+
const upgraded = ensurePipUpgrade(foundPython)
155+
if (!upgraded) {
156+
await setupPipSystem()
157+
}
158+
}
119159

120-
for (const pip of mayBePip) {
121-
if (which.sync(pip, { nothrow: true }) !== null) {
122-
// eslint-disable-next-line no-await-in-loop
123-
if (await isBinUptoDate(pip, DefaultVersions.pip!)) {
124-
return pip
125-
} else {
126-
// upgrade pip
127-
execaSync(foundPython, ["-m", "pip", "install", "-U", "--upgrade", "pip"], { stdio: "inherit" })
128-
return setupPip(foundPython) // recurse to check if pip is on PATH and up-to-date
129-
}
160+
function ensurePipUpgrade(foundPython: string) {
161+
try {
162+
execaSync(foundPython, ["-m", "ensurepip", "-U", "--upgrade"], { stdio: "inherit" })
163+
return true
164+
} catch {
165+
try {
166+
// ensure pip is disabled on Ubuntu
167+
execaSync(foundPython, ["-m", "pip", "install", "--upgrade", "pip"], { stdio: "inherit" })
168+
return true
169+
} catch {
170+
// pip module not found
130171
}
131172
}
173+
// all methods failed
174+
return false
175+
}
132176

133-
// install pip if not installed
177+
async function setupPipSystem() {
134178
if (process.platform === "linux") {
135179
// ensure that pip is installed on Linux (happens when python is found but pip not installed)
136180
if (isArch()) {
@@ -140,11 +184,13 @@ async function setupPip(foundPython: string) {
140184
} else if (isUbuntu()) {
141185
await setupAptPack([{ name: "python3-pip" }])
142186
}
143-
} else {
144-
throw new Error(`Could not find pip on ${process.platform}`)
145187
}
188+
throw new Error(`Could not install pip on ${process.platform}`)
189+
}
146190

147-
return setupPip(foundPython) // recurse to check if pip is on PATH and up-to-date
191+
/** Install wheel (required for Conan, Meson, etc.) */
192+
function setupWheel(foundPython: string) {
193+
execaSync(foundPython, ["-m", "pip", "install", "-U", "wheel"], { stdio: "inherit" })
148194
}
149195

150196
export async function addPythonBaseExecPrefix(python: string) {

src/utils/setup/setupPipPack.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { execaSync } from "execa"
33
import { pathExists } from "path-exists"
44
import { addExeExt, dirname, join } from "patha"
55
import which from "which"
6-
import { addPythonBaseExecPrefix, setupPythonAndPip } from "../../python/python"
6+
import { addPythonBaseExecPrefix, findOrSetupPythonAndPip } from "../../python/python"
77
import { addPath } from "../env/addEnv"
88
import { InstallationInfo } from "./setupBin"
99

@@ -16,7 +16,7 @@ export async function setupPipPack(name: string, version?: string): Promise<Inst
1616
info(`Installing ${name} ${version ?? ""} via pip`)
1717

1818
if (python === undefined) {
19-
python = await setupPythonAndPip()
19+
python = await findOrSetupPythonAndPip()
2020
}
2121

2222
execaSync(python, ["-m", "pip", "install", version !== undefined && version !== "" ? `${name}==${version}` : name], {

0 commit comments

Comments
 (0)