| /** |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| /* |
| A class for holidng the information currently stored in plugin.xml |
| It should also be able to answer questions like whether the plugin |
| is compatible with a given engine version. |
| |
| TODO (kamrik): refactor this to not use sync functions and return promises. |
| */ |
| |
| var path = require('path'); |
| var fs = require('fs'); |
| var xml_helpers = require('../util/xml-helpers'); |
| var CordovaError = require('../CordovaError/CordovaError'); |
| |
| function PluginInfo (dirname) { |
| var self = this; |
| |
| // METHODS |
| // Defined inside the constructor to avoid the "this" binding problems. |
| |
| // <preference> tag |
| // Example: <preference name="API_KEY" /> |
| // Used to require a variable to be specified via --variable when installing the plugin. |
| // returns { key : default | null} |
| self.getPreferences = getPreferences; |
| function getPreferences (platform) { |
| return _getTags(self._et, 'preference', platform, _parsePreference) |
| .reduce(function (preferences, pref) { |
| preferences[pref.preference] = pref.default; |
| return preferences; |
| }, {}); |
| } |
| |
| function _parsePreference (prefTag) { |
| var name = prefTag.attrib.name.toUpperCase(); |
| var def = prefTag.attrib.default || null; |
| return {preference: name, default: def}; |
| } |
| |
| // <asset> |
| self.getAssets = getAssets; |
| function getAssets (platform) { |
| var assets = _getTags(self._et, 'asset', platform, _parseAsset); |
| return assets; |
| } |
| |
| function _parseAsset (tag) { |
| var src = tag.attrib.src; |
| var target = tag.attrib.target; |
| |
| if (!src || !target) { |
| var msg = |
| 'Malformed <asset> tag. Both "src" and "target" attributes' |
| + 'must be specified in\n' |
| + self.filepath |
| ; |
| throw new Error(msg); |
| } |
| |
| var asset = { |
| itemType: 'asset', |
| src: src, |
| target: target |
| }; |
| return asset; |
| } |
| |
| // <dependency> |
| // Example: |
| // <dependency id="com.plugin.id" |
| // url="https://github.com/myuser/someplugin" |
| // commit="428931ada3891801" |
| // subdir="some/path/here" /> |
| self.getDependencies = getDependencies; |
| function getDependencies (platform) { |
| var deps = _getTags( |
| self._et, |
| 'dependency', |
| platform, |
| _parseDependency |
| ); |
| return deps; |
| } |
| |
| function _parseDependency (tag) { |
| var dep = |
| { id: tag.attrib.id, |
| version: tag.attrib.version || '', |
| url: tag.attrib.url || '', |
| subdir: tag.attrib.subdir || '', |
| commit: tag.attrib.commit |
| }; |
| |
| dep.git_ref = dep.commit; |
| |
| if (!dep.id) { |
| var msg = |
| '<dependency> tag is missing id attribute in ' |
| + self.filepath |
| ; |
| throw new CordovaError(msg); |
| } |
| return dep; |
| } |
| |
| // <config-file> tag |
| self.getConfigFiles = getConfigFiles; |
| function getConfigFiles (platform) { |
| var configFiles = _getTags(self._et, 'config-file', platform, _parseConfigFile); |
| return configFiles; |
| } |
| |
| function _parseConfigFile (tag) { |
| var configFile = |
| { target: tag.attrib['target'], |
| parent: tag.attrib['parent'], |
| after: tag.attrib['after'], |
| xmls: tag.getchildren(), |
| // To support demuxing via versions |
| versions: tag.attrib['versions'], |
| deviceTarget: tag.attrib['device-target'] |
| }; |
| return configFile; |
| } |
| |
| self.getEditConfigs = getEditConfigs; |
| function getEditConfigs (platform) { |
| var editConfigs = _getTags(self._et, 'edit-config', platform, _parseEditConfigs); |
| return editConfigs; |
| } |
| |
| function _parseEditConfigs (tag) { |
| var editConfig = |
| { file: tag.attrib['file'], |
| target: tag.attrib['target'], |
| mode: tag.attrib['mode'], |
| xmls: tag.getchildren() |
| }; |
| return editConfig; |
| } |
| |
| // <info> tags, both global and within a <platform> |
| // TODO (kamrik): Do we ever use <info> under <platform>? Example wanted. |
| self.getInfo = getInfo; |
| function getInfo (platform) { |
| var infos = _getTags( |
| self._et, |
| 'info', |
| platform, |
| function (elem) { return elem.text; } |
| ); |
| // Filter out any undefined or empty strings. |
| infos = infos.filter(Boolean); |
| return infos; |
| } |
| |
| // <source-file> |
| // Examples: |
| // <source-file src="src/ios/someLib.a" framework="true" /> |
| // <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" /> |
| self.getSourceFiles = getSourceFiles; |
| function getSourceFiles (platform) { |
| var sourceFiles = _getTagsInPlatform(self._et, 'source-file', platform, _parseSourceFile); |
| return sourceFiles; |
| } |
| |
| function _parseSourceFile (tag) { |
| return { |
| itemType: 'source-file', |
| src: tag.attrib.src, |
| framework: isStrTrue(tag.attrib.framework), |
| weak: isStrTrue(tag.attrib.weak), |
| compilerFlags: tag.attrib['compiler-flags'], |
| targetDir: tag.attrib['target-dir'] |
| }; |
| } |
| |
| // <header-file> |
| // Example: |
| // <header-file src="CDVFoo.h" /> |
| self.getHeaderFiles = getHeaderFiles; |
| function getHeaderFiles (platform) { |
| var headerFiles = _getTagsInPlatform(self._et, 'header-file', platform, function (tag) { |
| return { |
| itemType: 'header-file', |
| src: tag.attrib.src, |
| targetDir: tag.attrib['target-dir'] |
| }; |
| }); |
| return headerFiles; |
| } |
| |
| // <resource-file> |
| // Example: |
| // <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" device-target="win" arch="x86" versions=">=8.1" /> |
| self.getResourceFiles = getResourceFiles; |
| function getResourceFiles (platform) { |
| var resourceFiles = _getTagsInPlatform(self._et, 'resource-file', platform, function (tag) { |
| return { |
| itemType: 'resource-file', |
| src: tag.attrib.src, |
| target: tag.attrib.target, |
| versions: tag.attrib.versions, |
| deviceTarget: tag.attrib['device-target'], |
| arch: tag.attrib.arch, |
| reference: tag.attrib.reference |
| }; |
| }); |
| return resourceFiles; |
| } |
| |
| // <lib-file> |
| // Example: |
| // <lib-file src="src/BlackBerry10/native/device/libfoo.so" arch="device" /> |
| self.getLibFiles = getLibFiles; |
| function getLibFiles (platform) { |
| var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, function (tag) { |
| return { |
| itemType: 'lib-file', |
| src: tag.attrib.src, |
| arch: tag.attrib.arch, |
| Include: tag.attrib.Include, |
| versions: tag.attrib.versions, |
| deviceTarget: tag.attrib['device-target'] || tag.attrib.target |
| }; |
| }); |
| return libFiles; |
| } |
| |
| // <hook> |
| // Example: |
| // <hook type="before_build" src="scripts/beforeBuild.js" /> |
| self.getHookScripts = getHookScripts; |
| function getHookScripts (hook, platforms) { |
| var scriptElements = self._et.findall('./hook'); |
| |
| if (platforms) { |
| platforms.forEach(function (platform) { |
| scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/hook')); |
| }); |
| } |
| |
| function filterScriptByHookType (el) { |
| return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook; |
| } |
| |
| return scriptElements.filter(filterScriptByHookType); |
| } |
| |
| self.getJsModules = getJsModules; |
| function getJsModules (platform) { |
| var modules = _getTags(self._et, 'js-module', platform, _parseJsModule); |
| return modules; |
| } |
| |
| function _parseJsModule (tag) { |
| var ret = { |
| itemType: 'js-module', |
| name: tag.attrib.name, |
| src: tag.attrib.src, |
| clobbers: tag.findall('clobbers').map(function (tag) { return { target: tag.attrib.target }; }), |
| merges: tag.findall('merges').map(function (tag) { return { target: tag.attrib.target }; }), |
| runs: tag.findall('runs').length > 0 |
| }; |
| |
| return ret; |
| } |
| |
| self.getEngines = function () { |
| return self._et.findall('engines/engine').map(function (n) { |
| return { |
| name: n.attrib.name, |
| version: n.attrib.version, |
| platform: n.attrib.platform, |
| scriptSrc: n.attrib.scriptSrc |
| }; |
| }); |
| }; |
| |
| self.getPlatforms = function () { |
| return self._et.findall('platform').map(function (n) { |
| return { name: n.attrib.name }; |
| }); |
| }; |
| |
| self.getPlatformsArray = function () { |
| return self._et.findall('platform').map(function (n) { |
| return n.attrib.name; |
| }); |
| }; |
| |
| self.getFrameworks = function (platform, options) { |
| return _getTags(self._et, 'framework', platform, function (el) { |
| var src = el.attrib.src; |
| if (options) { |
| var vars = options.cli_variables || {}; |
| |
| if (Object.keys(vars).length === 0) { |
| // get variable defaults from plugin.xml for removal |
| vars = self.getPreferences(platform); |
| } |
| var regExp; |
| // Iterate over plugin variables. |
| // Replace them in framework src if they exist |
| Object.keys(vars).forEach(function (name) { |
| if (vars[name]) { |
| regExp = new RegExp('\\$' + name, 'g'); |
| src = src.replace(regExp, vars[name]); |
| } |
| }); |
| } |
| var ret = { |
| itemType: 'framework', |
| type: el.attrib.type, |
| parent: el.attrib.parent, |
| custom: isStrTrue(el.attrib.custom), |
| embed: isStrTrue(el.attrib.embed), |
| src: src, |
| spec: el.attrib.spec, |
| weak: isStrTrue(el.attrib.weak), |
| versions: el.attrib.versions, |
| targetDir: el.attrib['target-dir'], |
| deviceTarget: el.attrib['device-target'] || el.attrib.target, |
| arch: el.attrib.arch, |
| implementation: el.attrib.implementation |
| }; |
| return ret; |
| }); |
| }; |
| |
| self.getFilesAndFrameworks = getFilesAndFrameworks; |
| function getFilesAndFrameworks (platform, options) { |
| // Please avoid changing the order of the calls below, files will be |
| // installed in this order. |
| var items = [].concat( |
| self.getSourceFiles(platform), |
| self.getHeaderFiles(platform), |
| self.getResourceFiles(platform), |
| self.getFrameworks(platform, options), |
| self.getLibFiles(platform) |
| ); |
| return items; |
| } |
| /// // End of PluginInfo methods ///// |
| |
| /// // PluginInfo Constructor logic ///// |
| self.filepath = path.join(dirname, 'plugin.xml'); |
| if (!fs.existsSync(self.filepath)) { |
| throw new CordovaError('Cannot find plugin.xml for plugin "' + path.basename(dirname) + '". Please try adding it again.'); |
| } |
| |
| self.dir = dirname; |
| var et = self._et = xml_helpers.parseElementtreeSync(self.filepath); |
| var pelem = et.getroot(); |
| self.id = pelem.attrib.id; |
| self.version = pelem.attrib.version; |
| |
| // Optional fields |
| self.name = pelem.findtext('name'); |
| self.description = pelem.findtext('description'); |
| self.license = pelem.findtext('license'); |
| self.repo = pelem.findtext('repo'); |
| self.issue = pelem.findtext('issue'); |
| self.keywords = pelem.findtext('keywords'); |
| self.info = pelem.findtext('info'); |
| if (self.keywords) { |
| self.keywords = self.keywords.split(',').map(function (s) { return s.trim(); }); |
| } |
| self.getKeywordsAndPlatforms = function () { |
| var ret = self.keywords || []; |
| return ret.concat('ecosystem:cordova').concat(addCordova(self.getPlatformsArray())); |
| }; |
| } // End of PluginInfo constructor. |
| |
| // Helper function used to prefix every element of an array with cordova- |
| // Useful when we want to modify platforms to be cordova-platform |
| function addCordova (someArray) { |
| var newArray = someArray.map(function (element) { |
| return 'cordova-' + element; |
| }); |
| return newArray; |
| } |
| |
| // Helper function used by most of the getSomething methods of PluginInfo. |
| // Get all elements of a given name. Both in root and in platform sections |
| // for the given platform. If transform is given and is a function, it is |
| // applied to each element. |
| function _getTags (pelem, tag, platform, transform) { |
| var platformTag = pelem.find('./platform[@name="' + platform + '"]'); |
| var tagsInRoot = pelem.findall(tag); |
| tagsInRoot = tagsInRoot || []; |
| var tagsInPlatform = platformTag ? platformTag.findall(tag) : []; |
| var tags = tagsInRoot.concat(tagsInPlatform); |
| if (typeof transform === 'function') { |
| tags = tags.map(transform); |
| } |
| return tags; |
| } |
| |
| // Same as _getTags() but only looks inside a platform section. |
| function _getTagsInPlatform (pelem, tag, platform, transform) { |
| var platformTag = pelem.find('./platform[@name="' + platform + '"]'); |
| var tags = platformTag ? platformTag.findall(tag) : []; |
| if (typeof transform === 'function') { |
| tags = tags.map(transform); |
| } |
| return tags; |
| } |
| |
| // Check if x is a string 'true'. |
| function isStrTrue (x) { |
| return String(x).toLowerCase() === 'true'; |
| } |
| |
| module.exports = PluginInfo; |
| // Backwards compat: |
| PluginInfo.PluginInfo = PluginInfo; |
| PluginInfo.loadPluginsDir = function (dir) { |
| var PluginInfoProvider = require('./PluginInfoProvider'); |
| return new PluginInfoProvider().getAllWithinSearchPath(dir); |
| }; |