/** 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-extra'); 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. // tag // Example: // 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 }; } // 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 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; } // // Example: // 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 = ' tag is missing id attribute in ' + self.filepath ; throw new CordovaError(msg); } return dep; } // 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; } // tags, both global and within a // TODO (kamrik): Do we ever use under ? 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; } // // Examples: // // 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'] }; } // // Example: // 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'], type: tag.attrib['type'] }; }); return headerFiles; } // // Example: // 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; } // // Example: // 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; } // // Example: // // // // // // // // // // // // // // self.getPodSpecs = getPodSpecs; function getPodSpecs (platform) { var podSpecs = _getTagsInPlatform(self._et, 'podspec', platform, function (tag) { var declarations = null; var sources = null; var libraries = null; var config = tag.find('config'); var pods = tag.find('pods'); if (config != null) { sources = config.findall('source').map(function (t) { return { url: t.attrib.url }; }).reduce(function (acc, val) { return Object.assign({}, acc, { [val.url]: { source: val.url } }); }, {}); } if (pods != null) { declarations = Object.keys(pods.attrib).reduce(function (acc, key) { return pods.attrib[key] === undefined ? acc : Object.assign({}, acc, { [key]: pods.attrib[key] }); }, {}); libraries = pods.findall('pod').map(function (t) { return Object.keys(t.attrib).reduce(function (acc, key) { return t.attrib[key] === undefined ? acc : Object.assign({}, acc, { [key]: t.attrib[key] }); }, {}); }).reduce(function (acc, val) { return Object.assign({}, acc, { [val.name]: val }); }, {}); } return { declarations: declarations, sources: sources, libraries: libraries }; }); return podSpecs; } // // Example: // 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); };