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,69 @@ 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+
137+ function logNotice ( message : string , ...params : string [ ] ) {
138+ console . log ( `${ chalk . yellow ( "ℹ︎" ) } ${ message } ` , ...params ) ;
139+ }
140+
115141export const buildCommand = new Command ( "build" )
116142 . description ( "Build Rust Node-API module" )
117143 . addOption ( targetOption )
144+ . addOption ( cleanOption )
118145 . addOption ( appleTarget )
119146 . addOption ( androidTarget )
120147 . addOption ( ndkVersionOption )
121148 . addOption ( outputPathOption )
122149 . addOption ( configurationOption )
123150 . addOption ( xcframeworkExtensionOption )
124151 . addOption ( appleBundleIdentifierOption )
152+ . addOption ( concurrencyOption )
153+ . addOption ( verboseOption )
125154 . action (
126155 wrapAction (
127156 async ( {
128157 target : targetArg ,
158+ clean,
129159 apple,
130160 android,
131161 ndkVersion,
132162 output : outputPath ,
133163 configuration,
134164 xcframeworkExtension,
135165 appleBundleIdentifier,
166+ concurrency,
167+ verbose,
136168 } ) => {
169+ if ( clean ) {
170+ await oraPromise (
171+ ( ) => spawn ( "cargo" , [ "clean" ] , { outputMode : "buffered" } ) ,
172+ {
173+ text : "Cleaning target directory" ,
174+ successText : "Cleaned target directory" ,
175+ failText : ( error ) => `Failed to clean target directory: ${ error } ` ,
176+ } ,
177+ ) ;
178+ }
179+ if ( verbose && concurrency > 1 ) {
180+ logNotice (
181+ `Consider passing ${ chalk . blue ( "--concurrency" ) } 1 when running in verbose mode` ,
182+ ) ;
183+ }
184+ const limit = pLimit ( concurrency ) ;
137185 const targets = new Set ( [ ...targetArg ] ) ;
138186 if ( apple ) {
139187 for ( const target of APPLE_TARGETS ) {
@@ -159,15 +207,12 @@ export const buildCommand = new Command("build")
159207 targets . add ( "aarch64-apple-ios-sim" ) ;
160208 }
161209 }
162- console . error (
163- chalk . yellowBright ( "ℹ" ) ,
164- chalk . dim (
165- `Using default targets, pass ${ chalk . italic (
166- "--android" ,
167- ) } , ${ chalk . italic ( "--apple" ) } or individual ${ chalk . italic (
168- "--target" ,
169- ) } options, to avoid this.`,
170- ) ,
210+ logNotice (
211+ `Using default targets, pass ${ chalk . blue (
212+ "--android" ,
213+ ) } , ${ chalk . blue ( "--apple" ) } or individual ${ chalk . blue (
214+ "--target" ,
215+ ) } options, choose exactly what to target`,
171216 ) ;
172217 }
173218 ensureCargo ( ) ;
@@ -180,30 +225,40 @@ export const buildCommand = new Command("build")
180225 targets . size +
181226 ( targets . size === 1 ? " target" : " targets" ) +
182227 chalk . dim ( " (" + [ ...targets ] . join ( ", " ) + ")" ) ;
228+
183229 const [ appleLibraries , androidLibraries ] = await oraPromise (
184230 Promise . all ( [
185231 Promise . all (
186- appleTargets . map (
187- async ( target ) =>
188- [ target , await build ( { configuration, target } ) ] as const ,
232+ appleTargets . map ( ( target ) =>
233+ limit (
234+ async ( ) =>
235+ [
236+ target ,
237+ await build ( { configuration, target, verbose } ) ,
238+ ] as const ,
239+ ) ,
189240 ) ,
190241 ) ,
191242 Promise . all (
192- androidTargets . map (
193- async ( target ) =>
194- [
195- target ,
196- await build ( {
197- configuration,
243+ androidTargets . map ( ( target ) =>
244+ limit (
245+ async ( ) =>
246+ [
198247 target ,
199- ndkVersion,
200- androidApiLevel : ANDROID_API_LEVEL ,
201- } ) ,
202- ] as const ,
248+ await build ( {
249+ configuration,
250+ target,
251+ verbose,
252+ ndkVersion,
253+ androidApiLevel : ANDROID_API_LEVEL ,
254+ } ) ,
255+ ] as const ,
256+ ) ,
203257 ) ,
204258 ) ,
205259 ] ) ,
206260 {
261+ isSilent : verbose ,
207262 text : `Building ${ targetsDescription } ` ,
208263 successText : `Built ${ targetsDescription } ` ,
209264 failText : ( error : Error ) => `Failed to build: ${ error . message } ` ,
@@ -225,11 +280,13 @@ export const buildCommand = new Command("build")
225280 ) ;
226281
227282 await oraPromise (
228- createAndroidLibsDirectory ( {
229- outputPath : androidLibsOutputPath ,
230- libraries,
231- autoLink : true ,
232- } ) ,
283+ limit ( ( ) =>
284+ createAndroidLibsDirectory ( {
285+ outputPath : androidLibsOutputPath ,
286+ libraries,
287+ autoLink : true ,
288+ } ) ,
289+ ) ,
233290 {
234291 text : "Assembling Android libs directory" ,
235292 successText : `Android libs directory assembled into ${ prettyPath (
@@ -243,14 +300,25 @@ export const buildCommand = new Command("build")
243300
244301 if ( appleLibraries . length > 0 ) {
245302 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- } ) ,
303+
304+ const frameworkPaths = await oraPromise (
305+ Promise . all (
306+ libraryPaths . map ( ( libraryPath ) =>
307+ limit ( ( ) =>
308+ // TODO: Pass true as `versioned` argument for -darwin targets
309+ createAppleFramework ( {
310+ libraryPath,
311+ bundleIdentifier : appleBundleIdentifier ,
312+ } ) ,
313+ ) ,
314+ ) ,
253315 ) ,
316+ {
317+ text : "Creating Apple frameworks" ,
318+ successText : `Created Apple frameworks` ,
319+ failText : ( { message } ) =>
320+ `Failed to create Apple frameworks: ${ message } ` ,
321+ } ,
254322 ) ;
255323 const xcframeworkFilename = determineXCFrameworkFilename (
256324 frameworkPaths ,
0 commit comments