fix: detect externally managed pythons

This commit is contained in:
Amin Yahyaabadi 2025-03-23 02:40:21 -07:00
parent 9a2e61232c
commit 1158c50fa0
6 changed files with 82 additions and 34 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

@ -1,7 +1,6 @@
import assert from "assert"
import { homedir } from "os"
import { dirname, join, parse as pathParse } from "path"
import { getExecOutput } from "@actions/exec"
import ciInfo from "ci-info"
const { GITHUB_ACTIONS } = ciInfo
import { endGroup, startGroup } from "@actions/core"
@ -9,7 +8,6 @@ import { info, notice, warning } from "ci-log"
import { addPath } from "envosman"
import { execa } from "execa"
import { readdir } from "fs/promises"
import memoize from "memoizee"
import { pathExists } from "path-exists"
import { addExeExt } from "patha"
import { hasApk, installApkPackage } from "setup-alpine"
@ -27,11 +25,11 @@ import { setupPacmanPack } from "../utils/setup/setupPacmanPack.js"
import {
hasPipxBinary,
hasPipxModule,
isExternallyManaged,
setupPipPackSystem,
setupPipPackWithPython,
} from "../utils/setup/setupPipPack.js"
import { isBinUptoDate } from "../utils/setup/version.js"
import { unique } from "../utils/std/index.js"
import { getVersionDefault, isMinVersion } from "../versions/versions.js"
export async function setupPython(
@ -356,6 +354,11 @@ async function setupPip(foundPython: string) {
}
async function ensurePipUpgrade(foundPython: string) {
if (await isExternallyManaged(foundPython)) {
// let system tools handle pip
return false
}
try {
await execa(foundPython, ["-m", "ensurepip", "-U", "--upgrade"], { stdio: "inherit" })
return true
@ -373,29 +376,3 @@ async function ensurePipUpgrade(foundPython: string) {
// all methods failed
return false
}
async function addPythonBaseExecPrefix_(python: string) {
const dirs: string[] = []
// detection based on the platform
if (process.platform === "linux") {
dirs.push("/home/runner/.local/bin/")
} else if (process.platform === "darwin") {
dirs.push("/usr/local/bin/")
}
// detection using python.sys
const base_exec_prefix = (await getExecOutput(`${python} -c "import sys;print(sys.base_exec_prefix);"`)).stdout.trim()
// any of these are possible depending on the operating system!
dirs.push(join(base_exec_prefix, "Scripts"), join(base_exec_prefix, "Scripts", "bin"), join(base_exec_prefix, "bin"))
// remove duplicates
return unique(dirs)
}
/**
* Add the base exec prefix to the PATH. This is required for Conan, Meson, etc. to work properly.
*
* The answer is cached for subsequent calls
*/
export const addPythonBaseExecPrefix = memoize(addPythonBaseExecPrefix_, { promise: true })

View File

@ -1,5 +1,7 @@
import { dirname, join } from "path"
import { info } from "@actions/core"
import { getExecOutput } from "@actions/exec"
import { warning } from "ci-log"
import { addPath } from "envosman"
import { execa, execaSync } from "execa"
import memoize from "memoizee"
@ -12,15 +14,17 @@ import { installBrewPack } from "setup-brew"
import { untildifyUser } from "untildify-user"
import which from "which"
import { rcOptions } from "../../cli-options.js"
import { addPythonBaseExecPrefix, setupPython } from "../../python/python.js"
import { setupPython } from "../../python/python.js"
import { getVersion } from "../../versions/versions.js"
import { hasDnf } from "../env/hasDnf.js"
import { isArch } from "../env/isArch.js"
import { isUbuntu } from "../env/isUbuntu.js"
import { ubuntuVersion } from "../env/ubuntu_version.js"
import { unique } from "../std/index.js"
import type { InstallationInfo } from "./setupBin.js"
import { setupDnfPack } from "./setupDnfPack.js"
import { setupPacmanPack } from "./setupPacmanPack.js"
import { getBinVersion } from "./version.js"
export type SetupPipPackOptions = {
/** Whether to use pipx instead of pip */
@ -301,3 +305,70 @@ export async function setupPipPackSystem(name: string, givenAddPythonPrefix?: bo
}
return null
}
async function addPythonBaseExecPrefix_(python: string) {
const dirs: string[] = []
// detection based on the platform
if (process.platform === "linux") {
dirs.push("/home/runner/.local/bin/")
} else if (process.platform === "darwin") {
dirs.push("/usr/local/bin/")
}
// detection using python.sys
const base_exec_prefix = await getPythonBaseExecPrefix(python)
// any of these are possible depending on the operating system!
dirs.push(join(base_exec_prefix, "Scripts"), join(base_exec_prefix, "Scripts", "bin"), join(base_exec_prefix, "bin"))
// remove duplicates
return unique(dirs)
}
/**
* Add the base exec prefix to the PATH. This is required for Conan, Meson, etc. to work properly.
*
* The answer is cached for subsequent calls
*/
export const addPythonBaseExecPrefix = memoize(addPythonBaseExecPrefix_, { promise: true })
async function getPythonBaseExecPrefix_(python: string) {
return (await getExecOutput(`${python} -c "import sys;print(sys.base_exec_prefix);"`)).stdout.trim()
}
/**
* Get the base exec prefix of a Python installation
* This is the directory where the Python interpreter is installed
* and where the standard library is located
*/
export const getPythonBaseExecPrefix = memoize(getPythonBaseExecPrefix_, { promise: true })
async function isExternallyManaged_(python: string) {
try {
const base_exec_prefix = await getPythonBaseExecPrefix(python)
const pythonVersion = await getBinVersion(python)
if (pythonVersion === undefined) {
warning(`Failed to get the version of ${python}`)
return false
}
const externallyManagedPath = join(
base_exec_prefix,
"lib",
`python${pythonVersion.major}.${pythonVersion.minor}`,
"EXTERNALLY-MANAGED",
)
return pathExists(externallyManagedPath)
} catch (err) {
warning(`Failed to check if ${python} is externally managed: ${err}`)
return false
}
}
/**
* Check if the given Python installation is externally managed
* This is required for Conan, Meson, etc. to work properly
*
* The answer is cached for subsequent calls
*/
export const isExternallyManaged = memoize(isExternallyManaged_, { promise: true })