@@ -292,9 +292,68 @@ class RNIOS extends Platform.IOS implements RNPlatform {
292292 private static iosFirstBuild : any = { } ;
293293
294294 /**
295- * Builds the binary of the project on this platform.
295+ * Maps project directories to whether or not a real `xcodebuild` has completed for them yet.
296+ * Once true, subsequent scenario switches only need their JS bundle re-packaged, not a full
297+ * native rebuild, since the native code/Podfile don't change between scenarios.
298+ */
299+ private static hasBuiltOnce : { [ projectDirectory : string ] : boolean } = { } ;
300+
301+ /**
302+ * Builds the binary of the project on this platform. Only performs a real `xcodebuild` the
303+ * first time; subsequent calls for the same project just re-bundle the JS (see `bundleOnly`).
296304 */
297305 buildApp ( projectDirectory : string ) : Q . Promise < void > {
306+ if ( RNIOS . hasBuiltOnce [ projectDirectory ] ) {
307+ return this . bundleOnly ( projectDirectory ) ;
308+ }
309+ return this . realBuildApp ( projectDirectory )
310+ . then ( ( ) => {
311+ // realBuildApp can resolve even after a failed build (it swallows a failed
312+ // retry into a resolved null - pre-existing behavior, unchanged here). Only
313+ // mark this project as built if the .app it's supposed to have produced
314+ // actually exists, so a swallowed failure doesn't cause every subsequent
315+ // scenario switch to bundleOnly against a missing/stale binary - the next
316+ // buildApp call will instead retry a real xcodebuild.
317+ if ( fs . existsSync ( this . getBinaryPath ( projectDirectory ) ) ) {
318+ RNIOS . hasBuiltOnce [ projectDirectory ] = true ;
319+ }
320+ } ) ;
321+ }
322+
323+ /**
324+ * Re-packages the JS bundle (and copies assets) into the already-built `.app`, by invoking
325+ * `react-native-xcode.sh` directly instead of going through a full `xcodebuild`. This is the
326+ * same script Xcode's "Bundle React Native code and images" build phase runs; the native code
327+ * doesn't change between scenarios, so re-running the whole build graph is unnecessary.
328+ */
329+ private bundleOnly ( projectDirectory : string ) : Q . Promise < void > {
330+ const iOSProject : string = path . join ( projectDirectory , TestConfig . TestAppName , "ios" ) ;
331+ const configurationBuildDir = path . dirname ( this . getBinaryPath ( projectDirectory ) ) ;
332+ const wrapperName = `${ TestConfig . TestAppName } .app` ;
333+ const scriptPath = path . join ( projectDirectory , TestConfig . TestAppName , "node_modules" , "react-native" , "scripts" , "react-native-xcode.sh" ) ;
334+
335+ const env = Object . assign ( { } , process . env , {
336+ CONFIGURATION : "Release" ,
337+ PLATFORM_NAME : "iphonesimulator" ,
338+ CONFIGURATION_BUILD_DIR : configurationBuildDir ,
339+ TARGET_BUILD_DIR : configurationBuildDir ,
340+ BUILT_PRODUCTS_DIR : configurationBuildDir ,
341+ UNLOCALIZED_RESOURCES_FOLDER_PATH : wrapperName ,
342+ WRAPPER_NAME : wrapperName ,
343+ PROJECT_DIR : iOSProject ,
344+ SRCROOT : iOSProject ,
345+ SOURCE_ROOT : iOSProject ,
346+ PODS_ROOT : path . join ( iOSProject , "Pods" ) ,
347+ } ) ;
348+
349+ return TestUtil . getProcessOutput ( `"${ scriptPath } "` , { cwd : iOSProject , env, timeout : 2 * 60 * 1000 , noLogStdOut : true , noLogStdErr : true } )
350+ . then ( ( ) => { return null ; } ) ;
351+ }
352+
353+ /**
354+ * Performs a full `xcodebuild` of the project on this platform.
355+ */
356+ private realBuildApp ( projectDirectory : string ) : Q . Promise < void > {
298357 const iOSProject : string = path . join ( projectDirectory , TestConfig . TestAppName , "ios" ) ;
299358
300359 return this . getEmulatorManager ( ) . getTargetEmulator ( )
@@ -314,7 +373,7 @@ class RNIOS extends Platform.IOS implements RNPlatform {
314373 del . sync ( [ iosBuildFolder ] , { force : true } ) ;
315374 }
316375 RNIOS . iosFirstBuild [ projectDirectory ] = true ;
317- return this . buildApp ( projectDirectory ) ;
376+ return this . realBuildApp ( projectDirectory ) ;
318377 }
319378 return null ;
320379 } ) ;
0 commit comments