Skip to content

Commit 65cf4fe

Browse files
authored
Merge pull request #367 from aminya/macos-llvm-installer
feat: install LLVM via brew on Mac if possible
2 parents c99abc9 + 4532f2e commit 65cf4fe

File tree

10 files changed

+152
-25
lines changed

10 files changed

+152
-25
lines changed

dist/legacy/setup-cpp.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/legacy/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/modern/setup-cpp.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/modern/setup-cpp.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/setup-brew/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "setup-brew",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"description": "Setup brew and brew packages",
55
"repository": "https://github.com/aminya/setup-cpp",
66
"homepage": "https://github.com/aminya/setup-cpp/tree/master/packages/setup-brew",

packages/setup-brew/src/install-pack.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { join } from "path"
2-
import { info } from "ci-log"
2+
import { info, warning } from "ci-log"
33
import { execaSync } from "execa"
44
import which from "which"
55
import type { InstallationInfo } from "./InstallationInfo.js"
66
import type { BrewPackOptions } from "./install-pack-options.js"
7-
import { getBrewBinDir, setupBrew } from "./install.js"
7+
import { getBrewBinDir, getBrewBinPath, setupBrew } from "./install.js"
8+
import { brewPackInstallDir, brewPackNameAndVersion } from "./pack-install-dir.js"
89

910
/* eslint-disable require-atomic-updates */
1011
let hasBrew = false
@@ -36,14 +37,13 @@ export async function installBrewPack(
3637
hasBrew = true
3738
}
3839

39-
const binDir = getBrewBinDir()
40-
const brewPath = join(binDir, "brew")
40+
const brewPath = getBrewBinPath()
4141

42-
// Args
4342
const args = [
4443
"install",
45-
(version !== undefined && version !== "") ? `${name}@${version}` : name,
44+
brewPackNameAndVersion(name, version),
4645
]
46+
4747
// Add options to args
4848
for (const [key, value] of Object.entries(options)) {
4949
if (typeof value === "boolean" && value) {
@@ -56,5 +56,12 @@ export async function installBrewPack(
5656
// brew is not thread-safe
5757
execaSync(brewPath, args, { stdio: "inherit" })
5858

59-
return { binDir }
59+
const installDir = await brewPackInstallDir(name, version)
60+
61+
if (installDir === undefined) {
62+
warning(`Failed to find installation directory for ${name} ${version}`)
63+
return { binDir: getBrewBinDir(), installDir: undefined }
64+
}
65+
66+
return { installDir, binDir: join(installDir, "bin") }
6067
}

packages/setup-brew/src/install.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ export function getBrewBinDir() {
7474
return join(getBrewDir(), "bin")
7575
}
7676

77+
export function getBrewBinPath() {
78+
return join(getBrewBinDir(), "brew")
79+
}
80+
7781
/**
7882
* Get the path where brew is installed
7983
* @returns {string} The path where brew is installed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { existsSync } from "fs"
2+
import { join } from "path"
3+
import { execa } from "execa"
4+
import { getBrewBinPath, getBrewDir } from "./install.js"
5+
6+
export function brewPackNameAndVersion(name: string, version: string | undefined) {
7+
return (version !== undefined && version !== "") ? `${name}@${version}` : name
8+
}
9+
10+
/**
11+
* Get the installation directory of a package
12+
* @param name The name of the package
13+
* @param nameAndVersion The name and version of the package
14+
* @returns The installation directory of the package
15+
*/
16+
17+
export async function brewPackInstallDir(name: string, version: string | undefined) {
18+
const nameAndVersion = brewPackNameAndVersion(name, version)
19+
20+
// first try with --prefix
21+
const nameAndVersionPrefix = await getBrewPackPrefix(nameAndVersion)
22+
if (nameAndVersionPrefix !== undefined) {
23+
return nameAndVersionPrefix
24+
}
25+
26+
// try with --prefix name
27+
const namePrefix = await getBrewPackPrefix(name)
28+
if (namePrefix !== undefined) {
29+
return namePrefix
30+
}
31+
32+
// if that fails, try with searchInstallDir
33+
return searchInstallDir(name, nameAndVersion)
34+
}
35+
36+
async function getBrewPackPrefix(packArg: string) {
37+
try {
38+
const brewPath = getBrewBinPath()
39+
return (await execa(brewPath, ["--prefix", packArg], { stdio: "pipe" })).stdout
40+
} catch {
41+
return undefined
42+
}
43+
}
44+
/**
45+
* Searches for the installation directory of a package
46+
* @param name The name of the package
47+
* @param nameAndVersion The name and version of the package
48+
* @returns The installation directory of the package
49+
*/
50+
function searchInstallDir(name: string, nameAndVersion: string) {
51+
const brewDir = getBrewDir()
52+
53+
// Check in opt directory (most common location)
54+
const nameAndVersionOptDir = join(brewDir, "opt", nameAndVersion)
55+
if (existsSync(nameAndVersionOptDir)) {
56+
return nameAndVersionOptDir
57+
}
58+
const nameOptDir = join(brewDir, "opt", name)
59+
if (existsSync(nameOptDir)) {
60+
return nameOptDir
61+
}
62+
63+
// Check in Cellar (where casks and some formulae are installed)
64+
const nameAndVersionCellarDir = join(brewDir, "Cellar", nameAndVersion)
65+
if (existsSync(nameAndVersionCellarDir)) {
66+
return nameAndVersionCellarDir
67+
}
68+
const nameCellarDir = join(brewDir, "Cellar", name)
69+
if (existsSync(nameCellarDir)) {
70+
return nameCellarDir
71+
}
72+
73+
// Check in lib directory
74+
const nameAndVersionLibDir = join(brewDir, "lib", nameAndVersion)
75+
if (existsSync(nameAndVersionLibDir)) {
76+
return nameAndVersionLibDir
77+
}
78+
const nameLibDir = join(brewDir, "lib", name)
79+
if (existsSync(nameLibDir)) {
80+
return nameLibDir
81+
}
82+
83+
return undefined
84+
}

src/llvm/llvm.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,26 @@ import { quoteIfHasSpace } from "../utils/std/index.js"
1818
import { getVersion } from "../versions/versions.js"
1919
import { LLVMPackages, trySetupLLVMApt } from "./llvm_apt_installer.js"
2020
import { setupLLVMBin } from "./llvm_bin.js"
21+
import { trySetupLLVMBrew } from "./llvm_brew_installer.js"
2122
import { majorLLVMVersion } from "./utils.js"
2223

2324
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
2425

2526
export async function setupLLVM(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
26-
const installationInfo = await setupLLVMWithoutActivation(version, setupDir, arch)
27-
await activateLLVM(installationInfo.installDir ?? setupDir, version)
28-
return installationInfo
29-
}
27+
const installationInfo = await setupLLVMOnly(version, setupDir, arch)
3028

31-
async function setupLLVMWithoutActivation_(version: string, setupDir: string, arch: string) {
32-
// install LLVM
33-
const [installationInfo, _1] = await Promise.all([
34-
setupLLVMOnly(version, setupDir, arch),
35-
addLLVMLoggingMatcher(),
36-
])
37-
38-
// install LLVM dependencies
29+
// install gcc for LLVM (for ld, libstdc++, etc.)
3930
await setupGccForLLVM(arch)
4031

32+
// add the logging matcher
33+
await addLLVMLoggingMatcher()
34+
35+
// activate LLVM in the end
36+
if (installationInfo.installDir !== undefined) {
37+
await activateLLVM(installationInfo.installDir, version)
38+
}
4139
return installationInfo
4240
}
43-
const setupLLVMWithoutActivation = memoize(setupLLVMWithoutActivation_, { promise: true })
4441

4542
async function setupLLVMOnly(
4643
version: string,
@@ -53,6 +50,11 @@ async function setupLLVMOnly(
5350
return aptInstallInfo
5451
}
5552

53+
const brewInstallInfo = await trySetupLLVMBrew(version, setupDir, arch)
54+
if (brewInstallInfo !== undefined) {
55+
return brewInstallInfo
56+
}
57+
5658
return setupLLVMBin(version, setupDir, arch)
5759
}
5860

src/llvm/llvm_brew_installer.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { warning } from "ci-log"
2+
import { addPath } from "envosman"
3+
import { installBrewPack } from "setup-brew"
4+
import { rcOptions } from "../cli-options.ts"
5+
import { majorLLVMVersion } from "./utils.ts"
6+
7+
export async function trySetupLLVMBrew(version: string, _setupDir: string, _arch: string) {
8+
if (process.platform !== "darwin") {
9+
return Promise.resolve(undefined)
10+
}
11+
12+
try {
13+
return await setupLLVMBrew(version, _setupDir, _arch)
14+
} catch (err) {
15+
warning(`Failed to install llvm via brew: ${err}`)
16+
return undefined
17+
}
18+
}
19+
20+
export async function setupLLVMBrew(version: string, _setupDir: string, _arch: string) {
21+
const majorVersion = majorLLVMVersion(version)
22+
23+
// install llvm via brew if a bottle is available for it
24+
const installInfo = await installBrewPack("llvm", `${majorVersion}`, { "force-bottle": true })
25+
26+
// add the bin directory to the PATH
27+
await addPath(installInfo.binDir, rcOptions)
28+
29+
return installInfo
30+
}

0 commit comments

Comments
 (0)