Merge pull request #306 from aminya/versions [skip ci]

This commit is contained in:
Amin Yahyaabadi 2024-09-22 23:11:24 -07:00 committed by GitHub
commit 9560b8d586
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 340 additions and 145 deletions

View File

@ -1,5 +1,11 @@
import { buildTerserOptions } from "terser-config-atomic/dist/builder.js"
const config = buildTerserOptions(process.env.NODE_ENV, undefined, true)
if (
typeof config.compress === "object"
&& "unsafe_math" in config.compress
) {
config.compress.unsafe_math = false
}
export default config

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

@ -3,7 +3,8 @@ import { endGroup, startGroup } from "@actions/core"
import { error, info } from "ci-log"
import semverValid from "semver/functions/valid"
import { getSuccessMessage } from "./cli-options.js"
import { setupGcc, setupMingw } from "./gcc/gcc.js"
import { setupGcc } from "./gcc/gcc.js"
import { setupMingw } from "./gcc/mingw.js"
import { activateGcovGCC, activateGcovLLVM } from "./gcovr/gcovr.js"
import { setupAppleClang } from "./llvm/apple-clang.js"
import { setupLLVM } from "./llvm/llvm.js"

View File

@ -1,83 +1,34 @@
import path, { join } from "path"
import path from "path"
import { fileURLToPath } from "url"
import { GITHUB_ACTIONS } from "ci-info"
import { error, info, warning } from "ci-log"
import { addEnv, addPath } from "envosman"
import { addEnv } from "envosman"
import { execa } from "execa"
import { readdir } from "fs/promises"
import { pathExists } from "path-exists"
import { addExeExt } from "patha"
import semverCoerce from "semver/functions/coerce"
import semverMajor from "semver/functions/major"
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 { loadAssetList, matchAsset } from "../utils/asset/load-assets.js"
import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.js"
import { isUbuntu } from "../utils/env/isUbuntu.js"
import { extract7Zip } from "../utils/setup/extract.js"
import { type InstallationInfo, type PackageInfo, setupBin } from "../utils/setup/setupBin.js"
import { setupChocoPack } from "../utils/setup/setupChocoPack.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 } from "../utils/setup/version.js"
import { addGccLoggingMatcher } from "./gccMatcher.js"
import { setupMingw } from "./mingw.js"
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
async function getGccPackageInfo(version: string, platform: NodeJS.Platform, arch: string): Promise<PackageInfo> {
switch (platform) {
case "win32": {
const mingwAssets = await loadAssetList(
join(dirname, "github_brechtsanders_winlibs_mingw.json"),
)
const mingwArchMap = {
x64: "x86_64",
ia32: "i386",
} as Record<string, string | undefined>
const asset = matchAsset(
mingwAssets,
{
version,
keywords: [
mingwArchMap[arch] ?? arch,
],
},
)
if (asset === undefined) {
throw new Error(`No asset found for version ${version} and arch ${arch}`)
}
return {
binRelativeDir: "bin/",
binFileName: addExeExt("g++"),
extractedFolderName: "mingw64",
extractFunction: extract7Zip,
url: `https://github.com/brechtsanders/winlibs_mingw/releases/download/${asset.tag}/${asset.name}`,
}
}
default:
throw new Error(`Unsupported platform '${platform}'`)
}
}
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": {
if (arch === "arm" || arch === "arm64") {
await setupChocoPack("gcc-arm-embedded", version)
}
try {
installationInfo = await setupBin("g++", version, getGccPackageInfo, setupDir, arch)
} catch (err) {
info(`Failed to download g++ binary. ${err}. Falling back to chocolatey.`)
installationInfo = await setupChocoMingw(version, arch)
}
installationInfo = await setupMingw(version, setupDir, arch)
break
}
case "darwin": {
@ -159,75 +110,18 @@ export async function setupGcc(version: string, setupDir: string, arch: string,
return undefined
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function setupMingw(version: string, setupDir: string, arch: string) {
let installationInfo: InstallationInfo | undefined
switch (process.platform) {
case "win32":
case "darwin": {
return setupGcc(version, setupDir, arch)
}
case "linux": {
if (isArch()) {
installationInfo = await setupPacmanPack("mingw-w64-gcc", version)
} else if (hasDnf()) {
installationInfo = await setupDnfPack([{ name: "mingw64-gcc", version }])
} else if (isUbuntu()) {
installationInfo = await installAptPack([
{
name: "mingw-w64",
version,
repository: "ppa:ubuntu-toolchain-r/test",
key: { key: "1E9377A2BA9EF27F", fileName: "ubuntu-toolchain-r-test.gpg" },
},
])
}
break
}
default: {
throw new Error(`Unsupported platform for ${arch}`)
}
}
if (installationInfo !== undefined) {
// TODO: setup alternatives and update CC/CXX env. ?
// Setting up g++-mingw-w64-i686-win32 (10.3.0-14ubuntu1+24.3) ...
// update-alternatives: using /usr/bin/i686-w64-mingw32-g++-win32 to provide /usr/bin/i686-w64-mingw32-g++ (i686-w64-mingw32-g++) in auto mode
// Setting up g++-mingw-w64-x86-64-win32 (10.3.0-14ubuntu1+24.3) ...
// update-alternatives: using /usr/bin/x86_64-w64-mingw32-g++-win32 to provide /usr/bin/x86_64-w64-mingw32-g++ (x86_64-w64-mingw32-g++) in auto mode
// await activateGcc(version, installationInfo.binDir)
return installationInfo
}
return undefined
}
async function setupChocoMingw(version: string, arch: string): Promise<InstallationInfo | undefined> {
await setupChocoPack("mingw", version)
let binDir: string | undefined
if (arch === "x64" && (await pathExists("C:/tools/mingw64/bin"))) {
binDir = "C:/tools/mingw64/bin"
await addPath(binDir, rcOptions)
} else if (arch === "ia32" && (await pathExists("C:/tools/mingw32/bin"))) {
binDir = "C:/tools/mingw32/bin"
await addPath(binDir, rcOptions)
} else if (await pathExists(`${process.env.ChocolateyInstall ?? "C:/ProgramData/chocolatey"}/bin/g++.exe`)) {
binDir = `${process.env.ChocolateyInstall ?? "C:/ProgramData/chocolatey"}/bin`
}
if (binDir !== undefined) {
return { binDir }
}
return undefined
}
/** Setup gcc as the compiler */
/**
* 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>[] = []
if (process.platform === "win32") {
promises.push(
addEnv("CC", addExeExt(`${binDir}/gcc`), rcOptions),
addEnv("CXX", addExeExt(`${binDir}/g++`), rcOptions),
)
} else {
{
// if version is empty, get the version from the gcc command
let version = givenVersion
if (givenVersion === "") {
@ -329,11 +223,3 @@ async function getGccCmdVersion(binDir: string, givenVersion: string) {
return givenVersion
}
}
async function addGccLoggingMatcher() {
const matcherPath = join(dirname, "gcc_matcher.json")
if (!(await pathExists(matcherPath))) {
return warning("the gcc_matcher.json file does not exist in the same folder as setup-cpp.js")
}
info(`::add-matcher::${matcherPath}`)
}

12
src/gcc/gccMatcher.ts Normal file
View File

@ -0,0 +1,12 @@
import { join } from "path"
import { info, warning } from "ci-log"
import { pathExists } from "path-exists"
import { dirname } from "./gcc.ts"
export async function addGccLoggingMatcher() {
const matcherPath = join(dirname, "gcc_matcher.json")
if (!(await pathExists(matcherPath))) {
return warning("the gcc_matcher.json file does not exist in the same folder as setup-cpp.js")
}
info(`::add-matcher::${matcherPath}`)
}

217
src/gcc/mingw.ts Normal file
View File

@ -0,0 +1,217 @@
import path, { join } from "path"
import { fileURLToPath } from "url"
import { GITHUB_ACTIONS } from "ci-info"
import { info } from "ci-log"
import { addEnv, addPath } from "envosman"
import { pathExists } from "path-exists"
import { addExeExt } from "patha"
import semverCoerce from "semver/functions/coerce.js"
import semverSatisfies from "semver/functions/satisfies.js"
import { installAptPack } from "setup-apt"
import { rcOptions } from "../cli-options.js"
import { loadAssetList, matchAsset } from "../utils/asset/load-assets.js"
import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.js"
import { isUbuntu } from "../utils/env/isUbuntu.js"
import { extract7Zip } from "../utils/setup/extract.js"
import { type InstallationInfo, type PackageInfo, setupBin } from "../utils/setup/setupBin.js"
import { setupChocoPack } from "../utils/setup/setupChocoPack.js"
import { setupDnfPack } from "../utils/setup/setupDnfPack.js"
import { setupPacmanPack } from "../utils/setup/setupPacmanPack.js"
import { addGccLoggingMatcher } from "./gccMatcher.js"
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
export async function setupMingw(version: string, setupDir: string, arch: string) {
let installationInfo: InstallationInfo | undefined
switch (process.platform) {
case "win32": {
if (arch === "arm" || arch === "arm64") {
installationInfo = await setupChocoPack("gcc-arm-embedded", version)
}
try {
installationInfo = await setupBin("g++", version, getMinGWPackageInfo, setupDir, arch)
} catch (err) {
info(`Failed to download g++ binary. ${err}. Falling back to chocolatey.`)
installationInfo = await setupChocoMingw(version, arch)
}
break
}
case "linux": {
if (isArch()) {
installationInfo = await setupPacmanPack("mingw-w64-gcc", version)
} else if (hasDnf()) {
installationInfo = await setupDnfPack([{ name: "mingw64-gcc", version }])
} else if (isUbuntu()) {
installationInfo = await installAptPack([
{
name: "mingw-w64",
version,
repository: "ppa:ubuntu-toolchain-r/test",
key: { key: "1E9377A2BA9EF27F", fileName: "ubuntu-toolchain-r-test.gpg" },
},
])
} else {
throw new Error(`Unsupported Linux distro for ${arch}`)
}
break
}
default: {
throw new Error(`Unsupported platform for ${arch}`)
}
}
if (installationInfo !== undefined) {
await activateMinGW(installationInfo.binDir)
}
return installationInfo
}
async function setupChocoMingw(version: string, arch: string): Promise<InstallationInfo | undefined> {
await setupChocoPack("mingw", version)
let binDir: string | undefined
if (arch === "x64" && (await pathExists("C:/tools/mingw64/bin"))) {
binDir = "C:/tools/mingw64/bin"
await addPath(binDir, rcOptions)
} else if (arch === "ia32" && (await pathExists("C:/tools/mingw32/bin"))) {
binDir = "C:/tools/mingw32/bin"
await addPath(binDir, rcOptions)
} else if (await pathExists(`${process.env.ChocolateyInstall ?? "C:/ProgramData/chocolatey"}/bin/g++.exe`)) {
binDir = `${process.env.ChocolateyInstall ?? "C:/ProgramData/chocolatey"}/bin`
}
if (binDir !== undefined) {
return { binDir }
}
return undefined
}
export async function getMinGWPackageInfo(
version: string,
platform: NodeJS.Platform,
arch: string,
): Promise<PackageInfo> {
if (platform !== "win32") {
throw new Error(`Unsupported platform '${platform}'`)
}
const mingwAssets = await loadAssetList(
join(dirname, "github_brechtsanders_winlibs_mingw.json"),
)
const mingwArchMap = {
x64: "x86_64",
ia32: "i386",
} as Record<string, string | undefined>
const runtime = extractMinGWRuntime(version)
const threadModel = extractMinGWThreadModel(version)
const exceptionModel = extractMingwExceptionModel(version)
const asset = matchAsset(
mingwAssets,
{
version,
keywords: [
mingwArchMap[arch] ?? arch,
],
filterName: (assetName) => {
return (runtime === undefined || runtime === extractMinGWRuntime(assetName))
&& (threadModel === undefined || threadModel === extractMinGWThreadModel(assetName))
&& (exceptionModel === undefined || exceptionModel === extractMingwExceptionModel(assetName))
},
versionSatisfies: (assetVersion, versionRange) => {
// extract the base version by coercing the version
const assetCoerce = semverCoerce(assetVersion)
if (assetCoerce === null) {
throw new Error(`Invalid MinGW asset version: '${assetVersion}'`)
}
// if the asset version is satisfied by the version
// and the runtime and thread model match or not specified
return semverSatisfies(assetCoerce, versionRange)
&& (runtime === undefined || runtime === extractMinGWRuntime(assetVersion))
&& (threadModel === undefined || threadModel === extractMinGWThreadModel(assetVersion))
},
},
)
if (asset === undefined) {
throw new Error(`No asset found for version ${version} and arch ${arch}`)
}
return {
binRelativeDir: "bin/",
binFileName: addExeExt("g++"),
extractedFolderName: "mingw64",
extractFunction: extract7Zip,
url: `https://github.com/brechtsanders/winlibs_mingw/releases/download/${asset.tag}/${asset.name}`,
}
}
/**
* Extract the runtime used by the MinGW asset/version
* @param input The input to extract the runtime from
*
* @example
* extractMinGWRuntime("14.2.0posix-18.1.8-12.0.0-ucrt-r1") // "ucrt"
* extractMinGWRuntime("10.5.0-11.0.1-msvcrt-r2") // "msvcrt"
* extractMinGWRuntime("11.1.0-12.0.0-9.0.0-r1") // undefined
*/
function extractMinGWRuntime(input: string) {
const match = input.match(/(ucrt|msvcrt)/)
return match !== null ? match[1] : undefined
}
/**
* Extract the thread model used by the MinGW asset/version
* @param input The input to extract the thread model from
*
* @example
* extractMinGWThreadModel("14.2.0posix-18.1.8-12.0.0-ucrt-r1") // "posix"
* extractMinGWThreadModel("14.2.0mcf-12.0.0-ucrt-r1") // "mcf"
* extractMinGWThreadModel("10.5.0-11.0.1-msvcrt-r2") // undefined
* extractMinGWThreadModel("11.1.0-12.0.0-9.0.0-r1") // undefined
*/
function extractMinGWThreadModel(input: string) {
const match = input.match(/(posix|mcf)/)
return match !== null ? match[1] : undefined
}
/**
* Extract the exception model used by the MinGW asset/version
*
* @param input The input to extract the exception model from
*
* @example
* extractMingwExceptionModel("14.2.0posix-18.1.8-12.0.0-ucrt-r1") // "seh"
* extractMingwExceptionModel("14.2.0mcf-12.0.0-ucrt-r1") // undefined
* extractMingwExceptionModel("10.5.0-11.0.1-msvcrt-r2") // "dwarf"
*/
function extractMingwExceptionModel(input: string) {
const match = input.match(/(seh|dwarf)/)
return match !== null ? match[1] : undefined
}
async function activateMinGW(binDir: string) {
const promises: Promise<void>[] = []
if (process.platform === "win32") {
promises.push(
addEnv("CC", addExeExt(`${binDir}/gcc`), rcOptions),
addEnv("CXX", addExeExt(`${binDir}/g++`), rcOptions),
)
}
// TODO add update-alternatives for Ubuntu
// Setting up g++-mingw-w64-i686-win32 (10.3.0-14ubuntu1+24.3) ...
// update-alternatives: using /usr/bin/i686-w64-mingw32-g++-win32 to provide /usr/bin/i686-w64-mingw32-g++ (i686-w64-mingw32-g++) in auto mode
// Setting up g++-mingw-w64-x86-64-win32 (10.3.0-14ubuntu1+24.3) ...
// update-alternatives: using /usr/bin/x86_64-w64-mingw32-g++-win32 to provide /usr/bin/x86_64-w64-mingw32-g++ (x86_64-w64-mingw32-g++) in auto mode
if (GITHUB_ACTIONS) {
await addGccLoggingMatcher()
}
await Promise.all(promises)
}

View File

@ -9,7 +9,8 @@ import { setupCppcheck } from "./cppcheck/cppcheck.js"
import { setupCpplint } from "./cpplint/cpplint.js"
import { setupDoxygen } from "./doxygen/doxygen.js"
import { setupFlawfinder } from "./flawfinder/flawfinder.js"
import { setupGcc, setupMingw } from "./gcc/gcc.js"
import { setupGcc } from "./gcc/gcc.js"
import { setupMingw } from "./gcc/mingw.js"
import { setupGcovr } from "./gcovr/gcovr.js"
import { setupGraphviz } from "./graphviz/graphviz.js"
import { setupInfer } from "./infer/infer.js"

View File

@ -1,4 +1,6 @@
import { readFile } from "fs/promises"
import semverSatisfies from "semver/functions/satisfies.js"
import { semverCoercedRangeIfInvalid } from "../setup/version.ts"
/**
* The list of assets
@ -15,16 +17,56 @@ export async function loadAssetList(path: string): Promise<Assets> {
return JSON.parse(data)
}
type MatchAssetOpts = {
/**
* The options to match the asset
*/
export type MatchAssetOpts = {
/**
* The version to match
*/
version: string
/**
* The keywords that must be in the asset name
* @default []
*/
keywords?: string[]
/**
* Optional keywords that are not required to be in the asset name
* but increase the score of the asset if they are present
* @default []
*/
optionalKeywords?: string[]
/**
* Custom version compare function
* @param candidate The candidate version
* @param coeredVersion The coerced version to compare against
* @returns true if the candidate version satisfies the version
*
* @default semverSatisfies
*/
versionSatisfies?: (candidate: string, coeredVersion: string) => boolean
/**
* Custom tag filter and map function
* @param tag The tag to filter and map
* @returns The mapped tag or undefined if the tag should be
* excluded from the search
* @default undefined
*/
filterMapTag?: (tag: string) => string | undefined
/**
* Custom asset name filter function
* @param asset The asset name to filter
* @returns true if the asset should be included in the search
* @default undefined
*/
filterName?: (asset: string) => boolean
}
/**
* Match the asset that matches the version and given keywords
* @param assets The list of assets
* @param opts The options to match the asset
* @returns The tag and name of the asset that matches the version and keywords
*/
export function matchAsset(
assets: Assets,
@ -52,11 +94,17 @@ export function matchAsset(
return undefined
}
// Assume the version is a semver version if a custom version compare function is not given
const versionSatisfies: (c: string, v: string) => boolean = opts.versionSatisfies ?? semverSatisfies
// If not a valid semver version, coerce it to a semver version range
const versionRange = semverCoercedRangeIfInvalid(opts.version)
// find the first tag that starts with the version
// loop over the versions starting with the latest
const candidateTags: string[] = []
for (const [version, origTag] of versionMap.entries()) {
if (version.startsWith(opts.version)) {
if (versionSatisfies(version, versionRange)) {
candidateTags.push(origTag)
}
}

View File

@ -137,6 +137,31 @@ export function semverCoerceIfInvalid(version: string) {
return version
}
/**
* Coerce the given version to a semver range if it is invalid
*/
export function semverCoercedRangeIfInvalid(version: string) {
if (semverValid(version) === null) {
// version coercion
try {
// find the semver version of an integer
const coercedVersion = semverCoerce(version)
if (coercedVersion !== null) {
// if the versions doesn't specify a range specifier (e.g. ^, ~, >, <, etc.), add a ^ to the version
const versionRange = /^[<=>^~]/.test(coercedVersion.version)
? coercedVersion.version
: `^${coercedVersion.version}`
info(`Coerced version '${version}' to '${versionRange}'`)
return versionRange
}
} catch (err) {
// handled below
}
}
return version
}
export function removeVPrefix(version: string) {
return Number.parseInt(version.replace(/^v/, ""), 10)
}

View File

@ -18,13 +18,12 @@ export const DefaultVersions: Record<string, string | undefined> = {
"clang-format": defaultLLVM,
clangformat: defaultLLVM,
ninja: "1.12.1", // https://github.com/ninja-build/ninja/releases
cmake: "3.30.2", // https://github.com/Kitware/CMake/releases
gcovr: "5.2", // "6.0", // https://pypi.org/project/gcovr/
conan: "1.64.1", // 2.0.17 // https://github.com/conan-io/conan/releases
meson: "1.5.1", // https://github.com/mesonbuild/meson/releases
cmake: "3.30.3", // https://github.com/Kitware/CMake/releases
conan: "1.65.0", // 2.7.1 // https://github.com/conan-io/conan/releases
meson: "1.5.2", // https://github.com/mesonbuild/meson/releases
kcov: "42", // https://github.com/SimonKagstrom/kcov/releases
task: "3.38.0", // https://github.com/go-task/task/releases
doxygen: isArch() ? "1.11.0-4" : "1.11.0", // https://www.doxygen.nl/download.html // https://packages.ubuntu.com/search?suite=all&arch=any&searchon=names&keywords=doxygen // https://formulae.brew.sh/formula/doxygen // https://archlinux.org/packages/extra/x86_64/doxygen/
task: "3.39.2", // https://github.com/go-task/task/releases
doxygen: isArch() ? "1.12.0-2" : "1.12.0", // https://www.doxygen.nl/download.html // https://packages.ubuntu.com/search?suite=all&arch=any&searchon=names&keywords=doxygen // https://formulae.brew.sh/formula/doxygen // https://archlinux.org/packages/extra/x86_64/doxygen/
gcc: process.platform === "win32"
? "14.2.0posix-18.1.8-12.0.0-ucrt-r1"
: "", // use the default version on Ubuntu, Fedora, Arch, macOS, etc.