UNPKG

setup-cpp

Version:

Install all the tools required for building and testing C++/C projects.

265 lines (241 loc) 8.82 kB
import path from "path" import { fileURLToPath } from "url" import ciInfo from "ci-info" const { GITHUB_ACTIONS } = ciInfo import { error, warning } from "ci-log" import { addEnv } from "envosman" import escapeRegex from "escape-string-regexp" import { execa } from "execa" import { readdir } from "fs/promises" import { pathExists } from "path-exists" import { addExeExt } from "patha" import semverMajor from "semver/functions/major" import { hasApk, installApkPack } from "setup-alpine" import { addUpdateAlternativesToRc, installAptPack } from "setup-apt" import { installBrewPack } from "setup-brew" import { rcOptions } from "../cli-options.js" import { setupMacOSSDK } from "../macos-sdk/macos-sdk.js" import { hasDnf } from "../utils/env/hasDnf.js" import { isArch } from "../utils/env/isArch.js" import { isUbuntu } from "../utils/env/isUbuntu.js" import type { InstallationInfo } from "../utils/setup/setupBin.js" import { setupDnfPack } from "../utils/setup/setupDnfPack.js" import { setupPacmanPack } from "../utils/setup/setupPacmanPack.js" import { compareVersion, semverCoerceIfInvalid } from "../utils/setup/version.js" import { addGccLoggingMatcher } from "./gccMatcher.js" import { setupMingw } from "./mingw.js" export const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url)) export async function setupGcc(version: string, setupDir: string, arch: string, priority: number = 40) { let installationInfo: InstallationInfo | undefined switch (process.platform) { case "win32": { installationInfo = await setupMingw(version, setupDir, arch) break } case "darwin": { installationInfo = await installBrewPack("gcc", version) break } case "linux": { if (isArch()) { installationInfo = await setupPacmanPack("gcc", version) } else if (hasDnf()) { installationInfo = await setupDnfPack([ { name: "gcc", version }, { name: "gcc-c++", version }, { name: "libstdc++-devel" }, ]) } else if (await hasApk()) { installationInfo = await installApkPack([{ name: "gcc", version }, { name: "g++", version }]) } else if (isUbuntu()) { if (version === "") { // the default version installationInfo = await installAptPack([{ name: "gcc" }, { name: "g++" }]) } else { try { // first try to install the version from the default repository installationInfo = await installAptPack([ { name: "gcc", version, }, { name: "g++", version, }, ]) } catch (err) { // add the PPA for access to more versions installationInfo = await installAptPack([ { name: "gcc", version, repository: "ppa:ubuntu-toolchain-r/test", key: { key: "1E9377A2BA9EF27F", fileName: "ubuntu-toolchain-r-test.gpg" }, }, { name: "g++", version, repository: "ppa:ubuntu-toolchain-r/test", key: { key: "1E9377A2BA9EF27F", fileName: "ubuntu-toolchain-r-test.gpg" }, }, ]) } } } // if (arch !== "x64") { // try { // info(`Install g++-multilib because gcc for ${arch} was requested`) // if (isArch()) { // installationInfo = await setupPacmanPack("gcc-multilib", version) // } else if (isUbuntu()) { // if (version === "") { // // the default version // installationInfo = await installAptPack([{ name: "gcc-multilib" }]) // } else { // // add the PPA for access to more versions // installationInfo = await installAptPack([{ // name: "gcc-multilib", // version, // repository: "ppa:ubuntu-toolchain-r/test", // key: { key: "1E9377A2BA9EF27F", fileName: "ubuntu-toolchain-r-test.gpg" }, // }]) // } // } // } catch (err) { // info(`Failed to install gcc-multilib ${err}\nSkipping the dependency`) // } // } break } // TODO support bare-metal (need to support passing it as the input) // TODO support abi // case "none": { // if (arch === "arm" || arch === "arm64") { // return installAptPack("gcc-arm-none-eabi", version, [ // "ppa:ubuntu-toolchain-r/test", // ]) // } else { // throw new Error(`Unsupported platform for ${arch}`) // } // } default: { throw new Error(`Unsupported platform for ${arch}`) } } if (installationInfo !== undefined) { await activateGcc(version, installationInfo.binDir, priority) return installationInfo } return undefined } /** * Setup gcc as the compiler on Linux and macOS */ async function activateGcc(givenVersion: string, binDir: string, priority: number = 40) { if (process.platform === "win32") { // already done in setupMingw return } const promises: Promise<void>[] = [] { const binVersion = getGccBinVersion(givenVersion) const [gccPath, gxxPath] = await Promise.all([ findGccExe("gcc", binDir, binVersion), findGccExe("g++", binDir, binVersion), ]) promises.push( addEnv("CC", gccPath, rcOptions), addEnv("CXX", gxxPath, rcOptions), ) if (isUbuntu()) { promises.push( addUpdateAlternativesToRc("cc", gccPath, rcOptions, priority), addUpdateAlternativesToRc("cxx", gxxPath, rcOptions, priority), addUpdateAlternativesToRc("gcc", gccPath, rcOptions, priority), addUpdateAlternativesToRc("g++", gxxPath, rcOptions, priority), ) } } promises.push(setupMacOSSDK()) // TODO // const ld = process.env.LD_LIBRARY_PATH ?? "" // const dyld = process.env.DYLD_LIBRARY_PATH ?? "" // promises.push( // addEnv("LD_LIBRARY_PATH", `${installDir}/lib${path.delimiter}${ld}`, rcOptions), // addEnv("DYLD_LIBRARY_PATH", `${installDir}/lib${path.delimiter}${dyld}`, rcOptions), // addEnv("CPATH", `${installDir}/lib/gcc/${majorVersion}/include`, rcOptions), // addEnv("LDFLAGS", `-L${installDir}/lib`, rcOptions), // addEnv("CPPFLAGS", `-I${installDir}/include`, rcOptions), // ) if (GITHUB_ACTIONS) { await addGccLoggingMatcher() } await Promise.all(promises) } /** * Get the version string used in the gcc exe name */ function getGccBinVersion(givenVersion: string) { try { const coerced = semverCoerceIfInvalid(givenVersion) const majorVersion = semverMajor(coerced) return majorVersion >= 5 ? `${majorVersion}` : givenVersion } catch { // ignore return givenVersion } } async function findGccExe(variant: "gcc" | "g++", binDir: string, binVersion: string) { if (await pathExists(`${binDir}/${variant}-${binVersion}`)) { return addExeExt(`${binDir}/${variant}-${binVersion}`) } // try to find the gcc exe in the bin dir const gccExeRegex = new RegExp(`^${escapeRegex(variant)}-?([\\d\\.\\-]*)(?:\\.exe)?$`) const files = (await readdir(binDir)) .filter((file) => gccExeRegex.test(file)) .sort( (exe1, exe2) => { const version1 = exe1.match(gccExeRegex)?.[1] ?? "" const version2 = exe2.match(gccExeRegex)?.[1] ?? "" try { return compareVersion(version1, version2) } catch { return 0 } }, ) for (const file of files) { const gccExe = `${binDir}/${file}` if ( binVersion === "" || file.includes(binVersion) /* eslint-disable-next-line no-await-in-loop */ || ((await getGccCmdVersion(gccExe))?.includes(binVersion) ?? false) ) { return addExeExt(gccExe) } } if (await pathExists(`${binDir}/${variant}`)) { return addExeExt(`${binDir}/${variant}`) } return addExeExt(variant) } async function getGccCmdVersion(gccExe: string) { // TODO get the version from the package manager try { const { stdout: versionStdout } = await execa(gccExe, ["--version"], { stdio: "pipe" }) // gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 // gcc (Ubuntu 13.1.0-8ubuntu1~22.04) 13.1.0 // gcc-14 (Homebrew GCC 14.2.0_1) 14.2.0 // g++-14 (Homebrew GCC 14.2.0_1) 14.2.0 const versionMatch = versionStdout.match(/(gcc|g\+\+).* \(.*\) ([\d.]+)/) if (versionMatch !== null) { return versionMatch[2] } warning(`Failed to parse gcc version from: ${versionStdout}`) } catch (err) { error(`Failed to get gcc version: ${err}`) } return undefined }