UNPKG

15.1 kBJavaScriptView Raw
1/**
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18*/
19
20/*
21A class for holidng the information currently stored in plugin.xml
22It should also be able to answer questions like whether the plugin
23is compatible with a given engine version.
24
25TODO (kamrik): refactor this to not use sync functions and return promises.
26*/
27
28var path = require('path');
29var fs = require('fs');
30var xml_helpers = require('../util/xml-helpers');
31var CordovaError = require('../CordovaError/CordovaError');
32
33function PluginInfo (dirname) {
34 var self = this;
35
36 // METHODS
37 // Defined inside the constructor to avoid the "this" binding problems.
38
39 // <preference> tag
40 // Example: <preference name="API_KEY" />
41 // Used to require a variable to be specified via --variable when installing the plugin.
42 // returns { key : default | null}
43 self.getPreferences = getPreferences;
44 function getPreferences (platform) {
45 return _getTags(self._et, 'preference', platform, _parsePreference)
46 .reduce(function (preferences, pref) {
47 preferences[pref.preference] = pref.default;
48 return preferences;
49 }, {});
50 }
51
52 function _parsePreference (prefTag) {
53 var name = prefTag.attrib.name.toUpperCase();
54 var def = prefTag.attrib.default || null;
55 return {preference: name, default: def};
56 }
57
58 // <asset>
59 self.getAssets = getAssets;
60 function getAssets (platform) {
61 var assets = _getTags(self._et, 'asset', platform, _parseAsset);
62 return assets;
63 }
64
65 function _parseAsset (tag) {
66 var src = tag.attrib.src;
67 var target = tag.attrib.target;
68
69 if (!src || !target) {
70 var msg =
71 'Malformed <asset> tag. Both "src" and "target" attributes'
72 + 'must be specified in\n'
73 + self.filepath
74 ;
75 throw new Error(msg);
76 }
77
78 var asset = {
79 itemType: 'asset',
80 src: src,
81 target: target
82 };
83 return asset;
84 }
85
86 // <dependency>
87 // Example:
88 // <dependency id="com.plugin.id"
89 // url="https://github.com/myuser/someplugin"
90 // commit="428931ada3891801"
91 // subdir="some/path/here" />
92 self.getDependencies = getDependencies;
93 function getDependencies (platform) {
94 var deps = _getTags(
95 self._et,
96 'dependency',
97 platform,
98 _parseDependency
99 );
100 return deps;
101 }
102
103 function _parseDependency (tag) {
104 var dep =
105 { id: tag.attrib.id,
106 version: tag.attrib.version || '',
107 url: tag.attrib.url || '',
108 subdir: tag.attrib.subdir || '',
109 commit: tag.attrib.commit
110 };
111
112 dep.git_ref = dep.commit;
113
114 if (!dep.id) {
115 var msg =
116 '<dependency> tag is missing id attribute in '
117 + self.filepath
118 ;
119 throw new CordovaError(msg);
120 }
121 return dep;
122 }
123
124 // <config-file> tag
125 self.getConfigFiles = getConfigFiles;
126 function getConfigFiles (platform) {
127 var configFiles = _getTags(self._et, 'config-file', platform, _parseConfigFile);
128 return configFiles;
129 }
130
131 function _parseConfigFile (tag) {
132 var configFile =
133 { target: tag.attrib['target'],
134 parent: tag.attrib['parent'],
135 after: tag.attrib['after'],
136 xmls: tag.getchildren(),
137 // To support demuxing via versions
138 versions: tag.attrib['versions'],
139 deviceTarget: tag.attrib['device-target']
140 };
141 return configFile;
142 }
143
144 self.getEditConfigs = getEditConfigs;
145 function getEditConfigs (platform) {
146 var editConfigs = _getTags(self._et, 'edit-config', platform, _parseEditConfigs);
147 return editConfigs;
148 }
149
150 function _parseEditConfigs (tag) {
151 var editConfig =
152 { file: tag.attrib['file'],
153 target: tag.attrib['target'],
154 mode: tag.attrib['mode'],
155 xmls: tag.getchildren()
156 };
157 return editConfig;
158 }
159
160 // <info> tags, both global and within a <platform>
161 // TODO (kamrik): Do we ever use <info> under <platform>? Example wanted.
162 self.getInfo = getInfo;
163 function getInfo (platform) {
164 var infos = _getTags(
165 self._et,
166 'info',
167 platform,
168 function (elem) { return elem.text; }
169 );
170 // Filter out any undefined or empty strings.
171 infos = infos.filter(Boolean);
172 return infos;
173 }
174
175 // <source-file>
176 // Examples:
177 // <source-file src="src/ios/someLib.a" framework="true" />
178 // <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" />
179 self.getSourceFiles = getSourceFiles;
180 function getSourceFiles (platform) {
181 var sourceFiles = _getTagsInPlatform(self._et, 'source-file', platform, _parseSourceFile);
182 return sourceFiles;
183 }
184
185 function _parseSourceFile (tag) {
186 return {
187 itemType: 'source-file',
188 src: tag.attrib.src,
189 framework: isStrTrue(tag.attrib.framework),
190 weak: isStrTrue(tag.attrib.weak),
191 compilerFlags: tag.attrib['compiler-flags'],
192 targetDir: tag.attrib['target-dir']
193 };
194 }
195
196 // <header-file>
197 // Example:
198 // <header-file src="CDVFoo.h" />
199 self.getHeaderFiles = getHeaderFiles;
200 function getHeaderFiles (platform) {
201 var headerFiles = _getTagsInPlatform(self._et, 'header-file', platform, function (tag) {
202 return {
203 itemType: 'header-file',
204 src: tag.attrib.src,
205 targetDir: tag.attrib['target-dir']
206 };
207 });
208 return headerFiles;
209 }
210
211 // <resource-file>
212 // Example:
213 // <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" device-target="win" arch="x86" versions="&gt;=8.1" />
214 self.getResourceFiles = getResourceFiles;
215 function getResourceFiles (platform) {
216 var resourceFiles = _getTagsInPlatform(self._et, 'resource-file', platform, function (tag) {
217 return {
218 itemType: 'resource-file',
219 src: tag.attrib.src,
220 target: tag.attrib.target,
221 versions: tag.attrib.versions,
222 deviceTarget: tag.attrib['device-target'],
223 arch: tag.attrib.arch,
224 reference: tag.attrib.reference
225 };
226 });
227 return resourceFiles;
228 }
229
230 // <lib-file>
231 // Example:
232 // <lib-file src="src/BlackBerry10/native/device/libfoo.so" arch="device" />
233 self.getLibFiles = getLibFiles;
234 function getLibFiles (platform) {
235 var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, function (tag) {
236 return {
237 itemType: 'lib-file',
238 src: tag.attrib.src,
239 arch: tag.attrib.arch,
240 Include: tag.attrib.Include,
241 versions: tag.attrib.versions,
242 deviceTarget: tag.attrib['device-target'] || tag.attrib.target
243 };
244 });
245 return libFiles;
246 }
247
248 // <hook>
249 // Example:
250 // <hook type="before_build" src="scripts/beforeBuild.js" />
251 self.getHookScripts = getHookScripts;
252 function getHookScripts (hook, platforms) {
253 var scriptElements = self._et.findall('./hook');
254
255 if (platforms) {
256 platforms.forEach(function (platform) {
257 scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/hook'));
258 });
259 }
260
261 function filterScriptByHookType (el) {
262 return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook;
263 }
264
265 return scriptElements.filter(filterScriptByHookType);
266 }
267
268 self.getJsModules = getJsModules;
269 function getJsModules (platform) {
270 var modules = _getTags(self._et, 'js-module', platform, _parseJsModule);
271 return modules;
272 }
273
274 function _parseJsModule (tag) {
275 var ret = {
276 itemType: 'js-module',
277 name: tag.attrib.name,
278 src: tag.attrib.src,
279 clobbers: tag.findall('clobbers').map(function (tag) { return { target: tag.attrib.target }; }),
280 merges: tag.findall('merges').map(function (tag) { return { target: tag.attrib.target }; }),
281 runs: tag.findall('runs').length > 0
282 };
283
284 return ret;
285 }
286
287 self.getEngines = function () {
288 return self._et.findall('engines/engine').map(function (n) {
289 return {
290 name: n.attrib.name,
291 version: n.attrib.version,
292 platform: n.attrib.platform,
293 scriptSrc: n.attrib.scriptSrc
294 };
295 });
296 };
297
298 self.getPlatforms = function () {
299 return self._et.findall('platform').map(function (n) {
300 return { name: n.attrib.name };
301 });
302 };
303
304 self.getPlatformsArray = function () {
305 return self._et.findall('platform').map(function (n) {
306 return n.attrib.name;
307 });
308 };
309
310 self.getFrameworks = function (platform, options) {
311 return _getTags(self._et, 'framework', platform, function (el) {
312 var src = el.attrib.src;
313 if (options) {
314 var vars = options.cli_variables || {};
315
316 if (Object.keys(vars).length === 0) {
317 // get variable defaults from plugin.xml for removal
318 vars = self.getPreferences(platform);
319 }
320 var regExp;
321 // Iterate over plugin variables.
322 // Replace them in framework src if they exist
323 Object.keys(vars).forEach(function (name) {
324 if (vars[name]) {
325 regExp = new RegExp('\\$' + name, 'g');
326 src = src.replace(regExp, vars[name]);
327 }
328 });
329 }
330 var ret = {
331 itemType: 'framework',
332 type: el.attrib.type,
333 parent: el.attrib.parent,
334 custom: isStrTrue(el.attrib.custom),
335 embed: isStrTrue(el.attrib.embed),
336 src: src,
337 spec: el.attrib.spec,
338 weak: isStrTrue(el.attrib.weak),
339 versions: el.attrib.versions,
340 targetDir: el.attrib['target-dir'],
341 deviceTarget: el.attrib['device-target'] || el.attrib.target,
342 arch: el.attrib.arch,
343 implementation: el.attrib.implementation
344 };
345 return ret;
346 });
347 };
348
349 self.getFilesAndFrameworks = getFilesAndFrameworks;
350 function getFilesAndFrameworks (platform, options) {
351 // Please avoid changing the order of the calls below, files will be
352 // installed in this order.
353 var items = [].concat(
354 self.getSourceFiles(platform),
355 self.getHeaderFiles(platform),
356 self.getResourceFiles(platform),
357 self.getFrameworks(platform, options),
358 self.getLibFiles(platform)
359 );
360 return items;
361 }
362 /// // End of PluginInfo methods /////
363
364 /// // PluginInfo Constructor logic /////
365 self.filepath = path.join(dirname, 'plugin.xml');
366 if (!fs.existsSync(self.filepath)) {
367 throw new CordovaError('Cannot find plugin.xml for plugin "' + path.basename(dirname) + '". Please try adding it again.');
368 }
369
370 self.dir = dirname;
371 var et = self._et = xml_helpers.parseElementtreeSync(self.filepath);
372 var pelem = et.getroot();
373 self.id = pelem.attrib.id;
374 self.version = pelem.attrib.version;
375
376 // Optional fields
377 self.name = pelem.findtext('name');
378 self.description = pelem.findtext('description');
379 self.license = pelem.findtext('license');
380 self.repo = pelem.findtext('repo');
381 self.issue = pelem.findtext('issue');
382 self.keywords = pelem.findtext('keywords');
383 self.info = pelem.findtext('info');
384 if (self.keywords) {
385 self.keywords = self.keywords.split(',').map(function (s) { return s.trim(); });
386 }
387 self.getKeywordsAndPlatforms = function () {
388 var ret = self.keywords || [];
389 return ret.concat('ecosystem:cordova').concat(addCordova(self.getPlatformsArray()));
390 };
391} // End of PluginInfo constructor.
392
393// Helper function used to prefix every element of an array with cordova-
394// Useful when we want to modify platforms to be cordova-platform
395function addCordova (someArray) {
396 var newArray = someArray.map(function (element) {
397 return 'cordova-' + element;
398 });
399 return newArray;
400}
401
402// Helper function used by most of the getSomething methods of PluginInfo.
403// Get all elements of a given name. Both in root and in platform sections
404// for the given platform. If transform is given and is a function, it is
405// applied to each element.
406function _getTags (pelem, tag, platform, transform) {
407 var platformTag = pelem.find('./platform[@name="' + platform + '"]');
408 var tagsInRoot = pelem.findall(tag);
409 tagsInRoot = tagsInRoot || [];
410 var tagsInPlatform = platformTag ? platformTag.findall(tag) : [];
411 var tags = tagsInRoot.concat(tagsInPlatform);
412 if (typeof transform === 'function') {
413 tags = tags.map(transform);
414 }
415 return tags;
416}
417
418// Same as _getTags() but only looks inside a platform section.
419function _getTagsInPlatform (pelem, tag, platform, transform) {
420 var platformTag = pelem.find('./platform[@name="' + platform + '"]');
421 var tags = platformTag ? platformTag.findall(tag) : [];
422 if (typeof transform === 'function') {
423 tags = tags.map(transform);
424 }
425 return tags;
426}
427
428// Check if x is a string 'true'.
429function isStrTrue (x) {
430 return String(x).toLowerCase() === 'true';
431}
432
433module.exports = PluginInfo;
434// Backwards compat:
435PluginInfo.PluginInfo = PluginInfo;
436PluginInfo.loadPluginsDir = function (dir) {
437 var PluginInfoProvider = require('./PluginInfoProvider');
438 return new PluginInfoProvider().getAllWithinSearchPath(dir);
439};