Skip to content

Commit 23f7223

Browse files
committed
Implement ability to use static libraries in iOS projects.
1 parent ed61780 commit 23f7223

File tree

3 files changed

+126
-24
lines changed

3 files changed

+126
-24
lines changed

lib/definitions/xcode.d.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
declare module "xcode" {
2-
interface FrameworkOptions {
2+
interface Options {
33
[key: string]: any;
44

55
customFramework?: boolean;
6-
76
embed?: boolean;
7+
lastType?: string;
8+
plugin?: boolean;
89
}
9-
10+
1011
class project {
1112
constructor(filename: string);
1213

@@ -15,9 +16,13 @@ declare module "xcode" {
1516

1617
writeSync(): string;
1718

18-
addFramework(filepath: string, options?: FrameworkOptions): void;
19-
removeFramework(filePath: string, options?: FrameworkOptions): void;
20-
19+
addFramework(filepath: string, options?: Options): void;
20+
removeFramework(filePath: string, options?: Options): void;
21+
22+
addPbxGroup(filePathsArray: any[], name: string, path: string, sourceTree: string): void;
23+
24+
addToHeaderSearchPaths(filePath: string): void;
25+
removeFromHeaderSearchPaths(filePath: string): void;
2126
updateBuildProperty(key: string, value: any): void;
2227
}
2328
}

lib/services/ios-project-service.ts

+114-17
Original file line numberDiff line numberDiff line change
@@ -183,24 +183,72 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
183183
public isPlatformPrepared(projectRoot: string): IFuture<boolean> {
184184
return this.$fs.exists(path.join(projectRoot, this.$projectData.projectName, constants.APP_FOLDER_NAME));
185185
}
186-
186+
187187
public addLibrary(libraryPath: string): IFuture<void> {
188188
return (() => {
189-
this.validateDynamicFramework(libraryPath).wait();
190-
189+
let extension = path.extname(libraryPath);
190+
if (extension === ".framework") {
191+
this.addDynamicFramework(libraryPath);
192+
} else if (extension === ".lib") {
193+
this.addStaticLibrary(libraryPath);
194+
} else {
195+
this.$errors.failWithoutHelp("The bundle at %s does not appear to be a dynamic framework or static lib package.", libraryPath);
196+
}
197+
}).future<void>()();
198+
}
199+
200+
private addDynamicFramework(frameworkPath: string): IFuture<void> {
201+
return (() => {
202+
this.validateDynamicFramework(frameworkPath).wait();
203+
191204
let targetPath = path.join("lib", this.platformData.normalizedPlatformName);
192205
let fullTargetPath = path.join(this.$projectData.projectDir, targetPath);
193206
this.$fs.ensureDirectoryExists(fullTargetPath).wait();
194-
shell.cp("-R", libraryPath, fullTargetPath);
207+
shell.cp("-R", frameworkPath, fullTargetPath);
195208

196209
let project = this.createPbxProj();
197-
let frameworkPath = this.getFrameworkRelativePath(libraryPath);
198-
project.addFramework(frameworkPath, { customFramework: true, embed: true });
210+
let frameworkRelativePath = this.getLibSubpathRelativeToProjectPath(path.basename(frameworkPath));
211+
project.addFramework(frameworkRelativePath, { customFramework: true, embed: true });
199212
project.updateBuildProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
200213
this.savePbxProj(project).wait();
201214
this.$logger.info("The iOS Deployment Target is now 8.0 in order to support Cocoa Touch Frameworks.");
202215
}).future<void>()();
203216
}
217+
218+
private addStaticLibrary(libContainingFolderPath: string): IFuture<void> {
219+
return (() => {
220+
let project = this.createPbxProj();
221+
project.addPbxGroup([], "Plugins", this.$projectData.projectName, null);
222+
//Find all static libraries. Assume we have more than one static lib. This should be exception though.
223+
let libFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".a" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).wait().isFile());
224+
let libraryPathContents = this.$fs.readDirectory(libContainingFolderPath).wait();
225+
let staticLibs = _(libraryPathContents).filter(platformItemName => libFilter(platformItemName, libContainingFolderPath)).value();
226+
//Copy files to lib folder.
227+
let libDestinationPath = path.join(this.$projectData.projectDir, path.join("lib", this.platformData.normalizedPlatformName));
228+
this.$fs.ensureDirectoryExists(libDestinationPath).wait();
229+
shell.cp("-R", libContainingFolderPath, libDestinationPath);
230+
/*
231+
1. Validate each static lib.
232+
2. Setup project's header search paths. Add static libs to the project.
233+
*Libs destination is expected to be at lib/iOS/<library_name>.lib/.
234+
*Public headers are expected to be at <libs_location>/include/<library_name>/.
235+
*/
236+
237+
staticLibs.forEach((staticLib) => {
238+
let staticLibPath = path.join(libContainingFolderPath, staticLib);
239+
this.validateStaticLibrary(staticLibPath).wait();
240+
241+
let relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.join(path.basename(libContainingFolderPath), staticLib));
242+
project.addFramework(relativeStaticLibPath);
243+
//Process header files
244+
let absoluteHeaderSearchPath = path.join(path.basename(libContainingFolderPath), "include", path.basename(staticLib, ".a"));
245+
let relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(absoluteHeaderSearchPath));
246+
project.addToHeaderSearchPaths(relativeHeaderSearchPath);
247+
});
248+
249+
this.savePbxProj(project).wait();
250+
}).future<void>()();
251+
}
204252

205253
public canUpdatePlatform(currentVersion: string, newVersion: string): IFuture<boolean> {
206254
return (() => {
@@ -280,10 +328,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
280328
return name.replace(/\\\"/g, "\"");
281329
}
282330

283-
private getFrameworkRelativePath(libraryPath: string): string {
284-
let frameworkName = path.basename(libraryPath, path.extname(libraryPath));
331+
private getLibSubpathRelativeToProjectPath(subPath: string): string {
285332
let targetPath = path.join("lib", this.platformData.normalizedPlatformName);
286-
let frameworkPath = path.relative("platforms/ios", path.join(targetPath, frameworkName + ".framework"));
333+
let frameworkPath = path.relative("platforms/ios", path.join(targetPath, subPath));
287334
return frameworkPath;
288335
}
289336

@@ -306,6 +353,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
306353
return (() => {
307354
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
308355
this.prepareDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait();
356+
this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData).wait();
309357
this.prepareCocoapods(pluginPlatformsFolderPath).wait();
310358
}).future<void>()();
311359
}
@@ -314,6 +362,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
314362
return (() => {
315363
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
316364
this.removeDynamicFrameworks(pluginPlatformsFolderPath, pluginData).wait();
365+
this.removeStaticLibs(pluginPlatformsFolderPath, pluginData).wait();
317366
this.removeCocoapods(pluginPlatformsFolderPath).wait();
318367
}).future<void>()();
319368
}
@@ -343,11 +392,11 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
343392
}).future<void>()();
344393
}
345394

346-
private getAllDynamicFrameworksForPlugin(pluginData: IPluginData): IFuture<string[]> {
347-
let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === ".framework";
395+
private getAllLibsForPluginAndFileExtension(pluginData: IPluginData, fileExtension: string): IFuture<string[]> {
396+
let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension;
348397
return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback);
349-
}
350-
398+
};
399+
351400
private buildPathToXcodeProjectFile(version: string): string {
352401
return path.join(this.$npmInstallationManager.getCachedPackagePath(this.platformData.frameworkPackageName, version), constants.PROJECT_FRAMEWORK_FOLDER_NAME, util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER), "project.pbxproj");
353402
}
@@ -365,6 +414,22 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
365414
}
366415
}).future<void>()();
367416
}
417+
418+
private validateStaticLibrary(libraryPath: string): IFuture<void> {
419+
return (() => {
420+
if (path.extname(libraryPath) !== ".a") {
421+
this.$errors.failWithoutHelp("The bundle at %s does not contain valid '.a' extension.", libraryPath);
422+
}
423+
424+
let expectedArchs = ["armv7", "arm64", "x86_64"];
425+
let archsInTheFatFile = this.$childProcess.exec("lipo -i " + libraryPath).wait();
426+
expectedArchs.forEach(expectedArch => {
427+
if (archsInTheFatFile.indexOf(expectedArch) < 0) {
428+
this.$errors.failWithoutHelp("The static library at %s is not build for all required architectures - %s.", libraryPath, expectedArchs);
429+
}
430+
});
431+
}).future<void>()();
432+
}
368433

369434
private replaceFileContent(file: string): IFuture<void> {
370435
return (() => {
@@ -385,13 +450,20 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
385450

386451
private prepareDynamicFrameworks(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> {
387452
return (() => {
388-
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait());
453+
_.each(this.getAllLibsForPluginAndFileExtension(pluginData, ".framework").wait(), fileName => this.addDynamicFramework(path.join(pluginPlatformsFolderPath, fileName)).wait());
454+
}).future<void>()();
455+
}
456+
457+
private prepareStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> {
458+
return (() => {
459+
_.each(this.getAllLibsForPluginAndFileExtension(pluginData, ".lib").wait(), fileName => this.addStaticLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait());
389460
}).future<void>()();
390461
}
391462

392463
private prepareCocoapods(pluginPlatformsFolderPath: string): IFuture<void> {
393464
return (() => {
394465
let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
466+
console.log("prepareCocoapods" + pluginPlatformsFolderPath);
395467
if(this.$fs.exists(pluginPodFilePath).wait()) {
396468
let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait();
397469
let contentToWrite = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent);
@@ -404,15 +476,40 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
404476
return (() => {
405477
let project = this.createPbxProj();
406478

407-
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => {
408-
let fullFrameworkPath = path.join(pluginPlatformsFolderPath, fileName);
409-
let relativeFrameworkPath = this.getFrameworkRelativePath(fullFrameworkPath);
479+
_.each(this.getAllLibsForPluginAndFileExtension(pluginData, ".framework").wait(), fileName => {
480+
let relativeFrameworkPath = this.getLibSubpathRelativeToProjectPath(fileName);
410481
project.removeFramework(relativeFrameworkPath, { customFramework: true, embed: true });
411482
});
412483

413484
this.savePbxProj(project).wait();
414485
}).future<void>()();
415486
}
487+
488+
private removeStaticLibs(pluginPlatformsFolderPath: string, pluginData: IPluginData): IFuture<void> {
489+
return (() => {
490+
let project = this.createPbxProj();
491+
console.log("pluginPlatformsFolderPath is " + pluginPlatformsFolderPath);
492+
493+
_.each(this.getAllLibsForPluginAndFileExtension(pluginData, ".lib").wait(), fileName => {
494+
let libContainingFolderPath = path.join(pluginPlatformsFolderPath, fileName);
495+
let libFilter = (fileName: string, containingFolderPath: string) => (path.extname(fileName) === ".a" && this.$fs.getFsStats(path.join(containingFolderPath, fileName)).wait().isFile());
496+
let libraryPathContents = this.$fs.readDirectory(libContainingFolderPath).wait();
497+
let staticLibs = _(libraryPathContents).filter(platformItemName => libFilter(platformItemName, libContainingFolderPath)).value();
498+
499+
staticLibs.forEach((staticLib) => {
500+
let relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.join(fileName, staticLib));
501+
project.removeFramework(relativeStaticLibPath);
502+
503+
let absoluteHeaderSearchPath = path.join(fileName, "include", path.basename(staticLib, ".a"));
504+
let relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(absoluteHeaderSearchPath));
505+
console.log("relativeHeaderSearchPath is " + relativeHeaderSearchPath);
506+
project.removeFromHeaderSearchPaths(relativeHeaderSearchPath);
507+
});
508+
});
509+
510+
this.savePbxProj(project).wait();
511+
}).future<void>()();
512+
}
416513

417514
private removeCocoapods(pluginPlatformsFolderPath: string): IFuture<void> {
418515
return (() => {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"vinyl-filter-since": "2.0.2",
7373
"winreg": "0.0.12",
7474
"ws": "0.7.1",
75-
"xcode": "https://github.com/NativeScript/node-xcode/archive/NativeScript-1.2.2.tar.gz",
75+
"xcode": "https://github.com/e2l3n/node-xcode/archive/1.2.6.tar.gz",
7676
"xmldom": "0.1.19",
7777
"xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master",
7878
"xmlmerge-js": "0.2.4",

0 commit comments

Comments
 (0)