From 549519440ecba6b4ad894b5a0685f8a94822a404 Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Fri, 14 Jul 2017 18:16:11 +0300 Subject: [PATCH 1/9] Separate preparation of JS from native code * Introduce new parameter prepareNative to indicate that native code preparation is explicitly required. * Cache additional data in .nsprepareinfo file: * nativePlatformStatus - indicates whether native platform should be added, prepared or there's no need to do so; * projectFileHash - saves package.json file's contents hash for the current platform. We use this for better heuristics for checking whether package.json is changed. * Re-factoring i.e. extract common code to methods --- lib/bootstrap.ts | 1 + lib/commands/debug.ts | 113 ++++------ lib/commands/run.ts | 131 +++--------- lib/common | 2 +- lib/constants.ts | 6 + lib/definitions/livesync.d.ts | 14 ++ lib/definitions/platform.d.ts | 3 +- lib/definitions/plugins.d.ts | 1 + lib/definitions/project-changes.d.ts | 17 +- lib/definitions/project.d.ts | 4 + lib/services/android-project-service.ts | 2 +- lib/services/ios-project-service.ts | 13 +- .../livesync/livesync-command-helper.ts | 81 +++++++ lib/services/livesync/livesync-service.ts | 39 ++-- lib/services/platform-service.ts | 202 ++++++++++-------- lib/services/plugins-service.ts | 2 +- lib/services/project-changes-service.ts | 49 ++++- .../node-modules/node-modules-builder.ts | 27 ++- .../node-modules/node-modules-dest-copy.ts | 28 ++- test/debug.ts | 14 +- test/plugin-prepare.ts | 2 +- test/project-changes-service.ts | 6 +- test/stubs.ts | 6 +- 23 files changed, 452 insertions(+), 311 deletions(-) create mode 100644 lib/services/livesync/livesync-command-helper.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ef4717e4eb..1888b34cfe 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -105,6 +105,7 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); +$injector.requirePublicClass("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); $injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 7c090d2eb3..face4457ad 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -2,22 +2,23 @@ import { isInteractive } from "../common/helpers"; import { DebugCommandErrors } from "../constants"; -export abstract class DebugPlatformCommand implements ICommand { +export class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public platform: string; - constructor(private debugService: IPlatformDebugService, - private $devicesService: Mobile.IDevicesService, - private $debugDataService: IDebugDataService, + constructor(protected $devicesService: Mobile.IDevicesService, protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, protected $platformsData: IPlatformsData, protected $logger: ILogger, protected $errors: IErrors, + private debugService: IPlatformDebugService, + private $debugDataService: IDebugDataService, private $debugLiveSyncService: IDebugLiveSyncService, private $config: IConfiguration, - private $prompter: IPrompter) { + private $prompter: IPrompter, + private $liveSyncCommandHelper: ILiveSyncCommandHelper) { this.$projectData.initializeProjectData(); } @@ -36,40 +37,7 @@ export abstract class DebugPlatformCommand implements ICommand { const selectedDeviceForDebug = await this.getDeviceForDebug(); - const deviceDescriptors: ILiveSyncDeviceInfo[] = [selectedDeviceForDebug] - .map(d => { - const info: ILiveSyncDeviceInfo = { - identifier: d.deviceInfo.identifier, - buildAction: async (): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword - }; - - await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); - const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - return pathToBuildResult; - } - }; - - return info; - }); - - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch || this.$options.justlaunch, - watchAllFiles: this.$options.syncAllFiles - }; - + const { deviceDescriptors, liveSyncInfo } = await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(args, [selectedDeviceForDebug]); await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } @@ -149,23 +117,22 @@ export abstract class DebugPlatformCommand implements ICommand { } } -export class DebugIOSCommand extends DebugPlatformCommand { +export class DebugIOSCommand implements ICommand { + private get debugPlatformCommand(): DebugPlatformCommand { + return this.$injector.resolve(DebugPlatformCommand); + } + + public allowedParameters: ICommandParameter[] = []; + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $logger: ILogger, - $iOSDebugService: IPlatformDebugService, - $devicesService: Mobile.IDevicesService, - $config: IConfiguration, - $debugDataService: IDebugDataService, - $platformService: IPlatformService, - $options: IOptions, - $projectData: IProjectData, - $platformsData: IPlatformsData, - $iosDeviceOperations: IIOSDeviceOperations, - $debugLiveSyncService: IDebugLiveSyncService, - $prompter: IPrompter) { - super($iOSDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config, $prompter); + private $platformService: IPlatformService, + private $options: IOptions, + private $injector: IInjector, + private $projectData: IProjectData, + private $platformsData: IPlatformsData, + $iosDeviceOperations: IIOSDeviceOperations) { + // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -173,12 +140,16 @@ export class DebugIOSCommand extends DebugPlatformCommand { $iosDeviceOperations.setShouldDispose(false); } + public execute(args: string[]): Promise { + return this.debugPlatformCommand.execute(args); + } + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } public platform = this.$devicePlatformsConstants.iOS; @@ -186,26 +157,26 @@ export class DebugIOSCommand extends DebugPlatformCommand { $injector.registerCommand("debug|ios", DebugIOSCommand); -export class DebugAndroidCommand extends DebugPlatformCommand { +export class DebugAndroidCommand implements ICommand { + private get debugPlatformCommand(): DebugPlatformCommand { + return this.$injector.resolve(DebugPlatformCommand); + } + + public allowedParameters: ICommandParameter[] = []; + constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $logger: ILogger, - $androidDebugService: IPlatformDebugService, - $devicesService: Mobile.IDevicesService, - $config: IConfiguration, - $debugDataService: IDebugDataService, - $platformService: IPlatformService, - $options: IOptions, - $projectData: IProjectData, - $platformsData: IPlatformsData, - $debugLiveSyncService: IDebugLiveSyncService, - $prompter: IPrompter) { - super($androidDebugService, $devicesService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger, - $errors, $debugLiveSyncService, $config, $prompter); + private $platformService: IPlatformService, + private $options: IOptions, + private $injector: IInjector, + private $projectData: IProjectData, + private $platformsData: IPlatformsData) { } + + public execute(args: string[]): Promise { + return this.debugPlatformCommand.execute(args); } - public async canExecute(args: string[]): Promise { - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); + return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.Android); } public platform = this.$devicePlatformsConstants.Android; diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 548eacffb9..f5b8be691d 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -7,15 +7,13 @@ export class RunCommandBase implements ICommand { protected $liveSyncService: ILiveSyncService, protected $projectData: IProjectData, protected $options: IOptions, - protected $emulatorPlatformService: IEmulatorPlatformService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - private $devicesService: Mobile.IDevicesService, + protected $devicesService: Mobile.IDevicesService, + protected $platformsData: IPlatformsData, private $hostInfo: IHostInfo, - private $iosDeviceOperations: IIOSDeviceOperations, - private $mobileHelper: Mobile.IMobileHelper, - protected $platformsData: IPlatformsData) { - } + private $liveSyncCommandHelper: ILiveSyncCommandHelper + ) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { @@ -47,6 +45,8 @@ export class RunCommandBase implements ICommand { this.$options.watch = false; } + this.platform = args[0] || this.platform; + await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, @@ -56,93 +56,33 @@ export class RunCommandBase implements ICommand { }); await this.$devicesService.detectCurrentlyAttachedDevices(); - - const devices = this.$devicesService.getDeviceInstances(); - // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !this.platform || d.deviceInfo.platform === this.platform) - .map(d => { - const info: ILiveSyncDeviceInfo = { - identifier: d.deviceInfo.identifier, - buildAction: async (): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword - }; - - await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); - const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); - return pathToBuildResult; - } - }; - - return info; - }); - - const workingWithiOSDevices = !this.platform || this.$mobileHelper.isiOSPlatform(this.platform); - const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; - if (workingWithiOSDevices && shouldKeepProcessAlive) { - this.$iosDeviceOperations.setShouldDispose(false); - } - - if (this.$options.release || this.$options.bundle) { - const runPlatformOptions: IRunPlatformOptions = { - device: this.$options.device, - emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch - }; - - const deployOptions = _.merge({ - projectDir: this.$projectData.projectDir, - clean: true, - }, this.$options.argv); - - await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); - await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); - return this.$platformService.trackProjectType(this.$projectData); - } - - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch, - watchAllFiles: this.$options.syncAllFiles, - clean: this.$options.clean - }; - + let devices = this.$devicesService.getDeviceInstances(); + devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); + const { deviceDescriptors, liveSyncInfo } = await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(args, devices); + liveSyncInfo.skipNativePrepare = true; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } $injector.registerCommand("run|*all", RunCommandBase); -export class RunIosCommand extends RunCommandBase implements ICommand { +export class RunIosCommand implements ICommand { + private get runCommand(): RunCommandBase { + return this.$injector.resolve(RunCommandBase); + } + public allowedParameters: ICommandParameter[] = []; public get platform(): string { return this.$devicePlatformsConstants.iOS; } - constructor($platformService: IPlatformService, - protected $platformsData: IPlatformsData, + constructor(protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - $liveSyncService: ILiveSyncService, - $projectData: IProjectData, - $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService, - $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo, - $iosDeviceOperations: IIOSDeviceOperations, - $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, - $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); + private $injector: IInjector, + private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions) { } public async execute(args: string[]): Promise { @@ -150,44 +90,41 @@ export class RunIosCommand extends RunCommandBase implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return this.executeCore([this.$platformsData.availablePlatforms.iOS]); + return this.runCommand.executeCore([this.$platformsData.availablePlatforms.iOS]); } public async canExecute(args: string[]): Promise { - return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } $injector.registerCommand("run|ios", RunIosCommand); -export class RunAndroidCommand extends RunCommandBase implements ICommand { +export class RunAndroidCommand implements ICommand { + private get runCommand(): RunCommandBase { + return this.$injector.resolve(RunCommandBase); + } + public allowedParameters: ICommandParameter[] = []; public get platform(): string { return this.$devicePlatformsConstants.Android; } - constructor($platformService: IPlatformService, - protected $platformsData: IPlatformsData, + constructor(protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $errors: IErrors, - $liveSyncService: ILiveSyncService, - $projectData: IProjectData, - $options: IOptions, - $emulatorPlatformService: IEmulatorPlatformService, - $devicesService: Mobile.IDevicesService, - $hostInfo: IHostInfo, - $iosDeviceOperations: IIOSDeviceOperations, - $mobileHelper: Mobile.IMobileHelper) { - super($platformService, $liveSyncService, $projectData, $options, $emulatorPlatformService, $devicePlatformsConstants, $errors, - $devicesService, $hostInfo, $iosDeviceOperations, $mobileHelper, $platformsData); + private $injector: IInjector, + private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions) { } public async execute(args: string[]): Promise { - return this.executeCore([this.$platformsData.availablePlatforms.Android]); + return this.runCommand.executeCore([this.$platformsData.availablePlatforms.Android]); } public async canExecute(args: string[]): Promise { - await super.canExecute(args); + await this.runCommand.canExecute(args); if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } diff --git a/lib/common b/lib/common index d9491f33cb..9468b4b709 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d9491f33cbdb991caca18a94f5dc2a97eba4111b +Subproject commit 9468b4b7098792ca0e38ce2aa009b2a2f3795172 diff --git a/lib/constants.ts b/lib/constants.ts index d58e856ae4..d7512a2f78 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -92,3 +92,9 @@ export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; } + +export const enum NativePlatformStatus { + requiresPlatformAdd = "1", + requiresPrepare = "2", + alreadyPrepared = "3" +} diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 472c76cc50..a6ae536f17 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -120,6 +120,11 @@ interface ILiveSyncInfo { * Forces a build before the initial livesync. */ clean?: boolean; + + /** + * Whether to skip preparing the native platform. + */ + skipNativePrepare?: boolean; } interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } @@ -249,3 +254,12 @@ interface IDevicePathProvider { getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; getDeviceSyncZipPath(device: Mobile.IDevice): string; } + +interface ILiveSyncCommandHelper { + getDevicesLiveSyncInfo(args: string[], devices: Mobile.IDevice[]): Promise; +} + +interface IDevicesDescriptorsLiveSyncInfo { + deviceDescriptors: ILiveSyncDeviceInfo[] + liveSyncInfo: ILiveSyncInfo +} diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 57450d94f7..5645e49f21 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -47,7 +47,7 @@ interface IPlatformService extends NodeJS.EventEmitter { * @param {Array} filesToSync Files about to be synced to device. * @returns {boolean} true indicates that the platform was prepared. */ - preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise; + preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise; /** * Determines whether a build is necessary. A build is necessary when one of the following is true: @@ -288,6 +288,7 @@ interface IPlatformsData { interface INodeModulesBuilder { prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; + prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise; cleanNodeModules(absoluteOutputPath: string, platform: string): void; } diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 24926cd12a..7feb3d5bc6 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -4,6 +4,7 @@ interface IPluginsService { prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData): Promise; getAllInstalledPlugins(projectData: IProjectData): Promise; ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise; + preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void /** * Returns all dependencies and devDependencies from pacakge.json file. diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index ceefe2b371..a8b07074d4 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -1,14 +1,14 @@ -interface IPrepareInfo { +interface IPrepareInfo extends IAddedNativePlatform { time: string; bundle: boolean; release: boolean; + projectFileHash: string; changesRequireBuild: boolean; changesRequireBuildTime: string; - iOSProvisioningProfileUUID?: string; } -interface IProjectChangesInfo { +interface IProjectChangesInfo extends IAddedNativePlatform { appFilesChanged: boolean; appResourcesChanged: boolean; modulesChanged: boolean; @@ -22,12 +22,19 @@ interface IProjectChangesInfo { readonly changesRequirePrepare: boolean; } -interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision {} +interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision { + nativePlatformStatus?: "1" | "2" | "3"; +} interface IProjectChangesService { checkForChanges(platform: string, projectData: IProjectData, buildOptions: IProjectChangesOptions): IProjectChangesInfo; getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo; savePrepareInfo(platform: string, projectData: IProjectData): void; getPrepareInfoFilePath(platform: string, projectData: IProjectData): string; + ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean; currentChanges: IProjectChangesInfo; -} \ No newline at end of file +} + +interface IAddedNativePlatform { + nativePlatformStatus: "1" | "2" | "3"; +} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index a0aa63b25a..847d6ee221 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -132,6 +132,10 @@ interface IBuildForDevice { buildForDevice: boolean; } +interface INativePrepare { + skip: boolean; +} + interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig { projectDir: string; clean?: boolean; diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 170bae39d9..4dabcefe6d 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -221,7 +221,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject let manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectId, manifestPath); if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion).toString(); + const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index b684faec92..db60e0f23f 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -876,7 +876,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { this.$logger.info("Creating project scheme..."); - await this.checkIfXcodeprojIsRequired(); let createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; @@ -1123,13 +1122,11 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(projectFile, ""); } - if (this.$hostInfo.isDarwin) { - await this.checkIfXcodeprojIsRequired(); - let escapedProjectFile = projectFile.replace(/'/g, "\\'"), - escapedPluginFile = pluginFile.replace(/'/g, "\\'"), - mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; - await this.$childProcess.exec(`ruby -e "${mergeScript}"`); - } + await this.checkIfXcodeprojIsRequired(); + let escapedProjectFile = projectFile.replace(/'/g, "\\'"), + escapedPluginFile = pluginFile.replace(/'/g, "\\'"), + mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; + await this.$childProcess.exec(`ruby -e "${mergeScript}"`); } private async mergeProjectXcconfigFiles(release: boolean, projectData: IProjectData): Promise { diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts new file mode 100644 index 0000000000..570a1a2852 --- /dev/null +++ b/lib/services/livesync/livesync-command-helper.ts @@ -0,0 +1,81 @@ +export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { + protected platform: string; + + constructor(protected $platformService: IPlatformService, + protected $projectData: IProjectData, + protected $options: IOptions, + protected $devicesService: Mobile.IDevicesService, + private $iosDeviceOperations: IIOSDeviceOperations, + private $mobileHelper: Mobile.IMobileHelper) { + } + + public async getDevicesLiveSyncInfo(args: string[], devices: Mobile.IDevice[]): Promise { + await this.$devicesService.detectCurrentlyAttachedDevices(); + // Now let's take data for each device: + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + .map(d => { + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction: async (): Promise => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); + const result = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); + return result; + } + }; + + return info; + }); + + const workingWithiOSDevices = !this.platform || this.$mobileHelper.isiOSPlatform(this.platform); + const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; + if (workingWithiOSDevices && shouldKeepProcessAlive) { + this.$iosDeviceOperations.setShouldDispose(false); + } + + // TODO: test it, might have isuues with `tns run` (no args) + if (this.$options.release || this.$options.bundle) { + const runPlatformOptions: IRunPlatformOptions = { + device: this.$options.device, + emulator: this.$options.emulator, + justlaunch: this.$options.justlaunch + }; + + const deployOptions = _.merge({ + projectDir: this.$projectData.projectDir, + clean: true, + }, this.$options.argv); + + await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); + await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); + this.$platformService.trackProjectType(this.$projectData); + } + + const liveSyncInfo: ILiveSyncInfo = { + projectDir: this.$projectData.projectDir, + skipWatcher: !this.$options.watch, + watchAllFiles: this.$options.syncAllFiles, + clean: this.$options.clean + }; + + return { + deviceDescriptors, + liveSyncInfo + }; + } +} + +$injector.register("liveSyncCommandHelper", LiveSyncCommandHelper); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 4e2b289977..7c357eea68 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -154,7 +154,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } - private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { + private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { const platform = options.device.deviceInfo.platform; if (options.preparedPlatforms.indexOf(platform) === -1) { options.preparedPlatforms.push(platform); @@ -162,15 +162,11 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await this.$platformService.preparePlatform(platform, { bundle: false, release: false, - }, null, options.projectData, {}, options.modifiedFiles); + }, null, options.projectData, {}, options.modifiedFiles, nativePrepare); } - const rebuildInfo = _.find(options.rebuiltInformation, info => info.isEmulator === options.device.isEmulator && info.platform === platform); - - if (rebuildInfo) { - // Case where we have three devices attached, a change that requires build is found, - // we'll rebuild the app only for the first device, but we should install new package on all three devices. - await this.$platformService.installApplication(options.device, { release: false }, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + const buildResult = await this.installedCachedAppPackage(platform, options); + if (buildResult) { return; } @@ -185,6 +181,15 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { action = LiveSyncTrackActionNames.LIVESYNC_OPERATION_BUILD; } + await this.trackAction(action, platform, options); + + const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); + if (shouldInstall) { + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + } + } + + private async trackAction(action: string, platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { if (!options.settings[platform][options.device.deviceInfo.type]) { let isForDevice = !options.device.isEmulator; options.settings[platform][options.device.deviceInfo.type] = true; @@ -198,11 +203,19 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !options.device.isEmulator, deviceOsVersion: options.device.deviceInfo.version }); + } - const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options.deviceBuildInfoDescriptor.outputPath); - if (shouldInstall) { - await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { + const rebuildInfo = _.find(options.rebuiltInformation, info => info.isEmulator === options.device.isEmulator && info.platform === platform); + + if (rebuildInfo) { + // Case where we have three devices attached, a change that requires build is found, + // we'll rebuild the app only for the first device, but we should install new package on all three devices. + await this.$platformService.installApplication(options.device, { release: false }, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); + return rebuildInfo.pathToBuildItem; } + + return null; } private async initialSync(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { @@ -224,7 +237,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, liveSyncData, settings - }); + }, { skip: liveSyncData.skipNativePrepare }); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -326,7 +339,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, settings: latestAppPackageInstalledSettings, modifiedFiles: allModifiedFiles - }); + }, { skip: liveSyncData.skipNativePrepare }); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index fb68a435b6..18bc4d1fcd 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -39,8 +39,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $npm: INodePackageManager, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $projectChangesService: IProjectChangesService, - private $analyticsService: IAnalyticsService, - private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { + private $analyticsService: IAnalyticsService) { super(); } @@ -59,10 +58,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { - let platformsDir = projectData.platformsDir; + const platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); for (let platform of platforms) { + this.validatePlatform(platform, projectData); + const platformPath = path.join(projectData.platformsDir, platform); + + if (this.$fs.exists(platformPath)) { + this.$errors.failWithoutHelp(`Platform ${platform} already added`); + } + await this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, config, frameworkPath); } } @@ -78,19 +84,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { return version; } - private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string): Promise { + private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { let data = platformParam.split("@"), platform = data[0].toLowerCase(), version = data[1]; - this.validatePlatform(platform, projectData); - - let platformPath = path.join(projectData.platformsDir, platform); - - if (this.$fs.exists(platformPath)) { - this.$errors.failWithoutHelp("Platform %s already added", platform); - } - let platformData = this.$platformsData.getPlatformData(platform, projectData); if (version === undefined) { @@ -116,15 +114,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { npmOptions["version"] = version; } - let spinner = new clui.Spinner("Installing " + packageToInstall); - let projectDir = projectData.projectDir; + const spinner = new clui.Spinner("Installing " + packageToInstall); + const projectDir = projectData.projectDir; + const platformPath = path.join(projectData.platformsDir, platform); + try { spinner.start(); let downloadedPackagePath = await this.$npmInstallationManager.install(packageToInstall, projectDir, npmOptions); let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); frameworkDir = path.resolve(frameworkDir); - let coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config); + const coreModuleName = await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config, nativePrepare); await this.$npm.uninstall(coreModuleName, { save: true }, projectData.projectDir); } catch (err) { this.$fs.deleteDirectory(platformPath); @@ -137,17 +137,15 @@ export class PlatformService extends EventEmitter implements IPlatformService { } - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { - let coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); - let installedVersion = coreModuleData.version; - let coreModuleName = coreModuleData.name; - - let customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); + private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + const coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); + const installedVersion = coreModuleData.version; + const customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; - await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); - platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await platformData.platformProjectService.interpolateData(projectData, config); - platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + + if (!nativePrepare || !nativePrepare.skip) { + await this.addPlatformCoreNative(platformData, frameworkDir, installedVersion, projectData, config); + } let frameworkPackageNameData: any = { version: installedVersion }; if (customTemplateOptions) { @@ -156,10 +154,18 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); + const coreModuleName = coreModuleData.name; return coreModuleName; } + private async addPlatformCoreNative(platformData: IPlatformData, frameworkDir: string, installedVersion: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); + platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); + await platformData.platformProjectService.interpolateData(projectData, config); + platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + } + private async getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string, projectDir: string): Promise<{ selectedTemplate: string, pathToTemplate: string }> { if (!selectedTemplate) { // read data from package.json's nativescript key @@ -207,35 +213,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { public getPreparedPlatforms(projectData: IProjectData): string[] { return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } + public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const changesInfo = await this.initialPrepare(platform, platformData, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare); + const requiresNativePrepare = (!nativePrepare || !nativePrepare.skip) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; - public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array): Promise { - this.validatePlatform(platform, projectData); - - await this.trackProjectType(projectData); - - //We need dev-dependencies here, so before-prepare hooks will be executed correctly. - try { - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - } catch (err) { - this.$logger.trace(err); - this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); - } - - let platformData = this.$platformsData.getPlatformData(platform, projectData); - await this.$pluginsService.validate(platformData, projectData); - - const bundle = appFilesUpdaterOptions.bundle; - - await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config); - let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision }); - - this.$logger.trace("Changes info in prepare platform:", changesInfo); - - if (changesInfo.hasChanges) { - await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); - } - if (changesInfo.hasChanges || bundle) { - await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync); + if (changesInfo.hasChanges || appFilesUpdaterOptions.bundle || requiresNativePrepare) { + await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync, nativePrepare); this.$projectChangesService.savePrepareInfo(platform, projectData); } else { this.$logger.out("Skipping prepare."); @@ -281,15 +265,56 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } + private async initialPrepare(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + this.validatePlatform(platform, projectData); + + await this.trackProjectType(projectData); + + //We need dev-dependencies here, so before-prepare hooks will be executed correctly. + try { + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + } catch (err) { + this.$logger.trace(err); + this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); + } + + await this.$pluginsService.validate(platformData, projectData); + await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config, nativePrepare); + + const bundle = appFilesUpdaterOptions.bundle; + const nativePlatformStatus = (nativePrepare && nativePrepare.skip) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; + const changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision, nativePlatformStatus }); + + this.$logger.trace("Changes info in prepare platform:", changesInfo); + return changesInfo; + } + /* Hooks are expected to use "filesToSync" parameter, as to give plugin authors additional information about the sync process.*/ @helpers.hook('prepare') - private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array): Promise { + private async preparePlatformCore(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { this.$logger.out("Preparing project..."); let platformData = this.$platformsData.getPlatformData(platform, projectData); + await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + + if (!nativePrepare || !nativePrepare.skip) { + await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); + } + let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; + if (!changesInfo || !changesInfo.modulesChanged) { + excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); + } + + this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs); + + this.$logger.out(`Project successfully prepared (${platform})`); + } + + private async preparePlatformCoreJS(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo, filesToSync?: Array, ): Promise { if (!changesInfo || changesInfo.appFilesChanged) { - await this.copyAppFiles(platform, appFilesUpdaterOptions, projectData); + await this.copyAppFiles(platformData, appFilesUpdaterOptions, projectData); // remove the App_Resources folder from the app/assets as here we're applying other files changes. const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -299,45 +324,41 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - if (!changesInfo || changesInfo.changesRequirePrepare) { - await this.copyAppFiles(platform, appFilesUpdaterOptions, projectData); - this.copyAppResources(platform, projectData); - await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); + if (!changesInfo || changesInfo.modulesChanged) { + await this.copyTnsModules(platform, platformData, projectData); } + } - if (!changesInfo || changesInfo.modulesChanged) { - await this.copyTnsModules(platform, projectData); - } else if (appFilesUpdaterOptions.bundle) { - let dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - for (let dependencyKey in dependencies) { - const dependency = dependencies[dependencyKey]; - let isPlugin = !!dependency.nativescript; - if (isPlugin) { - let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); - await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); - } - } + public async preparePlatformCoreNative(platform: string, platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData, platformSpecificData: IPlatformSpecificData, changesInfo?: IProjectChangesInfo): Promise { + if (changesInfo.hasChanges) { + await this.cleanProject(platform, appFilesUpdaterOptions, platformData, projectData); } - let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; - if (!changesInfo || !changesInfo.modulesChanged) { - excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); + if (!changesInfo || changesInfo.changesRequirePrepare) { + await this.copyAppFiles(platformData, appFilesUpdaterOptions, projectData); + this.copyAppResources(platformData, projectData); + await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); } - this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs); + if (changesInfo && !changesInfo.modulesChanged && appFilesUpdaterOptions.bundle) { + let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); + let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; + + let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); + // Process node_modules folder + await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + } if (!changesInfo || changesInfo.configChanged || changesInfo.modulesChanged) { await platformData.platformProjectService.processConfigurationFilesFromAppResources(appFilesUpdaterOptions.release, projectData); } platformData.platformProjectService.interpolateConfigurationFile(projectData, platformSpecificData); - - this.$logger.out("Project successfully prepared (" + platform + ")"); + this.$projectChangesService.ensurePrepareInfo(platform, projectData, + { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared, release: appFilesUpdaterOptions.release, bundle: appFilesUpdaterOptions.bundle, provision: platformSpecificData.provision }); } - private async copyAppFiles(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private async copyAppFiles(platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -351,8 +372,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { }); } - private copyAppResources(platform: string, projectData: IProjectData): void { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private copyAppResources(platformData: IPlatformData, projectData: IProjectData): void { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); if (this.$fs.exists(appResourcesDirectoryPath)) { @@ -364,15 +384,14 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - private async copyTnsModules(platform: string, projectData: IProjectData): Promise { - let platformData = this.$platformsData.getPlatformData(platform, projectData); + private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData): Promise { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; try { let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); // Process node_modules folder - await this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); + await this.$nodeModulesBuilder.prepareJSNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData); } catch (error) { this.$logger.debug(error); shell.rm("-rf", appDestinationDirectoryPath); @@ -715,9 +734,18 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions): Promise { + public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, nativePrepare?: INativePrepare): Promise { + let requiresNativePlatformAdd = false; + if (!this.isPlatformInstalled(platform, projectData)) { - await this.addPlatform(platform, platformTemplate, projectData, config); + await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + } else { + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skip; + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + requiresNativePlatformAdd = !prepareInfo || prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; + if (requiresNativePlatformAdd && shouldAddNativePlatform) { + await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + } } } diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 38faf585e5..57d07d132b 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -122,7 +122,7 @@ export class PluginsService implements IPluginsService { } } - private preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void { + public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData): void { let platformData = this.$platformsData.getPlatformData(platform, projectData); let pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); let scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index c28b4272b8..b28aaf9f40 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,5 +1,6 @@ import * as path from "path"; -import { NODE_MODULES_FOLDER_NAME } from "../constants"; +import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME } from "../constants"; +import { getHash } from "../common/helpers"; const prepareInfoFileName = ".nsprepareinfo"; @@ -12,6 +13,7 @@ class ProjectChangesInfo implements IProjectChangesInfo { public packageChanged: boolean; public nativeChanged: boolean; public signingChanged: boolean; + public nativePlatformStatus: NativePlatformStatus; public get hasChanges(): boolean { return this.packageChanged || @@ -58,7 +60,7 @@ export class ProjectChangesService implements IProjectChangesService { if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) { this._newFiles = 0; this._changesInfo.appFilesChanged = this.containsNewerFiles(projectData.appDirectoryPath, projectData.appResourcesDirectoryPath, projectData); - this._changesInfo.packageChanged = this.filesChanged([path.join(projectData.projectDir, "package.json")]); + this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform); this._changesInfo.appResourcesChanged = this.containsNewerFiles(projectData.appResourcesDirectoryPath, null, projectData); /*done because currently all node_modules are traversed, a possible improvement could be traversing only the production dependencies*/ this._changesInfo.nativeChanged = this.containsNewerFiles( @@ -72,8 +74,8 @@ export class ProjectChangesService implements IProjectChangesService { let platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); if (platform === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), - path.join(platformResourcesDir, "LaunchScreen.storyboard"), - path.join(platformResourcesDir, "build.xcconfig") + path.join(platformResourcesDir, "LaunchScreen.storyboard"), + path.join(platformResourcesDir, "build.xcconfig") ]); } else { this._changesInfo.configChanged = this.filesChanged([ @@ -107,6 +109,9 @@ export class ProjectChangesService implements IProjectChangesService { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } } + + this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; + return this._changesInfo; } @@ -134,31 +139,64 @@ export class ProjectChangesService implements IProjectChangesService { this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } - private ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { + public ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { this._prepareInfo = this.getPrepareInfo(platform, projectData); if (this._prepareInfo) { + this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ? + projectChangesOptions.nativePlatformStatus : + this._prepareInfo.nativePlatformStatus || projectChangesOptions.nativePlatformStatus; + let platformData = this.$platformsData.getPlatformData(platform, projectData); let prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); this._outputProjectMtime = this.$fs.getFsStats(prepareInfoFile).mtime.getTime(); this._outputProjectCTime = this.$fs.getFsStats(prepareInfoFile).ctime.getTime(); + this.savePrepareInfo(platform, projectData); return false; } + this._prepareInfo = { time: "", + nativePlatformStatus: projectChangesOptions.nativePlatformStatus, bundle: projectChangesOptions.bundle, release: projectChangesOptions.release, changesRequireBuild: true, + projectFileHash: this.getProjectFileStrippedHash(projectData, platform), changesRequireBuildTime: null }; + this._outputProjectMtime = 0; this._outputProjectCTime = 0; + this._changesInfo = this._changesInfo || new ProjectChangesInfo(); this._changesInfo.appFilesChanged = true; this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; + + this.savePrepareInfo(platform, projectData); return true; } + private getProjectFileStrippedHash(projectData: IProjectData, platform: string): string { + platform = platform.toLowerCase(); + const projectFilePath = path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME); + const projectFileContents = this.$fs.readJson(projectFilePath); + _(this.$devicePlatformsConstants) + .keys() + .map(k => k.toLowerCase()) + .difference([platform]) + .each(otherPlatform => { + delete projectFileContents.nativescript[`tns-${otherPlatform}`]; + }); + + return getHash(JSON.stringify(projectFileContents)); + } + + private isProjectFileChanged(projectData: IProjectData, platform: string): boolean { + const projectFileStrippedContentsHash = this.getProjectFileStrippedHash(projectData, platform); + const prepareInfo = this.getPrepareInfo(platform, projectData); + return projectFileStrippedContentsHash !== prepareInfo.projectFileHash; + } + private filesChanged(files: string[]): boolean { for (let file of files) { if (this.$fs.exists(file)) { @@ -168,6 +206,7 @@ export class ProjectChangesService implements IProjectChangesService { } } } + return false; } diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index ac1556151d..0143d437ab 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -9,13 +9,29 @@ export class NodeModulesBuilder implements INodeModulesBuilder { ) { } public async prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); + const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); + await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData); + } + + public async prepareJSNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData): Promise { + const productionDependencies = this.initialPrepareNodeModules(absoluteOutputPath, platform, lastModifiedTime, projectData); + const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); + await npmPluginPrepare.prepareJSPlugins(productionDependencies, platform, projectData); + } + + public cleanNodeModules(absoluteOutputPath: string, platform: string): void { + shelljs.rm("-rf", absoluteOutputPath); + } + + private initialPrepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime: Date, projectData: IProjectData, ): IDependencyData[] { + const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + if (!this.$fs.exists(absoluteOutputPath)) { // Force copying if the destination doesn't exist. lastModifiedTime = null; } - let productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - if (!this.$options.bundle) { const tnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { outputRoot: absoluteOutputPath @@ -25,12 +41,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { this.cleanNodeModules(absoluteOutputPath, platform); } - const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.preparePlugins(productionDependencies, platform, projectData); - } - - public cleanNodeModules(absoluteOutputPath: string, platform: string): void { - shelljs.rm("-rf", absoluteOutputPath); + return productionDependencies; } } diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index a66fb2eebe..78d19c607c 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -81,7 +81,8 @@ export class NpmPluginPrepare { constructor( private $fs: IFileSystem, private $pluginsService: IPluginsService, - private $platformsData: IPlatformsData + private $platformsData: IPlatformsData, + private $logger: ILogger ) { } @@ -146,10 +147,33 @@ export class NpmPluginPrepare { const dependency = dependencies[dependencyKey]; let isPlugin = !!dependency.nativescript; if (isPlugin) { - await this.$pluginsService.prepare(dependency, platform, projectData); + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); } } await this.afterPrepare(dependencies, platform, projectData); } + + public async prepareJSPlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { + if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { + return; + } + + for (let dependencyKey in dependencies) { + const dependency = dependencies[dependencyKey]; + let isPlugin = !!dependency.nativescript; + if (isPlugin) { + platform = platform.toLowerCase(); + let pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + let platformData = this.$platformsData.getPlatformData(platform, projectData); + let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); + if (appFolderExists) { + this.$pluginsService.preparePluginScripts(pluginData, platform, projectData); + // Show message + this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + } + } + } + } } diff --git a/test/debug.ts b/test/debug.ts index d204cb3576..e3e856fbe7 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -1,6 +1,6 @@ import * as stubs from "./stubs"; import * as yok from "../lib/common/yok"; -import { DebugAndroidCommand } from "../lib/commands/debug"; +import { DebugAndroidCommand, DebugPlatformCommand } from "../lib/commands/debug"; import { assert } from "chai"; import { Configuration, StaticConfig } from "../lib/config"; import { Options } from "../lib/options"; @@ -78,7 +78,7 @@ describe("debug command tests", () => { const testInjector = createTestInjector(); const options = testInjector.resolve("options"); options.forDevice = options.emulator = true; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); }); @@ -95,7 +95,7 @@ describe("debug command tests", () => { const options = testInjector.resolve("options"); options.device = specifiedDeviceOption; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); const selectedDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(selectedDeviceInstance, deviceInstance); }); @@ -111,7 +111,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => getDeviceInstancesResult; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); }; @@ -184,7 +184,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance]; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance); }); @@ -243,7 +243,7 @@ describe("debug command tests", () => { return choices[1]; }; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); const expectedChoicesPassedToPrompter = [deviceInstance1, deviceInstance2].map(d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}`); assert.deepEqual(choicesPassedToPrompter, expectedChoicesPassedToPrompter); @@ -304,7 +304,7 @@ describe("debug command tests", () => { devicesService.getDeviceInstances = (): Mobile.IDevice[] => deviceInstances; - const debugCommand = testInjector.resolveCommand("debug|android"); + const debugCommand = testInjector.resolve(DebugPlatformCommand); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance2); diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index 45f3664147..6c07491663 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -7,7 +7,7 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { public preparedDependencies: IDictionary = {}; constructor(private previouslyPrepared: IDictionary) { - super(null, null, null); + super(null, null, null, null); } protected getPreviouslyPreparedDependencies(platform: string): IDictionary { diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 5fab8b9598..f29bb52f78 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -113,13 +113,15 @@ describe("Project Changes Service Tests", () => { // arrange const prepareInfoPath = path.join(serviceTest.projectDir, Constants.PLATFORMS_DIR_NAME, platform, ".nsprepareinfo"); - const expectedPrepareInfo = { + const expectedPrepareInfo: IPrepareInfo = { time: new Date().toString(), bundle: true, release: false, changesRequireBuild: true, changesRequireBuildTime: new Date().toString(), - iOSProvisioningProfileUUID: "provisioning_profile_test" + iOSProvisioningProfileUUID: "provisioning_profile_test", + projectFileHash: "", + nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd }; fs.writeJson(prepareInfoPath, expectedPrepareInfo); diff --git a/test/stubs.ts b/test/stubs.ts index 8fa4e100a7..95be4208f2 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -206,7 +206,7 @@ export class ErrorsStub implements IErrors { } export class NpmInstallationManagerStub implements INpmInstallationManager { - async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions ): Promise { + async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions): Promise { return Promise.resolve(""); } @@ -574,6 +574,10 @@ export class ProjectChangesService implements IProjectChangesService { public get currentChanges(): IProjectChangesInfo { return {}; } + + public ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { + return true; + } } export class CommandsService implements ICommandsService { From 3caaf098dd98e5d68354bf1d5bea3a34fd642d84 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 18 Jul 2017 23:30:54 +0300 Subject: [PATCH 2/9] Fix debug command and unit tests --- lib/commands/debug.ts | 19 +++++++++++-------- lib/common | 2 +- test/debug.ts | 17 +++++++++++------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index face4457ad..7797776e7b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -4,22 +4,21 @@ import { DebugCommandErrors } from "../constants"; export class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - public platform: string; - constructor(protected $devicesService: Mobile.IDevicesService, + constructor(private debugService: IPlatformDebugService, + private platform: string, + protected $devicesService: Mobile.IDevicesService, protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, protected $platformsData: IPlatformsData, protected $logger: ILogger, protected $errors: IErrors, - private debugService: IPlatformDebugService, private $debugDataService: IDebugDataService, private $debugLiveSyncService: IDebugLiveSyncService, private $config: IConfiguration, private $prompter: IPrompter, private $liveSyncCommandHelper: ILiveSyncCommandHelper) { - this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -119,7 +118,7 @@ export class DebugPlatformCommand implements ICommand { export class DebugIOSCommand implements ICommand { private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand); + return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$iOSDebugService, platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -131,8 +130,9 @@ export class DebugIOSCommand implements ICommand { private $injector: IInjector, private $projectData: IProjectData, private $platformsData: IPlatformsData, + private $iOSDebugService: IDebugService, $iosDeviceOperations: IIOSDeviceOperations) { - + this.$projectData.initializeProjectData(); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. @@ -159,7 +159,7 @@ $injector.registerCommand("debug|ios", DebugIOSCommand); export class DebugAndroidCommand implements ICommand { private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand); + return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$androidDebugService, platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -170,7 +170,10 @@ export class DebugAndroidCommand implements ICommand { private $options: IOptions, private $injector: IInjector, private $projectData: IProjectData, - private $platformsData: IPlatformsData) { } + private $platformsData: IPlatformsData, + private $androidDebugService: IDebugService) { + this.$projectData.initializeProjectData(); + } public execute(args: string[]): Promise { return this.debugPlatformCommand.execute(args); diff --git a/lib/common b/lib/common index 9468b4b709..c82bd612b1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 9468b4b7098792ca0e38ce2aa009b2a2f3795172 +Subproject commit c82bd612b157919d0ac97e14eff686804ff33830 diff --git a/test/debug.ts b/test/debug.ts index e3e856fbe7..0128757a82 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -68,6 +68,11 @@ function createTestInjector(): IInjector { testInjector.register("prompter", {}); testInjector.registerCommand("debug|android", DebugAndroidCommand); + testInjector.register("liveSyncCommandHelper", { + getDevicesLiveSyncInfo: async (): Promise => { + return { deviceDescriptors: [], liveSyncInfo: null }; + } + }); return testInjector; } @@ -78,7 +83,7 @@ describe("debug command tests", () => { const testInjector = createTestInjector(); const options = testInjector.resolve("options"); options.forDevice = options.emulator = true; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR); }); @@ -95,7 +100,7 @@ describe("debug command tests", () => { const options = testInjector.resolve("options"); options.device = specifiedDeviceOption; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const selectedDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(selectedDeviceInstance, deviceInstance); }); @@ -111,7 +116,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => getDeviceInstancesResult; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); }; @@ -184,7 +189,7 @@ describe("debug command tests", () => { const devicesService = testInjector.resolve("devicesService"); devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance]; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance); }); @@ -243,7 +248,7 @@ describe("debug command tests", () => { return choices[1]; }; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); const expectedChoicesPassedToPrompter = [deviceInstance1, deviceInstance2].map(d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}`); assert.deepEqual(choicesPassedToPrompter, expectedChoicesPassedToPrompter); @@ -304,7 +309,7 @@ describe("debug command tests", () => { devicesService.getDeviceInstances = (): Mobile.IDevice[] => deviceInstances; - const debugCommand = testInjector.resolve(DebugPlatformCommand); + const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" }); const actualDeviceInstance = await debugCommand.getDeviceForDebug(); assert.deepEqual(actualDeviceInstance, deviceInstance2); From b40c92bb6b8a5120794b132dd232e8aa4cb33a99 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 02:00:39 +0300 Subject: [PATCH 3/9] Fix npm support tests Fix the tests by modifying current logic: - stop using ensurePrepareInfo for setting `nativePlatformStatus` property - introduce new method for setting it. The problem with using `ensurePrepareInfo` is that it saves the prepareInfo and calling checkForChanges after that is incorrect in case we modify the `bundle` option. Check test: `Ensures that tns_modules absent when bundling` - the last case was failing because when we call ensurePrepareInfo last time, bundle is false, we overwrite the prepareInfo in the file and checkForChanges says that bundle is currently false and has been false in the previous case. - in case prepareInfo is missing, we should not call addPlatform again - the else in `ensurePlatformInstalled` is not correct in case we execute `tns platform add ` and then try `tns run/build/prepare ` - in this case we do not have prepareInfo, so we try adding platform again. However, due to current implementation, we are sure that when we do not have prepareInfo, we've called platform add from CLI, so the platform is already added correctly. - fix a strange if inside `preparePlatformCoreNative` - we must prepare the node_modules in case `--bundle` is passed or the modulesChanged property of `changesInfo` is true. --- lib/definitions/project-changes.d.ts | 2 +- lib/services/platform-service.ts | 9 +++++---- lib/services/project-changes-service.ts | 11 ++++++++--- test/stubs.ts | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index a8b07074d4..83e3bfb405 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -31,7 +31,7 @@ interface IProjectChangesService { getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo; savePrepareInfo(platform: string, projectData: IProjectData): void; getPrepareInfoFilePath(platform: string, projectData: IProjectData): string; - ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean; + setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void; currentChanges: IProjectChangesInfo; } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 18bc4d1fcd..8d40d2cfdc 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -340,7 +340,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await platformData.platformProjectService.prepareProject(projectData, platformSpecificData); } - if (changesInfo && !changesInfo.modulesChanged && appFilesUpdaterOptions.bundle) { + if (!changesInfo || changesInfo.modulesChanged || appFilesUpdaterOptions.bundle) { let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; @@ -354,8 +354,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } platformData.platformProjectService.interpolateConfigurationFile(projectData, platformSpecificData); - this.$projectChangesService.ensurePrepareInfo(platform, projectData, - { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared, release: appFilesUpdaterOptions.release, bundle: appFilesUpdaterOptions.bundle, provision: platformSpecificData.provision }); + this.$projectChangesService.setNativePlatformStatus(platform, projectData, + { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); } private async copyAppFiles(platformData: IPlatformData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectData: IProjectData): Promise { @@ -742,7 +742,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { } else { const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skip; const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - requiresNativePlatformAdd = !prepareInfo || prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; + // In case there's no prepare info, it means only platform add had been executed. So we've come from CLI and we do not need to prepare natively. + requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; if (requiresNativePlatformAdd && shouldAddNativePlatform) { await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index b28aaf9f40..a9a2383784 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -139,6 +139,14 @@ export class ProjectChangesService implements IProjectChangesService { this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } + public setNativePlatformStatus(platform: string, projectData: IProjectData, addedPlatform: IAddedNativePlatform): void { + this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platform, projectData); + if (this._prepareInfo) { + this._prepareInfo.nativePlatformStatus = addedPlatform.nativePlatformStatus; + this.savePrepareInfo(platform, projectData); + } + } + public ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { this._prepareInfo = this.getPrepareInfo(platform, projectData); if (this._prepareInfo) { @@ -150,7 +158,6 @@ export class ProjectChangesService implements IProjectChangesService { let prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); this._outputProjectMtime = this.$fs.getFsStats(prepareInfoFile).mtime.getTime(); this._outputProjectCTime = this.$fs.getFsStats(prepareInfoFile).ctime.getTime(); - this.savePrepareInfo(platform, projectData); return false; } @@ -171,8 +178,6 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; - - this.savePrepareInfo(platform, projectData); return true; } diff --git a/test/stubs.ts b/test/stubs.ts index 95be4208f2..e642b24ef6 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -575,7 +575,7 @@ export class ProjectChangesService implements IProjectChangesService { return {}; } - public ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { + public setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): boolean { return true; } } From a05083522a249fb5d9633309d4f1d4c5f71ae538 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 02:35:11 +0300 Subject: [PATCH 4/9] Fix tests requiring package.json at the root of the project As projectChangesService now checks the package.json at the root of the project, several tests are failing as they are mocking the project creation and there's no package.json at the specified place. Fix this by creating the package.json in the tests. --- test/platform-service.ts | 9 +++++++++ test/project-changes-service.ts | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/test/platform-service.ts b/test/platform-service.ts index 94ae456ac5..d017e07c76 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -49,6 +49,9 @@ function createTestInjector() { testInjector.register("nodeModulesBuilder", { prepareNodeModules: () => { return Promise.resolve(); + }, + prepareJSNodeModules: () => { + return Promise.resolve(); } }); testInjector.register("pluginsService", { @@ -382,6 +385,12 @@ describe('Platform Service Tests', () => { let appDestFolderPath = path.join(tempFolder, "appDest"); let appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); + fs.writeJson(path.join(tempFolder, "package.json"), { + name: "testname", + nativescript: { + id: "org.nativescript.testname" + } + }); return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; } diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index f29bb52f78..3593400340 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -32,6 +32,13 @@ class ProjectChangesServiceTest extends BaseServiceTest { this.injector.register("devicePlatformsConstants", {}); this.injector.register("projectChangesService", ProjectChangesService); + const fs = this.injector.resolve("fs"); + fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), { + nativescript: { + id: "org.nativescript.test" + } + }); + } get projectChangesService(): IProjectChangesService { From 2ca30c1caa3c9d5e16c17207e3e6a3e5799b25c8 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 02:36:49 +0300 Subject: [PATCH 5/9] Remove incorrect passing of skipNativePrepare --- lib/commands/run.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index f5b8be691d..90c7250691 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -59,7 +59,6 @@ export class RunCommandBase implements ICommand { let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); const { deviceDescriptors, liveSyncInfo } = await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(args, devices); - liveSyncInfo.skipNativePrepare = true; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } } From a7f0a9dad304fb5aeba0adc6fc8fcbfd9c93918b Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 08:55:05 +0300 Subject: [PATCH 6/9] Cache instances of "base" commands in debug and run --- lib/commands/debug.ts | 5 +++++ lib/commands/run.ts | 5 +++++ lib/services/project-changes-service.ts | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 7797776e7b..94fc68dd4b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,5 +1,6 @@ import { CONNECTED_STATUS } from "../common/constants"; import { isInteractive } from "../common/helpers"; +import { cache } from "../common/decorators"; import { DebugCommandErrors } from "../constants"; export class DebugPlatformCommand implements ICommand { @@ -117,6 +118,8 @@ export class DebugPlatformCommand implements ICommand { } export class DebugIOSCommand implements ICommand { + + @cache() private get debugPlatformCommand(): DebugPlatformCommand { return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$iOSDebugService, platform: this.platform }); } @@ -158,6 +161,8 @@ export class DebugIOSCommand implements ICommand { $injector.registerCommand("debug|ios", DebugIOSCommand); export class DebugAndroidCommand implements ICommand { + + @cache() private get debugPlatformCommand(): DebugPlatformCommand { return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$androidDebugService, platform: this.platform }); } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 90c7250691..c2e5885425 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -1,4 +1,5 @@ import { ERROR_NO_VALID_SUBCOMMAND_FORMAT } from "../common/constants"; +import { cache } from "../common/decorators"; export class RunCommandBase implements ICommand { protected platform: string; @@ -66,6 +67,8 @@ export class RunCommandBase implements ICommand { $injector.registerCommand("run|*all", RunCommandBase); export class RunIosCommand implements ICommand { + + @cache() private get runCommand(): RunCommandBase { return this.$injector.resolve(RunCommandBase); } @@ -100,6 +103,8 @@ export class RunIosCommand implements ICommand { $injector.registerCommand("run|ios", RunIosCommand); export class RunAndroidCommand implements ICommand { + + @cache() private get runCommand(): RunCommandBase { return this.$injector.resolve(RunCommandBase); } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index a9a2383784..77c6d536ee 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -147,7 +147,7 @@ export class ProjectChangesService implements IProjectChangesService { } } - public ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { + private ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean { this._prepareInfo = this.getPrepareInfo(platform, projectData); if (this._prepareInfo) { this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ? From 69a01e3d9db912ce9a8d3dc9e44da326a516fc58 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 09:55:14 +0300 Subject: [PATCH 7/9] Fix run android and run ios - set the platform correctly --- lib/commands/run.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands/run.ts b/lib/commands/run.ts index c2e5885425..3281875d43 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -27,6 +27,8 @@ export class RunCommandBase implements ICommand { } this.$projectData.initializeProjectData(); + this.platform = args[0] || this.platform; + if (!this.platform && !this.$hostInfo.isDarwin) { this.platform = this.$devicePlatformsConstants.Android; } @@ -46,8 +48,6 @@ export class RunCommandBase implements ICommand { this.$options.watch = false; } - this.platform = args[0] || this.platform; - await this.$devicesService.initialize({ deviceId: this.$options.device, platform: this.platform, From f4aa067377124ddf70442e7115e10be02fdb4ae6 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jul 2017 11:41:51 +0300 Subject: [PATCH 8/9] Fix run command Fix run command by setting correct platform. --- lib/commands/debug.ts | 3 +- lib/commands/run.ts | 18 +++-- lib/definitions/livesync.d.ts | 7 +- .../livesync/livesync-command-helper.ts | 66 ++++++++++--------- test/debug.ts | 4 +- 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 94fc68dd4b..5c1ff1b5a7 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -37,8 +37,7 @@ export class DebugPlatformCommand implements ICommand { const selectedDeviceForDebug = await this.getDeviceForDebug(); - const { deviceDescriptors, liveSyncInfo } = await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(args, [selectedDeviceForDebug]); - await this.$debugLiveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); } public async getDeviceForDebug(): Promise { diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 3281875d43..b966d73a3c 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -2,8 +2,8 @@ import { ERROR_NO_VALID_SUBCOMMAND_FORMAT } from "../common/constants"; import { cache } from "../common/decorators"; export class RunCommandBase implements ICommand { - protected platform: string; + public platform: string; constructor(protected $platformService: IPlatformService, protected $liveSyncService: ILiveSyncService, protected $projectData: IProjectData, @@ -59,8 +59,7 @@ export class RunCommandBase implements ICommand { await this.$devicesService.detectCurrentlyAttachedDevices(); let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); - const { deviceDescriptors, liveSyncInfo } = await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(args, devices); - await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncCommandHelper.getDevicesLiveSyncInfo(devices, this.$liveSyncService, this.platform); } } @@ -70,7 +69,9 @@ export class RunIosCommand implements ICommand { @cache() private get runCommand(): RunCommandBase { - return this.$injector.resolve(RunCommandBase); + const runCommand = this.$injector.resolve(RunCommandBase); + runCommand.platform = this.platform; + return runCommand; } public allowedParameters: ICommandParameter[] = []; @@ -92,7 +93,7 @@ export class RunIosCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - return this.runCommand.executeCore([this.$platformsData.availablePlatforms.iOS]); + return this.runCommand.executeCore(args); } public async canExecute(args: string[]): Promise { @@ -106,7 +107,9 @@ export class RunAndroidCommand implements ICommand { @cache() private get runCommand(): RunCommandBase { - return this.$injector.resolve(RunCommandBase); + const runCommand = this.$injector.resolve(RunCommandBase); + runCommand.platform = this.platform; + return runCommand; } public allowedParameters: ICommandParameter[] = []; @@ -124,11 +127,12 @@ export class RunAndroidCommand implements ICommand { } public async execute(args: string[]): Promise { - return this.runCommand.executeCore([this.$platformsData.availablePlatforms.Android]); + return this.runCommand.executeCore(args); } public async canExecute(args: string[]): Promise { await this.runCommand.canExecute(args); + if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index a6ae536f17..d420e15a64 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -256,10 +256,5 @@ interface IDevicePathProvider { } interface ILiveSyncCommandHelper { - getDevicesLiveSyncInfo(args: string[], devices: Mobile.IDevice[]): Promise; -} - -interface IDevicesDescriptorsLiveSyncInfo { - deviceDescriptors: ILiveSyncDeviceInfo[] - liveSyncInfo: ILiveSyncInfo + getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; } diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index 570a1a2852..e37dd20b5f 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -1,16 +1,28 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { - protected platform: string; constructor(protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, protected $devicesService: Mobile.IDevicesService, private $iosDeviceOperations: IIOSDeviceOperations, - private $mobileHelper: Mobile.IMobileHelper) { + private $mobileHelper: Mobile.IMobileHelper, + private $platformsData: IPlatformsData) { } - public async getDevicesLiveSyncInfo(args: string[], devices: Mobile.IDevice[]): Promise { + public async getDevicesLiveSyncInfo(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { await this.$devicesService.detectCurrentlyAttachedDevices(); + + const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); + const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; + if (workingWithiOSDevices && shouldKeepProcessAlive) { + this.$iosDeviceOperations.setShouldDispose(false); + } + + if (this.$options.release || this.$options.bundle) { + await this.runInReleaseMode(platform); + return; + } + // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceInfo[] = devices .map(d => { @@ -40,30 +52,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return info; }); - const workingWithiOSDevices = !this.platform || this.$mobileHelper.isiOSPlatform(this.platform); - const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; - if (workingWithiOSDevices && shouldKeepProcessAlive) { - this.$iosDeviceOperations.setShouldDispose(false); - } - - // TODO: test it, might have isuues with `tns run` (no args) - if (this.$options.release || this.$options.bundle) { - const runPlatformOptions: IRunPlatformOptions = { - device: this.$options.device, - emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch - }; - - const deployOptions = _.merge({ - projectDir: this.$projectData.projectDir, - clean: true, - }, this.$options.argv); - - await this.$platformService.deployPlatform(args[0], this.$options, deployOptions, this.$projectData, this.$options); - await this.$platformService.startApplication(args[0], runPlatformOptions, this.$projectData.projectId); - this.$platformService.trackProjectType(this.$projectData); - } - const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, @@ -71,10 +59,28 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { clean: this.$options.clean }; - return { - deviceDescriptors, - liveSyncInfo + await liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + + } + + private async runInReleaseMode(platform: string): Promise { + const runPlatformOptions: IRunPlatformOptions = { + device: this.$options.device, + emulator: this.$options.emulator, + justlaunch: this.$options.justlaunch }; + + const deployOptions = _.merge({ + projectDir: this.$projectData.projectDir, + clean: true, + }, this.$options.argv); + + const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + for (const currentPlatform of availablePlatforms) { + await this.$platformService.deployPlatform(currentPlatform, this.$options, deployOptions, this.$projectData, this.$options); + await this.$platformService.startApplication(currentPlatform, runPlatformOptions, this.$projectData.projectId); + this.$platformService.trackProjectType(this.$projectData); + } } } diff --git a/test/debug.ts b/test/debug.ts index 0128757a82..995c73c429 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -69,8 +69,8 @@ function createTestInjector(): IInjector { testInjector.register("prompter", {}); testInjector.registerCommand("debug|android", DebugAndroidCommand); testInjector.register("liveSyncCommandHelper", { - getDevicesLiveSyncInfo: async (): Promise => { - return { deviceDescriptors: [], liveSyncInfo: null }; + getDevicesLiveSyncInfo: async (): Promise => { + return null; } }); From 43fb25100d0cace2546570d67faaf111806e3338 Mon Sep 17 00:00:00 2001 From: Nadya Atanasova Date: Wed, 19 Jul 2017 14:46:36 +0300 Subject: [PATCH 9/9] Update common lib * Fix PR comments --- lib/bootstrap.ts | 2 +- lib/common | 2 +- lib/definitions/project-changes.d.ts | 3 +++ lib/definitions/project.d.ts | 2 +- lib/services/livesync/livesync-service.ts | 4 ++-- lib/services/platform-service.ts | 10 +++++----- test/stubs.ts | 4 ++-- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 1888b34cfe..4beee8e2c0 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -105,7 +105,7 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); -$injector.requirePublicClass("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); +$injector.require("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); $injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); diff --git a/lib/common b/lib/common index c82bd612b1..c33a233679 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c82bd612b157919d0ac97e14eff686804ff33830 +Subproject commit c33a23367970966d1993749886fd689f3d048213 diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 83e3bfb405..04ae5edab4 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -35,6 +35,9 @@ interface IProjectChangesService { currentChanges: IProjectChangesInfo; } +/** + * NativePlatformStatus.requiresPlatformAdd | NativePlatformStatus.requiresPrepare | NativePlatformStatus.alreadyPrepared + */ interface IAddedNativePlatform { nativePlatformStatus: "1" | "2" | "3"; } diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 847d6ee221..2656ddf251 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -133,7 +133,7 @@ interface IBuildForDevice { } interface INativePrepare { - skip: boolean; + skipNativePrepare: boolean; } interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 7c357eea68..dae7ecf36a 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -237,7 +237,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, liveSyncData, settings - }, { skip: liveSyncData.skipNativePrepare }); + }, { skipNativePrepare: liveSyncData.skipNativePrepare }); const liveSyncResultInfo = await this.getLiveSyncService(platform).fullSync({ projectData, device, @@ -339,7 +339,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { deviceBuildInfoDescriptor, settings: latestAppPackageInstalledSettings, modifiedFiles: allModifiedFiles - }, { skip: liveSyncData.skipNativePrepare }); + }, { skipNativePrepare: liveSyncData.skipNativePrepare }); const service = this.getLiveSyncService(device.deviceInfo.platform); const settings: ILiveSyncWatchInfo = { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 8d40d2cfdc..76e3bee56f 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -143,7 +143,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const customTemplateOptions = await this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir); config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; - if (!nativePrepare || !nativePrepare.skip) { + if (!nativePrepare || !nativePrepare.skipNativePrepare) { await this.addPlatformCoreNative(platformData, frameworkDir, installedVersion, projectData, config); } @@ -216,7 +216,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { public async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformTemplate: string, projectData: IProjectData, config: IAddPlatformCoreOptions, filesToSync?: Array, nativePrepare?: INativePrepare): Promise { const platformData = this.$platformsData.getPlatformData(platform, projectData); const changesInfo = await this.initialPrepare(platform, platformData, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare); - const requiresNativePrepare = (!nativePrepare || !nativePrepare.skip) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; + const requiresNativePrepare = (!nativePrepare || !nativePrepare.skipNativePrepare) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; if (changesInfo.hasChanges || appFilesUpdaterOptions.bundle || requiresNativePrepare) { await this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, config, changesInfo, filesToSync, nativePrepare); @@ -282,7 +282,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config, nativePrepare); const bundle = appFilesUpdaterOptions.bundle; - const nativePlatformStatus = (nativePrepare && nativePrepare.skip) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; + const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; const changesInfo = this.$projectChangesService.checkForChanges(platform, projectData, { bundle, release: appFilesUpdaterOptions.release, provision: config.provision, nativePlatformStatus }); this.$logger.trace("Changes info in prepare platform:", changesInfo); @@ -297,7 +297,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let platformData = this.$platformsData.getPlatformData(platform, projectData); await this.preparePlatformCoreJS(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); - if (!nativePrepare || !nativePrepare.skip) { + if (!nativePrepare || !nativePrepare.skipNativePrepare) { await this.preparePlatformCoreNative(platform, platformData, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo); } @@ -740,7 +740,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { if (!this.isPlatformInstalled(platform, projectData)) { await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); } else { - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skip; + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); // In case there's no prepare info, it means only platform add had been executed. So we've come from CLI and we do not need to prepare natively. requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; diff --git a/test/stubs.ts b/test/stubs.ts index e642b24ef6..1a4902a444 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -575,8 +575,8 @@ export class ProjectChangesService implements IProjectChangesService { return {}; } - public setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): boolean { - return true; + public setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void { + return; } }