Skip to content

Commit

Permalink
Merge pull request #69 from appcelerator/timob-25309
Browse files Browse the repository at this point in the history
[TIMOB-25309] Add support for Xcode 9's multiple simulator instances.
  • Loading branch information
cb1kenobi committed Sep 26, 2017
2 parents dd0fc0c + 9477a3b commit d4cd540
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 45 deletions.
29 changes: 26 additions & 3 deletions lib/simctl.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const fs = require('fs');
const __ = appc.i18n(__dirname).__;

exports.activatePair = activatePair;
exports.boot = boot;
exports.create = create;
exports.getSim = getSim;
exports.install = install;
Expand Down Expand Up @@ -149,6 +150,28 @@ function launch(params, callback) {
trySimctl(params, ['launch', params.udid, params.appId], callback);
}

/**
* Boots an simulator runtime. Simulator must be running.
*
* @param {Object} params - Various parameters.
* @param {String} params.simctl - The path to the `simctl` executable.
* @param {String} params.udid - The simulator udid to launch the app on.
* @param {Function} callback(err) - A function to call when finished.
*/
function boot(params, callback) {
if (!params || typeof params !== 'object') {
return callback(new Error(__('Missing params')));
}
if (!params.simctl) {
return callback(new Error(__('Missing "simctl" param')));
}
if (!params.udid) {
return callback(new Error(__('Missing "udid" param')));
}

trySimctl(params, ['boot', params.udid], callback);
}

/**
* Returns a list of all devices, runtimes, device types, and pairs.
*
Expand Down Expand Up @@ -272,7 +295,7 @@ function pair(params, callback) {

var watchSim = info.iosSimToWatchSimToPair[params.simUdid][params.watchSimUdid];
if (!watchSim) {
return callback(new Error(__('Failed to find device pair for iOS sim %s and watchOS sim %s.', params.simUdid, params.watchSimUdid)));
return callback(new Error(__('Failed to find device pair for iOS Simulator %s and watchOS Simulator %s.', params.simUdid, params.watchSimUdid)));
}

var udid = watchSim.udid;
Expand Down Expand Up @@ -503,7 +526,7 @@ function waitUntilBooted(params, callback) {
var maxTries = params.tries || 4;
var timer = null;

log('Waiting for sim ' + params.udid + ' to boot');
log('Waiting for simulator ' + params.udid + ' to boot');

if (params.timeout) {
timer = setTimeout(function () {
Expand Down Expand Up @@ -547,7 +570,7 @@ function waitUntilBooted(params, callback) {
return callback(err);
}
if (timedOut) {
err = new Error(__('Timed out waiting for sim to boot'));
err = new Error(__('Timed out waiting for simulator to boot'));
err.code = 666;
return callback(err);
}
Expand Down
145 changes: 104 additions & 41 deletions lib/simulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -945,11 +945,11 @@ function launch(simHandleOrUDID, options, callback) {

async.series([
function checkIfRunningAndBooted(next) {
emitter.emit('log-debug', __('Checking if simulator %s is already running', handle.simulator));
emitter.emit('log-debug', __('Checking if the simulator %s is already running', handle.simulator));

isRunning(handle.simulator, function (err, pid, udid) {
isSimulatorRunning(handle.simulator, function (err, pid, udid) {
if (err) {
emitter.emit('log-debug', __('Failed to check if simulator is running: %s', err.message || err.toString()));
emitter.emit('log-debug', __('Failed to check if the simulator is running: %s', err.message || err.toString()));
return next(err);
}

Expand All @@ -960,9 +960,9 @@ function launch(simHandleOrUDID, options, callback) {

emitter.emit('log-debug', __('Simulator is running (pid %s)', pid));

// check the udid
if (udid !== handle.udid) {
emitter.emit('log-debug', __('%s Simulator is running, but not the udid we want, stopping simulator', handle.name));
// if Xcode 8 or older and the udid doesn't match the running version, then we need to kill the simulator before continuing
if (appc.version.lt(selectedXcode.version, '9.0') && udid !== handle.udid) {
emitter.emit('log-debug', __('%s Simulator is running, but not the UDID we want, stopping simulator', handle.name));
stop(handle, next);
return;
}
Expand All @@ -985,44 +985,75 @@ function launch(simHandleOrUDID, options, callback) {
return next(new Error(__('Simulator is not available')));
}

if (/^shutdown/i.test(sim.state)) {
// the udid that is supposed to be running isn't, kill the simulator
emitter.emit('log-debug', __('%s Simulator is running, but udid %s is shut down, stopping simulator', handle.name, handle.udid));
stop(handle, next);
return;
}
function waitToBoot() {
emitter.emit('log-debug', __('Waiting for simulator to boot...'));
simctl.waitUntilBooted({ simctl: handle.simctl, udid: handle.udid, timeout: 30000 }, function (err, _booted) {
if (err && err.code !== 666) {
emitter.emit('log-debug', __('Error while waiting for simulator to boot: %s', err.message || err.toString()));
return next(err);
}

simctl.waitUntilBooted({ simctl: handle.simctl, udid: handle.udid, timeout: 30000 }, function (err, _booted) {
if (err && err.code !== 666) {
emitter.emit('log-debug', __('Error while waiting for simulator to boot: %s', err.message || err.toString()));
return next(err);
}
booted = _booted;

emitter.emit('log-debug', booted ? __('Simulator is booted!') : __('Simulator is NOT booted!'));

booted = _booted;
if (err || !booted) {
emitter.emit('log-debug', __('%s Simulator is running, but not in a booted state, stopping simulator', handle.name));
stop(handle, next);
return;
}

emitter.emit('log-debug', booted ? __('Simulator is booted!') : __('Simulator is NOT booted!'));
emitter.emit('log-debug', __('%s Simulator already running with the correct UDID', handle.name));

// because we didn't start the simulator, we have no child process to
// listen for when it exits, so we need to monitor it ourselves
setTimeout(function check() {
appc.subprocess.run('ps', ['-p', pid], function (code, out, err) {
if (code) {
simExited();
} else {
setTimeout(check, 1000);
}
});
}, 1000);

next();
});
}

if (err || !booted) {
emitter.emit('log-debug', __('%s Simulator is running, but not in a booted state, stopping simulator', handle.name));
if (appc.version.lt(selectedXcode.version, '9.0')) {
if (/^shutdown/i.test(sim.state)) {
// the udid that is supposed to be running isn't, kill the simulator
emitter.emit('log-debug', __('%s Simulator is running, but UDID %s is shut down, stopping simulator', handle.name, handle.udid));
stop(handle, next);
return;
}

emitter.emit('log-debug', __('%s Simulator already running with the correct udid', handle.name));
return waitToBoot();
}

// because we didn't start the simulator, we have no child process to
// listen for when it exits, so we need to monitor it ourselves
setTimeout(function check() {
appc.subprocess.run('ps', ['-p', pid], function (code, out, err) {
if (code) {
simExited();
} else {
setTimeout(check, 1000);
}
});
}, 1000);
// Xcode 9+ path

next();
if (/^booted/i.test(sim.state)) {
return waitToBoot();
}

emitter.emit('log-debug', __('Getting all running simulator runtimes'));
getRunningSimulatorDevices(function (err, sims) {
if (err) {
return next(err);
}

if (sims.some(function (s) { return s.udid === handle.udid; } )) {
return waitToBoot();
}

simctl.boot({ simctl: handle.simctl, udid: handle.udid }, function (err) {
if (err) {
return next(err);
}
waitToBoot();
});
});
});
});
Expand Down Expand Up @@ -1299,7 +1330,7 @@ function launch(simHandleOrUDID, options, callback) {
}
});

emitter.emit('log-debug', __('Unpair failed, checking %s alternative watch sims', candidates.length));
emitter.emit('log-debug', __('Unpair failed, checking %s alternative watch simulators', candidates.length));

var newWatchSimHandle = null;

Expand All @@ -1314,7 +1345,7 @@ function launch(simHandleOrUDID, options, callback) {
emitter.emit('log-debug', __('Pairing iOS and watchOS simulator pair: %s -> %s', newWatchSimHandle.udid, simHandle.udid));
simctl.pairAndActivate({ simctl: selectedXcode.executables.simctl, simUdid: simHandle.udid, watchSimUdid: newWatchSimHandle.udid }, function (err) {
if (err) {
emitter.emit('log-debug', __('Pairing failed, trying another watch sim'));
emitter.emit('log-debug', __('Pairing failed, trying another watch simulator'));
newWatchSimHandle = null;
}
cb();
Expand All @@ -1329,7 +1360,7 @@ function launch(simHandleOrUDID, options, callback) {
// create a new watch sim
var m = watchSimHandle.name.match(/^(.+ \[Titanium\])(?: (\d+))?$/);
var name = m ? (m[1] + ' ' + ((~~m[2] || 1) + 1)) : (watchSimHandle.name + ' [Titanium]');
emitter.emit('log-debug', __('Creating a new watch sim: %s', name));
emitter.emit('log-debug', __('Creating a new watch simulator: %s', name));
simctl.create({
simctl: selectedXcode.executables.simctl,
name: name,
Expand All @@ -1354,7 +1385,7 @@ function launch(simHandleOrUDID, options, callback) {

if (!found) {
// this shouldn't happen, we just added it!
return next(new Error(__('Unable to find the watch sim %s that was just created', udid)));
return next(new Error(__('Unable to find the watch simulator %s that was just created', udid)));
}

simctl.pairAndActivate({ simctl: selectedXcode.executables.simctl, simUdid: simHandle.udid, watchSimUdid: watchSimHandle.udid }, next);
Expand Down Expand Up @@ -1678,7 +1709,7 @@ function launch(simHandleOrUDID, options, callback) {
* @param {String} proc - The path of the executable to find the pid for
* @param {Function} callback - A function to call with the pid
*/
function isRunning(proc, callback) {
function isSimulatorRunning(proc, callback) {
appc.subprocess.run('ps', '-ef', function (code, out, err) {
if (code) {
return callback(new Error(__('Failed to get process list (exit code %d)', code)));
Expand All @@ -1701,6 +1732,38 @@ function isRunning(proc, callback) {
});
}

/**
* Returns a list of running simulators consisting of their pid and udid.
*
* @param {Function} callback - A function to call with the list of running simulators.
*/
function getRunningSimulatorDevices(callback) {
appc.subprocess.run('ps', '-ef', function (code, out, err) {
if (code) {
return callback(new Error(__('Failed to get process list (exit code %d)', code)));
}

var lines = out.split('\n'),
i = 0,
l = lines.length,
m,
procRE = /^\s*\d+\s+(\d+).+ launchd_sim .+\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\//,
results = [];

for (; i < l; i++) {
m = lines[i].match(procRE);
if (m) {
m.push({
pid: m[1],
udid: m[2]
});
}
}

callback(null, results);
});
}

/**
* Stops the specified iOS Simulator.
*
Expand All @@ -1724,7 +1787,7 @@ function stop(simHandle, callback) {
setTimeout(function () {
simHandle.disconnectLogServer && simHandle.disconnectLogServer();

isRunning(simHandle.simulator, function (err, pid) {
isSimulatorRunning(simHandle.simulator, function (err, pid) {
if (err) {
callback(err);
} else if (pid) {
Expand All @@ -1739,7 +1802,7 @@ function stop(simHandle, callback) {
async.whilst(
function () { return simHandle.running; },
function (cb) {
isRunning(simHandle.simulator, function (err, pid) {
isSimulatorRunning(simHandle.simulator, function (err, pid) {
if (!err && !pid) {
simHandle.running = false;
cb();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ioslib",
"version": "1.5.1",
"version": "1.6.0",
"description": "iOS Utility Library",
"keywords": [
"appcelerator",
Expand Down

0 comments on commit d4cd540

Please sign in to comment.