diff --git a/lib/constants.ts b/lib/constants.ts index aa0e764f84..b0a9de8a4e 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -215,3 +215,10 @@ export class AddPlaformErrors { export const PLUGIN_BUILD_DATA_FILENAME = "plugin-data.json"; export const PLUGINS_BUILD_DATA_FILENAME = ".ns-plugins-build-data.json"; + +export class PluginNativeDirNames { + public static iOS = "ios"; + public static Android = "android"; +} + +export const PODFILE_NAME = "Podfile"; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index add168c549..73c88f57ba 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -464,10 +464,32 @@ interface ICocoaPodsService { getPodfileFooter(): string; /** - * Merges the content of hooks with the provided name if there are more than one hooks with this name in the Podfile. - * @param {string} hookName The name of the hook. - * @param {string} pathToPodfile The path to the Podfile. - * @return {void} + * Prepares the Podfile content of a plugin and merges it in the project's Podfile. + * @param {IPluginData} pluginData Information about the plugin. + * @param {IProjectData} projectData Information about the project. + * @param {string} nativeProjectPath Path to the native Xcode project. + * @returns {Promise} + */ + applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise; + + /** + * Removes plugins Podfile content from the project. + * @param {IPluginData} pluginData Information about the plugin. + * @param {IProjectData} projectData Information about the project. + * @param {string} nativeProjectPath Path to the native Xcode project. + * @returns {void} */ - mergePodfileHookContent(sectionName: string, pathToPodfile: string): void + removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): void; + + /** + * Gives the path to project's Podfile. + * @param {string} nativeProjectPath Path to the native Xcode project. + * @returns {string} Path to project's Podfile. + */ + getProjectPodfilePath(nativeProjectPath: string): string; +} + +interface IRubyFunction { + functionName: string; + functionParameters?: string; } diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index e9f8dfc117..b52dabaf6d 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -1,11 +1,11 @@ import { EOL } from "os"; - -interface IRubyFunction { - functionName: string; - functionParameters?: string; -} +import * as path from "path"; +import { PluginNativeDirNames, PODFILE_NAME } from "../constants"; export class CocoaPodsService implements ICocoaPodsService { + private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; + private static INSTALLER_BLOCK_PARAMETER_NAME = "installer"; + constructor(private $fs: IFileSystem) { } public getPodfileHeader(targetName: string): string { @@ -16,20 +16,133 @@ export class CocoaPodsService implements ICocoaPodsService { return `${EOL}end`; } - public mergePodfileHookContent(hookName: string, pathToPodfile: string): void { - if (!this.$fs.exists(pathToPodfile)) { - throw new Error(`The Podfile ${pathToPodfile} does not exist.`); + public getProjectPodfilePath(projectRoot: string): string { + return path.join(projectRoot, PODFILE_NAME); + } + + public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise { + const pluginPodFilePath = this.getPluginPodfilePath(pluginData); + if (!this.$fs.exists(pluginPodFilePath)) { + return; + } + + const { pluginPodfileContent, replacedFunctions } = this.buildPodfileContent(pluginPodFilePath, pluginData.name); + const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath); + const projectPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.$fs.readText(pathToProjectPodfile).trim() : ""; + + if (projectPodfileContent.indexOf(pluginPodfileContent) === -1) { + // Remove old occurences of the plugin from the project's Podfile. + this.removePluginPodfileFromProject(pluginData, projectData, nativeProjectPath); + let finalPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.getPodfileContentWithoutTarget(projectData, this.$fs.readText(pathToProjectPodfile)) : ""; + + if (pluginPodfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) { + finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, pluginPodfileContent); + } + + finalPodfileContent = `${pluginPodfileContent}${EOL}${finalPodfileContent}`; + this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath); + } + } + + public removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, projectRoot: string): void { + const pluginPodfilePath = this.getPluginPodfilePath(pluginData); + + if (this.$fs.exists(pluginPodfilePath) && this.$fs.exists(this.getProjectPodfilePath(projectRoot))) { + let projectPodFileContent = this.$fs.readText(this.getProjectPodfilePath(projectRoot)); + // Remove the data between #Begin Podfile and #EndPodfile + const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(pluginPodfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg"); + projectPodFileContent = projectPodFileContent.replace(regExpToRemove, ""); + projectPodFileContent = this.removePostInstallHook(pluginData, projectPodFileContent); + + const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName); + const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${EOL}${this.getPostInstallHookHeader()}end${EOL}end`; + const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}end`; + const trimmedProjectPodFileContent = projectPodFileContent.trim(); + if (!trimmedProjectPodFileContent || trimmedProjectPodFileContent === defaultContentWithPostInstallHook || trimmedProjectPodFileContent === defaultContentWithoutPostInstallHook) { + this.$fs.deleteFile(this.getProjectPodfilePath(projectRoot)); + } else { + this.$fs.writeFile(this.getProjectPodfilePath(projectRoot), projectPodFileContent); + } } + } + + private getPluginPodfilePath(pluginData: IPluginData): string { + const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(PluginNativeDirNames.iOS); + const pluginPodFilePath = path.join(pluginPlatformsFolderPath, PODFILE_NAME); + return pluginPodFilePath; + } - const podfileContent = this.$fs.readText(pathToPodfile); + private addPostInstallHook(replacedFunctions: IRubyFunction[], finalPodfileContent: string, pluginPodfileContent: string): string { + const postInstallHookStart = this.getPostInstallHookHeader(); + let postInstallHookContent = ""; + _.each(replacedFunctions, rubyFunction => { + let functionExecution = rubyFunction.functionName; + if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) { + functionExecution = `${functionExecution} ${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}`; + } + + postInstallHookContent += ` ${functionExecution}${EOL}`; + }); + + if (postInstallHookContent) { + const index = finalPodfileContent.indexOf(postInstallHookStart); + if (index !== -1) { + finalPodfileContent = finalPodfileContent.replace(postInstallHookStart, `${postInstallHookStart}${postInstallHookContent}`); + } else { + const postInstallHook = `${postInstallHookStart}${postInstallHookContent}end`; + finalPodfileContent = `${finalPodfileContent}${postInstallHook}`; + } + } + + return finalPodfileContent; + } + + private getPodfileContentWithoutTarget(projectData: IProjectData, projectPodfileContent: string): string { + const podFileHeader = this.getPodfileHeader(projectData.projectName); + + if (_.startsWith(projectPodfileContent, podFileHeader)) { + projectPodfileContent = projectPodfileContent.substr(podFileHeader.length); + + const podFileFooter = this.getPodfileFooter(); + // Only remove the final end in case the file starts with the podFileHeader + if (_.endsWith(projectPodfileContent, podFileFooter)) { + projectPodfileContent = projectPodfileContent.substr(0, projectPodfileContent.length - podFileFooter.length); + } + } + + return projectPodfileContent.trim(); + } + + private saveProjectPodfile(projectData: IProjectData, projectPodfileContent: string, projectRoot: string): void { + projectPodfileContent = this.getPodfileContentWithoutTarget(projectData, projectPodfileContent); + const podFileHeader = this.getPodfileHeader(projectData.projectName); + const podFileFooter = this.getPodfileFooter(); + const contentToWrite = `${podFileHeader}${projectPodfileContent}${podFileFooter}`; + const projectPodfilePath = this.getProjectPodfilePath(projectRoot); + this.$fs.writeFile(projectPodfilePath, contentToWrite); + } + + private removePostInstallHook(pluginData: IPluginData, projectPodFileContent: string): string { + const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginData.name)}.*?$\\r?\\n`, "gm"); + projectPodFileContent = projectPodFileContent.replace(regExp, ""); + return projectPodFileContent; + } + + private getHookBasicFuncNameForPlugin(hookName: string, pluginName: string): string { + // nativescript-hook and nativescript_hook should have different names, so replace all _ with ___ first and then replace all special symbols with _ + // This will lead to a clash in case plugins are called nativescript-hook and nativescript___hook + const replacedPluginName = pluginName.replace(/_/g, "___").replace(/[^A-Za-z0-9_]/g, "_"); + return `${hookName}${replacedPluginName}`; + } + + private replaceHookContent(hookName: string, podfileContent: string, pluginName: string): { replacedContent: string, newFunctions: IRubyFunction[] } { const hookStart = `${hookName} do`; const hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g"); - let newFunctionNameIndex = 1; const newFunctions: IRubyFunction[] = []; const replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => { - const newFunctionName = `${hookName}${newFunctionNameIndex++}`; + const newFunctionName = `${this.getHookBasicFuncNameForPlugin(hookName, pluginName)}_${newFunctions.length}`; let newDefinition = `def ${newFunctionName}`; const rubyFunction: IRubyFunction = { functionName: newFunctionName }; @@ -43,26 +156,31 @@ export class CocoaPodsService implements ICocoaPodsService { return newDefinition; }); - if (newFunctions.length > 1) { - // Execute all methods in the hook and pass the parameter to them. - const blokParameterName = "installer"; - let mergedHookContent = `${hookStart} |${blokParameterName}|${EOL}`; + return { replacedContent, newFunctions }; + } - _.each(newFunctions, (rubyFunction: IRubyFunction) => { - let functionExecution = rubyFunction.functionName; - if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) { - functionExecution = `${functionExecution} ${blokParameterName}`; - } + private getPluginPodfileHeader(pluginPodFilePath: string): string { + return `# Begin Podfile - ${pluginPodFilePath}`; + } - mergedHookContent = `${mergedHookContent} ${functionExecution}${EOL}`; - }); + private getPluginPodfileEnd(): string { + return `# End Podfile${EOL}`; + } - mergedHookContent = `${mergedHookContent}end`; + private getPostInstallHookHeader() { + return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${EOL}`; + } - const newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`; - this.$fs.writeFile(pathToPodfile, newPodfileContent); - } + private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { pluginPodfileContent: string, replacedFunctions: IRubyFunction[] } { + const pluginPodfileContent = this.$fs.readText(pluginPodFilePath); + const { replacedContent, newFunctions: replacedFunctions } = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName); + + return { + pluginPodfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${EOL}${replacedContent}${EOL}${this.getPluginPodfileEnd()}`, + replacedFunctions + }; } + } $injector.register("cocoapodsService", CocoaPodsService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 1e11cf10d2..5fcd6963bf 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1,6 +1,5 @@ import * as path from "path"; import * as shell from "shelljs"; -import * as os from "os"; import * as semver from "semver"; import * as constants from "../constants"; import * as helpers from "../common/helpers"; @@ -28,11 +27,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private static XCODEBUILD_MIN_VERSION = "6.0"; private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__"; private static IOS_PLATFORM_NAME = "ios"; - private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; - - private get $npmInstallationManager(): INpmInstallationManager { - return this.$injector.resolve("npmInstallationManager"); - } constructor($fs: IFileSystem, private $childProcess: IChildProcess, @@ -900,10 +894,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName + IOSProjectService.XCODE_PROJECT_EXT_NAME); } - private getProjectPodFilePath(projectData: IProjectData): string { - return path.join(this.getPlatformData(projectData).projectRoot, "Podfile"); - } - private getPluginsDebugXcconfigFilePath(projectData: IProjectData): string { return path.join(this.getPlatformData(projectData).projectRoot, "plugins-debug.xcconfig"); } @@ -951,7 +941,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData); await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData); await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData); - await this.prepareCocoapods(pluginPlatformsFolderPath, projectData); + await this.prepareCocoapods(pluginPlatformsFolderPath, pluginData, projectData); } public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { @@ -960,20 +950,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData); this.removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData); this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData); - this.removeCocoapods(pluginPlatformsFolderPath, projectData); + const projectRoot = this.getPlatformData(projectData).projectRoot; + + this.$cocoapodsService.removePluginPodfileFromProject(pluginData, projectData, projectRoot); } public async afterPrepareAllPlugins(projectData: IProjectData): Promise { - if (this.$fs.exists(this.getProjectPodFilePath(projectData))) { - const projectPodfileContent = this.$fs.readText(this.getProjectPodFilePath(projectData)); - this.$logger.trace("Project Podfile content"); - this.$logger.trace(projectPodfileContent); - - const firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME); - if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) { - this.$cocoapodsService.mergePodfileHookContent(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME, this.getProjectPodFilePath(projectData)); - } - + const projectRoot = this.getPlatformData(projectData).projectRoot; + if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot))) { const xcuserDataPath = path.join(this.getXcodeprojPath(projectData), "xcuserdata"); const sharedDataPath = path.join(this.getXcodeprojPath(projectData), "xcshareddata"); @@ -1169,38 +1153,15 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } } - private async prepareCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData, opts?: any): Promise { + private async prepareCocoapods(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData, opts?: any): Promise { + const projectRoot = this.getPlatformData(projectData).projectRoot; + await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot); const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - if (this.$fs.exists(pluginPodFilePath)) { - const pluginPodFileContent = this.$fs.readText(pluginPodFilePath); - const pluginPodFilePreparedContent = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); - let projectPodFileContent = this.$fs.exists(this.getProjectPodFilePath(projectData)) ? this.$fs.readText(this.getProjectPodFilePath(projectData)) : ""; - - if (!~projectPodFileContent.indexOf(pluginPodFilePreparedContent)) { - const podFileHeader = this.$cocoapodsService.getPodfileHeader(projectData.projectName), - podFileFooter = this.$cocoapodsService.getPodfileFooter(); - - if (_.startsWith(projectPodFileContent, podFileHeader)) { - projectPodFileContent = projectPodFileContent.substr(podFileHeader.length); - } - - if (_.endsWith(projectPodFileContent, podFileFooter)) { - projectPodFileContent = projectPodFileContent.substr(0, projectPodFileContent.length - podFileFooter.length); - } - - const contentToWrite = `${podFileHeader}${projectPodFileContent}${pluginPodFilePreparedContent}${podFileFooter}`; - this.$fs.writeFile(this.getProjectPodFilePath(projectData), contentToWrite); - - const project = this.createPbxProj(projectData); - this.savePbxProj(project, projectData); - } - } if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath)) { await this.executePodInstall(projectData); } } - private removeNativeSourceCode(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void { const project = this.createPbxProj(projectData); const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath); @@ -1235,26 +1196,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.savePbxProj(project, projectData); } - private removeCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData): void { - const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - - if (this.$fs.exists(pluginPodFilePath) && this.$fs.exists(this.getProjectPodFilePath(projectData))) { - const pluginPodFileContent = this.$fs.readText(pluginPodFilePath); - let projectPodFileContent = this.$fs.readText(this.getProjectPodFilePath(projectData)); - const contentToRemove = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); - projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, ""); - if (projectPodFileContent.trim() === `use_frameworks!${os.EOL}${os.EOL}target "${projectData.projectName}" do${os.EOL}${os.EOL}end`) { - this.$fs.deleteFile(this.getProjectPodFilePath(projectData)); - } else { - this.$fs.writeFile(this.getProjectPodFilePath(projectData), projectPodFileContent); - } - } - } - - private buildPodfileContent(pluginPodFilePath: string, pluginPodFileContent: string): string { - return `# Begin Podfile - ${pluginPodFilePath} ${os.EOL} ${pluginPodFileContent} ${os.EOL} # End Podfile ${os.EOL}`; - } - private generateModulemap(headersFolderPath: string, libraryName: string): void { const headersFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".h" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile()); const headersFolderContents = this.$fs.readDirectory(headersFolderPath); diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index f63770aeba..49602612d0 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -7,6 +7,8 @@ interface IMergePodfileHooksTestCase { input: string; output: string; testCaseDescription: string; + projectPodfileContent?: string; + pluginData?: IPluginData; } function createTestInjector(): IInjector { @@ -26,23 +28,114 @@ function changeNewLineCharacter(input: string): string { } describe("Cocoapods service", () => { - describe("merge Podfile hooks", () => { - let testInjector: IInjector; - let cocoapodsService: ICocoaPodsService; - let newPodfileContent: string; - - const mockFileSystem = (injector: IInjector, podfileContent: string): void => { - const fs: IFileSystem = injector.resolve("fs"); - - fs.exists = () => true; - fs.readText = () => podfileContent; - fs.writeFile = (pathToFile: string, content: any) => { - newPodfileContent = content; - }; + const nativeProjectPath = "nativeProjectPath"; + const mockPluginData: any = { + name: "plugin1", + pluginPlatformsFolderPath: () => "pluginPlatformsFolderPath" + }; + const mockProjectData: any = { + projectDir: "projectDir", + projectName: "projectName" + }; + + let testInjector: IInjector; + let cocoapodsService: ICocoaPodsService; + let newPodfileContent = ""; + + const mockFileSystem = (injector: IInjector, podfileContent: string, projectPodfileContent?: string): void => { + const fs: IFileSystem = injector.resolve("fs"); + + fs.exists = () => true; + fs.readText = (file: string) => { + if (file.indexOf("pluginPlatformsFolderPath") !== -1) { + return podfileContent; + } + + return newPodfileContent || projectPodfileContent || ""; }; - const testCaces: IMergePodfileHooksTestCase[] = [ + fs.writeFile = (pathToFile: string, content: any) => { + newPodfileContent = content; + }; + + fs.deleteFile = (path: string): void => { + newPodfileContent = null; + projectPodfileContent = null; + }; + }; + + beforeEach(() => { + testInjector = createTestInjector(); + cocoapodsService = testInjector.resolve("cocoapodsService"); + newPodfileContent = ""; + }); + + describe("merges Podfile files correctly", () => { + const testCases: IMergePodfileHooksTestCase[] = [ + { + testCaseDescription: "adds plugin's Podfile to project's one", + input: ` +pod 'GoogleAnalytics', '~> 3.1' +`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +pod 'GoogleAnalytics', '~> 3.1' + +# End Podfile +end`, + projectPodfileContent: "" + }, + { + testCaseDescription: "replaces correctly special chars from plugin's name", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_with_special_symbols_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| + post_installplugin1_with_special_symbols_0 installer +end +end`, + projectPodfileContent: "", + pluginData: { + name: "plugin1-with-special-symbols", + pluginPlatformsFolderPath: () => "pluginPlatformsFolderPath" + } + }, { + testCaseDescription: "replaces correctly special chars from plugin's name when plugin has _", input: ` target 'MyApp' do pod 'GoogleAnalytics', '~> 3.1' @@ -56,18 +149,97 @@ post_install do |installer| installer.pods_project.targets.each do |target| puts target.name end +end`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_with_special_symbols___and___underscore_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| + post_installplugin1_with_special_symbols___and___underscore_0 installer end +end`, + projectPodfileContent: "", + pluginData: { + name: "plugin1-with-special-symbols_and_underscore", + pluginPlatformsFolderPath: () => "pluginPlatformsFolderPath" + } + }, + { + testCaseDescription: "treats plugin1_plugin and plugin1___plugin as the same plugin (by design as we do not expect plugins to have names with three underscores)", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' +end + post_install do |installer| installer.pods_project.targets.each do |target| puts target.name end +end`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' +end + +def post_installplugin1___plugin_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end end +# End Podfile + post_install do |installer| + post_installplugin1___plugin_0 installer +end +end`, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'OCMock', '~> 2.0.1' +end + +def post_installplugin1___plugin_0 (installer) installer.pods_project.targets.each do |target| puts target.name end +end +# End Podfile + +post_install do |installer| + post_installplugin1___plugin_0 installer +end end`, - output: ` + pluginData: { + name: "plugin1_plugin", + pluginPlatformsFolderPath: () => "pluginPlatformsFolderPath" + } + }, + { + testCaseDescription: "replaces the plugin's old Podfile with the new one inside project's Podfile", + input: ` target 'MyApp' do pod 'GoogleAnalytics', '~> 3.1' target 'MyAppTests' do @@ -76,28 +248,125 @@ target 'MyApp' do end end -def post_install1 (installer) +post_install do |installer| installer.pods_project.targets.each do |target| puts target.name end +end`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end end -def post_install2 (installer) + +def post_installplugin1_0 (installer) installer.pods_project.targets.each do |target| puts target.name end end -def post_install3 (installer) +# End Podfile + +post_install do |installer| + post_installplugin1_0 installer +end +end`, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 2.1' # version changed here + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) installer.pods_project.targets.each do |target| puts target.name end end +# End Podfile + post_install do |installer| - post_install1 installer - post_install2 installer - post_install3 installer + post_installplugin1_0 installer +end end`, - testCaseDescription: "should merge more than one hooks with block parameter correctly." - }, { + }, + { + testCaseDescription: "merges more than one hooks with block parameter correctly.", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end`, + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_installplugin1_1 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_installplugin1_2 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| + post_installplugin1_0 installer + post_installplugin1_1 installer + post_installplugin1_2 installer +end +end`, + }, + { + testCaseDescription: "merges more than one hooks with and without block parameter correctly", input: ` target 'MyApp' do pod 'GoogleAnalytics', '~> 3.1' @@ -115,7 +384,11 @@ target 'MyApp' do puts "Hello World!" end end`, - output: ` + output: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + target 'MyApp' do pod 'GoogleAnalytics', '~> 3.1' target 'MyAppTests' do @@ -123,21 +396,25 @@ target 'MyApp' do pod 'OCMock', '~> 2.0.1' end - def post_install1 (installer_representation) + def post_installplugin1_0 (installer_representation) installer_representation.pods_project.targets.each do |target| puts target.name end end - def post_install2 + def post_installplugin1_1 puts "Hello World!" end end +# End Podfile + post_install do |installer| - post_install1 installer - post_install2 + post_installplugin1_0 installer + post_installplugin1_1 +end end`, - testCaseDescription: "should merge more than one hooks with and without block parameter correctly." - }, { + }, + { + testCaseDescription: "should not change the Podfile when the plugin content is already part of the project", input: ` target 'MyApp' do pod 'GoogleAnalytics', '~> 3.1' @@ -152,22 +429,248 @@ post_install do |installer| puts target.name end end`, - output: null, - testCaseDescription: "should not change the Podfile when there is only one hook." + output: "", + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| + post_installplugin1_0 installer +end +end`, } ]; - beforeEach(() => { - testInjector = createTestInjector(); - cocoapodsService = testInjector.resolve("cocoapodsService"); - newPodfileContent = null; + _.each(testCases, (testCase: IMergePodfileHooksTestCase) => { + it(testCase.testCaseDescription, async () => { + mockFileSystem(testInjector, testCase.input, testCase.projectPodfileContent); + + await cocoapodsService.applyPluginPodfileToProject(testCase.pluginData || mockPluginData, mockProjectData, nativeProjectPath); + + assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output)); + }); }); + }); + + describe("removes plugin's Podfile correctly", () => { + const testCases: IMergePodfileHooksTestCase[] = [ + { + testCaseDescription: "removes plugin's Podfile from project's one and deletes project's Podfile as nothing is left there", + input: ` +pod 'GoogleAnalytics', '~> 3.1' +`, + output: null, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +pod 'GoogleAnalytics', '~> 3.1' + +# End Podfile +end` + }, + { + testCaseDescription: "removes plugin's Podfile (with hook) from project's one and deletes project's Podfile as nothing is left there", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end`, + output: null, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| +post_installplugin1_0 installer +end +end` + }, + { + testCaseDescription: "removes Podfile which has several postinstall hooks and deletes project's Podfile as nothing is left there", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end`, + output: null, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_installplugin1_1 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_installplugin1_2 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| + post_installplugin1_0 installer + post_installplugin1_1 installer + post_installplugin1_2 installer +end +end` + }, + { + testCaseDescription: "removes plugin's Podfile (with hook) from project's one when there are other plugins with hooks in the project Podfile", + input: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + puts target.name + end +end`, + output: `use_frameworks! + +target "projectName" do + +# Begin Podfile - pluginPlatformsFolderPath1/Podfile + +pod 'Firebase', '~> 3.1' + +def post_installplugin2_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| +post_installplugin2_0 installer +end +end`, + projectPodfileContent: `use_frameworks! + +target "projectName" do +# Begin Podfile - pluginPlatformsFolderPath/Podfile + +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_installplugin1_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +# Begin Podfile - pluginPlatformsFolderPath1/Podfile + +pod 'Firebase', '~> 3.1' + +def post_installplugin2_0 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +# End Podfile + +post_install do |installer| +post_installplugin1_0 installer +post_installplugin2_0 installer +end +end` + } + ]; - _.each(testCaces, (testCase: IMergePodfileHooksTestCase) => { - it(testCase.testCaseDescription, () => { - mockFileSystem(testInjector, testCase.input); + _.each(testCases, (testCase: IMergePodfileHooksTestCase) => { + it(testCase.testCaseDescription, async () => { + mockFileSystem(testInjector, testCase.input, testCase.projectPodfileContent); - cocoapodsService.mergePodfileHookContent("post_install", ""); + cocoapodsService.removePluginPodfileFromProject(mockPluginData, mockProjectData, nativeProjectPath); assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output)); }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index e65ffd51bb..7b6fa20e5c 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -27,7 +27,6 @@ import { PluginsService } from "../lib/services/plugins-service"; import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; import { Utils } from "../lib/common/utils"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; -import { NpmInstallationManager } from "../lib/npm-installation-manager"; import { NodePackageManager } from "../lib/node-package-manager"; import { assert } from "chai"; @@ -114,7 +113,6 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX pbxGroupByName() { /* */ } } }); - testInjector.register("npmInstallationManager", NpmInstallationManager); testInjector.register("npm", NodePackageManager); testInjector.register("xCConfigService", XCConfigService); testInjector.register("settingsService", SettingsService); @@ -384,9 +382,9 @@ describe("Cocoapods support", () => { const actualProjectPodfileContent = fs.readText(projectPodfilePath); const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, - `# Begin Podfile - ${pluginPodfilePath} `, - ` ${pluginPodfileContent} `, - " # End Podfile \n", + `# Begin Podfile - ${pluginPodfilePath}`, + `${pluginPodfileContent}`, + "# End Podfile", "end"] .join("\n"); assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); @@ -445,7 +443,9 @@ describe("Cocoapods support", () => { const pluginData = { pluginPlatformsFolderPath(platform: string): string { return pluginPlatformsFolderPath; - } + }, + name: "pluginName", + fullPath: "fullPath" }; const projectData: IProjectData = testInjector.resolve("projectData"); @@ -457,9 +457,9 @@ describe("Cocoapods support", () => { const actualProjectPodfileContent = fs.readText(projectPodfilePath); const expectedProjectPodfileContent = ["use_frameworks!\n", `target "${projectName}" do`, - `# Begin Podfile - ${pluginPodfilePath} `, - ` ${pluginPodfileContent} `, - " # End Podfile \n", + `# Begin Podfile - ${pluginPodfilePath}`, + `${pluginPodfileContent}`, + "# End Podfile", "end"] .join("\n"); assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent);