feat: add llvm installer

This commit is contained in:
Amin Yahyaabadi 2021-09-14 07:02:59 -05:00
parent a5ae2b4863
commit 9e223dad3f
8 changed files with 351 additions and 7 deletions

View File

@ -12,7 +12,7 @@ license-checker --summary --production
``` ```
``` ```
├─ MIT: 4 ├─ MIT: 5
├─ ISC: 1 ├─ ISC: 1
└─ Apache-2.0: 1 └─ Apache-2.0: 1
``` ```

View File

@ -15,7 +15,7 @@ The package will be usable from any environment (locally, GitHub Actions, etc).
- [x] setup cmake - [x] setup cmake
- [x] setup ninja - [x] setup ninja
- [ ] setup llvm - [x] setup llvm
- [ ] setup gcc/mingw - [ ] setup gcc/mingw
- [ ] setup msvc - [ ] setup msvc
- [ ] setup conan - [ ] setup conan

View File

@ -3,12 +3,12 @@ description: "Install all the tools required for building and testing C++/C proj
author: "Amin Yahyaabadi" author: "Amin Yahyaabadi"
inputs: inputs:
compiler:
description: "The compiler and its version."
required: false
architecture: architecture:
description: "The CPU architecture" description: "The CPU architecture"
required: false required: false
llvm:
description: "The llvm version to install"
required: false
cmake: cmake:
description: "The cmake version to install." description: "The cmake version to install."
default: "3.20.2" default: "3.20.2"

View File

@ -32,11 +32,12 @@
}, },
"prettier": "prettier-config-atomic", "prettier": "prettier-config-atomic",
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.0",
"@actions/core": "^1.5.0", "@actions/core": "^1.5.0",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"@actions/tool-cache": "^1.7.1", "@actions/tool-cache": "^1.7.1",
"semver": "^7.3.5", "hasha": "^5.2.2",
"hasha": "^5.2.2" "semver": "^7.3.5"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.1", "@types/jest": "^27.0.1",

View File

@ -5,6 +5,7 @@ importers:
.: .:
specifiers: specifiers:
'@actions/core': ^1.5.0 '@actions/core': ^1.5.0
'@actions/exec': ^1.1.0
'@actions/io': ^1.1.1 '@actions/io': ^1.1.1
'@actions/tool-cache': ^1.7.1 '@actions/tool-cache': ^1.7.1
'@types/jest': ^27.0.1 '@types/jest': ^27.0.1
@ -24,6 +25,7 @@ importers:
typescript: ^4.4.3 typescript: ^4.4.3
dependencies: dependencies:
'@actions/core': 1.5.0 '@actions/core': 1.5.0
'@actions/exec': 1.1.0
'@actions/io': 1.1.1 '@actions/io': 1.1.1
'@actions/tool-cache': 1.7.1 '@actions/tool-cache': 1.7.1
hasha: 5.2.2 hasha: 5.2.2
@ -8112,6 +8114,7 @@ packages:
/type-fest/0.8.1: /type-fest/0.8.1:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: false
/typedarray-to-buffer/3.1.5: /typedarray-to-buffer/3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}

View File

@ -0,0 +1,31 @@
import { getSpecificVersionAndUrl } from "../llvm"
import * as https from "https"
jest.setTimeout(100000)
function testUrl(version: string) {
const [specificVersion, url] = getSpecificVersionAndUrl(process.platform, version)
const input = `Version: ${version} => ${specificVersion} \n URL: ${url}`
return new Promise<string>((resolve, reject) => {
https.get(url, (res) => {
const report = `${input}\nStatus: ${res.statusCode}\nContent-Length: ${res.headers["content-length"]}`
if (res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode <= 399) {
resolve(report)
} else {
reject(new Error(`Failed to download LLVM and Clang binaries.\n${input}\n${report}`))
}
})
})
}
describe("setup-llvm", () => {
it("Finds valid LLVM URLs", async () => {
await Promise.all(
["12.0.0", "12", "11", "11.0.0", "10", "10.0.0", "9.0.0", "8.0.0", "7.0.0", "6", "6.0.0", "5", "5.0.0", "4"].map(
(version) => testUrl(version)
)
)
})
})

303
src/llvm/llvm.ts Normal file
View File

@ -0,0 +1,303 @@
import * as core from "@actions/core"
import * as exec from "@actions/exec"
import * as io from "@actions/io"
import * as tc from "@actions/tool-cache"
import * as path from "path"
import semverLte from "semver/functions/lte"
//================================================
// Version
//================================================
/**
* Gets the specific and minimum LLVM versions that can be used to refer to the supplied specific LLVM versions (e.g.,
* `3`, `3.5`, `3.5.2` for `3.5.2`).
*/
function getVersions(specific: string[]): Set<string> {
const versions = new Set(specific)
for (const version of specific) {
versions.add(/^\d+/.exec(version)![0])
versions.add(/^\d+\.\d+/.exec(version)![0])
}
return versions
}
/** The specific and minimum LLVM versions supported by this action. */
const VERSIONS: Set<string> = getVersions([
"3.5.0",
"3.5.1",
"3.5.2",
"3.6.0",
"3.6.1",
"3.6.2",
"3.7.0",
"3.7.1",
"3.8.0",
"3.8.1",
"3.9.0",
"3.9.1",
"4.0.0",
"4.0.1",
"5.0.0",
"5.0.1",
"5.0.2",
"6.0.0",
"6.0.1",
"7.0.0",
"7.0.1",
"7.1.0",
"8.0.0",
"8.0.1",
"9.0.0",
"9.0.1",
"10.0.0",
"10.0.1",
"11.0.0",
"11.0.1",
"11.1.0",
"12.0.0",
"12.0.1",
])
/**
* Gets the specific LLVM versions supported by this action compatible with the supplied (specific or minimum) LLVM
* version in descending order of release (e.g., `5.0.2`, `5.0.1`, and `5.0.0` for `5`).
*/
function getSpecificVersions(version: string): string[] {
return Array.from(VERSIONS)
.filter((v) => /^\d+\.\d+\.\d+$/.test(v) && v.startsWith(version))
.sort()
.reverse()
}
//================================================
// URL
//================================================
/** Gets a LLVM download URL for GitHub. */
function getGitHubUrl(version: string, prefix: string, suffix: string): string {
const file = `${prefix}${version}${suffix}`
return `https://github.com/llvm/llvm-project/releases/download/llvmorg-${version}/${file}`
}
/** Gets a LLVM download URL for https://releases.llvm.org. */
function getReleaseUrl(version: string, prefix: string, suffix: string): string {
const file = `${prefix}${version}${suffix}`
return `https://releases.llvm.org/${version}/${file}`
}
/** The LLVM versions that were never released for the Darwin platform. */
const DARWIN_MISSING: Set<string> = new Set([
"3.5.1",
"3.6.1",
"3.6.2",
"3.7.1",
"3.8.1",
"3.9.1",
"6.0.1",
"7.0.1",
"7.1.0",
"8.0.1",
"11.0.1",
"11.1.0",
"12.0.1",
])
/** Gets an LLVM download URL for the Darwin platform. */
function getDarwinUrl(version: string): string | null {
if (DARWIN_MISSING.has(version)) {
return null
}
const darwin = version === "9.0.0" ? "-darwin-apple" : "-apple-darwin"
const prefix = "clang+llvm-"
const suffix = `-x86_64${darwin}.tar.xz`
if (semverLte(version, "9.0.1")) {
return getReleaseUrl(version, prefix, suffix)
} else {
return getGitHubUrl(version, prefix, suffix)
}
}
/**
* The LLVM versions that should use the last RC version instead of the release version for the Linux (Ubuntu) platform.
* This is useful when there were binaries released for the Linux (Ubuntu) platform for the last RC version but not for
* the actual release version.
*/
const UBUNTU_RC: Map<string, string> = new Map([["12.0.1", "12.0.1-rc4"]])
/** The (latest) Ubuntu versions for each LLVM version. */
const UBUNTU: { [key: string]: string } = {
"3.5.0": "-ubuntu-14.04",
"3.5.1": "",
"3.5.2": "-ubuntu-14.04",
"3.6.0": "-ubuntu-14.04",
"3.6.1": "-ubuntu-14.04",
"3.6.2": "-ubuntu-14.04",
"3.7.0": "-ubuntu-14.04",
"3.7.1": "-ubuntu-14.04",
"3.8.0": "-ubuntu-16.04",
"3.8.1": "-ubuntu-16.04",
"3.9.0": "-ubuntu-16.04",
"3.9.1": "-ubuntu-16.04",
"4.0.0": "-ubuntu-16.04",
"5.0.0": "-ubuntu16.04",
"5.0.1": "-ubuntu-16.04",
"5.0.2": "-ubuntu-16.04",
"6.0.0": "-ubuntu-16.04",
"6.0.1": "-ubuntu-16.04",
"7.0.0": "-ubuntu-16.04",
"7.0.1": "-ubuntu-18.04",
"7.1.0": "-ubuntu-14.04",
"8.0.0": "-ubuntu-18.04",
"9.0.0": "-ubuntu-18.04",
"9.0.1": "-ubuntu-16.04",
"10.0.0": "-ubuntu-18.04",
"10.0.1": "-ubuntu-16.04",
"11.0.0": "-ubuntu-20.04",
"11.0.1": "-ubuntu-16.04",
"11.1.0": "-ubuntu-16.04",
"12.0.0": "-ubuntu-20.04",
"12.0.1-rc4": "-ubuntu-21.04",
}
/** The latest supported LLVM version for the Linux (Ubuntu) platform. */
const MAX_UBUNTU: string = "12.0.1-rc4"
/** Gets an LLVM download URL for the Linux (Ubuntu) platform. */
function getLinuxUrl(versionGiven: string): string {
let version = versionGiven
const rc = UBUNTU_RC.get(version)
if (rc !== undefined) {
version = rc
}
let ubuntu: string
// ubuntu-version is specified
if (version.includes("ubuntu")) {
ubuntu = version
} else if (version !== "") {
ubuntu = UBUNTU[version]
} else {
// default to the maximum vresion
ubuntu = UBUNTU[MAX_UBUNTU]
}
const prefix = "clang+llvm-"
const suffix = `-x86_64-linux-gnu${ubuntu}.tar.xz`
if (semverLte(version, "9.0.1")) {
return getReleaseUrl(version, prefix, suffix)
} else {
return getGitHubUrl(version, prefix, suffix)
}
}
/** The LLVM versions that were never released for the Windows platform. */
const WIN32_MISSING: Set<string> = new Set(["10.0.1"])
/** Gets an LLVM download URL for the Windows platform. */
function getWin32Url(version: string): string | null {
if (WIN32_MISSING.has(version)) {
return null
}
const prefix = "LLVM-"
const suffix = semverLte(version, "3.7.0") ? "-win32.exe" : "-win64.exe"
if (semverLte(version, "9.0.1")) {
return getReleaseUrl(version, prefix, suffix)
} else {
return getGitHubUrl(version, prefix, suffix)
}
}
/** Gets an LLVM download URL. */
function getUrl(platform: string, version: string): string | null {
switch (platform) {
case "darwin":
return getDarwinUrl(version)
case "linux":
return getLinuxUrl(version)
case "win32":
return getWin32Url(version)
default:
return null
}
}
/** Gets the most recent specific LLVM version for which there is a valid download URL. */
export function getSpecificVersionAndUrl(platform: string, version: string): [string, string] {
if (!VERSIONS.has(version)) {
throw new Error(`Unsupported target! (platform='${platform}', version='${version}')`)
}
for (const specificVersion of getSpecificVersions(version)) {
const url = getUrl(platform, specificVersion)
if (url !== null) {
return [specificVersion, url]
}
}
throw new Error(`Unsupported target! (platform='${platform}', version='${version}')`)
}
//================================================
// Action
//================================================
const DEFAULT_NIX_DIRECTORY = "./llvm"
const DEFAULT_WIN32_DIRECTORY = "C:/Program Files/LLVM"
async function install(version: string, directory: string): Promise<void> {
const platform = process.platform
const [specificVersion, url] = getSpecificVersionAndUrl(platform, version)
core.setOutput("version", specificVersion)
core.info(`Installing LLVM and Clang ${version} (${specificVersion})...`)
core.info(`Downloading and extracting '${url}'...`)
const archive = await tc.downloadTool(url)
let exit
if (platform === "win32") {
exit = await exec.exec("7z", ["x", archive, `-o${directory}`])
} else {
await io.mkdirP(directory)
exit = await exec.exec("tar", ["xf", archive, "-C", directory, "--strip-components=1"])
}
if (exit !== 0) {
throw new Error("Could not extract LLVM and Clang binaries.")
}
core.info(`Installed LLVM and Clang ${version} (${specificVersion})!`)
core.info(`Install location: ${directory}`)
}
export async function setupLLVM(version: string, directoryGiven?: string, cached: boolean = false): Promise<void> {
let directory = directoryGiven
if (directory === "" || directory === undefined) {
directory = process.platform === "win32" ? DEFAULT_WIN32_DIRECTORY : DEFAULT_NIX_DIRECTORY
}
directory = path.resolve(directory)
if (cached) {
core.info(`Using cached LLVM and Clang ${version}...`)
} else {
await install(version, directory)
}
const bin = path.join(directory, "bin")
const lib = path.join(directory, "lib")
core.addPath(bin)
const ld = process.env.LD_LIBRARY_PATH ?? ""
const dyld = process.env.DYLD_LIBRARY_PATH ?? ""
core.exportVariable("LLVM_PATH", directory)
core.exportVariable("LD_LIBRARY_PATH", `${lib}${path.delimiter}${ld}`)
core.exportVariable("DYLD_LIBRARY_PATH", `${lib}${path.delimiter}${dyld}`)
}

View File

@ -25,6 +25,12 @@ export async function main(): Promise<number> {
if (ninjaVersion !== undefined) { if (ninjaVersion !== undefined) {
await setupNinja(ninjaVersion, setupCppDir) await setupNinja(ninjaVersion, setupCppDir)
} }
// setup llvm
const llvmVersion = maybeGetInput("llvm")
if (llvmVersion !== undefined) {
await setupLLVM(llvmVersion, setupCppDir)
}
} catch (err) { } catch (err) {
core.error(err as string | Error) core.error(err as string | Error)
core.setFailed("install-cpp failed") core.setFailed("install-cpp failed")