fix: refactor LLVM installation

This commit is contained in:
Amin Yahyaabadi 2023-07-16 02:48:34 -07:00
parent 70a091c663
commit dd9ff769c8
7 changed files with 121 additions and 128 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

@ -1,119 +1,123 @@
import { join, addExeExt } from "patha" import { execRoot } from "admina"
import { GITHUB_ACTIONS } from "ci-info"
import { info, warning } from "ci-log"
import { ExecaReturnValue, execa } from "execa"
import { promises } from "fs"
const { readFile, writeFile, chmod } = promises
import memoize from "micro-memoize"
import { delimiter } from "path" import { delimiter } from "path"
import { InstallationInfo, setupBin } from "../utils/setup/setupBin" import { pathExists } from "path-exists"
import { semverCoerceIfInvalid } from "../utils/setup/version" import { addExeExt, join } from "patha"
import { setupGcc } from "../gcc/gcc"
import { setupMacOSSDK } from "../macos-sdk/macos-sdk" import { setupMacOSSDK } from "../macos-sdk/macos-sdk"
import { addEnv } from "../utils/env/addEnv" import { addEnv } from "../utils/env/addEnv"
import { hasNala, setupAptPack, updateAptAlternatives } from "../utils/setup/setupAptPack"
import { info, warning } from "ci-log"
import { GITHUB_ACTIONS } from "ci-info"
import { setupGcc } from "../gcc/gcc"
import { getVersion } from "../versions/versions"
import { isUbuntu } from "../utils/env/isUbuntu" import { isUbuntu } from "../utils/env/isUbuntu"
import { getLLVMPackageInfo } from "./llvm_url"
import { ubuntuVersion } from "../utils/env/ubuntu_version" import { ubuntuVersion } from "../utils/env/ubuntu_version"
import { pathExists } from "path-exists" import { hasNala, setupAptPack, updateAptAlternatives } from "../utils/setup/setupAptPack"
import { ExecaReturnValue, execa } from "execa" import { InstallationInfo, setupBin } from "../utils/setup/setupBin"
import { readFileSync, writeFileSync } from "fs" import { semverCoerceIfInvalid } from "../utils/setup/version"
import { execRootSync } from "admina" import { getVersion } from "../versions/versions"
import { getLLVMPackageInfo } from "./llvm_url"
export async function setupLLVM(version: string, setupDir: string, arch: string): Promise<InstallationInfo> { export async function setupLLVM(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
const installationInfo = await setupLLVMWithoutActivation(version, setupDir, arch) const installationInfo = await setupLLVMWithoutActivation(version, setupDir, arch)
await activateLLVM(installationInfo.installDir ?? setupDir, version) await activateLLVM(installationInfo.installDir ?? setupDir)
return installationInfo return installationInfo
} }
let installedDeps = false /** Setup llvm tools (clang tidy, clang format, etc) without activating llvm and using it as the compiler */
export const setupClangTools = setupLLVMWithoutActivation
async function setupLLVMWithoutActivation(version: string, setupDir: string, arch: string) {
// install LLVM and its dependencies in parallel
const [installationInfo, _1, _2] = await Promise.all([
setupLLVMOnly(version, setupDir, arch),
setupLLVMDeps(arch),
addLLVMLoggingMatcher(),
])
return installationInfo
}
async function setupLLVMOnly(version: string, setupDir: string, arch: string) { async function setupLLVMOnly(version: string, setupDir: string, arch: string) {
const coeredVersion = semverCoerceIfInvalid(version)
const majorVersion = parseInt(coeredVersion.split(".")[0], 10)
try { try {
if (isUbuntu()) { if (isUbuntu()) {
const coeredVersion = semverCoerceIfInvalid(version) return setupLLVMApt(majorVersion)
const majorVersion = parseInt(coeredVersion.split(".")[0], 10)
const installationFolder = `/usr/lib/llvm-${majorVersion}` // TODO for older versions, this also includes the minor version
await setupAptPack([{ name: "curl" }])
await execa("curl", ["-LJO", "https://apt.llvm.org/llvm.sh"], { cwd: "/tmp" })
let script = readFileSync("/tmp/llvm.sh", "utf-8")
// make the scirpt non-interactive and fix broken packages
script = script
.replace(
/add-apt-repository "\${REPO_NAME}"/g,
// eslint-disable-next-line no-template-curly-in-string
'add-apt-repository -y "${REPO_NAME}"'
)
.replace(/apt-get install -y/g, "apt-get install -y --fix-broken")
// use nala if it is available
if (hasNala()) {
script = script.replace(/apt-get/g, "nala")
}
writeFileSync("/tmp/llvm-setup-cpp.sh", script)
execRootSync("chmod", ["+x", "/tmp/llvm-setup-cpp.sh"])
execRootSync("bash", ["/tmp/setup-cpp-llvm.sh"], {
stdio: "inherit",
shell: true,
})
return {
installDir: `/usr/lib/${installationFolder}`,
binDir: `/usr/bin`,
version,
} as InstallationInfo
} }
} catch (err) { } catch (err) {
info(`Failed to install llvm via system package manager ${err}`) info(`Failed to install llvm via system package manager ${err}`)
} }
return setupBin("llvm", version, getLLVMPackageInfo, setupDir, arch) const installationInfo = await setupBin("llvm", version, getLLVMPackageInfo, setupDir, arch)
} await llvmBinaryDeps(majorVersion)
async function setupLLVMWithoutActivation(version: string, setupDir: string, arch: string) {
const installationInfoPromise = setupLLVMOnly(version, setupDir, arch)
let depsPromise: Promise<void> = Promise.resolve()
if (!installedDeps) {
depsPromise = setupLLVMDeps(arch, version)
// eslint-disable-next-line require-atomic-updates
installedDeps = true
}
// install LLVM and its dependencies in parallel
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [installationInfo, _] = await Promise.all([installationInfoPromise, depsPromise])
return installationInfo return installationInfo
} }
async function setupLLVMDeps(arch: string, version: string) { async function setupLLVMApt(majorVersion: number): Promise<InstallationInfo> {
if (process.platform === "linux") { // TODO for older versions, this also includes the minor version
// install llvm build dependencies const installationFolder = `/usr/lib/llvm-${majorVersion}`
await setupGcc(getVersion("gcc", undefined, await ubuntuVersion()), "", arch) // using llvm requires ld, an up to date libstdc++, etc. So, install gcc first
if (isUbuntu()) { await setupAptPack([{ name: "curl" }])
const majorVersion = parseInt(version.split(".")[0], 10) await execa("curl", ["-LJO", "https://apt.llvm.org/llvm.sh"], { cwd: "/tmp" })
if (majorVersion <= 10) { await patchAptLLVMScript("/tmp/llvm.sh", "/tmp/llvm-setup-cpp.sh")
await setupAptPack([{ name: "libtinfo5" }]) await chmod("/tmp/llvm-setup-cpp.sh", "755")
} else { await execRoot("bash", ["/tmp/setup-cpp-llvm.sh"], {
await setupAptPack([{ name: "libtinfo-dev" }]) stdio: "inherit",
} shell: true,
} })
// TODO: install libtinfo on other distros
// await setupPacmanPack("ncurses") return {
installDir: `${installationFolder}`,
binDir: `${installationFolder}/bin`,
bin: `${installationFolder}/bin/clang++`,
} }
} }
export async function activateLLVM(directory: string, versionGiven: string) { async function patchAptLLVMScript(path: string, target_path: string) {
const _version = semverCoerceIfInvalid(versionGiven) let script = await readFile(path, "utf-8")
// make the scirpt non-interactive and fix broken packages
script = script
.replace(
/add-apt-repository "\${REPO_NAME}"/g,
// eslint-disable-next-line no-template-curly-in-string
'add-apt-repository -y "${REPO_NAME}"'
)
.replace(/apt-get install -y/g, "apt-get install -y --fix-broken")
// use nala if it is available
if (hasNala()) {
script = script.replace(/apt-get/g, "nala")
}
await writeFile(target_path, script)
}
async function llvmBinaryDeps_raw(majorVersion: number) {
if (isUbuntu()) {
if (majorVersion <= 10) {
await setupAptPack([{ name: "libtinfo5" }])
} else {
await setupAptPack([{ name: "libtinfo-dev" }])
}
}
}
const llvmBinaryDeps = memoize(llvmBinaryDeps_raw, { isPromise: true })
async function setupLLVMDeps_raw(arch: string) {
if (process.platform === "linux") {
// using llvm requires ld, an up to date libstdc++, etc. So, install gcc first
await setupGcc(getVersion("gcc", undefined, await ubuntuVersion()), "", arch)
}
}
const setupLLVMDeps = memoize(setupLLVMDeps_raw, { isPromise: true })
export async function activateLLVM(directory: string) {
const lib = join(directory, "lib") const lib = join(directory, "lib")
const ld = process.env.LD_LIBRARY_PATH ?? "" const ld = process.env.LD_LIBRARY_PATH ?? ""
const dyld = process.env.DYLD_LIBRARY_PATH ?? "" const dyld = process.env.DYLD_LIBRARY_PATH ?? ""
const promises: Promise<void | ExecaReturnValue<string>>[] = [ const actPromises: Promise<void | ExecaReturnValue<string>>[] = [
// the output of this action // the output of this action
addEnv("LLVM_PATH", directory), addEnv("LLVM_PATH", directory),
@ -138,7 +142,6 @@ export async function activateLLVM(directory: string, versionGiven: string) {
// TODO Causes issues with clangd // TODO Causes issues with clangd
// TODO Windows builds fail with llvm's CPATH // TODO Windows builds fail with llvm's CPATH
// if (process.platform !== "win32") { // if (process.platform !== "win32") {
// const llvmMajor = semverMajor(version)
// if (await pathExists(`${directory}/lib/clang/${version}/include`)) { // if (await pathExists(`${directory}/lib/clang/${version}/include`)) {
// promises.push(addEnv("CPATH", `${directory}/lib/clang/${version}/include`)) // promises.push(addEnv("CPATH", `${directory}/lib/clang/${version}/include`))
// } else if (await pathExists(`${directory}/lib/clang/${llvmMajor}/include`)) { // } else if (await pathExists(`${directory}/lib/clang/${llvmMajor}/include`)) {
@ -147,7 +150,7 @@ export async function activateLLVM(directory: string, versionGiven: string) {
// } // }
if (isUbuntu()) { if (isUbuntu()) {
promises.push( actPromises.push(
updateAptAlternatives("cc", `${directory}/bin/clang`), updateAptAlternatives("cc", `${directory}/bin/clang`),
updateAptAlternatives("cxx", `${directory}/bin/clang++`), updateAptAlternatives("cxx", `${directory}/bin/clang++`),
updateAptAlternatives("clang", `${directory}/bin/clang`), updateAptAlternatives("clang", `${directory}/bin/clang`),
@ -158,25 +161,15 @@ export async function activateLLVM(directory: string, versionGiven: string) {
) )
} }
if (GITHUB_ACTIONS) { await Promise.all(actPromises)
await addLLVMLoggingMatcher()
}
await Promise.all(promises)
}
/** Setup llvm tools (clang tidy, clang format, etc) without activating llvm and using it as the compiler */
export async function setupClangTools(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
if (GITHUB_ACTIONS) {
await addLLVMLoggingMatcher()
}
return setupLLVMWithoutActivation(version, setupDir, arch)
} }
async function addLLVMLoggingMatcher() { async function addLLVMLoggingMatcher() {
const matcherPath = join(__dirname, "llvm_matcher.json") if (GITHUB_ACTIONS) {
if (!(await pathExists(matcherPath))) { const matcherPath = join(__dirname, "llvm_matcher.json")
return warning("the llvm_matcher.json file does not exist in the same folder as setup-cpp.js") 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}`)
} }
info(`::add-matcher::${matcherPath}`)
} }