Merge pull request #367 from aminya/macos-llvm-installer

feat: install LLVM via brew on Mac if possible
This commit is contained in:
Amin Ya 2025-03-10 00:37:44 -07:00 committed by GitHub
commit 65cf4fec22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 152 additions and 25 deletions

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

@ -1,6 +1,6 @@
{
"name": "setup-brew",
"version": "1.0.2",
"version": "1.1.0",
"description": "Setup brew and brew packages",
"repository": "https://github.com/aminya/setup-cpp",
"homepage": "https://github.com/aminya/setup-cpp/tree/master/packages/setup-brew",

View File

@ -1,10 +1,11 @@
import { join } from "path"
import { info } from "ci-log"
import { info, warning } from "ci-log"
import { execaSync } from "execa"
import which from "which"
import type { InstallationInfo } from "./InstallationInfo.js"
import type { BrewPackOptions } from "./install-pack-options.js"
import { getBrewBinDir, setupBrew } from "./install.js"
import { getBrewBinDir, getBrewBinPath, setupBrew } from "./install.js"
import { brewPackInstallDir, brewPackNameAndVersion } from "./pack-install-dir.js"
/* eslint-disable require-atomic-updates */
let hasBrew = false
@ -36,14 +37,13 @@ export async function installBrewPack(
hasBrew = true
}
const binDir = getBrewBinDir()
const brewPath = join(binDir, "brew")
const brewPath = getBrewBinPath()
// Args
const args = [
"install",
(version !== undefined && version !== "") ? `${name}@${version}` : name,
brewPackNameAndVersion(name, version),
]
// Add options to args
for (const [key, value] of Object.entries(options)) {
if (typeof value === "boolean" && value) {
@ -56,5 +56,12 @@ export async function installBrewPack(
// brew is not thread-safe
execaSync(brewPath, args, { stdio: "inherit" })
return { binDir }
const installDir = await brewPackInstallDir(name, version)
if (installDir === undefined) {
warning(`Failed to find installation directory for ${name} ${version}`)
return { binDir: getBrewBinDir(), installDir: undefined }
}
return { installDir, binDir: join(installDir, "bin") }
}

View File

@ -74,6 +74,10 @@ export function getBrewBinDir() {
return join(getBrewDir(), "bin")
}
export function getBrewBinPath() {
return join(getBrewBinDir(), "brew")
}
/**
* Get the path where brew is installed
* @returns {string} The path where brew is installed

View File

@ -0,0 +1,84 @@
import { existsSync } from "fs"
import { join } from "path"
import { execa } from "execa"
import { getBrewBinPath, getBrewDir } from "./install.js"
export function brewPackNameAndVersion(name: string, version: string | undefined) {
return (version !== undefined && version !== "") ? `${name}@${version}` : name
}
/**
* Get the installation directory of a package
* @param name The name of the package
* @param nameAndVersion The name and version of the package
* @returns The installation directory of the package
*/
export async function brewPackInstallDir(name: string, version: string | undefined) {
const nameAndVersion = brewPackNameAndVersion(name, version)
// first try with --prefix
const nameAndVersionPrefix = await getBrewPackPrefix(nameAndVersion)
if (nameAndVersionPrefix !== undefined) {
return nameAndVersionPrefix
}
// try with --prefix name
const namePrefix = await getBrewPackPrefix(name)
if (namePrefix !== undefined) {
return namePrefix
}
// if that fails, try with searchInstallDir
return searchInstallDir(name, nameAndVersion)
}
async function getBrewPackPrefix(packArg: string) {
try {
const brewPath = getBrewBinPath()
return (await execa(brewPath, ["--prefix", packArg], { stdio: "pipe" })).stdout
} catch {
return undefined
}
}
/**
* Searches for the installation directory of a package
* @param name The name of the package
* @param nameAndVersion The name and version of the package
* @returns The installation directory of the package
*/
function searchInstallDir(name: string, nameAndVersion: string) {
const brewDir = getBrewDir()
// Check in opt directory (most common location)
const nameAndVersionOptDir = join(brewDir, "opt", nameAndVersion)
if (existsSync(nameAndVersionOptDir)) {
return nameAndVersionOptDir
}
const nameOptDir = join(brewDir, "opt", name)
if (existsSync(nameOptDir)) {
return nameOptDir
}
// Check in Cellar (where casks and some formulae are installed)
const nameAndVersionCellarDir = join(brewDir, "Cellar", nameAndVersion)
if (existsSync(nameAndVersionCellarDir)) {
return nameAndVersionCellarDir
}
const nameCellarDir = join(brewDir, "Cellar", name)
if (existsSync(nameCellarDir)) {
return nameCellarDir
}
// Check in lib directory
const nameAndVersionLibDir = join(brewDir, "lib", nameAndVersion)
if (existsSync(nameAndVersionLibDir)) {
return nameAndVersionLibDir
}
const nameLibDir = join(brewDir, "lib", name)
if (existsSync(nameLibDir)) {
return nameLibDir
}
return undefined
}

View File

@ -18,29 +18,26 @@ import { quoteIfHasSpace } from "../utils/std/index.js"
import { getVersion } from "../versions/versions.js"
import { LLVMPackages, trySetupLLVMApt } from "./llvm_apt_installer.js"
import { setupLLVMBin } from "./llvm_bin.js"
import { trySetupLLVMBrew } from "./llvm_brew_installer.js"
import { majorLLVMVersion } from "./utils.js"
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url))
export async function setupLLVM(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
const installationInfo = await setupLLVMWithoutActivation(version, setupDir, arch)
await activateLLVM(installationInfo.installDir ?? setupDir, version)
return installationInfo
}
const installationInfo = await setupLLVMOnly(version, setupDir, arch)
async function setupLLVMWithoutActivation_(version: string, setupDir: string, arch: string) {
// install LLVM
const [installationInfo, _1] = await Promise.all([
setupLLVMOnly(version, setupDir, arch),
addLLVMLoggingMatcher(),
])
// install LLVM dependencies
// install gcc for LLVM (for ld, libstdc++, etc.)
await setupGccForLLVM(arch)
// add the logging matcher
await addLLVMLoggingMatcher()
// activate LLVM in the end
if (installationInfo.installDir !== undefined) {
await activateLLVM(installationInfo.installDir, version)
}
return installationInfo
}
const setupLLVMWithoutActivation = memoize(setupLLVMWithoutActivation_, { promise: true })
async function setupLLVMOnly(
version: string,
@ -53,6 +50,11 @@ async function setupLLVMOnly(
return aptInstallInfo
}
const brewInstallInfo = await trySetupLLVMBrew(version, setupDir, arch)
if (brewInstallInfo !== undefined) {
return brewInstallInfo
}
return setupLLVMBin(version, setupDir, arch)
}

View File

@ -0,0 +1,30 @@
import { warning } from "ci-log"
import { addPath } from "envosman"
import { installBrewPack } from "setup-brew"
import { rcOptions } from "../cli-options.ts"
import { majorLLVMVersion } from "./utils.ts"
export async function trySetupLLVMBrew(version: string, _setupDir: string, _arch: string) {
if (process.platform !== "darwin") {
return Promise.resolve(undefined)
}
try {
return await setupLLVMBrew(version, _setupDir, _arch)
} catch (err) {
warning(`Failed to install llvm via brew: ${err}`)
return undefined
}
}
export async function setupLLVMBrew(version: string, _setupDir: string, _arch: string) {
const majorVersion = majorLLVMVersion(version)
// install llvm via brew if a bottle is available for it
const installInfo = await installBrewPack("llvm", `${majorVersion}`, { "force-bottle": true })
// add the bin directory to the PATH
await addPath(installInfo.binDir, rcOptions)
return installInfo
}