fix: handle libc++ conflicting packages for LLVM

This commit is contained in:
Amin Yahyaabadi 2023-07-17 22:26:20 -07:00
parent eeed1d382d
commit 5f5040c0c8
10 changed files with 159 additions and 106 deletions

View File

@ -12,10 +12,6 @@ ignorePaths:
- .vscode/extensions.json - .vscode/extensions.json
words: words:
- aarch - aarch
- clangd
- Trofimovich
- cobertura
- whatwg
- aminya - aminya
- applellvm - applellvm
- bazel - bazel
@ -24,7 +20,9 @@ words:
- caxa - caxa
- ccache - ccache
- choco - choco
- clangd
- cmake - cmake
- cobertura
- copr - copr
- CPATH - CPATH
- Cppcheck - Cppcheck
@ -50,6 +48,7 @@ words:
- LDFLAGS - LDFLAGS
- lefticus - lefticus
- libbinutils - libbinutils
- libc
- libdw - libdw
- libstdc - libstdc
- libtinfo - libtinfo
@ -77,6 +76,7 @@ words:
- setx - setx
- Syuu - Syuu
- terserrc - terserrc
- Trofimovich
- tsbuildinfo - tsbuildinfo
- ucrt - ucrt
- untildify - untildify
@ -87,6 +87,7 @@ words:
- visualc - visualc
- visualcpp - visualcpp
- vsversion - vsversion
- whatwg
- xcrun - xcrun
- Yahyaabadi - Yahyaabadi
ignoreWords: [] ignoreWords: []

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,22 +1,19 @@
import { execRoot } from "admina"
import { GITHUB_ACTIONS } from "ci-info" import { GITHUB_ACTIONS } from "ci-info"
import { info, warning } from "ci-log" import { info, warning } from "ci-log"
import { execa } from "execa"
import { promises } from "fs"
const { readFile, writeFile, chmod } = promises
import memoize from "micro-memoize" import memoize from "micro-memoize"
import { delimiter } from "path" import { delimiter } from "path"
import { pathExists } from "path-exists" import { pathExists } from "path-exists"
import { addExeExt, join } from "patha" import { addExeExt, join } from "patha"
import { setupGcc } from "../gcc/gcc" import { setupGcc } from "../gcc/gcc"
import { setupMacOSSDK } from "../macos-sdk/macos-sdk" import { setupMacOSSDK } from "../macos-sdk/macos-sdk"
import { addEnv, addPath } from "../utils/env/addEnv" import { addEnv } from "../utils/env/addEnv"
import { isUbuntu } from "../utils/env/isUbuntu" import { isUbuntu } from "../utils/env/isUbuntu"
import { ubuntuVersion } from "../utils/env/ubuntu_version" import { ubuntuVersion } from "../utils/env/ubuntu_version"
import { hasNala, setupAptPack, updateAptAlternatives } from "../utils/setup/setupAptPack" import { setupAptPack, updateAptAlternatives } from "../utils/setup/setupAptPack"
import { InstallationInfo, setupBin } from "../utils/setup/setupBin" import { InstallationInfo, setupBin } from "../utils/setup/setupBin"
import { semverCoerceIfInvalid } from "../utils/setup/version" import { semverCoerceIfInvalid } from "../utils/setup/version"
import { getVersion } from "../versions/versions" import { getVersion } from "../versions/versions"
import { setupLLVMApt } from "./llvm_installer"
import { getLLVMPackageInfo } from "./llvm_url" 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> {
@ -55,50 +52,6 @@ async function setupLLVMOnly(version: string, setupDir: string, arch: string) {
return installationInfo return installationInfo
} }
async function setupLLVMApt(majorVersion: number): Promise<InstallationInfo> {
// TODO for older versions, this also includes the minor version
const installationFolder = `/usr/lib/llvm-${majorVersion}`
await setupAptPack([{ name: "curl" }])
await execa("curl", ["-LJO", "https://apt.llvm.org/llvm.sh"], { cwd: "/tmp" })
const neededPackages = await patchAptLLVMScript("/tmp/llvm.sh", "/tmp/llvm-setup-cpp.sh")
await setupAptPack(neededPackages)
await chmod("/tmp/llvm-setup-cpp.sh", "755")
await execRoot("bash", ["/tmp/llvm-setup-cpp.sh", `${majorVersion}`, "all"], {
stdio: "inherit",
shell: true,
})
await addPath(`${installationFolder}/bin`)
return {
installDir: `${installationFolder}`,
binDir: `${installationFolder}/bin`,
bin: `${installationFolder}/bin/clang++`,
}
}
async function patchAptLLVMScript(path: string, target_path: string) {
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}"'
)
// fix conflicts between libclang-rt and libclang
.replace(/apt-get install -y/g, 'apt-get install -o Dpkg::Options::="--force-overwrite" -y --fix-broken')
// use nala if it is available
if (hasNala()) {
script = script.replace(/apt-get/g, "nala")
}
await writeFile(target_path, script)
// the packages needed by the script
return [{ name: "lsb-release" }, { name: "wget" }, { name: "software-properties-common" }, { name: "gnupg" }]
}
async function llvmBinaryDeps_raw(majorVersion: number) { async function llvmBinaryDeps_raw(majorVersion: number) {
if (isUbuntu()) { if (isUbuntu()) {
if (majorVersion <= 10) { if (majorVersion <= 10) {

View File

@ -0,0 +1,81 @@
import { execRoot } from "admina"
import { execa } from "execa"
import { addPath } from "../utils/env/addEnv"
import { hasNala, isPackageInstalled, setupAptPack } from "../utils/setup/setupAptPack"
import { InstallationInfo } from "../utils/setup/setupBin"
import { promises } from "fs"
import { info } from "console"
const { readFile, writeFile, chmod } = promises
export async function setupLLVMApt(majorVersion: number): Promise<InstallationInfo> {
// TODO for older versions, this also includes the minor version
const installationFolder = `/usr/lib/llvm-${majorVersion}`
await setupAptPack([{ name: "curl" }])
await execa("curl", ["-LJO", "https://apt.llvm.org/llvm.sh"], { cwd: "/tmp" })
const neededPackages = await patchAptLLVMScript("/tmp/llvm.sh", "/tmp/llvm-setup-cpp.sh")
await setupAptPack(neededPackages)
await chmod("/tmp/llvm-setup-cpp.sh", "755")
await execRoot("bash", ["/tmp/llvm-setup-cpp.sh", `${majorVersion}`, "all"], {
stdio: "inherit",
shell: true,
})
await addPath(`${installationFolder}/bin`)
return {
installDir: `${installationFolder}`,
binDir: `${installationFolder}/bin`,
bin: `${installationFolder}/bin/clang++`,
}
}
async function patchAptLLVMScript(path: string, target_path: string) {
let script = await readFile(path, "utf-8")
script = nonInteractiveScript(script)
script = await removeConflictingPAckages(script)
script = useNalaScript(script)
await writeFile(target_path, script)
// the packages needed by the script
return [{ name: "lsb-release" }, { name: "wget" }, { name: "software-properties-common" }, { name: "gnupg" }]
}
function nonInteractiveScript(givenScript: string) {
// make the scirpt non-interactive and fix broken packages
return givenScript.replace(
/add-apt-repository "\${REPO_NAME}"/g,
// eslint-disable-next-line no-template-curly-in-string
'add-apt-repository -y "${REPO_NAME}"'
)
}
async function removeConflictingPAckages(givenScript: string) {
// fix conflicts between libclang-rt and libclang
let script = givenScript.replace(
/apt-get install -y/g,
'apt-get install -o Dpkg::Options::="--force-overwrite" -y --fix-broken'
)
// check if these are installed and if so, remove them from the script as they conflict
const conflictingPackages = ["libc++-$LLVM_VERSION-dev", "libc++abi-$LLVM_VERSION-dev", "libunwind-$LLVM_VERSION-dev"]
await Promise.all(
conflictingPackages.map(async (pack) => {
const installingPack = pack.replace("$LLVM_VERSION", "*")
if (await isPackageInstalled(installingPack)) {
info(`Removing conflicting package ${installingPack}`)
script = script.replace(pack, "")
}
})
)
return script
}
function useNalaScript(script: string) {
// use nala if it is available
if (hasNala()) {
return script.replace(/apt-get/g, "nala")
}
return script
}

View File

@ -20,6 +20,12 @@ export type AptPackage = {
repositories?: 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 */ /** A function that installs a package using apt */
export async function setupAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> { export async function setupAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
const apt: string = getApt() const apt: string = getApt()
@ -57,7 +63,7 @@ export async function setupAptPack(packages: AptPackage[], update = false): Prom
} catch (err) { } catch (err) {
if ("stderr" in (err as ExecaError)) { if ("stderr" in (err as ExecaError)) {
const stderr = (err as ExecaError).stderr const stderr = (err as ExecaError).stderr
if (stderr.includes("E: Could not get lock") || stderr.includes("dpkg: error processing archive")) { if (retryErrors.some((error) => stderr.includes(error))) {
warning(`Failed to install packages ${aptArgs}. Retrying...`) warning(`Failed to install packages ${aptArgs}. Retrying...`)
execRootSync(apt, ["install", "--fix-broken", "-y", ...aptArgs]) execRootSync(apt, ["install", "--fix-broken", "-y", ...aptArgs])
} }
@ -224,3 +230,15 @@ export async function updateAptAlternatives(name: string, path: string) {
) )
} }
} }
export async function isPackageInstalled(regexp: string) {
try {
// check if a package matching the regexp is installed
const { stdout } = await execa("dpkg", ["-l", regexp])
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
}
}