11import { execFileSync } from 'node:child_process'
2- import { existsSync , mkdirSync , statSync } from 'node:fs'
3- import { dirname , join } from 'node:path'
2+ import { cpSync , mkdirSync , rmSync } from 'node:fs'
3+ import { join } from 'node:path'
4+ import {
5+ nodeIncludeDirectory ,
6+ recordNativeBuild ,
7+ reuseNativeBuild ,
8+ } from '@desktop-scripts/native-build-cache'
49import { createLogger } from '@sim/logger'
510
611const logger = createLogger ( 'DesktopNativeBuild' )
12+ const args = process . argv . slice ( 2 )
13+ if ( args . some ( ( argument ) => argument !== '--computer-use' ) || args . length > 1 ) {
14+ throw new Error ( 'Usage: bun run scripts/build-native.ts [--computer-use]' )
15+ }
716if ( process . platform !== 'darwin' && process . platform !== 'linux' ) {
817 throw new Error ( 'Native desktop modules require macOS or Linux.' )
918}
10- const nodeExecutable = execFileSync ( 'node' , [ '-p' , 'process.execPath' ] , { encoding : 'utf8' } ) . trim ( )
11- const includeDirectory = join ( dirname ( nodeExecutable ) , '..' , 'include' , 'node' )
12- if ( ! existsSync ( join ( includeDirectory , 'node_api.h' ) ) ) {
13- throw new Error ( `Could not find Node-API headers in ${ includeDirectory } ` )
14- }
19+ const includeDirectory = nodeIncludeDirectory ( )
1520const outputDirectory = 'dist/native'
1621mkdirSync ( outputDirectory , { recursive : true } )
1722const modules = [
@@ -22,13 +27,20 @@ const modules = [
2227]
2328for ( const module of modules ) {
2429 const output = join ( outputDirectory , `${ module . name } .node` )
25- if (
26- existsSync ( output ) &&
27- statSync ( output ) . mtimeMs >=
28- Math . max ( statSync ( module . source ) . mtimeMs , statSync ( import . meta. filename ) . mtimeMs )
29- )
30- continue
3130 const macOS = process . platform === 'darwin'
31+ const outputs = [ { path : output , executable : true } ]
32+ if ( reuseNativeBuild ( module . name , outputs ) ) {
33+ try {
34+ if ( macOS ) {
35+ execFileSync ( 'xcrun' , [ 'lipo' , output , '-verify_arch' , 'arm64' , 'x86_64' ] )
36+ }
37+ continue
38+ } catch {
39+ logger . warn ( 'Rebuilding native module with invalid cached architectures' , {
40+ module : module . name ,
41+ } )
42+ }
43+ }
3244 execFileSync (
3345 macOS ? 'xcrun' : 'c++' ,
3446 [
@@ -58,5 +70,57 @@ for (const module of modules) {
5870 ] ,
5971 { stdio : 'inherit' }
6072 )
73+ recordNativeBuild ( module . name , outputs )
6174 logger . info ( 'Compiled native desktop module' , { module : module . name } )
6275}
76+
77+ /** Bundle the native helper; packaged builds sign it with the desktop distribution identity. */
78+ function compileComputerUse ( ) : void {
79+ if ( process . platform !== 'darwin' ) return
80+ const bundle = 'dist/native/Sim Computer Use.app'
81+ const binaryDirectory = join ( bundle , 'Contents' , 'MacOS' )
82+ const binary = join ( binaryDirectory , 'SimComputerUse' )
83+ const outputs = [
84+ { path : binary , executable : true } ,
85+ { path : join ( bundle , 'Contents' , 'Info.plist' ) } ,
86+ { path : join ( bundle , 'Contents' , '_CodeSignature' , 'CodeResources' ) } ,
87+ ]
88+ if ( reuseNativeBuild ( 'computer-use' , outputs ) ) {
89+ try {
90+ execFileSync ( 'xcrun' , [ 'lipo' , binary , '-verify_arch' , 'arm64' , 'x86_64' ] )
91+ execFileSync ( 'codesign' , [ '--verify' , '--strict' , bundle ] )
92+ return
93+ } catch {
94+ rmSync ( bundle , { recursive : true , force : true } )
95+ }
96+ }
97+ rmSync ( bundle , { recursive : true , force : true } )
98+ mkdirSync ( binaryDirectory , { recursive : true } )
99+ cpSync ( 'native/computer-use/Info.plist' , join ( bundle , 'Contents' , 'Info.plist' ) )
100+ const parts : string [ ] = [ ]
101+ for ( const arch of [ 'arm64' , 'x86_64' ] ) {
102+ const output = join ( 'dist/native' , `computer-use-${ arch } ` )
103+ execFileSync (
104+ 'xcrun' ,
105+ [
106+ 'swiftc' ,
107+ '-parse-as-library' ,
108+ '-O' ,
109+ '-target' ,
110+ `${ arch } -apple-macosx14.0` ,
111+ 'native/computer-use/ComputerUse.swift' ,
112+ '-o' ,
113+ output ,
114+ ] ,
115+ { stdio : 'inherit' }
116+ )
117+ parts . push ( output )
118+ }
119+ execFileSync ( 'xcrun' , [ 'lipo' , '-create' , ...parts , '-output' , binary ] , { stdio : 'inherit' } )
120+ for ( const part of parts ) rmSync ( part )
121+ execFileSync ( 'codesign' , [ '--force' , '--sign' , '-' , bundle ] , { stdio : 'inherit' } )
122+ recordNativeBuild ( 'computer-use' , outputs )
123+ logger . info ( 'Compiled native macOS Computer Use helper' )
124+ }
125+
126+ if ( args . includes ( '--computer-use' ) ) compileComputerUse ( )
0 commit comments