Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add timeout for installation of the tools + feat: fail fast inside CI when an error happens #188

Merged
merged 6 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ inputs:
powershell:
description: "The powershell version to install."
required: false
timeout:
description: "The timeout for installation of one tool (in minutes)."
default: "20"
required: false

runs:
using: "node16"
Expand Down
7 changes: 7 additions & 0 deletions dist/actions/actions_python.0e245fb6.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions dist/actions/actions_python.dac7d648.js

This file was deleted.

36 changes: 19 additions & 17 deletions dist/actions/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/actions/setup-cpp.js.map

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions dist/legacy/actions_python.30df6221.js

This file was deleted.

7 changes: 7 additions & 0 deletions dist/legacy/actions_python.cd609164.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

36 changes: 19 additions & 17 deletions dist/legacy/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/legacy/setup-cpp.js.map

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions dist/modern/actions_python.7a34735b.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions dist/modern/actions_python.d4cc9106.js

This file was deleted.

36 changes: 19 additions & 17 deletions dist/modern/setup-cpp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/modern/setup-cpp.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"npm-check-updates": "^16.10.17",
"npm-run-all2": "^6.0.6",
"numerous": "1.0.3",
"p-timeout": "^6.1.2",
"parcel": "2.9.3",
"path-exists": "^5.0.0",
"patha": "^0.4.1",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { syncVersions, getVersion } from "../versions/versions"
import { parseArgs } from "../cli-options"
import { Inputs } from "../tool"
import { getCompilerInfo } from "../compilers"
import { Inputs } from "../tool"

jest.setTimeout(300000)
describe("getCompilerInfo", () => {
Expand Down
5 changes: 3 additions & 2 deletions src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Inputs, inputs } from "./tool"

export function parseArgs(args: string[]): Opts {
return mri<Record<Inputs, string | undefined> & { help: boolean }>(args, {
string: inputs,
string: [...inputs, "timeout"],
default: Object.fromEntries(inputs.map((inp) => [inp, maybeGetInput(inp)])),
alias: { h: "help" },
boolean: "help",
Expand All @@ -21,9 +21,9 @@ setup-cpp --compiler llvm --cmake true --ninja true --ccache true --vcpkg true
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.
--timeout\t the timeout for the installation of each tool in minutes. By default it is 10 minutes.
--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'

--$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"

All the available tools:
Expand Down Expand Up @@ -54,6 +54,7 @@ export function maybeGetInput(key: string) {
export type Opts = mri.Argv<
Record<Inputs, string | undefined> & {
help: boolean
timeout?: string
}
>

Expand Down
68 changes: 68 additions & 0 deletions src/installTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { endGroup, startGroup } from "@actions/core"
import { error } from "ci-log"
import { join } from "patha"
import { getSuccessMessage } from "./cli-options"
import { InstallationInfo } from "./utils/setup/setupBin"
import { setupVCVarsall } from "./vcvarsall/vcvarsall"
import { getVersion } from "./versions/versions"
import pTimeout from "p-timeout"
import { ToolName, setups } from "./tool"

export const DEFAULT_TIMEOUT = 20 * 60 * 1000 // 20 minutes

export async function installTool(
tool: ToolName,
version: string,
osVersion: number[] | null,
arch: string,
setupCppDir: string,
successMessages: string[],
errorMessages: string[],
timeout: number = DEFAULT_TIMEOUT,
) {
startGroup(`Installing ${tool} ${version}`)
let hasLLVM = false
try {
hasLLVM = await pTimeout(installToolImpl(tool, version, osVersion, arch, hasLLVM, setupCppDir, successMessages), {
milliseconds: timeout,
message: `Timeout while installing ${tool} ${version}. You can increase the timeout from options`,
})
} catch (e) {
// push error message to the logger
error(e as string | Error)
errorMessages.push(`${tool} failed to install`)
}
endGroup()
return hasLLVM
}

async function installToolImpl(
tool: ToolName,
version: string,
osVersion: number[] | null,
arch: string,
hasLLVM: boolean,
setupCppDir: string,
successMessages: string[],
) {
let installationInfo: InstallationInfo | undefined | void
if (tool === "vcvarsall") {
// eslint-disable-next-line no-await-in-loop
await setupVCVarsall(getVersion(tool, version, osVersion), undefined, arch, undefined, undefined, false, false)
} else {
// get the setup function
const setupFunction = setups[tool]

// eslint-disable-next-line no-param-reassign
hasLLVM = ["llvm", "clangformat", "clangtidy"].includes(tool)

// the tool installation directory (for the functions that ue it)
const setupDir = join(setupCppDir, hasLLVM ? "llvm" : tool)

// eslint-disable-next-line no-await-in-loop
installationInfo = await setupFunction(getVersion(tool, version, osVersion), setupDir, arch)
}
// preparing a report string
successMessages.push(getSuccessMessage(tool, installationInfo))
return hasLLVM
}
4 changes: 3 additions & 1 deletion src/llvm/llvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ async function setupLLVMWithoutActivation_raw(version: string, setupDir: string,
const setupLLVMWithoutActivation = memoize(setupLLVMWithoutActivation_raw, { isPromise: true })

/** Setup llvm tools (clang tidy, clang format, etc) without activating llvm and using it as the compiler */
export const setupClangTools = setupLLVMWithoutActivation
export function setupClangTools(version: string, setupDir: string, arch: string) {
return setupLLVMOnly(version, setupDir, arch)
}

async function setupLLVMOnly(version: string, setupDir: string, arch: string) {
const coeredVersion = semverCoerceIfInvalid(version)
Expand Down
15 changes: 13 additions & 2 deletions src/llvm/llvm_installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { hasNala, isPackageInstalled, setupAptPack } from "../utils/setup/setupA
import { InstallationInfo } from "../utils/setup/setupBin"
import { promises } from "fs"
import { info } from "console"
import { DEFAULT_TIMEOUT } from "../installTool"
const { readFile, writeFile, chmod } = promises

export async function setupLLVMApt(majorVersion: number): Promise<InstallationInfo> {
Expand All @@ -19,6 +20,7 @@ export async function setupLLVMApt(majorVersion: number): Promise<InstallationIn
await execRoot("bash", ["/tmp/llvm-setup-cpp.sh", `${majorVersion}`, "all"], {
stdio: "inherit",
shell: true,
timeout: DEFAULT_TIMEOUT,
})

await addPath(`${installationFolder}/bin`)
Expand All @@ -33,6 +35,7 @@ export async function setupLLVMApt(majorVersion: number): Promise<InstallationIn
async function patchAptLLVMScript(path: string, target_path: string) {
let script = await readFile(path, "utf-8")

script = debugScript(script)
script = nonInteractiveScript(script)
script = await removeConflictingPAckages(script)
script = useNalaScript(script)
Expand All @@ -42,9 +45,17 @@ async function patchAptLLVMScript(path: string, target_path: string) {
// the packages needed by the script
return [{ name: "lsb-release" }, { name: "wget" }, { name: "software-properties-common" }, { name: "gnupg" }]
}
function nonInteractiveScript(givenScript: string) {

function debugScript(script: string) {
if (!process.env.NODE_DEBUG) {
return script.replace(/set -eux/g, "set -eu")
}
return script
}

function nonInteractiveScript(script: string) {
// make the scirpt non-interactive and fix broken packages
return givenScript.replace(
return script.replace(
/add-apt-repository "\${REPO_NAME}"/g,
// eslint-disable-next-line no-template-curly-in-string
'add-apt-repository -y "${REPO_NAME}"',
Expand Down
42 changes: 32 additions & 10 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */

import { GITHUB_ACTIONS } from "ci-info"
import { GITHUB_ACTIONS, isCI } from "ci-info"
import { error, info, success, warning } from "ci-log"
import * as numerous from "numerous"
import numerousLocale from "numerous/locales/en.js"
Expand All @@ -11,12 +11,13 @@ import { untildifyUser } from "untildify-user"
import { checkUpdates } from "./check-updates"
import { parseArgs, printHelp } from "./cli-options"
import { installCompiler } from "./compilers"
import { installTool, tools } from "./tool"
import { finalizeCpprc } from "./utils/env/addEnv"
import { isArch } from "./utils/env/isArch"
import { ubuntuVersion } from "./utils/env/ubuntu_version"
import { setupPacmanPack } from "./utils/setup/setupPacmanPack"
import { syncVersions } from "./versions/versions"
import { installTool } from "./installTool"
import { tools } from "./tool"

/** The main entry function */
async function main(args: string[]): Promise<number> {
Expand Down Expand Up @@ -69,7 +70,17 @@ async function main(args: string[]): Promise<number> {
let hasLLVM = false

// loop over the tools and run their setup function

let failedFast = false
for (const tool of tools) {
// fail fast inside CI when any tool fails
if (isCI) {
if (errorMessages.length !== 0) {
failedFast = true
break
}
}

// get the version or "true" or undefined for this tool from the options
const version = opts[tool]

Expand All @@ -78,19 +89,30 @@ async function main(args: string[]): Promise<number> {
// running the setup function for this tool
time1 = Date.now()
// eslint-disable-next-line no-await-in-loop
hasLLVM = await installTool(tool, version, osVersion, arch, setupCppDir, successMessages, errorMessages)
hasLLVM = await installTool(
tool,
version,
osVersion,
arch,
setupCppDir,
successMessages,
errorMessages,
parseFloat(opts.timeout ?? "20") * 60 * 1000,
)
time2 = Date.now()
info(`took ${timeFormatter.format(time1, time2) || "0 seconds"}`)
}
}

// installing the specified compiler
const maybeCompiler = opts.compiler
if (maybeCompiler !== undefined) {
const time1Compiler = Date.now()
await installCompiler(maybeCompiler, osVersion, setupCppDir, arch, successMessages, hasLLVM, errorMessages)
const time2Compiler = Date.now()
info(`took ${timeFormatter.format(time1Compiler, time2Compiler) || "0 seconds"}`)
if (!failedFast) {
// installing the specified compiler
const maybeCompiler = opts.compiler
if (maybeCompiler !== undefined) {
const time1Compiler = Date.now()
await installCompiler(maybeCompiler, osVersion, setupCppDir, arch, successMessages, hasLLVM, errorMessages)
const time2Compiler = Date.now()
info(`took ${timeFormatter.format(time1Compiler, time2Compiler) || "0 seconds"}`)
}
}

await finalizeCpprc()
Expand Down
54 changes: 5 additions & 49 deletions src/tool.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { endGroup, startGroup } from "@actions/core"
import { error } from "ci-log"
import { join } from "patha"
import { setupBazel } from "./bazel/bazel"
import { setupBrew } from "./brew/brew"
import { setupCcache } from "./ccache/ccache"
import { setupChocolatey } from "./chocolatey/chocolatey"
import { getSuccessMessage } from "./cli-options"
import { setupCmake } from "./cmake/cmake"
import { setupConan } from "./conan/conan"
import { setupCppcheck } from "./cppcheck/cppcheck"
Expand All @@ -26,50 +22,10 @@ import { setupPython } from "./python/python"
import { setupSccache } from "./sccache/sccache"
import { setupSevenZip } from "./sevenzip/sevenzip"
import { setupTask } from "./task/task"
import { InstallationInfo } from "./utils/setup/setupBin"
import { setupVcpkg } from "./vcpkg/vcpkg"
import { setupVCVarsall } from "./vcvarsall/vcvarsall"
import { getVersion } from "./versions/versions"

export async function installTool(
tool: ToolName,
version: string,
osVersion: number[] | null,
arch: string,
setupCppDir: string,
successMessages: string[],
errorMessages: string[],
) {
startGroup(`Installing ${tool} ${version}`)
let hasLLVM = false
try {
let installationInfo: InstallationInfo | undefined | void
if (tool === "vcvarsall") {
// eslint-disable-next-line no-await-in-loop
await setupVCVarsall(getVersion(tool, version, osVersion), undefined, arch, undefined, undefined, false, false)
} 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)

// eslint-disable-next-line no-await-in-loop
installationInfo = await setupFunction(getVersion(tool, version, osVersion), setupDir, arch)
}
// preparing a report string
successMessages.push(getSuccessMessage(tool, installationInfo))
} catch (e) {
// push error message to the logger
error(e as string | Error)
errorMessages.push(`${tool} failed to install`)
}
endGroup()
return hasLLVM
} /** The setup functions */

/** The setup functions */
export const setups = {
nala: setupNala,
cmake: setupCmake,
Expand Down Expand Up @@ -102,12 +58,12 @@ export const setups = {
}

export type ToolName = keyof typeof setups
/** The tools that can be installed */

/** The tools that can be installed */
export const tools = Object.keys(setups) as Array<ToolName>

/** The possible inputs to the program */
export type Inputs = keyof typeof setups | "compiler" | "architecture" | "timeout"

export type Inputs = keyof typeof setups | "compiler" | "architecture"
/** ‌ an array of possible inputs */

export const inputs: Array<Inputs> = ["compiler", "architecture", ...tools]
export const inputs: Array<Inputs> = ["compiler", "architecture", "timeout", ...tools]
Loading