From 96ccd94090d51a72fe6af0cbd0b98d4f5809b4e0 Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Thu, 17 Nov 2016 22:14:20 +0200 Subject: [PATCH] Merge post_install hooks in Podfile Currently the CLI does not merge the post_install hooks in the Podfile. When there are more than one post_install hooks cocoapods will fail on pod install. The solution is to find all the post_install hooks in the merged Podfile and transform them to methods. After that post_install will be called with block in which all the transformed methods will be called. --- lib/definitions/project.d.ts | 8 ++ lib/services/cocoapods-service.ts | 57 +++++++++ lib/services/ios-project-service.ts | 2 +- test/cocoapods-service.ts | 178 ++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 test/cocoapods-service.ts diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 1020efd93c..d8c4943c3f 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -122,4 +122,12 @@ interface ICocoaPodsService { * @return {string} The footer which needs to be placed at the end of a Podfile. */ 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 {IFuture} + */ + mergePodfileHookContent(sectionName: string, pathToPodfile: string): IFuture } diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index 9bb3383ff4..73fa586ba8 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -1,6 +1,13 @@ import {EOL} from "os"; +interface IRubyFunction { + functionName: string; + functionParameters?: string; +} + export class CocoaPodsService implements ICocoaPodsService { + constructor(private $fs: IFileSystem) { } + public getPodfileHeader(targetName: string): string { return `use_frameworks!${EOL}${EOL}target "${targetName}" do${EOL}`; } @@ -8,6 +15,56 @@ export class CocoaPodsService implements ICocoaPodsService { public getPodfileFooter(): string { return `${EOL}end`; } + + public mergePodfileHookContent(hookName: string, pathToPodfile: string): IFuture { + return (() => { + if (!this.$fs.exists(pathToPodfile).wait()) { + throw new Error(`The Podfile ${pathToPodfile} does not exist.`); + } + + let podfileContent = this.$fs.readText(pathToPodfile).wait(); + let hookStart = `${hookName} do`; + + let hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g"); + let newFunctionNameIndex = 1; + let newFunctions: IRubyFunction[] = []; + + let replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => { + let newFunctionName = `${hookName}${newFunctionNameIndex++}`; + let newDefinition = `def ${newFunctionName}`; + + let rubyFunction: IRubyFunction = { functionName: newFunctionName }; + // firstGroup is the block parameter, secondGroup is the block parameter name. + if (firstGroup && secondGroup) { + newDefinition = `${newDefinition} (${secondGroup})`; + rubyFunction.functionParameters = secondGroup; + } + + newFunctions.push(rubyFunction); + return newDefinition; + }); + + if (newFunctions.length > 1) { + // Execute all methods in the hook and pass the parameter to them. + let blokParameterName = "installer"; + let mergedHookContent = `${hookStart} |${blokParameterName}|${EOL}`; + + _.each(newFunctions, (rubyFunction: IRubyFunction) => { + let functionExecution = rubyFunction.functionName; + if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) { + functionExecution = `${functionExecution} ${blokParameterName}`; + } + + mergedHookContent = `${mergedHookContent} ${functionExecution}${EOL}`; + }); + + mergedHookContent = `${mergedHookContent}end`; + + let newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`; + this.$fs.writeFile(pathToPodfile, newPodfileContent).wait(); + } + }).future()(); + } } $injector.register("cocoapodsService", CocoaPodsService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index a35e6c865a..2e3c1a5b2f 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -782,7 +782,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f let firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME); if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) { - this.$logger.warn(`Podfile contains more than one post_install sections. You need to open ${this.projectPodFilePath} file and manually resolve this issue.`); + this.$cocoapodsService.mergePodfileHookContent(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME, this.projectPodFilePath).wait(); } let xcuserDataPath = path.join(this.xcodeprojPath, "xcuserdata"); diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts new file mode 100644 index 0000000000..ce7b6af783 --- /dev/null +++ b/test/cocoapods-service.ts @@ -0,0 +1,178 @@ +import * as yok from "../lib/common/yok"; +import {assert} from "chai"; +import {CocoaPodsService} from "../lib/services/cocoapods-service"; +import {EOL} from "os"; +import Future = require("fibers/future"); + +interface IMergePodfileHooksTestCase { + input: string; + output: string; + testCaseDescription: string; +} + +function createTestInjector(): IInjector { + let testInjector: IInjector = new yok.Yok(); + + testInjector.register("fs", {}); + testInjector.register("cocoapodsService", CocoaPodsService); + + return testInjector; +} + +// The newline characters should be replaced with EOL because on Windows the EOL is \r\n +// but the character which is placed in `` for newline is only \n +// if we do not replace the newline characters the tests will pass only on linux and mac. +function changeNewLineCharacter(input: string): string { + return input ? input.replace(/\r?\n/g, EOL) : input; +} + +describe("Cocoapods service", () => { + describe("merge Podfile hooks", () => { + let testInjector: IInjector; + let cocoapodsService: ICocoaPodsService; + let newPodfileContent: string; + + let mockFileSystem = (injector: IInjector, podfileContent: string): void => { + let fs: IFileSystem = injector.resolve("fs"); + + fs.exists = () => Future.fromResult(true); + fs.readText = () => Future.fromResult(podfileContent); + fs.writeFile = (pathToFile: string, content: any) => { + newPodfileContent = content; + return Future.fromResult(); + }; + }; + + let testCaces: IMergePodfileHooksTestCase[] = [ + { + 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: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end +end + +def post_install1 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_install2 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +def post_install3 (installer) + installer.pods_project.targets.each do |target| + puts target.name + end +end +post_install do |installer| + post_install1 installer + post_install2 installer + post_install3 installer +end`, + testCaseDescription: "should merge 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 + + post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + puts target.name + end + end + post_install do + puts "Hello World!" + end +end`, + output: ` +target 'MyApp' do + pod 'GoogleAnalytics', '~> 3.1' + target 'MyAppTests' do + inherit! :search_paths + pod 'OCMock', '~> 2.0.1' + end + + def post_install1 (installer_representation) + installer_representation.pods_project.targets.each do |target| + puts target.name + end + end + def post_install2 + puts "Hello World!" + end +end +post_install do |installer| + post_install1 installer + post_install2 +end`, + testCaseDescription: "should merge more than one hooks with and without 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`, + output: null, + testCaseDescription: "should not change the Podfile when there is only one hook." + } + ]; + + beforeEach(() => { + testInjector = createTestInjector(); + cocoapodsService = testInjector.resolve("cocoapodsService"); + newPodfileContent = null; + }); + + _.each(testCaces, (testCase: IMergePodfileHooksTestCase) => { + it(testCase.testCaseDescription, () => { + mockFileSystem(testInjector, testCase.input); + + cocoapodsService.mergePodfileHookContent("post_install", "").wait(); + + assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output)); + }); + }); + }); +});