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 | | category | tools |
| --------------------- | --------------------------------------------------------------------------- | | --------------------- | --------------------------------------------------------------------------- |
| compiler and analyzer | llvm, gcc, msvc, apple-clang, vcvarsall, cppcheck, clang-tidy, clang-format | | 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 | | package manager | vcpkg, conan, choco, brew, nala |
| cache | ccache, sccache | | cache | ccache, sccache |
| documentation | doxygen, graphviz | | documentation | doxygen, graphviz |

View File

@ -70,6 +70,21 @@ inputs:
cmake: cmake:
description: "Wether to install cmake (true/false) or the specific version to install." description: "Wether to install cmake (true/false) or the specific version to install."
required: false 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: ninja:
description: "Wether to install ninja (true/false) or the specific version to install." description: "Wether to install ninja (true/false) or the specific version to install."
required: false required: false

View File

@ -28,6 +28,7 @@ words:
- choco - choco
- clangd - clangd
- cmake - cmake
- cmakeformat
- cobertura - cobertura
- copr - copr
- CPATH - 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,6 +68,7 @@ const retryErrors = [
* ``` * ```
*/ */
export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> { export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
try {
const apt: string = getApt() const apt: string = getApt()
for (const { name, version } of packages) { for (const { name, version } of packages) {
@ -117,6 +118,10 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
} }
return { binDir: "/usr/bin/" } 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[]) { async function addRepositories(apt: string, packages: AptPackage[]) {

View File

@ -35,7 +35,9 @@ All the available tools:
"compiler and analyzer": { "compiler and analyzer": {
tools: "--llvm, --gcc, --msvc, --apple-clang, --vcvarsall, --cppcheck, --clang-tidy, --clang-format", 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" }, "package manager": { tools: "--vcpkg, --conan, --choco, --brew, --nala" },
cache: { tools: "--ccache, --sccache" }, cache: { tools: "--ccache, --sccache" },
documentation: { tools: "--doxygen, --graphviz" }, 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 { unique } from "../utils/std/index.js"
import { MinVersions } from "../versions/default_versions.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) const installInfo = await findOrSetupPython(version, setupDir, arch)
assert(installInfo.bin !== undefined) assert(installInfo.bin !== undefined)
const foundPython = installInfo.bin const foundPython = installInfo.bin
@ -41,7 +45,7 @@ export async function setupPython(version: string, setupDir: string, arch: strin
await setupWheel(foundPython) await setupWheel(foundPython)
return installInfo return installInfo as InstallationInfo & { bin: string }
} }
async function setupPipx(foundPython: string) { async function setupPipx(foundPython: string) {
@ -56,12 +60,20 @@ async function setupPipx(foundPython: string) {
} }
} }
await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" }) await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" })
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false }) await setupVenv(foundPython)
} catch (err) { } catch (err) {
warning(`Failed to install pipx: ${(err as Error).toString()}. Ignoring...`) 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 */ /** Setup wheel and setuptools */
async function setupWheel(foundPython: string) { async function setupWheel(foundPython: string) {
try { try {
@ -70,9 +82,9 @@ async function setupWheel(foundPython: string) {
isLibrary: true, isLibrary: true,
usePipx: false, 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) { } 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) { 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 // eslint-disable-next-line no-await-in-loop
const foundPython = await isPythonUpToDate(pythonBin, binDir) const foundPython = await isPythonUpToDate(pythonBin, binDir)
if (foundPython !== undefined) { if (foundPython !== undefined) {
@ -203,12 +215,10 @@ async function isPythonUpToDate(candidate: string, binDir?: string) {
try { try {
if (binDir !== undefined) { if (binDir !== undefined) {
const pythonBinPath = join(binDir, addExeExt(candidate)) const pythonBinPath = join(binDir, addExeExt(candidate))
if (await pathExists(pythonBinPath)) { if (await pathExists(pythonBinPath) && await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
if (await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
return pythonBinPath return pythonBinPath
} }
} }
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const pythonBinPaths = (await which(candidate, { nothrow: true, all: true })) ?? [] const pythonBinPaths = (await which(candidate, { nothrow: true, all: true })) ?? []
for (const pythonBinPath of pythonBinPaths) { for (const pythonBinPath of pythonBinPaths) {

View File

@ -3,6 +3,7 @@ import { setupBazel } from "./bazel/bazel.js"
import { setupCcache } from "./ccache/ccache.js" import { setupCcache } from "./ccache/ccache.js"
import { setupChocolatey } from "./chocolatey/chocolatey.js" import { setupChocolatey } from "./chocolatey/chocolatey.js"
import { setupCmake } from "./cmake/cmake.js" import { setupCmake } from "./cmake/cmake.js"
import { setupCmakelang } from "./cmakelang/cmakelang.js"
import { setupConan } from "./conan/conan.js" import { setupConan } from "./conan/conan.js"
import { setupCppcheck } from "./cppcheck/cppcheck.js" import { setupCppcheck } from "./cppcheck/cppcheck.js"
import { setupDoxygen } from "./doxygen/doxygen.js" import { setupDoxygen } from "./doxygen/doxygen.js"
@ -42,19 +43,32 @@ export const appleClangSetups = {
"apple-llvm": setupAppleClang, "apple-llvm": setupAppleClang,
} as const } 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"] export const llvmTools = ["llvm", "clang", "clang++", "clang-tidy", "clang-format", "clangtidy", "clangformat"]
/** The setup functions */ /** The setup functions */
export const setups = { export const setups = {
nala: setupNala,
brew: setupBrew,
choco: setupChocolatey,
python: setupPython,
powershell: setupPowershell,
pwsh: setupPowershell,
...llvmSetups, ...llvmSetups,
...gccSetups, ...gccSetups,
...mingwSetups, ...mingwSetups,
...msvcSetups, ...msvcSetups,
...appleClangSetups, ...appleClangSetups,
nala: setupNala, ...cmakeLangSetups,
cmake: setupCmake, cmake: setupCmake,
ninja: setupNinja, ninja: setupNinja,
python: setupPython,
vcpkg: setupVcpkg, vcpkg: setupVcpkg,
bazel: setupBazel, bazel: setupBazel,
conan: setupConan, conan: setupConan,
@ -62,10 +76,6 @@ export const setups = {
gcovr: setupGcovr, gcovr: setupGcovr,
opencppcoverage: setupOpencppcoverage, opencppcoverage: setupOpencppcoverage,
OpenCppCoverage: setupOpencppcoverage, OpenCppCoverage: setupOpencppcoverage,
choco: setupChocolatey,
brew: setupBrew,
powershell: setupPowershell,
pwsh: setupPowershell,
ccache: setupCcache, ccache: setupCcache,
sccache: setupSccache, sccache: setupSccache,
doxygen: setupDoxygen, 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 { usePipx = true, user = true, upgrade = false, isLibrary = false } = options
const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython)) const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython))
const pip = isPipx ? "pipx" : "pip" 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) { if (hasPackage) {
try { try {
info(`Installing ${name} ${version ?? ""} via ${pip}`) info(`Installing ${name} ${version ?? ""} via ${pip}`)
@ -74,27 +91,36 @@ export async function setupPipPackWithPython(
env, env,
}) })
} catch (err) { } 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) { if ((await setupPipPackSystem(name)) === null) {
throw new Error(`Failed to install ${name} via ${pip}: ${err}.`) throw new Error(`Failed to install ${name} via ${pip}: ${err}.`)
} }
} }
} else { } else if ((await setupPipPackSystem(name)) === null) {
if ((await setupPipPackSystem(name)) === null) {
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`) 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 } 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) { 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_() { async function getPipxHome_() {
@ -144,14 +170,77 @@ async function getPipxBinDir_() {
} }
const getPipxBinDir = memoize(getPipxBinDir_, { promise: true }) const getPipxBinDir = memoize(getPipxBinDir_, { promise: true })
async function getPython_(): Promise<string> { /* eslint-disable require-atomic-updates */
const pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin let pythonBin: string | undefined
if (pythonBin === undefined) {
throw new Error("Python binary was not found") async function getPython(): Promise<string> {
if (pythonBin !== undefined) {
return pythonBin
} }
pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin
return pythonBin 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) { async function pipHasPackage(python: string, name: string) {
const result = await execa(python, ["-m", "pip", "-qq", "index", "versions", name], { const result = await execa(python, ["-m", "pip", "-qq", "index", "versions", name], {

View File

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