11import path from "node:path" ;
22import fs from "node:fs" ;
3+ import os from "node:os" ;
34
45import {
56 chalk ,
@@ -9,6 +10,8 @@ import {
910 assertFixable ,
1011 wrapAction ,
1112 prettyPath ,
13+ pLimit ,
14+ spawn ,
1215} from "@react-native-node-api/cli-utils" ;
1316
1417import {
@@ -85,6 +88,10 @@ function getDefaultTargets() {
8588const targetOption = new Option ( "--target <target...>" , "Target triple" )
8689 . choices ( ALL_TARGETS )
8790 . default ( getDefaultTargets ( ) ) ;
91+ const cleanOption = new Option (
92+ "--clean" ,
93+ "Delete the target directory before building" ,
94+ ) . default ( false ) ;
8895const appleTarget = new Option ( "--apple" , "Use all Apple targets" ) ;
8996const androidTarget = new Option ( "--android" , "Use all Android targets" ) ;
9097const ndkVersionOption = new Option (
@@ -112,28 +119,61 @@ const appleBundleIdentifierOption = new Option(
112119 "Unique CFBundleIdentifier used for Apple framework artifacts" ,
113120) . default ( undefined , "com.callstackincubator.node-api.{libraryName}" ) ;
114121
122+ const concurrencyOption = new Option (
123+ "--concurrency <limit>" ,
124+ "Limit the number of concurrent tasks" ,
125+ )
126+ . argParser ( ( value ) => parseInt ( value , 10 ) )
127+ . default (
128+ os . availableParallelism ( ) ,
129+ `${ os . availableParallelism ( ) } or 1 when verbose is enabled` ,
130+ ) ;
131+
132+ const verboseOption = new Option (
133+ "--verbose" ,
134+ "Print more output from underlying compiler & tools" ,
135+ ) . default ( process . env . CI ? true : false , `false in general and true on CI` ) ;
136+
115137export const buildCommand = new Command ( "build" )
116138 . description ( "Build Rust Node-API module" )
117139 . addOption ( targetOption )
140+ . addOption ( cleanOption )
118141 . addOption ( appleTarget )
119142 . addOption ( androidTarget )
120143 . addOption ( ndkVersionOption )
121144 . addOption ( outputPathOption )
122145 . addOption ( configurationOption )
123146 . addOption ( xcframeworkExtensionOption )
124147 . addOption ( appleBundleIdentifierOption )
148+ . addOption ( concurrencyOption )
149+ . addOption ( verboseOption )
125150 . action (
126151 wrapAction (
127152 async ( {
128153 target : targetArg ,
154+ clean,
129155 apple,
130156 android,
131157 ndkVersion,
132158 output : outputPath ,
133159 configuration,
134160 xcframeworkExtension,
135161 appleBundleIdentifier,
162+ concurrency,
163+ verbose,
136164 } ) => {
165+ if ( clean ) {
166+ await oraPromise (
167+ ( ) => spawn ( "cargo" , [ "clean" ] , { outputMode : "buffered" } ) ,
168+ {
169+ text : "Cleaning target directory" ,
170+ successText : "Cleaned target directory" ,
171+ failText : ( error ) => `Failed to clean target directory: ${ error } ` ,
172+ } ,
173+ ) ;
174+ }
175+ // Force a limit of 1 concurrent task to avoid interleaving output
176+ const limit = pLimit ( verbose ? 1 : concurrency ) ;
137177 const targets = new Set ( [ ...targetArg ] ) ;
138178 if ( apple ) {
139179 for ( const target of APPLE_TARGETS ) {
@@ -180,30 +220,40 @@ export const buildCommand = new Command("build")
180220 targets . size +
181221 ( targets . size === 1 ? " target" : " targets" ) +
182222 chalk . dim ( " (" + [ ...targets ] . join ( ", " ) + ")" ) ;
223+
183224 const [ appleLibraries , androidLibraries ] = await oraPromise (
184225 Promise . all ( [
185226 Promise . all (
186- appleTargets . map (
187- async ( target ) =>
188- [ target , await build ( { configuration, target } ) ] as const ,
227+ appleTargets . map ( ( target ) =>
228+ limit (
229+ async ( ) =>
230+ [
231+ target ,
232+ await build ( { configuration, target, verbose } ) ,
233+ ] as const ,
234+ ) ,
189235 ) ,
190236 ) ,
191237 Promise . all (
192- androidTargets . map (
193- async ( target ) =>
194- [
195- target ,
196- await build ( {
197- configuration,
238+ androidTargets . map ( ( target ) =>
239+ limit (
240+ async ( ) =>
241+ [
198242 target ,
199- ndkVersion,
200- androidApiLevel : ANDROID_API_LEVEL ,
201- } ) ,
202- ] as const ,
243+ await build ( {
244+ configuration,
245+ target,
246+ verbose,
247+ ndkVersion,
248+ androidApiLevel : ANDROID_API_LEVEL ,
249+ } ) ,
250+ ] as const ,
251+ ) ,
203252 ) ,
204253 ) ,
205254 ] ) ,
206255 {
256+ isSilent : verbose ,
207257 text : `Building ${ targetsDescription } ` ,
208258 successText : `Built ${ targetsDescription } ` ,
209259 failText : ( error : Error ) => `Failed to build: ${ error . message } ` ,
@@ -225,11 +275,13 @@ export const buildCommand = new Command("build")
225275 ) ;
226276
227277 await oraPromise (
228- createAndroidLibsDirectory ( {
229- outputPath : androidLibsOutputPath ,
230- libraries,
231- autoLink : true ,
232- } ) ,
278+ limit ( ( ) =>
279+ createAndroidLibsDirectory ( {
280+ outputPath : androidLibsOutputPath ,
281+ libraries,
282+ autoLink : true ,
283+ } ) ,
284+ ) ,
233285 {
234286 text : "Assembling Android libs directory" ,
235287 successText : `Android libs directory assembled into ${ prettyPath (
@@ -243,14 +295,25 @@ export const buildCommand = new Command("build")
243295
244296 if ( appleLibraries . length > 0 ) {
245297 const libraryPaths = await combineLibraries ( appleLibraries ) ;
246- const frameworkPaths = await Promise . all (
247- libraryPaths . map ( ( libraryPath ) =>
248- // TODO: Pass true as `versioned` argument for -darwin targets
249- createAppleFramework ( {
250- libraryPath,
251- bundleIdentifier : appleBundleIdentifier ,
252- } ) ,
298+
299+ const frameworkPaths = await oraPromise (
300+ Promise . all (
301+ libraryPaths . map ( ( libraryPath ) =>
302+ limit ( ( ) =>
303+ // TODO: Pass true as `versioned` argument for -darwin targets
304+ createAppleFramework ( {
305+ libraryPath,
306+ bundleIdentifier : appleBundleIdentifier ,
307+ } ) ,
308+ ) ,
309+ ) ,
253310 ) ,
311+ {
312+ text : "Creating Apple frameworks" ,
313+ successText : `Created Apple frameworks` ,
314+ failText : ( { message } ) =>
315+ `Failed to create Apple frameworks: ${ message } ` ,
316+ } ,
254317 ) ;
255318 const xcframeworkFilename = determineXCFrameworkFilename (
256319 frameworkPaths ,
0 commit comments