From 8b01617142809a687ffb42334221ad0b5d4b2789 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 18 Jan 2019 09:42:16 +0200 Subject: [PATCH 1/6] fix: remove installPods property from platform's project interface --- lib/definitions/project.d.ts | 2 +- lib/services/ios-project-service.ts | 6 +----- lib/services/prepare-platform-native-service.ts | 11 ++++++----- lib/tools/node-modules/node-modules-dest-copy.ts | 5 ----- test/platform-service.ts | 2 ++ 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index b9f7aa950f..ed88665959 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -410,7 +410,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string; cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise; - processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise; + processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean }): Promise; /** * Ensures there is configuration file (AndroidManifest.xml, Info.plist) in app/App_Resources. diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 6845651be2..c59eed9a78 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -791,7 +791,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); } - public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise { + public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean }): Promise { await this.mergeInfoPlists({ release: opts.release }, projectData); await this.$iOSEntitlementsService.merge(projectData); await this.mergeProjectXcconfigFiles(opts.release, projectData); @@ -800,10 +800,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } this.$pluginVariablesService.interpolateAppIdentifier(this.getPlatformData(projectData).configurationFilePath, projectData.projectIdentifiers.ios); - - if (opts.installPods) { - await this.installPodsIfAny(projectData); - } } private getInfoPlistPath(projectData: IProjectData): string { diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index d78f3cd709..1d90e99e3e 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -42,9 +42,10 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme await config.platformData.platformProjectService.prepareProject(config.projectData, config.platformSpecificData); } - const shouldPrepareModules = !config.changesInfo || config.changesInfo.modulesChanged; + const hasModulesChange = !config.changesInfo || config.changesInfo.modulesChanged; + const hasConfigChange = !config.changesInfo || config.changesInfo.configChanged; - if (shouldPrepareModules) { + if (hasModulesChange) { await this.$pluginsService.validate(config.platformData, config.projectData); const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -64,9 +65,9 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme await this.$nodeModulesBuilder.prepareNodeModules({ nodeModulesData, release: config.appFilesUpdaterOptions.release }); } - if (!config.changesInfo || config.changesInfo.configChanged || config.changesInfo.modulesChanged) { - // Passing !shouldPrepareModules` we assume that if the node modules are prepared base Podfile content is added and `pod install` is executed. - await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, {release:config.appFilesUpdaterOptions.release, installPods: !shouldPrepareModules}); + if (hasModulesChange || hasConfigChange) { + await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, { release: config.appFilesUpdaterOptions.release }); + await config.platformData.platformProjectService.afterPrepareAllPlugins(config.projectData); } config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData); diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index cbdfd51ae0..d3b90bf13b 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -112,11 +112,6 @@ export class NpmPluginPrepare { } protected async afterPrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { - await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.afterPrepareAllPlugins(projectData); - this.writePreparedDependencyInfo(dependencies, platform, projectData); - } - - private writePreparedDependencyInfo(dependencies: IDependencyData[], platform: string, projectData: IProjectData): void { const prepareData: IDictionary = {}; _.each(dependencies, d => { prepareData[d.name] = true; diff --git a/test/platform-service.ts b/test/platform-service.ts index 16881a7d57..ded98e64fd 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -496,6 +496,7 @@ describe('Platform Service Tests', () => { } }, processConfigurationFilesFromAppResources: () => Promise.resolve(), + afterPrepareAllPlugins: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, @@ -931,6 +932,7 @@ describe('Platform Service Tests', () => { afterCreateProject: (projectRoot: string): any => null, getAppResourcesDestinationDirectoryPath: () => testDirData.appResourcesFolderPath, processConfigurationFilesFromAppResources: () => Promise.resolve(), + afterPrepareAllPlugins: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, From c7876f1252e2ef986d3bc5f72a791b6cb72642cf Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 21 Jan 2019 11:34:02 +0200 Subject: [PATCH 2/6] fix: apply `pod install` after merging all pod files (from node_modules and from App_Resources) --- lib/definitions/project.d.ts | 3 +- lib/services/android-project-service.ts | 6 +-- lib/services/ios-project-service.ts | 53 +++++++++---------- .../prepare-platform-native-service.ts | 2 +- .../node-modules/node-modules-dest-copy.ts | 5 -- test/debug.ts | 4 +- test/npm-support.ts | 3 +- test/platform-service.ts | 4 +- test/plugin-prepare.ts | 6 +-- test/stubs.ts | 5 +- 10 files changed, 36 insertions(+), 55 deletions(-) diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index ed88665959..272de0174e 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -400,8 +400,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS */ removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise; - afterPrepareAllPlugins(projectData: IProjectData): Promise; - beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; + handleNativeDependenciesChange(projectData: IProjectData): Promise; /** * Gets the path wheren App_Resources should be copied. diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index f53f16765e..5efe5d32e3 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -577,11 +577,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public async afterPrepareAllPlugins(projectData: IProjectData): Promise { - return; - } - - public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { + public async handleNativeDependenciesChange(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { const shouldUseNewRoutine = this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0"); if (dependencies) { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index c59eed9a78..0c9f397fbc 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -800,6 +800,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } this.$pluginVariablesService.interpolateAppIdentifier(this.getPlatformData(projectData).configurationFilePath, projectData.projectIdentifiers.ios); + await this.mergeProjectPodFile(projectData); } private getInfoPlistPath(projectData: IProjectData): string { @@ -976,34 +977,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$cocoapodsService.removePodfileFromProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot); } - public async afterPrepareAllPlugins(projectData: IProjectData): Promise { - await this.installPodsIfAny(projectData); - } - - public async installPodsIfAny(projectData: IProjectData): Promise { + public async handleNativeDependenciesChange(projectData: IProjectData): Promise { const projectRoot = this.getPlatformData(projectData).projectRoot; - const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.PODFILE_NAME); - if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot)) || this.$fs.exists(mainPodfilePath)) { - const xcodeProjPath = this.getXcodeprojPath(projectData); - const xcuserDataPath = path.join(xcodeProjPath, "xcuserdata"); - const sharedDataPath = path.join(xcodeProjPath, "xcshareddata"); - - if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { - this.$logger.info("Creating project scheme..."); - await this.checkIfXcodeprojIsRequired(); - - const createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; - await this.$childProcess.exec(createSchemeRubyScript, { cwd: this.getPlatformData(projectData).projectRoot }); - } - - await this.$cocoapodsService.applyPodfileToProject(constants.NS_BASE_PODFILE, mainPodfilePath, projectData, this.getPlatformData(projectData).projectRoot); - - await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath); - } - } - - public beforePrepareAllPlugins(): Promise { - return Promise.resolve(); + const xcodeProjPath = this.getXcodeprojPath(projectData); + await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath); } public async checkForChanges(changesInfo: IProjectChangesInfo, { provision, teamId }: IProjectChangesOptions, projectData: IProjectData): Promise { @@ -1068,6 +1045,28 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return semver.coerce(target); } + private async mergeProjectPodFile(projectData: IProjectData): Promise { + const platformData = this.getPlatformData(projectData); + const { projectRoot, normalizedPlatformName } = platformData; + const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, normalizedPlatformName, constants.PODFILE_NAME); + const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(projectRoot); + if (this.$fs.exists(projectPodfilePath) || this.$fs.exists(mainPodfilePath)) { + const xcodeProjPath = this.getXcodeprojPath(projectData); + const xcuserDataPath = path.join(xcodeProjPath, "xcuserdata"); + const sharedDataPath = path.join(xcodeProjPath, "xcshareddata"); + + if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { + this.$logger.info("Creating project scheme..."); + await this.checkIfXcodeprojIsRequired(); + + const createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; + await this.$childProcess.exec(createSchemeRubyScript, { cwd: projectRoot }); + } + + await this.$cocoapodsService.applyPodfileToProject(constants.NS_BASE_PODFILE, mainPodfilePath, projectData, projectRoot); + } + } + private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index 1d90e99e3e..c189b34db0 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -67,7 +67,7 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme if (hasModulesChange || hasConfigChange) { await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, { release: config.appFilesUpdaterOptions.release }); - await config.platformData.platformProjectService.afterPrepareAllPlugins(config.projectData); + await config.platformData.platformProjectService.handleNativeDependenciesChange(config.projectData); } config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData); diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index d3b90bf13b..73d017fe25 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -107,10 +107,6 @@ export class NpmPluginPrepare { ) { } - protected async beforePrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { - await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.beforePrepareAllPlugins(projectData, dependencies); - } - protected async afterPrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { const prepareData: IDictionary = {}; _.each(dependencies, d => { @@ -158,7 +154,6 @@ export class NpmPluginPrepare { return; } - await this.beforePrepare(dependencies, platform, projectData); for (const dependencyKey in dependencies) { const dependency = dependencies[dependencyKey]; const isPlugin = !!dependency.nativescript; diff --git a/test/debug.ts b/test/debug.ts index ae180e5c6c..a1afca21e5 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -350,7 +350,7 @@ describe("debug command tests", () => { testInjector = createTestInjector(); }); - it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { + it("Ensures that handleNativeDependenciesChange will call gradle with clean option when *NOT* livesyncing", async () => { const platformData = testInjector.resolve("platformsData"); platformData.frameworkPackageName = "tns-android"; @@ -367,7 +367,7 @@ describe("debug command tests", () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); const spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); + await androidProjectService.handleNativeDependenciesChange(projectData); assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); assert.isTrue(spawnFromEventCount === 0); diff --git a/test/npm-support.ts b/test/npm-support.ts index 8039530151..bd433fbd52 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -182,8 +182,7 @@ async function setupProject(dependencies?: any): Promise { platformProjectService: { prepareProject: (): any => null, prepareAppResources: (): any => null, - afterPrepareAllPlugins: () => Promise.resolve(), - beforePrepareAllPlugins: () => Promise.resolve(), + handleNativeDependenciesChange: () => Promise.resolve(), getAppResourcesDestinationDirectoryPath: () => path.join(androidFolderPath, "src", "main", "res"), processConfigurationFilesFromAppResources: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, diff --git a/test/platform-service.ts b/test/platform-service.ts index ded98e64fd..19c9fb8c53 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -496,7 +496,7 @@ describe('Platform Service Tests', () => { } }, processConfigurationFilesFromAppResources: () => Promise.resolve(), - afterPrepareAllPlugins: () => Promise.resolve(), + handleNativeDependenciesChange: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, @@ -932,7 +932,7 @@ describe('Platform Service Tests', () => { afterCreateProject: (projectRoot: string): any => null, getAppResourcesDestinationDirectoryPath: () => testDirData.appResourcesFolderPath, processConfigurationFilesFromAppResources: () => Promise.resolve(), - afterPrepareAllPlugins: () => Promise.resolve(), + handleNativeDependenciesChange: () => Promise.resolve(), ensureConfigurationFileInAppResources: (): any => null, interpolateConfigurationFile: (): void => undefined, isPlatformPrepared: (projectRoot: string) => false, diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index 27a93cd10a..b847f1a4aa 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -14,15 +14,11 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { return this.previouslyPrepared; } - protected async beforePrepare(dependencies: IDependencyData[], platform: string): Promise { + protected async afterPrepare(dependencies: IDependencyData[], platform: string): Promise { _.each(dependencies, d => { this.preparedDependencies[d.name] = true; }); } - - protected async afterPrepare(dependencies: IDependencyData[], platform: string): Promise { - // DO NOTHING - } } describe("Plugin preparation", () => { diff --git a/test/stubs.ts b/test/stubs.ts index bbf28e6eeb..0f67df2c80 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -445,10 +445,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async removePluginNativeCode(pluginData: IPluginData): Promise { } - async afterPrepareAllPlugins(): Promise { - return Promise.resolve(); - } - async beforePrepareAllPlugins(): Promise { + async handleNativeDependenciesChange(): Promise { return Promise.resolve(); } async cleanDeviceTempFolder(deviceIdentifier: string): Promise { From 1848c301853a96964150b520ded466509075fdda Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 21 Jan 2019 23:12:37 +0200 Subject: [PATCH 3/6] chore: fix PR comments --- lib/services/ios-project-service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 0c9f397fbc..89a3e9b917 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -980,7 +980,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async handleNativeDependenciesChange(projectData: IProjectData): Promise { const projectRoot = this.getPlatformData(projectData).projectRoot; const xcodeProjPath = this.getXcodeprojPath(projectData); - await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath); + const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(projectRoot); + if (this.$fs.exists(projectPodfilePath)) { + await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath); + } } public async checkForChanges(changesInfo: IProjectChangesInfo, { provision, teamId }: IProjectChangesOptions, projectData: IProjectData): Promise { From ce33b5ff34a9c39ba11a0e1308c5365c1abdb67b Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 22 Jan 2019 12:44:57 +0200 Subject: [PATCH 4/6] chore: undo the changes on beforePrepareAllPlugins method --- lib/definitions/project.d.ts | 2 ++ lib/services/android-project-service.ts | 6 +++++- lib/services/ios-project-service.ts | 4 ++++ lib/tools/node-modules/node-modules-dest-copy.ts | 2 ++ test/debug.ts | 4 ++-- test/npm-support.ts | 1 + test/plugin-prepare.ts | 10 +++++++++- test/stubs.ts | 3 +++ 8 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 272de0174e..39726c6658 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -400,6 +400,8 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS */ removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise; + beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; + handleNativeDependenciesChange(projectData: IProjectData): Promise; /** diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 5efe5d32e3..9b2498146b 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -577,7 +577,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public async handleNativeDependenciesChange(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { + public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { const shouldUseNewRoutine = this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0"); if (dependencies) { @@ -605,6 +605,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } + public async handleNativeDependenciesChange(projectData: IProjectData): Promise { + return; + } + private filterUniqueDependencies(dependencies: IDependencyData[]): IDependencyData[] { const depsDictionary = dependencies.reduce((dict, dep) => { const collision = dict[dep.name]; diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 89a3e9b917..7ae03225d3 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -986,6 +986,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } } + public beforePrepareAllPlugins(): Promise { + return Promise.resolve(); + } + public async checkForChanges(changesInfo: IProjectChangesInfo, { provision, teamId }: IProjectChangesOptions, projectData: IProjectData): Promise { const hasProvision = provision !== undefined; const hasTeamId = teamId !== undefined; diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 73d017fe25..55bcaaedc4 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -154,6 +154,8 @@ export class NpmPluginPrepare { return; } + await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.beforePrepareAllPlugins(projectData, dependencies); + for (const dependencyKey in dependencies) { const dependency = dependencies[dependencyKey]; const isPlugin = !!dependency.nativescript; diff --git a/test/debug.ts b/test/debug.ts index a1afca21e5..ae180e5c6c 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -350,7 +350,7 @@ describe("debug command tests", () => { testInjector = createTestInjector(); }); - it("Ensures that handleNativeDependenciesChange will call gradle with clean option when *NOT* livesyncing", async () => { + it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { const platformData = testInjector.resolve("platformsData"); platformData.frameworkPackageName = "tns-android"; @@ -367,7 +367,7 @@ describe("debug command tests", () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); const spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.handleNativeDependenciesChange(projectData); + await androidProjectService.beforePrepareAllPlugins(projectData); assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); assert.isTrue(spawnFromEventCount === 0); diff --git a/test/npm-support.ts b/test/npm-support.ts index bd433fbd52..bde20bc4f8 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -182,6 +182,7 @@ async function setupProject(dependencies?: any): Promise { platformProjectService: { prepareProject: (): any => null, prepareAppResources: (): any => null, + beforePrepareAllPlugins: () => Promise.resolve(), handleNativeDependenciesChange: () => Promise.resolve(), getAppResourcesDestinationDirectoryPath: () => path.join(androidFolderPath, "src", "main", "res"), processConfigurationFilesFromAppResources: () => Promise.resolve(), diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts index b847f1a4aa..00dd7b04b1 100644 --- a/test/plugin-prepare.ts +++ b/test/plugin-prepare.ts @@ -7,7 +7,15 @@ class TestNpmPluginPrepare extends NpmPluginPrepare { public preparedDependencies: IDictionary = {}; constructor(private previouslyPrepared: IDictionary) { - super(null, null, null, null); + super(null, null, { + getPlatformData: () => { + return { + platformProjectService: { + beforePrepareAllPlugins: () => Promise.resolve() + } + }; + } + }, null); } protected getPreviouslyPreparedDependencies(platform: string): IDictionary { diff --git a/test/stubs.ts b/test/stubs.ts index 0f67df2c80..752e7125d4 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -448,6 +448,9 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async handleNativeDependenciesChange(): Promise { return Promise.resolve(); } + async beforePrepareAllPlugins(): Promise { + return Promise.resolve(); + } async cleanDeviceTempFolder(deviceIdentifier: string): Promise { return Promise.resolve(); } From d23f7bf490c355fc1796bfe39ed6897154a85a90 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 30 Jan 2019 10:51:56 +0200 Subject: [PATCH 5/6] fix: execute `pod install` and merge of pod's xcconfig file in the correct order --- lib/bootstrap.ts | 2 +- lib/declarations.d.ts | 38 ++++++ lib/definitions/project.d.ts | 18 ++- lib/services/android-project-service.ts | 2 +- lib/services/cocoapods-service.ts | 25 +++- lib/services/ios-project-service.ts | 126 +++++------------- .../prepare-platform-native-service.ts | 2 +- lib/services/xcconfig-service.ts | 46 +++++-- lib/services/xcproj-service.ts | 17 +++ 9 files changed, 168 insertions(+), 108 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 80ab81e867..d01c52eb73 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -15,7 +15,7 @@ $injector.require("androidPluginBuildService", "./services/android-plugin-build- $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); -$injector.require("xCConfigService", "./services/xcconfig-service"); +$injector.require("xcconfigService", "./services/xcconfig-service"); $injector.require("cocoapodsService", "./services/cocoapods-service"); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 631462421e..e7933a5152 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -844,6 +844,13 @@ interface IVerifyXcprojOptions { * Designed for getting information about xcproj. */ interface IXcprojService { + /** + * Returns the path to the xcodeproj file + * @param projectData Information about the project. + * @param platformData Information about the platform. + * @return {string} The full path to the xcodeproj + */ + getXcodeprojPath(projectData: IProjectData, platformData: IPlatformData): string; /** * Checks whether the system needs xcproj to execute ios builds successfully. * In case the system does need xcproj but does not have it, prints an error message. @@ -856,6 +863,11 @@ interface IXcprojService { * @return {Promise} collected info about xcproj. */ getXcprojInfo(): Promise; + /** + * Checks if xcproj is available and throws an error in case when it is not available. + * @return {Promise} + */ + checkIfXcodeprojIsRequired(): Promise; } /** @@ -880,6 +892,32 @@ interface IXcprojInfo { xcprojAvailable: boolean; } +interface IXcconfigService { + /** + * Returns the path to the xcconfig file + * @param projectRoot The path to root folder of native project (platforms/ios) + * @param opts + * @returns {string} + */ + getPluginsXcconfigFilePath(projectRoot: string, opts: IRelease): string; + + /** + * Returns the value of a property from a xcconfig file. + * @param xcconfigFilePath The path to the xcconfig file + * @param propertyName The name of the property which value will be returned + * @returns {string} + */ + readPropertyValue(xcconfigFilePath: string, propertyName: string): string; + + /** + * Merges the content of source file into destination file + * @param sourceFile The content of thes source file + * @param destinationFile The content of the destination file + * @returns {Promise} + */ + mergeFiles(sourceFile: string, destinationFile: string): Promise; +} + /** * Describes helper used during execution of deploy commands. */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 39726c6658..ac8b8bd1c2 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -402,7 +402,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; - handleNativeDependenciesChange(projectData: IProjectData): Promise; + handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise; /** * Gets the path wheren App_Resources should be copied. @@ -484,6 +484,14 @@ interface ICocoaPodsService { */ getPodfileFooter(): string; + /** + * Merges the Podfile's content from App_Resources in the project's Podfile. + * @param projectData Information about the project. + * @param platformData Information about the platform. + * @returns {Promise} + */ + applyPodfileFromAppResources(projectData: IProjectData, platformData: IPlatformData): Promise; + /** * Prepares the Podfile content of a plugin and merges it in the project's Podfile. * @param {string} moduleName The module which the Podfile is from. @@ -525,6 +533,14 @@ interface ICocoaPodsService { * @returns {Promise} Information about the spawned process. */ executePodInstall(projectRoot: string, xcodeProjPath: string): Promise; + + /** + * Merges pod's xcconfig file into project's xcconfig file + * @param projectData + * @param platformData + * @param opts + */ + mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData, opts: IRelease): Promise; } interface IRubyFunction { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 9b2498146b..396cc69c45 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -605,7 +605,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public async handleNativeDependenciesChange(projectData: IProjectData): Promise { + public async handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise { return; } diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index 5f793a9be8..042970e5c4 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -1,6 +1,6 @@ import { EOL } from "os"; import * as path from "path"; -import { PluginNativeDirNames, PODFILE_NAME } from "../constants"; +import { PluginNativeDirNames, PODFILE_NAME, NS_BASE_PODFILE } from "../constants"; export class CocoaPodsService implements ICocoaPodsService { private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; @@ -11,7 +11,8 @@ export class CocoaPodsService implements ICocoaPodsService { private $errors: IErrors, private $xcprojService: IXcprojService, private $logger: ILogger, - private $config: IConfiguration) { } + private $config: IConfiguration, + private $xcconfigService: IXcconfigService) { } public getPodfileHeader(targetName: string): string { return `use_frameworks!${EOL}${EOL}target "${targetName}" do${EOL}`; @@ -52,6 +53,26 @@ export class CocoaPodsService implements ICocoaPodsService { return podInstallResult; } + public async mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData, opts: IRelease) { + const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); + const podFolder = path.join(platformData.projectRoot, podFilesRootDirName); + if (this.$fs.exists(podFolder)) { + const podXcconfigFilePath = opts && opts.release ? path.join(podFolder, `Pods-${projectData.projectName}.release.xcconfig`) + : path.join(podFolder, `Pods-${projectData.projectName}.debug.xcconfig`); + const pluginsXcconfigFilePath = this.$xcconfigService.getPluginsXcconfigFilePath(platformData.projectRoot, opts); + await this.$xcconfigService.mergeFiles(podXcconfigFilePath, pluginsXcconfigFilePath); + } + } + + public async applyPodfileFromAppResources(projectData: IProjectData, platformData: IPlatformData): Promise { + const { projectRoot, normalizedPlatformName } = platformData; + const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, normalizedPlatformName, PODFILE_NAME); + const projectPodfilePath = this.getProjectPodfilePath(projectRoot); + if (this.$fs.exists(projectPodfilePath) || this.$fs.exists(mainPodfilePath)) { + await this.applyPodfileToProject(NS_BASE_PODFILE, mainPodfilePath, projectData, projectRoot); + } + } + public async applyPodfileToProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): Promise { if (!this.$fs.exists(podfilePath)) { this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 7ae03225d3..a5bfa13aaf 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -11,7 +11,6 @@ import * as temp from "temp"; import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; -import { XCConfigService } from "./xcconfig-service"; import * as mobileprovision from "ios-mobileprovision-finder"; import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; @@ -47,7 +46,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $plistParser: IPlistParser, private $sysInfo: ISysInfo, - private $xCConfigService: XCConfigService) { + private $xcconfigService: IXcconfigService) { super($fs, $projectDataService); } @@ -791,16 +790,15 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); } - public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean }): Promise { - await this.mergeInfoPlists({ release: opts.release }, projectData); + public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: IRelease): Promise { + await this.mergeInfoPlists(projectData, opts); await this.$iOSEntitlementsService.merge(projectData); - await this.mergeProjectXcconfigFiles(opts.release, projectData); + await this.mergeProjectXcconfigFiles(projectData, opts); for (const pluginData of await this.getAllInstalledPlugins(projectData)) { await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData.projectDir); } this.$pluginVariablesService.interpolateAppIdentifier(this.getPlatformData(projectData).configurationFilePath, projectData.projectIdentifiers.ios); - await this.mergeProjectPodFile(projectData); } private getInfoPlistPath(projectData: IProjectData): string { @@ -823,7 +821,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return Promise.resolve(); } - private async mergeInfoPlists(buildOptions: IRelease, projectData: IProjectData): Promise { + private async mergeInfoPlists(projectData: IProjectData, buildOptions: IRelease): Promise { const projectDir = projectData.projectDir; const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); this.ensureConfigurationFileInAppResources(); @@ -910,18 +908,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); } - private getXcodeprojPath(projectData: IProjectData): string { - return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName + IosProjectConstants.XcodeProjExtName); - } - - private getPluginsDebugXcconfigFilePath(projectData: IProjectData): string { - return path.join(this.getPlatformData(projectData).projectRoot, "plugins-debug.xcconfig"); - } - - private getPluginsReleaseXcconfigFilePath(projectData: IProjectData): string { - return path.join(this.getPlatformData(projectData).projectRoot, "plugins-release.xcconfig"); - } - private replace(name: string): string { if (_.startsWith(name, '"')) { name = name.substr(1, name.length - 2); @@ -936,7 +922,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private getPbxProjPath(projectData: IProjectData): string { - return path.join(this.getXcodeprojPath(projectData), "project.pbxproj"); + return path.join(this.$xcprojService.getXcodeprojPath(projectData, this.getPlatformData(projectData)), "project.pbxproj"); } private createPbxProj(projectData: IProjectData): any { @@ -977,15 +963,20 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$cocoapodsService.removePodfileFromProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot); } - public async handleNativeDependenciesChange(projectData: IProjectData): Promise { - const projectRoot = this.getPlatformData(projectData).projectRoot; - const xcodeProjPath = this.getXcodeprojPath(projectData); - const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(projectRoot); + public async handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise { + const platformData = this.getPlatformData(projectData); + await this.$cocoapodsService.applyPodfileFromAppResources(projectData, platformData); + + const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot); if (this.$fs.exists(projectPodfilePath)) { - await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath); + await this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData)); + // The `pod install` command adds a new target to the .pbxproject. This target adds additional build phases to xcodebuild. + // Some of these phases relies on env variables (like PODS_PODFILE_DIR_PATH or PODS_ROOT). + // These variables are produced from merge of pod's xcconfig file and project's xcconfig file. + // So the correct order is `pod install` to be executed before merging pod's xcconfig file. + await this.$cocoapodsService.mergePodXcconfigFile(projectData, platformData, opts); } } - public beforePrepareAllPlugins(): Promise { return Promise.resolve(); } @@ -1044,7 +1035,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } public getDeploymentTarget(projectData: IProjectData): semver.SemVer { - const target = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET"); + const target = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET"); if (!target) { return null; } @@ -1052,28 +1043,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return semver.coerce(target); } - private async mergeProjectPodFile(projectData: IProjectData): Promise { - const platformData = this.getPlatformData(projectData); - const { projectRoot, normalizedPlatformName } = platformData; - const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, normalizedPlatformName, constants.PODFILE_NAME); - const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(projectRoot); - if (this.$fs.exists(projectPodfilePath) || this.$fs.exists(mainPodfilePath)) { - const xcodeProjPath = this.getXcodeprojPath(projectData); - const xcuserDataPath = path.join(xcodeProjPath, "xcuserdata"); - const sharedDataPath = path.join(xcodeProjPath, "xcshareddata"); - - if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) { - this.$logger.info("Creating project scheme..."); - await this.checkIfXcodeprojIsRequired(); - - const createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; - await this.$childProcess.exec(createSchemeRubyScript, { cwd: projectRoot }); - } - - await this.$cocoapodsService.applyPodfileToProject(constants.NS_BASE_PODFILE, mainPodfilePath, projectData, projectRoot); - } - } - private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); @@ -1210,34 +1179,24 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$fs.writeFile(path.join(headersFolderPath, "module.modulemap"), modulemap); } - private async mergeXcconfigFiles(pluginFile: string, projectFile: string): Promise { - if (!this.$fs.exists(projectFile)) { - this.$fs.writeFile(projectFile, ""); - } - - await this.checkIfXcodeprojIsRequired(); - const 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 { - const pluginsXcconfigFilePath = release ? this.getPluginsReleaseXcconfigFilePath(projectData) : this.getPluginsDebugXcconfigFilePath(projectData); + private async mergeProjectXcconfigFiles(projectData: IProjectData, opts: IRelease): Promise { + const platformData = this.getPlatformData(projectData); + const pluginsXcconfigFilePath = this.$xcconfigService.getPluginsXcconfigFilePath(platformData.projectRoot, opts); this.$fs.deleteFile(pluginsXcconfigFilePath); - const allPlugins: IPluginData[] = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); + const pluginsService = this.$injector.resolve("pluginsService"); + const allPlugins: IPluginData[] = await pluginsService.getAllInstalledPlugins(projectData); for (const plugin of allPlugins) { const pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); const pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, BUILD_XCCONFIG_FILE_NAME); if (this.$fs.exists(pluginXcconfigFilePath)) { - await this.mergeXcconfigFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); + await this.$xcconfigService.mergeFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath); } } const appResourcesXcconfigPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, BUILD_XCCONFIG_FILE_NAME); if (this.$fs.exists(appResourcesXcconfigPath)) { - await this.mergeXcconfigFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); + await this.$xcconfigService.mergeFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath); } if (!this.$fs.exists(pluginsXcconfigFilePath)) { @@ -1248,7 +1207,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } // Set Entitlements Property to point to default file if not set explicitly by the user. - const entitlementsPropertyValue = this.$xCConfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); + const entitlementsPropertyValue = this.$xcconfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); if (entitlementsPropertyValue === null && this.$fs.exists(this.$iOSEntitlementsService.getPlatformsEntitlementsPath(projectData))) { temp.track(); const tempEntitlementsDir = temp.mkdirSync("entitlements"); @@ -1256,28 +1215,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const entitlementsRelativePath = this.$iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData); this.$fs.writeFile(tempEntitlementsFilePath, `CODE_SIGN_ENTITLEMENTS = ${entitlementsRelativePath}${EOL}`); - await this.mergeXcconfigFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); - } - - const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); - const podFolder = path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName); - if (this.$fs.exists(podFolder)) { - if (release) { - await this.mergeXcconfigFiles(path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName, `Pods-${projectData.projectName}.release.xcconfig`), this.getPluginsReleaseXcconfigFilePath(projectData)); - } else { - await this.mergeXcconfigFiles(path.join(this.getPlatformData(projectData).projectRoot, podFilesRootDirName, `Pods-${projectData.projectName}.debug.xcconfig`), this.getPluginsDebugXcconfigFilePath(projectData)); - } - } - } - - private async checkIfXcodeprojIsRequired(): Promise { - const xcprojInfo = await this.$xcprojService.getXcprojInfo(); - if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { - const errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; - - this.$errors.failWithoutHelp(errorMessage); - - return true; + await this.$xcconfigService.mergeFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath); } } @@ -1303,7 +1241,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private readTeamId(projectData: IProjectData): string { - let teamId = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); + let teamId = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); const fileName = path.join(this.getPlatformData(projectData).projectRoot, "teamid"); if (this.$fs.exists(fileName)) { @@ -1314,19 +1252,19 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } private readXCConfigProvisioningProfile(projectData: IProjectData): string { - return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE"); + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE"); } private readXCConfigProvisioningProfileForIPhoneOs(projectData: IProjectData): string { - return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE[sdk=iphoneos*]"); + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE[sdk=iphoneos*]"); } private readXCConfigProvisioningProfileSpecifier(projectData: IProjectData): string { - return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER"); + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER"); } private readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData: IProjectData): string { - return this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"); + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"); } private async getDevelopmentTeam(projectData: IProjectData, teamId?: string): Promise { diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index c189b34db0..4de09e99a7 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -67,7 +67,7 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme if (hasModulesChange || hasConfigChange) { await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, { release: config.appFilesUpdaterOptions.release }); - await config.platformData.platformProjectService.handleNativeDependenciesChange(config.projectData); + await config.platformData.platformProjectService.handleNativeDependenciesChange(config.projectData, { release: config.appFilesUpdaterOptions.release }); } config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData); diff --git a/lib/services/xcconfig-service.ts b/lib/services/xcconfig-service.ts index f595d5332b..59a33353ec 100644 --- a/lib/services/xcconfig-service.ts +++ b/lib/services/xcconfig-service.ts @@ -1,12 +1,42 @@ -export class XCConfigService { - constructor(private $fs: IFileSystem) { +import * as path from "path"; + +export class XcconfigService implements IXcconfigService { + constructor( + private $childProcess: IChildProcess, + private $fs: IFileSystem, + private $xcprojService: IXcprojService) { } + + public getPluginsXcconfigFilePath(projectRoot: string, opts: IRelease): string { + if (opts && opts.release) { + return this.getPluginsReleaseXcconfigFilePath(projectRoot); + } + + return this.getPluginsDebugXcconfigFilePath(projectRoot); + } + + private getPluginsDebugXcconfigFilePath(projectRoot: string): string { + return path.join(projectRoot, "plugins-debug.xcconfig"); + } + + private getPluginsReleaseXcconfigFilePath(projectRoot: string): string { + return path.join(projectRoot, "plugins-release.xcconfig"); + } + + public async mergeFiles(sourceFile: string, destinationFile: string): Promise { + if (!this.$fs.exists(destinationFile)) { + this.$fs.writeFile(destinationFile, ""); + } + + // TODO: Consider to remove this method + await this.$xcprojService.checkIfXcodeprojIsRequired(); + + const escapedDestinationFile = destinationFile.replace(/'/g, "\\'"); + const escapedSourceFile = sourceFile.replace(/'/g, "\\'"); + + const mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedDestinationFile}').merge(Xcodeproj::Config.new('${escapedSourceFile}')).save_as(Pathname.new('${escapedDestinationFile}'))`; + await this.$childProcess.exec(`ruby -e "${mergeScript}"`); } - /** - * Returns the Value of a Property from a XC Config file. - * @param xcconfigFilePath - * @param propertyName - */ public readPropertyValue(xcconfigFilePath: string, propertyName: string): string { if (this.$fs.exists(xcconfigFilePath)) { const text = this.$fs.readText(xcconfigFilePath); @@ -37,4 +67,4 @@ export class XCConfigService { } } -$injector.register("xCConfigService", XCConfigService); +$injector.register("xcconfigService", XcconfigService); diff --git a/lib/services/xcproj-service.ts b/lib/services/xcproj-service.ts index c4ca1f4c8f..2344a89065 100644 --- a/lib/services/xcproj-service.ts +++ b/lib/services/xcproj-service.ts @@ -1,6 +1,8 @@ import * as semver from "semver"; import * as helpers from "../common/helpers"; import { EOL } from "os"; +import * as path from "path"; +import { IosProjectConstants } from "../constants"; class XcprojService implements IXcprojService { private xcprojInfoCache: IXcprojInfo; @@ -12,6 +14,10 @@ class XcprojService implements IXcprojService { private $xcodeSelectService: IXcodeSelectService) { } + public getXcodeprojPath(projectData: IProjectData, platformData: IPlatformData): string { + return path.join(platformData.projectRoot, projectData.projectName + IosProjectConstants.XcodeProjExtName); + } + public async verifyXcproj(opts: IVerifyXcprojOptions): Promise { const xcprojInfo = await this.getXcprojInfo(); if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { @@ -62,6 +68,17 @@ class XcprojService implements IXcprojService { return this.xcprojInfoCache; } + + public async checkIfXcodeprojIsRequired(): Promise { + const xcprojInfo = await this.getXcprojInfo(); + if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { + const errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`; + + this.$errors.failWithoutHelp(errorMessage); + + return true; + } + } } $injector.register("xcprojService", XcprojService); From 4339a988ef60c8a7640e6a7dc49b4f0391a350ad Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 30 Jan 2019 10:52:50 +0200 Subject: [PATCH 6/6] chore: fix tests and add test to ensure the correct order of pod install and merging of pod's xcconfig file --- test/cocoapods-service.ts | 2 + test/ios-project-service.ts | 78 ++++++++++++++++++++++++++----------- test/xcconfig-service.ts | 10 +++-- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index 92fe7f314a..df8a0d64c5 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -3,6 +3,7 @@ import { assert } from "chai"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; import { EOL } from "os"; import { LoggerStub, ErrorsStub } from "./stubs"; +import { XcconfigService } from "../lib/services/xcconfig-service"; interface IMergePodfileHooksTestCase { input: string; @@ -22,6 +23,7 @@ function createTestInjector(): IInjector { testInjector.register("xcprojService", {}); testInjector.register("logger", LoggerStub); testInjector.register("config", {}); + testInjector.register("xcconfigService", XcconfigService); return testInjector; } diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 61bfcad100..4978372bb5 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -8,7 +8,7 @@ import * as HostInfoLib from "../lib/common/host-info"; import * as iOSProjectServiceLib from "../lib/services/ios-project-service"; import { IOSProjectService } from "../lib/services/ios-project-service"; import { IOSEntitlementsService } from "../lib/services/ios-entitlements-service"; -import { XCConfigService } from "../lib/services/xcconfig-service"; +import { XcconfigService } from "../lib/services/xcconfig-service"; import * as LoggerLib from "../lib/common/logger"; import * as OptionsLib from "../lib/options"; import * as yok from "../lib/common/yok"; @@ -63,7 +63,7 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX testInjector.register("cocoapodsService", CocoaPodsService); testInjector.register("iOSProjectService", iOSProjectServiceLib.IOSProjectService); testInjector.register("iOSProvisionService", {}); - testInjector.register("xCConfigService", XCConfigService); + testInjector.register("xcconfigService", XcconfigService); testInjector.register("iOSEntitlementsService", IOSEntitlementsService); testInjector.register("logger", LoggerLib.Logger); testInjector.register("options", OptionsLib.Options); @@ -106,7 +106,11 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX return { shouldUseXcproj: false }; - } + }, + getXcodeprojPath: (projData: IProjectData, platformData: IPlatformData) => { + return path.join(platformData.projectRoot, projData.projectName + ".xcodeproj"); + }, + checkIfXcodeprojIsRequired: () => ({}) }); testInjector.register("iosDeviceOperations", {}); testInjector.register("pluginVariablesService", PluginVariablesService); @@ -129,7 +133,7 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX testInjector.register("packageManager", PackageManager); testInjector.register("npm", NodePackageManager); testInjector.register("yarn", YarnPackageManager); - testInjector.register("xCConfigService", XCConfigService); + testInjector.register("xcconfigService", XcconfigService); testInjector.register("settingsService", SettingsService); testInjector.register("httpClient", {}); testInjector.register("platformEnvironmentRequirements", {}); @@ -578,11 +582,12 @@ describe("Source code support", () => { const platformsFolderPath = path.join(projectPath, "platforms", "ios"); fs.createDirectory(platformsFolderPath); - const iOSProjectService = testInjector.resolve("iOSProjectService"); - - iOSProjectService.getXcodeprojPath = () => { + const xcprojService = testInjector.resolve("xcprojService"); + xcprojService.getXcodeprojPath = () => { return path.join(__dirname, "files"); }; + + const iOSProjectService = testInjector.resolve("iOSProjectService"); let pbxProj: any; iOSProjectService.savePbxProj = (project: any): Promise => { pbxProj = project; @@ -636,9 +641,11 @@ describe("Source code support", () => { }; }); - iOSProjectService.getXcodeprojPath = () => { + const xcprojService = testInjector.resolve("xcprojService"); + xcprojService.getXcodeprojPath = () => { return path.join(__dirname, "files"); }; + let pbxProj: any; iOSProjectService.savePbxProj = (project: any): Promise => { pbxProj = project; @@ -993,6 +1000,7 @@ describe("iOS Project Service Signing", () => { }; const changes = {}; await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); + console.log("CHANGES !!!! ", changes); assert.isFalse(!!changes.signingChanged); }); }); @@ -1083,7 +1091,7 @@ describe("Merge Project XCConfig files", () => { return; } const assertPropertyValues = (expected: any, xcconfigPath: string, injector: IInjector) => { - const service = injector.resolve('xCConfigService'); + const service = injector.resolve('xcconfigService'); _.forOwn(expected, (value, key) => { const actual = service.readPropertyValue(xcconfigPath, key); assert.equal(actual, value); @@ -1092,6 +1100,7 @@ describe("Merge Project XCConfig files", () => { let projectName: string; let projectPath: string; + let projectRoot: string; let testInjector: IInjector; let iOSProjectService: IOSProjectService; let projectData: IProjectData; @@ -1099,6 +1108,7 @@ describe("Merge Project XCConfig files", () => { let appResourcesXcconfigPath: string; let appResourceXCConfigContent: string; let iOSEntitlementsService: IOSEntitlementsService; + let xcconfigService: IXcconfigService; beforeEach(() => { projectName = "projectDirectory"; @@ -1125,6 +1135,8 @@ describe("Merge Project XCConfig files", () => { }; fs = testInjector.resolve("fs"); fs.writeJson(path.join(projectPath, "package.json"), testPackageJson); + xcconfigService = testInjector.resolve("xcconfigService"); + projectRoot = iOSProjectService.getPlatformData(projectData).projectRoot; }); it("Uses the build.xcconfig file content from App_Resources", async () => { @@ -1133,10 +1145,9 @@ describe("Merge Project XCConfig files", () => { // run merge for all release: debug|release for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); - const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) - : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); const expected = { @@ -1160,10 +1171,9 @@ describe("Merge Project XCConfig files", () => { return realExistsFunction(filePath); }; - await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); - const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) - : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); const expected = { @@ -1181,10 +1191,9 @@ describe("Merge Project XCConfig files", () => { // run merge for all release: debug|release for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); - const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) - : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); const expected = { @@ -1200,10 +1209,9 @@ describe("Merge Project XCConfig files", () => { it("creates empty plugins-.xcconfig in case there are no build.xcconfig in App_Resources and in plugins", async () => { // run merge for all release: debug|release for (const release in [true, false]) { - await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); + await (iOSProjectService).mergeProjectXcconfigFiles(projectData, { release }); - const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) - : (iOSProjectService).getPluginsDebugXcconfigFilePath(projectData); + const destinationFilePath = xcconfigService.getPluginsXcconfigFilePath(projectRoot, { release: !!release }); assert.isTrue(fs.exists(destinationFilePath), 'Target build xcconfig is missing for release: ' + release); const content = fs.readFile(destinationFilePath).toString(); @@ -1244,8 +1252,8 @@ describe("buildProject", () => { devicesService.initialize = () => ({}); devicesService.getDeviceInstances = () => data.devices || []; - const xCConfigService = testInjector.resolve("xCConfigService"); - xCConfigService.readPropertyValue = (projectDir: string, propertyName: string) => { + const xcconfigService = testInjector.resolve("xcconfigService"); + xcconfigService.readPropertyValue = (projectDir: string, propertyName: string) => { if (propertyName === "IPHONEOS_DEPLOYMENT_TARGET") { return data.deploymentTarget; } @@ -1366,3 +1374,27 @@ describe("buildProject", () => { executeTests(testCases, { buildForDevice: false }); }); }); + +describe("handleNativeDependenciesChange", () => { + it("ensure the correct order of pod install and merging pod's xcconfig file", async () => { + const executedCocoapodsMethods: string[] = []; + const projectPodfilePath = "my/test/project/platforms/ios/Podfile"; + + const testInjector = createTestInjector("myTestProjectPath", "myTestProjectName"); + const iOSProjectService = testInjector.resolve("iOSProjectService"); + const projectData = testInjector.resolve("projectData"); + + const cocoapodsService = testInjector.resolve("cocoapodsService"); + cocoapodsService.executePodInstall = async () => executedCocoapodsMethods.push("podInstall"); + cocoapodsService.mergePodXcconfigFile = async () => executedCocoapodsMethods.push("podMerge"); + cocoapodsService.applyPodfileFromAppResources = async () => ({}); + cocoapodsService.getProjectPodfilePath = () => projectPodfilePath; + + const fs = testInjector.resolve("fs"); + fs.exists = (filePath: string) => filePath === projectPodfilePath; + + await iOSProjectService.handleNativeDependenciesChange(projectData); + + assert.deepEqual(executedCocoapodsMethods, ["podInstall", "podMerge"]); + }); +}); diff --git a/test/xcconfig-service.ts b/test/xcconfig-service.ts index c60c5998e4..9305f882ab 100644 --- a/test/xcconfig-service.ts +++ b/test/xcconfig-service.ts @@ -1,6 +1,6 @@ import temp = require("temp"); import { assert } from "chai"; -import { XCConfigService } from "../lib/services/xcconfig-service"; +import { XcconfigService } from "../lib/services/xcconfig-service"; import * as yok from "../lib/common/yok"; // start tracking temporary folders/files @@ -14,8 +14,10 @@ describe("XCConfig Service Tests", () => { return true; } }); + testInjector.register('childProcess', {}); + testInjector.register('xcprojService', {}); - testInjector.register('xCConfigService', XCConfigService); + testInjector.register('xcconfigService', XcconfigService); return testInjector; }; @@ -28,8 +30,8 @@ describe("XCConfig Service Tests", () => { }); }; - function getXCConfigService(injector: IInjector): XCConfigService { - return injector.resolve("xCConfigService"); + function getXCConfigService(injector: IInjector): IXcconfigService { + return injector.resolve("xcconfigService"); } function getFileSystemMock(injector: IInjector): any {