2016-01-06 01:11:53 +00:00
/ * *
2016-09-06 15:00:00 +00:00
* Copyright ( c ) 2015 - present , Facebook , Inc .
*
2018-02-17 02:24:55 +00:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
2016-09-06 15:00:00 +00:00
* /
2016-01-06 01:11:53 +00:00
'use strict' ;
const child _process = require ( 'child_process' ) ;
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const findXcodeProject = require ( './findXcodeProject' ) ;
2017-08-03 18:55:40 +00:00
const findReactNativeScripts = require ( '../util/findReactNativeScripts' ) ;
2016-09-06 15:00:00 +00:00
const parseIOSDevicesList = require ( './parseIOSDevicesList' ) ;
const findMatchingSimulator = require ( './findMatchingSimulator' ) ;
2018-01-29 21:20:48 +00:00
const getBuildPath = function ( configuration = 'Debug' , appName , isDevice ) {
let device ;
if ( isDevice ) {
device = 'iphoneos' ;
} else if ( appName . toLowerCase ( ) . includes ( 'tvos' ) ) {
device = 'appletvsimulator' ;
} else {
device = 'iphonesimulator' ;
}
2018-04-05 21:36:07 +00:00
return ` build/Build/Products/ ${ configuration } - ${ device } / ${ appName } .app ` ;
2016-11-10 17:54:25 +00:00
} ;
2017-09-08 16:57:22 +00:00
const xcprettyAvailable = function ( ) {
try {
child _process . execSync ( 'xcpretty --version' , {
stdio : [ 0 , 'pipe' , 'ignore' , ]
} ) ;
} catch ( error ) {
return false ;
}
return true ;
} ;
2016-01-06 01:11:53 +00:00
2016-07-30 15:59:16 +00:00
function runIOS ( argv , config , args ) {
2017-08-03 18:55:40 +00:00
if ( ! fs . existsSync ( args . projectPath ) ) {
const reactNativeScriptsPath = findReactNativeScripts ( ) ;
if ( reactNativeScriptsPath ) {
child _process . spawnSync (
reactNativeScriptsPath ,
[ 'ios' ] . concat ( process . argv . slice ( 1 ) ) ,
{ stdio : 'inherit' }
) ;
return ;
} else {
throw new Error ( 'iOS project folder not found. Are you sure this is a React Native project?' ) ;
}
}
2016-07-30 15:59:16 +00:00
process . chdir ( args . projectPath ) ;
2016-01-06 01:11:53 +00:00
const xcodeProject = findXcodeProject ( fs . readdirSync ( '.' ) ) ;
if ( ! xcodeProject ) {
2016-07-30 15:59:16 +00:00
throw new Error ( 'Could not find Xcode project files in ios folder' ) ;
2016-01-06 01:11:53 +00:00
}
const inferredSchemeName = path . basename ( xcodeProject . name , path . extname ( xcodeProject . name ) ) ;
2016-04-01 15:02:40 +00:00
const scheme = args . scheme || inferredSchemeName ;
2016-01-06 01:11:53 +00:00
console . log ( ` Found Xcode ${ xcodeProject . isWorkspace ? 'workspace' : 'project' } ${ xcodeProject . name } ` ) ;
2016-09-06 15:00:00 +00:00
const devices = parseIOSDevicesList (
child _process . execFileSync ( 'xcrun' , [ 'instruments' , '-s' ] , { encoding : 'utf8' } )
2016-01-06 01:11:53 +00:00
) ;
2016-09-06 15:00:00 +00:00
if ( args . device ) {
const selectedDevice = matchingDevice ( devices , args . device ) ;
2017-08-23 06:33:21 +00:00
if ( selectedDevice ) {
2018-02-14 16:02:13 +00:00
return runOnDevice ( selectedDevice , scheme , xcodeProject , args . configuration , args . packager , args . verbose , args . port ) ;
2016-09-06 15:00:00 +00:00
} else {
2017-08-23 06:33:21 +00:00
if ( devices && devices . length > 0 ) {
2016-09-06 15:00:00 +00:00
console . log ( 'Could not find device with the name: "' + args . device + '".' ) ;
console . log ( 'Choose one of the following:' ) ;
printFoundDevices ( devices ) ;
} else {
console . log ( 'No iOS devices connected.' ) ;
}
}
} else if ( args . udid ) {
2016-11-10 17:54:25 +00:00
return runOnDeviceByUdid ( args , scheme , xcodeProject , devices ) ;
2016-09-06 15:00:00 +00:00
} else {
2017-04-19 18:19:05 +00:00
return runOnSimulator ( xcodeProject , args , scheme ) ;
2016-09-06 15:00:00 +00:00
}
}
2016-11-10 17:54:25 +00:00
function runOnDeviceByUdid ( args , scheme , xcodeProject , devices ) {
const selectedDevice = matchingDeviceByUdid ( devices , args . udid ) ;
2017-08-23 06:33:21 +00:00
if ( selectedDevice ) {
2018-01-05 04:02:37 +00:00
return runOnDevice ( selectedDevice , scheme , xcodeProject , args . configuration , args . packager , args . verbose , args . port ) ;
2016-09-06 15:00:00 +00:00
} else {
2017-08-23 06:33:21 +00:00
if ( devices && devices . length > 0 ) {
2016-11-10 17:54:25 +00:00
console . log ( 'Could not find device with the udid: "' + args . udid + '".' ) ;
2016-09-06 15:00:00 +00:00
console . log ( 'Choose one of the following:' ) ;
printFoundDevices ( devices ) ;
} else {
console . log ( 'No iOS devices connected.' ) ;
}
}
}
2017-08-23 06:33:21 +00:00
function runOnSimulator ( xcodeProject , args , scheme ) {
2016-10-14 21:42:14 +00:00
return new Promise ( ( resolve ) => {
try {
var simulators = JSON . parse (
child _process . execFileSync ( 'xcrun' , [ 'simctl' , 'list' , '--json' , 'devices' ] , { encoding : 'utf8' } )
) ;
} catch ( e ) {
throw new Error ( 'Could not parse the simulator list output' ) ;
}
2016-06-22 10:30:30 +00:00
2016-10-14 21:42:14 +00:00
const selectedSimulator = findMatchingSimulator ( simulators , args . simulator ) ;
if ( ! selectedSimulator ) {
2016-11-22 19:48:36 +00:00
throw new Error ( ` Could not find ${ args . simulator } simulator ` ) ;
2016-10-14 21:42:14 +00:00
}
2016-01-06 01:11:53 +00:00
2018-02-27 22:03:50 +00:00
if ( ! selectedSimulator . booted ) {
const simulatorFullName = formattedDeviceName ( selectedSimulator ) ;
2018-04-06 16:16:55 +00:00
console . log ( ` Launching ${ simulatorFullName } ... ` ) ;
2018-02-27 22:03:50 +00:00
try {
2018-04-06 16:16:55 +00:00
child _process . spawnSync ( 'xcrun' , [ 'instruments' , '-w' , selectedSimulator . udid ] ) ;
2018-02-27 22:03:50 +00:00
} catch ( e ) {
2018-04-06 16:16:55 +00:00
// instruments always fail with 255 because it expects more arguments,
// but we want it to only launch the simulator
2018-02-27 22:03:50 +00:00
}
2016-10-14 21:42:14 +00:00
}
2018-02-27 22:03:50 +00:00
2018-04-05 21:30:39 +00:00
buildProject ( xcodeProject , selectedSimulator . udid , scheme , args . configuration , args . packager , args . verbose , args . port )
. then ( ( appName ) => resolve ( { udid : selectedSimulator . udid , appName } ) ) ;
2016-10-14 21:42:14 +00:00
} )
2018-04-05 21:30:39 +00:00
. then ( ( { udid , appName } ) => {
2016-10-14 21:42:14 +00:00
if ( ! appName ) {
2017-04-19 18:19:05 +00:00
appName = scheme ;
2016-10-14 21:42:14 +00:00
}
2016-11-10 17:54:25 +00:00
let appPath = getBuildPath ( args . configuration , appName ) ;
2016-10-14 21:42:14 +00:00
console . log ( ` Installing ${ appPath } ` ) ;
2018-02-27 22:03:50 +00:00
child _process . spawnSync ( 'xcrun' , [ 'simctl' , 'install' , udid , appPath ] , { stdio : 'inherit' } ) ;
2016-10-14 21:42:14 +00:00
const bundleID = child _process . execFileSync (
'/usr/libexec/PlistBuddy' ,
[ '-c' , 'Print:CFBundleIdentifier' , path . join ( appPath , 'Info.plist' ) ] ,
{ encoding : 'utf8' }
) . trim ( ) ;
console . log ( ` Launching ${ bundleID } ` ) ;
2018-02-27 22:03:50 +00:00
child _process . spawnSync ( 'xcrun' , [ 'simctl' , 'launch' , udid , bundleID ] , { stdio : 'inherit' } ) ;
2017-09-08 16:57:22 +00:00
} ) ;
2016-01-06 01:11:53 +00:00
}
2018-01-05 04:02:37 +00:00
function runOnDevice ( selectedDevice , scheme , xcodeProject , configuration , launchPackager , verbose , port ) {
return buildProject ( xcodeProject , selectedDevice . udid , scheme , configuration , launchPackager , verbose , port )
2016-10-14 21:42:14 +00:00
. then ( ( appName ) => {
if ( ! appName ) {
appName = scheme ;
}
const iosDeployInstallArgs = [
2016-11-10 17:54:25 +00:00
'--bundle' , getBuildPath ( configuration , appName , true ) ,
2016-10-14 21:42:14 +00:00
'--id' , selectedDevice . udid ,
'--justlaunch'
] ;
console . log ( ` installing and launching your app on ${ selectedDevice . name } ... ` ) ;
const iosDeployOutput = child _process . spawnSync ( 'ios-deploy' , iosDeployInstallArgs , { encoding : 'utf8' } ) ;
if ( iosDeployOutput . error ) {
console . log ( '' ) ;
console . log ( '** INSTALLATION FAILED **' ) ;
console . log ( 'Make sure you have ios-deploy installed globally.' ) ;
console . log ( '(e.g "npm install -g ios-deploy")' ) ;
} else {
console . log ( '** INSTALLATION SUCCEEDED **' ) ;
}
} ) ;
2016-09-06 15:00:00 +00:00
}
2018-01-05 04:02:37 +00:00
function buildProject ( xcodeProject , udid , scheme , configuration = 'Debug' , launchPackager = false , verbose , port ) {
2016-10-14 21:42:14 +00:00
return new Promise ( ( resolve , reject ) =>
{
2016-11-10 17:54:25 +00:00
var xcodebuildArgs = [
2016-10-14 21:42:14 +00:00
xcodeProject . isWorkspace ? '-workspace' : '-project' , xcodeProject . name ,
2016-11-10 17:54:25 +00:00
'-configuration' , configuration ,
2016-10-14 21:42:14 +00:00
'-scheme' , scheme ,
'-destination' , ` id= ${ udid } ` ,
'-derivedDataPath' , 'build' ,
] ;
console . log ( ` Building using "xcodebuild ${ xcodebuildArgs . join ( ' ' ) } " ` ) ;
2017-09-08 16:57:22 +00:00
let xcpretty ;
if ( ! verbose ) {
xcpretty = xcprettyAvailable ( ) && child _process . spawn ( 'xcpretty' , [ ] , { stdio : [ 'pipe' , process . stdout , process . stderr ] } ) ;
}
2018-01-05 04:02:37 +00:00
const buildProcess = child _process . spawn ( 'xcodebuild' , xcodebuildArgs , getProcessOptions ( launchPackager , port ) ) ;
2017-09-08 16:57:22 +00:00
let buildOutput = '' ;
2016-10-14 21:42:14 +00:00
buildProcess . stdout . on ( 'data' , function ( data ) {
buildOutput += data . toString ( ) ;
2017-09-08 16:57:22 +00:00
if ( xcpretty ) {
xcpretty . stdin . write ( data ) ;
} else {
console . log ( data . toString ( ) ) ;
}
2016-10-14 21:42:14 +00:00
} ) ;
buildProcess . stderr . on ( 'data' , function ( data ) {
console . error ( data . toString ( ) ) ;
} ) ;
buildProcess . on ( 'close' , function ( code ) {
2017-09-08 16:57:22 +00:00
if ( xcpretty ) {
xcpretty . stdin . end ( ) ;
}
2016-10-14 21:42:14 +00:00
//FULL_PRODUCT_NAME is the actual file name of the app, which actually comes from the Product Name in the build config, which does not necessary match a scheme name, example output line: export FULL_PRODUCT_NAME="Super App Dev.app"
2017-05-26 22:57:29 +00:00
let productNameMatch = /export FULL_PRODUCT_NAME="?(.+).app"?$/m . exec ( buildOutput ) ;
2016-10-14 21:42:14 +00:00
if ( productNameMatch && productNameMatch . length && productNameMatch . length > 1 ) {
return resolve ( productNameMatch [ 1 ] ) ; //0 is the full match, 1 is the app name
}
2017-09-08 16:57:22 +00:00
return buildProcess . error ? reject ( buildProcess . error ) : resolve ( ) ;
2016-10-14 21:42:14 +00:00
} ) ;
} ) ;
2016-09-06 15:00:00 +00:00
}
function matchingDevice ( devices , deviceName ) {
2016-10-14 21:42:14 +00:00
if ( deviceName === true && devices . length === 1 )
{
2017-09-08 16:57:22 +00:00
console . log ( ` Using first available device ${ devices [ 0 ] . name } due to lack of name supplied. ` ) ;
2016-10-14 21:42:14 +00:00
return devices [ 0 ] ;
2016-11-10 17:54:25 +00:00
}
2016-09-06 15:00:00 +00:00
for ( let i = devices . length - 1 ; i >= 0 ; i -- ) {
if ( devices [ i ] . name === deviceName || formattedDeviceName ( devices [ i ] ) === deviceName ) {
return devices [ i ] ;
}
}
}
function matchingDeviceByUdid ( devices , udid ) {
for ( let i = devices . length - 1 ; i >= 0 ; i -- ) {
if ( devices [ i ] . udid === udid ) {
return devices [ i ] ;
2016-01-06 01:11:53 +00:00
}
}
}
2016-09-06 15:00:00 +00:00
function formattedDeviceName ( simulator ) {
2016-07-05 16:46:22 +00:00
return ` ${ simulator . name } ( ${ simulator . version } ) ` ;
}
2017-08-23 06:33:21 +00:00
function printFoundDevices ( devices ) {
2016-09-06 15:00:00 +00:00
for ( let i = devices . length - 1 ; i >= 0 ; i -- ) {
console . log ( devices [ i ] . name + ' Udid: ' + devices [ i ] . udid ) ;
}
}
2018-01-05 04:02:37 +00:00
function getProcessOptions ( launchPackager , port ) {
2017-01-09 11:25:27 +00:00
if ( launchPackager ) {
2018-01-05 04:02:37 +00:00
return {
env : { ... process . env , RCT _METRO _PORT : port }
} ;
2017-01-09 11:25:27 +00:00
}
return {
2018-01-05 04:02:37 +00:00
env : { ... process . env , RCT _NO _LAUNCH _PACKAGER : true } ,
2017-01-09 11:25:27 +00:00
} ;
}
2016-07-30 15:59:16 +00:00
module . exports = {
name : 'run-ios' ,
description : 'builds your app and starts it on iOS simulator' ,
func : runIOS ,
examples : [
2016-09-06 15:00:00 +00:00
{
desc : 'Run on a different simulator, e.g. iPhone 5' ,
cmd : 'react-native run-ios --simulator "iPhone 5"' ,
} ,
{
desc : 'Pass a non-standard location of iOS directory' ,
cmd : 'react-native run-ios --project-path "./app/ios"' ,
} ,
{
desc : "Run on a connected device, e.g. Max's iPhone" ,
2017-03-29 18:31:05 +00:00
cmd : 'react-native run-ios --device "Max\'s iPhone"' ,
2016-09-06 15:00:00 +00:00
} ,
2018-01-29 21:20:48 +00:00
{
desc : 'Run on the AppleTV simulator' ,
cmd : 'react-native run-ios --simulator "Apple TV" --scheme "helloworld-tvOS"' ,
}
2016-07-30 15:59:16 +00:00
] ,
options : [ {
command : '--simulator [string]' ,
description : 'Explicitly set simulator to use' ,
default : 'iPhone 6' ,
2016-11-10 17:54:25 +00:00
} , {
command : '--configuration [string]' ,
description : 'Explicitly set the scheme configuration to use' ,
} , {
2016-07-30 15:59:16 +00:00
command : '--scheme [string]' ,
description : 'Explicitly set Xcode scheme to use' ,
} , {
command : '--project-path [string]' ,
description : 'Path relative to project root where the Xcode project '
2016-09-06 15:00:00 +00:00
+ '(.xcodeproj) lives. The default is \'ios\'.' ,
2016-07-30 15:59:16 +00:00
default : 'ios' ,
2016-09-06 15:00:00 +00:00
} , {
command : '--device [string]' ,
2016-10-14 21:42:14 +00:00
description : 'Explicitly set device to use by name. The value is not required if you have a single device connected.' ,
2017-01-09 11:25:27 +00:00
} , {
2016-09-06 15:00:00 +00:00
command : '--udid [string]' ,
description : 'Explicitly set device to use by udid' ,
2017-01-09 11:25:27 +00:00
} , {
command : '--no-packager' ,
description : 'Do not launch packager while building' ,
2017-09-08 16:57:22 +00:00
} , {
command : '--verbose' ,
description : 'Do not use xcpretty even if installed' ,
2018-01-05 04:02:37 +00:00
} , {
command : '--port [number]' ,
default : process . env . RCT _METRO _PORT || 8081 ,
parse : ( val : string ) => Number ( val ) ,
2017-01-09 11:25:27 +00:00
} ] ,
2016-07-30 15:59:16 +00:00
} ;