Return-Path: X-Original-To: apmail-cordova-dev-archive@www.apache.org Delivered-To: apmail-cordova-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D0121182CA for ; Wed, 2 Mar 2016 14:08:56 +0000 (UTC) Received: (qmail 53634 invoked by uid 500); 2 Mar 2016 14:08:56 -0000 Delivered-To: apmail-cordova-dev-archive@cordova.apache.org Received: (qmail 53588 invoked by uid 500); 2 Mar 2016 14:08:54 -0000 Mailing-List: contact dev-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cordova.apache.org Delivered-To: mailing list dev@cordova.apache.org Received: (qmail 53577 invoked by uid 99); 2 Mar 2016 14:08:54 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 02 Mar 2016 14:08:54 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 11721DFF12; Wed, 2 Mar 2016 14:08:54 +0000 (UTC) From: TimBarham To: dev@cordova.apache.org Reply-To: dev@cordova.apache.org References: In-Reply-To: Subject: [GitHub] cordova-lib pull request: New plugin version selection implementat... Content-Type: text/plain Message-Id: <20160302140854.11721DFF12@git1-us-west.apache.org> Date: Wed, 2 Mar 2016 14:08:54 +0000 (UTC) Github user TimBarham commented on a diff in the pull request: https://github.com/apache/cordova-lib/pull/363#discussion_r54726389 --- Diff: cordova-lib/src/cordova/plugin.js --- @@ -512,3 +543,219 @@ function versionString(version) { return null; } + +/** + * Gets the version of a plugin that should be fetched for a given project based + * on the plugin's engine information from NPM and the platforms/plugins installed + * in the project. The cordovaDependencies object in the package.json's engines + * entry takes the form of an object that maps plugin versions to a series of + * constraints and semver ranges. For example: + * + * { plugin-version: { constraint: semver-range, ...}, ...} + * + * Constraint can be a plugin, platform, or cordova version. Plugin-version + * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0) + * + * @param {string} projectRoot The path to the root directory of the project + * @param {object} pluginInfo The NPM info of the plugin be fetched (e.g. the + * result of calling `registry.info()`) + * @param {string} cordovaVersion The semver version of cordova-lib + * + * @return {Promise} A promise that will resolve to either a string + * if there is a version of the plugin that this + * project satisfies or null if there is not + */ +function getFetchVersion(projectRoot, pluginInfo, cordovaVersion) { + // Figure out the project requirements + if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) { + var pluginList = getInstalledPlugins(projectRoot); + var pluginMap = {}; + + pluginList.forEach(function(plugin) { + pluginMap[plugin.id] = plugin.version; + }); + + return cordova_util.getInstalledPlatformsWithVersions(projectRoot) + .then(function(platformVersions) { + return determinePluginVersionToFetch( + pluginInfo.versions, + pluginInfo.engines.cordovaDependencies, + pluginMap, + platformVersions, + cordovaVersion); + }); + } else { + // If we have no engine, we want to fall back to the default behavior + events.emit('verbose', 'No plugin engine info found or not using registry, falling back to latest or pinned version'); + return Q(null); + } +} + +function findVersion(versions, version) { + var cleanedVersion = semver.clean(version); + for(var i = 0; i < versions.length; i++) { + if(semver.clean(versions[i]) === cleanedVersion) { + return versions[i]; + } + } + return null; +} + +/* + * The engine entry maps plugin versions to constraints like so: + * { + * '1.0.0' : { 'cordova': '<5.0.0' }, + * '<2.0.0': { + * 'cordova': '>=5.0.0', + * 'cordova-ios': '~5.0.0', + * 'cordova-plugin-camera': '~5.0.0' + * }, + * '3.0.0' : { 'cordova-ios': '>5.0.0' } + * } + * + * See cordova-spec/plugin_fetch.spec.js for test cases and examples + */ +function determinePluginVersionToFetch(allVersions, engine, pluginMap, platformMap, cordovaVersion) { + // Filters out pre-release versions + var latest = semver.maxSatisfying(allVersions, '>=0.0.0'); + + var versions = []; + var upperBound = null; + var upperBoundRange = null; + + for(var version in engine) { + if(semver.valid(semver.clean(version)) && !semver.gt(version, latest)) { + versions.push(version); + } else { + // Check if this is an upperbound; validRange() handles whitespace + var cleanedRange = semver.validRange(version); + if(cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) { + // We only care about the highest upper bound that our project does not support + if(getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) { + var maxMatchingUpperBound = cleanedRange.substring(1); + if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) { + upperBound = maxMatchingUpperBound; + upperBoundRange = version; + } + } + } + } + } + + // Handle the lower end of versions by giving them a satisfied engine + if(!findVersion(versions, '0.0.0')) { + versions.push('0.0.0'); + engine['0.0.0'] = {}; + } + + // Add an entry after the upper bound to handle the versions above the + // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0 + // needs a 1.0.0 entry that has the same engine as 0.0.0 + if(upperBound && !findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) { + versions.push(upperBound); + var below = semver.maxSatisfying(versions, upperBoundRange); + + // Get the original entry without trimmed whitespace + below = below ? findVersion(versions, below) : null; + engine[upperBound] = below ? engine[below] : {}; + } + + // Sort in descending order; we want to start at latest and work back + versions.sort(semver.rcompare); + + for(var i = 0; i < versions.length; i++) { + if(upperBound && semver.lt(versions[i], upperBound)) { + // Because we sorted in desc. order, if the upper bound we found + // applies to this version (and thus the ones below) we can just + // quit + break; + } + + var range = i? ('>=' + versions[i] + ' <' + versions[i-1]) : ('>=' + versions[i]); + var maxMatchingVersion = semver.maxSatisfying(allVersions, range); + + if (maxMatchingVersion && getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) { + + // Because we sorted in descending order, we can stop searching once + // we hit a satisfied constraint + if (maxMatchingVersion !== latest) { + var failedReqs = getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion); + + // Warn the user that we are not fetching latest + listUnmetRequirements(failedReqs); + events.emit('warn', 'Fetching highest version of plugin that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')'); + } + return maxMatchingVersion; + } + } + + // No version of the plugin is satisfied. In this case, we fall back to + // fetching latest or pinned versions, but also output a warning + var latestFailedReqs = versions.length > 0 ? getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : []; + + // If the upper bound is greater than latest, we need to combine its engine + // requirements with latest to print out in the warning + if(upperBound && semver.satisfies(latest, upperBoundRange)) { + var upperFailedReqs = getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion); + upperFailedReqs.forEach(function(failedReq) { + for(var i = 0; i < latestFailedReqs.length; i++) { + if(latestFailedReqs[i].dependency === failedReq.dependency) { + // Not going to overcomplicate things and actually merge the ranges + latestFailedReqs[i].required = latestFailedReqs[i].required + ' AND ' + failedReq.required; + return; + } + } + + // There is no req to merge it with + latestFailedReqs.push(failedReq); + }); + } + + listUnmetRequirements(latestFailedReqs); + events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of this plugin. Fetching latest or pinned version of plugin anyway (may be incompatible)'); + + // No constraints were satisfied + return null; +} + + +function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) { + var failed = []; + + for (var req in reqs) { + if(reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) { + var badInstalledVersion = null; + var trimmedReq = req.trim(); + + if(pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) { + badInstalledVersion = pluginMap[req]; + } else if(trimmedReq === 'cordova' && !semver.satisfies(cordovaVersion, reqs[req])) { + badInstalledVersion = cordovaVersion; + } else if(trimmedReq.indexOf('cordova-') === 0) { + // Might be a platform constraint + var platform = trimmedReq.substring(8); + if(platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) { + badInstalledVersion = platformMap[platform]; + } + } + + if(badInstalledVersion) { + failed.push({ + dependency: trimmedReq, + installed: badInstalledVersion.trim(), + required: reqs[req].trim() + }); + } + } + } + + return failed; +} + +function listUnmetRequirements(failedRequirments) { --- End diff -- Typo: 'failedRequir**e**ments' --- If your project is set up for it, you can reply to this email and have your reply appear on GitHub as well. If your project does not have this feature enabled and wishes so, or if the feature is enabled but not working, please contact infrastructure at infrastructure@apache.org or file a JIRA ticket with INFRA. --- --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscribe@cordova.apache.org For additional commands, e-mail: dev-help@cordova.apache.org