Skip to content

Commit

Permalink
Merge pull request #1408 from NativeScript/fatme/plugin-vars
Browse files Browse the repository at this point in the history
Out of the box support for application id in plugins
  • Loading branch information
Fatme authored and Fatme committed Jan 22, 2016
2 parents d625f28 + c1f934f commit f8d7831
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 231 deletions.
1 change: 0 additions & 1 deletion lib/definitions/platform.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ interface IPlatformData {
configurationFileName?: string;
configurationFilePath?: string;
relativeToFrameworkConfigurationFilePath: string;
mergeXmlConfig?: any[];
fastLivesyncFileExtensions: string[];
}

Expand Down
16 changes: 13 additions & 3 deletions lib/definitions/plugins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,20 @@ interface IPluginVariablesService {
/**
* Replaces all plugin variables with their corresponding values.
* @param {IPluginData} pluginData for the plugin.
* @param {pluginConfigurationFileContent} pluginConfigurationFileContent for the plugin.
* @return {IFuture<string>} returns the changed plugin configuration file content.
* @param {pluginConfigurationFilePath} pluginConfigurationFilePath for the plugin.
* @return {IFuture<void>}
*/
interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFilePath: string): IFuture<void>;
/**
* Replaces {nativescript.id} expression with the application identifier from package.json.
* @param {pluginConfigurationFilePath} pluginConfigurationFilePath for the plugin.
* @return {IFuture<void>}
*/
interpolateAppIdentifier(pluginConfigurationFilePath: string): IFuture<void>;
/**
* Replaces both plugin variables and appIdentifier
*/
interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFileContent: string): IFuture<string>;
interpolate(pluginData: IPluginData, pluginConfigurationFilePath: string): IFuture<void>;
/**
* Returns the
* @param {IPluginData} pluginData for the plugin.
Expand Down
17 changes: 11 additions & 6 deletions lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
$projectDataService: IProjectDataService,
private $sysInfo: ISysInfo,
private $mobileHelper: Mobile.IMobileHelper,
private $injector: IInjector) {
private $injector: IInjector,
private $pluginVariablesService: IPluginVariablesService) {
super($fs, $projectData, $projectDataService);
this._androidProjectPropertiesManagers = Object.create(null);
}
Expand Down Expand Up @@ -56,7 +57,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
configurationFileName: "AndroidManifest.xml",
configurationFilePath: path.join(projectRoot, "src", "main", "AndroidManifest.xml"),
relativeToFrameworkConfigurationFilePath: path.join("src", "main", "AndroidManifest.xml"),
mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }, {"nodename": "application", "attrname": "*"}],
fastLivesyncFileExtensions: [".jpg", ".gif", ".png", ".bmp", ".webp"] // http://developer.android.com/guide/appendix/media-formats.html
};
}
Expand Down Expand Up @@ -268,27 +268,32 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
public preparePluginNativeCode(pluginData: IPluginData): IFuture<void> {
return (() => {
let pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME);
this.processResourcesFromPlugin(pluginData.name, pluginPlatformsFolderPath).wait();
this.processResourcesFromPlugin(pluginData, pluginPlatformsFolderPath).wait();
}).future<void>()();
}

public processConfigurationFilesFromAppResources(): IFuture<void> {
return Future.fromResult();
}

private processResourcesFromPlugin(pluginName: string, pluginPlatformsFolderPath: string): IFuture<void> {
private processResourcesFromPlugin(pluginData: IPluginData, pluginPlatformsFolderPath: string): IFuture<void> {
return (() => {
let configurationsDirectoryPath = path.join(this.platformData.projectRoot, "configurations");
this.$fs.ensureDirectoryExists(configurationsDirectoryPath).wait();

let pluginConfigurationDirectoryPath = path.join(configurationsDirectoryPath, pluginName);
let pluginConfigurationDirectoryPath = path.join(configurationsDirectoryPath, pluginData.name);
if (this.$fs.exists(pluginPlatformsFolderPath).wait()) {
this.$fs.ensureDirectoryExists(pluginConfigurationDirectoryPath).wait();

// Copy all resources from plugin
let resourcesDestinationDirectoryPath = path.join(this.platformData.projectRoot, "src", pluginName);
let resourcesDestinationDirectoryPath = path.join(this.platformData.projectRoot, "src", pluginData.name);
this.$fs.ensureDirectoryExists(resourcesDestinationDirectoryPath).wait();
shell.cp("-Rf", path.join(pluginPlatformsFolderPath, "*"), resourcesDestinationDirectoryPath);

let pluginConfigurationFilePath = path.join(resourcesDestinationDirectoryPath, this.platformData.configurationFileName);
if (this.$fs.exists(pluginConfigurationFilePath).wait()) {
this.$pluginVariablesService.interpolate(pluginData, pluginConfigurationFilePath).wait();
}
}

// Copy include.gradle file
Expand Down
16 changes: 12 additions & 4 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
private $config: IConfiguration,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $devicesService: Mobile.IDevicesService,
private $mobileHelper: Mobile.IMobileHelper) {
private $mobileHelper: Mobile.IMobileHelper,
private $pluginVariablesService: IPluginVariablesService) {
super($fs, $projectData, $projectDataService);
}

Expand Down Expand Up @@ -66,7 +67,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
configurationFileName: "Info.plist",
configurationFilePath: path.join(projectRoot, this.$projectData.projectName, this.$projectData.projectName+"-Info.plist"),
relativeToFrameworkConfigurationFilePath: path.join("__PROJECT_NAME__", "__PROJECT_NAME__-Info.plist"),
mergeXmlConfig: [{ "nodename": "plist", "attrname": "*" }, {"nodename": "dict", "attrname": "*"}],
fastLivesyncFileExtensions: [".tiff", ".tif", ".jpg", "jpeg", "gif", ".png", ".bmp", ".BMPf", ".ico", ".cur", ".xbm"] // https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/
};
}
Expand Down Expand Up @@ -392,6 +392,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
public processConfigurationFilesFromAppResources(): IFuture<void> {
return (() => {
this.mergeInfoPlists().wait();
_(this.getAllInstalledPlugins().wait())
.map(pluginData => this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.platformData.configurationFilePath).wait())
.value();
this.$pluginVariablesService.interpolateAppIdentifier(this.platformData.configurationFilePath).wait();
}).future<void>()();
}

Expand All @@ -401,7 +405,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
let infoPlistPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME, this.platformData.normalizedPlatformName, this.platformData.configurationFileName);

if (!this.$fs.exists(infoPlistPath).wait()) {
// The project is missing Info.plist, try to populate it from the project tempalte.
// The project is missing Info.plist, try to populate it from the project template.
let projectTemplateService: IProjectTemplatesService = this.$injector.resolve("projectTemplatesService");
let defaultTemplatePath = projectTemplateService.defaultTemplatePath.wait();
let templateInfoPlist = path.join(defaultTemplatePath, constants.APP_RESOURCES_FOLDER_NAME, this.$devicePlatformsConstants.iOS, this.platformData.configurationFileName);
Expand Down Expand Up @@ -434,7 +438,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
});
};

let allPlugins: IPluginData[] = (<IPluginsService>this.$injector.resolve("pluginsService")).getAllInstalledPlugins().wait();
let allPlugins = this.getAllInstalledPlugins().wait();
for (let plugin of allPlugins) {
let pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME), this.platformData.configurationFileName);
makePatch(pluginInfoPlistPath);
Expand Down Expand Up @@ -465,6 +469,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
}).future<void>()();
}

private getAllInstalledPlugins(): IFuture<IPluginData[]> {
return (<IPluginsService>this.$injector.resolve("pluginsService")).getAllInstalledPlugins();
}

private get projectPodFilePath(): string {
return path.join(this.platformData.projectRoot, "Podfile");
}
Expand Down
31 changes: 26 additions & 5 deletions lib/services/plugin-variables-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export class PluginVariablesService implements IPluginVariablesService {
private $pluginVariablesHelper: IPluginVariablesHelper,
private $projectData: IProjectData,
private $projectDataService: IProjectDataService,
private $prompter: IPrompter) { }
private $prompter: IPrompter,
private $fs: IFileSystem) { }

public getPluginVariablePropertyName(pluginData: IPluginData): string {
return `${pluginData.name}-${PluginVariablesService.PLUGIN_VARIABLES_KEY}`;
Expand Down Expand Up @@ -38,15 +39,35 @@ export class PluginVariablesService implements IPluginVariablesService {
return this.$projectDataService.removeProperty(this.getPluginVariablePropertyName(pluginData));
}

public interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFileContent: string): IFuture<string> {
public interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFilePath: string): IFuture<void> {
return (() => {
let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait();
this.executeForAllPluginVariables(pluginData, (pluginVariableData: IPluginVariableData) =>
(() => {
this.ensurePluginVariableValue(pluginVariableData.value, `Unable to find the value for ${pluginVariableData.name} plugin variable into project package.json file. Verify that your package.json file is correct and try again.`);
pluginConfigurationFileContent = pluginConfigurationFileContent.replace(new RegExp(`{${pluginVariableData.name}}`, "gi"), pluginVariableData.value);
pluginConfigurationFileContent = this.interpolateCore(pluginVariableData.name, pluginVariableData.value, pluginConfigurationFileContent);
}).future<void>()()).wait();
return pluginConfigurationFileContent;
}).future<string>()();
this.$fs.writeFile(pluginConfigurationFilePath, pluginConfigurationFileContent).wait();
}).future<void>()();
}

public interpolateAppIdentifier(pluginConfigurationFilePath: string): IFuture<void> {
return (() => {
let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait();
let newContent = this.interpolateCore("nativescript.id", this.$projectData.projectId, pluginConfigurationFileContent);
this.$fs.writeFile(pluginConfigurationFilePath, newContent).wait();
}).future<void>()();
}

public interpolate(pluginData: IPluginData, pluginConfigurationFilePath: string): IFuture<void> {
return (() => {
this.interpolatePluginVariables(pluginData, pluginConfigurationFilePath).wait();
this.interpolateAppIdentifier(pluginConfigurationFilePath).wait();
}).future<void>()();
}

private interpolateCore(name: string, value: string, content: string): string {
return content.replace(new RegExp(`{${name}}`, "gi"), value);
}

private ensurePluginVariableValue(pluginVariableValue: string, errorMessage: string): void {
Expand Down
110 changes: 0 additions & 110 deletions lib/services/plugins-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import * as path from "path";
import * as shelljs from "shelljs";
import * as semver from "semver";
import Future = require("fibers/future");
import * as constants from "../constants";
let xmlmerge = require("xmlmerge-js");
let DOMParser = require('xmldom').DOMParser;

export class PluginsService implements IPluginsService {
private static INSTALL_COMMAND_NAME = "install";
Expand Down Expand Up @@ -72,12 +69,6 @@ export class PluginsService implements IPluginsService {

platformData.platformProjectService.removePluginNativeCode(pluginData).wait();

// Remove the plugin and call merge for another plugins that have configuration file
let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData);
if(this.$fs.exists(pluginConfigurationFilePath).wait()) {
this.merge(pluginData, platformData, (data: IPluginData) => data.name !== pluginData.name).wait();
}

if(pluginData.pluginVariables) {
this.$pluginVariablesService.removePluginVariablesFromProjectFile(pluginData).wait();
}
Expand All @@ -104,24 +95,6 @@ export class PluginsService implements IPluginsService {
}).future<void>()();
}

private initializeConfigurationFileFromCache(platformData: IPlatformData): IFuture<void> {
return (() => {
this.$projectDataService.initialize(this.$projectData.projectDir);
let frameworkVersion = this.$projectDataService.getValue(platformData.frameworkPackageName).wait().version;

// We need to resolve this manager here due to some restrictions from npm api and in order to load PluginsService.NPM_CONFIG config
let npmInstallationManager: INpmInstallationManager = this.$injector.resolve("npmInstallationManager");
npmInstallationManager.addToCache(platformData.frameworkPackageName, frameworkVersion).wait();

let cachedPackagePath = npmInstallationManager.getCachedPackagePath(platformData.frameworkPackageName, frameworkVersion);
let cachedConfigurationFilePath = this.$options.baseConfig ? path.resolve(this.$options.baseConfig) : path.join(cachedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME, platformData.relativeToFrameworkConfigurationFilePath);
let cachedConfigurationFileContent = this.$fs.readText(cachedConfigurationFilePath).wait();
this.$fs.writeFile(platformData.configurationFilePath, cachedConfigurationFileContent).wait();

platformData.platformProjectService.interpolateConfigurationFile().wait();
}).future<void>()();
}

public prepare(dependencyData: IDependencyData, platform: string): IFuture<void> {
return (() => {
platform = platform.toLowerCase();
Expand All @@ -137,12 +110,6 @@ export class PluginsService implements IPluginsService {
this.$fs.ensureDirectoryExists(pluginDestinationPath).wait();
shelljs.cp("-Rf", pluginData.fullPath, pluginDestinationPath);

let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData);

if(this.$fs.exists(pluginConfigurationFilePath).wait()) {
this.merge(pluginData, platformData).wait();
}

this.$projectFilesManager.processPlatformSpecificFiles(pluginDestinationPath, platform).wait();

pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform);
Expand Down Expand Up @@ -290,83 +257,6 @@ export class PluginsService implements IPluginsService {
}).future<string>()();
}

private mergeXml(xml1: string, xml2: string, config: any[]): IFuture<string> {
let future = new Future<string>();

try {
xmlmerge.merge(xml1, xml2, config, (mergedXml: string) => {
future.return(mergedXml);
});
} catch(err) {
future.throw(err);
}

return future;
}

private validateXml(xml: string, xmlFilePath?: string): void {
let doc = new DOMParser({
locator: {},
errorHandler: (level: any, msg: string) => {
let errorMessage = xmlFilePath ? `Invalid xml file ${xmlFilePath}.` : `Invalid xml ${xml}.`;
this.$errors.fail(errorMessage + ` Additional technical information: ${msg}.` );
}
});
doc.parseFromString(xml, 'text/xml');
}

private mergeCore(pluginData: IPluginData, platformData: IPlatformData): IFuture<void> {
return (() => {
let pluginConfigurationFilePath = this.getPluginConfigurationFilePath(pluginData, platformData);
let configurationFilePath = platformData.configurationFilePath;

// Validate plugin configuration file
let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait();
pluginConfigurationFileContent = this.$pluginVariablesService.interpolatePluginVariables(pluginData, pluginConfigurationFileContent).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();
}).future<void>()();
}

private merge(pluginData: IPluginData, platformData: IPlatformData, mergeCondition?: (_pluginData: IPluginData) => boolean): IFuture<void> {
return (() => {
let tnsModulesDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME);
let nodeModules = this.$broccoliBuilder.getChangedNodeModules(tnsModulesDestinationPath, platformData.normalizedPlatformName.toLowerCase()).wait();

_(nodeModules)
.keys()
.filter(nodeModule => this.$fs.exists(path.join(nodeModule, "package.json")).wait())
.map(nodeModule => this.getNodeModuleData(path.join(nodeModule, "package.json")).wait())
.map(nodeModuleData => this.convertToPluginData(nodeModuleData))
.filter(data => data.isPlugin && this.$fs.exists(this.getPluginConfigurationFilePath(data, platformData)).wait())
.forEach((data, index) => {
if(index === 0) {
this.initializeConfigurationFileFromCache(platformData).wait();
}

if(!mergeCondition || (mergeCondition && mergeCondition(data))) {
this.mergeCore(data, platformData).wait();
}
})
.value();

}).future<void>()();
}

private getPluginConfigurationFilePath(pluginData: IPluginData, platformData: IPlatformData): string {
let pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(platformData.normalizedPlatformName.toLowerCase());
let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName);
return pluginConfigurationFilePath;
}

private isPluginDataValidForPlatform(pluginData: IPluginData, platform: string): IFuture<boolean> {
return (() => {
let isValid = true;
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@
"xcode": "https://github.com/NativeScript/node-xcode/archive/1.4.0.tar.gz",
"xmldom": "0.1.21",
"xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master",
"xmlmerge-js": "0.2.4",
"yargs": "3.15.0"
},
"analyze": true,
Expand Down
4 changes: 4 additions & 0 deletions test/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {LoggingLevels} from "../lib/common/mobile/logging-levels";
import {DeviceDiscovery} from "../lib/common/mobile/mobile-core/device-discovery";
import {IOSDeviceDiscovery} from "../lib/common/mobile/mobile-core/ios-device-discovery";
import {AndroidDeviceDiscovery} from "../lib/common/mobile/mobile-core/android-device-discovery";
import {PluginVariablesService} from "../lib/services/plugin-variables-service";
import {PluginVariablesHelper} from "../lib/common/plugin-variables-helper";
import {Utils} from "../lib/common/utils";
import { assert } from "chai";
import temp = require("temp");
Expand Down Expand Up @@ -73,6 +75,8 @@ function createTestInjector(projectPath: string, projectName: string): IInjector
testInjector.register("loggingLevels", LoggingLevels);
testInjector.register("utils", Utils);
testInjector.register("iTunesValidator", {});
testInjector.register("pluginVariablesService", PluginVariablesService);
testInjector.register("pluginVariablesHelper", PluginVariablesHelper);
return testInjector;
}

Expand Down
Loading

0 comments on commit f8d7831

Please sign in to comment.