Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 70 additions & 45 deletions lib/types/AbstractBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class AbstractBuilder {
this.taskLog = this.log.createTaskLogger("🔨");

this.tasks = {};
this.taskExecutions = {};
this.taskExecutionOrder = [];
this.addStandardTasks({
resourceCollections,
Expand Down Expand Up @@ -85,25 +86,34 @@ class AbstractBuilder {
`at index ${i}`);
}
if (taskDef.beforeTask && taskDef.afterTask) {
throw new Error(`Custom task definition ${taskDef.name} of project ${project.metadata.name} ` +
`defines both "beforeTask" and "afterTask" parameters. Only one must be defined.`);
throw new Error(`Custom task definition ${taskDef.id || taskDef.name} of project ` +
`${project.metadata.name} defines both "beforeTask" and "afterTask" parameters. ` +
`Only one must be defined.`);
}
if (this.taskExecutionOrder.length && !taskDef.beforeTask && !taskDef.afterTask) {
// Iff there are tasks configured, beforeTask or afterTask must be given
throw new Error(`Custom task definition ${taskDef.name} of project ${project.metadata.name} ` +
`defines neither a "beforeTask" nor an "afterTask" parameter. One must be defined.`);
// If there are tasks configured, beforeTask or afterTask must be given
throw new Error(`Custom task definition ${taskDef.id || taskDef.name} of project ` +
`${project.metadata.name} defines neither a "beforeTask" nor an "afterTask" parameter. ` +
`One must be defined.`);
}

let newTaskName = taskDef.name;
if (this.tasks[newTaskName]) {
// Task is already known
// => add a suffix to allow for multiple configurations of the same task
let suffixCounter = 0;
while (this.tasks[newTaskName]) {
suffixCounter++; // Start at 1
newTaskName = `${taskDef.name}--${suffixCounter}`;
let taskId = taskDef.id;
if (!taskId) {
// No identifier defined, use the task name and add a suffix if necessary
taskId = taskDef.name;
if (this.tasks[taskId]) {
// Task is already known => add a suffix to allow for multiple configurations of the same task
let suffixCounter = 0;
while (this.tasks[taskId]) {
suffixCounter++; // Start at 1
taskId = `${taskDef.name}--${suffixCounter}`;
}
}
} else if (this.tasks[taskId]) {
throw new Error(`Conflicting custom task definition ${taskId} of project ${project.metadata.name}, ` +
`more than one task with the same identifier defined. Task identifiers must be unique.`);
}

// Create custom task if not already done (task might be referenced multiple times, first one wins)
const {specVersion, task} = taskRepository.getTask(taskDef.name);
const execTask = function() {
Expand Down Expand Up @@ -140,24 +150,25 @@ class AbstractBuilder {
return task(params);
};

this.tasks[newTaskName] = execTask;
this.tasks[taskId] = execTask;

(this.taskExecutions[taskDef.name] || (this.taskExecutions[taskDef.name] = [])).push(taskId);
if (this.taskExecutionOrder.length) {
// There is at least one task configured. Use before- and afterTask to add the custom task
const refTaskName = taskDef.beforeTask || taskDef.afterTask;
let refTaskIdx = this.taskExecutionOrder.indexOf(refTaskName);
const refTaskId = taskDef.beforeTask || taskDef.afterTask;
let refTaskIdx = this.taskExecutionOrder.indexOf(refTaskId);
if (refTaskIdx === -1) {
throw new Error(`Could not find task ${refTaskName}, referenced by custom task ${newTaskName}, ` +
throw new Error(`Could not find task ${refTaskId}, referenced by custom task ${taskId}, ` +
`to be scheduled for project ${project.metadata.name}`);
}
if (taskDef.afterTask) {
// Insert after index of referenced task
refTaskIdx++;
}
this.taskExecutionOrder.splice(refTaskIdx, 0, newTaskName);
this.taskExecutionOrder.splice(refTaskIdx, 0, taskId);
} else {
// There is no task configured so far. Just add the custom task
this.taskExecutionOrder.push(newTaskName);
this.taskExecutionOrder.push(taskId);
}
}
}
Expand All @@ -167,67 +178,81 @@ class AbstractBuilder {
*
* The order this function is being called defines the build order. FIFO.
*
* @param {string} [taskId] Identifier of the task which should be in the list availableTasks.
* @param {string} taskName Name of the task which should be in the list availableTasks.
* @param {Function} taskFunction
*/
addTask(taskName, taskFunction) {
if (this.tasks[taskName]) {
throw new Error(`Failed to add duplicate task ${taskName} for project ${this.project.metadata.name}`);
addTask(taskId, taskName, taskFunction) {
if (typeof taskName === "function") {
taskFunction = taskName;
taskName = taskId;
}

if (this.tasks[taskId]) {
throw new Error(`Failed to add duplicate task ${taskId} for project ${this.project.metadata.name}`);
}
if (this.taskExecutionOrder.includes(taskName)) {
throw new Error(`Builder: Failed to add task ${taskName} for project ${this.project.metadata.name}. ` +
if (this.taskExecutionOrder.includes(taskId)) {
throw new Error(`Builder: Failed to add task ${taskId} for project ${this.project.metadata.name}. ` +
`It has already been scheduled for execution.`);
}
this.tasks[taskName] = taskFunction;
this.taskExecutionOrder.push(taskName);

this.tasks[taskId] = taskFunction;
(this.taskExecutions[taskName] || (this.taskExecutions[taskName] = [])).push(taskId);
this.taskExecutionOrder.push(taskId);
}

/**
* Check whether a task is defined
*
* @private
* @param {string} taskName
* @param {string} taskId
* @returns {boolean}
*/
hasTask(taskName) {
return Object.prototype.hasOwnProperty.call(this.tasks, taskName);
hasTask(taskId) {
return Object.prototype.hasOwnProperty.call(this.tasks, taskId);
}

/**
* Takes a list of tasks which should be executed from the available task list of the current builder
*
* @param {Array} tasksToRun List of tasks which should be executed
* @param {Array} tasksToRun List of tasks names which should be executed
* @returns {Promise} Returns promise chain with tasks
*/
build(tasksToRun) {
const allTasksCount = tasksToRun.filter((value) => this.hasTask(value)).length;
this.taskLog.addWork(allTasksCount);
const taskIdsToRun = tasksToRun.reduce((tasks, taskName) => {
if (this.taskExecutions[taskName]) {
tasks.push(...this.taskExecutions[taskName]);
}

let taskChain = Promise.resolve();
for (let i = 0; i < this.taskExecutionOrder.length; i++) {
const taskName = this.taskExecutionOrder[i];
if (this.hasTask(taskName) && tasksToRun.includes(taskName)) {
const taskFunction = this.tasks[taskName];
return tasks;
}, []);

if (typeof taskFunction === "function") {
taskChain = taskChain.then(this.wrapTask(taskName, taskFunction));
}
const allTasks = this.taskExecutionOrder.filter((taskId) =>
this.hasTask(taskId) && taskIdsToRun.includes(taskId));
this.taskLog.addWork(allTasks.length);

return allTasks.reduce((taskChain, taskId) => {
const taskFunction = this.tasks[taskId];

if (typeof taskFunction === "function") {
taskChain = taskChain.then(this.wrapTask(taskId, taskFunction));
}
}
return taskChain;

return taskChain;
}, Promise.resolve());
}

/**
* Adds progress related functionality to task function.
*
* @private
* @param {string} taskName Name of the task
* @param {string} taskId Name of the task
* @param {Function} taskFunction Function which executed the task
* @returns {Function} Wrapped task function
*/
wrapTask(taskName, taskFunction) {
wrapTask(taskId, taskFunction) {
return () => {
this.taskLog.startWork(`Running task ${taskName}...`);
this.taskLog.startWork(`Running task ${taskId}...`);
return taskFunction().then(() => this.taskLog.completeWork(1));
};
}
Expand Down
Loading