Skip to content

Commit 64b3768

Browse files
Merge pull request #2289 from NativeScript/milanov/appbuilder-2.4.0-update-with-podfile-merge
Merge post_install hooks in Podfile
2 parents 8d5684d + 9a2314c commit 64b3768

File tree

4 files changed

+256
-13
lines changed

4 files changed

+256
-13
lines changed

lib/definitions/project.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,12 @@ interface ICocoaPodsService {
122122
* @return {string} The footer which needs to be placed at the end of a Podfile.
123123
*/
124124
getPodfileFooter(): string;
125+
126+
/**
127+
* Merges the content of hooks with the provided name if there are more than one hooks with this name in the Podfile.
128+
* @param {string} hookName The name of the hook.
129+
* @param {string} pathToPodfile The path to the Podfile.
130+
* @return {IFuture<void>}
131+
*/
132+
mergePodfileHookContent(sectionName: string, pathToPodfile: string): IFuture<void>
125133
}

lib/services/cocoapods-service.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,70 @@
11
import {EOL} from "os";
22

3+
interface IRubyFunction {
4+
functionName: string;
5+
functionParameters?: string;
6+
}
7+
38
export class CocoaPodsService implements ICocoaPodsService {
9+
constructor(private $fs: IFileSystem) { }
10+
411
public getPodfileHeader(targetName: string): string {
512
return `use_frameworks!${EOL}${EOL}target "${targetName}" do${EOL}`;
613
}
714

815
public getPodfileFooter(): string {
916
return `${EOL}end`;
1017
}
18+
19+
public mergePodfileHookContent(hookName: string, pathToPodfile: string): IFuture<void> {
20+
return (() => {
21+
if (!this.$fs.exists(pathToPodfile).wait()) {
22+
throw new Error(`The Podfile ${pathToPodfile} does not exist.`);
23+
}
24+
25+
let podfileContent = this.$fs.readText(pathToPodfile).wait();
26+
let hookStart = `${hookName} do`;
27+
28+
let hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g");
29+
let newFunctionNameIndex = 1;
30+
let newFunctions: IRubyFunction[] = [];
31+
32+
let replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => {
33+
let newFunctionName = `${hookName}${newFunctionNameIndex++}`;
34+
let newDefinition = `def ${newFunctionName}`;
35+
36+
let rubyFunction: IRubyFunction = { functionName: newFunctionName };
37+
// firstGroup is the block parameter, secondGroup is the block parameter name.
38+
if (firstGroup && secondGroup) {
39+
newDefinition = `${newDefinition} (${secondGroup})`;
40+
rubyFunction.functionParameters = secondGroup;
41+
}
42+
43+
newFunctions.push(rubyFunction);
44+
return newDefinition;
45+
});
46+
47+
if (newFunctions.length > 1) {
48+
// Execute all methods in the hook and pass the parameter to them.
49+
let blokParameterName = "installer";
50+
let mergedHookContent = `${hookStart} |${blokParameterName}|${EOL}`;
51+
52+
_.each(newFunctions, (rubyFunction: IRubyFunction) => {
53+
let functionExecution = rubyFunction.functionName;
54+
if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) {
55+
functionExecution = `${functionExecution} ${blokParameterName}`;
56+
}
57+
58+
mergedHookContent = `${mergedHookContent} ${functionExecution}${EOL}`;
59+
});
60+
61+
mergedHookContent = `${mergedHookContent}end`;
62+
63+
let newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`;
64+
this.$fs.writeFile(pathToPodfile, newPodfileContent).wait();
65+
}
66+
}).future<void>()();
67+
}
1168
}
1269

1370
$injector.register("cocoapodsService", CocoaPodsService);

lib/services/ios-project-service.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
280280
args = args.concat((buildConfig && buildConfig.architectures) || defaultArchitectures);
281281

282282
let xcodeBuildVersion = this.getXcodeVersion();
283-
if (helpers.versionCompare(xcodeBuildVersion, "8.0")>=0) {
283+
if (helpers.versionCompare(xcodeBuildVersion, "8.0") >= 0) {
284284
let teamId = this.getDevelopmentTeam();
285285
if (teamId) {
286-
args = args.concat("DEVELOPMENT_TEAM="+teamId);
286+
args = args.concat("DEVELOPMENT_TEAM=" + teamId);
287287
}
288288
}
289289
} else {
@@ -551,10 +551,10 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
551551
try {
552552
this.$fs.createDirectory(path.dirname(compatabilityXibPath)).wait();
553553
this.$fs.writeFile(compatabilityXibPath, content).wait();
554-
} catch(e) {
554+
} catch (e) {
555555
this.$logger.warn("We have failed to add compatability LaunchScreen.xib due to: " + e);
556556
}
557-
} catch(e) {
557+
} catch (e) {
558558
this.$logger.warn("We have failed to check if we need to add a compatability LaunchScreen.xib due to: " + e);
559559
}
560560
}
@@ -761,7 +761,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
761761

762762
let firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME);
763763
if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) {
764-
this.$logger.warn(`Podfile contains more than one post_install sections. You need to open ${this.projectPodFilePath} file and manually resolve this issue.`);
764+
this.$cocoapodsService.mergePodfileHookContent(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME, this.projectPodFilePath).wait();
765765
}
766766

767767
let xcuserDataPath = path.join(this.xcodeprojPath, "xcuserdata");
@@ -1019,7 +1019,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10191019
let pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
10201020
let pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, "build.xcconfig");
10211021
if (this.$fs.exists(pluginXcconfigFilePath).wait()) {
1022-
this.mergeXcconfigFiles(pluginXcconfigFilePath,this.$options.release ? this.pluginsReleaseXcconfigFilePath : this.pluginsDebugXcconfigFilePath).wait();
1022+
this.mergeXcconfigFiles(pluginXcconfigFilePath, this.$options.release ? this.pluginsReleaseXcconfigFilePath : this.pluginsDebugXcconfigFilePath).wait();
10231023
}
10241024
}
10251025

@@ -1070,7 +1070,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10701070
return xcodeBuildVersion;
10711071
}
10721072

1073-
private getDevelopmentTeams(): Array<{id:string, name: string}> {
1073+
private getDevelopmentTeams(): Array<{ id: string, name: string }> {
10741074
let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/");
10751075
let files = this.$fs.readDirectory(dir).wait();
10761076
let teamIds: any = {};
@@ -1083,18 +1083,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10831083
teamIds[teamId] = teamName;
10841084
}
10851085
}
1086-
let teamIdsArray = new Array<{id:string, name: string}>();
1086+
let teamIdsArray = new Array<{ id: string, name: string }>();
10871087
for (let teamId in teamIds) {
1088-
teamIdsArray.push({ id:teamId, name:teamIds[teamId] });
1088+
teamIdsArray.push({ id: teamId, name: teamIds[teamId] });
10891089
}
10901090
return teamIdsArray;
10911091
}
10921092

10931093
private getProvisioningProfileValue(name: string, text: string): string {
1094-
let findStr = "<key>"+name+"</key>";
1094+
let findStr = "<key>" + name + "</key>";
10951095
let index = text.indexOf(findStr);
10961096
if (index > 0) {
1097-
index = text.indexOf("<string>", index+findStr.length);
1097+
index = text.indexOf("<string>", index + findStr.length);
10981098
if (index > 0) {
10991099
index += "<string>".length;
11001100
let endIndex = text.indexOf("</string>", index);
@@ -1114,7 +1114,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
11141114
line = line.replace(/\/(\/)[^\n]*$/, "");
11151115
if (line.indexOf("DEVELOPMENT_TEAM") >= 0) {
11161116
teamId = line.split("=")[1].trim();
1117-
if (teamId[teamId.length-1] === ';') {
1117+
if (teamId[teamId.length - 1] === ';') {
11181118
teamId = teamId.slice(0, -1);
11191119
}
11201120
}
@@ -1157,7 +1157,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
11571157
"Yes, persist the team id in platforms folder.",
11581158
"No, don't persist this setting."
11591159
];
1160-
let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: "+teamId+" a persistent choice for your app?", choicesPersist).wait();
1160+
let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist).wait();
11611161
switch (choicesPersist.indexOf(choicePersist)) {
11621162
case 0:
11631163
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");

test/cocoapods-service.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import * as yok from "../lib/common/yok";
2+
import {assert} from "chai";
3+
import {CocoaPodsService} from "../lib/services/cocoapods-service";
4+
import {EOL} from "os";
5+
import Future = require("fibers/future");
6+
7+
interface IMergePodfileHooksTestCase {
8+
input: string;
9+
output: string;
10+
testCaseDescription: string;
11+
}
12+
13+
function createTestInjector(): IInjector {
14+
let testInjector: IInjector = new yok.Yok();
15+
16+
testInjector.register("fs", {});
17+
testInjector.register("cocoapodsService", CocoaPodsService);
18+
19+
return testInjector;
20+
}
21+
22+
// The newline characters should be replaced with EOL because on Windows the EOL is \r\n
23+
// but the character which is placed in `` for newline is only \n
24+
// if we do not replace the newline characters the tests will pass only on linux and mac.
25+
function changeNewLineCharacter(input: string): string {
26+
return input ? input.replace(/\r?\n/g, EOL) : input;
27+
}
28+
29+
describe("Cocoapods service", () => {
30+
describe("merge Podfile hooks", () => {
31+
let testInjector: IInjector;
32+
let cocoapodsService: ICocoaPodsService;
33+
let newPodfileContent: string;
34+
35+
let mockFileSystem = (injector: IInjector, podfileContent: string): void => {
36+
let fs: IFileSystem = injector.resolve("fs");
37+
38+
fs.exists = () => Future.fromResult(true);
39+
fs.readText = () => Future.fromResult(podfileContent);
40+
fs.writeFile = (pathToFile: string, content: any) => {
41+
newPodfileContent = content;
42+
return Future.fromResult();
43+
};
44+
};
45+
46+
let testCaces: IMergePodfileHooksTestCase[] = [
47+
{
48+
input: `
49+
target 'MyApp' do
50+
pod 'GoogleAnalytics', '~> 3.1'
51+
target 'MyAppTests' do
52+
inherit! :search_paths
53+
pod 'OCMock', '~> 2.0.1'
54+
end
55+
end
56+
57+
post_install do |installer|
58+
installer.pods_project.targets.each do |target|
59+
puts target.name
60+
end
61+
end
62+
post_install do |installer|
63+
installer.pods_project.targets.each do |target|
64+
puts target.name
65+
end
66+
end
67+
post_install do |installer|
68+
installer.pods_project.targets.each do |target|
69+
puts target.name
70+
end
71+
end`,
72+
output: `
73+
target 'MyApp' do
74+
pod 'GoogleAnalytics', '~> 3.1'
75+
target 'MyAppTests' do
76+
inherit! :search_paths
77+
pod 'OCMock', '~> 2.0.1'
78+
end
79+
end
80+
81+
def post_install1 (installer)
82+
installer.pods_project.targets.each do |target|
83+
puts target.name
84+
end
85+
end
86+
def post_install2 (installer)
87+
installer.pods_project.targets.each do |target|
88+
puts target.name
89+
end
90+
end
91+
def post_install3 (installer)
92+
installer.pods_project.targets.each do |target|
93+
puts target.name
94+
end
95+
end
96+
post_install do |installer|
97+
post_install1 installer
98+
post_install2 installer
99+
post_install3 installer
100+
end`,
101+
testCaseDescription: "should merge more than one hooks with block parameter correctly."
102+
}, {
103+
input: `
104+
target 'MyApp' do
105+
pod 'GoogleAnalytics', '~> 3.1'
106+
target 'MyAppTests' do
107+
inherit! :search_paths
108+
pod 'OCMock', '~> 2.0.1'
109+
end
110+
111+
post_install do |installer_representation|
112+
installer_representation.pods_project.targets.each do |target|
113+
puts target.name
114+
end
115+
end
116+
post_install do
117+
puts "Hello World!"
118+
end
119+
end`,
120+
output: `
121+
target 'MyApp' do
122+
pod 'GoogleAnalytics', '~> 3.1'
123+
target 'MyAppTests' do
124+
inherit! :search_paths
125+
pod 'OCMock', '~> 2.0.1'
126+
end
127+
128+
def post_install1 (installer_representation)
129+
installer_representation.pods_project.targets.each do |target|
130+
puts target.name
131+
end
132+
end
133+
def post_install2
134+
puts "Hello World!"
135+
end
136+
end
137+
post_install do |installer|
138+
post_install1 installer
139+
post_install2
140+
end`,
141+
testCaseDescription: "should merge more than one hooks with and without block parameter correctly."
142+
}, {
143+
input: `
144+
target 'MyApp' do
145+
pod 'GoogleAnalytics', '~> 3.1'
146+
target 'MyAppTests' do
147+
inherit! :search_paths
148+
pod 'OCMock', '~> 2.0.1'
149+
end
150+
end
151+
152+
post_install do |installer|
153+
installer.pods_project.targets.each do |target|
154+
puts target.name
155+
end
156+
end`,
157+
output: null,
158+
testCaseDescription: "should not change the Podfile when there is only one hook."
159+
}
160+
];
161+
162+
beforeEach(() => {
163+
testInjector = createTestInjector();
164+
cocoapodsService = testInjector.resolve("cocoapodsService");
165+
newPodfileContent = null;
166+
});
167+
168+
_.each(testCaces, (testCase: IMergePodfileHooksTestCase) => {
169+
it(testCase.testCaseDescription, () => {
170+
mockFileSystem(testInjector, testCase.input);
171+
172+
cocoapodsService.mergePodfileHookContent("post_install", "").wait();
173+
174+
assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output));
175+
});
176+
});
177+
});
178+
});

0 commit comments

Comments
 (0)