Skip to content
Permalink
Browse files

Xcode 9 supports running multiple simulators

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 2ad34075f1d048bebb08ef30799ac0d081073150
@@ -55,6 +55,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: 'BA0D93BD-07E6-4182-9B0A-F60A2474139C',
name: 'iPhone 6',
booted: false,
version: 'iOS 9.2'
});
});
@@ -145,6 +146,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '1CCBBF8B-5773-4EA6-BD6F-C308C87A1ADB',
name: 'iPhone 5',
booted: false,
version: 'iOS 9.2'
});
});
@@ -216,6 +218,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '1CCBBF8B-5773-4EA6-BD6F-C308C87A1ADB',
name: 'iPhone 5',
booted: false,
version: 'iOS 9.2'
});
});
@@ -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': [
@@ -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'
});
});
@@ -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': [
@@ -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'
});
});

@@ -481,6 +488,7 @@ describe('findMatchingSimulator', () => {
)).toEqual({
udid: '816C30EA-38EA-41AC-BFDA-96FB632D522E',
name: 'Apple TV',
booted: true,
version: 'tvOS 11.2'
});
});
@@ -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
};
}
@@ -57,6 +56,7 @@ function findMatchingSimulator(simulators, simulatorName) {
match = {
udid: simulator.udid,
name: simulator.name,
booted,
version
};
}
@@ -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

Collaborator

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

Collaborator

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

Collaborator

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

Collaborator

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

Collaborator

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',
@@ -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'});
});
}

0 comments on commit 2ad3407

Please sign in to comment.
You can’t perform that action at this time.