@@ -276,9 +276,58 @@ class RNIOS extends Platform.IOS implements RNPlatform {
276276 private static iosFirstBuild : any = { } ;
277277
278278 /**
279- * Builds the binary of the project on this platform.
279+ * Maps project directories to whether or not a real `xcodebuild` has completed for them yet.
280+ * Once true, subsequent scenario switches only need their JS bundle re-packaged, not a full
281+ * native rebuild, since the native code/Podfile don't change between scenarios.
282+ */
283+ private static hasBuiltOnce : { [ projectDirectory : string ] : boolean } = { } ;
284+
285+ /**
286+ * Builds the binary of the project on this platform. Only performs a real `xcodebuild` the
287+ * first time; subsequent calls for the same project just re-bundle the JS (see `bundleOnly`).
280288 */
281289 buildApp ( projectDirectory : string ) : Q . Promise < void > {
290+ if ( RNIOS . hasBuiltOnce [ projectDirectory ] ) {
291+ return this . bundleOnly ( projectDirectory ) ;
292+ }
293+ return this . realBuildApp ( projectDirectory )
294+ . then ( ( ) => { RNIOS . hasBuiltOnce [ projectDirectory ] = true ; } ) ;
295+ }
296+
297+ /**
298+ * Re-packages the JS bundle (and copies assets) into the already-built `.app`, by invoking
299+ * `react-native-xcode.sh` directly instead of going through a full `xcodebuild`. This is the
300+ * same script Xcode's "Bundle React Native code and images" build phase runs; the native code
301+ * doesn't change between scenarios, so re-running the whole build graph is unnecessary.
302+ */
303+ private bundleOnly ( projectDirectory : string ) : Q . Promise < void > {
304+ const iOSProject : string = path . join ( projectDirectory , TestConfig . TestAppName , "ios" ) ;
305+ const configurationBuildDir = path . dirname ( this . getBinaryPath ( projectDirectory ) ) ;
306+ const wrapperName = `${ TestConfig . TestAppName } .app` ;
307+ const scriptPath = path . join ( projectDirectory , TestConfig . TestAppName , "node_modules" , "react-native" , "scripts" , "react-native-xcode.sh" ) ;
308+
309+ const env = Object . assign ( { } , process . env , {
310+ CONFIGURATION : "Release" ,
311+ PLATFORM_NAME : "iphonesimulator" ,
312+ CONFIGURATION_BUILD_DIR : configurationBuildDir ,
313+ TARGET_BUILD_DIR : configurationBuildDir ,
314+ BUILT_PRODUCTS_DIR : configurationBuildDir ,
315+ UNLOCALIZED_RESOURCES_FOLDER_PATH : wrapperName ,
316+ WRAPPER_NAME : wrapperName ,
317+ PROJECT_DIR : iOSProject ,
318+ SRCROOT : iOSProject ,
319+ SOURCE_ROOT : iOSProject ,
320+ PODS_ROOT : path . join ( iOSProject , "Pods" ) ,
321+ } ) ;
322+
323+ return TestUtil . getProcessOutput ( `"${ scriptPath } "` , { cwd : iOSProject , env, timeout : 2 * 60 * 1000 , noLogStdOut : true , noLogStdErr : true } )
324+ . then ( ( ) => { return null ; } ) ;
325+ }
326+
327+ /**
328+ * Performs a full `xcodebuild` of the project on this platform.
329+ */
330+ private realBuildApp ( projectDirectory : string ) : Q . Promise < void > {
282331 const iOSProject : string = path . join ( projectDirectory , TestConfig . TestAppName , "ios" ) ;
283332
284333 return this . getEmulatorManager ( ) . getTargetEmulator ( )
@@ -298,7 +347,7 @@ class RNIOS extends Platform.IOS implements RNPlatform {
298347 del . sync ( [ iosBuildFolder ] , { force : true } ) ;
299348 }
300349 RNIOS . iosFirstBuild [ projectDirectory ] = true ;
301- return this . buildApp ( projectDirectory ) ;
350+ return this . realBuildApp ( projectDirectory ) ;
302351 }
303352 return null ;
304353 } ) ;
0 commit comments