diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 960e9875d8..e53b8a8182 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -112,4 +112,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 9af05811fe..38da9f2230 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -269,10 +269,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args = args.concat((buildConfig && buildConfig.architectures) || defaultArchitectures); let xcodeBuildVersion = this.getXcodeVersion(); - if (helpers.versionCompare(xcodeBuildVersion, "8.0")>=0) { + if (helpers.versionCompare(xcodeBuildVersion, "8.0") >= 0) { let teamId = this.getDevelopmentTeam(); if (teamId) { - args = args.concat("DEVELOPMENT_TEAM="+teamId); + args = args.concat("DEVELOPMENT_TEAM=" + teamId); } } } else { @@ -478,10 +478,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f try { this.$fs.createDirectory(path.dirname(compatabilityXibPath)).wait(); this.$fs.writeFile(compatabilityXibPath, content).wait(); - } catch(e) { + } catch (e) { this.$logger.warn("We have failed to add compatability LaunchScreen.xib due to: " + e); } - } catch(e) { + } catch (e) { this.$logger.warn("We have failed to check if we need to add a compatability LaunchScreen.xib due to: " + e); } } @@ -687,7 +687,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"); @@ -948,7 +948,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f let pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); let pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, "build.xcconfig"); if (this.$fs.exists(pluginXcconfigFilePath).wait()) { - this.mergeXcconfigFiles(pluginXcconfigFilePath,this.$options.release ? this.pluginsReleaseXcconfigFilePath : this.pluginsDebugXcconfigFilePath).wait(); + this.mergeXcconfigFiles(pluginXcconfigFilePath, this.$options.release ? this.pluginsReleaseXcconfigFilePath : this.pluginsDebugXcconfigFilePath).wait(); } } @@ -999,7 +999,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return xcodeBuildVersion; } - private getDevelopmentTeams(): Array<{id:string, name: string}> { + private getDevelopmentTeams(): Array<{ id: string, name: string }> { let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/"); let files = this.$fs.readDirectory(dir).wait(); let teamIds: any = {}; @@ -1012,18 +1012,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f teamIds[teamId] = teamName; } } - let teamIdsArray = new Array<{id:string, name: string}>(); + let teamIdsArray = new Array<{ id: string, name: string }>(); for (let teamId in teamIds) { - teamIdsArray.push({ id:teamId, name:teamIds[teamId] }); + teamIdsArray.push({ id: teamId, name: teamIds[teamId] }); } return teamIdsArray; } private getProvisioningProfileValue(name: string, text: string): string { - let findStr = ""+name+""; + let findStr = "" + name + ""; let index = text.indexOf(findStr); if (index > 0) { - index = text.indexOf("", index+findStr.length); + index = text.indexOf("", index + findStr.length); if (index > 0) { index += "".length; let endIndex = text.indexOf("", index); @@ -1043,7 +1043,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f line = line.replace(/\/(\/)[^\n]*$/, ""); if (line.indexOf("DEVELOPMENT_TEAM") >= 0) { teamId = line.split("=")[1].trim(); - if (teamId[teamId.length-1] === ';') { + if (teamId[teamId.length - 1] === ';') { teamId = teamId.slice(0, -1); } } @@ -1086,7 +1086,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f "Yes, persist the team id in platforms folder.", "No, don't persist this setting." ]; - let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: "+teamId+" a persistent choice for your app?", choicesPersist).wait(); + let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist).wait(); switch (choicesPersist.indexOf(choicePersist)) { case 0: let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig"); 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)); + }); + }); + }); +}); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index b9adef3fc8..c71416a9ca 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -303,7 +303,7 @@ describe("Cocoapods support", () => { let pluginPath = temp.mkdirSync("pluginDirectory"); let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); + let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait(); let pluginData = { @@ -373,7 +373,7 @@ describe("Cocoapods support", () => { let pluginPath = temp.mkdirSync("pluginDirectory"); let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios"); let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); + let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait(); let pluginData = { @@ -417,7 +417,7 @@ describe("Static libraries support", () => { let testInjector = createTestInjector(projectPath, projectName); let fs: IFileSystem = testInjector.resolve("fs"); let staticLibraryPath = path.join(path.join(temp.mkdirSync("pluginDirectory"), "platforms", "ios")); - let staticLibraryHeadersPath = path.join(staticLibraryPath,"include", libraryName); + let staticLibraryHeadersPath = path.join(staticLibraryPath, "include", libraryName); it("checks validation of header files", () => { let iOSProjectService = testInjector.resolve("iOSProjectService"); @@ -430,7 +430,7 @@ describe("Static libraries support", () => { let error: any; try { iOSProjectService.validateStaticLibrary(path.join(staticLibraryPath, libraryName + ".a")).wait(); - } catch(err) { + } catch (err) { error = err; } @@ -457,7 +457,7 @@ describe("Static libraries support", () => { let error: any; try { modulemap = fs.readFile(path.join(staticLibraryHeadersPath, "module.modulemap")).wait(); - } catch(err) { + } catch (err) { error = err; } @@ -466,16 +466,16 @@ describe("Static libraries support", () => { }); describe("Relative paths", () => { - it("checks for correct calculation of relative paths", () => { - let projectName = "projectDirectory"; - let projectPath = temp.mkdirSync(projectName); - let subpath = path.join(projectPath, "sub/path"); + it("checks for correct calculation of relative paths", () => { + let projectName = "projectDirectory"; + let projectPath = temp.mkdirSync(projectName); + let subpath = path.join(projectPath, "sub", "path"); - let testInjector = createTestInjector(projectPath, projectName); - createPackageJson(testInjector, projectPath, projectName); - let iOSProjectService = testInjector.resolve("iOSProjectService"); + let testInjector = createTestInjector(projectPath, projectName); + createPackageJson(testInjector, projectPath, projectName); + let iOSProjectService = testInjector.resolve("iOSProjectService"); - let result = iOSProjectService.getLibSubpathRelativeToProjectPath(subpath); - assert.equal(result, "../../sub/path"); - }); + let result = iOSProjectService.getLibSubpathRelativeToProjectPath(subpath); + assert.equal(result, path.join("..", "..", "sub", "path")); + }); });