refactor: refactor and add docs for setup-apt functions

This commit is contained in:
Amin Yahyaabadi 2024-08-16 02:13:47 -07:00
parent 1865b24b57
commit ad1b1ee820
No known key found for this signature in database
GPG Key ID: F52AF77F636088F0
17 changed files with 662 additions and 509 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,77 +19,131 @@ npm install --save setup-apt
<!-- INSERT GENERATED DOCS START -->
### `InstallationInfo` (type)
### `aptTimeout` (variable)
### `AptPackage` (type)
### `installAptPack` (function)
A function that installs a package using apt
**Parameters:**
- packages (`AptPackage[]`)
- update (`boolean`)
**returns:** Promise<InstallationInfo>
### `hasNala` (function)
**returns:** boolean
### `getApt` (function)
**returns:** string
### `addAptKeyViaServer` (function)
**Parameters:**
- keys (`string[]`)
- name (`string`)
- server (`string`)
**returns:** Promise<string>
### `addAptKeyViaDownload` (function)
**Parameters:**
- name (`string`)
- url (`string`)
**returns:** Promise<string>
### `updateAptAlternatives` (function)
Update the alternatives for a package
**Parameters:**
- name (`string`)
- path (`string`)
- rcOptions (`RcOptions`)
- priority (`number`)
- name (`string`) - The name of the package
- path (`string`) - The path to the binary
- priority (`number`) - The priority of the alternative (Defaults to `40`)
**returns:** Promise<void>
### `addUpdateAlternativesToRc` (function)
Add the update-alternatives command to the rc file
**Parameters:**
- name (`string`) - The name of the package
- path (`string`) - The path to the binary
- rcOptions (`RcOptions`) - The options for the rc file to add the update-alternatives command to
- priority (`number`) - The priority of the alternative (Defaults to `40`)
**returns:** Promise<void>
### `isAptPackInstalled` (function)
Check if a package is installed
**Parameters:**
- pack (`string`)
- pack (`string`) - The package to check
**returns:** Promise<boolean>
### `isAptPackRegexInstalled` (function)
Check if a package matching a regexp is installed
**Parameters:**
- regexp (`string`)
- regexp (`string`) - The regexp to check
**returns:** Promise<boolean>
### `updateRepos` (function)
Update the apt repositories
**Parameters:**
- apt (`string`) - The apt command to use (optional)
**returns:** void
### `InstallationInfo` (type)
The information about an installation result
### `aptTimeout` (variable)
The timeout to use for apt commands
Wait up to 300 seconds if the apt-get lock is held
### `AptPackage` (type)
The information about an apt package
### `installAptPack` (function)
Install a package using apt
**Parameters:**
- packages (`AptPackage[]`) - The packages to install (name, and optional info like version and repositories)
- update (`boolean`) - Whether to update the package list before installing (Defaults to `false`)
**returns:** Promise<InstallationInfo>
### `hasNala` (function)
Check if nala is installed
**returns:** boolean
### `getApt` (function)
Get the apt command to use
If nala is installed, use that, otherwise use apt-get
**returns:** string
### `getEnv` (function)
Get the environment variables to use for the apt command
**Parameters:**
- apt (`string`) - The apt command to use
**returns:** ProcessEnv
### `addAptKeyViaServer` (function)
Add an apt key via a keyserver
**Parameters:**
- keys (`string[]`) - The keys to add
- name (`string`) - The name of the key
- server (`string`) - The keyserver to use (Defaults to `keyserver.ubuntu.com`)
**returns:** Promise<string>
### `addAptKeyViaDownload` (function)
Add an apt key via a download
**Parameters:**
- name (`string`) - The name of the key
- url (`string`) - The URL of the key
**returns:** Promise<string>
<!-- INSERT GENERATED DOCS END -->
## 🤝 Contributing

View File

@ -0,0 +1,40 @@
import { promises } from "fs"
import { execRoot } from "admina"
import { GITHUB_ACTIONS } from "ci-info"
import { sourceRC } from "os-env"
import type { RcOptions } from "os-env/dist/rc-file.js"
const { appendFile } = promises
/**
* Update the alternatives for a package
* @param name The name of the package
* @param path The path to the binary
* @param priority The priority of the alternative (Defaults to `40`)
*/
export async function updateAptAlternatives(name: string, path: string, priority: number = 40) {
await execRoot("update-alternatives", ["--install", `/usr/bin/${name}`, name, path, priority.toString()])
}
/**
* Add the update-alternatives command to the rc file
* @param name The name of the package
* @param path The path to the binary
* @param rcOptions The options for the rc file to add the update-alternatives command to
* @param priority The priority of the alternative (Defaults to `40`)
*/
export async function addUpdateAlternativesToRc(
name: string,
path: string,
rcOptions: RcOptions,
priority: number = 40,
) {
if (GITHUB_ACTIONS) {
await updateAptAlternatives(name, path, priority)
} else {
await sourceRC(rcOptions)
await appendFile(
rcOptions.rcPath,
`\nif [ $UID -eq 0 ]; then update-alternatives --install /usr/bin/${name} ${name} ${path} ${priority}; fi\n`,
)
}
}

View File

@ -0,0 +1,62 @@
import { execRoot, execRootSync } from "admina"
import { warning } from "ci-log"
import { execa } from "execa"
import { pathExists } from "path-exists"
import { installAptPack } from "./install.js"
function initGpg() {
execRootSync("gpg", ["-k"])
}
/**
* Add an apt key via a keyserver
* @param keys The keys to add
* @param name The name of the key
* @param server The keyserver to use (Defaults to `keyserver.ubuntu.com`)
* @returns The file name of the key that was added or `undefined` if it failed
*/
export async function addAptKeyViaServer(keys: string[], name: string, server = "keyserver.ubuntu.com") {
try {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
initGpg()
await Promise.all(
keys.map(async (key) => {
await execRoot("gpg", [
"--no-default-keyring",
"--keyring",
`gnupg-ring:${fileName}`,
"--keyserver",
server,
"--recv-keys",
key,
])
await execRoot("chmod", ["644", fileName])
}),
)
}
return fileName
} catch (err) {
warning(`Failed to add apt key via server ${server}: ${err}`)
return undefined
}
}
/**
* Add an apt key via a download
* @param name The name of the key
* @param url The URL of the key
* @returns The file name of the key that was added
*/
export async function addAptKeyViaDownload(name: string, url: string) {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
initGpg()
await installAptPack([{ name: "curl" }, { name: "ca-certificates" }], undefined)
await execa("curl", ["-s", url, "-o", `/tmp/${name}`])
execRootSync("gpg", ["--no-default-keyring", "--keyring", `gnupg-ring:${fileName}`, "--import", `/tmp/${name}`])
execRootSync("chmod", ["644", fileName])
}
return fileName
}

View File

@ -1,354 +1,5 @@
import { promises } from "fs"
import { defaultExecOptions, execRoot, execRootSync } from "admina"
import { GITHUB_ACTIONS } from "ci-info"
import { info, warning } from "ci-log"
import escapeRegex from "escape-string-regexp"
import { type ExecaError, execa } from "execa"
import { sourceRC } from "os-env"
import type { RcOptions } from "os-env/dist/rc-file.js"
import { pathExists } from "path-exists"
import which from "which"
const { appendFile } = promises
export type InstallationInfo = {
/** The install dir of the package (Defaults to `undefined`) */
installDir?: string
/** The bin dir of the package (Defaults to `/usr/bin`) */
binDir: string
/** The bin path of the package (Defaults to `undefined`) */
bin?: string
}
/* eslint-disable require-atomic-updates */
let didUpdate: boolean = false
let didInit: boolean = false
// wait up to 300 seconds if the apt-get lock is held
export const aptTimeout = "Dpkg::Lock::Timeout=300"
export type AptPackage = {
name: string
version?: string
repositories?: string[]
}
const retryErrors = [
"E: Could not get lock",
"dpkg: error processing archive",
"dpkg: error: dpkg status database is locked by another process",
]
/** A function that installs a package using apt */
export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
const apt: string = getApt()
for (const { name, version } of packages) {
info(`Installing ${name} ${version ?? ""} via ${apt}`)
}
// Update the repos if needed
if (update) {
updateRepos(apt)
didUpdate = true
}
// Add the repos if needed
await addRepositories(apt, packages)
const needToInstall = await filterAndQualifyAptPackages(apt, packages)
if (needToInstall.length === 0) {
info("All packages are already installed")
return { binDir: "/usr/bin/" }
}
// Initialize apt if needed
if (!didInit) {
await initApt(apt)
didInit = true
}
// Install
try {
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], { ...defaultExecOptions, env: getEnv(apt) })
} catch (err) {
if (isExecaError(err)) {
if (retryErrors.some((error) => err.stderr.includes(error))) {
warning(`Failed to install packages ${needToInstall}. Retrying...`)
execRootSync(
apt,
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
{ ...defaultExecOptions, env: getEnv(apt) },
)
}
} else {
throw err
}
}
return { binDir: "/usr/bin/" }
}
function isExecaError(err: unknown): err is ExecaError {
return typeof (err as ExecaError).stderr === "string"
}
export function hasNala() {
return which.sync("nala", { nothrow: true }) !== null
}
export function getApt() {
let apt: string
if (hasNala()) {
apt = "nala"
} else {
apt = "apt-get"
}
return apt
}
function getEnv(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
}
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.repositories ?? []))]
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: getEnv(apt) })
}
updateRepos(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: getEnv(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: getEnv(apt) })
if (stdout.trim() === "") {
return AptPackageType.NameEqualsVersion
}
} catch {
// ignore
}
}
try {
const { stdout: showStdout } = await execa("apt-cache", ["show", name], { env: getEnv(apt), stdio: "pipe" })
if (showStdout.trim() !== "") {
return AptPackageType.Name
}
} catch {
// ignore
}
// If apt-cache fails, update the repos and try again
if (!didUpdate) {
updateRepos(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 ?? ""}`)
}
}
function updateRepos(apt: string) {
execRootSync(
apt,
apt !== "nala" ? ["update", "-y", "-o", aptTimeout] : ["update", "-o", aptTimeout],
{ ...defaultExecOptions, env: getEnv(apt) },
)
}
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: getEnv(apt) },
)
}
/** Install gnupg and certificates (usually missing from docker containers) */
async function initApt(apt: string) {
// Update the repos if needed
if (!didUpdate) {
updateRepos(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: getEnv(apt),
})
}
await Promise.all([
addAptKeyViaServer(["3B4FE6ACC0B21F32", "40976EAF437D05B5"], "setup-cpp-ubuntu-archive.gpg"),
addAptKeyViaServer(["1E9377A2BA9EF27F"], "launchpad-toolchain.gpg"),
])
}
function initGpg() {
execRootSync("gpg", ["-k"])
}
export async function addAptKeyViaServer(keys: string[], name: string, server = "keyserver.ubuntu.com") {
try {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
initGpg()
await Promise.all(
keys.map(async (key) => {
await execRoot("gpg", [
"--no-default-keyring",
"--keyring",
`gnupg-ring:${fileName}`,
"--keyserver",
server,
"--recv-keys",
key,
])
await execRoot("chmod", ["644", fileName])
}),
)
}
return fileName
} catch (err) {
warning(`Failed to add apt key via server ${server}: ${err}`)
return undefined
}
}
export async function addAptKeyViaDownload(name: string, url: string) {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
initGpg()
await installAptPack([{ name: "curl" }, { name: "ca-certificates" }], undefined)
await execa("curl", ["-s", url, "-o", `/tmp/${name}`])
execRootSync("gpg", ["--no-default-keyring", "--keyring", `gnupg-ring:${fileName}`, "--import", `/tmp/${name}`])
execRootSync("chmod", ["644", fileName])
}
return fileName
}
export async function updateAptAlternatives(name: string, path: string, rcOptions: RcOptions, priority: number = 40) {
if (GITHUB_ACTIONS) {
await execRoot("update-alternatives", ["--install", `/usr/bin/${name}`, name, path, priority.toString()])
} else {
await sourceRC(rcOptions)
await appendFile(
rcOptions.rcPath,
`\nif [ $UID -eq 0 ]; then update-alternatives --install /usr/bin/${name} ${name} ${path} ${priority}; fi\n`,
)
}
}
export async function isAptPackInstalled(pack: string) {
try {
// check if a package is installed
const { stdout } = await execa("dpkg", ["-s", pack], { env: getEnv("apt-get"), stdio: "pipe" })
if (typeof stdout !== "string") {
return false
}
const lines = stdout.split("\n")
// check if the output contains a line that starts with "Status: install ok installed"
return lines.some((line) => line.startsWith("Status: install ok installed"))
} catch {
return false
}
}
export async function isAptPackRegexInstalled(regexp: string) {
try {
// check if a package matching the regexp is installed
const { stdout } = await execa("dpkg", ["-l", regexp], { env: getEnv("apt-get"), stdio: "pipe" })
if (typeof stdout !== "string") {
return false
}
const lines = stdout.split("\n")
// check if the output contains any lines that start with "ii"
return lines.some((line) => line.startsWith("ii"))
} catch {
return false
}
}
export * from "./alternatives.js"
export * from "./apt-key.js"
export * from "./install.js"
export * from "./is-installed.js"
export * from "./update.js"

View File

@ -0,0 +1,290 @@
import { defaultExecOptions, execRootSync } from "admina"
import { info, warning } from "ci-log"
import escapeRegex from "escape-string-regexp"
import { type ExecaError, execa } from "execa"
import which from "which"
import { addAptKeyViaServer } from "./apt-key.js"
import { isAptPackInstalled } from "./is-installed.js"
import { updateRepos } from "./update.js"
/**
* The information about an installation result
*/
export type InstallationInfo = {
/** The install dir of the package (Defaults to `undefined`) */
installDir?: string
/** The bin dir of the package (Defaults to `/usr/bin`) */
binDir: string
/** The bin path of the package (Defaults to `undefined`) */
bin?: string
}
/* eslint-disable require-atomic-updates */
let didUpdate: 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
*/
export type AptPackage = {
/** The name of the package */
name: string
/** The version of the package (optional) */
version?: string
/** The repositories to add before installing the package (optional) */
repositories?: string[]
}
const retryErrors = [
"E: Could not get lock",
"dpkg: error processing archive",
"dpkg: error: dpkg status database is locked by another process",
]
/**
* Install a package using apt
*
* @param packages The packages to install (name, and optional info like version and repositories)
* @param update Whether to update the package list before installing (Defaults to `false`)
*/
export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
const apt: string = getApt()
for (const { name, version } of packages) {
info(`Installing ${name} ${version ?? ""} via ${apt}`)
}
// Update the repos if needed
if (update) {
updateRepos(apt)
didUpdate = true
}
// Add the repos if needed
await addRepositories(apt, packages)
const needToInstall = await filterAndQualifyAptPackages(apt, packages)
if (needToInstall.length === 0) {
info("All packages are already installed")
return { binDir: "/usr/bin/" }
}
// Initialize apt if needed
if (!didInit) {
await initApt(apt)
didInit = true
}
// Install
try {
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], { ...defaultExecOptions, env: getEnv(apt) })
} catch (err) {
if (isExecaError(err)) {
if (retryErrors.some((error) => err.stderr.includes(error))) {
warning(`Failed to install packages ${needToInstall}. Retrying...`)
execRootSync(
apt,
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
{ ...defaultExecOptions, env: getEnv(apt) },
)
}
} else {
throw err
}
}
return { binDir: "/usr/bin/" }
}
function isExecaError(err: unknown): err is ExecaError {
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 getEnv(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.repositories ?? []))]
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: getEnv(apt) })
}
updateRepos(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: getEnv(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: getEnv(apt) })
if (stdout.trim() === "") {
return AptPackageType.NameEqualsVersion
}
} catch {
// ignore
}
}
try {
const { stdout: showStdout } = await execa("apt-cache", ["show", name], { env: getEnv(apt), stdio: "pipe" })
if (showStdout.trim() !== "") {
return AptPackageType.Name
}
} catch {
// ignore
}
// If apt-cache fails, update the repos and try again
if (!didUpdate) {
updateRepos(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: getEnv(apt) },
)
}
/** Install gnupg and certificates (usually missing from docker containers) */
async function initApt(apt: string) {
// Update the repos if needed
if (!didUpdate) {
updateRepos(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: getEnv(apt),
})
}
await Promise.all([
addAptKeyViaServer(["3B4FE6ACC0B21F32", "40976EAF437D05B5"], "setup-cpp-ubuntu-archive.gpg"),
addAptKeyViaServer(["1E9377A2BA9EF27F"], "launchpad-toolchain.gpg"),
])
}

View File

@ -0,0 +1,42 @@
import { execa } from "execa"
import { getEnv } from "./install.js"
/**
* Check if a package is installed
* @param pack The package to check
* @returns `true` if the package is installed, `false` otherwise
*/
export async function isAptPackInstalled(pack: string) {
try {
// check if a package is installed
const { stdout } = await execa("dpkg", ["-s", pack], { env: getEnv("apt-get"), stdio: "pipe" })
if (typeof stdout !== "string") {
return false
}
const lines = stdout.split("\n")
// check if the output contains a line that starts with "Status: install ok installed"
return lines.some((line) => line.startsWith("Status: install ok installed"))
} catch {
return false
}
}
/**
* Check if a package matching a regexp is installed
* @param regexp The regexp to check
* @returns `true` if a package is installed, `false` otherwise
*/
export async function isAptPackRegexInstalled(regexp: string) {
try {
// check if a package matching the regexp is installed
const { stdout } = await execa("dpkg", ["-l", regexp], { env: getEnv("apt-get"), stdio: "pipe" })
if (typeof stdout !== "string") {
return false
}
const lines = stdout.split("\n")
// check if the output contains any lines that start with "ii"
return lines.some((line) => line.startsWith("ii"))
} catch {
return false
}
}

View File

@ -0,0 +1,14 @@
import { defaultExecOptions, execRootSync } from "admina"
import { aptTimeout, getApt, getEnv } from "./install.js"
/**
* Update the apt repositories
* @param apt The apt command to use (optional)
*/
export function updateRepos(apt: string = getApt()) {
execRootSync(
apt,
apt !== "nala" ? ["update", "-y", "-o", aptTimeout] : ["update", "-o", aptTimeout],
{ ...defaultExecOptions, env: getEnv(apt) },
)
}

View File

@ -33,7 +33,7 @@ Replaces a tilde with the user's home directory
**returns:** string
```tsx
UntildifyUser('~/foo'); // /home/user/foo
UntildifyUser("~/foo") // /home/user/foo
```
<!-- INSERT GENERATED DOCS END -->

View File

@ -7,7 +7,7 @@ import { pathExists } from "path-exists"
import { addExeExt, join } from "patha"
import semverCoerce from "semver/functions/coerce"
import semverMajor from "semver/functions/major"
import { installAptPack, updateAptAlternatives } from "setup-apt"
import { addUpdateAlternativesToRc, installAptPack } from "setup-apt"
import { rcOptions } from "../cli-options.js"
import { setupMacOSSDK } from "../macos-sdk/macos-sdk.js"
import { hasDnf } from "../utils/env/hasDnf.js"
@ -231,10 +231,10 @@ async function activateGcc(version: string, binDir: string, priority: number = 4
if (isUbuntu()) {
promises.push(
updateAptAlternatives("cc", `${binDir}/gcc-${majorVersion}`, rcOptions, priority),
updateAptAlternatives("cxx", `${binDir}/g++-${majorVersion}`, rcOptions, priority),
updateAptAlternatives("gcc", `${binDir}/gcc-${majorVersion}`, rcOptions, priority),
updateAptAlternatives("g++", `${binDir}/g++-${majorVersion}`, rcOptions, priority),
addUpdateAlternativesToRc("cc", `${binDir}/gcc-${majorVersion}`, rcOptions, priority),
addUpdateAlternativesToRc("cxx", `${binDir}/g++-${majorVersion}`, rcOptions, priority),
addUpdateAlternativesToRc("gcc", `${binDir}/gcc-${majorVersion}`, rcOptions, priority),
addUpdateAlternativesToRc("g++", `${binDir}/g++-${majorVersion}`, rcOptions, priority),
)
}
} else {
@ -245,10 +245,10 @@ async function activateGcc(version: string, binDir: string, priority: number = 4
if (isUbuntu()) {
promises.push(
updateAptAlternatives("cc", `${binDir}/gcc-${version}`, rcOptions, priority),
updateAptAlternatives("cxx", `${binDir}/g++-${version}`, rcOptions, priority),
updateAptAlternatives("gcc", `${binDir}/gcc-${version}`, rcOptions, priority),
updateAptAlternatives("g++", `${binDir}/g++-${version}`, rcOptions, priority),
addUpdateAlternativesToRc("cc", `${binDir}/gcc-${version}`, rcOptions, priority),
addUpdateAlternativesToRc("cxx", `${binDir}/g++-${version}`, rcOptions, priority),
addUpdateAlternativesToRc("gcc", `${binDir}/gcc-${version}`, rcOptions, priority),
addUpdateAlternativesToRc("g++", `${binDir}/g++-${version}`, rcOptions, priority),
)
}
}

View File

@ -5,7 +5,7 @@ import memoize from "micro-memoize"
import { addEnv } from "os-env"
import { pathExists } from "path-exists"
import { addExeExt, join } from "patha"
import { installAptPack, updateAptAlternatives } from "setup-apt"
import { addUpdateAlternativesToRc, installAptPack } from "setup-apt"
import { rcOptions } from "../cli-options.js"
import { setupGcc } from "../gcc/gcc.js"
import { setupMacOSSDK } from "../macos-sdk/macos-sdk.js"
@ -131,13 +131,13 @@ export async function activateLLVM(directory: string) {
if (isUbuntu()) {
const priority = 60
actPromises.push(
updateAptAlternatives("cc", `${directory}/bin/clang`, rcOptions, priority),
updateAptAlternatives("cxx", `${directory}/bin/clang++`, rcOptions, priority),
updateAptAlternatives("clang", `${directory}/bin/clang`, rcOptions),
updateAptAlternatives("clang++", `${directory}/bin/clang++`, rcOptions),
updateAptAlternatives("lld", `${directory}/bin/lld`, rcOptions),
updateAptAlternatives("ld.lld", `${directory}/bin/ld.lld`, rcOptions),
updateAptAlternatives("llvm-ar", `${directory}/bin/llvm-ar`, rcOptions),
addUpdateAlternativesToRc("cc", `${directory}/bin/clang`, rcOptions, priority),
addUpdateAlternativesToRc("cxx", `${directory}/bin/clang++`, rcOptions, priority),
addUpdateAlternativesToRc("clang", `${directory}/bin/clang`, rcOptions),
addUpdateAlternativesToRc("clang++", `${directory}/bin/clang++`, rcOptions),
addUpdateAlternativesToRc("lld", `${directory}/bin/lld`, rcOptions),
addUpdateAlternativesToRc("ld.lld", `${directory}/bin/ld.lld`, rcOptions),
addUpdateAlternativesToRc("llvm-ar", `${directory}/bin/llvm-ar`, rcOptions),
)
}