feat: skip installation of pip/pipx packages if already installed

This commit is contained in:
Amin Yahyaabadi 2024-09-17 15:32:20 -07:00
parent 67a1d8d27d
commit 99db11072d
No known key found for this signature in database
GPG Key ID: F52AF77F636088F0
6 changed files with 83 additions and 19 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -56,12 +56,20 @@ async function setupPipx(foundPython: string) {
} }
} }
await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" }) await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" })
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false }) await setupVenv(foundPython)
} catch (err) { } catch (err) {
warning(`Failed to install pipx: ${(err as Error).toString()}. Ignoring...`) warning(`Failed to install pipx: ${(err as Error).toString()}. Ignoring...`)
} }
} }
async function setupVenv(foundPython: string) {
try {
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false })
} catch (err) {
warning(`Failed to install venv: ${(err as Error).toString()}. Ignoring...`)
}
}
/** Setup wheel and setuptools */ /** Setup wheel and setuptools */
async function setupWheel(foundPython: string) { async function setupWheel(foundPython: string) {
try { try {
@ -70,9 +78,9 @@ async function setupWheel(foundPython: string) {
isLibrary: true, isLibrary: true,
usePipx: false, usePipx: false,
}) })
await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: true, isLibrary: true, usePipx: false }) await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: false, isLibrary: true, usePipx: false })
} catch (err) { } catch (err) {
warning(`Failed to install setuptools or wheel: ${(err as Error).toString()}. Ignoring...`) warning(`Failed to install setuptools/wheel: ${(err as Error).toString()}. Ignoring...`)
} }
} }

View File

@ -52,7 +52,16 @@ export async function setupPipPackWithPython(
const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython)) const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython))
const pip = isPipx ? "pipx" : "pip" const pip = isPipx ? "pipx" : "pip"
const hasPackage = await pipHasPackage(givenPython, name) // if upgrade is not requested, check if the package is already installed, and return if it is
if (!upgrade) {
const installed = await pipPackageIsInstalled(givenPython, pip, name)
if (installed) {
const binDir = await finishPipPackageInstall(givenPython, name)
return { binDir }
}
}
const hasPackage = await pipHasPackage(givenPython, pip, name)
if (hasPackage) { if (hasPackage) {
try { try {
info(`Installing ${name} ${version ?? ""} via ${pip}`) info(`Installing ${name} ${version ?? ""} via ${pip}`)
@ -79,20 +88,21 @@ export async function setupPipPackWithPython(
throw new Error(`Failed to install ${name} via ${pip}: ${err}.`) throw new Error(`Failed to install ${name} via ${pip}: ${err}.`)
} }
} }
} else { } else if ((await setupPipPackSystem(name)) === null) {
if ((await setupPipPackSystem(name)) === null) { throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
}
} }
const execPaths = await addPythonBaseExecPrefix(givenPython) const binDir = await finishPipPackageInstall(givenPython, name)
const binDir = await findBinDir(execPaths, name)
await addPath(binDir, rcOptions)
return { binDir } return { binDir }
} }
async function finishPipPackageInstall(givenPython: string, name: string) {
const pythonBaseExecPrefix = await addPythonBaseExecPrefix(givenPython)
const binDir = await findBinDir(pythonBaseExecPrefix, name)
await addPath(binDir, rcOptions)
return binDir
}
export async function hasPipx(givenPython: string) { export async function hasPipx(givenPython: string) {
return (await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })).exitCode === 0 return (await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })).exitCode === 0
} }
@ -153,8 +163,54 @@ async function getPython_(): Promise<string> {
} }
const getPython = memoize(getPython_, { promise: true }) const getPython = memoize(getPython_, { promise: true })
async function pipHasPackage(python: string, name: string) { type PipxShowType = {
const result = await execa(python, ["-m", "pip", "-qq", "index", "versions", name], { venvs: Record<string, {
metadata: {
main_package: {
package: string
package_or_url: string
apps: string[]
}
}
}>
}
async function pipPackageIsInstalled(python: string, pip: string, name: string) {
try {
if (pip === "pipx") {
const result = await execa(python, ["-m", pip, "list", "--json"], {
stdio: "ignore",
reject: false,
})
if (result.exitCode !== 0 || typeof result.stdout !== "string") {
return false
}
const pipxOut = JSON.parse(result.stdout as unknown as string) as PipxShowType
// search among the venvs
if (name in pipxOut.venvs) {
return true
}
// search among the urls
for (const venv of Object.values(pipxOut.venvs)) {
if (venv.metadata.main_package.package_or_url === name || venv.metadata.main_package.package === name) {
return true
}
}
}
const result = await execa(python, ["-m", pip, "-qq", "show", name], {
stdio: "ignore",
reject: false,
})
return result.exitCode === 0
} catch {
return false
}
}
async function pipHasPackage(python: string, pip: string, name: string) {
const result = await execa(python, ["-m", pip, "-qq", "index", "versions", name], {
stdio: "ignore", stdio: "ignore",
reject: false, reject: false,
}) })