setup-cpp/src/main.ts

410 lines
12 KiB
TypeScript
Raw Normal View History

#!/usr/bin/env node
/* eslint-disable node/shebang */
import { endGroup, getInput, notice, startGroup } from "@actions/core"
import { GITHUB_ACTIONS } from "ci-info"
import { error, info, success, warning } from "ci-log"
import mri from "mri"
import * as numerous from "numerous"
import numerousLocale from "numerous/locales/en.js"
import { join } from "patha"
import semverValid from "semver/functions/valid"
import * as timeDelta from "time-delta"
import timeDeltaLocale from "time-delta/locales/en.js"
import { untildifyUser } from "untildify-user"
import { setupBazel } from "./bazel/bazel"
2021-09-16 16:26:53 +08:00
import { setupBrew } from "./brew/brew"
2021-09-16 16:41:47 +08:00
import { setupCcache } from "./ccache/ccache"
2021-09-15 05:01:08 +08:00
import { setupChocolatey } from "./chocolatey/chocolatey"
import { setupCmake } from "./cmake/cmake"
2021-09-15 01:06:20 +08:00
import { setupConan } from "./conan/conan"
2021-09-16 16:47:47 +08:00
import { setupCppcheck } from "./cppcheck/cppcheck"
import { setupDoxygen } from "./doxygen/doxygen"
import { setupGcc } from "./gcc/gcc"
2022-11-09 09:01:31 +08:00
import { activateGcovGCC, activateGcovLLVM, setupGcovr } from "./gcovr/gcovr"
import { setupGraphviz } from "./graphviz/graphviz"
import { setupKcov } from "./kcov/kcov"
import { setupClangTools, setupLLVM } from "./llvm/llvm"
import { setupMake } from "./make/make"
2021-09-15 01:09:58 +08:00
import { setupMeson } from "./meson/meson"
2021-09-15 18:25:02 +08:00
import { setupMSVC } from "./msvc/msvc"
import { setupNala } from "./nala/nala"
import { setupNinja } from "./ninja/ninja"
2021-09-16 16:49:25 +08:00
import { setupOpencppcoverage } from "./opencppcoverage/opencppcoverage"
import { setupPowershell } from "./powershell/powershell"
import { setupPython } from "./python/python"
import { setupSccache } from "./sccache/sccache"
import { setupSevenZip } from "./sevenzip/sevenzip"
import { setupTask } from "./task/task"
import { addEnv, finalizeCpprc } from "./utils/env/addEnv"
import { isArch } from "./utils/env/isArch"
import { ubuntuVersion } from "./utils/env/ubuntu_version"
2021-09-18 01:51:59 +08:00
import { InstallationInfo } from "./utils/setup/setupBin"
import { setupPacmanPack } from "./utils/setup/setupPacmanPack"
2021-11-22 00:49:22 +08:00
import { setupVcpkg } from "./vcpkg/vcpkg"
2021-12-05 22:20:14 +08:00
import { setupVCVarsall } from "./vcvarsall/vcvarsall"
import { getVersion, syncVersions } from "./versions/versions"
2021-09-16 22:03:54 +08:00
2021-09-18 01:51:59 +08:00
/** The setup functions */
const setups = {
2022-07-28 08:36:18 +08:00
nala: setupNala,
cmake: setupCmake,
ninja: setupNinja,
python: setupPython,
2021-11-22 00:49:22 +08:00
vcpkg: setupVcpkg,
2022-07-28 10:32:33 +08:00
bazel: setupBazel,
conan: setupConan,
meson: setupMeson,
gcovr: setupGcovr,
opencppcoverage: setupOpencppcoverage,
llvm: setupLLVM,
gcc: setupGcc,
choco: setupChocolatey,
brew: setupBrew,
2022-08-08 06:20:34 +08:00
powershell: setupPowershell,
ccache: setupCcache,
2022-11-21 11:14:20 +08:00
sccache: setupSccache,
doxygen: setupDoxygen,
graphviz: setupGraphviz,
cppcheck: setupCppcheck,
clangtidy: setupClangTools,
clangformat: setupClangTools,
msvc: setupMSVC,
2021-12-05 22:20:14 +08:00
vcvarsall: setupVCVarsall,
2021-12-07 19:30:33 +08:00
kcov: setupKcov,
2022-01-31 06:40:11 +08:00
make: setupMake,
2022-01-31 07:33:48 +08:00
task: setupTask,
sevenzip: setupSevenZip,
}
2021-09-18 01:51:59 +08:00
/** The tools that can be installed */
const tools = Object.keys(setups) as Array<keyof typeof setups>
2021-09-18 01:51:59 +08:00
/** The possible inputs to the program */
export type Inputs = keyof typeof setups | "compiler" | "architecture"
2021-09-18 01:51:59 +08:00
// an array of possible inputs
const inputs: Array<Inputs> = ["compiler", "architecture", ...tools]
/** The main entry function */
export async function main(args: string[]): Promise<number> {
if (!GITHUB_ACTIONS) {
process.env.ACTIONS_ALLOW_UNSECURE_COMMANDS = "true"
}
2021-09-18 01:51:59 +08:00
// parse options using mri or github actions
const opts = parseArgs(args)
2021-09-14 15:03:59 +08:00
2021-09-18 02:09:00 +08:00
// print help
if (opts.help) {
printHelp()
}
2021-09-18 01:51:59 +08:00
// cpu architecture
const arch = opts.architecture ?? process.arch
// the installation dir for the tools that are downloaded directly
2022-08-08 11:04:59 +08:00
const setupCppDir = process.env.SETUP_CPP_DIR ?? untildifyUser("")
2021-09-18 01:51:59 +08:00
// report messages
const successMessages: string[] = []
const errorMessages: string[] = []
2022-04-18 20:27:28 +08:00
const timeFormatter = timeDelta.create({ autoloadLocales: true })
2022-04-18 21:16:11 +08:00
timeDelta.addLocale(timeDeltaLocale as timeDelta.Locale)
numerous.addLocale(numerousLocale as numerous.Locale)
let time1: number
let time2: number
2022-04-18 18:23:58 +08:00
// installing the specified tools
const osVersion = await ubuntuVersion()
// sync the version for the llvm tools
if (!syncVersions(opts, ["llvm", "clangtidy", "clangformat"])) {
error("The same version must be used for llvm, clangformat and clangtidy")
return 1
}
let hasLLVM = false // used to unset CPPFLAGS of LLVM when other compilers are used as the main compiler
if (isArch() && typeof opts.cppcheck === "string" && typeof opts.gcovr === "string") {
info("installing python-pygments to avoid conflicts with cppcheck and gcovr on Arch linux")
setupPacmanPack("python-pygments")
}
// loop over the tools and run their setup function
for (const tool of tools) {
// get the version or "true" or undefined for this tool from the options
const version = opts[tool]
// skip if undefined
if (version !== undefined) {
2021-11-22 04:20:51 +08:00
// running the setup function for this tool
time1 = Date.now()
startGroup(`Installing ${tool} ${version}`)
try {
2021-12-05 22:20:14 +08:00
let installationInfo: InstallationInfo | undefined | void
if (tool === "vcvarsall") {
2022-05-13 03:55:00 +08:00
// eslint-disable-next-line no-await-in-loop
await setupVCVarsall(
getVersion(tool, version, osVersion),
undefined,
arch,
undefined,
undefined,
false,
false
)
2021-12-05 22:20:14 +08:00
} else {
// get the setup function
const setupFunction = setups[tool]
hasLLVM = ["llvm", "clangformat", "clangtidy"].includes(tool)
// the tool installation directory (for the functions that ue it)
const setupDir = join(setupCppDir, hasLLVM ? "llvm" : tool)
2021-12-05 22:20:14 +08:00
// eslint-disable-next-line no-await-in-loop
installationInfo = await setupFunction(getVersion(tool, version, osVersion), setupDir, arch)
2021-12-05 22:20:14 +08:00
}
// preparing a report string
successMessages.push(getSuccessMessage(tool, installationInfo))
} catch (e) {
// push error message to the logger
2021-09-18 21:21:22 +08:00
error(e as string | Error)
errorMessages.push(`${tool} failed to install`)
}
endGroup()
time2 = Date.now()
info(`took ${timeFormatter.format(time1, time2) || "0 seconds"}`)
}
}
2021-09-18 01:51:59 +08:00
// installing the specified compiler
const maybeCompiler = opts.compiler
time1 = Date.now()
2021-09-14 15:03:59 +08:00
try {
2021-09-16 19:07:52 +08:00
if (maybeCompiler !== undefined) {
const { compiler, version } = getCompilerInfo(maybeCompiler)
2021-09-16 19:07:52 +08:00
2021-09-18 01:51:59 +08:00
// install the compiler. We allow some aliases for the compiler name
startGroup(`Installing ${compiler} ${version ?? ""}`)
2021-09-16 19:07:52 +08:00
switch (compiler) {
case "llvm":
case "clang":
case "clang++": {
const installationInfo = await setupLLVM(
getVersion("llvm", version, osVersion),
join(setupCppDir, "llvm"),
arch
)
2022-11-09 09:01:31 +08:00
await activateGcovLLVM()
successMessages.push(getSuccessMessage("llvm", installationInfo))
2021-09-16 19:07:52 +08:00
break
}
2021-09-16 22:03:54 +08:00
case "gcc":
case "mingw":
case "cygwin":
case "msys": {
2022-11-09 09:01:31 +08:00
const gccVersion = getVersion("gcc", version, osVersion)
const installationInfo = await setupGcc(gccVersion, join(setupCppDir, "gcc"), arch)
if (hasLLVM) {
2023-05-24 11:59:10 +08:00
// remove back the added CPPFLAGS of LLVM that include the LLVM headers
await addEnv("CPPFLAGS", "")
}
2022-11-09 09:01:31 +08:00
await activateGcovGCC(gccVersion)
successMessages.push(getSuccessMessage("gcc", installationInfo))
2021-09-16 22:03:54 +08:00
break
}
2021-09-16 19:07:52 +08:00
case "cl":
case "msvc":
case "msbuild":
case "vs":
case "visualstudio":
case "visualcpp":
case "visualc++": {
2022-05-13 03:55:00 +08:00
const installationInfo = await setupMSVC(
getVersion("msvc", version, osVersion),
join(setupCppDir, "msvc"),
arch
)
if (hasLLVM) {
// remove the CPPFLAGS of LLVM that include the LLVM headers
await addEnv("CPPFLAGS", "")
}
successMessages.push(getSuccessMessage("msvc", installationInfo))
2021-09-16 19:07:52 +08:00
break
}
2021-12-07 17:01:13 +08:00
case "appleclang":
case "applellvm": {
notice("Assuming apple-clang is already installed")
await Promise.all([addEnv("CC", "clang"), addEnv("CXX", "clang++")])
successMessages.push(getSuccessMessage("apple-clang", undefined))
2021-12-07 17:01:13 +08:00
break
}
2021-09-16 19:07:52 +08:00
default: {
2021-09-18 01:51:59 +08:00
errorMessages.push(`Unsupported compiler ${compiler}`)
2021-09-16 19:07:52 +08:00
}
}
endGroup()
time2 = Date.now()
info(`took ${timeFormatter.format(time1, time2) || "0 seconds"}`)
2021-09-16 19:07:52 +08:00
}
2021-09-18 01:51:59 +08:00
} catch (e) {
2021-09-18 21:21:22 +08:00
error(e as string | Error)
2021-09-18 01:51:59 +08:00
errorMessages.push(`Failed to install the ${maybeCompiler}`)
endGroup()
time2 = Date.now()
info(`took ${timeFormatter.format(time1, time2) || "0 seconds"}`)
2021-09-18 01:51:59 +08:00
}
await finalizeCpprc()
2022-11-21 15:14:33 +08:00
if (successMessages.length === 0 && errorMessages.length === 0) {
warning("setup-cpp was called without any arguments. Nothing to do.")
return 0
}
2021-09-18 01:51:59 +08:00
// report the messages in the end
2021-09-18 21:21:22 +08:00
successMessages.forEach((tool) => success(tool))
errorMessages.forEach((tool) => error(tool))
2021-09-18 01:51:59 +08:00
info("setup-cpp finished")
if (!GITHUB_ACTIONS) {
switch (process.platform) {
case "win32": {
2022-03-01 19:04:33 +08:00
warning("Run `RefreshEnv.cmd` or restart your shell to update the environment.")
break
}
case "linux":
case "darwin": {
2022-03-01 19:04:33 +08:00
warning("Run `source ~/.cpprc` or restart your shell to update the environment.")
break
}
default: {
// nothing
}
}
}
2021-09-18 01:51:59 +08:00
return errorMessages.length === 0 ? 0 : 1 // exit with non-zero if any error message
2021-09-14 15:03:59 +08:00
}
2021-09-18 01:51:59 +08:00
// Run main
main(process.argv)
2021-09-14 15:03:59 +08:00
.then((ret) => {
process.exitCode = ret
})
2021-09-18 21:21:22 +08:00
.catch((err) => {
error("main() panicked!")
error(err as string | Error)
2021-09-14 15:03:59 +08:00
process.exitCode = 1
})
2021-09-18 01:51:59 +08:00
export type Opts = mri.Argv<
Record<Inputs, string | undefined> & {
help: boolean
}
>
export function parseArgs(args: string[]): Opts {
return mri<Record<Inputs, string | undefined> & { help: boolean }>(args, {
string: inputs,
default: Object.fromEntries(inputs.map((inp) => [inp, maybeGetInput(inp)])),
alias: { h: "help" },
boolean: "help",
})
}
/** Detecting the compiler version. Divide the given string by `-` and use the second element as the version */
export function getCompilerInfo(maybeCompiler: string) {
const compilerAndMaybeVersion = maybeCompiler.split("-")
const compiler = compilerAndMaybeVersion[0]
if (1 in compilerAndMaybeVersion) {
const maybeVersion = compilerAndMaybeVersion[1]
if (semverValid(maybeVersion) !== null) {
return { compiler, version: maybeVersion }
} else {
info(`Invalid semver version ${maybeVersion} used for the compiler.`)
return { compiler, version: maybeVersion }
}
}
return { compiler, version: undefined }
}
2021-09-18 02:09:00 +08:00
function printHelp() {
info(`
setup-cpp [options]
setup-cpp --compiler llvm --cmake true --ninja true --ccache true --vcpkg true
2021-09-18 02:09:00 +08:00
Install all the tools required for building and testing C++/C projects.
--architecture\t the cpu architecture to install the tools for. By default it uses the current CPU architecture.
--compiler\t the <compiler> to install.
\t You can specify the version instead of specifying just the name e.g: --compiler 'llvm-13.0.0'
2021-09-18 02:09:00 +08:00
--tool_name\t pass "true" or pass the <version> you would like to install for this tool. e.g. --conan true or --conan "1.42.1"
2021-09-18 02:09:00 +08:00
All the available tools:
--llvm
--gcc
2021-12-05 22:20:14 +08:00
--vcvarsall
2021-09-18 02:09:00 +08:00
--cmake
--ninja
2021-11-22 00:49:22 +08:00
--vcpkg
2022-07-28 10:32:33 +08:00
--bazel
2021-09-18 02:09:00 +08:00
--meson
--conan
2022-01-31 06:40:11 +08:00
--make
2022-01-31 07:38:33 +08:00
--task
2021-09-18 02:09:00 +08:00
--ccache
2022-11-21 11:14:20 +08:00
--sccache
2021-09-18 02:09:00 +08:00
--cppcheck
--clangformat
--clangtidy
2021-09-18 02:09:00 +08:00
--doxygen
--gcovr
--opencppcoverage
2021-12-07 19:30:33 +08:00
--kcov
2021-09-18 02:09:00 +08:00
--python
--choco
--brew
2022-07-28 08:36:18 +08:00
--nala
--sevenzip
--graphviz
2022-11-21 11:14:20 +08:00
--powershell
2021-09-18 02:09:00 +08:00
`)
}
2021-09-18 01:51:59 +08:00
/** Get an object from github actions */
function maybeGetInput(key: string) {
const value = getInput(key.toLowerCase())
2021-09-18 01:51:59 +08:00
if (value !== "false" && value !== "") {
return value
}
return undefined // skip installation
}
function getSuccessMessage(tool: string, installationInfo: InstallationInfo | undefined | void) {
let msg = `${tool} was installed successfully:`
if (installationInfo === undefined) {
return msg
}
2021-09-18 01:51:59 +08:00
if ("installDir" in installationInfo) {
msg += `\n- The installation directory is ${installationInfo.installDir}`
2021-09-18 01:51:59 +08:00
}
if (installationInfo.binDir !== "") {
msg += `\n- The binary directory is ${installationInfo.binDir}`
2021-09-18 01:51:59 +08:00
}
2021-09-18 21:21:22 +08:00
return msg
2021-09-18 01:51:59 +08:00
}