2016-01-06 01:11:53 +00:00
/ * *
2016-09-06 15:00:00 +00:00
* Copyright ( c ) 2015 - present , Facebook , Inc .
* All rights reserved .
*
* This source code is licensed under the BSD - style license found in the
* LICENSE file in the root directory of this source tree . An additional grant
* of patent rights can be found in the PATENTS file in the same directory .
* /
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' ) ;
2016-09-06 15:00:00 +00:00
const parseIOSDevicesList = require ( './parseIOSDevicesList' ) ;
const findMatchingSimulator = require ( './findMatchingSimulator' ) ;
2016-11-10 17:54:25 +00:00
const getBuildPath = function ( configuration = 'Debug' , appName , isDevice ) {
return ` build/Build/Products/ ${ configuration } - ${ isDevice ? 'iphoneos' : 'iphonesimulator' } / ${ appName } .app ` ;
} ;
2016-01-06 01:11:53 +00:00
2016-07-30 15:59:16 +00:00
function runIOS ( argv , config , args ) {
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 ) ;
if ( selectedDevice ) {
2017-01-09 11:25:27 +00:00
return runOnDevice ( selectedDevice , scheme , xcodeProject , args . configuration , args . packager ) ;
2016-09-06 15:00:00 +00:00
} else {
if ( devices ) {
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 {
2016-10-14 21:42:14 +00:00
return runOnSimulator ( xcodeProject , args , inferredSchemeName , 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 ) ;
2016-09-06 15:00:00 +00:00
if ( selectedDevice ) {
2017-01-09 11:25:27 +00:00
return runOnDevice ( selectedDevice , scheme , xcodeProject , args . configuration , args . packager ) ;
2016-09-06 15:00:00 +00:00
} else {
if ( devices ) {
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.' ) ;
}
}
}
function runOnSimulator ( xcodeProject , args , inferredSchemeName , 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
2016-10-14 21:42:14 +00:00
const simulatorFullName = formattedDeviceName ( selectedSimulator ) ;
console . log ( ` Launching ${ simulatorFullName } ... ` ) ;
try {
child _process . spawnSync ( 'xcrun' , [ 'instruments' , '-w' , selectedSimulator . udid ] ) ;
} catch ( e ) {
// instruments always fail with 255 because it expects more arguments,
// but we want it to only launch the simulator
}
resolve ( selectedSimulator . udid )
} )
2017-01-09 11:25:27 +00:00
. then ( ( udid ) => buildProject ( xcodeProject , udid , scheme , args . configuration , args . packager ) )
2016-10-14 21:42:14 +00:00
. then ( ( appName ) => {
if ( ! appName ) {
appName = inferredSchemeName ;
}
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 } ` ) ;
child _process . spawnSync ( 'xcrun' , [ 'simctl' , 'install' , 'booted' , appPath ] , { stdio : 'inherit' } ) ;
const bundleID = child _process . execFileSync (
'/usr/libexec/PlistBuddy' ,
[ '-c' , 'Print:CFBundleIdentifier' , path . join ( appPath , 'Info.plist' ) ] ,
{ encoding : 'utf8' }
) . trim ( ) ;
console . log ( ` Launching ${ bundleID } ` ) ;
child _process . spawnSync ( 'xcrun' , [ 'simctl' , 'launch' , 'booted' , bundleID ] , { stdio : 'inherit' } ) ;
} )
2016-01-06 01:11:53 +00:00
}
2017-01-09 11:25:27 +00:00
function runOnDevice ( selectedDevice , scheme , xcodeProject , configuration , launchPackager ) {
return buildProject ( xcodeProject , selectedDevice . udid , scheme , configuration , launchPackager )
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
}
2017-01-09 11:25:27 +00:00
function buildProject ( xcodeProject , udid , scheme , configuration = 'Debug' , launchPackager = false ) {
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-01-09 11:25:27 +00:00
const buildProcess = child _process . spawn ( 'xcodebuild' , xcodebuildArgs , getProcessOptions ( launchPackager ) ) ;
2016-10-14 21:42:14 +00:00
let buildOutput = "" ;
buildProcess . stdout . on ( 'data' , function ( data ) {
console . log ( data . toString ( ) ) ;
buildOutput += data . toString ( ) ;
} ) ;
buildProcess . stderr . on ( 'data' , function ( data ) {
console . error ( data . toString ( ) ) ;
} ) ;
buildProcess . on ( 'close' , function ( code ) {
//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-03-18 20:01:54 +00:00
let productNameMatch = /export FULL_PRODUCT_NAME="?(.+).app"?$/ . 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
}
return buildProcess . error ? reject ( error ) : resolve ( ) ;
} ) ;
} ) ;
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 )
{
console . log ( ` Using first available device ${ devices [ 0 ] . name } due to lack of name supplied. ` )
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 } ) ` ;
}
2016-09-06 15:00:00 +00:00
function printFoundDevices ( devices ) {
for ( let i = devices . length - 1 ; i >= 0 ; i -- ) {
console . log ( devices [ i ] . name + ' Udid: ' + devices [ i ] . udid ) ;
}
}
2017-01-09 11:25:27 +00:00
function getProcessOptions ( launchPackager ) {
if ( launchPackager ) {
return { } ;
}
return {
env : Object . assign ( { } , process . env , { RCT _NO _LAUNCH _PACKAGER : true } ) ,
} ;
}
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
} ,
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' ,
} ] ,
2016-07-30 15:59:16 +00:00
} ;