Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit d335e73

Browse files
FatmeFatme
Fatme
authored and
Fatme
committed
Merge pull request #404 from telerik/fatme/livesync-ios-sim
LiveSync support for iOS simulator
2 parents 665ade9 + e99dca3 commit d335e73

File tree

7 files changed

+171
-14
lines changed

7 files changed

+171
-14
lines changed

bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,4 @@ $injector.requireCommand("dev-preuninstall", "./common/commands/preuninstall");
9292
$injector.requireCommand("doctor", "./common/commands/doctor");
9393

9494
$injector.require("utils", "./common/utils");
95+
$injector.require("bplistParser", "./common/bplist-parser");

bplist-parser.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
4+
import Future = require("fibers/future");
5+
let bplistParser = require('bplist-parser');
6+
7+
export class BPlistParser implements IBinaryPlistParser{
8+
constructor() { }
9+
10+
public parseFile(plistFilePath: string): IFuture<any> {
11+
let future = new Future<any>();
12+
bplistParser.parseFile(plistFilePath, (err: any, obj: any) => {
13+
if(err) {
14+
future.throw(err);
15+
} else {
16+
future.return(obj);
17+
}
18+
});
19+
return future;
20+
}
21+
}
22+
$injector.register("bplistParser", BPlistParser);

declarations.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ interface IUsbLiveSyncServiceBase {
267267
sync(platform: string, appIdentifier: string, projectFilesPath: string, excludedProjectDirsAndFiles: string[], watchGlob: any,
268268
restartAppOnDeviceAction: (device: Mobile.IDevice, deviceAppData: Mobile.IDeviceAppData) => IFuture<void>,
269269
notInstalledAppOnDeviceAction: (device: Mobile.IDevice) => IFuture<void>,
270+
notRunningiOSSimulatorAction: () => IFuture<void>,
271+
localProjectRootPath?: string,
270272
beforeLiveSyncAction?: (device: Mobile.IDevice, deviceAppData: Mobile.IDeviceAppData) => IFuture<void>,
271273
beforeBatchLiveSyncAction?: (filePath: string) => IFuture<string>): IFuture<void>;
272274
}
@@ -455,3 +457,6 @@ interface IUtils {
455457
getParsedTimeout(defaultTimeout: number): number;
456458
getMilliSecondsTimeout(defaultTimeout: number): number;
457459
}
460+
interface IBinaryPlistParser {
461+
parseFile(plistFilePath: string): IFuture<any>;
462+
}

definitions/bplist-parser.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
declare module "bplist-parser" {
22
export function parseBuffer(buff: NodeBuffer): any;
3+
export function parseFile(plistFilePath: string): any;
34
}

definitions/mobile.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ declare module Mobile {
287287

288288
interface IiOSSimulatorService extends IEmulatorPlatformServices {
289289
postDarwinNotification(notification: string): IFuture<void>;
290+
sync(appIdentifier: string, projectFilesPath: string, notRunningSimulatorAction: () => IFuture<void>): IFuture<void>;
291+
syncFiles(appIdentifier: string, projectFilesPath: string, projectFiles: string[], notRunningSimulatorAction: () => IFuture<void>): IFuture<void>;
292+
isSimulatorRunning(): IFuture<boolean>;
290293
}
291294

292295
interface IEmulatorSettingsService {

mobile/ios/ios-emulator-services.ts

+110-4
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
///<reference path="../../../.d.ts"/>
22
"use strict";
33

4-
import util = require("util");
54
import Future = require("fibers/future");
5+
import osenv = require("osenv");
6+
import path = require("path");
7+
import shell = require("shelljs");
8+
import util = require("util");
69

710
class IosEmulatorServices implements Mobile.IiOSSimulatorService {
11+
private _cachedSimulatorId: string;
12+
813
constructor(private $logger: ILogger,
914
private $emulatorSettingsService: Mobile.IEmulatorSettingsService,
1015
private $errors: IErrors,
1116
private $childProcess: IChildProcess,
1217
private $mobileHelper: Mobile.IMobileHelper,
1318
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
1419
private $hostInfo: IHostInfo,
15-
private $options: IOptions) { }
20+
private $options: IOptions,
21+
private $fs: IFileSystem,
22+
private $bplistParser: IBinaryPlistParser) { }
1623

1724
public checkDependencies(): IFuture<void> {
1825
return (() => {
1926
}).future<void>()();
2027
}
2128

22-
checkAvailability(dependsOnProject: boolean = true): IFuture<void> {
29+
public checkAvailability(dependsOnProject: boolean = true): IFuture<void> {
2330
return (() => {
2431
if(!this.$hostInfo.isDarwin) {
2532
this.$errors.fail("iOS Simulator is available only on Mac OS X.");
@@ -32,7 +39,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {
3239
}).future<void>()();
3340
}
3441

35-
startEmulator(app: string, emulatorOptions?: Mobile.IEmulatorOptions): IFuture<void> {
42+
public startEmulator(app: string, emulatorOptions?: Mobile.IEmulatorOptions): IFuture<void> {
3643
return (() => {
3744
this.killLaunchdSim().wait();
3845
this.startEmulatorCore(app, emulatorOptions);
@@ -51,6 +58,30 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {
5158

5259
return this.$childProcess.exec(`${nodeCommandName} ${iosSimPath} ${opts.join(' ')}`);
5360
}
61+
62+
public sync(appIdentifier: string, projectFilesPath: string, notRunningSimulatorAction: () => IFuture<void>): IFuture<void> {
63+
let syncAction = (applicationPath: string) => shell.cp("-Rf", projectFilesPath, applicationPath);
64+
return this.syncCore(appIdentifier, notRunningSimulatorAction, syncAction);
65+
}
66+
67+
public syncFiles(appIdentifier: string, projectFilesPath: string, projectFiles: string[], notRunningSimulatorAction: () => IFuture<void>): IFuture<void> {
68+
let syncAction = (applicationPath: string) => _.each(projectFiles, projectFile => {
69+
this.$logger.trace(`Transfering ${projectFile} to ${path.join(applicationPath, "app")}`);
70+
shell.cp("-Rf", projectFile, path.join(applicationPath, "app"));
71+
});
72+
return this.syncCore(appIdentifier, notRunningSimulatorAction, syncAction);
73+
}
74+
75+
public isSimulatorRunning(): IFuture<boolean> {
76+
return (() => {
77+
try {
78+
let output = this.$childProcess.exec("ps cax | grep launchd_sim").wait();
79+
return output.indexOf('launchd_sim') !== -1;
80+
} catch(e) {
81+
return false;
82+
}
83+
}).future<boolean>()();
84+
}
5485

5586
private killLaunchdSim(): IFuture<void> {
5687
this.$logger.info("Cleaning up before starting the iOS Simulator");
@@ -104,5 +135,80 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {
104135

105136
this.$childProcess.spawn(nodeCommandName, opts, { stdio: "inherit" });
106137
}
138+
139+
private getRunningSimulatorId(appIdentifier: string): IFuture<string> {
140+
return ((): string => {
141+
if(this.$options.device) {
142+
this._cachedSimulatorId = this.$options.device;
143+
}
144+
145+
if(!this._cachedSimulatorId) {
146+
let output = this.$childProcess.exec("xcrun simctl list").wait();
147+
let lines = output.split("\n");
148+
let regex = /[\s\S]+?\(([0-9A-F\-]+?)\)\s+?\(Booted\)/;
149+
_.each(lines, (line: string) => {
150+
let match: any = regex.exec(line);
151+
if(match) {
152+
this._cachedSimulatorId = match[1];
153+
return false;
154+
}
155+
});
156+
157+
if(!this._cachedSimulatorId) {
158+
regex = /[\s\S]+?\(([0-9A-F\-]+?)\)\s+?\(Shutdown\)/;
159+
_.each(lines, (line: string) => {
160+
let match: any = regex.exec(line);
161+
if(match) {
162+
this._cachedSimulatorId = match[1];
163+
return false;
164+
}
165+
});
166+
}
167+
}
168+
169+
return this._cachedSimulatorId;
170+
}).future<string>()();
171+
}
172+
173+
private getApplicationPath(appIdentifier: string, runningSimulatorId: string): IFuture<string> {
174+
return (() => {
175+
let rootApplicationsPath = path.join(osenv.home(), `/Library/Developer/CoreSimulator/Devices/${runningSimulatorId}/data/Containers/Bundle/Application`);
176+
let applicationGuids = this.$fs.readDirectory(rootApplicationsPath).wait();
177+
let result: string = null;
178+
_.each(applicationGuids, applicationGuid => {
179+
let fullApplicationPath = path.join(rootApplicationsPath, applicationGuid);
180+
let applicationDirContents = this.$fs.readDirectory(fullApplicationPath).wait();
181+
let applicationName = _.find(applicationDirContents, fileName => path.extname(fileName) === ".app");
182+
let plistFilePath = path.join(fullApplicationPath, applicationName, "Info.plist");
183+
let applicationData = this.$bplistParser.parseFile(plistFilePath).wait();
184+
if(applicationData[0].CFBundleIdentifier === appIdentifier) {
185+
result = path.join(fullApplicationPath, applicationName);
186+
return false;
187+
}
188+
});
189+
190+
return result;
191+
}).future<string>()();
192+
}
193+
194+
private syncCore(appIdentifier: string, notRunningSimulatorAction: () => IFuture<void>, syncAction: (applicationPath: string) => void): IFuture<void> {
195+
return (() => {
196+
if(!this.isSimulatorRunning().wait()) {
197+
notRunningSimulatorAction().wait();
198+
}
199+
200+
let runningSimulatorId = this.getRunningSimulatorId(appIdentifier).wait();
201+
let applicationPath = this.getApplicationPath(appIdentifier, runningSimulatorId).wait();
202+
syncAction(applicationPath);
203+
204+
try {
205+
this.$childProcess.exec("killall -KILL launchd_sim").wait();
206+
this.$childProcess.exec(`xcrun simctl launch ${runningSimulatorId} ${appIdentifier}`).wait();
207+
} catch(e) {
208+
this.$logger.trace("Unable to kill simulator: " + e);
209+
}
210+
211+
}).future<void>()();
212+
}
107213
}
108214
$injector.register("iOSEmulatorServices", IosEmulatorServices);

services/usb-livesync-service-base.ts

+29-10
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,41 @@ export class UsbLiveSyncServiceBase implements IUsbLiveSyncServiceBase {
2424
protected $options: IOptions,
2525
private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory,
2626
private $fs: IFileSystem,
27-
private $dispatcher: IFutureDispatcher) { }
27+
private $dispatcher: IFutureDispatcher,
28+
protected $childProcess: IChildProcess,
29+
protected $iOSEmulatorServices: Mobile.IiOSSimulatorService) { }
2830

2931
public initialize(platform: string): IFuture<string> {
3032
return (() => {
31-
this.$devicesServices.initialize({ platform: platform, deviceId: this.$options.device }).wait();
32-
this._initialized = true;
33-
return this.$devicesServices.platform;
33+
if(!this.$options.emulator) {
34+
this.$devicesServices.initialize({ platform: platform, deviceId: this.$options.device }).wait();
35+
this._initialized = true;
36+
return this.$devicesServices.platform;
37+
}
3438
}).future<string>()();
3539
}
3640

3741
public sync(platform: string, appIdentifier: string, projectFilesPath: string, excludedProjectDirsAndFiles: string[], watchGlob: any,
3842
restartAppOnDeviceAction: (device: Mobile.IDevice, deviceAppData: Mobile.IDeviceAppData, localToDevicePaths?: Mobile.ILocalToDevicePathData[]) => IFuture<void>,
3943
notInstalledAppOnDeviceAction: (device: Mobile.IDevice) => IFuture<void>,
44+
notRunningiOSSimulatorAction: () => IFuture<void>,
45+
localProjectRootPath?: string,
4046
beforeLiveSyncAction?: (device: Mobile.IDevice, deviceAppData: Mobile.IDeviceAppData) => IFuture<void>,
4147
beforeBatchLiveSyncAction?: (filePath: string) => IFuture<string>): IFuture<void> {
4248
return (() => {
43-
if(!this._initialized) {
49+
if(!this._initialized && !this.$options.emulator) {
4450
this.initialize(platform).wait();
4551
}
4652

47-
let projectFiles = this.$fs.enumerateFilesInDirectorySync(projectFilesPath, (filePath, stat) => !this.isFileExcluded(path.relative(projectFilesPath, filePath), excludedProjectDirsAndFiles, projectFilesPath), { enumerateDirectories: true});
48-
this.syncCore(projectFiles, appIdentifier, projectFilesPath, restartAppOnDeviceAction, notInstalledAppOnDeviceAction).wait();
53+
let isiOSSimulatorRunning = this.$iOSEmulatorServices.isSimulatorRunning().wait();
54+
if(isiOSSimulatorRunning || this.$options.emulator) {
55+
this.$iOSEmulatorServices.sync(appIdentifier, projectFilesPath, notRunningiOSSimulatorAction).wait();
56+
}
57+
58+
if(!this.$options.emulator) {
59+
let projectFiles = this.$fs.enumerateFilesInDirectorySync(projectFilesPath, (filePath, stat) => !this.isFileExcluded(path.relative(projectFilesPath, filePath), excludedProjectDirsAndFiles, projectFilesPath), { enumerateDirectories: true});
60+
this.syncCore(projectFiles, appIdentifier, localProjectRootPath || projectFilesPath, restartAppOnDeviceAction, notInstalledAppOnDeviceAction).wait();
61+
}
4962

5063
if(this.$options.watch) {
5164
let __this = this;
@@ -54,14 +67,20 @@ export class UsbLiveSyncServiceBase implements IUsbLiveSyncServiceBase {
5467
this.on('all', (event: string, filePath: string) => {
5568
if(event === "added" || event === "changed") {
5669
if(!_.contains(excludedProjectDirsAndFiles, filePath)) {
57-
__this.batchLiveSync(filePath, appIdentifier, projectFilesPath, restartAppOnDeviceAction, notInstalledAppOnDeviceAction, beforeLiveSyncAction, beforeBatchLiveSyncAction);
70+
if(isiOSSimulatorRunning || __this.$options.emulator) {
71+
__this.$dispatcher.dispatch(() => __this.$iOSEmulatorServices.syncFiles(appIdentifier, projectFilesPath, [filePath], notRunningiOSSimulatorAction));
72+
}
73+
74+
if(!__this.$options.emulator) {
75+
__this.batchLiveSync(filePath, appIdentifier, projectFilesPath, restartAppOnDeviceAction, notInstalledAppOnDeviceAction, beforeLiveSyncAction, beforeBatchLiveSyncAction);
76+
}
5877
}
5978
}
6079
});
6180
});
6281

6382
this.$dispatcher.run();
64-
}
83+
}
6584
}).future<void>()();
6685
}
6786

@@ -129,7 +148,7 @@ export class UsbLiveSyncServiceBase implements IUsbLiveSyncServiceBase {
129148
}, 500);
130149
}
131150
this.$dispatcher.dispatch( () => (() => { this.syncQueue.push(beforeBatchLiveSyncAction(filePath).wait()) }).future<void>()());
132-
}
151+
}
133152

134153
private isFileExcluded(path: string, exclusionList: string[], projectDir: string): boolean {
135154
return !!_.find(exclusionList, (pattern) => minimatch(path, pattern, { nocase: true }));

0 commit comments

Comments
 (0)