Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow specifying the vcpkg version + fix: fix check for apt package installs + feat: allow parallel apt-get calls #257

Merged
merged 6 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ exe/
*.log
*.exe
.cache/

coverage
3 changes: 2 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"**/dist/**",
"dev/cpp_vcpkg_project/**/*",
"**/.venv/",
"**/.*cache/"
"**/.*cache/",
"**/coverage/"
],
"ignoreUnknown": true
},
Expand Down
55 changes: 28 additions & 27 deletions dist/actions/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/actions/setup-cpp.js.map

Large diffs are not rendered by default.

55 changes: 28 additions & 27 deletions dist/legacy/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/legacy/setup-cpp.js.map

Large diffs are not rendered by default.

55 changes: 28 additions & 27 deletions dist/modern/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/modern/setup-cpp.js.map

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/llvm/llvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ export async function setupLLVM(version: string, setupDir: string, arch: string)
}

async function setupLLVMWithoutActivation_raw(version: string, setupDir: string, arch: string) {
// install LLVM and its dependencies in parallel
const [installationInfo, _1, _2] = await Promise.all([
// install LLVM
const [installationInfo, _1] = await Promise.all([
setupLLVMOnly(version, setupDir, arch),
setupLLVMDeps(arch),
addLLVMLoggingMatcher(),
])

// install LLVM dependencies
await setupLLVMDeps(arch)

return installationInfo
}
const setupLLVMWithoutActivation = memoize(setupLLVMWithoutActivation_raw, { isPromise: true })
Expand Down
8 changes: 4 additions & 4 deletions src/llvm/llvm_installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { execa } from "execa"
import { chmod, readFile, writeFile } from "fs/promises"
import { DEFAULT_TIMEOUT } from "../installTool"
import { addPath } from "../utils/env/addEnv"
import { hasNala, isPackageRegexInstalled, setupAptPack } from "../utils/setup/setupAptPack"
import { aptTimeout, hasNala, isPackageRegexInstalled, setupAptPack } from "../utils/setup/setupAptPack"
import type { InstallationInfo } from "../utils/setup/setupBin"

export enum LLVMPackages {
Expand Down Expand Up @@ -70,16 +70,16 @@ function nonInteractiveScript(script: string) {
// make the scirpt non-interactive and fix broken packages
return script.replace(
/add-apt-repository "\${REPO_NAME}"/g,
// eslint-disable-next-line no-template-curly-in-string
"add-apt-repository -y \"${REPO_NAME}\"",
`add-apt-repository -y -n "\${REPO_NAME}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downloaded llvm.sh script contains add-apt-repository -y "${REPO_NAME}" so I don't think this replacement will be done.
The script also has an update afterwards which could be patch instead of adding the apt-get update.

See:
https://github.com/opencollab/llvm-jenkins.debian.net/blob/master/llvm.sh#L173-L174

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like they added the flag recently. The code needs to be updated. Thanks for catching this.
opencollab/llvm-jenkins.debian.net@33f9c64

apt-get update -o ${aptTimeout} -y`,
)
}

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",
`apt-get install -o Dpkg::Options::="--force-overwrite" -o ${aptTimeout} -y --fix-broken`,
)

// check if these are installed and if so, remove them from the script as they conflict
Expand Down
52 changes: 35 additions & 17 deletions src/utils/setup/setupAptPack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import type { InstallationInfo } from "./setupBin"
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
Expand Down Expand Up @@ -44,13 +47,9 @@ export async function setupAptPack(packages: AptPackage[], update = false): Prom
// Add the repos if needed
await addRepositories(apt, packages)

// Qualify the packages into full package name/version
let qualifiedPacks = await Promise.all(packages.map((pack) => getAptArg(pack.name, pack.version)))

// find the packages that are not installed
qualifiedPacks = await Promise.all(qualifiedPacks.filter(async (pack) => !(await isPackageInstalled(pack))))
const needToInstall = await filterAndQualifyAptPackages(packages)

if (qualifiedPacks.length === 0) {
if (needToInstall.length === 0) {
info("All packages are already installed")
return { binDir: "/usr/bin/" }
}
Expand All @@ -63,13 +62,13 @@ export async function setupAptPack(packages: AptPackage[], update = false): Prom

// Install
try {
execRootSync(apt, ["install", "--fix-broken", "-y", ...qualifiedPacks])
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall])
} catch (err) {
if ("stderr" in (err as ExecaError)) {
const stderr = (err as ExecaError).stderr
if (retryErrors.some((error) => stderr.includes(error))) {
warning(`Failed to install packages ${qualifiedPacks}. Retrying...`)
execRootSync(apt, ["install", "--fix-broken", "-y", ...qualifiedPacks])
warning(`Failed to install packages ${needToInstall}. Retrying...`)
execRootSync(apt, ["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall])
}
} else {
throw err
Expand All @@ -86,17 +85,32 @@ export enum AptPackageType {
None = 3,
}

/**
* Filter out the packages that are already installed and qualify the packages into a full package name/version
*/
async function filterAndQualifyAptPackages(packages: AptPackage[]) {
return (await Promise.all(packages.map(qualifiedNeededAptPackage)))
.filter((pack) => pack !== undefined)
}

async function qualifiedNeededAptPackage(pack: AptPackage) {
// Qualify the packages into full package name/version
const qualified = await getAptArg(pack.name, pack.version)
// filter out the packages that are already installed
return (await isPackageInstalled(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()
await installAddAptRepo(apt)
for (const repo of allRepositories) {
// eslint-disable-next-line no-await-in-loop
execRootSync("add-apt-repository", ["-y", repo])
execRootSync("add-apt-repository", ["-y", "--no-update", repo])
}
updateRepos(apt)
didUpdate = true
Expand Down Expand Up @@ -163,7 +177,7 @@ export function hasNala() {
return which.sync("nala", { nothrow: true }) !== null
}

function getApt() {
export function getApt() {
let apt: string
if (hasNala()) {
apt = "nala"
Expand All @@ -174,14 +188,14 @@ function getApt() {
}

function updateRepos(apt: string) {
execRootSync(apt, apt !== "nala" ? ["update", "-y"] : ["update"])
execRootSync(apt, apt !== "nala" ? ["update", "-y", "-o", aptTimeout] : ["update", "-o", aptTimeout])
}

async function installAddAptRepo() {
async function installAddAptRepo(apt: string) {
if (await isPackageInstalled("software-properties-common")) {
return
}
execRootSync("apt-get", ["install", "-y", "--fix-broken", "software-properties-common"])
execRootSync(apt, ["install", "-y", "--fix-broken", "-o", aptTimeout, "software-properties-common"])
}

/** Install gnupg and certificates (usually missing from docker containers) */
Expand All @@ -192,10 +206,14 @@ async function initApt(apt: string) {
didUpdate = true
}

const toInstall = ["ca-certificates", "gnupg", "apt-utils"].filter(async (pack) => !(await isPackageInstalled(pack)))
const toInstall = await filterAndQualifyAptPackages([
{ name: "ca-certificates" },
{ name: "gnupg" },
{ name: "apt-utils" },
])

if (toInstall.length !== 0) {
execRootSync(apt, ["install", "-y", "--fix-broken", ...toInstall])
execRootSync(apt, ["install", "-y", "--fix-broken", "-o", aptTimeout, ...toInstall])
}

const promises: Promise<string | void>[] = [
Expand Down
24 changes: 16 additions & 8 deletions src/vcpkg/__tests__/vcpkg.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { setupTmpDir, testBin } from "../../utils/tests/test-helpers"
import { cleanupTmpDir, setupTmpDir, testBin } from "../../utils/tests/test-helpers"
import { setupVcpkg } from "../vcpkg"

jest.setTimeout(300000)
async function testVcpkg(directory: string) {
const { binDir } = await setupVcpkg("", directory, "")
await testBin("vcpkg", ["--version"], binDir)
return binDir
}

describe("setup-vcpkg", () => {
let directory: string
beforeAll(async () => {
beforeEach(async () => {
directory = await setupTmpDir("vcpkg")
})

it("should setup vcpkg", async () => {
await testVcpkg(directory)
console.log(!("true" in ["", "true"]))
const { binDir } = await setupVcpkg("", directory, "")
await testBin("vcpkg", ["--version"], binDir)
return binDir
})

it("should setup vcpkg with specific version", async () => {
const { binDir } = await setupVcpkg("e590c2b30c08caf1dd8d612ec602a003f9784b7d", directory, "")
await testBin("vcpkg", ["--version"], binDir)
return binDir
})

afterEach(async () => {
await cleanupTmpDir(directory)
})
})
17 changes: 14 additions & 3 deletions src/vcpkg/vcpkg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { grantUserWriteAccess } from "admina"
import { notice } from "ci-log"
import { info, notice } from "ci-log"
import { execaSync } from "execa"
import { pathExists } from "path-exists"
import { addShExt, addShRelativePrefix, dirname, join } from "patha"
Expand All @@ -16,7 +16,7 @@ import { setupPacmanPack } from "../utils/setup/setupPacmanPack"
let hasVCPKG = false

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function setupVcpkg(_version: string, setupDir: string, _arch: string): Promise<InstallationInfo> {
export async function setupVcpkg(version: string, setupDir: string, _arch: string): Promise<InstallationInfo> {
if (!hasVCPKG || which.sync("vcpkg", { nothrow: true }) === null) {
if (process.platform === "linux") {
// vcpkg download and extraction dependencies
Expand Down Expand Up @@ -50,12 +50,23 @@ export async function setupVcpkg(_version: string, setupDir: string, _arch: stri
}
}

// clone if not already exists
if (!(await pathExists(join(setupDir, addShExt("bootstrap-vcpkg", ".bat"))))) {
execaSync("git", ["clone", "https://github.com/microsoft/vcpkg"], { cwd: dirname(setupDir), stdio: "inherit" })
} else {
notice(`Vcpkg folder already exists at ${setupDir}. This might mean that ~/vcpkg is restored from the cache.`)
notice(`Vcpkg folder already exists at ${setupDir}. Skipping the clone`)
}

// if version specified, checkout the version
if (version !== "" && version !== "true") {
info(`Checking out vcpkg version ${version}`)
execaSync("git", ["checkout", version], {
cwd: setupDir,
stdio: "inherit",
})
}

// bootstrap vcpkg
execaSync(addShExt(addShRelativePrefix("bootstrap-vcpkg"), ".bat"), {
cwd: setupDir,
shell: true,
Expand Down
Loading