refactor: refactor llvm install into modules

This commit is contained in:
Amin Yahyaabadi 2025-03-09 01:08:52 -08:00
parent 57e4acf400
commit 935a8bc14f
8 changed files with 162 additions and 109 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,32 +1,24 @@
import { tmpdir } from "os"
import path, { delimiter, join } from "path"
import { fileURLToPath } from "url"
import { execRootSync } from "admina"
import ciInfo from "ci-info"
const { GITHUB_ACTIONS } = ciInfo
import { info, warning } from "ci-log"
import { addEnv } from "envosman"
import memoize from "memoizee"
import { DownloaderHelper } from "node-downloader-helper"
import { pathExists } from "path-exists"
import { addExeExt } from "patha"
import { addUpdateAlternativesToRc, installAptPack } from "setup-apt"
import { addUpdateAlternativesToRc } from "setup-apt"
import { rcOptions } from "../cli-options.js"
import { setupGcc } from "../gcc/gcc.js"
import { setupMacOSSDK } from "../macos-sdk/macos-sdk.js"
import { arm64, x86_64 } from "../utils/env/arch.js"
import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.js"
import { isUbuntu } from "../utils/env/isUbuntu.js"
import { ubuntuVersion } from "../utils/env/ubuntu_version.js"
import { type InstallationInfo, setupBin } from "../utils/setup/setupBin.js"
import { setupDnfPack } from "../utils/setup/setupDnfPack.js"
import { setupPacmanPack } from "../utils/setup/setupPacmanPack.js"
import { semverCoerceIfInvalid } from "../utils/setup/version.js"
import type { InstallationInfo } from "../utils/setup/setupBin.js"
import { quoteIfHasSpace } from "../utils/std/index.js"
import { getVersion } from "../versions/versions.js"
import { LLVMPackages, setupLLVMApt } from "./llvm_installer.js"
import { getLLVMPackageInfo } from "./llvm_url.js"
import { LLVMPackages, trySetupLLVMApt } from "./llvm_apt_installer.js"
import { setupLLVMBin } from "./llvm_bin.js"
import { majorLLVMVersion } from "./utils.js"
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
@ -44,110 +36,34 @@ async function setupLLVMWithoutActivation_(version: string, setupDir: string, ar
])
// install LLVM dependencies
await setupLLVMDeps(arch)
await setupGccForLLVM(arch)
return installationInfo
}
const setupLLVMWithoutActivation = memoize(setupLLVMWithoutActivation_, { promise: true })
/**
* Setup clang-format
*
* This uses the LLVM installer on Ubuntu, and the LLVM binaries on other platforms
*/
export function setupClangFormat(version: string, setupDir: string, arch: string) {
return setupLLVMOnly(version, setupDir, arch, LLVMPackages.ClangFormat)
}
/** Setup llvm tools (clang tidy, etc.) without activating llvm and using it as the compiler */
export function setupClangTools(version: string, setupDir: string, arch: string) {
return setupLLVMOnly(version, setupDir, arch)
}
async function setupLLVMOnly(
version: string,
setupDir: string,
arch: string,
packages: LLVMPackages = LLVMPackages.All,
) {
const majorVersion = majorLLVMVersion(version)
if (isUbuntu()) {
try {
return await setupLLVMApt(majorVersion, packages)
} catch (err) {
info(`Failed to install llvm via system package manager ${err}. Trying to remove the repository`)
try {
execRootSync(join(dirname, "llvm_repo_remove.bash"), [`${majorVersion}`])
} catch (removeErr) {
info(`Failed to remove llvm repository ${removeErr}`)
}
}
const aptInstallInfo = await trySetupLLVMApt(version, packages)
if (aptInstallInfo !== undefined) {
return aptInstallInfo
}
const installationInfo = await setupBin("llvm", version, getLLVMPackageInfo, setupDir, arch)
await llvmBinaryDeps(majorVersion)
return installationInfo
return setupLLVMBin(version, setupDir, arch)
}
function majorLLVMVersion(version: string) {
const coeredVersion = semverCoerceIfInvalid(version)
return Number.parseInt(coeredVersion.split(".")[0], 10)
}
async function llvmBinaryDeps_(_majorVersion: number) {
if (isUbuntu()) {
for (const dep of ["libtinfo5", "libtinfo6"]) {
/* eslint-disable no-await-in-loop */
try {
await installAptPack([{ name: dep }])
} catch (_err) {
try {
if (dep === "libtinfo5") {
// Manually install libtinfo5 if the package is not available
info(`Failed to install ${dep}\nManually installing the package`)
const arch = x86_64.includes(process.arch)
? "amd64"
: arm64.includes(process.arch)
? "arm64"
: process.arch
const fileName = `libtinfo5_6.3-2ubuntu0.1_${arch}.deb`
const url = `http://launchpadlibrarian.net/666971015/${fileName}`
const dl = new DownloaderHelper(url, tmpdir(), { fileName })
dl.on("error", async (dlErr) => {
info(`Failed to download ${url}: ${dlErr}`)
await dl.stop()
})
await dl.start()
// Install the downloaded package via dpkg
execRootSync("dpkg", ["-i", join(tmpdir(), fileName)])
}
} catch (_errFallback) {
info(`Failed to install ${dep}. Ignoring`)
}
}
/* eslint-enable no-await-in-loop */
}
} else if (isArch()) {
// https://aur.archlinux.org/packages/ncurses5-compat-libs
await setupPacmanPack("ncurses5-compat-libs", undefined, "yay")
} else if (hasDnf()) {
// https://packages.fedoraproject.org/pkgs/ncurses/ncurses-compat-libs/index.html
await setupDnfPack([
{ name: "ncurses-compat-libs" },
])
}
}
const llvmBinaryDeps = memoize(llvmBinaryDeps_, { promise: true })
async function setupLLVMDeps_(arch: string) {
async function setupGccForLLVM_(arch: string) {
if (process.platform === "linux") {
// using llvm requires ld, an up to date libstdc++, etc. So, install gcc first,
// but with a lower priority than the one used by activateLLVM()
await setupGcc(getVersion("gcc", undefined, await ubuntuVersion()), "", arch, 40)
}
}
const setupLLVMDeps = memoize(setupLLVMDeps_, { promise: true })
const setupGccForLLVM = memoize(setupGccForLLVM_, { promise: true })
export async function activateLLVM(directory: string, version: string) {
const ld = process.env.LD_LIBRARY_PATH ?? ""
@ -201,11 +117,26 @@ export async function activateLLVM(directory: string, version: string) {
}
async function addLLVMLoggingMatcher() {
if (GITHUB_ACTIONS) {
const matcherPath = join(dirname, "llvm_matcher.json")
if (!(await pathExists(matcherPath))) {
return warning("the llvm_matcher.json file does not exist in the same folder as setup-cpp.js")
}
info(`::add-matcher::${matcherPath}`)
if (!GITHUB_ACTIONS) {
return
}
const matcherPath = join(dirname, "llvm_matcher.json")
if (!(await pathExists(matcherPath))) {
return warning("the llvm_matcher.json file does not exist in the same folder as setup-cpp.js")
}
info(`::add-matcher::${matcherPath}`)
}
/**
* Setup clang-format
*
* This uses the LLVM installer on Ubuntu, and the LLVM binaries on other platforms
*/
export function setupClangFormat(version: string, setupDir: string, arch: string) {
return setupLLVMOnly(version, setupDir, arch, LLVMPackages.ClangFormat)
}
/** Setup llvm tools (clang tidy, etc.) without activating llvm and using it as the compiler */
export function setupClangTools(version: string, setupDir: string, arch: string) {
return setupLLVMOnly(version, setupDir, arch)
}

View File

@ -1,14 +1,18 @@
import { info } from "console"
import { tmpdir } from "os"
import { join } from "path"
import { execRoot } from "admina"
import path, { join } from "path"
import { fileURLToPath } from "url"
import { execRoot, execRootSync } from "admina"
import { addPath } from "envosman"
import { chmod, readFile, writeFile } from "fs/promises"
import { DownloaderHelper } from "node-downloader-helper"
import { aptTimeout, hasNala, installAddAptRepo, installAptPack, isAptPackRegexInstalled } from "setup-apt"
import { rcOptions } from "../cli-options.js"
import { DEFAULT_TIMEOUT } from "../installTool.js"
import { isUbuntu } from "../utils/env/isUbuntu.js"
import type { InstallationInfo } from "../utils/setup/setupBin.js"
import { majorLLVMVersion } from "./utils.js"
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
export enum LLVMPackages {
All = 0,
@ -16,10 +20,52 @@ export enum LLVMPackages {
Core = 2,
}
/**
* Try to setup LLVM via the apt package manager
*
* @param {string} version - The version of LLVM to install
* @param {LLVMPackages} packages - The packages to install
*
* @returns {InstallationInfo} The installation info if the installation was successful
* @returns {undefined} If the installation fails, it will try to remove the repository and will return undefined
*/
export async function trySetupLLVMApt(
version: string,
packages: LLVMPackages = LLVMPackages.All,
): Promise<InstallationInfo | undefined> {
if (!isUbuntu()) {
return undefined
}
try {
return await setupLLVMApt(version, packages)
} catch (err) {
info(`Failed to install llvm via system package manager ${err}. Trying to remove the repository`)
try {
execRootSync(join(dirname, "llvm_repo_remove.bash"), [`${majorLLVMVersion(version)}`])
} catch (removeErr) {
info(`Failed to remove llvm repository ${removeErr}`)
}
}
return undefined
}
/**
* Setup LLVM via the apt package manager
*
* @note assumes this is running on an Ubuntu/Debian system
*
* @param {string} version - The version of LLVM to install
* @param {LLVMPackages} packages - The packages to install
*
* @returns {InstallationInfo} The installation info if the installation was successful
*/
export async function setupLLVMApt(
majorVersion: number,
version: string,
packages: LLVMPackages = LLVMPackages.All,
): Promise<InstallationInfo> {
const majorVersion = majorLLVMVersion(version)
// TODO for older versions, this also includes the minor version
const installationFolder = `/usr/lib/llvm-${majorVersion}`

68
src/llvm/llvm_bin.ts Normal file
View File

@ -0,0 +1,68 @@
import { tmpdir } from "os"
import { join } from "path"
import { execRootSync } from "admina"
import { info } from "ci-log"
import memoize from "memoizee"
import { DownloaderHelper } from "node-downloader-helper"
import { installAptPack } from "setup-apt"
import { arm64, x86_64 } from "../utils/env/arch.js"
import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.js"
import { isUbuntu } from "../utils/env/isUbuntu.js"
import { setupBin } from "../utils/setup/setupBin.js"
import { setupDnfPack } from "../utils/setup/setupDnfPack.js"
import { setupPacmanPack } from "../utils/setup/setupPacmanPack.js"
import { getLLVMPackageInfo } from "./llvm_url.js"
import { majorLLVMVersion } from "./utils.js"
export async function setupLLVMBin(version: string, setupDir: string, arch: string) {
const installInfo = await setupBin("llvm", version, getLLVMPackageInfo, setupDir, arch)
await llvmBinaryDeps(majorLLVMVersion(version))
return installInfo
}
async function llvmBinaryDeps_(_majorVersion: number) {
if (isUbuntu()) {
for (const dep of ["libtinfo5", "libtinfo6"]) {
/* eslint-disable no-await-in-loop */
try {
await installAptPack([{ name: dep }])
} catch (_err) {
try {
if (dep === "libtinfo5") {
// Manually install libtinfo5 if the package is not available
info(`Failed to install ${dep}\nManually installing the package`)
const arch = x86_64.includes(process.arch)
? "amd64"
: arm64.includes(process.arch)
? "arm64"
: process.arch
const fileName = `libtinfo5_6.3-2ubuntu0.1_${arch}.deb`
const url = `http://launchpadlibrarian.net/666971015/${fileName}`
const dl = new DownloaderHelper(url, tmpdir(), { fileName })
dl.on("error", async (dlErr) => {
info(`Failed to download ${url}: ${dlErr}`)
await dl.stop()
})
await dl.start()
// Install the downloaded package via dpkg
execRootSync("dpkg", ["-i", join(tmpdir(), fileName)])
}
} catch (_errFallback) {
info(`Failed to install ${dep}. Ignoring`)
}
}
/* eslint-enable no-await-in-loop */
}
} else if (isArch()) {
// https://aur.archlinux.org/packages/ncurses5-compat-libs
await setupPacmanPack("ncurses5-compat-libs", undefined, "yay")
} else if (hasDnf()) {
// https://packages.fedoraproject.org/pkgs/ncurses/ncurses-compat-libs/index.html
await setupDnfPack([
{ name: "ncurses-compat-libs" },
])
}
}
const llvmBinaryDeps = memoize(llvmBinaryDeps_, { promise: true })

8
src/llvm/utils.ts Normal file
View File

@ -0,0 +1,8 @@
import memoize from "memoizee"
import { semverCoerceIfInvalid } from "../utils/setup/version.ts"
function majorLLVMVersion_(version: string) {
const coeredVersion = semverCoerceIfInvalid(version)
return Number.parseInt(coeredVersion.split(".")[0], 10)
}
export const majorLLVMVersion = memoize(majorLLVMVersion_)