1+ #!/usr/bin/env node
2+ import { $ } from 'zx' ;
3+ import path from 'node:path' ;
4+ import { parseArgs } from 'node:util' ;
5+
6+ $ . verbose = true ;
7+
8+ const { positionals } = parseArgs ( { allowPositionals : true } ) ;
9+ const [ workspace , sdk , scheme , action , ...extra ] = positionals ;
10+
11+ if ( ! workspace || ! sdk || ! scheme || ! action ) {
12+ console . error ( 'Usage: xcodebuild.mts <workspace> <sdk> <scheme> <action> [...args]' ) ;
13+ process . exit ( 1 ) ;
14+ }
15+
16+ async function getDestination ( ) : Promise < string [ ] > {
17+ const isTest = action === 'test' || action === 'test-without-building' ;
18+
19+ if ( sdk === 'iphoneos' || sdk === 'iphonesimulator' ) {
20+ if ( isTest ) {
21+ const devices = ( await $ `xcrun simctl list devices iPhone available` ) . stdout ;
22+ const match = devices . match ( / i P h o n e \d + \( ( [ - 0 - 9 A - F a - f ] + ) \) / ) ;
23+ if ( ! match ) throw new Error ( 'No available iPhone simulator found' ) ;
24+ return [ '-destination' , `platform=iOS Simulator,id=${ match [ 1 ] } ` ] ;
25+ }
26+ return [ '- destination ', 'generic/platform=iOS Simulator' ] ;
27+ }
28+
29+ if ( sdk === 'macosx' ) {
30+ return [ ] ;
31+ }
32+
33+ if ( sdk === 'xros' || sdk === 'xrsimulator' ) {
34+ if ( isTest ) {
35+ const devices = ( await $ `xcrun simctl list devices visionOS available` ) . stdout ;
36+ const match = devices . match ( / A p p l e V i s i o n P r o \( ( [ - 0 - 9 A - F a - f ] + ) \) / ) ;
37+ if ( ! match ) throw new Error ( 'No available visionOS simulator found' ) ;
38+ return [ '-destination' , `platform=visionOS Simulator,id=${ match [ 1 ] } ` ] ;
39+ }
40+ return [ '- destination ', 'generic/platform=visionOS Simulator' ] ;
41+ }
42+
43+ throw new Error ( `Cannot detect sdk: ${ sdk } ` ) ;
44+ }
45+
46+ async function setupCcache ( ) : Promise < void > {
47+ if ( ! ( await $ `command -v ccache` . nothrow ( ) ) . stdout ) {
48+ await $ `brew install ccache` ;
49+ }
50+
51+ const ccachePath = ( await $ `which ccache` ) . stdout . trim ( ) ;
52+ const ccacheHome = path . join ( path . dirname ( path . dirname ( ccachePath ) ) , 'opt' , 'ccache' ) ;
53+ const repoRoot = ( await $ `git rev-parse --show-toplevel` ) . stdout . trim ( ) ;
54+
55+ process . env . CCACHE_DIR = path . join ( repoRoot , '.ccache' ) ;
56+ process . env . CC = path . join ( ccacheHome , 'libexec' , 'clang' ) ;
57+ process . env . CXX = path . join ( ccacheHome , 'libexec' , 'clang++' ) ;
58+ process . env . CMAKE_C_COMPILER_LAUNCHER = ccachePath ;
59+ process . env . CMAKE_CXX_COMPILER_LAUNCHER = ccachePath ;
60+
61+ await $ `ccache --zero-stats` . quiet ( ) ;
62+ }
63+
64+ const destination = await getDestination ( ) ;
65+ const derivedDataPath = path . join ( path . dirname ( workspace ) , 'build' ) ;
66+ const useCcache = process . env . CCACHE_DISABLE !== '1' ;
67+
68+ if ( useCcache ) {
69+ await setupCcache ( ) ;
70+ }
71+
72+ if ( ! ( await $ `command -v xcbeautify` . nothrow ( ) ) . stdout ) {
73+ await $ `brew install xcbeautify` ;
74+ }
75+
76+ const xcodebuildArgs = [
77+ '-workspace' , workspace ,
78+ '-scheme' , scheme ,
79+ '-sdk' , sdk ,
80+ ...destination ,
81+ '-derivedDataPath' , derivedDataPath ,
82+ 'CODE_SIGNING_ALLOWED=NO' ,
83+ 'COMPILER_INDEX_STORE_ENABLE=NO' ,
84+ action ,
85+ ...extra ,
86+ ] ;
87+
88+ // Pipe xcodebuild through xcbeautify
89+ await $ `xcodebuild ${ xcodebuildArgs } | xcbeautify --report junit` ;
90+
91+ if ( useCcache ) {
92+ await $ `ccache --show-stats --verbose` ;
93+ }
0 commit comments