Repository: cordova-lib Updated Branches: refs/heads/master 812373df4 -> 6b98390ec
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js ---------------------------------------------------------------------- diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js index bb56cda..125e3c5 100644 --- a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js +++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js @@ -1,3 +1,364 @@ -module.exports = function PlatformApi (argument) { - this.platform = 'windows'; +/** + 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. +*/ + +var path = require('path'); +var events = require('cordova-common').events; +var JsprojManager = require('./lib/JsprojManager'); +var PluginManager = require('cordova-common').PluginManager; +var CordovaLogger = require('cordova-common').CordovaLogger; +var PlatformMunger = require('./lib/ConfigChanges.js').PlatformMunger; +var PlatformJson = require('cordova-common').PlatformJson; +var PluginInfo = require('./lib/PluginInfo').PluginInfo; +var PluginInfoProvider = require('cordova-common').PluginInfoProvider; + +var PLATFORM = 'windows'; + +function setupEvents(externalEventEmitter) { + if (externalEventEmitter) { + // This will make the platform internal events visible outside + events.forwardEventsTo(externalEventEmitter); + return; + } + + // There is no logger if external emitter is not present, + // so attach a console logger + CordovaLogger.get().subscribe(events); +} + +/** + * Class, that acts as abstraction over particular platform. Encapsulates the + * platform's properties and methods. + * + * Platform that implements own PlatformApi instance _should implement all + * prototype methods_ of this class to be fully compatible with cordova-lib. + * + * The PlatformApi instance also should define the following field: + * + * * platform: String that defines a platform name. + */ +function Api(platform, platformRootDir, eventEmitter) { + this.platform = PLATFORM; + this.root = path.resolve(__dirname, '..'); + + setupEvents(eventEmitter); + + var self = this; + this.locations = { + root: self.root, + www: path.join(self.root, 'www'), + platformWww: path.join(self.root, 'platform_www'), + configXml: path.join(self.root, 'config.xml'), + defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'), + // NOTE: Due to platformApi spec we need to return relative paths here + cordovaJs: 'template/www/cordova.js', + cordovaJsSrc: 'cordova-js-src' + }; +} + +/** + * Installs platform to specified directory and creates a platform project. + * + * @param {String} destinationDir A directory, where platform should be + * created/installed. + * @param {ConfigParser} [projectConfig] A ConfigParser instance, used to get + * some application properties for new platform like application name, package + * id, etc. If not defined, this means that platform is used as standalone + * project and is not a part of cordova project, so platform will use some + * default values. + * @param {Object} [options] An options object. The most common options are: + * @param {String} [options.customTemplate] A path to custom template, that + * should override the default one from platform. + * @param {Boolean} [options.link=false] Flag that indicates that platform's + * sources will be linked to installed platform instead of copying. + * + * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi + * instance or rejected with CordovaError. + */ +Api.createPlatform = function (destinationDir, projectConfig, options, events) { + setupEvents(events); + var result; + + try { + result = require('../../bin/lib/create') + .create(destinationDir, projectConfig, options) + .then(function () { + var PlatformApi = require(path.resolve(destinationDir, 'cordova/Api')); + return new PlatformApi(PLATFORM, destinationDir, events); + }); + } + catch(e) { + events.emit('error','createPlatform is not callable from the windows project API.'); + throw(e); + } + + return result; }; + +/** + * Updates already installed platform. + * + * @param {String} destinationDir A directory, where existing platform + * installed, that should be updated. + * @param {Object} [options] An options object. The most common options are: + * @param {String} [options.customTemplate] A path to custom template, that + * should override the default one from platform. + * @param {Boolean} [options.link=false] Flag that indicates that platform's sources + * will be linked to installed platform instead of copying. + * @param {EventEmitter} [events] The emitter that will be used for logging + * + * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi + * instance or rejected with CordovaError. + */ +Api.updatePlatform = function (destinationDir, options, events) { + setupEvents(events); + try { + return require('../../bin/lib/update') + .update(destinationDir, options) + .then(function () { + var PlatformApi = require(path.resolve(destinationDir, 'cordova/Api')); + return new PlatformApi(PLATFORM, destinationDir, events); + }); + } + catch(e) { + events.emit('error','updatePlatform is not callable from the windows project API.'); + throw(e); + } +}; + +/** + * Gets a CordovaPlatform object, that represents the platform structure. + * + * @return {CordovaPlatform} A structure that contains the description of + * platform's file structure and other properties of platform. + */ +Api.prototype.getPlatformInfo = function () { + + var result = {}; + result.locations = this.locations; + result.root = this.root; + result.name = this.platform; + result.version = require('./version'); + result.projectConfig = this._config; + + return result; +}; + +/** + * Updates installed platform with provided www assets and new app + * configuration. This method is required for CLI workflow and will be called + * each time before build, so the changes, made to app configuration and www + * code, will be applied to platform. + * + * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a + * project structure and configuration, that should be applied to platform + * (contains project's www location and ConfigParser instance for project's + * config). + * + * @return {Promise} Return a promise either fulfilled, or rejected with + * CordovaError instance. + */ +Api.prototype.prepare = function (cordovaProject, prepareOptions) { + return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions); +}; + +/** + * Installs a new plugin into platform. This method only copies non-www files + * (sources, libs, etc.) to platform. It also doesn't resolves the + * dependencies of plugin. Both of handling of www files, such as assets and + * js-files and resolving dependencies are the responsibility of caller. + * + * @param {PluginInfo} plugin A PluginInfo instance that represents plugin + * that will be installed. + * @param {Object} installOptions An options object. Possible options below: + * @param {Boolean} installOptions.link: Flag that specifies that plugin + * sources will be symlinked to app's directory instead of copying (if + * possible). + * @param {Object} installOptions.variables An object that represents + * variables that will be used to install plugin. See more details on plugin + * variables in documentation: + * https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html + * + * @return {Promise} Return a promise either fulfilled, or rejected with + * CordovaError instance. + */ +Api.prototype.addPlugin = function (plugin, installOptions) { + + var self = this; + + // We need to use custom PluginInfo to trigger windows-specific processing + // of changes in .appxmanifest files. See PluginInfo.js for details + var pluginInfo = new PluginInfo(plugin.dir); + var jsProject = JsprojManager.getProject(this.root); + installOptions = installOptions || {}; + installOptions.variables = installOptions.variables || {}; + // Add PACKAGE_NAME variable into vars + if (!installOptions.variables.PACKAGE_NAME) { + installOptions.variables.PACKAGE_NAME = jsProject.getPackageName(); + } + + var platformJson = PlatformJson.load(this.root, this.platform); + var pluginManager = PluginManager.get(this.platform, this.locations, jsProject); + pluginManager.munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider()); + return pluginManager + .addPlugin(pluginInfo, installOptions) + .then(function () { + // CB-11657 Add BOM to www files here because files added by plugin + // probably don't have it. Prepare would add BOM but it might not be called + return require('./lib/prepare').addBOMSignature(self.locations.www); + }) + // CB-11022 return non-falsy value to indicate + // that there is no need to run prepare after + .thenResolve(true); +}; + +/** + * Removes an installed plugin from platform. + * + * Since method accepts PluginInfo instance as input parameter instead of plugin + * id, caller shoud take care of managing/storing PluginInfo instances for + * future uninstalls. + * + * @param {PluginInfo} plugin A PluginInfo instance that represents plugin + * that will be installed. + * + * @return {Promise} Return a promise either fulfilled, or rejected with + * CordovaError instance. + */ +Api.prototype.removePlugin = function (plugin, uninstallOptions) { + var self = this; + + // We need to use custom PluginInfo to trigger windows-specific processing + // of changes in .appxmanifest files. See PluginInfo.js for details + var pluginInfo = new PluginInfo(plugin.dir); + var jsProject = JsprojManager.getProject(this.root); + var platformJson = PlatformJson.load(this.root, this.platform); + var pluginManager = PluginManager.get(this.platform, this.locations, jsProject); + // CB-11933 We override this field by windows specific one because windows has special logic + // for appxmanifest's capabilities removal (see also https://issues.apache.org/jira/browse/CB-11066) + pluginManager.munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider()); + return pluginManager + .removePlugin(pluginInfo, uninstallOptions) + .then(function () { + // CB-11657 Add BOM to cordova_plugins, since it is was + // regenerated after plugin uninstallation and does not have BOM + return require('./lib/prepare').addBOMToFile(path.resolve(self.locations.www, 'cordova_plugins.js')); + }) + // CB-11022 return non-falsy value to indicate + // that there is no need to run prepare after + .thenResolve(true); +}; + +/** + * Builds an application package for current platform. + * + * @param {Object} buildOptions A build options. This object's structure is + * highly depends on platform's specific. The most common options are: + * @param {Boolean} buildOptions.debug Indicates that packages should be + * built with debug configuration. This is set to true by default unless the + * 'release' option is not specified. + * @param {Boolean} buildOptions.release Indicates that packages should be + * built with release configuration. If not set to true, debug configuration + * will be used. + * @param {Boolean} buildOptions.device Specifies that built app is intended + * to run on device + * @param {Boolean} buildOptions.emulator: Specifies that built app is + * intended to run on emulator + * @param {String} buildOptions.target Specifies the device id that will be + * used to run built application. + * @param {Boolean} buildOptions.nobuild Indicates that this should be a + * dry-run call, so no build artifacts will be produced. + * @param {String[]} buildOptions.archs Specifies chip architectures which + * app packages should be built for. List of valid architectures is depends on + * platform. + * @param {String} buildOptions.buildConfig The path to build configuration + * file. The format of this file is depends on platform. + * @param {String[]} buildOptions.argv Raw array of command-line arguments, + * passed to `build` command. The purpose of this property is to pass a + * platform-specific arguments, and eventually let platform define own + * arguments processing logic. + * + * @return {Promise<Object[]>} A promise either fulfilled with an array of build + * artifacts (application packages) if package was built successfully, + * or rejected with CordovaError. The resultant build artifact objects is not + * strictly typed and may conatin arbitrary set of fields as in sample below. + * + * { + * architecture: 'x86', + * buildType: 'debug', + * path: '/path/to/build', + * type: 'app' + * } + * + * The return value in most cases will contain only one item but in some cases + * there could be multiple items in output array, e.g. when multiple + * arhcitectures is specified. + */ +Api.prototype.build = function(buildOptions) { + // TODO: Should we run check_reqs first? Android does this, but Windows appears doesn't. + return require('./lib/build').run.call(this, buildOptions) + .then(function (result) { + // Wrap result into array according to PlatformApi spec + return [result]; + }); +}; + +/** + * Builds an application package for current platform and runs it on + * specified/default device. If no 'device'/'emulator'/'target' options are + * specified, then tries to run app on default device if connected, otherwise + * runs the app on emulator. + * + * @param {Object} runOptions An options object. The structure is the same + * as for build options. + * + * @return {Promise} A promise either fulfilled if package was built and ran + * successfully, or rejected with CordovaError. + */ +Api.prototype.run = function(runOptions) { + // TODO: Should we run check_reqs first? Android does this, but Windows appears doesn't. + return require('./lib/run').run.call(this, runOptions); +}; + +/** + * Cleans out the build artifacts from platform's directory. + * + * @return {Promise} Return a promise either fulfilled, or rejected with + * CordovaError. + */ +Api.prototype.clean = function(cleanOpts) { + var self = this; + return require('./lib/build').clean.call(this, cleanOpts) + .then(function () { + return require('./lib/prepare').clean.call(self, cleanOpts); + }); +}; + +/** + * Performs a requirements check for current platform. Each platform defines its + * own set of requirements, which should be resolved before platform can be + * built successfully. + * + * @return {Promise<Requirement[]>} Promise, resolved with set of Requirement + * objects for current platform. + */ +Api.prototype.requirements = function() { + return require('./lib/check_reqs').check_all(); +}; + +module.exports = Api; http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/platforms/platforms.spec.js ---------------------------------------------------------------------- diff --git a/spec/cordova/platforms/platforms.spec.js b/spec/cordova/platforms/platforms.spec.js index b57ff2d..7045ae7 100644 --- a/spec/cordova/platforms/platforms.spec.js +++ b/spec/cordova/platforms/platforms.spec.js @@ -74,8 +74,10 @@ describe('getPlatformApi method', function () { var platformApi = platforms.getPlatformApi('browser', 'PLATFORM_WOUT_API'); expect(platformApi).toEqual(jasmine.any(PlatformApiPoly)); expect(util.convertToRealPathSafe.calls.count()).toEqual(1); - expect(events.emit.calls.count()).toEqual(1); - expect(events.emit.calls.argsFor(0)[1]).toEqual('Failed to require PlatformApi instance for platform "browser". Using polyfill instead.'); + expect(events.emit.calls.count()).toEqual(3); + expect(events.emit.calls.argsFor(0)[1]).toContain('Unable to load PlatformApi from platform. Error: Cannot find module'); + expect(events.emit.calls.argsFor(1)[1]).toEqual('Platform not found or needs polyfill.'); + expect(events.emit.calls.argsFor(2)[1]).toEqual('Failed to require PlatformApi instance for platform "browser". Using polyfill instead.'); expect(util.isCordova.calls.count()).toEqual(0); expect(util.requireNoCache.calls.count()).toEqual(0); }); @@ -83,8 +85,8 @@ describe('getPlatformApi method', function () { it('should throw error if using deprecated platform', function () { try { platforms.getPlatformApi('android', path.join(CORDOVA_ROOT, 'platforms/android')); - } catch (error) { - expect(error.toString()).toContain('platform does not have Api.js'); + } catch(error) { + expect(error.toString()).toContain('Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to [email protected] or newer.'); } }); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/util.spec.js ---------------------------------------------------------------------- diff --git a/spec/cordova/util.spec.js b/spec/cordova/util.spec.js index 6c63421..5d4a2f6 100644 --- a/spec/cordova/util.spec.js +++ b/spec/cordova/util.spec.js @@ -309,5 +309,45 @@ describe('util module', function () { }); }); }); + + describe('getPlatformApiFunction', function() { + it('Test 027 : should throw error informing user to update platform', function() { + expect(function(){util.getPlatformApiFunction('some/path', 'android');}).toThrow(new Error + ('Uncaught, unspecified "error" event. ( Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to [email protected] or newer.)')); + }); + + it('Test 028 : should throw error if platform is not supported', function() { + spyOn(events, 'emit').and.returnValue(true); + expect(function(){util.getPlatformApiFunction('some/path', 'somePlatform');}).toThrow(); + expect(events.emit.calls.count()).toBe(2); + expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\''); + expect(events.emit.calls.argsFor(1)[1]).toBe('The platform "somePlatform" does not appear to be a valid cordova platform. It is missing API.js. somePlatform not supported.'); + }); + + it('Test 029 : should use polyfill if blackberry10, webos, ubuntu', function() { + spyOn(events, 'emit').and.returnValue(true); + util.getPlatformApiFunction('some/path', 'blackberry10'); + expect(events.emit.calls.count()).toBe(3); + expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\''); + expect(events.emit.calls.argsFor(1)[1]).toBe('Platform not found or needs polyfill.'); + expect(events.emit.calls.argsFor(2)[1]).toBe('Failed to require PlatformApi instance for platform "blackberry10". Using polyfill instead.'); + }); + + it('Test 030 : successfully find platform Api', function() { + spyOn(events, 'emit').and.returnValue(true); + var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman'); + util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'Api.js')), 'android'); + expect(events.emit.calls.count()).toBe(1); + expect(events.emit.calls.argsFor(0)[1]).toBe('PlatformApi successfully found for platform android'); + }); + + it('Test 031 : should inform user that entry point should be called Api.js', function() { + spyOn(events, 'emit').and.returnValue(true); + var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman'); + expect(function(){util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'clean')), 'android');}).toThrow(); + expect(events.emit.calls.count()).toBe(3); + expect(events.emit.calls.argsFor(0)[1]).toBe('File name should be called Api.js.'); + }); + }); }); }); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/src/cordova/util.js ---------------------------------------------------------------------- diff --git a/src/cordova/util.js b/src/cordova/util.js index e08fbd5..6bf8f82 100644 --- a/src/cordova/util.js +++ b/src/cordova/util.js @@ -459,49 +459,62 @@ function getLatestNpmVersion (module_name) { }); } -// Display deprecation message if platform is api compatible -function checkPlatformApiCompatible (platform) { - if (platforms[platform] && platforms[platform].apiCompatibleSince) { - events.emit('warn', ' Using this version of Cordova with older version of cordova-' + platform + - ' is deprecated. Upgrade to cordova-' + platform + '@' + - platforms[platform].apiCompatibleSince + ' or newer.'); - } -} - -// libdir should be path to API.js +// Takes a libDir (root of platform where pkgJson is expected) & a platform name. +// Platform is used if things go wrong, so we can use polyfill. +// Potential errors : path doesn't exist, module isn't found or can't load. +// Message prints if file not named Api.js or falls back to pollyfill. function getPlatformApiFunction (libDir, platform) { var PlatformApi; try { // First we need to find whether platform exposes its' API via js module // If it does, then we require and instantiate it. + // This will throw if package.json does not exist, or specify 'main'. var apiEntryPoint = require.resolve(libDir); - if (path.basename(apiEntryPoint) === 'Api.js') { + if (apiEntryPoint) { + if(path.basename(apiEntryPoint) !== 'Api.js') { + events.emit('verbose', 'File name should be called Api.js.'); + // Not an error, still load it ... + } PlatformApi = exports.requireNoCache(apiEntryPoint); - events.emit('verbose', 'PlatformApi successfully found for platform ' + platform); - } - // For versions which are not api compatible, require.resolve returns path to /bin/create ('main' field in package.json) - // We should display the same deprecation message as in the catch block - if (!PlatformApi && ['android', 'windows', 'ios', 'osx'].indexOf(platform) >= 0) { - checkPlatformApiCompatible(platform); + if (!PlatformApi.createPlatform) { + PlatformApi = null; + events.emit('error', 'Does not appear to implement platform Api.'); + } else { + events.emit('verbose', 'PlatformApi successfully found for platform ' + platform); + } + } + else { + events.emit('verbose', 'No Api.js entry point found.'); } - } catch (err) { + } + catch (err) { + // Emit the err, someone might care ... + events.emit('warn','Unable to load PlatformApi from platform. ' + err); // Check if platform already compatible w/ PlatformApi and show deprecation warning if not - if (err && err.code === 'MODULE_NOT_FOUND') { - checkPlatformApiCompatible(platform); + //checkPlatformApiCompatible(platform); + if (platforms[platform] && platforms[platform].apiCompatibleSince) { + events.emit('error', ' Using this version of Cordova with older version of cordova-' + platform + + ' is deprecated. Upgrade to cordova-' + platform + '@' + + platforms[platform].apiCompatibleSince + ' or newer.'); + } + else if (!platforms[platform]) { + // Throw error because polyfill doesn't support non core platforms + events.emit('error', 'The platform "' + platform + '" does not appear to be a valid cordova platform. It is missing API.js. '+ platform +' not supported.'); } else { - events.emit('verbose', 'Error: PlatformApi not loaded for platform.' + err); + events.emit('verbose', 'Platform not found or needs polyfill.'); } - } finally { + } - // here is no Api.js and no deprecation information hence - // the platform just does not expose Api and we will try polyfill if applicable - if (!PlatformApi && (platform === 'blackberry10' || platform === 'browser' || platform === 'ubuntu' || platform === 'webos')) { + if (!PlatformApi) { + // The platform just does not expose Api and we will try to polyfill it + var polyPlatforms = ['blackberry10','browser','ubuntu','webos']; + if( polyPlatforms.indexOf(platform) > -1) { events.emit('verbose', 'Failed to require PlatformApi instance for platform "' + platform + - '". Using polyfill instead.'); + '". Using polyfill instead.'); PlatformApi = require('../platforms/PlatformApiPoly.js'); - } else if (!PlatformApi) { - throw new Error('Your ' + platform + ' platform does not have Api.js'); // eslint-disable-line no-unsafe-finally + } else { + throw new Error('Your ' + platform + ' platform does not have Api.js'); } } return PlatformApi; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
