Skip to content

Commit

Permalink
Xcode 9 supports running multiple simulators
Browse files Browse the repository at this point in the history
Summary:
Since Xcode 9 you can run multiple simultaneously. And since I believe React Native advocates using the latest version of Xcode, we can safely remove this constraint.

Updated the unit tests. Furthermore it can be found in the [Xcode release notes](https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/WhatsNewXcode/xcode_9/xcode_9.html#//apple_ref/doc/uid/TP40004626-CH8-SW12) that multiple simulators are now supported.

This can be tested with the CLI by running `react-native run-ios` twice, but with a different `--simulator` flag, e.g.;

    react-native run-ios --simulator "iPhone SE"
    react-native run-ios --simulator "iPhone X"

[IOS] [ENHANCEMENT] [local-cli/runIOS/findMatchingSimulator.js] - Allow running multiple simulators
Closes #17284

Differential Revision: D7102790

Pulled By: hramos

fbshipit-source-id: 750e7039201e28a1feda2bec1e78144fd9deff98
  • Loading branch information
koenpunt authored and facebook-github-bot committed Feb 27, 2018
1 parent 2dc559d commit 2ad3407
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 24 deletions.
22 changes: 15 additions & 7 deletions local-cli/runIOS/__tests__/findMatchingSimulator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: 'BA0D93BD-07E6-4182-9B0A-F60A2474139C',
name: 'iPhone 6',
booted: false,
version: 'iOS 9.2'
});
});
Expand Down Expand Up @@ -145,6 +146,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '1CCBBF8B-5773-4EA6-BD6F-C308C87A1ADB',
name: 'iPhone 5',
booted: false,
version: 'iOS 9.2'
});
});
Expand Down Expand Up @@ -216,6 +218,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '1CCBBF8B-5773-4EA6-BD6F-C308C87A1ADB',
name: 'iPhone 5',
booted: false,
version: 'iOS 9.2'
});
});
Expand Down Expand Up @@ -261,11 +264,12 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: 'D0F29BE7-CC3C-4976-888D-C739B4F50508',
name: 'iPhone 6s',
booted: true,
version: 'iOS 9.2'
});
});

it('should return the booted simulator in list even if another device is defined', () => {
it('should return the defined simulator in list even if another device is booted', () => {
expect(findMatchingSimulator({
'devices': {
'iOS 9.2': [
Expand Down Expand Up @@ -304,8 +308,9 @@ describe('findMatchingSimulator', () => {
},
'iPhone 6'
)).toEqual({
udid: 'D0F29BE7-CC3C-4976-888D-C739B4F50508',
name: 'iPhone 6s',
udid: 'BA0D93BD-07E6-4182-9B0A-F60A2474139C',
name: 'iPhone 6',
booted: false,
version: 'iOS 9.2'
});
});
Expand Down Expand Up @@ -377,11 +382,12 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '3A409DC5-5188-42A6-8598-3AA6F34607A5',
name: 'iPhone 7',
booted: true,
version: 'iOS 10.0'
});
});

it('should return the booted simulator in list even if another device is defined (multi ios versions)', () => {
it('should return the defined simulator in list even if another device is booted (multi ios versions)', () => {
expect(findMatchingSimulator({
'devices': {
'iOS 9.2': [
Expand Down Expand Up @@ -446,9 +452,10 @@ describe('findMatchingSimulator', () => {
},
'iPhone 6s'
)).toEqual({
udid: '3A409DC5-5188-42A6-8598-3AA6F34607A5',
name: 'iPhone 7',
version: 'iOS 10.0'
udid: 'D0F29BE7-CC3C-4976-888D-C739B4F50508',
name: 'iPhone 6s',
booted: false,
version: 'iOS 9.2'
});
});

Expand Down Expand Up @@ -481,6 +488,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '816C30EA-38EA-41AC-BFDA-96FB632D522E',
name: 'Apple TV',
booted: true,
version: 'tvOS 11.2'
});
});
Expand Down
10 changes: 5 additions & 5 deletions local-cli/runIOS/findMatchingSimulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,20 @@ function findMatchingSimulator(simulators, simulatorName) {
if (simulator.availability !== '(available)') {
continue;
}
// If there is a booted simulator, we'll use that as instruments will not boot a second simulator
if (simulator.state === 'Booted') {
if (simulatorName !== null) {
console.warn("We couldn't boot your defined simulator due to an already booted simulator. We are limited to one simulator launched at a time.");
}
let booted = simulator.state === 'Booted';
if (booted && simulatorName === null) {
return {
udid: simulator.udid,
name: simulator.name,
booted,
version
};
}
if (simulator.name === simulatorName && !match) {
match = {
udid: simulator.udid,
name: simulator.name,
booted,
version
};
}
Expand All @@ -57,6 +56,7 @@ function findMatchingSimulator(simulators, simulatorName) {
match = {
udid: simulator.udid,
name: simulator.name,
booted,
version
};
}
Expand Down
30 changes: 18 additions & 12 deletions local-cli/runIOS/runIOS.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,30 @@ function runOnSimulator(xcodeProject, args, scheme) {
throw new Error(`Could not find ${args.simulator} simulator`);
}

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
if (!selectedSimulator.booted) {
const simulatorFullName = formattedDeviceName(selectedSimulator);
console.log(`Booting ${simulatorFullName}...`);
try {
child_process.execFileSync('xcrun', ['simctl', 'boot', selectedSimulator.udid]);

This comment has been minimized.

Copy link
@grabbou

grabbou Apr 6, 2018

Contributor

This command only "boots" simulators, but the Simulator app has to be already opened. Without it, nothing happens.

This comment has been minimized.

Copy link
@grabbou

grabbou Apr 6, 2018

Contributor

We need to always run:

child_process.execFileSync('open',['/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app']);

to make sure the Simulator app is open.

Even if a simulator with given udid is booted, that doesn't mean it's turned on. It's just its service that is running and it will show up as soon as Simulator.app is opened.

Note: Simulator.app is only going to work for xcode 8 and newer. Previously, the app was named iOS Simulator. Question is whether we need to keep that compatibility layer?

This comment has been minimized.

Copy link
@grabbou

grabbou Apr 6, 2018

Contributor

I tested this multiple times and turns out Simulator is a singleton app (not sure if that applies generally to all Mac apps). Running this many times with Simulator already opened doesn't trigger any errors.

This comment has been minimized.

Copy link
@kelset

kelset Apr 6, 2018

Contributor

Considering that latest is 9.3, I think that it's not necessary/mandatory to be compatible with XCode lower than 8.

} catch (e) {
throw new Error(
`Could not boot ${args.simulator} simulator. Is there already a simulator running?
Running multiple simulators is only supported from Xcode 9 and up.
Try closing the simulator or run the command again without specifying a simulator.`
);
}
}
resolve(selectedSimulator.udid);

buildProject(xcodeProject, selectedSimulator.udid, scheme, args.configuration, args.packager, args.verbose)
.then((appName) => resolve(selectedSimulator.udid, appName));

This comment has been minimized.

Copy link
@oguzbilgener

oguzbilgener Mar 1, 2018

resolve takes a single parameter. The appName here is never passed to the next function in the promise chain. This breaks the install stage when the scheme name is different from the product name.

The amount of code that gets merged into react-native master without proper testing is astonishing.

This comment has been minimized.

Copy link
@grabbou

grabbou Apr 5, 2018

Contributor

Yeah, @koenpunt that looks weird

})
.then((udid) => buildProject(xcodeProject, udid, scheme, args.configuration, args.packager, args.verbose, args.port))
.then((appName) => {
.then((udid, appName) => {
if (!appName) {
appName = scheme;
}
let appPath = getBuildPath(args.configuration, appName);
console.log(`Installing ${appPath}`);
child_process.spawnSync('xcrun', ['simctl', 'install', 'booted', appPath], {stdio: 'inherit'});
child_process.spawnSync('xcrun', ['simctl', 'install', udid, appPath], {stdio: 'inherit'});

const bundleID = child_process.execFileSync(
'/usr/libexec/PlistBuddy',
Expand All @@ -139,7 +145,7 @@ function runOnSimulator(xcodeProject, args, scheme) {
).trim();

console.log(`Launching ${bundleID}`);
child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'});
child_process.spawnSync('xcrun', ['simctl', 'launch', udid, bundleID], {stdio: 'inherit'});
});
}

Expand Down

0 comments on commit 2ad3407

Please sign in to comment.