Merge pull request #294 from aminya/cmakelang [skip ci]

This commit is contained in:
Amin Yahyaabadi 2024-09-18 17:17:08 -07:00 committed by GitHub
commit fb2a9a2418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 330 additions and 86 deletions

View File

@ -17,7 +17,7 @@ Setting up a **cross-platform** environment for building and testing C++/C proje
| category | tools |
| --------------------- | --------------------------------------------------------------------------- |
| compiler and analyzer | llvm, gcc, msvc, apple-clang, vcvarsall, cppcheck, clang-tidy, clang-format |
| build system | cmake, ninja, meson, make, task, bazel |
| build system | cmake, ninja, meson, make, task, bazel, cmakelang, cmake-format, cmake-lint |
| package manager | vcpkg, conan, choco, brew, nala |
| cache | ccache, sccache |
| documentation | doxygen, graphviz |

View File

@ -70,6 +70,21 @@ inputs:
cmake:
description: "Wether to install cmake (true/false) or the specific version to install."
required: false
cmakelang:
description: "Wether to install cmakelang (true/false) or the specific version to install."
required: false
cmake-lint:
description: "Wether to install cmake-lint (true/false) or the specific version to install."
required: false
cmakelint:
description: "Wether to install cmake-lint (true/false) or the specific version to install."
required: false
cmake-format:
description: "Wether to install cmake-format (true/false) or the specific version to install."
required: false
cmakeformat:
description: "Wether to install cmake-format (true/false) or the specific version to install."
required: false
ninja:
description: "Wether to install ninja (true/false) or the specific version to install."
required: false

View File

@ -28,6 +28,7 @@ words:
- choco
- clangd
- cmake
- cmakeformat
- cobertura
- copr
- CPATH

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

@ -68,55 +68,60 @@ const retryErrors = [
* ```
*/
export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
const apt: string = getApt()
for (const { name, version } of packages) {
info(`Installing ${name} ${version ?? ""} via ${apt}`)
}
// Update the repos if needed
if (update) {
updateAptReposMemoized(apt)
}
// Add the repos if needed
await addRepositories(apt, packages)
const needToInstall = await filterAndQualifyAptPackages(packages, apt)
if (needToInstall.length === 0) {
info("All packages are already installed")
return { binDir: "/usr/bin/" }
}
// Initialize apt if needed
await initAptMemoized(apt)
try {
// Add the keys if needed
await addAptKeys(packages)
const apt: string = getApt()
// Install
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
...defaultExecOptions,
env: getAptEnv(apt),
})
} catch (err) {
if (isExecaError(err)) {
if (retryErrors.some((error) => err.stderr.includes(error))) {
warning(`Failed to install packages ${needToInstall}. Retrying...`)
execRootSync(
apt,
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
{ ...defaultExecOptions, env: getAptEnv(apt) },
)
}
} else {
throw err
for (const { name, version } of packages) {
info(`Installing ${name} ${version ?? ""} via ${apt}`)
}
}
return { binDir: "/usr/bin/" }
// Update the repos if needed
if (update) {
updateAptReposMemoized(apt)
}
// Add the repos if needed
await addRepositories(apt, packages)
const needToInstall = await filterAndQualifyAptPackages(packages, apt)
if (needToInstall.length === 0) {
info("All packages are already installed")
return { binDir: "/usr/bin/" }
}
// Initialize apt if needed
await initAptMemoized(apt)
try {
// Add the keys if needed
await addAptKeys(packages)
// Install
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
...defaultExecOptions,
env: getAptEnv(apt),
})
} catch (err) {
if (isExecaError(err)) {
if (retryErrors.some((error) => err.stderr.includes(error))) {
warning(`Failed to install packages ${needToInstall}. Retrying...`)
execRootSync(
apt,
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
{ ...defaultExecOptions, env: getAptEnv(apt) },
)
}
} else {
throw err
}
}
return { binDir: "/usr/bin/" }
} catch (err) {
const msg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err)
throw new Error(`Failed to install apt packages: ${msg}`)
}
}
async function addRepositories(apt: string, packages: AptPackage[]) {

View File

@ -35,7 +35,9 @@ All the available tools:
"compiler and analyzer": {
tools: "--llvm, --gcc, --msvc, --apple-clang, --vcvarsall, --cppcheck, --clang-tidy, --clang-format",
},
"build system": { tools: "--cmake, --ninja, --meson, --make, --task, --bazel" },
"build system": {
tools: "--cmake, --ninja, --meson, --make, --task, --bazel, --cmakelang, --cmake-lint, --cmake-format",
},
"package manager": { tools: "--vcpkg, --conan, --choco, --brew, --nala" },
cache: { tools: "--ccache, --sccache" },
documentation: { tools: "--doxygen, --graphviz" },

View File

@ -0,0 +1,13 @@
import { ubuntuVersion } from "../../utils/env/ubuntu_version.js"
import { testBin } from "../../utils/tests/test-helpers.js"
import { getVersion } from "../../versions/versions.js"
import { setupCmakelang } from "../cmakelang.js"
jest.setTimeout(300000)
describe("setup-cmakelang", () => {
it("should setup cmakelang", async () => {
const installInfo = await setupCmakelang(getVersion("cmakelang", "true", await ubuntuVersion()), "", process.arch)
await testBin("cmake-lint", ["--version"], installInfo.binDir)
await testBin("cmake-format", ["--version"], installInfo.binDir)
})
})

View File

@ -0,0 +1,6 @@
import { setupPipPack } from "../utils/setup/setupPipPack.js"
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function setupCmakelang(version: string | undefined, _setupDir: string, _arch: string) {
return setupPipPack("cmakelang[YAML]", version)
}

View File

@ -26,7 +26,11 @@ import { isBinUptoDate } from "../utils/setup/version.js"
import { unique } from "../utils/std/index.js"
import { MinVersions } from "../versions/default_versions.js"
export async function setupPython(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
export async function setupPython(
version: string,
setupDir: string,
arch: string,
): Promise<InstallationInfo & { bin: string }> {
const installInfo = await findOrSetupPython(version, setupDir, arch)
assert(installInfo.bin !== undefined)
const foundPython = installInfo.bin
@ -41,7 +45,7 @@ export async function setupPython(version: string, setupDir: string, arch: strin
await setupWheel(foundPython)
return installInfo
return installInfo as InstallationInfo & { bin: string }
}
async function setupPipx(foundPython: string) {
@ -56,12 +60,20 @@ async function setupPipx(foundPython: string) {
}
}
await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" })
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false })
await setupVenv(foundPython)
} catch (err) {
warning(`Failed to install pipx: ${(err as Error).toString()}. Ignoring...`)
}
}
async function setupVenv(foundPython: string) {
try {
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false })
} catch (err) {
warning(`Failed to install venv: ${(err as Error).toString()}. Ignoring...`)
}
}
/** Setup wheel and setuptools */
async function setupWheel(foundPython: string) {
try {
@ -70,9 +82,9 @@ async function setupWheel(foundPython: string) {
isLibrary: true,
usePipx: false,
})
await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: true, isLibrary: true, usePipx: false })
await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: false, isLibrary: true, usePipx: false })
} catch (err) {
warning(`Failed to install setuptools or wheel: ${(err as Error).toString()}. Ignoring...`)
warning(`Failed to install setuptools/wheel: ${(err as Error).toString()}. Ignoring...`)
}
}
@ -171,7 +183,7 @@ async function setupPythonSystem(setupDir: string, version: string) {
}
async function findPython(binDir?: string) {
for (const pythonBin of ["python3", "python"]) {
for (const pythonBin of ["python", "python3"]) {
// eslint-disable-next-line no-await-in-loop
const foundPython = await isPythonUpToDate(pythonBin, binDir)
if (foundPython !== undefined) {
@ -203,10 +215,8 @@ async function isPythonUpToDate(candidate: string, binDir?: string) {
try {
if (binDir !== undefined) {
const pythonBinPath = join(binDir, addExeExt(candidate))
if (await pathExists(pythonBinPath)) {
if (await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
return pythonBinPath
}
if (await pathExists(pythonBinPath) && await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
return pythonBinPath
}
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition

View File

@ -3,6 +3,7 @@ import { setupBazel } from "./bazel/bazel.js"
import { setupCcache } from "./ccache/ccache.js"
import { setupChocolatey } from "./chocolatey/chocolatey.js"
import { setupCmake } from "./cmake/cmake.js"
import { setupCmakelang } from "./cmakelang/cmakelang.js"
import { setupConan } from "./conan/conan.js"
import { setupCppcheck } from "./cppcheck/cppcheck.js"
import { setupDoxygen } from "./doxygen/doxygen.js"
@ -42,19 +43,32 @@ export const appleClangSetups = {
"apple-llvm": setupAppleClang,
} as const
const cmakeLangSetups = {
cmakelang: setupCmakelang,
"cmake-lint": setupCmakelang,
"cmake-format": setupCmakelang,
cmakelint: setupCmakelang,
cmakeformat: setupCmakelang,
} as const
export const llvmTools = ["llvm", "clang", "clang++", "clang-tidy", "clang-format", "clangtidy", "clangformat"]
/** The setup functions */
export const setups = {
nala: setupNala,
brew: setupBrew,
choco: setupChocolatey,
python: setupPython,
powershell: setupPowershell,
pwsh: setupPowershell,
...llvmSetups,
...gccSetups,
...mingwSetups,
...msvcSetups,
...appleClangSetups,
nala: setupNala,
...cmakeLangSetups,
cmake: setupCmake,
ninja: setupNinja,
python: setupPython,
vcpkg: setupVcpkg,
bazel: setupBazel,
conan: setupConan,
@ -62,10 +76,6 @@ export const setups = {
gcovr: setupGcovr,
opencppcoverage: setupOpencppcoverage,
OpenCppCoverage: setupOpencppcoverage,
choco: setupChocolatey,
brew: setupBrew,
powershell: setupPowershell,
pwsh: setupPowershell,
ccache: setupCcache,
sccache: setupSccache,
doxygen: setupDoxygen,

91
src/utils/compat/node.d.ts vendored Normal file
View File

@ -0,0 +1,91 @@
// the installed @types/node package is version 12 so that backwards compatibility is maintained
// node: prefix is removed by Babel, so define the types of those packages here so that TypeScript can find them
declare module "node:fs" {
import fs from "fs"
export = fs
}
declare module "node:path" {
import path from "path"
export = path
}
declare module "node:child_process" {
import child_process from "child_process"
export = child_process
}
declare module "node:os" {
import os from "os"
export = os
}
declare module "node:util" {
import util from "util"
export = util
}
declare module "node:stream" {
import stream from "stream"
export = stream
}
declare module "node:zlib" {
import zlib from "zlib"
export = zlib
}
declare module "node:crypto" {
import crypto from "crypto"
export = crypto
}
declare module "node:http" {
import http from "http"
export = http
}
declare module "node:https" {
import https from "https"
export = https
}
declare module "node:events" {
import events from "events"
export = events
}
declare module "node:assert" {
import assert from "assert"
export = assert
}
declare module "node:constants" {
import constants from "constants"
export = constants
}
declare module "node:querystring" {
import querystring from "querystring"
export = querystring
}
declare module "node:url" {
import url from "url"
export = url
}
declare module "node:fs/promises" {
import fsPromises from "fs/promises"
export = fsPromises
}
declare module "node:path/posix" {
import pathPosix from "path/posix"
export = pathPosix
}
declare module "node:path/win32" {
import pathWin32 from "path/win32"
export = pathWin32
}

View File

@ -50,9 +50,26 @@ export async function setupPipPackWithPython(
const { usePipx = true, user = true, upgrade = false, isLibrary = false } = options
const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython))
const pip = isPipx ? "pipx" : "pip"
const hasPackage = await pipHasPackage(givenPython, name)
// remove `[]` extensions
const nameOnly = getPackageName(name)
// if upgrade is not requested, check if the package is already installed, and return if it is
if (!upgrade) {
const installed = isPipx
? await pipxPackageInstalled(givenPython, nameOnly)
: await pipPackageIsInstalled(givenPython, nameOnly)
if (installed) {
const binDir = isPipx
? await finishPipxPackageInstall()
: await finishPipPackageInstall(givenPython, nameOnly)
return { binDir }
}
}
const hasPackage = await pipHasPackage(givenPython, nameOnly)
if (hasPackage) {
try {
info(`Installing ${name} ${version ?? ""} via ${pip}`)
@ -74,27 +91,36 @@ export async function setupPipPackWithPython(
env,
})
} catch (err) {
info(`Failed to install ${name} via ${pip}: ${err}.`)
const msg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err)
info(`Failed to install ${name} via ${pip}: ${msg}`)
if ((await setupPipPackSystem(name)) === null) {
throw new Error(`Failed to install ${name} via ${pip}: ${err}.`)
}
}
} else {
if ((await setupPipPackSystem(name)) === null) {
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
}
} else if ((await setupPipPackSystem(name)) === null) {
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
}
const execPaths = await addPythonBaseExecPrefix(givenPython)
const binDir = await findBinDir(execPaths, name)
await addPath(binDir, rcOptions)
const binDir = isPipx
? await finishPipxPackageInstall()
: await finishPipPackageInstall(givenPython, nameOnly)
return { binDir }
}
function finishPipxPackageInstall() {
return getPipxBinDir()
}
async function finishPipPackageInstall(givenPython: string, name: string) {
const pythonBaseExecPrefix = await addPythonBaseExecPrefix(givenPython)
const binDir = await findBinDir(pythonBaseExecPrefix, name)
await addPath(binDir, rcOptions)
return binDir
}
export async function hasPipx(givenPython: string) {
return (await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })).exitCode === 0
const res = await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })
return res.exitCode === 0
}
async function getPipxHome_() {
@ -144,14 +170,77 @@ async function getPipxBinDir_() {
}
const getPipxBinDir = memoize(getPipxBinDir_, { promise: true })
async function getPython_(): Promise<string> {
const pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin
if (pythonBin === undefined) {
throw new Error("Python binary was not found")
/* eslint-disable require-atomic-updates */
let pythonBin: string | undefined
async function getPython(): Promise<string> {
if (pythonBin !== undefined) {
return pythonBin
}
pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin
return pythonBin
}
const getPython = memoize(getPython_, { promise: true })
/**
* Get the actual name of a pip package from the given string
* @param pkg the given name that might contain extensions in `[]`.
* @returns stirped down name of the package
*/
function getPackageName(pkg: string) {
return pkg.replace(/\[.*]/g, "").trim()
}
async function pipPackageIsInstalled(python: string, name: string) {
try {
const result = await execa(python, ["-m", "pip", "-qq", "show", name], {
stdio: "ignore",
reject: false,
})
return result.exitCode === 0
} catch {
return false
}
}
type PipxShowType = {
venvs: Record<string, {
metadata: {
main_package: {
package: string
package_or_url: string
apps: string[]
}
}
}>
}
async function pipxPackageInstalled(python: string, name: string) {
try {
const result = await execa(python, ["-m", "pipx", "list", "--json"], {
stdio: "ignore",
reject: false,
})
if (result.exitCode !== 0 || typeof result.stdout !== "string") {
return false
}
const pipxOut = JSON.parse(result.stdout) as PipxShowType
// search among the venvs
if (name in pipxOut.venvs) {
return true
}
// search among the urls
for (const venv of Object.values(pipxOut.venvs)) {
if (venv.metadata.main_package.package_or_url === name || venv.metadata.main_package.package === name) {
return true
}
}
} catch {
// ignore
}
return false
}
async function pipHasPackage(python: string, name: string) {
const result = await execa(python, ["-m", "pip", "-qq", "index", "versions", name], {

View File

@ -20,9 +20,11 @@
"skipLibCheck": true,
"allowImportingTsExtensions": true,
"noEmit": true,
// target Node.js 12 https://node.green/#ES2019
"lib": [
"ES2019"
// target Node.js 12 https://node.green/#ES2019
"ES2019",
// https://node.green/#ES2020-features-String-prototype-matchAll
"ES2020.String"
],
"target": "ESNext",
"allowJs": true,