Skip to content

Allow tns debug with Hot Module Replacement (HMR) #4114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
45ea8ba
fix: stop forcing app restart on debug
Jul 26, 2018
1b52128
fix: allow device socket and client proxy socket reuse on iOS
DimitarTachev Oct 30, 2018
e111b79
fix: move the ios debug and livesync sockets logic in the iOS devices
DimitarTachev Nov 2, 2018
6d6bce7
refactor: remove sockets knowledge from the ios-debug-service
DimitarTachev Nov 7, 2018
9bea8ac
refactor: remove most of the emulator specific logic from the ios-deb…
DimitarTachev Nov 8, 2018
670f1c8
refactor: unify deviceId usage in ios-debug-service
DimitarTachev Nov 8, 2018
31d7d4a
refactor: keep deviceId in a single place in the device debug services
DimitarTachev Nov 8, 2018
72ab337
refactor: remove the emulator debug option
DimitarTachev Nov 8, 2018
dbd7a05
fix: validate debugBrk + hmr combo as we cannot force restarts and av…
DimitarTachev Nov 8, 2018
b1f3e76
refactor: remove debugging infos
DimitarTachev Nov 8, 2018
76d402e
reafactor: fix some error messages
DimitarTachev Nov 8, 2018
04868e2
refactor: renamed the debug services in order to follow the livesync …
DimitarTachev Nov 8, 2018
15c9db3
fix: fix PR comments
DimitarTachev Nov 14, 2018
4b6d169
fix: stop printing the debug port before ensuring a running app
DimitarTachev Nov 28, 2018
63b5192
fix: destroy the inspector socket on debug socket close in order to a…
DimitarTachev Nov 29, 2018
3f0ea54
fix: close the Inspector proxy on runtime socket close in allow creat…
DimitarTachev Dec 3, 2018
4bd9408
fix: close the inspector app on Inspector proxy `close` in order to c…
DimitarTachev Dec 3, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ $injector.require("preparePlatformNativeService", "./services/prepare-platform-n

$injector.require("debugDataService", "./services/debug-data-service");
$injector.requirePublicClass("debugService", "./services/debug-service");
$injector.require("iOSDebugService", "./services/ios-debug-service");
$injector.require("androidDebugService", "./services/android-debug-service");
$injector.require("iOSDeviceDebugService", "./services/ios-device-debug-service");
$injector.require("androidDeviceDebugService", "./services/android-device-debug-service");

$injector.require("userSettingsService", "./services/user-settings-service");
$injector.requirePublic("analyticsSettingsService", "./services/analytics-settings-service");
Expand Down Expand Up @@ -146,7 +146,7 @@ $injector.requirePublic("previewQrCodeService", "./services/livesync/playground/
$injector.requirePublic("sysInfo", "./sys-info");

$injector.require("iOSNotificationService", "./services/ios-notification-service");
$injector.require("socketProxyFactory", "./device-sockets/ios/socket-proxy-factory");
$injector.require("appDebugSocketProxyFactory", "./device-sockets/ios/app-debug-socket-proxy-factory");
$injector.require("iOSNotification", "./device-sockets/ios/notification");
$injector.require("iOSSocketRequestExecutor", "./device-sockets/ios/socket-request-executor");
$injector.require("messages", "./common/messages/messages");
Expand Down
17 changes: 13 additions & 4 deletions lib/commands/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { isInteractive } from "../common/helpers";
import { cache } from "../common/decorators";
import { DebugCommandErrors } from "../constants";
import { ValidatePlatformCommandBase } from "./command-base";
import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper";

export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand {
public allowedParameters: ICommandParameter[] = [];

constructor(private platform: string,
private $bundleValidatorHelper: IBundleValidatorHelper,
private $debugService: IDebugService,
protected $devicesService: Mobile.IDevicesService,
$platformService: IPlatformService,
Expand All @@ -21,7 +23,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements
private $prompter: IPrompter,
private $liveSyncCommandHelper: ILiveSyncCommandHelper,
private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) {
super($options, $platformsData, $platformService, $projectData);
super($options, $platformsData, $platformService, $projectData);
}

public async execute(args: string[]): Promise<void> {
Expand All @@ -36,7 +38,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements

const selectedDeviceForDebug = await this.getDeviceForDebug();

const debugData = this.$debugDataService.createDebugData(this.$projectData, {device: selectedDeviceForDebug.deviceInfo.identifier});
const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier });

if (this.$options.start) {
await this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions));
Expand Down Expand Up @@ -118,7 +120,14 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements
this.$errors.fail("--release flag is not applicable to this command");
}

const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true }});
if (this.$options.hmr && this.$options.debugBrk) {
this.$errors.fail("--debug-brk and --hmr flags cannot be combined");
}

const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null;
this.$bundleValidatorHelper.validate(minSupportedWebpackVersion);

const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true, hideSyncToPreviewAppOption: true } });
return result;
}
}
Expand Down Expand Up @@ -209,7 +218,7 @@ export class DebugAndroidCommand implements ICommand {
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $injector: IInjector,
private $projectData: IProjectData) {
this.$projectData.initializeProjectData();
this.$projectData.initializeProjectData();
}

public execute(args: string[]): Promise<void> {
Expand Down
3 changes: 3 additions & 0 deletions lib/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,7 @@ export class AndroidVirtualDevice {
static GENYMOTION_DEFAULT_STDERR_STRING = "Logging activities to file";

static UNABLE_TO_START_EMULATOR_MESSAGE = "Cannot run the app in the selected native emulator. Try to restart the adb server by running the `adb kill-server` command in the Command Prompt, or increase the allocated RAM of the virtual device through the Android Virtual Device manager. NativeScript CLI users can try to increase the timeout of the operation by adding the `--timeout` flag.";

}

export const SOCKET_CONNECTION_TIMEOUT_MS = 30000;
10 changes: 7 additions & 3 deletions lib/common/definitions/mobile.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,14 @@ declare module Mobile {
}

interface IiOSDevice extends IDevice {
connectToPort(port: number): Promise<any>;
getLiveSyncSocket(appId: string): Promise<any>;
destroyLiveSyncSocket(appId: string): void;

getDebugSocket(appId: string): Promise<any>;
destroyDebugSocket(appId: string): void;

openDeviceLogStream(options?: IiOSLogStreamOptions): Promise<void>;
destroyAllSockets(): void;
}

interface IAndroidDevice extends IDevice {
Expand All @@ -125,8 +131,6 @@ declare module Mobile {
getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService;
}

interface IiOSSimulator extends IDevice { }

/**
* Describes log stream options
*/
Expand Down
77 changes: 36 additions & 41 deletions lib/common/mobile/ios/device/ios-device.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
import * as applicationManagerPath from "./ios-application-manager";
import * as fileSystemPath from "./ios-device-file-system";
import * as constants from "../../../constants";
import * as commonConstants from "../../../constants";
import * as constants from "../../../../constants";
import * as net from "net";
import { cache } from "../../../decorators";
import * as helpers from "../../../../common/helpers";
import { IOSDeviceBase } from "../ios-device-base";

export class IOSDevice implements Mobile.IiOSDevice {
export class IOSDevice extends IOSDeviceBase {
public applicationManager: Mobile.IDeviceApplicationManager;
public fileSystem: Mobile.IDeviceFileSystem;
public deviceInfo: Mobile.IDeviceInfo;

private _socket: net.Socket;
private _deviceLogHandler: (...args: any[]) => void;

constructor(private deviceActionInfo: IOSDeviceLib.IDeviceActionInfo,
protected $errors: IErrors,
private $injector: IInjector,
private $processService: IProcessService,
protected $iOSDebuggerPortService: IIOSDebuggerPortService,
private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor,
protected $processService: IProcessService,
private $deviceLogProvider: Mobile.IDeviceLogProvider,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $iOSDeviceProductNameMapper: Mobile.IiOSDeviceProductNameMapper,
private $iosDeviceOperations: IIOSDeviceOperations,
private $mobileHelper: Mobile.IMobileHelper) {

super();
this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSApplicationManager, { device: this, devicePointer: this.deviceActionInfo });
this.fileSystem = this.$injector.resolve(fileSystemPath.IOSDeviceFileSystem, { device: this, devicePointer: this.deviceActionInfo });

const productType = deviceActionInfo.productType;
const isTablet = this.$mobileHelper.isiOSTablet(productType);
const deviceStatus = deviceActionInfo.status || constants.UNREACHABLE_STATUS;
const deviceStatus = deviceActionInfo.status || commonConstants.UNREACHABLE_STATUS;
this.deviceInfo = {
identifier: deviceActionInfo.deviceId,
vendor: "Apple",
platform: this.$devicePlatformsConstants.iOS,
status: deviceStatus,
errorHelp: deviceStatus === constants.UNREACHABLE_STATUS ? `Device ${deviceActionInfo.deviceId} is ${constants.UNREACHABLE_STATUS}` : null,
errorHelp: deviceStatus === commonConstants.UNREACHABLE_STATUS ? `Device ${deviceActionInfo.deviceId} is ${commonConstants.UNREACHABLE_STATUS}` : null,
type: "Device",
isTablet: isTablet,
displayName: this.$iOSDeviceProductNameMapper.resolveProductName(deviceActionInfo.deviceName) || deviceActionInfo.deviceName,
Expand All @@ -47,8 +50,29 @@ export class IOSDevice implements Mobile.IiOSDevice {
return false;
}

public getApplicationInfo(applicationIdentifier: string): Promise<Mobile.IApplicationInfo> {
return this.applicationManager.getApplicationInfo(applicationIdentifier);
@cache()
public async openDeviceLogStream(): Promise<void> {
if (this.deviceInfo.status !== commonConstants.UNREACHABLE_STATUS) {
this._deviceLogHandler = this.actionOnDeviceLog.bind(this);
this.$iosDeviceOperations.on(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler);
this.$iosDeviceOperations.startDeviceLog(this.deviceInfo.identifier);
}
}

protected async getSocketCore(appId: string): Promise<net.Socket> {
await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId);
const port = await this.getDebuggerPort(appId);
const deviceId = this.deviceInfo.identifier;
const socket = await helpers.connectEventuallyUntilTimeout(
async () => {
const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]);
const _socket = new net.Socket();
_socket.connect(deviceResponse.port, deviceResponse.host);
return _socket;
},
commonConstants.SOCKET_CONNECTION_TIMEOUT_MS);

return socket;
}

private actionOnDeviceLog(response: IOSDeviceLib.IDeviceLogData): void {
Expand All @@ -57,34 +81,12 @@ export class IOSDevice implements Mobile.IiOSDevice {
}
}

@cache()
public async openDeviceLogStream(): Promise<void> {
if (this.deviceInfo.status !== constants.UNREACHABLE_STATUS) {
this._deviceLogHandler = this.actionOnDeviceLog.bind(this);
this.$iosDeviceOperations.on(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler);
this.$iosDeviceOperations.startDeviceLog(this.deviceInfo.identifier);
}
}

public detach(): void {
if (this._deviceLogHandler) {
this.$iosDeviceOperations.removeListener(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler);
this.$iosDeviceOperations.removeListener(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler);
}
}

// This function works only on OSX
public async connectToPort(port: number): Promise<net.Socket> {
const deviceId = this.deviceInfo.identifier;
const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]);

const socket = new net.Socket();
socket.connect(deviceResponse.port, deviceResponse.host);
this._socket = socket;

this.$processService.attachToProcessExitSignals(this, this.destroySocket);
return this._socket;
}

private getActiveArchitecture(productType: string): string {
let activeArchitecture = "";
if (productType) {
Expand All @@ -106,13 +108,6 @@ export class IOSDevice implements Mobile.IiOSDevice {

return activeArchitecture;
}

private destroySocket() {
if (this._socket) {
this._socket.destroy();
this._socket = null;
}
}
}

$injector.register("iOSDevice", IOSDevice);
81 changes: 81 additions & 0 deletions lib/common/mobile/ios/ios-device-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as net from "net";

export abstract class IOSDeviceBase implements Mobile.IiOSDevice {
private cachedSockets: IDictionary<net.Socket> = {};
protected abstract $errors: IErrors;
protected abstract $iOSDebuggerPortService: IIOSDebuggerPortService;
protected abstract $processService: IProcessService;
abstract deviceInfo: Mobile.IDeviceInfo;
abstract applicationManager: Mobile.IDeviceApplicationManager;
abstract fileSystem: Mobile.IDeviceFileSystem;
abstract isEmulator: boolean;
abstract openDeviceLogStream(): Promise<void>;

public getApplicationInfo(applicationIdentifier: string): Promise<Mobile.IApplicationInfo> {
return this.applicationManager.getApplicationInfo(applicationIdentifier);
}

public async getLiveSyncSocket(appId: string): Promise<net.Socket> {
return this.getSocket(appId);
}

public async getDebugSocket(appId: string): Promise<net.Socket> {
return this.getSocket(appId);
}

public async getSocket(appId: string): Promise<net.Socket> {
if (this.cachedSockets[appId]) {
return this.cachedSockets[appId];
}

this.cachedSockets[appId] = await this.getSocketCore(appId);

if (this.cachedSockets[appId]) {
this.cachedSockets[appId].on("close", () => {
this.destroySocket(appId);
});

this.$processService.attachToProcessExitSignals(this, () => this.destroySocket(appId));
}

return this.cachedSockets[appId];
}

public destroyLiveSyncSocket(appId: string) {
this.destroySocket(appId);
}

public destroyDebugSocket(appId: string) {
this.destroySocket(appId);
}

protected abstract async getSocketCore(appId: string): Promise<net.Socket>;

protected async getDebuggerPort(appId: string): Promise<number> {
const port = await this.$iOSDebuggerPortService.getPort({ deviceId: this.deviceInfo.identifier, appId });
if (!port) {
this.$errors.failWithoutHelp("Device socket port cannot be found.");
}

return port;
}

public destroyAllSockets() {
for (const appId in this.cachedSockets) {
this.destroySocketSafe(this.cachedSockets[appId]);
}

this.cachedSockets = {};
}

private destroySocket(appId: string) {
this.destroySocketSafe(this.cachedSockets[appId]);
this.cachedSockets[appId] = null;
}

private destroySocketSafe(socket: net.Socket) {
if (socket && !socket.destroyed) {
socket.destroy();
}
}
}
9 changes: 3 additions & 6 deletions lib/common/mobile/ios/simulator/ios-emulator-services.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import * as net from "net";
import { connectEventuallyUntilTimeout } from "../../../helpers";
import { APPLE_VENDOR_NAME, DeviceTypes, RUNNING_EMULATOR_STATUS, NOT_RUNNING_EMULATOR_STATUS } from "../../../constants";

class IosEmulatorServices implements Mobile.IiOSSimulatorService {
private static DEFAULT_TIMEOUT = 10000;

constructor(private $logger: ILogger,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $iOSSimResolver: Mobile.IiOSSimResolver,
Expand Down Expand Up @@ -72,7 +69,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {

public async connectToPort(data: Mobile.IConnectToPortData): Promise<net.Socket> {
try {
const socket = await connectEventuallyUntilTimeout(async () => net.connect(data.port), data.timeout || IosEmulatorServices.DEFAULT_TIMEOUT);
const socket = net.connect(data.port);
return socket;
} catch (e) {
this.$logger.debug(e);
Expand All @@ -85,7 +82,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {

const output = await this.tryGetiOSSimDevices();
if (output.devices && output.devices.length) {
devices = _(output.devices)
devices = _(output.devices)
.map(simDevice => this.convertSimDeviceToDeviceInfo(simDevice))
.sortBy(deviceInfo => deviceInfo.version)
.value();
Expand All @@ -102,7 +99,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {
return [];
}

private async tryGetiOSSimDevices(): Promise<{devices: Mobile.IiSimDevice[], error: string}> {
private async tryGetiOSSimDevices(): Promise<{ devices: Mobile.IiSimDevice[], error: string }> {
let devices: Mobile.IiSimDevice[] = [];
let error: string = null;

Expand Down
Loading