Skip to content

Commit 61704cc

Browse files
committed
Merge pull request #638 from NativeScript/fatme/dynamic-libraries-support
Support for dynamic frameworks in iOS from plugins
2 parents 2cda8fe + 55f92f1 commit 61704cc

File tree

6 files changed

+117
-78
lines changed

6 files changed

+117
-78
lines changed

lib/definitions/project.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ interface IProjectTemplatesService {
2020
defaultTemplatePath: IFuture<string>;
2121
}
2222

23+
interface IPlatformProjectServiceBase {
24+
getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string;
25+
}
26+
2327
interface IPlatformProjectService {
2428
platformData: IPlatformData;
2529
validate(): IFuture<void>;
@@ -30,7 +34,7 @@ interface IPlatformProjectService {
3034
prepareProject(): IFuture<void>;
3135
prepareAppResources(appResourcesDirectoryPath: string): IFuture<void>;
3236
isPlatformPrepared(projectRoot: string): IFuture<boolean>;
33-
addLibrary(platformData: IPlatformData, libraryPath: string): IFuture<void>;
37+
addLibrary(libraryPath: string): IFuture<void>;
3438
canUpdatePlatform(currentVersion: string, newVersion: string): IFuture<boolean>;
3539
updatePlatform(currentVersion: string, newVersion: string): IFuture<void>;
3640
preparePluginNativeCode(pluginData: IPluginData): IFuture<void>;

lib/services/android-project-service.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import fs = require("fs");
1010
import os = require("os");
1111

1212
import androidProjectPropertiesManagerLib = require("./android-project-properties-manager");
13+
import projectServiceBaseLib = require("./platform-project-service-base");
1314

14-
class AndroidProjectService implements IPlatformProjectService {
15+
class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
1516
private static MIN_SUPPORTED_VERSION = 17;
1617
private SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22"]; // forbidden for now: "android-MNC"
1718
private static ANDROID_TARGET_PREFIX = "android";
@@ -28,13 +29,14 @@ class AndroidProjectService implements IPlatformProjectService {
2829
constructor(private $androidEmulatorServices: Mobile.IEmulatorPlatformServices,
2930
private $childProcess: IChildProcess,
3031
private $errors: IErrors,
31-
private $fs: IFileSystem,
32+
private $hostInfo: IHostInfo,
33+
private $injector: IInjector,
3234
private $logger: ILogger,
35+
private $options: IOptions,
3336
private $projectData: IProjectData,
3437
private $propertiesParser: IPropertiesParser,
35-
private $options: IOptions,
36-
private $hostInfo: IHostInfo,
37-
private $injector: IInjector) {
38+
$fs: IFileSystem) {
39+
super($fs);
3840
this._androidProjectPropertiesManagers = Object.create(null);
3941
}
4042

@@ -223,7 +225,7 @@ class AndroidProjectService implements IPlatformProjectService {
223225
}).future<void>()();
224226
}
225227

226-
public addLibrary(platformData: IPlatformData, libraryPath: string): IFuture<void> {
228+
public addLibrary(libraryPath: string): IFuture<void> {
227229
return (() => {
228230
let name = path.basename(libraryPath);
229231
let targetLibPath = this.getLibraryPath(name);
@@ -234,13 +236,13 @@ class AndroidProjectService implements IPlatformProjectService {
234236
this.parseProjectProperties(libraryPath, targetPath).wait();
235237

236238
shell.cp("-f", path.join(libraryPath, "*.jar"), targetPath);
237-
let projectLibsDir = path.join(platformData.projectRoot, "libs");
239+
let projectLibsDir = path.join(this.platformData.projectRoot, "libs");
238240
this.$fs.ensureDirectoryExists(projectLibsDir).wait();
239241
shell.cp("-f", path.join(libraryPath, "*.jar"), projectLibsDir);
240242

241243
let libProjProp = path.join(libraryPath, "project.properties");
242244
if (this.$fs.exists(libProjProp).wait()) {
243-
this.updateProjectReferences(platformData.projectRoot, targetLibPath).wait();
245+
this.updateProjectReferences(this.platformData.projectRoot, targetLibPath).wait();
244246
}
245247
}).future<void>()();
246248
}
@@ -266,21 +268,21 @@ class AndroidProjectService implements IPlatformProjectService {
266268

267269
public preparePluginNativeCode(pluginData: IPluginData): IFuture<void> {
268270
return (() => {
269-
let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData);
271+
let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME);
270272

271273
// Handle *.jars inside libs folder
272274
let libsFolderPath = path.join(pluginPlatformsFolderPath, AndroidProjectService.LIBS_FOLDER_NAME);
273275
if(this.$fs.exists(libsFolderPath).wait()) {
274276
let libsFolderContents = this.$fs.readDirectory(libsFolderPath).wait();
275277
_(libsFolderContents)
276278
.filter(libsFolderItem => path.extname(libsFolderItem) === ".jar")
277-
.map(jar => this.addLibrary(this.platformData, path.join(libsFolderPath, jar)).wait())
279+
.each(jar => this.addLibrary(path.join(libsFolderPath, jar)).wait())
278280
.value();
279281
}
280282

281283
// Handle android libraries
282284
let librarries = this.getAllLibrariesForPlugin(pluginData).wait();
283-
_.each(librarries, libraryName => this.addLibrary(this.platformData, path.join(pluginPlatformsFolderPath, libraryName)).wait());
285+
_.each(librarries, libraryName => this.addLibrary(path.join(pluginPlatformsFolderPath, libraryName)).wait());
284286
}).future<void>()();
285287
}
286288

@@ -299,10 +301,6 @@ class AndroidProjectService implements IPlatformProjectService {
299301
}).future<void>()();
300302
}
301303

302-
private getPluginPlatformsFolderPath(pluginData: IPluginData) {
303-
return pluginData.pluginPlatformsFolderPath(AndroidProjectService.ANDROID_PLATFORM_NAME);
304-
}
305-
306304
private getLibraryRelativePath(basePath: string, libraryPath: string): string {
307305
return path.relative(basePath, libraryPath).split("\\").join("/");
308306
}
@@ -320,16 +318,8 @@ class AndroidProjectService implements IPlatformProjectService {
320318

321319
private getAllLibrariesForPlugin(pluginData: IPluginData): IFuture<string[]> {
322320
return (() => {
323-
let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData);
324-
if(pluginPlatformsFolderPath && this.$fs.exists(pluginPlatformsFolderPath).wait()) {
325-
let platformsContents = this.$fs.readDirectory(pluginPlatformsFolderPath).wait();
326-
return _(platformsContents)
327-
.filter(platformItemName => platformItemName !== AndroidProjectService.LIBS_FOLDER_NAME &&
328-
this.$fs.exists(path.join(pluginPlatformsFolderPath, platformItemName, "project.properties")).wait())
329-
.value();
330-
}
331-
332-
return [];
321+
let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => fileName !== AndroidProjectService.LIBS_FOLDER_NAME && this.$fs.exists(path.join(pluginPlatformsFolderPath, fileName, "project.properties")).wait();
322+
return this.getAllNativeLibrariesForPlugin(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME, filterCallback).wait();
333323
}).future<string[]>()();
334324
}
335325

lib/services/ios-project-service.ts

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,28 @@ import util = require("util");
88
import xcode = require("xcode");
99
import constants = require("./../constants");
1010
import helpers = require("./../common/helpers");
11+
import projectServiceBaseLib = require("./platform-project-service-base");
1112

12-
class IOSProjectService implements IPlatformProjectService {
13+
class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
1314
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
1415
private static XCODEBUILD_MIN_VERSION = "6.0";
1516
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
17+
private static IOS_PLATFORM_NAME = "ios";
1618

1719
private get $npmInstallationManager(): INpmInstallationManager {
1820
return this.$injector.resolve("npmInstallationManager");
1921
}
2022

2123
constructor(private $projectData: IProjectData,
22-
private $fs: IFileSystem,
24+
$fs: IFileSystem,
2325
private $childProcess: IChildProcess,
2426
private $errors: IErrors,
2527
private $logger: ILogger,
2628
private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices,
2729
private $options: IOptions,
28-
private $injector: IInjector) { }
30+
private $injector: IInjector) {
31+
super($fs);
32+
}
2933

3034
public get platformData(): IPlatformData {
3135
var projectRoot = path.join(this.$projectData.platformsDir, "ios");
@@ -67,7 +71,7 @@ class IOSProjectService implements IPlatformProjectService {
6771
var splitedXcodeBuildVersion = xcodeBuildVersion.split(".");
6872
if(splitedXcodeBuildVersion.length === 3) {
6973
xcodeBuildVersion = util.format("%s.%s", splitedXcodeBuildVersion[0], splitedXcodeBuildVersion[1]);
70-
}
74+
}
7175

7276
if(helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) {
7377
this.$errors.fail("NativeScript can only run in Xcode version %s or greater", IOSProjectService.XCODEBUILD_MIN_VERSION);
@@ -172,23 +176,23 @@ class IOSProjectService implements IPlatformProjectService {
172176
return this.$fs.exists(path.join(projectRoot, this.$projectData.projectName, constants.APP_FOLDER_NAME));
173177
}
174178

175-
public addLibrary(platformData: IPlatformData, libraryPath: string): IFuture<void> {
176-
return (() => {
177-
this.validateDynamicFramework(libraryPath).wait();
178-
var umbrellaHeader = this.getUmbrellaHeaderFromDynamicFramework(libraryPath).wait();
179+
public addLibrary(libraryPath: string): IFuture<void> {
180+
return (() => {
181+
this.validateDynamicFramework(libraryPath).wait();
182+
var umbrellaHeader = this.getUmbrellaHeaderFromDynamicFramework(libraryPath).wait();
179183

180-
var frameworkName = path.basename(libraryPath, path.extname(libraryPath));
181-
var targetPath = path.join(this.$projectData.projectDir, "lib", platformData.normalizedPlatformName, frameworkName);
182-
this.$fs.ensureDirectoryExists(targetPath).wait();
183-
shell.cp("-R", libraryPath, targetPath);
184+
var frameworkName = path.basename(libraryPath, path.extname(libraryPath));
185+
var targetPath = path.join(this.$projectData.projectDir, "lib", this.platformData.normalizedPlatformName, frameworkName);
186+
this.$fs.ensureDirectoryExists(targetPath).wait();
187+
shell.cp("-R", libraryPath, targetPath);
184188

185-
let project = this.createPbxProj();
189+
let project = this.createPbxProj();
186190

187-
project.addFramework(path.join(targetPath, frameworkName + ".framework"), { customFramework: true, embed: true });
188-
project.updateBuildProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
189-
this.savePbxProj(project).wait();
190-
this.$logger.info("The iOS Deployment Target is now 8.0 in order to support Cocoa Touch Frameworks.");
191-
}).future<void>()();
191+
project.addFramework(path.join(targetPath, frameworkName + ".framework"), { customFramework: true, embed: true });
192+
project.updateBuildProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
193+
this.savePbxProj(project).wait();
194+
this.$logger.info("The iOS Deployment Target is now 8.0 in order to support Cocoa Touch Frameworks.");
195+
}).future<void>()();
192196
}
193197

194198
public canUpdatePlatform(currentVersion: string, newVersion: string): IFuture<boolean> {
@@ -282,48 +286,63 @@ class IOSProjectService implements IPlatformProjectService {
282286
}
283287

284288
public preparePluginNativeCode(pluginData: IPluginData): IFuture<void> {
285-
return Future.fromResult();
289+
return (() => {
290+
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
291+
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => this.addLibrary(path.join(pluginPlatformsFolderPath, fileName)).wait());
292+
}).future<void>()();
286293
}
287294

288295
public removePluginNativeCode(pluginData: IPluginData): IFuture<void> {
289-
return Future.fromResult();
296+
return (() => {
297+
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
298+
let project = this.createPbxProj();
299+
300+
_.each(this.getAllDynamicFrameworksForPlugin(pluginData).wait(), fileName => project.removeFramework(path.join(pluginPlatformsFolderPath, fileName + ".framework"), { customFramework: true, embed: true }));
301+
302+
this.savePbxProj(project).wait();
303+
}).future<void>()();
304+
}
305+
306+
private getAllDynamicFrameworksForPlugin(pluginData: IPluginData): IFuture<string[]> {
307+
let filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === ".framework";
308+
return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback);
290309
}
291310

292311
private buildPathToXcodeProjectFile(version: string): string {
293312
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");
294313
}
295314

296-
private validateDynamicFramework(libraryPath: string): IFuture<void> {
297-
return (() => {
298-
var infoPlistPath = path.join(libraryPath, "Info.plist");
299-
if (!this.$fs.exists(infoPlistPath).wait()) {
300-
this.$errors.failWithoutHelp("The bundle at %s does not contain an Info.plist file.", libraryPath);
301-
}
302-
303-
var packageType = this.$childProcess.exec(`/usr/libexec/PlistBuddy -c "Print :CFBundlePackageType" "${infoPlistPath}"`).wait().trim();
304-
if (packageType !== "FMWK") {
305-
this.$errors.failWithoutHelp("The bundle at %s does not appear to be a dynamic framework.", libraryPath);
306-
}
307-
}).future<void>()();
308-
}
309-
310-
private getUmbrellaHeaderFromDynamicFramework(libraryPath: string): IFuture<string> {
311-
return (() => {
312-
var modulemapPath = path.join(libraryPath, "Modules", "module.modulemap");
313-
if (!this.$fs.exists(modulemapPath).wait()) {
315+
private validateDynamicFramework(libraryPath: string): IFuture<void> {
316+
return (() => {
317+
var infoPlistPath = path.join(libraryPath, "Info.plist");
318+
if (!this.$fs.exists(infoPlistPath).wait()) {
319+
this.$errors.failWithoutHelp("The bundle at %s does not contain an Info.plist file.", libraryPath);
320+
}
321+
322+
var packageType = this.$childProcess.exec(`/usr/libexec/PlistBuddy -c "Print :CFBundlePackageType" "${infoPlistPath}"`).wait().trim();
323+
if (packageType !== "FMWK") {
324+
this.$errors.failWithoutHelp("The bundle at %s does not appear to be a dynamic framework.", libraryPath);
325+
}
326+
}).future<void>()();
327+
}
328+
329+
private getUmbrellaHeaderFromDynamicFramework(libraryPath: string): IFuture<string> {
330+
return (() => {
331+
var modulemapPath = path.join(libraryPath, "Modules", "module.modulemap");
332+
if (!this.$fs.exists(modulemapPath).wait()) {
314333
this.$errors.failWithoutHelp("The framework at %s does not contain a module.modulemap file.", modulemapPath);
315-
}
316-
317-
var modulemap = this.$fs.readText(modulemapPath).wait();
318-
var umbrellaRegex = /umbrella header "(.+\.h)"/g;
319-
var match = umbrellaRegex.exec(modulemap);
320-
if (!match) {
321-
this.$errors.failWithoutHelp("The modulemap at %s does not specify an umbrella header.", modulemapPath);
322-
}
323-
324-
return match[1];
325-
}).future<string>()();
326-
}
334+
}
335+
336+
var modulemap = this.$fs.readText(modulemapPath).wait();
337+
var umbrellaRegex = /umbrella header "(.+\.h)"/g;
338+
var match = umbrellaRegex.exec(modulemap);
339+
if (!match) {
340+
this.$errors.failWithoutHelp("The modulemap at %s does not specify an umbrella header.", modulemapPath);
341+
}
342+
343+
return match[1];
344+
}).future<string>()();
345+
}
327346

328347
private replaceFileContent(file: string): IFuture<void> {
329348
return (() => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
import path = require("path");
4+
5+
export class PlatformProjectServiceBase implements IPlatformProjectServiceBase {
6+
constructor(protected $fs: IFileSystem) { }
7+
8+
public getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string) {
9+
return pluginData.pluginPlatformsFolderPath(platform);
10+
}
11+
12+
public getAllNativeLibrariesForPlugin(pluginData: IPluginData, platform: string, filter: (fileName: string, pluginPlatformsFolderPath: string) => boolean): IFuture<string[]> {
13+
return (() => {
14+
let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, platform),
15+
nativeLibraries: string[] = [];
16+
if(pluginPlatformsFolderPath && this.$fs.exists(pluginPlatformsFolderPath).wait()) {
17+
let platformsContents = this.$fs.readDirectory(pluginPlatformsFolderPath).wait();
18+
nativeLibraries = _(platformsContents)
19+
.filter(platformItemName => filter(platformItemName, pluginPlatformsFolderPath))
20+
.value();
21+
}
22+
23+
return nativeLibraries;
24+
}).future<string[]>()();
25+
}
26+
}

lib/services/platform-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ export class PlatformService implements IPlatformService {
323323
this.$errors.failWithoutHelp("The path %s does not exist", libraryPath);
324324
} else {
325325
var platformData = this.$platformsData.getPlatformData(platform);
326-
platformData.platformProjectService.addLibrary(platformData, libraryPath).wait();
326+
platformData.platformProjectService.addLibrary(libraryPath).wait();
327327
}
328328
}).future<void>()();
329329
}

test/stubs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ export class PlatformProjectServiceStub implements IPlatformProjectService {
287287
isPlatformPrepared(projectRoot: string): IFuture<boolean> {
288288
return Future.fromResult(false);
289289
}
290-
addLibrary(platformData: IPlatformData, libraryPath: string): IFuture<void> {
290+
addLibrary(libraryPath: string): IFuture<void> {
291291
return Future.fromResult();
292292
}
293293
getDebugOnDeviceSetup(): Mobile.IDebugOnDeviceSetup {

0 commit comments

Comments
 (0)