diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index e7d16fc9e1..88fde3fc04 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -4,7 +4,7 @@ import { hook } from "../common/helpers"; import { performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; import * as path from "path"; -import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE } from "../constants"; +import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME } from "../constants"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; @@ -115,6 +115,8 @@ export class PrepareController extends EventEmitter { } const patterns = [ + path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), + path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), `node_modules/**/platforms/${platformData.platformNameLowerCase}/` ]; @@ -130,7 +132,7 @@ export class PrepareController extends EventEmitter { const watcher = choki.watch(patterns, watcherOptions) .on("all", async (event: string, filePath: string) => { filePath = path.join(projectData.projectDir, filePath); - this.$logger.info(`Chokidar raised event ${event} for ${filePath}.`); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); this.emitPrepareEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); }); diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ddcba150c1..485f4291db 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -302,8 +302,9 @@ export class RunController extends EventEmitter implements IRunController { try { if (data.hasNativeChanges) { - await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await deviceDescriptor.buildAction(); + if (await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData)) { + await deviceDescriptor.buildAction(); + } } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 8841e98b41..e799efbb5d 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -14,9 +14,7 @@ interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes { interface IProjectChangesInfo extends IAddedNativePlatform { appResourcesChanged: boolean; - modulesChanged: boolean; configChanged: boolean; - packageChanged: boolean; nativeChanged: boolean; signingChanged: boolean; diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 253f3d189b..6328c50955 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -21,11 +21,11 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); - const hasModulesChange = !changesInfo || changesInfo.modulesChanged; + const hasNativeModulesChange = !changesInfo || changesInfo.nativeChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; const hasChangesRequirePrepare = !changesInfo || changesInfo.changesRequirePrepare; - const hasChanges = hasModulesChange || hasConfigChange || hasChangesRequirePrepare; + const hasChanges = hasNativeModulesChange || hasConfigChange || hasChangesRequirePrepare; if (changesInfo.hasChanges) { await this.cleanProject(platformData, { release }); @@ -37,11 +37,11 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi await platformData.platformProjectService.prepareProject(projectData, prepareData); } - if (hasModulesChange) { + if (hasNativeModulesChange) { await this.$nodeModulesBuilder.prepareNodeModules(platformData, projectData); } - if (hasModulesChange || hasConfigChange) { + if (hasNativeModulesChange || hasConfigChange) { await platformData.platformProjectService.processConfigurationFilesFromAppResources(projectData, { release }); await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 70605294ab..8148b806aa 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants"; +import { NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; import { PrepareData } from "../data/prepare-data"; @@ -8,24 +8,20 @@ const prepareInfoFileName = ".nsprepareinfo"; class ProjectChangesInfo implements IProjectChangesInfo { public appResourcesChanged: boolean; - public modulesChanged: boolean; public configChanged: boolean; - public packageChanged: boolean; public nativeChanged: boolean; public signingChanged: boolean; public nativePlatformStatus: NativePlatformStatus; public get hasChanges(): boolean { - return this.packageChanged || + return this.nativeChanged || this.appResourcesChanged || - this.modulesChanged || this.configChanged || this.signingChanged; } public get changesRequireBuild(): boolean { - return this.packageChanged || - this.appResourcesChanged || + return this.appResourcesChanged || this.nativeChanged; } @@ -39,7 +35,6 @@ export class ProjectChangesService implements IProjectChangesService { private _changesInfo: IProjectChangesInfo; private _prepareInfo: IPrepareInfo; - private _newFiles: number = 0; private _outputProjectMtime: number; private _outputProjectCTime: number; @@ -47,7 +42,8 @@ export class ProjectChangesService implements IProjectChangesService { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $fs: IFileSystem, private $logger: ILogger, - public $hooksService: IHooksService) { + public $hooksService: IHooksService, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { } public get currentChanges(): IProjectChangesInfo { @@ -59,26 +55,24 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo = new ProjectChangesInfo(); const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, prepareData); if (!isNewPrepareInfo) { - this._newFiles = 0; - - this._changesInfo.packageChanged = this.isProjectFileChanged(projectData.projectDir, platformData); - const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); - this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, 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( - path.join(projectData.projectDir, NODE_MODULES_FOLDER_NAME), - path.join(projectData.projectDir, NODE_MODULES_FOLDER_NAME, "tns-ios-inspector"), - projectData, - this.fileChangeRequiresBuild); + this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, projectData); + + this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) + .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase))) + .forEach(dep => { + this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || + this.containsNewerFiles(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase), projectData) || + this.isFileModified(path.join(dep.directory, PACKAGE_JSON_FILE_NAME)); + }); + + if (!this._changesInfo.nativeChanged) { + this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); + this._changesInfo.nativeChanged = this.isProjectFileChanged(projectData.projectDir, platformData); + } this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); - if (this._newFiles > 0 || this._changesInfo.nativeChanged) { - this.$logger.trace(`Setting modulesChanged to true, newFiles: ${this._newFiles}, nativeChanged: ${this._changesInfo.nativeChanged}`); - this._changesInfo.modulesChanged = true; - } - if (platformData.platformNameLowerCase === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), path.join(platformResourcesDir, "LaunchScreen.storyboard"), @@ -101,16 +95,11 @@ export class ProjectChangesService implements IProjectChangesService { if (prepareData.release !== this._prepareInfo.release) { this.$logger.trace(`Setting all setting to true. Current options are: `, prepareData, " old prepare info is: ", this._prepareInfo); this._changesInfo.appResourcesChanged = true; - this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; this._prepareInfo.release = prepareData.release; } - if (this._changesInfo.packageChanged) { - this.$logger.trace("Set modulesChanged to true as packageChanged is true"); - this._changesInfo.modulesChanged = true; - } - if (this._changesInfo.modulesChanged || this._changesInfo.appResourcesChanged) { - this.$logger.trace(`Set configChanged to true, current value of moduleChanged is: ${this._changesInfo.modulesChanged}, appResourcesChanged is: ${this._changesInfo.appResourcesChanged}`); + if (this._changesInfo.appResourcesChanged) { + this.$logger.trace(`Set configChanged to true, appResourcesChanged is: ${this._changesInfo.appResourcesChanged}`); this._changesInfo.configChanged = true; } if (this._changesInfo.hasChanges) { @@ -119,8 +108,6 @@ export class ProjectChangesService implements IProjectChangesService { if (this._prepareInfo.changesRequireBuild) { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } - - this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); } this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; @@ -191,7 +178,6 @@ export class ProjectChangesService implements IProjectChangesService { this._outputProjectCTime = 0; this._changesInfo = this._changesInfo || new ProjectChangesInfo(); this._changesInfo.appResourcesChanged = true; - this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; return true; } @@ -229,7 +215,7 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - private containsNewerFiles(dir: string, skipDir: string, projectData: IProjectData, processFunc?: (filePath: string, projectData: IProjectData) => boolean): boolean { + private containsNewerFiles(dir: string, projectData: IProjectData): boolean { const dirName = path.basename(dir); this.$logger.trace(`containsNewerFiles will check ${dir}`); if (_.startsWith(dirName, '.')) { @@ -237,8 +223,7 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - const dirFileStat = this.$fs.getFsStats(dir); - if (this.isFileModified(dirFileStat, dir)) { + if (this.isFileModified(dir)) { this.$logger.trace(`containsNewerFiles returns true for ${dir} as the dir itself has been modified.`); return true; } @@ -246,31 +231,17 @@ export class ProjectChangesService implements IProjectChangesService { const files = this.$fs.readDirectory(dir); for (const file of files) { const filePath = path.join(dir, file); - if (filePath === skipDir) { - continue; - } const fileStats = this.$fs.getFsStats(filePath); - const changed = this.isFileModified(fileStats, filePath); + const changed = this.isFileModified(filePath, fileStats); if (changed) { - this.$logger.trace(`File ${filePath} has been changed.`); - if (processFunc) { - this._newFiles++; - this.$logger.trace(`Incremented the newFiles counter. Current value is ${this._newFiles}`); - const filePathRelative = path.relative(projectData.projectDir, filePath); - if (processFunc.call(this, filePathRelative, projectData)) { - this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); - return true; - } - } else { - this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); - return true; - } + this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); + return true; } if (fileStats.isDirectory()) { - if (this.containsNewerFiles(filePath, skipDir, projectData, processFunc)) { + if (this.containsNewerFiles(filePath, projectData)) { this.$logger.trace(`containsNewerFiles returns true for ${dir}.`); return true; } @@ -281,9 +252,10 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - private isFileModified(filePathStat: IFsStats, filePath: string): boolean { - let changed = filePathStat.mtime.getTime() >= this._outputProjectMtime || - filePathStat.ctime.getTime() >= this._outputProjectCTime; + private isFileModified(filePath: string, filePathStats?: IFsStats): boolean { + filePathStats = filePathStats || this.$fs.getFsStats(filePath); + let changed = filePathStats.mtime.getTime() >= this._outputProjectMtime || + filePathStats.ctime.getTime() >= this._outputProjectCTime; if (!changed) { const lFileStats = this.$fs.getLsStats(filePath); @@ -293,29 +265,5 @@ export class ProjectChangesService implements IProjectChangesService { return changed; } - - private fileChangeRequiresBuild(file: string, projectData: IProjectData) { - if (path.basename(file) === PACKAGE_JSON_FILE_NAME) { - return true; - } - const projectDir = projectData.projectDir; - if (_.startsWith(path.join(projectDir, file), projectData.appResourcesDirectoryPath)) { - return true; - } - if (_.startsWith(file, NODE_MODULES_FOLDER_NAME)) { - let filePath = file; - while (filePath !== NODE_MODULES_FOLDER_NAME) { - filePath = path.dirname(filePath); - const fullFilePath = path.join(projectDir, path.join(filePath, PACKAGE_JSON_FILE_NAME)); - if (this.$fs.exists(fullFilePath)) { - const json = this.$fs.readJson(fullFilePath); - if (json["nativescript"] && _.startsWith(file, path.join(filePath, "platforms"))) { - return true; - } - } - } - } - return false; - } } $injector.register("projectChangesService", ProjectChangesService); diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index a5383ae4ee..b7bd3ffdfd 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -36,6 +36,7 @@ class ProjectChangesServiceTest extends BaseServiceTest { }); this.injector.register("logger", LoggerStub); this.injector.register("hooksService", HooksServiceStub); + this.injector.register("nodeModulesDependenciesBuilder", {}); const fs = this.injector.resolve("fs"); fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), { diff --git a/test/stubs.ts b/test/stubs.ts index 05964e8250..e61e357fae 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -506,16 +506,10 @@ export class ProjectDataService implements IProjectDataService { removeDependency(dependencyName: string): void { } getProjectData(projectDir: string): IProjectData { - return { - projectDir: "/path/to/my/projecDir", - projectName: "myTestProjectName", - platformsDir: "/path/to/my/projecDir/platforms", - projectIdentifiers: { - ios: "org.nativescript.myiosApp", - android: "org.nativescript.myAndroidApp" - }, - getAppResourcesRelativeDirectoryPath: () => "/path/to/my/projecDir/App_Resources" - }; + const projectData = new ProjectDataStub(); + projectData.initializeProjectData(projectDir); + + return projectData; } async getAssetsStructure(opts: IProjectDir): Promise {