Skip to content

Commit 534b2db

Browse files
Merge pull request #4332 from NativeScript/fatme/fix-multiple-platforms-pod
fix: remove duplicated platforms from project's podfile
2 parents 7223754 + 0472afb commit 534b2db

7 files changed

+433
-18
lines changed

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ $injector.require("iOSProvisionService", "./services/ios-provision-service");
1717
$injector.require("xcconfigService", "./services/xcconfig-service");
1818

1919
$injector.require("cocoapodsService", "./services/cocoapods-service");
20+
$injector.require("cocoaPodsPlatformManager", "./services/cocoapods-platform-manager");
2021

2122
$injector.require("projectTemplatesService", "./services/project-templates-service");
2223
$injector.require("projectNameService", "./services/project-name-service");

lib/definitions/project.d.ts

+21
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,28 @@ interface ICocoaPodsService {
542542
mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData, opts: IRelease): Promise<void>;
543543
}
544544

545+
interface ICocoaPodsPlatformManager {
546+
addPlatformSection(projectData: IProjectData, podfilePlatformData: IPodfilePlatformData, projectPodfileContent: string): string;
547+
removePlatformSection(moduleName: string, projectPodFileContent: string, podfilePath: string): string;
548+
replacePlatformRow(podfileContent: string, podfilePath: string): { replacedContent: string, podfilePlatformData: IPodfilePlatformData };
549+
}
550+
545551
interface IRubyFunction {
546552
functionName: string;
547553
functionParameters?: string;
548554
}
555+
556+
interface IPodfilePlatformData {
557+
/**
558+
* The content of the whole pod's platform row
559+
*/
560+
content: string;
561+
/**
562+
* The version of the pod's platform
563+
*/
564+
version: string;
565+
/**
566+
* The path to the pod's file
567+
*/
568+
path: string;
569+
}
+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { EOL } from "os";
2+
import * as path from "path";
3+
import * as semver from "semver";
4+
import { PODFILE_NAME } from "../constants";
5+
6+
export class CocoaPodsPlatformManager implements ICocoaPodsPlatformManager {
7+
public addPlatformSection(projectData: IProjectData, podfilePlatformData: IPodfilePlatformData, projectPodfileContent: string): string {
8+
const platformSectionData = this.getPlatformSectionData(projectPodfileContent);
9+
if (platformSectionData && platformSectionData.podfilePlatformData) {
10+
const shouldReplacePlatformSection = this.shouldReplacePlatformSection(projectData, platformSectionData.podfilePlatformData, podfilePlatformData);
11+
if (shouldReplacePlatformSection) {
12+
const newSection = this.buildPlatformSection(podfilePlatformData);
13+
projectPodfileContent = projectPodfileContent.replace(platformSectionData.platformSectionContent, newSection);
14+
}
15+
} else {
16+
projectPodfileContent += this.buildPlatformSection(podfilePlatformData);
17+
}
18+
19+
return projectPodfileContent;
20+
}
21+
22+
public removePlatformSection(moduleName: string, projectPodfileContent: string, podfilePath: string): string {
23+
const platformSectionData = this.getPlatformSectionData(projectPodfileContent);
24+
if (platformSectionData && platformSectionData.podfilePlatformData && platformSectionData.podfilePlatformData.path === podfilePath) {
25+
const podfileContentRegExp = new RegExp(`# Begin Podfile - ([\\s\\S]*?)# End Podfile`, "mg");
26+
const allPodfiles = projectPodfileContent.match(podfileContentRegExp) || [];
27+
const selectedPlatformData = this.selectPlatformDataFromProjectPodfile(allPodfiles);
28+
const newPlatformSection = selectedPlatformData ? this.buildPlatformSection(selectedPlatformData) : "";
29+
const regExp = new RegExp(`\\r?\\n${platformSectionData.platformSectionContent}\\r?\\n`, "mg");
30+
projectPodfileContent = projectPodfileContent.replace(regExp, newPlatformSection);
31+
}
32+
33+
return projectPodfileContent;
34+
}
35+
36+
public replacePlatformRow(podfileContent: string, podfilePath: string): { replacedContent: string, podfilePlatformData: IPodfilePlatformData } {
37+
let podfilePlatformData: IPodfilePlatformData = null;
38+
const platformRowRegExp = new RegExp(`^\\s*?(platform\\b\\s*?\\:\\s*?ios\\b(?:,\\s*?['"](.+)['"])?)`, "gm");
39+
const replacedContent = podfileContent.replace(platformRowRegExp, (substring: string, firstGroup: string, secondGroup: string) => {
40+
podfilePlatformData = { content: firstGroup, version: secondGroup, path: podfilePath };
41+
return `# ${substring.trim()}`;
42+
});
43+
44+
return { replacedContent, podfilePlatformData };
45+
}
46+
47+
private getPlatformSectionData(projectPodfileContent: string): { podfilePlatformData: IPodfilePlatformData, platformSectionContent: string } {
48+
const platformSectionRegExp = new RegExp(`${this.getPlatformSectionHeader()} ([\\s\\S]*?)with (.*)[\\s\\S]*?${this.getPlatformSectionFooter()}`, "m");
49+
const match = platformSectionRegExp.exec(projectPodfileContent);
50+
let result = null;
51+
if (match && match[0]) {
52+
result = {
53+
platformSectionContent: match[0],
54+
podfilePlatformData: {
55+
path: match[1].trim(),
56+
content: "",
57+
version: match[2]
58+
}
59+
};
60+
}
61+
62+
return result;
63+
}
64+
65+
private selectPlatformDataFromProjectPodfile(allPodfiles: string[]): IPodfilePlatformData {
66+
const platformRowRegExp = new RegExp(`^\\s*?#\\s*?(platform\\b\\s*?\\:\\s*?ios\\b(?:,\\s*?['"](.+)['"])?)`, "m");
67+
const podfilePathRegExp = new RegExp(`# Begin Podfile - ([\\s\\S]*?)${EOL}`);
68+
let selectedPlatformData: IPodfilePlatformData = null;
69+
_.each(allPodfiles, podfileContent => {
70+
const platformMatch = platformRowRegExp.exec(podfileContent);
71+
const podfilePathMatch: any[] = podfilePathRegExp.exec(podfileContent) || [];
72+
// platform without version -> select it with highest priority
73+
if (platformMatch && platformMatch[0] && !platformMatch[2]) {
74+
selectedPlatformData = {
75+
version: null,
76+
content: platformMatch[1],
77+
path: podfilePathMatch[1]
78+
};
79+
return false;
80+
}
81+
82+
// platform with version
83+
if (platformMatch && platformMatch[0] && platformMatch[2]) {
84+
if (!selectedPlatformData || semver.gt(semver.coerce(platformMatch[2]), semver.coerce(selectedPlatformData.version))) {
85+
selectedPlatformData = {
86+
version: platformMatch[2],
87+
content: platformMatch[1],
88+
path: podfilePathMatch[1]
89+
};
90+
}
91+
}
92+
});
93+
94+
return selectedPlatformData;
95+
}
96+
97+
private shouldReplacePlatformSection(projectData: IProjectData, oldPodfilePlatformData: IPodfilePlatformData, currentPodfilePlatformData: IPodfilePlatformData): boolean {
98+
// The selected platform should be replaced in the following cases:
99+
// 1. When the pod file is from App_Resources and the selected platform is not from App_Resources
100+
// 2. When the pod file is from App_Resources, the selected platform is also from App_Resources
101+
// and the pod's version is greater than the selected one
102+
// 3. When the pod file doesn't have platform's version -> `platform :ios`
103+
// 4. When the pod file has a version greater than the selected platform's version
104+
const appResourcesPodfilePath = path.join(projectData.getAppResourcesDirectoryPath(), "iOS", PODFILE_NAME);
105+
const isFromAppResources = oldPodfilePlatformData.path !== appResourcesPodfilePath && currentPodfilePlatformData.path === appResourcesPodfilePath;
106+
const isFromAppResourcesWithGreaterPlatformVersion = oldPodfilePlatformData.path === appResourcesPodfilePath && currentPodfilePlatformData.path === appResourcesPodfilePath && semver.gt(semver.coerce(currentPodfilePlatformData.version), semver.coerce(oldPodfilePlatformData.version));
107+
const isPodfileWithGreaterPlatformVersion = !currentPodfilePlatformData.version || semver.gt(semver.coerce(currentPodfilePlatformData.version), semver.coerce(oldPodfilePlatformData.version));
108+
const result = isFromAppResources || isFromAppResourcesWithGreaterPlatformVersion || isPodfileWithGreaterPlatformVersion;
109+
return result;
110+
}
111+
112+
private buildPlatformSection(podfilePlatformData: IPodfilePlatformData) {
113+
let result = `${this.getPlatformSectionHeader()} ${podfilePlatformData.path} with`;
114+
if (podfilePlatformData.version) {
115+
result += ` ${podfilePlatformData.version}`;
116+
}
117+
118+
result += `${EOL}${podfilePlatformData.content}${EOL}${this.getPlatformSectionFooter()}`;
119+
return result;
120+
}
121+
122+
private getPlatformSectionHeader(): string {
123+
return '# NativeScriptPlatformSection';
124+
}
125+
126+
private getPlatformSectionFooter(): string {
127+
return '# End NativeScriptPlatformSection';
128+
}
129+
}
130+
$injector.register("cocoaPodsPlatformManager", CocoaPodsPlatformManager);

lib/services/cocoapods-service.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export class CocoaPodsService implements ICocoaPodsService {
66
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
77
private static INSTALLER_BLOCK_PARAMETER_NAME = "installer";
88

9-
constructor(private $fs: IFileSystem,
9+
constructor(
10+
private $cocoaPodsPlatformManager: ICocoaPodsPlatformManager,
11+
private $fs: IFileSystem,
1012
private $childProcess: IChildProcess,
1113
private $errors: IErrors,
1214
private $xcprojService: IXcprojService,
@@ -79,7 +81,7 @@ export class CocoaPodsService implements ICocoaPodsService {
7981
return;
8082
}
8183

82-
const { podfileContent, replacedFunctions } = this.buildPodfileContent(podfilePath, moduleName);
84+
const { podfileContent, replacedFunctions, podfilePlatformData } = this.buildPodfileContent(podfilePath, moduleName);
8385
const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath);
8486
const projectPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.$fs.readText(pathToProjectPodfile).trim() : "";
8587

@@ -92,19 +94,23 @@ export class CocoaPodsService implements ICocoaPodsService {
9294
finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, podfileContent);
9395
}
9496

97+
if (podfilePlatformData) {
98+
finalPodfileContent = this.$cocoaPodsPlatformManager.addPlatformSection(projectData, podfilePlatformData, finalPodfileContent);
99+
}
100+
95101
finalPodfileContent = `${podfileContent}${EOL}${finalPodfileContent}`;
96102
this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath);
97103
}
98104
}
99105

100106
public removePodfileFromProject(moduleName: string, podfilePath: string, projectData: IProjectData, projectRoot: string): void {
101-
102107
if (this.$fs.exists(this.getProjectPodfilePath(projectRoot))) {
103108
let projectPodFileContent = this.$fs.readText(this.getProjectPodfilePath(projectRoot));
104109
// Remove the data between #Begin Podfile and #EndPodfile
105110
const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(podfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg");
106111
projectPodFileContent = projectPodFileContent.replace(regExpToRemove, "");
107112
projectPodFileContent = this.removePostInstallHook(moduleName, projectPodFileContent);
113+
projectPodFileContent = this.$cocoaPodsPlatformManager.removePlatformSection(moduleName, projectPodFileContent, podfilePath);
108114

109115
const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName);
110116
const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${EOL}${this.getPostInstallHookHeader()}end${EOL}end`;
@@ -226,13 +232,15 @@ export class CocoaPodsService implements ICocoaPodsService {
226232
return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${EOL}`;
227233
}
228234

229-
private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { podfileContent: string, replacedFunctions: IRubyFunction[] } {
235+
private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { podfileContent: string, replacedFunctions: IRubyFunction[], podfilePlatformData: IPodfilePlatformData } {
230236
const pluginPodfileContent = this.$fs.readText(pluginPodFilePath);
231-
const { replacedContent, newFunctions: replacedFunctions } = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName);
237+
const data = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName);
238+
const { replacedContent, podfilePlatformData } = this.$cocoaPodsPlatformManager.replacePlatformRow(data.replacedContent, pluginPodFilePath);
232239

233240
return {
234241
podfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${EOL}${replacedContent}${EOL}${this.getPluginPodfileEnd()}`,
235-
replacedFunctions
242+
replacedFunctions: data.newFunctions,
243+
podfilePlatformData
236244
};
237245
}
238246

lib/services/ios-project-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
985985
const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot);
986986
if (this.$fs.exists(projectPodfilePath)) {
987987
await this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData));
988-
// The `pod install` command adds a new target to the .pbxproject. This target adds additional build phases to xcodebuild.
988+
// The `pod install` command adds a new target to the .pbxproject. This target adds additional build phases to Xcode project.
989989
// Some of these phases relies on env variables (like PODS_PODFILE_DIR_PATH or PODS_ROOT).
990990
// These variables are produced from merge of pod's xcconfig file and project's xcconfig file.
991991
// So the correct order is `pod install` to be executed before merging pod's xcconfig file.

0 commit comments

Comments
 (0)