-
-
Notifications
You must be signed in to change notification settings - Fork 196
Cocoapods support #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cocoapods support #769
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,19 +2,21 @@ | |
"use strict"; | ||
|
||
import Future = require("fibers/future"); | ||
import path = require("path"); | ||
import shell = require("shelljs"); | ||
import util = require("util"); | ||
import xcode = require("xcode"); | ||
import * as path from "path"; | ||
import * as shell from "shelljs"; | ||
import * as util from "util"; | ||
import * as os from "os"; | ||
import * as xcode from "xcode"; | ||
import constants = require("./../constants"); | ||
import helpers = require("./../common/helpers"); | ||
import projectServiceBaseLib = require("./platform-project-service-base"); | ||
|
||
class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { | ||
export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { | ||
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; | ||
private static XCODEBUILD_MIN_VERSION = "6.0"; | ||
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__"; | ||
private static IOS_PLATFORM_NAME = "ios"; | ||
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; | ||
|
||
private get $npmInstallationManager(): INpmInstallationManager { | ||
return this.$injector.resolve("npmInstallationManager"); | ||
|
@@ -262,14 +264,17 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase | |
|
||
this.savePbxProj(project).wait(); | ||
} | ||
|
||
}).future<void>()(); | ||
} | ||
|
||
public prepareAppResources(appResourcesDirectoryPath: string): IFuture<void> { | ||
return this.$fs.deleteDirectory(this.platformData.appResourcesDestinationDirectoryPath); | ||
} | ||
|
||
private get projectPodFilePath(): string { | ||
return path.join(this.platformData.projectRoot, "Podfile"); | ||
} | ||
|
||
private replace(name: string): string { | ||
if(_.startsWith(name, '"')) { | ||
name = name.substr(1, name.length-2); | ||
|
@@ -303,22 +308,41 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase | |
public preparePluginNativeCode(pluginData: IPluginData): IFuture<void> { | ||
return (() => { | ||
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); | ||
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait()); | ||
this.prepareDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait(); | ||
this.prepareCocoapods(pluginPlatformsFolderPath).wait(); | ||
}).future<void>()(); | ||
} | ||
|
||
public removePluginNativeCode(pluginData: IPluginData): IFuture<void> { | ||
return (() => { | ||
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME); | ||
let project = this.createPbxProj(); | ||
|
||
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => { | ||
let fullFrameworkPath = path.join(pluginPlatformsFolderPath, fileName); | ||
let relativeFrameworkPath = this.getFrameworkRelativePath(fullFrameworkPath); | ||
project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true }) | ||
}); | ||
|
||
this.savePbxProj(project).wait(); | ||
this.removeDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait(); | ||
this.removeCocoapods(pluginPlatformsFolderPath).wait(); | ||
}).future<void>()(); | ||
} | ||
|
||
public afterPrepareAllPlugins(): IFuture<void> { | ||
return (() => { | ||
if(this.$fs.exists(this.projectPodFilePath).wait()) { | ||
// Check availability | ||
try { | ||
this.$childProcess.exec("gem which cocoapods").wait(); | ||
} catch(e) { | ||
this.$errors.failWithoutHelp("CocoaPods are not installed. Run `sudo gem install cocoapods` and try again."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The message on my machine is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed the issue in person and we decided that the code is correct. |
||
} | ||
|
||
let projectPodfileContent = this.$fs.readText(this.projectPodFilePath).wait(); | ||
this.$logger.trace("Project Podfile content"); | ||
this.$logger.trace(projectPodfileContent); | ||
|
||
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.$logger.info("Installing pods..."); | ||
this.$childProcess.exec("pod install", { cwd: this.platformData.projectRoot }).wait(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does happen when this program fails? We should check its exit code or error output. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the command fails the error will be printed on the console. |
||
} | ||
}).future<void>()(); | ||
} | ||
|
||
|
@@ -379,5 +403,57 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase | |
this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName)).wait(); | ||
}).future<void>()(); | ||
} | ||
|
||
private prepareDynamicFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> { | ||
return (() => { | ||
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait()); | ||
}).future<void>()(); | ||
} | ||
|
||
private prepareCocoapods(pluginPlatformsFolderPath: string): IFuture<void> { | ||
return (() => { | ||
let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); | ||
if(this.$fs.exists(pluginPodFilePath).wait()) { | ||
let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait(); | ||
let contentToWrite = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); | ||
this.$fs.appendFile(this.projectPodFilePath, contentToWrite).wait(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This potentially generates invalid podfile.
Are we going to address these? perhaps a warning to check the resulting podfile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have two dependencies with incompatible versions If Podfile has more than one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should document this behavior and give a warning to the user. Ping @ikoevska There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a warning message. |
||
} | ||
}).future<void>()(); | ||
} | ||
|
||
private removeDynamicFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> { | ||
return (() => { | ||
let project = this.createPbxProj(); | ||
|
||
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => { | ||
let fullFrameworkPath = path.join(pluginPlatformsFolderPath, fileName); | ||
let relativeFrameworkPath = this.getFrameworkRelativePath(fullFrameworkPath); | ||
project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true }) | ||
}); | ||
|
||
this.savePbxProj(project).wait(); | ||
}).future<void>()(); | ||
} | ||
|
||
private removeCocoapods(pluginPlatformsFolderPath: string): IFuture<void> { | ||
return (() => { | ||
let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); | ||
if(this.$fs.exists(pluginPodFilePath).wait()) { | ||
let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait(); | ||
let projectPodFileContent = this.$fs.readText(this.projectPodFilePath).wait(); | ||
let contentToRemove= this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); | ||
projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, ""); | ||
if(_.isEmpty(projectPodFileContent)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may trim the content - there is no need to keep files consisting of empty space. This may happen when the user manually adds empty space. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to keep the content as is from original Podfile. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is when the user has manually edit the podfile. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed the issue in person and decided to keep the current code. |
||
this.$fs.deleteFile(this.projectPodFilePath).wait(); | ||
} else { | ||
this.$fs.writeFile(this.projectPodFilePath, projectPodFileContent).wait(); | ||
} | ||
} | ||
}).future<void>()(); | ||
} | ||
|
||
private buildPodfileContent(pluginPodFilePath: string, pluginPodFileContent: string): string { | ||
return `# Begin Podfile - ${pluginPodFilePath} ${os.EOL} ${pluginPodFileContent} ${os.EOL} # End Podfile ${os.EOL}`; | ||
} | ||
} | ||
$injector.register("iOSProjectService", IOSProjectService); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/// <reference path=".d.ts" /> | ||
"use strict"; | ||
|
||
import Future = require("fibers/future"); | ||
import * as path from "path"; | ||
import temp = require("temp"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This leaks the created directories. Consider executing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other tests can be rewritten in the future. Do not depend on side effects of unrelated code. If you go this route, you can move temp.track in test's bootstrapper and remove it from all other places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We've decided to implement this in another PR. |
||
temp.track(); | ||
|
||
import ChildProcessLib = require("../lib/common/child-process"); | ||
import ConfigLib = require("../lib/config"); | ||
import ErrorsLib = require("../lib/common/errors"); | ||
import FileSystemLib = require("../lib/common/file-system"); | ||
import HostInfoLib = require("../lib/common/host-info"); | ||
import iOSProjectServiceLib = require("../lib/services/ios-project-service"); | ||
import LoggerLib = require("../lib/common/logger"); | ||
import OptionsLib = require("../lib/options"); | ||
import ProjectDataLib = require("../lib/project-data"); | ||
|
||
import yok = require("../lib/common/yok"); | ||
|
||
import { assert } from "chai"; | ||
|
||
function createTestInjector(projectPath: string, projectName: string): IInjector { | ||
let testInjector = new yok.Yok(); | ||
testInjector.register("childProcess", ChildProcessLib.ChildProcess); | ||
testInjector.register("config", ConfigLib.Configuration); | ||
testInjector.register("errors", ErrorsLib.Errors); | ||
testInjector.register("fs", FileSystemLib.FileSystem); | ||
testInjector.register("hostInfo", HostInfoLib.HostInfo); | ||
testInjector.register("injector", testInjector); | ||
testInjector.register("iOSEmulatorServices", {}); | ||
testInjector.register("iOSProjectService", iOSProjectServiceLib.IOSProjectService); | ||
testInjector.register("logger", LoggerLib.Logger); | ||
testInjector.register("options", OptionsLib.Options); | ||
testInjector.register("projectData", { | ||
platformsDir: projectPath, | ||
projectName: projectName | ||
}); | ||
testInjector.register("projectHelper", {}); | ||
testInjector.register("staticConfig", ConfigLib.StaticConfig); | ||
|
||
return testInjector; | ||
} | ||
|
||
describe("Cocoapods support", () => { | ||
it("adds plugin with Podfile", () => { | ||
let projectName = "projectDirectory"; | ||
let projectPath = temp.mkdirSync(projectName); | ||
|
||
let testInjector = createTestInjector(projectPath, projectName); | ||
let fs: IFileSystem = testInjector.resolve("fs"); | ||
|
||
let packageJsonData = { | ||
"name": "myProject", | ||
"version": "0.1.0", | ||
"nativescript": { | ||
"id": "org.nativescript.myProject", | ||
"tns-android": { | ||
"version": "1.0.0" | ||
} | ||
} | ||
}; | ||
fs.writeJson(path.join(projectPath, "package.json"), packageJsonData).wait(); | ||
|
||
let platformsFolderPath = path.join(projectPath, "ios"); | ||
fs.createDirectory(platformsFolderPath).wait(); | ||
|
||
let iOSProjectService = testInjector.resolve("iOSProjectService"); | ||
iOSProjectService.prepareDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => { | ||
return Future.fromResult(); | ||
}; | ||
|
||
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"); | ||
fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait(); | ||
|
||
let pluginData = { | ||
pluginPlatformsFolderPath(platform: string): string { | ||
return pluginPlatformsFolderPath; | ||
} | ||
}; | ||
|
||
iOSProjectService.preparePluginNativeCode(pluginData).wait(); | ||
|
||
let projectPodfilePath = path.join(platformsFolderPath, "Podfile"); | ||
assert.isTrue(fs.exists(projectPodfilePath).wait()); | ||
|
||
let actualProjectPodfileContent = fs.readText(projectPodfilePath).wait(); | ||
let expectedProjectPodfileContent = [`# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, " # End Podfile \n"].join("\n"); | ||
assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); | ||
}); | ||
it("adds and removes plugin with Podfile", () => { | ||
let projectName = "projectDirectory2"; | ||
let projectPath = temp.mkdirSync(projectName); | ||
|
||
let testInjector = createTestInjector(projectPath, projectName); | ||
let fs: IFileSystem = testInjector.resolve("fs"); | ||
|
||
let packageJsonData = { | ||
"name": "myProject2", | ||
"version": "0.1.0", | ||
"nativescript": { | ||
"id": "org.nativescript.myProject2", | ||
"tns-android": { | ||
"version": "1.0.0" | ||
} | ||
} | ||
}; | ||
fs.writeJson(path.join(projectPath, "package.json"), packageJsonData).wait(); | ||
|
||
let platformsFolderPath = path.join(projectPath, "ios"); | ||
fs.createDirectory(platformsFolderPath).wait(); | ||
|
||
let iOSProjectService = testInjector.resolve("iOSProjectService"); | ||
iOSProjectService.prepareDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => { | ||
return Future.fromResult(); | ||
}; | ||
iOSProjectService.removeDynamicFrameworks = (pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> => { | ||
return Future.fromResult(); | ||
} | ||
|
||
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"); | ||
fs.writeFile(pluginPodfilePath, pluginPodfileContent).wait(); | ||
|
||
let pluginData = { | ||
pluginPlatformsFolderPath(platform: string): string { | ||
return pluginPlatformsFolderPath; | ||
} | ||
}; | ||
|
||
iOSProjectService.preparePluginNativeCode(pluginData).wait(); | ||
|
||
let projectPodfilePath = path.join(platformsFolderPath, "Podfile"); | ||
assert.isTrue(fs.exists(projectPodfilePath).wait()); | ||
|
||
let actualProjectPodfileContent = fs.readText(projectPodfilePath).wait(); | ||
let expectedProjectPodfileContent = [`# Begin Podfile - ${pluginPodfilePath} `, ` ${pluginPodfileContent} `, " # End Podfile \n"].join("\n"); | ||
assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); | ||
|
||
iOSProjectService.removePluginNativeCode(pluginData).wait(); | ||
|
||
assert.isFalse(fs.exists(projectPodfilePath).wait()); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost all other imports can also be converted (Future can't).