Every rung of the macOS SDK resolver is a read: a failing xcode-select, an SDKROOT that names no installed SDK, or a bare directory posing as one falls to the next rung, ending in the xcrun selection the builders used before. An unusable SDKROOT is reported on stderr and ignored, which matches how `xcrun --sdk macosx` treated it. SDKSettings.plist proves a directory is an SDK, since xcrun accepts any existing path. Native coverage builds both universal helpers with a stale SDKROOT, a plain directory as SDKROOT, and an older installed SDK when the host has one.
110 lines
4.1 KiB
JavaScript
110 lines
4.1 KiB
JavaScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
|
|
vi.mock('node:child_process', () => ({
|
|
execFileSync: vi.fn(),
|
|
}))
|
|
|
|
const { execFileSync } = await import('node:child_process')
|
|
const { macosSysroot, xcrunClangArgv } = await import('./macos-sysroot.mjs')
|
|
|
|
const exec = vi.mocked(execFileSync)
|
|
const opts = env => ({ encoding: 'utf8', env, stdio: 'pipe' })
|
|
const XCODE_SDK = 'Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'
|
|
const CLT_SDK = 'SDKs/MacOSX.sdk'
|
|
|
|
let developerDir, env, warn
|
|
const sdkDir = relative => {
|
|
const dir = path.join(developerDir, relative)
|
|
fs.mkdirSync(dir, { recursive: true })
|
|
fs.writeFileSync(path.join(dir, 'SDKSettings.plist'), '')
|
|
return dir
|
|
}
|
|
// Answers `xcode-select -p` with the fixture developer dir and `xcrun --sdk X
|
|
// --show-sdk-path` with the given result (a string or an Error to throw).
|
|
const toolchain = sdkPath => exec.mockImplementation(file => {
|
|
if (file === 'xcode-select') return `${developerDir}\n`
|
|
if (sdkPath instanceof Error) throw sdkPath
|
|
return `${sdkPath}\n`
|
|
})
|
|
|
|
beforeEach(() => {
|
|
developerDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sysroot-'))
|
|
env = { DEVELOPER_DIR: developerDir }
|
|
warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => {
|
|
exec.mockReset()
|
|
warn.mockRestore()
|
|
fs.rmSync(developerDir, { recursive: true, force: true })
|
|
})
|
|
|
|
it('resolves an explicit SDKROOT path or name through xcrun before any developer default', () => {
|
|
const pinned = sdkDir('SDKs/MacOSX15.4.sdk')
|
|
sdkDir(CLT_SDK)
|
|
for (const SDKROOT of [pinned, 'macosx15.4']) {
|
|
const override = { ...env, SDKROOT }
|
|
toolchain(pinned)
|
|
expect(macosSysroot(override)).toBe(pinned)
|
|
expect(exec).toHaveBeenCalledWith('xcrun', ['--sdk', SDKROOT, '--show-sdk-path'], opts(override))
|
|
expect(exec).not.toHaveBeenCalledWith('xcode-select', expect.anything(), expect.anything())
|
|
expect(xcrunClangArgv(pinned)).toEqual(['clang', '-isysroot', pinned])
|
|
}
|
|
expect(warn).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('reports a stale SDKROOT and falls back to the toolchain default instead of failing the build', () => {
|
|
const paired = sdkDir(CLT_SDK)
|
|
const missing = path.join(developerDir, 'SDKs/MacOSX99.sdk')
|
|
// xcrun accepts any existing directory as an SDK path; a plain directory is not one.
|
|
const bare = fs.mkdtempSync(path.join(developerDir, 'not-an-sdk-'))
|
|
for (const [SDKROOT, answer] of [
|
|
['macosx99', new Error('SDK "macosx99" cannot be located')],
|
|
[missing, missing],
|
|
[bare, bare]
|
|
]) {
|
|
toolchain(answer)
|
|
expect(macosSysroot({ ...env, SDKROOT })).toBe(paired)
|
|
expect(warn).toHaveBeenLastCalledWith(expect.stringContaining(`SDKROOT=${SDKROOT}`))
|
|
}
|
|
expect(warn).toHaveBeenCalledTimes(3)
|
|
})
|
|
|
|
it('ignores an empty SDKROOT', () => {
|
|
const paired = sdkDir(CLT_SDK)
|
|
toolchain(new Error('unexpected xcrun call'))
|
|
expect(macosSysroot({ ...env, SDKROOT: '' })).toBe(paired)
|
|
expect(exec).toHaveBeenCalledTimes(1)
|
|
expect(exec).toHaveBeenLastCalledWith('xcode-select', ['-p'], opts({ ...env, SDKROOT: '' }))
|
|
})
|
|
|
|
it('prefers the Command Line Tools alias over the Xcode platform alias', () => {
|
|
toolchain()
|
|
for (const relative of [XCODE_SDK, CLT_SDK]) {
|
|
const paired = sdkDir(relative)
|
|
expect(macosSysroot(env)).toBe(paired)
|
|
expect(exec).toHaveBeenLastCalledWith('xcode-select', ['-p'], opts(env))
|
|
}
|
|
})
|
|
|
|
it('hands SDK selection back to xcrun when the toolchain has no default alias', () => {
|
|
toolchain()
|
|
expect(macosSysroot(env)).toBeNull()
|
|
// A bare MacOSX.sdk directory with no SDKSettings.plist is not an SDK either.
|
|
fs.mkdirSync(path.join(developerDir, CLT_SDK), { recursive: true })
|
|
expect(macosSysroot(env)).toBeNull()
|
|
expect(xcrunClangArgv(null)).toEqual(['--sdk', 'macosx', 'clang'])
|
|
})
|
|
|
|
it('hands SDK selection back to xcrun when xcode-select fails or answers nothing', () => {
|
|
sdkDir(CLT_SDK)
|
|
exec.mockImplementation(() => { throw new Error('xcode-select: error: unable to get active developer directory') })
|
|
expect(macosSysroot(env)).toBeNull()
|
|
exec.mockReturnValue('\n')
|
|
expect(macosSysroot(env)).toBeNull()
|
|
expect(warn).not.toHaveBeenCalled()
|
|
})
|