Skip to content

Commit 382a3d6

Browse files
committed
Add comprehensive dependency auto-install for build tools
Automatically check and install all required build dependencies across platforms to ensure builds succeed without manual setup. Changes: - Auto-install Homebrew and packages on macOS - Check for Xcode Command Line Tools and prompt install - Check for git and other essential tools - Platform-specific checks for Linux and Windows - Helper functions for command checking and package installation This ensures the build process is as automated as possible, only prompting users when absolutely necessary.
1 parent 760c475 commit 382a3d6

File tree

1 file changed

+222
-22
lines changed

1 file changed

+222
-22
lines changed

scripts/build/build-stub.mjs

Lines changed: 222 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,41 @@ export async function buildStub(options = {}) {
8484
return 1
8585
}
8686

87-
// Step 3: Create output directory
87+
// Step 3: Check and install required tools
88+
console.log('🔧 Checking build requirements...')
89+
90+
// Check for essential tools on all platforms
91+
const essentialTools = await checkEssentialTools(platform, arch, quiet)
92+
if (!essentialTools) {
93+
console.error('❌ Failed to install essential build tools')
94+
return 1
95+
}
96+
97+
// Additional checks for macOS
98+
if (platform === 'darwin') {
99+
// Check for ldid on ARM64 (required for proper signing)
100+
if (arch === 'arm64') {
101+
const ldidCheck = await checkAndInstallLdid(quiet)
102+
if (ldidCheck === 'not-found') {
103+
console.error('❌ Could not install ldid - binary will be malformed')
104+
console.error(' The stub binary will not work properly on ARM64')
105+
console.error(' Try installing manually: brew install ldid')
106+
// Exit early - no point building a broken binary
107+
return 1
108+
} else if (ldidCheck === 'newly-installed') {
109+
console.log('✅ ldid installed successfully')
110+
} else {
111+
console.log('✅ ldid is available')
112+
}
113+
}
114+
}
115+
116+
console.log('✅ All build requirements met\n')
117+
118+
// Step 4: Create output directory
88119
await mkdir(STUB_DIR, { recursive: true })
89120

90-
// Step 4: Build with pkg
121+
// Step 5: Build with pkg
91122
const target = getPkgTarget(platform, arch, nodeVersion)
92123
const outputName = getOutputName(platform, arch)
93124
const outputPath = join(STUB_DIR, outputName)
@@ -211,16 +242,15 @@ export async function buildStub(options = {}) {
211242
return 1
212243
}
213244

214-
// Step 5: Sign with ldid on ARM64 macOS (fixes yao-pkg malformed binary issue)
245+
// Step 6: Sign with ldid on ARM64 macOS (if needed)
215246
if (platform === 'darwin' && arch === 'arm64' && existsSync(outputPath)) {
216-
console.log('🔏 Signing macOS ARM64 binary...')
247+
console.log('🔏 Signing macOS ARM64 binary with ldid...')
217248
const signResult = await signMacOSBinaryWithLdid(outputPath, quiet)
218249
if (signResult === 'ldid-not-found') {
219-
console.error('⚠️ Warning: Could not install or find ldid')
220-
console.error(' Binary may be malformed without ldid signing')
221-
console.error(' To fix manually: brew install ldid && ldid -S ./binaries/stub/socket-macos-arm64')
250+
console.error('⚠️ Warning: ldid disappeared after install?')
222251
} else if (signResult !== 0) {
223252
console.error('⚠️ Warning: Failed to sign with ldid')
253+
console.error(' The binary may be malformed')
224254
} else {
225255
console.log('✅ Binary signed with ldid successfully\n')
226256
}
@@ -257,12 +287,190 @@ export async function buildStub(options = {}) {
257287
return 0
258288
}
259289

290+
/**
291+
* Check and install essential build tools
292+
*/
293+
async function checkEssentialTools(platform, arch, quiet = false) {
294+
const tools = []
295+
296+
// Git is essential for many operations
297+
tools.push({ name: 'git', installCmd: 'git' })
298+
299+
// Platform-specific tools
300+
if (platform === 'darwin') {
301+
// Xcode Command Line Tools provide essential build tools
302+
const hasXcodeTools = await checkCommand('xcodebuild', ['-version'])
303+
if (!hasXcodeTools) {
304+
console.log(' Xcode Command Line Tools not found, installing...')
305+
// This will prompt for installation
306+
await new Promise((resolve) => {
307+
const child = spawn('xcode-select', ['--install'], {
308+
stdio: 'inherit'
309+
})
310+
child.on('exit', resolve)
311+
child.on('error', resolve)
312+
})
313+
}
314+
} else if (platform === 'linux') {
315+
// Linux needs build-essential
316+
tools.push({ name: 'make', installCmd: 'build-essential' })
317+
tools.push({ name: 'gcc', installCmd: 'build-essential' })
318+
} else if (platform === 'win32') {
319+
// Windows needs Visual Studio Build Tools
320+
// These are harder to auto-install, so just check
321+
const hasMSBuild = await checkCommand('msbuild', ['/version'])
322+
if (!hasMSBuild) {
323+
console.error(' Visual Studio Build Tools not found')
324+
console.error(' Please install from: https://visualstudio.microsoft.com/downloads/')
325+
return false
326+
}
327+
}
328+
329+
// Check and install tools sequentially
330+
const installTool = async (tool) => {
331+
const hasCommand = await checkCommand(tool.name)
332+
if (!hasCommand) {
333+
console.log(` ${tool.name} not found`)
334+
if (platform === 'darwin') {
335+
// Try to install via Homebrew
336+
const installed = await installViaHomebrew(tool.installCmd, quiet)
337+
if (!installed) {
338+
console.error(` Failed to install ${tool.name}`)
339+
return false
340+
}
341+
} else if (platform === 'linux') {
342+
console.error(` Please install ${tool.installCmd} manually`)
343+
console.error(` Ubuntu/Debian: sudo apt-get install ${tool.installCmd}`)
344+
console.error(` RHEL/Fedora: sudo dnf install ${tool.installCmd}`)
345+
return false
346+
}
347+
}
348+
return true
349+
}
350+
351+
// Process tools sequentially (needed for dependency order)
352+
for (const tool of tools) {
353+
// eslint-disable-next-line no-await-in-loop
354+
const success = await installTool(tool)
355+
if (!success) {
356+
return false
357+
}
358+
}
359+
360+
return true
361+
}
362+
363+
/**
364+
* Check if a command exists
365+
*/
366+
async function checkCommand(command, args = ['--version']) {
367+
return new Promise((resolve) => {
368+
const child = spawn(command, args, {
369+
stdio: 'pipe'
370+
})
371+
child.on('exit', (code) => resolve(code === 0))
372+
child.on('error', () => resolve(false))
373+
})
374+
}
375+
376+
/**
377+
* Install a package via Homebrew
378+
*/
379+
async function installViaHomebrew(packageName, quiet = false) {
380+
// First ensure Homebrew is available
381+
let brewAvailable = await checkCommand('brew')
382+
if (!brewAvailable) {
383+
console.log(' Installing Homebrew first...')
384+
brewAvailable = await installHomebrew(quiet)
385+
if (!brewAvailable) {
386+
return false
387+
}
388+
}
389+
390+
// Install the package
391+
console.log(` Installing ${packageName} via Homebrew...`)
392+
return new Promise((resolve) => {
393+
const child = spawn('brew', ['install', packageName], {
394+
stdio: quiet ? 'pipe' : 'inherit'
395+
})
396+
child.on('exit', (code) => resolve(code === 0))
397+
child.on('error', () => resolve(false))
398+
})
399+
}
400+
401+
/**
402+
* Check for ldid and install if needed
403+
* @returns {Promise<'available'|'newly-installed'|'not-found'>}
404+
*/
405+
async function checkAndInstallLdid(quiet = false) {
406+
// First check if ldid is already available
407+
const ldidAvailable = await new Promise((resolve) => {
408+
const child = spawn('which', ['ldid'], {
409+
stdio: 'pipe'
410+
})
411+
child.on('exit', (code) => resolve(code === 0))
412+
child.on('error', () => resolve(false))
413+
})
414+
415+
if (ldidAvailable) {
416+
return 'available'
417+
}
418+
419+
// Try to install ldid
420+
console.log(' ldid not found, auto-installing...')
421+
const installed = await installLdidViaBrew(quiet)
422+
423+
return installed ? 'newly-installed' : 'not-found'
424+
}
425+
426+
/**
427+
* Install Homebrew if not available
428+
*/
429+
async function installHomebrew(quiet = false) {
430+
console.log(' Homebrew not found, installing...')
431+
console.log(' This may take a few minutes...')
432+
433+
// Download and run Homebrew installer
434+
return new Promise((resolve) => {
435+
const child = spawn('/bin/bash', ['-c',
436+
'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash'
437+
], {
438+
stdio: quiet ? 'pipe' : 'inherit',
439+
// Non-interactive install
440+
env: { ...process.env, NONINTERACTIVE: '1' }
441+
})
442+
443+
child.on('exit', async (code) => {
444+
if (code === 0) {
445+
// Add Homebrew to PATH for Apple Silicon Macs
446+
if (process.arch === 'arm64') {
447+
process.env.PATH = `/opt/homebrew/bin:${process.env.PATH}`
448+
} else {
449+
process.env.PATH = `/usr/local/bin:${process.env.PATH}`
450+
}
451+
console.log(' Homebrew installed successfully')
452+
resolve(true)
453+
} else {
454+
console.error(' Failed to install Homebrew automatically')
455+
console.error(' Please install manually from https://brew.sh')
456+
resolve(false)
457+
}
458+
})
459+
child.on('error', (error) => {
460+
if (!quiet) {
461+
console.error(' Error installing Homebrew:', error.message)
462+
}
463+
resolve(false)
464+
})
465+
})
466+
}
467+
260468
/**
261469
* Install ldid using Homebrew
262470
*/
263471
async function installLdidViaBrew(quiet = false) {
264472
// First check if brew is available
265-
const brewAvailable = await new Promise((resolve) => {
473+
let brewAvailable = await new Promise((resolve) => {
266474
const child = spawn('which', ['brew'], {
267475
stdio: 'pipe'
268476
})
@@ -271,13 +479,11 @@ async function installLdidViaBrew(quiet = false) {
271479
})
272480

273481
if (!brewAvailable) {
274-
if (!quiet) {
275-
console.error(' Homebrew not found, cannot auto-install ldid')
276-
console.error(' To install Homebrew, run:')
277-
console.error(' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
278-
console.error(' Then re-run this build command')
482+
// Try to install Homebrew automatically
483+
brewAvailable = await installHomebrew(quiet)
484+
if (!brewAvailable) {
485+
return false
279486
}
280-
return false
281487
}
282488

283489
// Install ldid using brew
@@ -310,7 +516,7 @@ async function installLdidViaBrew(quiet = false) {
310516
* Sign macOS ARM64 binary with ldid (fixes yao-pkg malformed binary issue)
311517
*/
312518
async function signMacOSBinaryWithLdid(binaryPath, quiet = false) {
313-
// First check if ldid is available
519+
// Verify ldid is still available (should have been installed earlier)
314520
const ldidAvailable = await new Promise((resolve) => {
315521
const child = spawn('which', ['ldid'], {
316522
stdio: 'pipe'
@@ -320,13 +526,7 @@ async function signMacOSBinaryWithLdid(binaryPath, quiet = false) {
320526
})
321527

322528
if (!ldidAvailable) {
323-
// Try to install ldid automatically
324-
console.log(' ldid not found, attempting to install via Homebrew...')
325-
const brewInstalled = await installLdidViaBrew(quiet)
326-
if (!brewInstalled) {
327-
return 'ldid-not-found'
328-
}
329-
console.log(' ldid installed successfully')
529+
return 'ldid-not-found'
330530
}
331531

332532
// Remove existing signature first (if any)

0 commit comments

Comments
 (0)