Skip to content

Commit

Permalink
sort adapters by their dependencies (#1263)
Browse files Browse the repository at this point in the history
- upgrade adapters w/o dependecies and with irrelevant dependencies first
- then the dependent adapters
- then the adapters which need the dependent adapters
- no longer list 'is-up-to-date' + 'not installed' infos for all adapters anymore, use 'iob update' if you need this
- use debug module as described in #309 - maybe we can also close this issue if we spread debug like here in more places in the future
- implements #1015
  • Loading branch information
foxriver76 authored Apr 16, 2021
1 parent feca5eb commit 6631072
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 20 deletions.
2 changes: 1 addition & 1 deletion lib/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ function processCommand(command, args, params, callback) {
}
if (links) {
result.sort();
upgrade.upgradeAdapterHelper(links, result, 0, false, params.y || params.yes, callback);
upgrade.upgradeAdapterHelper(links, result, false, params.y || params.yes, callback);
} else {
// No information
return void callback(EXIT_CODES.INVALID_REPO);
Expand Down
126 changes: 107 additions & 19 deletions lib/setup/setupUpgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,40 @@
*/

'use strict';
const debug = require('debug')('iobroker:cli');

/** @class */
function Upgrade(options) {
const fs = require('fs-extra');
const tools = require('../tools.js');
const fs = require('fs-extra');
const tools = require('../tools.js');

options = options || {};

if (!options.processExit) {
if (!options.processExit) {
throw new Error('Invalid arguments: processExit is missing');
}
if (!options.installNpm) {
if (!options.installNpm) {
throw new Error('Invalid arguments: installNpm is missing');
}
if (!options.restartController) {
throw new Error('Invalid arguments: restartController is missing');
}
if (!options.getRepository) {
if (!options.getRepository) {
throw new Error('Invalid arguments: getRepository is missing');
}

const processExit = options.processExit;
const installNpm = options.installNpm;
const processExit = options.processExit;
const installNpm = options.installNpm;
const restartController = options.restartController;
const getRepository = options.getRepository;
const params = options.params;
const objects = options.objects;
const getRepository = options.getRepository;
const params = options.params;
const objects = options.objects;
/** @type {import("semver")} */
const semver = require('semver');
let rl;
let tty;

const hostname = tools.getHostName();
const hostname = tools.getHostName();
const EXIT_CODES = require('../exitCodes');

const Upload = require('./setupUpload.js');
Expand All @@ -50,29 +51,116 @@ function Upgrade(options) {
const install = new Install(options);

/**
* Upgrade multiple adapters from the given repository url
* Sorts the adapters by their dependencies and then upgrades multiple adapters from the given repository url
*
* @param {string} repoUrl url of the selected repository
* @param {object} repo the repository content
* @param {string[]} list list of adapters to upgrade
* @param {number} i current index used by list
* @param {boolean} forceDowngrade flag to force downgrade
* @param {boolean} autoConfirm automatically confirm the tty questions (bypass)
* @param {function} callback callback to be executed after function is finished
*/
this.upgradeAdapterHelper = function (repoUrl, list, i, forceDowngrade, autoConfirm, callback) {
this.upgradeAdapterHelper = (repo, list, forceDowngrade, autoConfirm, callback) => {
if (typeof autoConfirm === 'function') {
callback = autoConfirm;
autoConfirm = false;
}

this.upgradeAdapter(repoUrl, list[i], forceDowngrade, autoConfirm, true, () => {
const relevantAdapters = [];
// check which adapters are upgradeable and sort them according to their dependencies
for (const adapter of list) {
if (repo[adapter].controller) {
// skip controller
continue;
}
const adapterDir = tools.getAdapterDir(adapter);
if (fs.existsSync(`${adapterDir}/io-package.json`)) {
const ioInstalled = require(`${adapterDir}/io-package.json`);
if (!tools.upToDate(repo[adapter].version, ioInstalled.common.version)) {
// not up to date, we need to put it into account for our dependency check
relevantAdapters.push(adapter);
}
}
}

if (relevantAdapters) {
const sortedAdapters = [];

while (relevantAdapters.length) {
let oneAdapterAdded = false;
// create ordered list for upgrades
for (let i = relevantAdapters.length - 1; i >= 0; i--) {
const relAdapter = relevantAdapters[i];
// if new version has no dependencies we can upgrade
if (!repo[relAdapter].dependencies && !repo[relAdapter].globalDependencies) {
// no deps, simply add it
sortedAdapters.push(relAdapter);
} else {
const deps = repo[relAdapter].dependencies || [];
const globalDeps = repo[relAdapter].globalDependencies || [];

// we have to check if the deps are there
let conflict = false;
for (const dep of [...deps, ...globalDeps]) {
debug(`adapter "${relAdapter}" has dependency "${JSON.stringify(dep)}"`);
if (tools.isObject(dep)) {
// object -> dependency is important, because it affects version range
for (const depName of Object.keys(dep)) {
if (relevantAdapters.includes(depName)) {
// the dependency is also in the upgrade list and not previously added, we should add the dependency first
debug(`conflict for dependency "${depName}" at adapter "${relAdapter}"`);
conflict = true;
break;
}
}
}
// else it is a string, so doesn't matter because dep is also there before upgrade
}
// we reached here and no conflict so every dep is satisfied
if (!conflict) {
sortedAdapters.push(relAdapter);
relevantAdapters.splice(relevantAdapters.indexOf(relAdapter), 1);
oneAdapterAdded = true;
}
}
}

if (!oneAdapterAdded) {
// no adapter during this loop -> circular dependency
console.warn(`Circular dependency detected between adapters "${relevantAdapters.join(', ')}"`);
sortedAdapters.concat(relevantAdapters);
break; // however, break and try to update
}
}

debug(`upgrade order is "${sortedAdapters.join(', ')}"`);
this._upgradeAdapterHelper(repo, sortedAdapters, 0, forceDowngrade, autoConfirm, callback);
} else {
console.log('All adapters are up to date');
if (typeof callback === 'function') {
callback();
}
}
};

/**
* Upgrade multiple adapters from the given repository url
*
* @param {object} repo the repository content
* @param {string[]} list list of adapters to upgrade
* @param {number} i current index used by list
* @param {boolean} forceDowngrade flag to force downgrade
* @param {boolean} autoConfirm automatically confirm the tty questions (bypass)
* @param {function} callback callback to be executed after function is finished
*/
this._upgradeAdapterHelper = (repo, list, i, forceDowngrade, autoConfirm, callback) => {
this.upgradeAdapter(repo, list[i], forceDowngrade, autoConfirm, true, () => {
i++;
while (repoUrl[list[i]] && repoUrl[list[i]].controller) {
while (repo[list[i]] && repo[list[i]].controller) {
i++;
}

if (list[i]) {
setImmediate(() => this.upgradeAdapterHelper(repoUrl, list, i, forceDowngrade, autoConfirm, callback));
setImmediate(() => this._upgradeAdapterHelper(repo, list, i, forceDowngrade, autoConfirm, callback));
} else if (callback) {
callback();
}
Expand Down Expand Up @@ -129,7 +217,7 @@ function Upgrade(options) {
if (deps[dName] !== undefined) {
// local dep get all instances on same host
locInstances = objs.rows.filter(obj => obj && obj.value && obj.value.common && obj.value.common.name === dName && obj.value.common.host === hostname);
if (locInstances.length === 0) {
if (locInstances.length === 0) {
return Promise.reject(new Error(`Required dependency "${dName}" not found on this host.`));
}
}
Expand Down

0 comments on commit 6631072

Please sign in to comment.