From efba2b13f2f715a25f2fdaf4e282a1033677be61 Mon Sep 17 00:00:00 2001 From: Fatme Havaluova Date: Mon, 22 Jun 2015 12:33:44 +0300 Subject: [PATCH] Support for android native libraries in plugin --- lib/common | 2 +- lib/definitions/libref.ts | 1 + lib/definitions/plugins.d.ts | 3 +- lib/definitions/project.d.ts | 8 + .../android-project-properties-manager.ts | 118 ++++++++++ lib/services/android-project-service.ts | 202 +++++++++++------- lib/services/ios-project-service.ts | 8 + lib/services/plugins-service.ts | 128 ++++++----- test/android-project-properties-manager.ts | 144 +++++++++++++ test/plugins-service.ts | 5 +- test/stubs.ts | 12 +- 11 files changed, 490 insertions(+), 141 deletions(-) create mode 100644 lib/services/android-project-properties-manager.ts create mode 100644 test/android-project-properties-manager.ts diff --git a/lib/common b/lib/common index 76ba70be6f..edf10eb9f1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 76ba70be6f7b385bc1a30ad25f94f85c104762d3 +Subproject commit edf10eb9f10fac608638384087ecfc7cca30b001 diff --git a/lib/definitions/libref.ts b/lib/definitions/libref.ts index 0d10284d2b..e43f573038 100644 --- a/lib/definitions/libref.ts +++ b/lib/definitions/libref.ts @@ -2,4 +2,5 @@ interface ILibRef { idx: number; path: string; adjustedPath?: string; + key?: string; } diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 8b0f46d95c..c8269c74b9 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -7,7 +7,8 @@ interface IPluginsService { } interface IPluginData extends INodeModuleData { - platformsData: IPluginPlatformsData; + platformsData: IPluginPlatformsData; + pluginPlatformsFolderPath(platform: string): string; } interface INodeModuleData { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 3ac9337349..3b927893f3 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -33,4 +33,12 @@ interface IPlatformProjectService { addLibrary(platformData: IPlatformData, libraryPath: string): IFuture; canUpdatePlatform(currentVersion: string, newVersion: string): IFuture; updatePlatform(currentVersion: string, newVersion: string): IFuture; + preparePluginNativeCode(pluginData: IPluginData): IFuture; + removePluginNativeCode(pluginData: IPluginData): IFuture; +} + +interface IAndroidProjectPropertiesManager { + getProjectReferences(): IFuture; + addProjectReference(referencePath: string): IFuture; + removeProjectReference(referencePath: string): IFuture; } diff --git a/lib/services/android-project-properties-manager.ts b/lib/services/android-project-properties-manager.ts new file mode 100644 index 0000000000..d0132534d6 --- /dev/null +++ b/lib/services/android-project-properties-manager.ts @@ -0,0 +1,118 @@ +/// +"use strict"; + +import path = require("path"); + +export class AndroidProjectPropertiesManager implements IAndroidProjectPropertiesManager { + private static LIBRARY_REFERENCE_KEY_PREFIX = "android.library.reference."; + + private _editor: IPropertiesParserEditor = null; + private filePath: string = null; + private projectReferences: ILibRef[]; + private dirty = false; + + constructor(private $propertiesParser: IPropertiesParser, + private $fs: IFileSystem, + private $logger: ILogger, + directoryPath: string) { + this.filePath = path.join(directoryPath, "project.properties"); + } + + + + public getProjectReferences(): IFuture { + return (() => { + if(!this.projectReferences || this.dirty) { + let allProjectProperties = this.getAllProjectProperties().wait(); + let allProjectPropertiesKeys = _.keys(allProjectProperties); + this.projectReferences = _(allProjectPropertiesKeys) + .filter(key => _.startsWith(key, "android.library.reference.")) + .map(key => this.createLibraryReference(key, allProjectProperties[key])) + .value(); + } + + return this.projectReferences; + }).future()(); + } + + public addProjectReference(referencePath: string): IFuture { + return (() => { + let references = this.getProjectReferences().wait(); + let libRefExists = _.any(references, r => path.normalize(r.path) === path.normalize(referencePath)); + if(!libRefExists) { + this.addToPropertyList("android.library.reference", referencePath).wait(); + } + }).future()(); + } + + public removeProjectReference(referencePath: string): IFuture { + return (() => { + let references = this.getProjectReferences().wait(); + let libRefExists = _.any(references, r => path.normalize(r.path) === path.normalize(referencePath)); + if(libRefExists) { + this.removeFromPropertyList("android.library.reference", referencePath).wait(); + } else { + this.$logger.error(`Could not find ${referencePath}.`); + } + }).future()(); + } + + private createEditor(): IFuture { + return (() => { + return this._editor || this.$propertiesParser.createEditor(this.filePath).wait(); + }).future()(); + } + + private buildKeyName(key: string, index: number): string { + return `${key}.${index}`; + } + + private getAllProjectProperties(): IFuture { + return this.$propertiesParser.read(this.filePath); + } + + private createLibraryReference(referenceName: string, referencePath: string): ILibRef { + return { + idx: parseInt(referenceName.split("android.library.reference.")[1]), + key: referenceName, + path: referencePath, + adjustedPath: path.join(path.dirname(this.filePath), referencePath) + } + } + + private addToPropertyList(key: string, value: string): IFuture { + return (() => { + let editor = this.createEditor().wait(); + let i = 1; + while (editor.get(this.buildKeyName(key, i))) { + i++; + } + + editor.set(this.buildKeyName(key, i), value); + this.$propertiesParser.saveEditor().wait(); + this.dirty = true; + }).future()(); + } + + private removeFromPropertyList(key: string, value: string): IFuture { + return (() => { + let editor = this.createEditor().wait(); + let valueLowerCase = value.toLowerCase(); + let i = 1; + let currentValue: any; + while (currentValue = editor.get(this.buildKeyName(key, i))) { + if (currentValue.toLowerCase() === valueLowerCase) { + while (currentValue = editor.get(this.buildKeyName(key, i+1))) { + editor.set(this.buildKeyName(key, i), currentValue); + i++; + } + editor.set(this.buildKeyName(key, i)); + break; + } + i++; + } + this.$propertiesParser.saveEditor().wait(); + this.dirty = true; + }).future()(); + } +} \ No newline at end of file diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 1561015c85..07ce660bc5 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -9,6 +9,8 @@ import helpers = require("../common/helpers"); import fs = require("fs"); import os = require("os"); +import androidProjectPropertiesManagerLib = require("./android-project-properties-manager"); + class AndroidProjectService implements IPlatformProjectService { private static MIN_SUPPORTED_VERSION = 17; private SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22"]; // forbidden for now: "android-MNC" @@ -16,8 +18,12 @@ class AndroidProjectService implements IPlatformProjectService { private static RES_DIRNAME = "res"; private static VALUES_DIRNAME = "values"; private static VALUES_VERSION_DIRNAME_PREFIX = AndroidProjectService.VALUES_DIRNAME + "-v"; + private static ANDROID_PLATFORM_NAME = "android"; + private static LIBS_FOLDER_NAME = "libs"; + private targetApi: string; + private _androidProjectPropertiesManagers: IDictionary; constructor(private $androidEmulatorServices: Mobile.IEmulatorPlatformServices, private $childProcess: IChildProcess, @@ -27,7 +33,9 @@ class AndroidProjectService implements IPlatformProjectService { private $projectData: IProjectData, private $propertiesParser: IPropertiesParser, private $options: IOptions, - private $hostInfo: IHostInfo) { + private $hostInfo: IHostInfo, + private $injector: IInjector) { + this._androidProjectPropertiesManagers = Object.create(null); } private _platformData: IPlatformData = null; @@ -51,7 +59,7 @@ class AndroidProjectService implements IPlatformProjectService { frameworkFilesExtensions: [".jar", ".dat", ".so"], configurationFileName: "AndroidManifest.xml", configurationFilePath: path.join(this.$projectData.platformsDir, "android", "AndroidManifest.xml"), - mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }, { "application": "manifest", "attrname": "*" }] + mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }, {"nodename": "application", "attrname": "*"}] }; } @@ -182,104 +190,61 @@ class AndroidProjectService implements IPlatformProjectService { return this.$fs.exists(path.join(projectRoot, "assets", constants.APP_FOLDER_NAME)); } - public prepareAppResources(appResourcesDirectoryPath: string): IFuture { - return (() => { - let resourcesDirPath = path.join(appResourcesDirectoryPath, this.platformData.normalizedPlatformName); - let resourcesDirs = this.$fs.readDirectory(resourcesDirPath).wait(); - _.each(resourcesDirs, resourceDir => { - this.$fs.deleteDirectory(path.join(this.platformData.appResourcesDestinationDirectoryPath, resourceDir)).wait(); - }); - }).future()(); - } - - private parseProjectProperties(projDir: string, destDir: string): void { - let projProp = path.join(projDir, "project.properties"); - - if (!this.$fs.exists(projProp).wait()) { - this.$logger.warn("Warning: File %s does not exist", projProp); - return; + private getProjectPropertiesManager(filePath: string): IAndroidProjectPropertiesManager { + if(!this._androidProjectPropertiesManagers[filePath]) { + this._androidProjectPropertiesManagers[filePath] = this.$injector.resolve(androidProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: filePath }); } - - let lines = this.$fs.readText(projProp, "utf-8").wait().split(os.EOL); - - let regEx = /android\.library\.reference\.(\d+)=(.*)/; - lines.forEach(elem => { - let match = elem.match(regEx); - if (match) { - let libRef: ILibRef = { idx: parseInt(match[1]), path: match[2].trim() }; - libRef.adjustedPath = this.$fs.isRelativePath(libRef.path) ? path.join(projDir, libRef.path) : libRef.path; - this.parseProjectProperties(libRef.adjustedPath, destDir); - } - }); - - this.$logger.info("Copying %s", projDir); - shell.cp("-Rf", projDir, destDir); - - let targetDir = path.join(destDir, path.basename(projDir)); - // TODO: parametrize targetSdk - let targetSdk = "android-17"; - this.$logger.info("Generate build.xml for %s", targetDir); - this.runAndroidUpdate(targetDir, targetSdk).wait(); - } - - private getProjectReferences(projDir: string): ILibRef[]{ - let projProp = path.join(projDir, "project.properties"); - - let lines = this.$fs.readText(projProp, "utf-8").wait().split(os.EOL); - - let refs: ILibRef[] = []; - - let regEx = /android\.library\.reference\.(\d+)=(.*)/; - lines.forEach(elem => { - let match = elem.match(regEx); - if (match) { - let libRef: ILibRef = { idx: parseInt(match[1]), path: match[2] }; - libRef.adjustedPath = path.join(projDir, libRef.path); - refs.push(libRef); - } - }); - - return refs; + + return this._androidProjectPropertiesManagers[filePath]; } - - private updateProjectReferences(projDir: string, libraryPath: string): void { - let refs = this.getProjectReferences(projDir); - let maxIdx = refs.length > 0 ? _.max(refs, r => r.idx).idx : 0; - - let relLibDir = path.relative(projDir, libraryPath).split("\\").join("/"); - - let libRefExists = _.any(refs, r => path.normalize(r.path) === path.normalize(relLibDir)); - - if (!libRefExists) { - let projRef = util.format("%sandroid.library.reference.%d=%s", os.EOL, maxIdx + 1, relLibDir); + + private parseProjectProperties(projDir: string, destDir: string): IFuture { // projDir is libraryPath, targetPath is the path to lib folder + return (() => { let projProp = path.join(projDir, "project.properties"); - fs.appendFileSync(projProp, projRef, { encoding: "utf-8" }); - } + if (!this.$fs.exists(projProp).wait()) { + this.$logger.warn("Warning: File %s does not exist", projProp); + return; + } + + let projectPropertiesManager = this.getProjectPropertiesManager(projDir); + let references = projectPropertiesManager.getProjectReferences().wait(); + _.each(references, reference => { + let adjustedPath = this.$fs.isRelativePath(reference.path) ? path.join(projDir, reference.path) : reference.path; + this.parseProjectProperties(adjustedPath, destDir).wait(); + }); + + this.$logger.info("Copying %s", projDir); + shell.cp("-Rf", projDir, destDir); + + let targetDir = path.join(destDir, path.basename(projDir)); + let targetSdk = `android-${this.$options.sdk || AndroidProjectService.MIN_SUPPORTED_VERSION}`; + this.$logger.info("Generate build.xml for %s", targetDir); + this.runAndroidUpdate(targetDir, targetSdk).wait(); + }).future()(); } public addLibrary(platformData: IPlatformData, libraryPath: string): IFuture { return (() => { let name = path.basename(libraryPath); - let projDir = this.$projectData.projectDir; - let targetPath = path.join(projDir, "lib", platformData.normalizedPlatformName); + let targetLibPath = this.getLibraryPath(name); + + let targetPath = path.dirname(targetLibPath); this.$fs.ensureDirectoryExists(targetPath).wait(); - this.parseProjectProperties(libraryPath, targetPath); + this.parseProjectProperties(libraryPath, targetPath).wait(); shell.cp("-f", path.join(libraryPath, "*.jar"), targetPath); let projectLibsDir = path.join(platformData.projectRoot, "libs"); this.$fs.ensureDirectoryExists(projectLibsDir).wait(); shell.cp("-f", path.join(libraryPath, "*.jar"), projectLibsDir); - let targetLibPath = path.join(targetPath, path.basename(libraryPath)); - let libProjProp = path.join(libraryPath, "project.properties"); if (this.$fs.exists(libProjProp).wait()) { - this.updateProjectReferences(platformData.projectRoot, targetLibPath); + this.updateProjectReferences(platformData.projectRoot, targetLibPath).wait(); } }).future()(); } - + public getFrameworkFilesExtensions(): string[] { return [".jar", ".dat"]; } @@ -287,6 +252,85 @@ class AndroidProjectService implements IPlatformProjectService { public prepareProject(): IFuture { return (() => { }).future()(); } + + public prepareAppResources(appResourcesDirectoryPath: string): IFuture { + return (() => { + let resourcesDirPath = path.join(appResourcesDirectoryPath, this.platformData.normalizedPlatformName); + let resourcesDirs = this.$fs.readDirectory(resourcesDirPath).wait(); + _.each(resourcesDirs, resourceDir => { + this.$fs.deleteDirectory(path.join(this.platformData.appResourcesDestinationDirectoryPath, resourceDir)).wait(); + }); + }).future()(); + } + + public preparePluginNativeCode(pluginData: IPluginData): IFuture { + return (() => { + let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData); + + // Handle *.jars inside libs folder + let libsFolderPath = path.join(pluginPlatformsFolderPath, AndroidProjectService.LIBS_FOLDER_NAME); + if(this.$fs.exists(libsFolderPath).wait()) { + let libsFolderContents = this.$fs.readDirectory(libsFolderPath).wait(); + _(libsFolderContents) + .filter(libsFolderItem => path.extname(libsFolderItem) === ".jar") + .map(jar => this.addLibrary(this.platformData, path.join(libsFolderPath, jar)).wait()) + .value(); + } + + // Handle android libraries + let librarries = this.getAllLibrariesForPlugin(pluginData).wait(); + _.each(librarries, libraryName => this.addLibrary(this.platformData, path.join(pluginPlatformsFolderPath, libraryName)).wait()); + }).future()(); + } + + public removePluginNativeCode(pluginData: IPluginData): IFuture { + return (() => { + let projectPropertiesManager = this.getProjectPropertiesManager(this.platformData.projectRoot); + + let libraries = this.getAllLibrariesForPlugin(pluginData).wait(); + _.each(libraries, libraryName => { + let libraryPath = this.getLibraryPath(libraryName); + let libraryRelativePath = this.getLibraryRelativePath(this.platformData.projectRoot, libraryPath); + projectPropertiesManager.removeProjectReference(libraryRelativePath).wait(); + this.$fs.deleteDirectory(libraryPath).wait(); + }); + + }).future()(); + } + + private getPluginPlatformsFolderPath(pluginData: IPluginData) { + return pluginData.pluginPlatformsFolderPath(AndroidProjectService.ANDROID_PLATFORM_NAME); + } + + private getLibraryRelativePath(basePath: string, libraryPath: string): string { + return path.relative(basePath, libraryPath).split("\\").join("/"); + } + + private getLibraryPath(libraryName: string): string { + return path.join(this.$projectData.projectDir, "lib", this.platformData.normalizedPlatformName, libraryName); + } + + private updateProjectReferences(projDir: string, libraryPath: string): IFuture { + let relLibDir = this.getLibraryRelativePath(projDir, libraryPath); + + let projectPropertiesManager = this.getProjectPropertiesManager(projDir); + return projectPropertiesManager.addProjectReference(relLibDir); + } + + private getAllLibrariesForPlugin(pluginData: IPluginData): IFuture { + return (() => { + let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData); + if(pluginPlatformsFolderPath && this.$fs.exists(pluginPlatformsFolderPath).wait()) { + let platformsContents = this.$fs.readDirectory(pluginPlatformsFolderPath).wait(); + return _(platformsContents) + .filter(platformItemName => platformItemName !== AndroidProjectService.LIBS_FOLDER_NAME && + this.$fs.exists(path.join(pluginPlatformsFolderPath, platformItemName, "project.properties")).wait()) + .value(); + } + + return []; + }).future()(); + } private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): IFuture { return (() => { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 810244d4c0..0166b62a20 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -280,6 +280,14 @@ class IOSProjectService implements IPlatformProjectService { private savePbxProj(project: any): IFuture { return this.$fs.writeFile(this.pbxProjPath, project.writeSync()); } + + public preparePluginNativeCode(pluginData: IPluginData): IFuture { + return Future.fromResult(); + } + + public removePluginNativeCode(pluginData: IPluginData): IFuture { + return Future.fromResult(); + } private buildPathToXcodeProjectFile(version: string): string { 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"); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index b5e5b10559..29122d88e4 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -43,14 +43,24 @@ export class PluginsService implements IPluginsService { public remove(pluginName: string): IFuture { return (() => { + let removePluginNativeCodeAction = (modulesDestinationPath: string, platform: string, platformData: IPlatformData) => { + let pluginData = this.convertToPluginData(this.getNodeModuleData(pluginName).wait()); + pluginData.isPlugin = true; + return platformData.platformProjectService.removePluginNativeCode(pluginData); + }; + this.executeForAllInstalledPlatforms(removePluginNativeCodeAction).wait(); + this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait(); let showMessage = true; let action = (modulesDestinationPath: string, platform: string, platformData: IPlatformData) => { - shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName)); - this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform}.`); - showMessage = false; + return (() => { + shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName)); + + this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform}.`); + showMessage = false; + }).future()(); }; - this.executeForAllInstalledPlatforms(action); + this.executeForAllInstalledPlatforms(action).wait(); if(showMessage) { this.$logger.out(`Succsessfully removed plugin ${pluginName}`); @@ -61,57 +71,58 @@ export class PluginsService implements IPluginsService { public prepare(pluginData: IPluginData): IFuture { return (() => { let action = (pluginDestinationPath: string, platform: string, platformData: IPlatformData) => { - // Process .js files - let installedFrameworkVersion = this.getInstalledFrameworkVersion(platform).wait(); - let pluginPlatformsData = pluginData.platformsData; - if(pluginPlatformsData) { - let pluginVersion = (pluginPlatformsData)[platform]; - if(!pluginVersion) { - this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`); - return; - } - - if(semver.gt(pluginVersion, installedFrameworkVersion)) { - this.$logger.warn(`${pluginData.name} ${pluginVersion} for ${platform} is not compatible with the currently installed framework version ${installedFrameworkVersion}.`); - return; + return (() => { + // Process .js files + let installedFrameworkVersion = this.getInstalledFrameworkVersion(platform).wait(); + let pluginPlatformsData = pluginData.platformsData; + if(pluginPlatformsData) { + let pluginVersion = (pluginPlatformsData)[platform]; + if(!pluginVersion) { + this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`); + return; + } + + if(semver.gt(pluginVersion, installedFrameworkVersion)) { + this.$logger.warn(`${pluginData.name} ${pluginVersion} for ${platform} is not compatible with the currently installed framework version ${installedFrameworkVersion}.`); + return; + } } - } - - this.$fs.ensureDirectoryExists(pluginDestinationPath).wait(); - shelljs.cp("-R", pluginData.fullPath, pluginDestinationPath); - - let pluginPlatformsFolderPath = path.join(pluginDestinationPath, pluginData.name, "platforms", platform); - let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName); - let configurationFilePath = platformData.configurationFilePath; - if(this.$fs.exists(pluginConfigurationFilePath).wait()) { - // Validate plugin configuration file - let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait(); - this.validateXml(pluginConfigurationFileContent, pluginConfigurationFilePath); + this.$fs.ensureDirectoryExists(pluginDestinationPath).wait(); + shelljs.cp("-R", pluginData.fullPath, pluginDestinationPath); + + let pluginPlatformsFolderPath = path.join(pluginDestinationPath, pluginData.name, "platforms", platform); + let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName); + let configurationFilePath = platformData.configurationFilePath; - // Validate configuration file - let configurationFileContent = this.$fs.readText(configurationFilePath).wait(); - this.validateXml(configurationFileContent, configurationFilePath); + if(this.$fs.exists(pluginConfigurationFilePath).wait()) { + // Validate plugin configuration file + let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait(); + this.validateXml(pluginConfigurationFileContent, pluginConfigurationFilePath); + + // Validate configuration file + let configurationFileContent = this.$fs.readText(configurationFilePath).wait(); + this.validateXml(configurationFileContent, configurationFilePath); + + // Merge xml + let resultXml = this.mergeXml(configurationFileContent, pluginConfigurationFileContent, platformData.mergeXmlConfig || []).wait(); + this.validateXml(resultXml); + this.$fs.writeFile(configurationFilePath, resultXml).wait(); + } - // Merge xml - let resultXml = this.mergeXml(configurationFileContent, pluginConfigurationFileContent, platformData.mergeXmlConfig || []).wait(); - this.validateXml(resultXml); - this.$fs.writeFile(configurationFilePath, resultXml).wait(); - } - if(this.$fs.exists(pluginPlatformsFolderPath).wait()) { - shelljs.rm("-rf", pluginPlatformsFolderPath); - } - - this.$projectFilesManager.processPlatformSpecificFiles(pluginDestinationPath, platform).wait(); + this.$projectFilesManager.processPlatformSpecificFiles(pluginDestinationPath, platform).wait(); - // TODO: Add libraries + pluginData.pluginPlatformsFolderPath = (platform: string) => path.join(pluginData.fullPath, "platforms", platform); + platformData.platformProjectService.preparePluginNativeCode(pluginData).wait(); - // Show message - this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + // Show message + this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); + + }).future()(); }; - this.executeForAllInstalledPlatforms(action); + this.executeForAllInstalledPlatforms(action).wait(); }).future()(); } @@ -161,12 +172,13 @@ export class PluginsService implements IPluginsService { }).future()(); } - private convertToPluginData(cacheData: ICacheData): IPluginData { + private convertToPluginData(cacheData: any): IPluginData { let pluginData: any = {}; pluginData.name = cacheData.name; pluginData.version = cacheData.version; pluginData.fullPath = path.dirname(this.getPackageJsonFilePathForModule(cacheData.name)); pluginData.isPlugin = !!cacheData.nativescript; + pluginData.pluginPlatformsFolderPath = (platform: string) => path.join(pluginData.fullPath, "platforms", platform); if(pluginData.isPlugin) { pluginData.platformsData = cacheData.nativescript.platforms; @@ -203,16 +215,18 @@ export class PluginsService implements IPluginsService { return npmCommandResult[0][0].split("@")[0]; // returns plugin name } - private executeForAllInstalledPlatforms(action: (pluginDestinationPath: string, pl: string, platformData: IPlatformData) => void): void { - let availablePlatforms = _.keys(this.$platformsData.availablePlatforms); - _.each(availablePlatforms, platform => { - let isPlatformInstalled = this.$fs.exists(path.join(this.$projectData.platformsDir, platform.toLowerCase())).wait(); - if(isPlatformInstalled) { - let platformData = this.$platformsData.getPlatformData(platform.toLowerCase()); - let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); - action(pluginDestinationPath, platform.toLowerCase(), platformData); - } - }); + private executeForAllInstalledPlatforms(action: (pluginDestinationPath: string, pl: string, platformData: IPlatformData) => IFuture): IFuture { + return (() => { + let availablePlatforms = _.keys(this.$platformsData.availablePlatforms); + _.each(availablePlatforms, platform => { + let isPlatformInstalled = this.$fs.exists(path.join(this.$projectData.platformsDir, platform.toLowerCase())).wait(); + if(isPlatformInstalled) { + let platformData = this.$platformsData.getPlatformData(platform.toLowerCase()); + let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); + action(pluginDestinationPath, platform.toLowerCase(), platformData).wait(); + } + }); + }).future()(); } private getInstalledFrameworkVersion(platform: string): IFuture { diff --git a/test/android-project-properties-manager.ts b/test/android-project-properties-manager.ts new file mode 100644 index 0000000000..01adcc0651 --- /dev/null +++ b/test/android-project-properties-manager.ts @@ -0,0 +1,144 @@ +/// +"use strict"; + +import ProjectPropertiesParserLib = require("../lib/common/properties-parser"); +import FsLib = require("../lib/common/file-system"); +import ProjectPropertiesManagerLib = require("../lib/services/android-project-properties-manager"); +import HostInfoLib = require("../lib/common/host-info"); +import StaticConfigLib = require("../lib/config"); +import ErrorsLib = require("../lib/common/errors"); +import LoggerLib = require("../lib/common/logger"); +import ConfigLib = require("../lib/config"); +import OptionsLib = require("../lib/options"); +import yok = require("../lib/common/yok"); + +import os = require("os"); +import path = require("path"); + +import temp = require("temp"); +temp.track(); + +let assert = require("chai").assert; + +function createTestInjector(): IInjector { + let testInjector = new yok.Yok(); + testInjector.register("propertiesParser", ProjectPropertiesParserLib.PropertiesParser); + testInjector.register("fs", FsLib.FileSystem); + testInjector.register("hostInfo", HostInfoLib.HostInfo); + testInjector.register("staticConfig", StaticConfigLib.StaticConfig); + testInjector.register("errors", ErrorsLib.Errors); + testInjector.register("logger", LoggerLib.Logger); + testInjector.register("config", ConfigLib.Configuration); + testInjector.register("options", OptionsLib.Options); + + return testInjector; +} + +describe("Android project properties parser tests", () => { + it("adds project reference", () => { + let testInjector = createTestInjector(); + let fs = testInjector.resolve("fs"); + + let projectPropertiesFileContent = 'target=android-21'; + let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent).wait(); + + let projectPropertiesManager = testInjector.resolve(ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, {directoryPath: tempFolder}); + projectPropertiesManager.addProjectReference("testValue").wait(); + + let expectedContent = 'target=android-21' + os.EOL + + 'android.library.reference.1=testValue'; + let actualContent = fs.readText(path.join(tempFolder, "project.properties")).wait(); + + assert.equal(expectedContent, actualContent); + assert.equal(1, _.keys(projectPropertiesManager.getProjectReferences().wait()).length); + }); + it("adds project reference if another referencence already exists in project.properties file", () => { + let testInjector = createTestInjector(); + let fs = testInjector.resolve("fs"); + + let projectPropertiesFileContent = 'target=android-21' + os.EOL + + 'android.library.reference.1=someValue'; + let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent).wait(); + + let projectPropertiesManager = testInjector.resolve(ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, {directoryPath: tempFolder}); + projectPropertiesManager.addProjectReference("testValue").wait(); + + let expectedContent = 'target=android-21' + os.EOL + + 'android.library.reference.1=someValue' + os.EOL + + 'android.library.reference.2=testValue'; + let actualContent = fs.readText(path.join(tempFolder, "project.properties")).wait(); + + assert.equal(expectedContent, actualContent); + assert.equal(2, _.keys(projectPropertiesManager.getProjectReferences().wait()).length); + }); + it("adds project reference if more than one references exist in project.properties file", () => { + let testInjector = createTestInjector(); + let fs = testInjector.resolve("fs"); + + let projectPropertiesFileContent = 'target=android-21' + os.EOL + + 'android.library.reference.1=value1' + os.EOL + + 'android.library.reference.2=value2' + os.EOL + + 'android.library.reference.3=value3' + os.EOL + + 'android.library.reference.4=value4' + os.EOL + + 'android.library.reference.5=value5'; + let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent).wait(); + + let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve(ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, {directoryPath: tempFolder}); + projectPropertiesManager.addProjectReference("testValue").wait(); + + let expectedContent = projectPropertiesFileContent + os.EOL + + 'android.library.reference.6=testValue'; + + let actualContent = fs.readText(path.join(tempFolder, "project.properties")).wait(); + + assert.equal(expectedContent, actualContent); + assert.equal(6, _.keys(projectPropertiesManager.getProjectReferences().wait()).length); + }); + it("removes project reference if only one reference exists", () => { + let testInjector = createTestInjector(); + let fs = testInjector.resolve("fs"); + + let projectPropertiesFileContent = 'android.library.reference.1=value1' + os.EOL + + 'target=android-21'; + let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent).wait(); + + let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve(ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, {directoryPath: tempFolder}); + projectPropertiesManager.removeProjectReference("value1").wait(); + + let expectedContent = 'target=android-21'; + let actualContent = fs.readText(path.join(tempFolder, "project.properties")).wait(); + + assert.equal(expectedContent, actualContent); + assert.equal(0, _.keys(projectPropertiesManager.getProjectReferences().wait()).length); + }); + it("removes project reference when another references exist before and after the specified reference", () => { + let testInjector = createTestInjector(); + let fs = testInjector.resolve("fs"); + + let projectPropertiesFileContent = 'target=android-17' + os.EOL + + 'android.library.reference.1=value1' + os.EOL + + 'android.library.reference.2=value2' + os.EOL + + 'android.library.reference.3=value3' + os.EOL + + 'android.library.reference.4=value4' + os.EOL + + 'android.library.reference.5=value5'; + let tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); + fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent).wait(); + + let projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve(ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, {directoryPath: tempFolder}); + projectPropertiesManager.removeProjectReference("value3").wait(); + + let expectedContent = 'target=android-17' + os.EOL + + 'android.library.reference.1=value1' + os.EOL + + 'android.library.reference.2=value2' + os.EOL + + 'android.library.reference.3=value4' + os.EOL + + 'android.library.reference.4=value5' + os.EOL; + let actualContent = fs.readText(path.join(tempFolder, "project.properties")).wait(); + + assert.equal(expectedContent, actualContent); + assert.equal(4, _.keys(projectPropertiesManager.getProjectReferences().wait()).length); + }); +}); \ No newline at end of file diff --git a/test/plugins-service.ts b/test/plugins-service.ts index fdb5cd2083..2d7ca69aa8 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -555,7 +555,10 @@ describe("Plugins service", () => { frameworkPackageName: "tns-android", configurationFileName: "AndroidManifest.xml", configurationFilePath: path.join(appDestinationDirectoryPath, "AndroidManifest.xml"), - mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }] + mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }], + platformProjectService: { + preparePluginNativeCode: (pluginData: IPluginData) => (() => {}).future()() + } } } diff --git a/test/stubs.ts b/test/stubs.ts index 79b60c2a90..ae549f8972 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -240,7 +240,9 @@ export class PlatformsDataStub implements IPlatformsData { validPackageNamesForDevice: [], frameworkFilesExtensions: [], appDestinationDirectoryPath: "", - appResourcesDestinationDirectoryPath: "" + appResourcesDestinationDirectoryPath: "", + preparePluginNativeCode: () => Future.fromResult(), + removePluginNativeCode: () => Future.fromResult() }; } @@ -297,7 +299,13 @@ export class PlatformProjectServiceStub implements IPlatformProjectService { updatePlatform(currentVersion: string, newVersion: string): IFuture { return Future.fromResult(); } - prepareAppResources(appResourcesDirectoryPath: string): IFuture { + prepareAppResources(appResourcesDirectoryPath: string): IFuture { + return Future.fromResult(); + } + preparePluginNativeCode(pluginData: IPluginData): IFuture { + return Future.fromResult(); + } + removePluginNativeCode(pluginData: IPluginData): IFuture { return Future.fromResult(); } }