-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Works the same way as `react-native run-android`, but targets iOS simulator instead. Under the hood, it uses `xcodebuild` to compile the app and store it in `ios/build` folder, then triggers `instruments` and `simctl` to install and launch the app on simulator. Since Facebook relies on BUCK to build and run iOS app, we probably won't use `run-ios` internally. That's why I'm putting this as public PR instead of internal diff. To test this, I hacked global `react-native` script to install react native from my local checkout instead of from npm, cd into the folder and ran `react-native run-ios`. Closes #5119 Reviewed By: svcscm Differential Revision: D2805199 Pulled By: frantic fb-gh-sync-id: 423a45ba885cb5e48a16ac22095d757d8cca7e37
- Loading branch information
Showing
7 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** | ||
* 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. | ||
*/ | ||
'use strict'; | ||
|
||
jest.dontMock('../findXcodeProject'); | ||
|
||
const findXcodeProject = require('../findXcodeProject'); | ||
|
||
describe('findXcodeProject', () => { | ||
it('should find *.xcodeproj file', () => { | ||
expect(findXcodeProject([ | ||
'.DS_Store', | ||
'AwesomeApp', | ||
'AwesomeApp.xcodeproj', | ||
'AwesomeAppTests', | ||
'PodFile', | ||
'Podfile.lock', | ||
'Pods' | ||
])).toEqual({ | ||
name: 'AwesomeApp.xcodeproj', | ||
isWorkspace: false, | ||
}); | ||
}); | ||
|
||
it('should prefer *.xcworkspace', () => { | ||
expect(findXcodeProject([ | ||
'.DS_Store', | ||
'AwesomeApp', | ||
'AwesomeApp.xcodeproj', | ||
'AwesomeApp.xcworkspace', | ||
'AwesomeAppTests', | ||
'PodFile', | ||
'Podfile.lock', | ||
'Pods' | ||
])).toEqual({ | ||
name: 'AwesomeApp.xcworkspace', | ||
isWorkspace: true, | ||
}); | ||
}); | ||
|
||
it('should return null if nothing found', () => { | ||
expect(findXcodeProject([ | ||
'.DS_Store', | ||
'AwesomeApp', | ||
'AwesomeAppTests', | ||
'PodFile', | ||
'Podfile.lock', | ||
'Pods' | ||
])).toEqual(null); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
jest.dontMock('../parseIOSSimulatorsList'); | ||
var parseIOSSimulatorsList = require('../parseIOSSimulatorsList'); | ||
|
||
describe('parseIOSSimulatorsList', () => { | ||
it('parses typical output', () => { | ||
var simulators = parseIOSSimulatorsList([ | ||
'== Devices ==', | ||
'-- iOS 8.1 --', | ||
' iPhone 4s (4FE43B33-EF13-49A5-B6A6-658D32F20988) (Shutdown)', | ||
'-- iOS 8.4 --', | ||
' iPhone 4s (EAB622C7-8ADE-4FAE-A911-94C0CA4709BB) (Shutdown)', | ||
' iPhone 5 (AE1CD3D0-A85B-4A73-B320-9CA7BA4FAEB0) (Shutdown)', | ||
].join('\n')); | ||
|
||
expect(simulators).toEqual([ | ||
{name: 'iPhone 4s', udid: '4FE43B33-EF13-49A5-B6A6-658D32F20988', version: '8.1'}, | ||
{name: 'iPhone 4s', udid: 'EAB622C7-8ADE-4FAE-A911-94C0CA4709BB', version: '8.4'}, | ||
{name: 'iPhone 5', udid: 'AE1CD3D0-A85B-4A73-B320-9CA7BA4FAEB0', version: '8.4'}, | ||
]); | ||
}); | ||
|
||
it('ignores unavailable simulators', () => { | ||
var simulators = parseIOSSimulatorsList([ | ||
'== Devices ==', | ||
'-- iOS 8.1 --', | ||
' iPhone 4s (4FE43B33-EF13-49A5-B6A6-658D32F20988) (Shutdown)', | ||
'-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-8-3 --', | ||
' iPhone 5s (EAB622C7-8ADE-4FAE-A911-94C0CA4709BB) (Shutdown)', | ||
].join('\n')); | ||
|
||
expect(simulators).toEqual([{ | ||
name: 'iPhone 4s', | ||
udid: '4FE43B33-EF13-49A5-B6A6-658D32F20988', | ||
version: '8.1', | ||
}]); | ||
|
||
}); | ||
|
||
it('ignores garbage', () => { | ||
expect(parseIOSSimulatorsList('Something went terribly wrong (-42)')).toEqual([]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* 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. | ||
* | ||
* @flow | ||
*/ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
|
||
type ProjectInfo = { | ||
name: string; | ||
isWorkspace: boolean; | ||
} | ||
|
||
function findXcodeProject(files: Array<string>): ?ProjectInfo { | ||
const sortedFiles = files.sort(); | ||
for (let i = sortedFiles.length - 1; i >= 0; i--) { | ||
const fileName = files[i]; | ||
const ext = path.extname(fileName); | ||
|
||
if (ext === '.xcworkspace') { | ||
return { | ||
name: fileName, | ||
isWorkspace: true, | ||
}; | ||
} | ||
if (ext === '.xcodeproj') { | ||
return { | ||
name: fileName, | ||
isWorkspace: false, | ||
}; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
module.exports = findXcodeProject; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* 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. | ||
* | ||
* @flow | ||
*/ | ||
'use strict'; | ||
|
||
type IOSSimulatorInfo = { | ||
name: string; | ||
udid: string; | ||
version: string; | ||
} | ||
|
||
/** | ||
* Parses the output of `xcrun simctl list devices` command | ||
*/ | ||
function parseIOSSimulatorsList(text: string): Array<IOSSimulatorInfo> { | ||
const devices = []; | ||
var currentOS = null; | ||
|
||
text.split('\n').forEach((line) => { | ||
var section = line.match(/^-- (.+) --$/); | ||
if (section) { | ||
var header = section[1].match(/^iOS (.+)$/); | ||
if (header) { | ||
currentOS = header[1]; | ||
} else { | ||
currentOS = null; | ||
} | ||
return; | ||
} | ||
|
||
const device = line.match(/^[ ]*([^()]+) \(([^()]+)\)/); | ||
if (device && currentOS) { | ||
var name = device[1]; | ||
var udid = device[2]; | ||
devices.push({udid, name, version: currentOS}); | ||
} | ||
}); | ||
|
||
return devices; | ||
} | ||
|
||
module.exports = parseIOSSimulatorsList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/** | ||
* 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. | ||
*/ | ||
'use strict'; | ||
|
||
const child_process = require('child_process'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const parseCommandLine = require('../util/parseCommandLine'); | ||
const findXcodeProject = require('./findXcodeProject'); | ||
const parseIOSSimulatorsList = require('./parseIOSSimulatorsList'); | ||
const Promise = require('promise'); | ||
|
||
/** | ||
* Starts the app on iOS simulator | ||
*/ | ||
function runIOS(argv, config) { | ||
return new Promise((resolve, reject) => { | ||
_runIOS(argv, config, resolve, reject); | ||
resolve(); | ||
}); | ||
} | ||
|
||
function _runIOS(argv, config, resolve, reject) { | ||
const args = parseCommandLine([{ | ||
command: 'simulator', | ||
description: 'Explicitly set simulator to use', | ||
type: 'string', | ||
required: false, | ||
default: 'iPhone 6', | ||
}], argv); | ||
|
||
process.chdir('ios'); | ||
const xcodeProject = findXcodeProject(fs.readdirSync('.')); | ||
if (!xcodeProject) { | ||
throw new Error(`Could not find Xcode project files in ios folder`); | ||
} | ||
|
||
const inferredSchemeName = path.basename(xcodeProject.name, path.extname(xcodeProject.name)); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
console.log(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} ${xcodeProject.name}`); | ||
|
||
const simulators = parseIOSSimulatorsList( | ||
child_process.execFileSync('xcrun', ['simctl', 'list', 'devices'], {encoding: 'utf8'}) | ||
); | ||
const selectedSimulator = matchingSimulator(simulators, args.simulator); | ||
if (!selectedSimulator) { | ||
throw new Error(`Cound't find ${args.simulator} simulator`); | ||
} | ||
|
||
const simulatorFullName = `${selectedSimulator.name} (${selectedSimulator.version})`; | ||
console.log(`Launching ${simulatorFullName}...`); | ||
try { | ||
child_process.spawnSync('xcrun', ['instruments', '-w', simulatorFullName]); | ||
} catch(e) { | ||
// instruments always fail with 255 because it expects more arguments, | ||
// but we want it to only launch the simulator | ||
} | ||
|
||
const xcodebuildArgs = [ | ||
xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name, | ||
'-scheme', inferredSchemeName, | ||
'-destination', `id=${selectedSimulator.udid}`, | ||
'-derivedDataPath', 'build', | ||
]; | ||
console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`); | ||
child_process.spawnSync('xcodebuild', xcodebuildArgs, {stdio: 'inherit'}); | ||
|
||
const appPath = `build/Build/Products/Debug-iphonesimulator/${inferredSchemeName}.app`; | ||
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')], | ||
This comment has been minimized.
Sorry, something went wrong.
gre
Contributor
|
||
{encoding: 'utf8'} | ||
).trim(); | ||
|
||
console.log(`Launching ${bundleID}`); | ||
child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'}); | ||
} | ||
|
||
function matchingSimulator(simulators, simulatorName) { | ||
for (let i = simulators.length - 1; i >= 0; i--) { | ||
if (simulators[i].name === simulatorName) { | ||
return simulators[i]; | ||
} | ||
} | ||
} | ||
|
||
module.exports = runIOS; |
1 comment
on commit 9490c2c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool
Thanks for doing this. We can't use this immediately though since we use different schemes in our project that load their own plist files. It would be great to be able to pass in a --scheme argument to run-ios.