mirror of https://github.com/aminya/setup-cpp
fix: refactor apt functions into separate files
This commit is contained in:
parent
adb1af1e17
commit
0dbc7a0d00
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Get the environment variables to use for the apt command
|
||||||
|
* @param apt The apt command to use
|
||||||
|
* @private Used internally
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getAptEnv(apt: string) {
|
||||||
|
const env: NodeJS.ProcessEnv = { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
|
||||||
|
|
||||||
|
if (apt === "nala") {
|
||||||
|
// if LANG/LC_ALL is not set, enable utf8 otherwise nala fails because of ASCII encoding
|
||||||
|
if (env.LANG === undefined) {
|
||||||
|
env.LANG = "C.UTF-8"
|
||||||
|
}
|
||||||
|
if (env.LC_ALL === undefined) {
|
||||||
|
env.LC_ALL = "C.UTF-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { defaultExecOptions, execRootSync } from "admina"
|
||||||
|
import { getAptEnv } from "./apt-env.js"
|
||||||
|
import { aptTimeout } from "./apt-timeout.js"
|
||||||
|
import { getApt } from "./get-apt.js"
|
||||||
|
import { isAptPackInstalled } from "./is-installed.js"
|
||||||
|
import { updateAptRepos } from "./update.js"
|
||||||
|
|
||||||
|
export async function addAptRepository(repo: string, apt = getApt()) {
|
||||||
|
await installAddAptRepo(apt)
|
||||||
|
execRootSync("add-apt-repository", ["-y", "--no-update", repo], { ...defaultExecOptions, env: getAptEnv(apt) })
|
||||||
|
updateAptRepos(apt)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function installAddAptRepo(apt: string) {
|
||||||
|
if (await isAptPackInstalled("software-properties-common")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
execRootSync(
|
||||||
|
apt,
|
||||||
|
["install", "-y", "--fix-broken", "-o", aptTimeout, "software-properties-common"],
|
||||||
|
{ ...defaultExecOptions, env: getAptEnv(apt) },
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* The timeout to use for apt commands
|
||||||
|
* Wait up to 300 seconds if the apt-get lock is held
|
||||||
|
* @private Used internally
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const aptTimeout = "Dpkg::Lock::Timeout=300"
|
|
@ -0,0 +1,22 @@
|
||||||
|
import which from "which"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if nala is installed
|
||||||
|
*/
|
||||||
|
export function hasNala() {
|
||||||
|
return which.sync("nala", { nothrow: true }) !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the apt command to use
|
||||||
|
* If nala is installed, use that, otherwise use apt-get
|
||||||
|
*/
|
||||||
|
export function getApt() {
|
||||||
|
let apt: string
|
||||||
|
if (hasNala()) {
|
||||||
|
apt = "nala"
|
||||||
|
} else {
|
||||||
|
apt = "apt-get"
|
||||||
|
}
|
||||||
|
return apt
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
export * from "./alternatives.js"
|
export * from "./alternatives.js"
|
||||||
export * from "./apt-key.js"
|
export * from "./apt-key.js"
|
||||||
|
export * from "./apt-repository.js"
|
||||||
|
export * from "./apt-timeout.js"
|
||||||
|
export * from "./get-apt.js"
|
||||||
|
export * from "./init-apt.js"
|
||||||
export * from "./install.js"
|
export * from "./install.js"
|
||||||
export * from "./is-installed.js"
|
export * from "./is-installed.js"
|
||||||
export * from "./update.js"
|
export * from "./update.js"
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { defaultExecOptions, execRootSync } from "admina"
|
||||||
|
import { getAptEnv } from "./apt-env.js"
|
||||||
|
import { aptTimeout } from "./apt-timeout.js"
|
||||||
|
import { filterAndQualifyAptPackages } from "./qualify-install.js"
|
||||||
|
import { updateAptRepos } from "./update.js"
|
||||||
|
|
||||||
|
/** Install gnupg and certificates (usually missing from docker containers) */
|
||||||
|
export async function initApt(apt: string) {
|
||||||
|
// Update the repos
|
||||||
|
updateAptRepos(apt)
|
||||||
|
|
||||||
|
const toInstall = await filterAndQualifyAptPackages(apt, [
|
||||||
|
{ name: "ca-certificates" },
|
||||||
|
{ name: "gnupg" },
|
||||||
|
{ name: "apt-utils" },
|
||||||
|
])
|
||||||
|
|
||||||
|
if (toInstall.length !== 0) {
|
||||||
|
execRootSync(apt, ["install", "-y", "--fix-broken", "-o", aptTimeout, ...toInstall], {
|
||||||
|
...defaultExecOptions,
|
||||||
|
env: getAptEnv(apt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
import { defaultExecOptions, execRootSync } from "admina"
|
import { defaultExecOptions, execRootSync } from "admina"
|
||||||
import { info, warning } from "ci-log"
|
import { info, warning } from "ci-log"
|
||||||
import escapeRegex from "escape-string-regexp"
|
import type { ExecaError } from "execa"
|
||||||
import { type ExecaError, execa } from "execa"
|
import { getAptEnv } from "./apt-env.js"
|
||||||
import which from "which"
|
|
||||||
import { type AddAptKeyOptions, addAptKey } from "./apt-key.js"
|
import { type AddAptKeyOptions, addAptKey } from "./apt-key.js"
|
||||||
import { isAptPackInstalled } from "./is-installed.js"
|
import { addAptRepository } from "./apt-repository.js"
|
||||||
|
import { aptTimeout } from "./apt-timeout.js"
|
||||||
|
import { getApt } from "./get-apt.js"
|
||||||
|
import { initApt } from "./init-apt.js"
|
||||||
|
import { filterAndQualifyAptPackages } from "./qualify-install.js"
|
||||||
import { updateAptRepos } from "./update.js"
|
import { updateAptRepos } from "./update.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,16 +23,9 @@ export type InstallationInfo = {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable require-atomic-updates */
|
/* eslint-disable require-atomic-updates */
|
||||||
let didUpdate: boolean = false
|
export let didUpdate: boolean = false
|
||||||
let didInit: boolean = false
|
let didInit: boolean = false
|
||||||
|
|
||||||
/**
|
|
||||||
* The timeout to use for apt commands
|
|
||||||
* Wait up to 300 seconds if the apt-get lock is held
|
|
||||||
* @private Used internally
|
|
||||||
*/
|
|
||||||
export const aptTimeout = "Dpkg::Lock::Timeout=300"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The information about an apt package
|
* The information about an apt package
|
||||||
*/
|
*/
|
||||||
|
@ -131,6 +127,11 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
|
||||||
return { binDir: "/usr/bin/" }
|
return { binDir: "/usr/bin/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addRepositories(apt: string, packages: AptPackage[]) {
|
||||||
|
const allRepositories = [...new Set(packages.flatMap((pack) => pack.repository ?? []))]
|
||||||
|
await Promise.all(allRepositories.map((repo) => addAptRepository(repo, apt)))
|
||||||
|
}
|
||||||
|
|
||||||
async function addAptKeys(packages: AptPackage[]) {
|
async function addAptKeys(packages: AptPackage[]) {
|
||||||
await Promise.all(packages.map(async (pack) => {
|
await Promise.all(packages.map(async (pack) => {
|
||||||
if (pack.key !== undefined) {
|
if (pack.key !== undefined) {
|
||||||
|
@ -142,179 +143,3 @@ async function addAptKeys(packages: AptPackage[]) {
|
||||||
function isExecaError(err: unknown): err is ExecaError {
|
function isExecaError(err: unknown): err is ExecaError {
|
||||||
return typeof (err as ExecaError).stderr === "string"
|
return typeof (err as ExecaError).stderr === "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if nala is installed
|
|
||||||
*/
|
|
||||||
export function hasNala() {
|
|
||||||
return which.sync("nala", { nothrow: true }) !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the apt command to use
|
|
||||||
* If nala is installed, use that, otherwise use apt-get
|
|
||||||
*/
|
|
||||||
export function getApt() {
|
|
||||||
let apt: string
|
|
||||||
if (hasNala()) {
|
|
||||||
apt = "nala"
|
|
||||||
} else {
|
|
||||||
apt = "apt-get"
|
|
||||||
}
|
|
||||||
return apt
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the environment variables to use for the apt command
|
|
||||||
* @param apt The apt command to use
|
|
||||||
* @private Used internally
|
|
||||||
*/
|
|
||||||
export function getAptEnv(apt: string) {
|
|
||||||
const env: NodeJS.ProcessEnv = { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
|
|
||||||
|
|
||||||
if (apt === "nala") {
|
|
||||||
// if LANG/LC_ALL is not set, enable utf8 otherwise nala fails because of ASCII encoding
|
|
||||||
if (env.LANG === undefined) {
|
|
||||||
env.LANG = "C.UTF-8"
|
|
||||||
}
|
|
||||||
if (env.LC_ALL === undefined) {
|
|
||||||
env.LC_ALL = "C.UTF-8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of apt package to install
|
|
||||||
*/
|
|
||||||
export enum AptPackageType {
|
|
||||||
NameDashVersion = 0,
|
|
||||||
NameEqualsVersion = 1,
|
|
||||||
Name = 2,
|
|
||||||
None = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter out the packages that are already installed and qualify the packages into a full package name/version
|
|
||||||
*/
|
|
||||||
async function filterAndQualifyAptPackages(apt: string, packages: AptPackage[]) {
|
|
||||||
return (await Promise.all(packages.map((pack) => qualifiedNeededAptPackage(apt, pack))))
|
|
||||||
.filter((pack) => pack !== undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function qualifiedNeededAptPackage(apt: string, pack: AptPackage) {
|
|
||||||
// Qualify the packages into full package name/version
|
|
||||||
const qualified = await getAptArg(apt, pack.name, pack.version)
|
|
||||||
// filter out the packages that are already installed
|
|
||||||
return (await isAptPackInstalled(qualified)) ? undefined : qualified
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addRepositories(apt: string, packages: AptPackage[]) {
|
|
||||||
const allRepositories = [...new Set(packages.flatMap((pack) => pack.repository ?? []))]
|
|
||||||
if (allRepositories.length !== 0) {
|
|
||||||
if (!didInit) {
|
|
||||||
await initApt(apt)
|
|
||||||
didInit = true
|
|
||||||
}
|
|
||||||
await installAddAptRepo(apt)
|
|
||||||
for (const repo of allRepositories) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
execRootSync("add-apt-repository", ["-y", "--no-update", repo], { ...defaultExecOptions, env: getAptEnv(apt) })
|
|
||||||
}
|
|
||||||
updateAptRepos(apt)
|
|
||||||
didUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function aptPackageType(apt: string, name: string, version: string | undefined): Promise<AptPackageType> {
|
|
||||||
if (version !== undefined && version !== "") {
|
|
||||||
const { stdout } = await execa("apt-cache", [
|
|
||||||
"search",
|
|
||||||
"--names-only",
|
|
||||||
`^${escapeRegex(name)}-${escapeRegex(version)}$`,
|
|
||||||
], { env: getAptEnv(apt), stdio: "pipe" })
|
|
||||||
if (stdout.trim() !== "") {
|
|
||||||
return AptPackageType.NameDashVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// check if apt-get show can find the version
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
||||||
const { stdout } = await execa("apt-cache", ["show", `${name}=${version}`], { env: getAptEnv(apt) })
|
|
||||||
if (stdout.trim() === "") {
|
|
||||||
return AptPackageType.NameEqualsVersion
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { stdout: showStdout } = await execa("apt-cache", ["show", name], { env: getAptEnv(apt), stdio: "pipe" })
|
|
||||||
if (showStdout.trim() !== "") {
|
|
||||||
return AptPackageType.Name
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
// If apt-cache fails, update the repos and try again
|
|
||||||
if (!didUpdate) {
|
|
||||||
updateAptRepos(getApt())
|
|
||||||
didUpdate = true
|
|
||||||
return aptPackageType(apt, name, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return AptPackageType.None
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAptArg(apt: string, name: string, version: string | undefined) {
|
|
||||||
const package_type = await aptPackageType(apt, name, version)
|
|
||||||
switch (package_type) {
|
|
||||||
case AptPackageType.NameDashVersion:
|
|
||||||
return `${name}-${version}`
|
|
||||||
case AptPackageType.NameEqualsVersion:
|
|
||||||
return `${name}=${version}`
|
|
||||||
case AptPackageType.Name:
|
|
||||||
if (version !== undefined && version !== "") {
|
|
||||||
warning(`Could not find package ${name} with version ${version}. Installing the latest version.`)
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
default:
|
|
||||||
throw new Error(`Could not find package ${name} ${version ?? ""}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function installAddAptRepo(apt: string) {
|
|
||||||
if (await isAptPackInstalled("software-properties-common")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
execRootSync(
|
|
||||||
apt,
|
|
||||||
["install", "-y", "--fix-broken", "-o", aptTimeout, "software-properties-common"],
|
|
||||||
{ ...defaultExecOptions, env: getAptEnv(apt) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Install gnupg and certificates (usually missing from docker containers) */
|
|
||||||
async function initApt(apt: string) {
|
|
||||||
// Update the repos if needed
|
|
||||||
if (!didUpdate) {
|
|
||||||
updateAptRepos(apt)
|
|
||||||
didUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const toInstall = await filterAndQualifyAptPackages(apt, [
|
|
||||||
{ name: "ca-certificates" },
|
|
||||||
{ name: "gnupg" },
|
|
||||||
{ name: "apt-utils" },
|
|
||||||
])
|
|
||||||
|
|
||||||
if (toInstall.length !== 0) {
|
|
||||||
execRootSync(apt, ["install", "-y", "--fix-broken", "-o", aptTimeout, ...toInstall], {
|
|
||||||
...defaultExecOptions,
|
|
||||||
env: getAptEnv(apt),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { execa } from "execa"
|
import { execa } from "execa"
|
||||||
import { getAptEnv } from "./install.js"
|
import { getAptEnv } from "./apt-env.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a package is installed
|
* Check if a package is installed
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { warning } from "ci-log"
|
||||||
|
import escapeRegex from "escape-string-regexp"
|
||||||
|
import { execa } from "execa"
|
||||||
|
import { getAptEnv } from "./apt-env.js"
|
||||||
|
import { getApt } from "./get-apt.js"
|
||||||
|
import { type AptPackage, didUpdate } from "./install.js"
|
||||||
|
import { isAptPackInstalled } from "./is-installed.js"
|
||||||
|
import { updateAptRepos } from "./update.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of apt package to install
|
||||||
|
*/
|
||||||
|
export enum AptPackageType {
|
||||||
|
NameDashVersion = 0,
|
||||||
|
NameEqualsVersion = 1,
|
||||||
|
Name = 2,
|
||||||
|
None = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter out the packages that are already installed and qualify the packages into a full package name/version
|
||||||
|
*/
|
||||||
|
export async function filterAndQualifyAptPackages(apt: string, packages: AptPackage[]) {
|
||||||
|
return (await Promise.all(packages.map((pack) => qualifiedNeededAptPackage(apt, pack))))
|
||||||
|
.filter((pack) => pack !== undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function qualifiedNeededAptPackage(apt: string, pack: AptPackage) {
|
||||||
|
// Qualify the packages into full package name/version
|
||||||
|
const qualified = await getAptArg(apt, pack.name, pack.version)
|
||||||
|
// filter out the packages that are already installed
|
||||||
|
return (await isAptPackInstalled(qualified)) ? undefined : qualified
|
||||||
|
}
|
||||||
|
|
||||||
|
async function aptPackageType(apt: string, name: string, version: string | undefined): Promise<AptPackageType> {
|
||||||
|
if (version !== undefined && version !== "") {
|
||||||
|
const { stdout } = await execa("apt-cache", [
|
||||||
|
"search",
|
||||||
|
"--names-only",
|
||||||
|
`^${escapeRegex(name)}-${escapeRegex(version)}$`,
|
||||||
|
], { env: getAptEnv(apt), stdio: "pipe" })
|
||||||
|
if (stdout.trim() !== "") {
|
||||||
|
return AptPackageType.NameDashVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// check if apt-get show can find the version
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const { stdout } = await execa("apt-cache", ["show", `${name}=${version}`], { env: getAptEnv(apt) })
|
||||||
|
if (stdout.trim() === "") {
|
||||||
|
return AptPackageType.NameEqualsVersion
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: showStdout } = await execa("apt-cache", ["show", name], { env: getAptEnv(apt), stdio: "pipe" })
|
||||||
|
if (showStdout.trim() !== "") {
|
||||||
|
return AptPackageType.Name
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// If apt-cache fails, update the repos and try again
|
||||||
|
if (!didUpdate) {
|
||||||
|
updateAptRepos(getApt())
|
||||||
|
return aptPackageType(apt, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AptPackageType.None
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAptArg(apt: string, name: string, version: string | undefined) {
|
||||||
|
const package_type = await aptPackageType(apt, name, version)
|
||||||
|
switch (package_type) {
|
||||||
|
case AptPackageType.NameDashVersion:
|
||||||
|
return `${name}-${version}`
|
||||||
|
case AptPackageType.NameEqualsVersion:
|
||||||
|
return `${name}=${version}`
|
||||||
|
case AptPackageType.Name:
|
||||||
|
if (version !== undefined && version !== "") {
|
||||||
|
warning(`Could not find package ${name} with version ${version}. Installing the latest version.`)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
default:
|
||||||
|
throw new Error(`Could not find package ${name} ${version ?? ""}`)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import { defaultExecOptions, execRootSync } from "admina"
|
import { defaultExecOptions, execRootSync } from "admina"
|
||||||
import { aptTimeout, getApt, getAptEnv } from "./install.js"
|
import { getAptEnv } from "./apt-env.js"
|
||||||
|
import { aptTimeout } from "./apt-timeout.js"
|
||||||
|
import { getApt } from "./get-apt.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the apt repositories
|
* Update the apt repositories
|
||||||
|
|
Loading…
Reference in New Issue