Skip to content

Commit eb3dd35

Browse files
FatmeFatme
authored andcommitted
Merge pull request #55 from telerik/fatme/isim
Integrate iOS simulator with device discovery
2 parents d552d95 + 41bcfb9 commit eb3dd35

9 files changed

+286
-60
lines changed

lib/child-process.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import * as errors from "./errors";
66
import Future = require("fibers/future");
77
import * as util from "util";
88

9-
export function exec(command: string): IFuture<any> {
9+
export function exec(command: string, opts?: any): IFuture<any> {
1010
var future = new Future<any>();
1111

1212
child_process.exec(command, (error: Error, stdout: NodeBuffer, stderr: NodeBuffer) => {
1313
if(error) {
14-
future.throw(new Error(`Error ${error.message} while executing ${command}.`));
14+
if (opts && opts.skipError) {
15+
future.return(error);
16+
} else {
17+
future.throw(new Error(`Error ${error.message} while executing ${command}.`));
18+
}
1519
} else {
1620
future.return(stdout ? stdout.toString() : "");
1721
}
@@ -20,7 +24,7 @@ export function exec(command: string): IFuture<any> {
2024
return future;
2125
}
2226

23-
export function spawn(command: string, args: string[]): IFuture<string> {
27+
export function spawn(command: string, args: string[], opts?: any): IFuture<string> {
2428
let future = new Future<string>();
2529
let capturedOut = "";
2630
let capturedErr = "";
@@ -44,7 +48,11 @@ export function spawn(command: string, args: string[]): IFuture<string> {
4448
if(exitCode === 0) {
4549
future.return(capturedOut ? capturedOut.trim() : null);
4650
} else {
47-
future.throw(new Error(util.format("Command %s with arguments %s failed with exit code %s. Error output:\n %s", command, args.join(" "), exitCode, capturedErr)));
51+
if (opts && opts.skipError) {
52+
future.return(capturedErr);
53+
} else {
54+
future.throw(new Error(util.format("Command %s with arguments %s failed with exit code %s. Error output:\n %s", command, args.join(" "), exitCode, capturedErr)));
55+
}
4856
}
4957
});
5058

lib/declarations.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface IDevice {
2929
interface ISimctl {
3030
launch(deviceId: string, applicationIdentifier: string): IFuture<string>;
3131
install(deviceId: string, applicationPath: string): IFuture<void>;
32-
uninstall(deviceId: string, applicationIdentifier: string): IFuture<void>;
32+
uninstall(deviceId: string, applicationIdentifier: string, opts?: any): IFuture<void>;
3333
notifyPost(deviceId: string, notification: string): IFuture<void>;
3434
getDevices(): IFuture<IDevice[]>;
3535
getAppContainer(deviceId: string, applicationIdentifier: string): IFuture<string>;
@@ -50,6 +50,19 @@ interface ISimulator {
5050
run(applicationPath: string, applicationIdentifier: string): IFuture<void>;
5151
sendNotification(notification: string): IFuture<void>;
5252
getApplicationPath(deviceId: string, applicationIdentifier: string): IFuture<string>;
53+
getInstalledApplications(deviceId: string): IFuture<IApplication[]>;
54+
installApplication(deviceId: string, applicationPath: string): IFuture<void>;
55+
uninstallApplication(deviceId: string, appIdentifier: string): IFuture<void>;
56+
startApplication(deviceId: string, appIdentifier: string): IFuture<string>;
57+
stopApplication(deviceId: string, appIdentifier: string): IFuture<string>;
58+
printDeviceLog(deviceId: string): void;
59+
startSimulator(): IFuture<void>;
60+
}
61+
62+
interface IApplication {
63+
guid: string;
64+
appIdentifier: string;
65+
path: string;
5366
}
5467

5568
interface IExecuteOptions {

lib/ios-sim.ts

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ var fiber = Fiber(() => {
1515

1616
fiber.run();
1717

18+
function getSimulator(): IFuture<ISimulator> {
19+
let libraryPath = require("./iphone-simulator");
20+
let obj = new libraryPath.iPhoneSimulator();
21+
return obj.createSimulator();
22+
}
23+
1824
global.publicApi = {};
1925

2026
Object.defineProperty(global.publicApi, "getRunningSimulator", {
@@ -42,14 +48,76 @@ Object.defineProperty(global.publicApi, "getRunningSimulator", {
4248
Object.defineProperty(global.publicApi, "getApplicationPath", {
4349
get: () => {
4450
return (...args: any[]) => {
45-
let libraryPath = require("./iphone-simulator");
46-
let obj = new libraryPath.iPhoneSimulator();
47-
let simulator = obj.createSimulator().wait();
51+
let simulator = getSimulator().wait();
4852
let result = simulator.getApplicationPath.apply(simulator, args).wait();
4953
return result;
5054
}
5155
}
5256
});
5357

58+
Object.defineProperty(global.publicApi, "getInstalledApplications", {
59+
get: () => {
60+
return (...args: any[]) => {
61+
let simulator = getSimulator().wait();
62+
let installedApplications: IApplication[] = simulator.getInstalledApplications.apply(simulator, args).wait();
63+
let result = _.map(installedApplications, application => application.appIdentifier);
64+
return result;
65+
}
66+
}
67+
});
68+
69+
Object.defineProperty(global.publicApi, "installApplication", {
70+
get: () => {
71+
return (...args: any[]) => {
72+
let simulator = getSimulator().wait();
73+
return simulator.installApplication.apply(simulator, args);
74+
}
75+
}
76+
});
77+
78+
Object.defineProperty(global.publicApi, "uninstallApplication", {
79+
get: () => {
80+
return (...args: any[]) => {
81+
let simulator = getSimulator().wait();
82+
return simulator.uninstallApplication.apply(simulator, args);
83+
}
84+
}
85+
});
86+
87+
Object.defineProperty(global.publicApi, "startApplication", {
88+
get: () => {
89+
return (...args: any[]) => {
90+
let simulator = getSimulator().wait();
91+
return simulator.startApplication.apply(simulator, args);
92+
}
93+
}
94+
});
95+
96+
Object.defineProperty(global.publicApi, "stopApplication", {
97+
get: () => {
98+
return (...args: any[]) => {
99+
let simulator = getSimulator().wait();
100+
return simulator.stopApplication.apply(simulator, args);
101+
}
102+
}
103+
});
104+
105+
Object.defineProperty(global.publicApi, "printDeviceLog", {
106+
get: () => {
107+
return (...args: any[]) => {
108+
let simulator = getSimulator().wait();
109+
return simulator.printDeviceLog.apply(simulator, args);
110+
}
111+
}
112+
});
113+
114+
Object.defineProperty(global.publicApi, "startSimulator", {
115+
get: () => {
116+
return (...args: any[]) => {
117+
let simulator = getSimulator().wait();
118+
return simulator.startSimulator.apply(simulator, args);
119+
}
120+
}
121+
});
54122

55123
module.exports = global.publicApi;

lib/iphone-simulator-common.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
///<reference path="./.d.ts"/>
2+
"use strict";
3+
4+
import childProcess = require("./child-process");
5+
import Future = require("fibers/future");
6+
import * as fs from "fs";
7+
import * as path from "path";
8+
import * as os from "os";
9+
import xcode = require("./xcode");
10+
let bplistParser = require("bplist-parser");
11+
let osenv = require("osenv");
12+
13+
export function getInstalledApplications(deviceId: string): IFuture<IApplication[]> {
14+
return (() => {
15+
let rootApplicationsPath = path.join(osenv.home(), `/Library/Developer/CoreSimulator/Devices/${deviceId}/data/Containers/Bundle/Application`);
16+
if(!fs.existsSync(rootApplicationsPath)) {
17+
rootApplicationsPath = path.join(osenv.home(), `/Library/Developer/CoreSimulator/Devices/${deviceId}/data/Applications`);
18+
}
19+
let applicationGuids = fs.readdirSync(rootApplicationsPath);
20+
let result: IApplication[] = [];
21+
_.each(applicationGuids, applicationGuid => {
22+
let fullApplicationPath = path.join(rootApplicationsPath, applicationGuid);
23+
if (fs.statSync(fullApplicationPath).isDirectory()) {
24+
let applicationDirContents = fs.readdirSync(fullApplicationPath);
25+
let applicationName = _.find(applicationDirContents, fileName => path.extname(fileName) === ".app");
26+
let plistFilePath = path.join(fullApplicationPath, applicationName, "Info.plist");
27+
let applicationData = parseFile(plistFilePath).wait();
28+
result.push({
29+
guid: applicationGuid,
30+
appIdentifier: applicationData[0].CFBundleIdentifier,
31+
path: path.join(fullApplicationPath, applicationName)
32+
});
33+
}
34+
});
35+
36+
return result;
37+
}).future<IApplication[]>()();
38+
}
39+
40+
export function printDeviceLog(deviceId: string): void {
41+
let logFilePath = path.join(osenv.home(), "Library", "Logs", "CoreSimulator", deviceId, "system.log");
42+
43+
let childProcess = require("child_process").spawn("tail", ['-f', '-n', '1', logFilePath]);
44+
if (childProcess.stdout) {
45+
childProcess.stdout.on("data", (data: NodeBuffer) => {
46+
process.stdout.write(data.toString());
47+
});
48+
}
49+
50+
if (childProcess.stderr) {
51+
childProcess.stderr.on("data", (data: string) => {
52+
process.stdout.write(data.toString());
53+
});
54+
}
55+
}
56+
57+
export function startSimulator(deviceId: string): IFuture<void> {
58+
return (() => {
59+
let simulatorPath = path.resolve(xcode.getPathFromXcodeSelect().wait(), "Applications", "Simulator.app");
60+
let args = [simulatorPath, '--args', '-CurrentDeviceUDID', deviceId];
61+
childProcess.spawn("open", args).wait();
62+
}).future<void>()();
63+
}
64+
65+
function parseFile(plistFilePath: string): IFuture<any> {
66+
let future = new Future<any>();
67+
bplistParser.parseFile(plistFilePath, (err: Error, obj: any) => {
68+
if(err) {
69+
future.throw(err);
70+
} else {
71+
future.return(obj);
72+
}
73+
});
74+
return future;
75+
}

lib/iphone-simulator-xcode-5.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,32 @@ export class XCode5Simulator extends iPhoneSimulatorBaseLib.IPhoneInteropSimulat
6363
return Future.fromResult("");
6464
}
6565

66+
public getInstalledApplications(deviceId: string): IFuture<IApplication[]> {
67+
return Future.fromResult(<IApplication[]>[]);
68+
}
69+
70+
public installApplication(deviceId: string, applicationPath: string): IFuture<void> {
71+
return Future.fromResult();
72+
}
73+
74+
public uninstallApplication(deviceId: string, appIdentifier: string): IFuture<void> {
75+
return Future.fromResult();
76+
}
77+
78+
public startApplication(deviceId: string, appIdentifier: string): IFuture<string> {
79+
return Future.fromResult("");
80+
}
81+
82+
public stopApplication(deviceId: string, appIdentifier: string): IFuture<string> {
83+
return Future.fromResult("");
84+
}
85+
86+
public printDeviceLog(deviceId: string): void { }
87+
88+
public startSimulator(): IFuture<void> {
89+
return Future.fromResult();
90+
}
91+
6692
private get deviceIdentifier(): string {
6793
let identifier = options.device || XCode5Simulator.DEFAULT_DEVICE_IDENTIFIER;
6894
return XCode5Simulator.allowedDeviceIdentifiers[identifier];

lib/iphone-simulator-xcode-6.ts

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
///<reference path="./.d.ts"/>
22
"use strict";
3+
import childProcess = require("./child-process");
34
import * as errors from "./errors";
45
import * as options from "./options";
56
import * as utils from "./utils";
6-
import * as fs from "fs";
7+
78
import Future = require("fibers/future");
9+
import * as fs from "fs";
810
import * as path from "path";
911
import * as util from "util";
1012
import * as os from "os";
13+
import common = require("./iphone-simulator-common");
14+
import { Simctl } from "./simctl";
1115
let $ = require("nodobjc");
12-
let bplistParser = require("bplist-parser");
1316
let osenv = require("osenv");
1417

1518
import iPhoneSimulatorBaseLib = require("./iphone-interop-simulator-base");
@@ -20,11 +23,13 @@ export class XCode6Simulator extends iPhoneSimulatorBaseLib.IPhoneInteropSimulat
2023
private static DEFAULT_DEVICE_IDENTIFIER = "iPhone-4s";
2124

2225
private cachedDevices: IDevice[];
26+
private simctl: ISimctl;
2327

2428
constructor() {
2529
super(this);
2630

2731
this.cachedDevices = null;
32+
this.simctl = new Simctl();
2833
}
2934

3035
public setSimulatedDevice(config: any): void {
@@ -46,38 +51,42 @@ export class XCode6Simulator extends iPhoneSimulatorBaseLib.IPhoneInteropSimulat
4651

4752
public getApplicationPath(deviceId: string, applicationIdentifier: string): IFuture<string> {
4853
return (() => {
49-
let rootApplicationsPath = path.join(osenv.home(), `/Library/Developer/CoreSimulator/Devices/${deviceId}/data/Containers/Bundle/Application`);
50-
if(!fs.existsSync(rootApplicationsPath)) {
51-
rootApplicationsPath = path.join(osenv.home(), `/Library/Developer/CoreSimulator/Devices/${deviceId}/data/Applications`);
52-
}
53-
let applicationGuids = fs.readdirSync(rootApplicationsPath);
54-
let result: string = null;
55-
_.each(applicationGuids, applicationGuid => {
56-
let fullApplicationPath = path.join(rootApplicationsPath, applicationGuid);
57-
let applicationDirContents = fs.readdirSync(fullApplicationPath);
58-
let applicationName = _.find(applicationDirContents, fileName => path.extname(fileName) === ".app");
59-
let plistFilePath = path.join(fullApplicationPath, applicationName, "Info.plist");
60-
let applicationData = this.parseFile(plistFilePath).wait();
61-
if(applicationData[0].CFBundleIdentifier === applicationIdentifier) {
62-
result = path.join(fullApplicationPath, applicationName);
63-
return false;
64-
}
65-
});
66-
67-
return result;
54+
let applications = this.getInstalledApplications(deviceId).wait();
55+
let application = _.find(applications, app => app.appIdentifier === applicationIdentifier);
56+
return application ? application.path : null;
6857
}).future<string>()();
6958
}
7059

71-
private parseFile(plistFilePath: string): IFuture<any> {
72-
let future = new Future<any>();
73-
bplistParser.parseFile(plistFilePath, (err: Error, obj: any) => {
74-
if(err) {
75-
future.throw(err);
76-
} else {
77-
future.return(obj);
78-
}
79-
});
80-
return future;
60+
public getInstalledApplications(deviceId: string): IFuture<IApplication[]> {
61+
return common.getInstalledApplications(deviceId);
62+
}
63+
64+
public installApplication(deviceId: string, applicationPath: string): IFuture<void> {
65+
return this.simctl.install(deviceId, applicationPath);
66+
}
67+
68+
public uninstallApplication(deviceId: string, appIdentifier: string): IFuture<void> {
69+
return this.simctl.uninstall(deviceId, appIdentifier);
70+
}
71+
72+
public startApplication(deviceId: string, appIdentifier: string): IFuture<string> {
73+
return this.simctl.launch(deviceId, appIdentifier);
74+
}
75+
76+
public stopApplication(deviceId: string, cfBundleExecutable: string): IFuture<string> {
77+
try {
78+
return childProcess.exec(`killall ${cfBundleExecutable}.app`);
79+
} catch(e) {
80+
}
81+
}
82+
83+
public printDeviceLog(deviceId: string): void {
84+
common.printDeviceLog(deviceId);
85+
}
86+
87+
public startSimulator(): IFuture<void> {
88+
let device = this.devices[0];
89+
return common.startSimulator(device.id);
8190
}
8291

8392
private get devices(): IDevice[] {

0 commit comments

Comments
 (0)