Skip to content

Commit 283bc8d

Browse files
FatmeFatme
Fatme
authored and
Fatme
committed
Merge pull request #769 from NativeScript/fatme/cocoapods
Cocoapods support
2 parents 3ceec8b + 8350405 commit 283bc8d

File tree

9 files changed

+267
-20
lines changed

9 files changed

+267
-20
lines changed

lib/definitions/plugins.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface IPluginsService {
44
prepare(pluginData: IDependencyData): IFuture<void>;
55
getAllInstalledPlugins(): IFuture<IPluginData[]>;
66
ensureAllDependenciesAreInstalled(): IFuture<void>;
7+
afterPrepareAllPlugins(): IFuture<void>;
78
}
89

910
interface IPluginData extends INodeModuleData {

lib/definitions/project.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface IPlatformProjectService {
4040
updatePlatform(currentVersion: string, newVersion: string): IFuture<void>;
4141
preparePluginNativeCode(pluginData: IPluginData): IFuture<void>;
4242
removePluginNativeCode(pluginData: IPluginData): IFuture<void>;
43+
afterPrepareAllPlugins(): IFuture<void>;
4344
}
4445

4546
interface IAndroidProjectPropertiesManager {

lib/services/android-project-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ class AndroidProjectService extends projectServiceBaseLib.PlatformProjectService
298298
}).future<void>()();
299299
}
300300

301+
public afterPrepareAllPlugins(): IFuture<void> {
302+
return Future.fromResult();
303+
}
304+
301305
private getLibraryRelativePath(basePath: string, libraryPath: string): string {
302306
return path.relative(basePath, libraryPath).split("\\").join("/");
303307
}

lib/services/ios-project-service.ts

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22
"use strict";
33

44
import Future = require("fibers/future");
5-
import path = require("path");
6-
import shell = require("shelljs");
7-
import util = require("util");
8-
import xcode = require("xcode");
5+
import * as path from "path";
6+
import * as shell from "shelljs";
7+
import * as util from "util";
8+
import * as os from "os";
9+
import * as xcode from "xcode";
910
import constants = require("./../constants");
1011
import helpers = require("./../common/helpers");
1112
import projectServiceBaseLib = require("./platform-project-service-base");
1213

13-
class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
14+
export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
1415
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
1516
private static XCODEBUILD_MIN_VERSION = "6.0";
1617
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
1718
private static IOS_PLATFORM_NAME = "ios";
19+
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
1820

1921
private get $npmInstallationManager(): INpmInstallationManager {
2022
return this.$injector.resolve("npmInstallationManager");
@@ -262,14 +264,17 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
262264

263265
this.savePbxProj(project).wait();
264266
}
265-
266267
}).future<void>()();
267268
}
268269

269270
public prepareAppResources(appResourcesDirectoryPath: string): IFuture<void> {
270271
return this.$fs.deleteDirectory(this.platformData.appResourcesDestinationDirectoryPath);
271272
}
272273

274+
private get projectPodFilePath(): string {
275+
return path.join(this.platformData.projectRoot, "Podfile");
276+
}
277+
273278
private replace(name: string): string {
274279
if(_.startsWith(name, '"')) {
275280
name = name.substr(1, name.length-2);
@@ -303,22 +308,41 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
303308
public preparePluginNativeCode(pluginData: IPluginData): IFuture<void> {
304309
return (() => {
305310
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
306-
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait());
311+
this.prepareDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait();
312+
this.prepareCocoapods(pluginPlatformsFolderPath).wait();
307313
}).future<void>()();
308314
}
309315

310316
public removePluginNativeCode(pluginData: IPluginData): IFuture<void> {
311317
return (() => {
312318
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
313-
let project = this.createPbxProj();
314-
315-
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => {
316-
let fullFrameworkPath = path.join(pluginPlatformsFolderPath, fileName);
317-
let relativeFrameworkPath = this.getFrameworkRelativePath(fullFrameworkPath);
318-
project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true })
319-
});
320-
321-
this.savePbxProj(project).wait();
319+
this.removeDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait();
320+
this.removeCocoapods(pluginPlatformsFolderPath).wait();
321+
}).future<void>()();
322+
}
323+
324+
public afterPrepareAllPlugins(): IFuture<void> {
325+
return (() => {
326+
if(this.$fs.exists(this.projectPodFilePath).wait()) {
327+
// Check availability
328+
try {
329+
this.$childProcess.exec("gem which cocoapods").wait();
330+
} catch(e) {
331+
this.$errors.failWithoutHelp("CocoaPods are not installed. Run `sudo gem install cocoapods` and try again.");
332+
}
333+
334+
let projectPodfileContent = this.$fs.readText(this.projectPodFilePath).wait();
335+
this.$logger.trace("Project Podfile content");
336+
this.$logger.trace(projectPodfileContent);
337+
338+
let firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME);
339+
if(firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) {
340+
this.$logger.warn(`Podfile contains more than one post_install sections. You need to open ${this.projectPodFilePath} file and manually resolve this issue.`);
341+
}
342+
343+
this.$logger.info("Installing pods...");
344+
this.$childProcess.exec("pod install", { cwd: this.platformData.projectRoot }).wait();
345+
}
322346
}).future<void>()();
323347
}
324348

@@ -379,5 +403,57 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
379403
this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName)).wait();
380404
}).future<void>()();
381405
}
406+
407+
private prepareDynamicFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> {
408+
return (() => {
409+
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait());
410+
}).future<void>()();
411+
}
412+
413+
private prepareCocoapods(pluginPlatformsFolderPath: string): IFuture<void> {
414+
return (() => {
415+
let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
416+
if(this.$fs.exists(pluginPodFilePath).wait()) {
417+
let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait();
418+
let contentToWrite = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent);
419+
this.$fs.appendFile(this.projectPodFilePath, contentToWrite).wait();
420+
}
421+
}).future<void>()();
422+
}
423+
424+
private removeDynamicFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> {
425+
return (() => {
426+
let project = this.createPbxProj();
427+
428+
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => {
429+
let fullFrameworkPath = path.join(pluginPlatformsFolderPath, fileName);
430+
let relativeFrameworkPath = this.getFrameworkRelativePath(fullFrameworkPath);
431+
project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true })
432+
});
433+
434+
this.savePbxProj(project).wait();
435+
}).future<void>()();
436+
}
437+
438+
private removeCocoapods(pluginPlatformsFolderPath: string): IFuture<void> {
439+
return (() => {
440+
let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
441+
if(this.$fs.exists(pluginPodFilePath).wait()) {
442+
let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait();
443+
let projectPodFileContent = this.$fs.readText(this.projectPodFilePath).wait();
444+
let contentToRemove= this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent);
445+
projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, "");
446+
if(_.isEmpty(projectPodFileContent)) {
447+
this.$fs.deleteFile(this.projectPodFilePath).wait();
448+
} else {
449+
this.$fs.writeFile(this.projectPodFilePath, projectPodFileContent).wait();
450+
}
451+
}
452+
}).future<void>()();
453+
}
454+
455+
private buildPodfileContent(pluginPodFilePath: string, pluginPodFileContent: string): string {
456+
return `# Begin Podfile - ${pluginPodFilePath} ${os.EOL} ${pluginPodFileContent} ${os.EOL} # End Podfile ${os.EOL}`;
457+
}
382458
}
383459
$injector.register("iOSProjectService", IOSProjectService);

lib/services/plugins-service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ export class PluginsService implements IPluginsService {
111111
this.validateXml(resultXml);
112112
this.$fs.writeFile(configurationFilePath, resultXml).wait();
113113
}
114-
115114

116115
this.$projectFilesManager.processPlatformSpecificFiles(pluginDestinationPath, platform).wait();
117116

@@ -145,6 +144,14 @@ export class PluginsService implements IPluginsService {
145144
}).future<IPluginData[]>()();
146145
}
147146

147+
public afterPrepareAllPlugins(): IFuture<void> {
148+
let action = (pluginDestinationPath: string, platform: string, platformData: IPlatformData) => {
149+
return platformData.platformProjectService.afterPrepareAllPlugins();
150+
};
151+
152+
return this.executeForAllInstalledPlatforms(action);
153+
}
154+
148155
private get nodeModulesPath(): string {
149156
return path.join(this.$projectData.projectDir, "node_modules");
150157
}

lib/tools/broccoli/node-modules-dest-copy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export class DestCopy implements IBroccoliPlugin {
7676
this.$pluginsService.prepare(dependency).wait();
7777
}
7878
});
79+
80+
if(!_.isEmpty(this.dependencies)) {
81+
this.$pluginsService.afterPrepareAllPlugins().wait();
82+
}
7983
}
8084

8185
public rebuild(treeDiff: IDiffResult): void {

test/ios-project-service.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/// <reference path=".d.ts" />
2+
"use strict";
3+
4+
import Future = require("fibers/future");
5+
import * as path from "path";
6+
import temp = require("temp");
7+
temp.track();
8+
9+
import ChildProcessLib = require("../lib/common/child-process");
10+
import ConfigLib = require("../lib/config");
11+
import ErrorsLib = require("../lib/common/errors");
12+
import FileSystemLib = require("../lib/common/file-system");
13+
import HostInfoLib = require("../lib/common/host-info");
14+
import iOSProjectServiceLib = require("../lib/services/ios-project-service");
15+
import LoggerLib = require("../lib/common/logger");
16+
import OptionsLib = require("../lib/options");
17+
import ProjectDataLib = require("../lib/project-data");
18+
19+
import yok = require("../lib/common/yok");
20+
21+
import { assert } from "chai";
22+
23+
function createTestInjector(projectPath: string, projectName: string): IInjector {
24+
let testInjector = new yok.Yok();
25+
testInjector.register("childProcess", ChildProcessLib.ChildProcess);
26+
testInjector.register("config", ConfigLib.Configuration);
27+
testInjector.register("errors", ErrorsLib.Errors);
28+
testInjector.register("fs", FileSystemLib.FileSystem);
29+
testInjector.register("hostInfo", HostInfoLib.HostInfo);
30+
testInjector.register("injector", testInjector);
31+
testInjector.register("iOSEmulatorServices", {});
32+
testInjector.register("iOSProjectService", iOSProjectServiceLib.IOSProjectService);
33+
testInjector.register("logger", LoggerLib.Logger);
34+
testInjector.register("options", OptionsLib.Options);
35+
testInjector.register("projectData", {
36+
platformsDir: projectPath,
37+
projectName: projectName
38+
});
39+
testInjector.register("projectHelper", {});
40+
testInjector.register("staticConfig", ConfigLib.StaticConfig);
41+
42+
return testInjector;
43+
}
44+
45+
describe("Cocoapods support", () => {
46+
it("adds plugin with Podfile", () => {
47+
let projectName = "projectDirectory";
48+
let projectPath = temp.mkdirSync(projectName);
49+
50+
let testInjector = createTestInjector(projectPath, projectName);
51+
let fs: IFileSystem = testInjector.resolve("fs");
52+
53+
let packageJsonData = {
54+
"name": "myProject",
55+
"version": "0.1.0",
56+
"nativescript": {
57+
"id": "org.nativescript.myProject",
58+
"tns-android": {
59+
"version": "1.0.0"
60+
}
61+
}
62+
};
63+
fs.writeJson(path.join(projectPath, "package.json"), packageJsonData).wait();
64+
65+
let platformsFolderPath = path.join(projectPath, "ios");
66+
fs.createDirectory(platformsFolderPath).wait();
67+
68+
let iOSProjectService = testInjector.resolve("iOSProjectService");
69+
iOSProjectService.prepareDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => {
70+
return Future.fromResult();
71+
};
72+
73+
let pluginPath = temp.mkdirSync("pluginDirectory");
74+
let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios");
75+
let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile");
76+
let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n");
77+
fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait();
78+
79+
let pluginData = {
80+
pluginPlatformsFolderPath(platform: string): string {
81+
return pluginPlatformsFolderPath;
82+
}
83+
};
84+
85+
iOSProjectService.preparePluginNativeCode(pluginData).wait();
86+
87+
let projectPodfilePath = path.join(platformsFolderPath, "Podfile");
88+
assert.isTrue(fs.exists(projectPodfilePath).wait());
89+
90+
let actualProjectPodfileContent = fs.readText(projectPodfilePath).wait();
91+
let expectedProjectPodfileContent = [`# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, " # End Podfile \n"].join("\n");
92+
assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent);
93+
});
94+
it("adds and removes plugin with Podfile", () => {
95+
let projectName = "projectDirectory2";
96+
let projectPath = temp.mkdirSync(projectName);
97+
98+
let testInjector = createTestInjector(projectPath, projectName);
99+
let fs: IFileSystem = testInjector.resolve("fs");
100+
101+
let packageJsonData = {
102+
"name": "myProject2",
103+
"version": "0.1.0",
104+
"nativescript": {
105+
"id": "org.nativescript.myProject2",
106+
"tns-android": {
107+
"version": "1.0.0"
108+
}
109+
}
110+
};
111+
fs.writeJson(path.join(projectPath, "package.json"), packageJsonData).wait();
112+
113+
let platformsFolderPath = path.join(projectPath, "ios");
114+
fs.createDirectory(platformsFolderPath).wait();
115+
116+
let iOSProjectService = testInjector.resolve("iOSProjectService");
117+
iOSProjectService.prepareDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => {
118+
return Future.fromResult();
119+
};
120+
iOSProjectService.removeDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => {
121+
return Future.fromResult();
122+
}
123+
124+
let pluginPath = temp.mkdirSync("pluginDirectory");
125+
let pluginPlatformsFolderPath = path.join(pluginPath, "platforms", "ios");
126+
let pluginPodfilePath = path.join(pluginPlatformsFolderPath, "Podfile");
127+
let pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n");
128+
fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait();
129+
130+
let pluginData = {
131+
pluginPlatformsFolderPath(platform: string): string {
132+
return pluginPlatformsFolderPath;
133+
}
134+
};
135+
136+
iOSProjectService.preparePluginNativeCode(pluginData).wait();
137+
138+
let projectPodfilePath = path.join(platformsFolderPath, "Podfile");
139+
assert.isTrue(fs.exists(projectPodfilePath).wait());
140+
141+
let actualProjectPodfileContent = fs.readText(projectPodfilePath).wait();
142+
let expectedProjectPodfileContent = [`# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, " # End Podfile \n"].join("\n");
143+
assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent);
144+
145+
iOSProjectService.removePluginNativeCode(pluginData).wait();
146+
147+
assert.isFalse(fs.exists(projectPodfilePath).wait());
148+
});
149+
});

test/npm-support.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ function setupProject(): IFuture<any> {
120120
normalizedPlatformName: "Android",
121121
platformProjectService: {
122122
prepareProject: () => Future.fromResult(),
123-
prepareAppResources: () => Future.fromResult()
123+
prepareAppResources: () => Future.fromResult(),
124+
afterPrepareAllPlugins: () => Future.fromResult()
124125
}
125126
}
126127
};

0 commit comments

Comments
 (0)