Skip to content

Commit b2241e3

Browse files
committed
Add library for iOS dynamic frameworks
1 parent d971f77 commit b2241e3

File tree

4 files changed

+107
-9
lines changed

4 files changed

+107
-9
lines changed

lib/common

lib/definitions/xcode.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
declare module "xcode" {
2+
interface FrameworkOptions {
3+
[key: string]: any;
4+
5+
customFramework?: boolean;
6+
7+
embed?: boolean;
8+
}
9+
10+
class project {
11+
constructor(filename: string);
12+
13+
parse(callback: () => void): void;
14+
parseSync(): void;
15+
16+
writeSync(): string;
17+
18+
addFramework(filepath: string, options?: FrameworkOptions): void;
19+
removeFramework(filePath: string, options?: FrameworkOptions): void;
20+
21+
updateBuildProperty(key: string, value: any): void;
22+
}
23+
}

lib/services/ios-project-service.ts

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ import Future = require("fibers/future");
55
import path = require("path");
66
import shell = require("shelljs");
77
import util = require("util");
8+
import xcode = require("xcode");
89
import constants = require("./../constants");
910
import helpers = require("./../common/helpers");
1011
import options = require("../common/options");
1112

1213
class IOSProjectService implements IPlatformProjectService {
1314
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
14-
private static XCODEBUILD_MIN_VERSION = "5.0";
15+
private static XCODEBUILD_MIN_VERSION = "6.0";
1516
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
1617

1718
constructor(private $projectData: IProjectData,
1819
private $fs: IFileSystem,
1920
private $childProcess: IChildProcess,
2021
private $errors: IErrors,
22+
private $logger: ILogger,
2123
private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices) { }
2224

2325
public get platformData(): IPlatformData {
@@ -170,14 +172,86 @@ class IOSProjectService implements IPlatformProjectService {
170172
}
171173

172174
public addLibrary(platformData: IPlatformData, libraryPath: string): IFuture<void> {
173-
var name = path.basename(libraryPath);
174-
var targetPath = path.join(this.$projectData.projectDir, "lib", platformData.normalizedPlatformName, path.basename(name, path.extname(name)));
175-
this.$fs.ensureDirectoryExists(targetPath).wait();
175+
return (() => {
176+
this.validateDynamicFramework(libraryPath).wait();
177+
var umbrellaHeader = this.getUmbrellaHeaderFromDynamicFramework(libraryPath).wait();
178+
179+
var frameworkName = path.basename(libraryPath, path.extname(libraryPath));
180+
var targetPath = path.join(this.$projectData.projectDir, "lib", platformData.normalizedPlatformName, frameworkName);
181+
this.$fs.ensureDirectoryExists(targetPath).wait();
182+
shell.cp("-R", libraryPath, targetPath);
183+
184+
this.generateFrameworkMetadata(platformData.projectRoot, targetPath, frameworkName, umbrellaHeader).wait();
185+
186+
var pbxProjPath = path.join(platformData.projectRoot, this.$projectData.projectName + ".xcodeproj", "project.pbxproj");
187+
var project = new xcode.project(pbxProjPath);
188+
project.parseSync();
189+
190+
project.addFramework(path.join(targetPath, frameworkName + ".framework"), { customFramework: true, embed: true });
191+
project.updateBuildProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
192+
this.$fs.writeFile(pbxProjPath, project.writeSync()).wait();
193+
this.$logger.info("The iOS Deployment Target is now 8.0 in order to support Cocoa Touch Frameworks.");
194+
}).future<void>()();
195+
}
196+
197+
private validateDynamicFramework(libraryPath: string): IFuture<void> {
198+
return (() => {
199+
var infoPlistPath = path.join(libraryPath, "Info.plist");
200+
if (!this.$fs.exists(infoPlistPath).wait()) {
201+
this.$errors.failWithoutHelp("The bundle at %s does not contain an Info.plist file.", libraryPath);
202+
}
203+
204+
var packageType = this.$childProcess.exec(util.format("/usr/libexec/PlistBuddy -c \"Print :CFBundlePackageType\" %s", infoPlistPath)).wait().trim();
205+
if (packageType != "FMWK") {
206+
this.$errors.failWithoutHelp("The bundle at %s does not appear to be a dynamic framework.", libraryPath);
207+
}
208+
}).future<void>()();
209+
}
210+
211+
private getUmbrellaHeaderFromDynamicFramework(libraryPath: string): IFuture<string> {
212+
return (() => {
213+
var modulemapPath = path.join(libraryPath, "Modules", "module.modulemap");
214+
if (!this.$fs.exists(modulemapPath).wait()) {
215+
this.$errors.failWithoutHelp("The framework at %s does not contain a module.modulemap file.");
216+
}
217+
218+
var modulemap = this.$fs.readText(modulemapPath).wait();
219+
var umbrellaRegex = /umbrella header "(.+\.h)"/g;
220+
var match = umbrellaRegex.exec(modulemap);
221+
if (!match) {
222+
this.$errors.failWithoutHelp("The modulemap at %s does not specify an umbrella header.", modulemapPath);
223+
}
224+
225+
return match[1];
226+
}).future<string>()();
227+
}
228+
229+
private generateFrameworkMetadata(projectRoot: string, frameworkDir: string, frameworkName: string, umbrellaHeader: string): IFuture<void> {
230+
return (() => {
231+
if (!this.$fs.exists("/usr/local/lib/libmonoboehm-2.0.1.dylib").wait()) {
232+
this.$errors.failWithoutHelp("NativeScript needs Mono 3.10 or newer installed in /usr/local");
233+
}
234+
235+
var yamlOut = path.join(frameworkDir, "Metadata");
236+
this.$fs.createDirectory(yamlOut).wait();
237+
238+
var tempHeader = path.join(yamlOut, "Metadata.h");
239+
this.$fs.writeFile(tempHeader, util.format("#import <%s/%s>", frameworkName, umbrellaHeader)).wait();
240+
241+
this.$logger.info("Generating metadata for %s.framework. This can take a minute.", frameworkName);
242+
var sdkPath = this.$childProcess.exec("xcrun -sdk iphoneos --show-sdk-path").wait().trim();
243+
var generatorExecOptions = {
244+
env: {
245+
DYLD_FALLBACK_LIBRARY_PATH: this.$childProcess.exec("xcode-select -p").wait().trim() + "/Toolchains/XcodeDefault.xctoolchain/usr/lib"
246+
}
247+
};
248+
this.$childProcess.exec(util.format("%s/Metadata/MetadataGenerator -s %s -u %s -o %s -cflags=\"-F%s\"", projectRoot, sdkPath, tempHeader, yamlOut, frameworkDir), generatorExecOptions).wait();
176249

177-
shell.cp("-R", libraryPath, targetPath);
250+
this.$fs.copyFile(path.join(yamlOut, "Metadata-armv7", frameworkName + ".yaml"), path.join(projectRoot, "Metadata", "Metadata-armv7", frameworkName + ".yaml")).wait();
251+
this.$fs.copyFile(path.join(yamlOut, "Metadata-arm64", frameworkName + ".yaml"), path.join(projectRoot, "Metadata", "Metadata-arm64", frameworkName + ".yaml")).wait();
178252

179-
this.$errors.fail("Implement me!");
180-
return Future.fromResult();
253+
this.$fs.deleteDirectory(yamlOut).wait();
254+
}).future<void>()();
181255
}
182256

183257
private replaceFileContent(file: string): IFuture<void> {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"unzip": "0.1.9",
5959
"xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master",
6060
"yargs": "1.2.2",
61-
"node-inspector": "0.7.4"
61+
"node-inspector": "0.7.4",
62+
"xcode": "https://github.com/fealebenpae/node-xcode/tarball/master"
6263
},
6364
"analyze": true,
6465
"devDependencies": {

0 commit comments

Comments
 (0)