Skip to content

Commit 4480df2

Browse files
Merge pull request #3900 from NativeScript/vladimirov/fix-podfile-issues-2
fix: Project's Podfile is regenerated incorrectly
2 parents 4d34b84 + 17aa4f4 commit 4480df2

File tree

6 files changed

+739
-148
lines changed

6 files changed

+739
-148
lines changed

lib/constants.ts

+7
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,10 @@ export class AddPlaformErrors {
215215

216216
export const PLUGIN_BUILD_DATA_FILENAME = "plugin-data.json";
217217
export const PLUGINS_BUILD_DATA_FILENAME = ".ns-plugins-build-data.json";
218+
219+
export class PluginNativeDirNames {
220+
public static iOS = "ios";
221+
public static Android = "android";
222+
}
223+
224+
export const PODFILE_NAME = "Podfile";

lib/definitions/project.d.ts

+27-5
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,32 @@ interface ICocoaPodsService {
464464
getPodfileFooter(): string;
465465

466466
/**
467-
* Merges the content of hooks with the provided name if there are more than one hooks with this name in the Podfile.
468-
* @param {string} hookName The name of the hook.
469-
* @param {string} pathToPodfile The path to the Podfile.
470-
* @return {void}
467+
* Prepares the Podfile content of a plugin and merges it in the project's Podfile.
468+
* @param {IPluginData} pluginData Information about the plugin.
469+
* @param {IProjectData} projectData Information about the project.
470+
* @param {string} nativeProjectPath Path to the native Xcode project.
471+
* @returns {Promise<void>}
472+
*/
473+
applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void>;
474+
475+
/**
476+
* Removes plugins Podfile content from the project.
477+
* @param {IPluginData} pluginData Information about the plugin.
478+
* @param {IProjectData} projectData Information about the project.
479+
* @param {string} nativeProjectPath Path to the native Xcode project.
480+
* @returns {void}
471481
*/
472-
mergePodfileHookContent(sectionName: string, pathToPodfile: string): void
482+
removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): void;
483+
484+
/**
485+
* Gives the path to project's Podfile.
486+
* @param {string} nativeProjectPath Path to the native Xcode project.
487+
* @returns {string} Path to project's Podfile.
488+
*/
489+
getProjectPodfilePath(nativeProjectPath: string): string;
490+
}
491+
492+
interface IRubyFunction {
493+
functionName: string;
494+
functionParameters?: string;
473495
}

lib/services/cocoapods-service.ts

+144-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { EOL } from "os";
2-
3-
interface IRubyFunction {
4-
functionName: string;
5-
functionParameters?: string;
6-
}
2+
import * as path from "path";
3+
import { PluginNativeDirNames, PODFILE_NAME } from "../constants";
74

85
export class CocoaPodsService implements ICocoaPodsService {
6+
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
7+
private static INSTALLER_BLOCK_PARAMETER_NAME = "installer";
8+
99
constructor(private $fs: IFileSystem) { }
1010

1111
public getPodfileHeader(targetName: string): string {
@@ -16,20 +16,133 @@ export class CocoaPodsService implements ICocoaPodsService {
1616
return `${EOL}end`;
1717
}
1818

19-
public mergePodfileHookContent(hookName: string, pathToPodfile: string): void {
20-
if (!this.$fs.exists(pathToPodfile)) {
21-
throw new Error(`The Podfile ${pathToPodfile} does not exist.`);
19+
public getProjectPodfilePath(projectRoot: string): string {
20+
return path.join(projectRoot, PODFILE_NAME);
21+
}
22+
23+
public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void> {
24+
const pluginPodFilePath = this.getPluginPodfilePath(pluginData);
25+
if (!this.$fs.exists(pluginPodFilePath)) {
26+
return;
27+
}
28+
29+
const { pluginPodfileContent, replacedFunctions } = this.buildPodfileContent(pluginPodFilePath, pluginData.name);
30+
const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath);
31+
const projectPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.$fs.readText(pathToProjectPodfile).trim() : "";
32+
33+
if (projectPodfileContent.indexOf(pluginPodfileContent) === -1) {
34+
// Remove old occurences of the plugin from the project's Podfile.
35+
this.removePluginPodfileFromProject(pluginData, projectData, nativeProjectPath);
36+
let finalPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.getPodfileContentWithoutTarget(projectData, this.$fs.readText(pathToProjectPodfile)) : "";
37+
38+
if (pluginPodfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) {
39+
finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, pluginPodfileContent);
40+
}
41+
42+
finalPodfileContent = `${pluginPodfileContent}${EOL}${finalPodfileContent}`;
43+
this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath);
44+
}
45+
}
46+
47+
public removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, projectRoot: string): void {
48+
const pluginPodfilePath = this.getPluginPodfilePath(pluginData);
49+
50+
if (this.$fs.exists(pluginPodfilePath) && this.$fs.exists(this.getProjectPodfilePath(projectRoot))) {
51+
let projectPodFileContent = this.$fs.readText(this.getProjectPodfilePath(projectRoot));
52+
// Remove the data between #Begin Podfile and #EndPodfile
53+
const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(pluginPodfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg");
54+
projectPodFileContent = projectPodFileContent.replace(regExpToRemove, "");
55+
projectPodFileContent = this.removePostInstallHook(pluginData, projectPodFileContent);
56+
57+
const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName);
58+
const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${EOL}${this.getPostInstallHookHeader()}end${EOL}end`;
59+
const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}end`;
60+
const trimmedProjectPodFileContent = projectPodFileContent.trim();
61+
if (!trimmedProjectPodFileContent || trimmedProjectPodFileContent === defaultContentWithPostInstallHook || trimmedProjectPodFileContent === defaultContentWithoutPostInstallHook) {
62+
this.$fs.deleteFile(this.getProjectPodfilePath(projectRoot));
63+
} else {
64+
this.$fs.writeFile(this.getProjectPodfilePath(projectRoot), projectPodFileContent);
65+
}
2266
}
67+
}
68+
69+
private getPluginPodfilePath(pluginData: IPluginData): string {
70+
const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(PluginNativeDirNames.iOS);
71+
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, PODFILE_NAME);
72+
return pluginPodFilePath;
73+
}
2374

24-
const podfileContent = this.$fs.readText(pathToPodfile);
75+
private addPostInstallHook(replacedFunctions: IRubyFunction[], finalPodfileContent: string, pluginPodfileContent: string): string {
76+
const postInstallHookStart = this.getPostInstallHookHeader();
77+
let postInstallHookContent = "";
78+
_.each(replacedFunctions, rubyFunction => {
79+
let functionExecution = rubyFunction.functionName;
80+
if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) {
81+
functionExecution = `${functionExecution} ${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}`;
82+
}
83+
84+
postInstallHookContent += ` ${functionExecution}${EOL}`;
85+
});
86+
87+
if (postInstallHookContent) {
88+
const index = finalPodfileContent.indexOf(postInstallHookStart);
89+
if (index !== -1) {
90+
finalPodfileContent = finalPodfileContent.replace(postInstallHookStart, `${postInstallHookStart}${postInstallHookContent}`);
91+
} else {
92+
const postInstallHook = `${postInstallHookStart}${postInstallHookContent}end`;
93+
finalPodfileContent = `${finalPodfileContent}${postInstallHook}`;
94+
}
95+
}
96+
97+
return finalPodfileContent;
98+
}
99+
100+
private getPodfileContentWithoutTarget(projectData: IProjectData, projectPodfileContent: string): string {
101+
const podFileHeader = this.getPodfileHeader(projectData.projectName);
102+
103+
if (_.startsWith(projectPodfileContent, podFileHeader)) {
104+
projectPodfileContent = projectPodfileContent.substr(podFileHeader.length);
105+
106+
const podFileFooter = this.getPodfileFooter();
107+
// Only remove the final end in case the file starts with the podFileHeader
108+
if (_.endsWith(projectPodfileContent, podFileFooter)) {
109+
projectPodfileContent = projectPodfileContent.substr(0, projectPodfileContent.length - podFileFooter.length);
110+
}
111+
}
112+
113+
return projectPodfileContent.trim();
114+
}
115+
116+
private saveProjectPodfile(projectData: IProjectData, projectPodfileContent: string, projectRoot: string): void {
117+
projectPodfileContent = this.getPodfileContentWithoutTarget(projectData, projectPodfileContent);
118+
const podFileHeader = this.getPodfileHeader(projectData.projectName);
119+
const podFileFooter = this.getPodfileFooter();
120+
const contentToWrite = `${podFileHeader}${projectPodfileContent}${podFileFooter}`;
121+
const projectPodfilePath = this.getProjectPodfilePath(projectRoot);
122+
this.$fs.writeFile(projectPodfilePath, contentToWrite);
123+
}
124+
125+
private removePostInstallHook(pluginData: IPluginData, projectPodFileContent: string): string {
126+
const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginData.name)}.*?$\\r?\\n`, "gm");
127+
projectPodFileContent = projectPodFileContent.replace(regExp, "");
128+
return projectPodFileContent;
129+
}
130+
131+
private getHookBasicFuncNameForPlugin(hookName: string, pluginName: string): string {
132+
// nativescript-hook and nativescript_hook should have different names, so replace all _ with ___ first and then replace all special symbols with _
133+
// This will lead to a clash in case plugins are called nativescript-hook and nativescript___hook
134+
const replacedPluginName = pluginName.replace(/_/g, "___").replace(/[^A-Za-z0-9_]/g, "_");
135+
return `${hookName}${replacedPluginName}`;
136+
}
137+
138+
private replaceHookContent(hookName: string, podfileContent: string, pluginName: string): { replacedContent: string, newFunctions: IRubyFunction[] } {
25139
const hookStart = `${hookName} do`;
26140

27141
const hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g");
28-
let newFunctionNameIndex = 1;
29142
const newFunctions: IRubyFunction[] = [];
30143

31144
const replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring: string, firstGroup: string, secondGroup: string, index: number): string => {
32-
const newFunctionName = `${hookName}${newFunctionNameIndex++}`;
145+
const newFunctionName = `${this.getHookBasicFuncNameForPlugin(hookName, pluginName)}_${newFunctions.length}`;
33146
let newDefinition = `def ${newFunctionName}`;
34147

35148
const rubyFunction: IRubyFunction = { functionName: newFunctionName };
@@ -43,26 +156,31 @@ export class CocoaPodsService implements ICocoaPodsService {
43156
return newDefinition;
44157
});
45158

46-
if (newFunctions.length > 1) {
47-
// Execute all methods in the hook and pass the parameter to them.
48-
const blokParameterName = "installer";
49-
let mergedHookContent = `${hookStart} |${blokParameterName}|${EOL}`;
159+
return { replacedContent, newFunctions };
160+
}
50161

51-
_.each(newFunctions, (rubyFunction: IRubyFunction) => {
52-
let functionExecution = rubyFunction.functionName;
53-
if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) {
54-
functionExecution = `${functionExecution} ${blokParameterName}`;
55-
}
162+
private getPluginPodfileHeader(pluginPodFilePath: string): string {
163+
return `# Begin Podfile - ${pluginPodFilePath}`;
164+
}
56165

57-
mergedHookContent = `${mergedHookContent} ${functionExecution}${EOL}`;
58-
});
166+
private getPluginPodfileEnd(): string {
167+
return `# End Podfile${EOL}`;
168+
}
59169

60-
mergedHookContent = `${mergedHookContent}end`;
170+
private getPostInstallHookHeader() {
171+
return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${EOL}`;
172+
}
61173

62-
const newPodfileContent = `${replacedContent}${EOL}${mergedHookContent}`;
63-
this.$fs.writeFile(pathToPodfile, newPodfileContent);
64-
}
174+
private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { pluginPodfileContent: string, replacedFunctions: IRubyFunction[] } {
175+
const pluginPodfileContent = this.$fs.readText(pluginPodFilePath);
176+
const { replacedContent, newFunctions: replacedFunctions } = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName);
177+
178+
return {
179+
pluginPodfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${EOL}${replacedContent}${EOL}${this.getPluginPodfileEnd()}`,
180+
replacedFunctions
181+
};
65182
}
183+
66184
}
67185

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

lib/services/ios-project-service.ts

+9-68
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as path from "path";
22
import * as shell from "shelljs";
3-
import * as os from "os";
43
import * as semver from "semver";
54
import * as constants from "../constants";
65
import * as helpers from "../common/helpers";
@@ -28,11 +27,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
2827
private static XCODEBUILD_MIN_VERSION = "6.0";
2928
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
3029
private static IOS_PLATFORM_NAME = "ios";
31-
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
32-
33-
private get $npmInstallationManager(): INpmInstallationManager {
34-
return this.$injector.resolve("npmInstallationManager");
35-
}
3630

3731
constructor($fs: IFileSystem,
3832
private $childProcess: IChildProcess,
@@ -900,10 +894,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
900894
return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName + IOSProjectService.XCODE_PROJECT_EXT_NAME);
901895
}
902896

903-
private getProjectPodFilePath(projectData: IProjectData): string {
904-
return path.join(this.getPlatformData(projectData).projectRoot, "Podfile");
905-
}
906-
907897
private getPluginsDebugXcconfigFilePath(projectData: IProjectData): string {
908898
return path.join(this.getPlatformData(projectData).projectRoot, "plugins-debug.xcconfig");
909899
}
@@ -951,7 +941,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
951941
await this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData);
952942
await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
953943
await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
954-
await this.prepareCocoapods(pluginPlatformsFolderPath, projectData);
944+
await this.prepareCocoapods(pluginPlatformsFolderPath, pluginData, projectData);
955945
}
956946

957947
public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
@@ -960,20 +950,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
960950
this.removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData);
961951
this.removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
962952
this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
963-
this.removeCocoapods(pluginPlatformsFolderPath, projectData);
953+
const projectRoot = this.getPlatformData(projectData).projectRoot;
954+
955+
this.$cocoapodsService.removePluginPodfileFromProject(pluginData, projectData, projectRoot);
964956
}
965957

966958
public async afterPrepareAllPlugins(projectData: IProjectData): Promise<void> {
967-
if (this.$fs.exists(this.getProjectPodFilePath(projectData))) {
968-
const projectPodfileContent = this.$fs.readText(this.getProjectPodFilePath(projectData));
969-
this.$logger.trace("Project Podfile content");
970-
this.$logger.trace(projectPodfileContent);
971-
972-
const firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME);
973-
if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) {
974-
this.$cocoapodsService.mergePodfileHookContent(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME, this.getProjectPodFilePath(projectData));
975-
}
976-
959+
const projectRoot = this.getPlatformData(projectData).projectRoot;
960+
if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot))) {
977961
const xcuserDataPath = path.join(this.getXcodeprojPath(projectData), "xcuserdata");
978962
const sharedDataPath = path.join(this.getXcodeprojPath(projectData), "xcshareddata");
979963

@@ -1169,38 +1153,15 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
11691153
}
11701154
}
11711155

1172-
private async prepareCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData, opts?: any): Promise<void> {
1156+
private async prepareCocoapods(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData, opts?: any): Promise<void> {
1157+
const projectRoot = this.getPlatformData(projectData).projectRoot;
1158+
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
11731159
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
1174-
if (this.$fs.exists(pluginPodFilePath)) {
1175-
const pluginPodFileContent = this.$fs.readText(pluginPodFilePath);
1176-
const pluginPodFilePreparedContent = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent);
1177-
let projectPodFileContent = this.$fs.exists(this.getProjectPodFilePath(projectData)) ? this.$fs.readText(this.getProjectPodFilePath(projectData)) : "";
1178-
1179-
if (!~projectPodFileContent.indexOf(pluginPodFilePreparedContent)) {
1180-
const podFileHeader = this.$cocoapodsService.getPodfileHeader(projectData.projectName),
1181-
podFileFooter = this.$cocoapodsService.getPodfileFooter();
1182-
1183-
if (_.startsWith(projectPodFileContent, podFileHeader)) {
1184-
projectPodFileContent = projectPodFileContent.substr(podFileHeader.length);
1185-
}
1186-
1187-
if (_.endsWith(projectPodFileContent, podFileFooter)) {
1188-
projectPodFileContent = projectPodFileContent.substr(0, projectPodFileContent.length - podFileFooter.length);
1189-
}
1190-
1191-
const contentToWrite = `${podFileHeader}${projectPodFileContent}${pluginPodFilePreparedContent}${podFileFooter}`;
1192-
this.$fs.writeFile(this.getProjectPodFilePath(projectData), contentToWrite);
1193-
1194-
const project = this.createPbxProj(projectData);
1195-
this.savePbxProj(project, projectData);
1196-
}
1197-
}
11981160

11991161
if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath)) {
12001162
await this.executePodInstall(projectData);
12011163
}
12021164
}
1203-
12041165
private removeNativeSourceCode(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void {
12051166
const project = this.createPbxProj(projectData);
12061167
const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
@@ -1235,26 +1196,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
12351196
this.savePbxProj(project, projectData);
12361197
}
12371198

1238-
private removeCocoapods(pluginPlatformsFolderPath: string, projectData: IProjectData): void {
1239-
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
1240-
1241-
if (this.$fs.exists(pluginPodFilePath) && this.$fs.exists(this.getProjectPodFilePath(projectData))) {
1242-
const pluginPodFileContent = this.$fs.readText(pluginPodFilePath);
1243-
let projectPodFileContent = this.$fs.readText(this.getProjectPodFilePath(projectData));
1244-
const contentToRemove = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent);
1245-
projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, "");
1246-
if (projectPodFileContent.trim() === `use_frameworks!${os.EOL}${os.EOL}target "${projectData.projectName}" do${os.EOL}${os.EOL}end`) {
1247-
this.$fs.deleteFile(this.getProjectPodFilePath(projectData));
1248-
} else {
1249-
this.$fs.writeFile(this.getProjectPodFilePath(projectData), projectPodFileContent);
1250-
}
1251-
}
1252-
}
1253-
1254-
private buildPodfileContent(pluginPodFilePath: string, pluginPodFileContent: string): string {
1255-
return `# Begin Podfile - ${pluginPodFilePath} ${os.EOL} ${pluginPodFileContent} ${os.EOL} # End Podfile ${os.EOL}`;
1256-
}
1257-
12581199
private generateModulemap(headersFolderPath: string, libraryName: string): void {
12591200
const headersFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".h" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile());
12601201
const headersFolderContents = this.$fs.readDirectory(headersFolderPath);

0 commit comments

Comments
 (0)