Skip to content

Commit

Permalink
[FEATURE] Builder: Add handling for custom task configurations
Browse files Browse the repository at this point in the history
As per RFC0004: SAP/ui5-tooling#54
  • Loading branch information
RandomByte committed Nov 16, 2018
1 parent 51cc7e1 commit f882676
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 72 deletions.
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const ui5Builder = {
generateVersionInfo: require("./lib/tasks/generateVersionInfo"),
replaceCopyright: require("./lib/tasks/replaceCopyright"),
replaceVersion: require("./lib/tasks/replaceVersion"),
uglify: require("./lib/tasks/uglify")
uglify: require("./lib/tasks/uglify"),
taskRepository: require("./lib/tasks/taskRepository")
},
types: {
AbstractBuilder: require("./lib/types/AbstractBuilder"),
Expand Down
18 changes: 2 additions & 16 deletions lib/builder/builder.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
const log = require("@ui5/logger").getGroupLogger("builder:builder");
const resourceFactory = require("@ui5/fs").resourceFactory;
const typeRepository = require("../types/typeRepository");
const taskRepository = require("../tasks/taskRepository");

const definedTasks = {
replaceCopyright: require("../tasks/replaceCopyright"),
replaceVersion: require("../tasks/replaceVersion"),
createDebugFiles: require("../tasks/createDebugFiles"),
uglify: require("../tasks/uglify"),
buildThemes: require("../tasks/buildThemes"),
generateLibraryManifest: require("../tasks/generateLibraryManifest"),
generateVersionInfo: require("../tasks/generateVersionInfo"),
generateManifestBundle: require("../tasks/bundlers/generateManifestBundle"),
generateFlexChangesBundle: require("../tasks/bundlers/generateFlexChangesBundle"),
generateComponentPreload: require("../tasks/bundlers/generateComponentPreload"),
generateStandaloneAppBundle: require("../tasks/bundlers/generateStandaloneAppBundle"),
generateBundle: require("../tasks/bundlers/generateBundle"),
generateLibraryPreload: require("../tasks/bundlers/generateLibraryPreload")
};

const definedTasks = taskRepository.getAllTasks();

// Set of tasks for development
const devTasks = [
Expand Down
41 changes: 41 additions & 0 deletions lib/tasks/taskRepository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const tasks = {
replaceCopyright: require("./replaceCopyright"),
replaceVersion: require("./replaceVersion"),
createDebugFiles: require("./createDebugFiles"),
uglify: require("./uglify"),
buildThemes: require("./buildThemes"),
generateLibraryManifest: require("./generateLibraryManifest"),
generateVersionInfo: require("./generateVersionInfo"),
generateManifestBundle: require("./bundlers/generateManifestBundle"),
generateFlexChangesBundle: require("./bundlers/generateFlexChangesBundle"),
generateComponentPreload: require("./bundlers/generateComponentPreload"),
generateStandaloneAppBundle: require("./bundlers/generateStandaloneAppBundle"),
generateBundle: require("./bundlers/generateBundle"),
generateLibraryPreload: require("./bundlers/generateLibraryPreload")
};

function getTask(taskName) {
const task = tasks[taskName];

if (!task) {
throw new Error(`taskRepository: Unkown Task ${taskName}`);
}
return task;
}

function addTask(name, task) {
if (tasks[name]) {
throw new Error(`taskRepository: Task ${name} already registered`);
}
tasks[name] = task;
}

function getAllTasks() {
return tasks;
}

module.exports = {
getTask: getTask,
addTask: addTask,
getAllTasks: getAllTasks
};
142 changes: 124 additions & 18 deletions lib/types/AbstractBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,140 @@ class AbstractBuilder {
/**
* Constructor
*
* @param {object} resourceCollections Resource collections
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
* @param {object} project Project configuration
* @param {GroupLogger} parentLogger Logger to use
*/
constructor({project, parentLogger}) {
this.tasks = {};
constructor({resourceCollections, project, parentLogger}) {
if (new.target === AbstractBuilder) {
throw new TypeError("Class 'AbstractBuilder' is abstract");
}

this.project = project;

this.log = parentLogger.createSubLogger(project.type + " " + project.metadata.name, 0.2);
this.taskLog = this.log.createTaskLogger("🔨");
this.availableTasks = [];

this.tasks = {};
this.taskExecutionOrder = [];
this.addStandardTasks({resourceCollections, project});
this.addCustomTasks({resourceCollections, project});
}

/**
* Adds all standard tasks to execute
*
* @abstract
* @protected
* @param {object} resourceCollections Resource collections
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
* @param {object} project Project configuration
*/
addStandardTasks() {
throw new Error("Function 'addStandardTasks' is not implemented");
}

/**
* Adds custom tasks to execute
*
* @private
* @param {object} resourceCollections Resource collections
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
* @param {object} project Project configuration
*/
addCustomTasks({resourceCollections, project}) {
const projectCustomTasks = project.builder && project.builder.customTasks;
if (!projectCustomTasks || projectCustomTasks.length === 0) {
return; // No custom tasks defined
}
const taskRepository = require("../tasks/taskRepository");
for (let i = 0; i < projectCustomTasks.length; i++) {
const taskDef = projectCustomTasks[i];
if (!taskDef.name) {
throw new Error(`Missing name for custom task definition of project ${project.metadata.name} ` +
`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.`);
}
if (!taskDef.beforeTask && !taskDef.afterTask) {
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.`);
}

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 = `${newTaskName}--${suffixCounter}`;
}
}
// Create custom task if not already done (task might be referenced multiple times, first one wins)
const task = taskRepository.getTask(taskDef.name);
const execTask = function() {
/* Custom Task Interface
Parameters:
{Object} parameters Parameters
{DuplexCollection} parameters.workspace DuplexCollection to read and write files
{AbstractReader} parameters.dependencies Reader or Collection to read dependency files
{Object} parameters.options Options
{string} parameters.options.projectName Project name
{string} [parameters.options.configuration] Task configuration if given in ui5.yaml
Returns:
{Promise<undefined>} Promise resolving with undefined once data has been written
*/
return task({
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
configuration: taskDef.configuration
}
});
};

this.tasks[newTaskName] = execTask;

const refTaskName = taskDef.beforeTask || taskDef.afterTask;
let refTaskIdx = this.taskExecutionOrder.indexOf(refTaskName);
if (refTaskIdx === -1) {
throw new Error(`Could not find task ${refTaskName}, referenced by custom task ${newTaskName}, ` +
`to be scheduled for project ${project.metadata.name}`);
}
if (taskDef.afterTask) {
// Insert after index of referenced task
refTaskIdx++;
}
this.taskExecutionOrder.splice(refTaskIdx, 0, newTaskName);
}
}

/**
* Adds a executable task to the builder
*
* This does not ensure the correct build order. The order is maintained through the property
* [availableTasks]{@link AbstractBuilder#availableTasks}
* The order this function is being called defines the build order. FIFO.
*
* @param {string} taskName Name of the task which should be in the list availableTasks.
* @param {function} taskFunction
*/
addTask(taskName, taskFunction) {
if (this.availableTasks.indexOf(taskName) === -1) {
throw new Error(`Task "${taskName}" does not exist.`);
if (this.tasks[taskName]) {
throw new Error(`Failed to add duplicative task ${taskName} for project ${this.project.metadata.name}`);
}
if (this.taskExecutionOrder.includes(taskName)) {
throw new Error(`Builder: Failed ot add task ${taskName} for project ${this.project.metadata.name}. ` +
`It has already been scheduled for execution.`);
}
this.tasks[taskName] = taskFunction;
this.taskExecutionOrder.push(taskName);
}

/**
Expand All @@ -40,21 +150,17 @@ class AbstractBuilder {
* @returns {Promise} Returns promise chain with tasks
*/
build(tasksToRun) {
const allTasksCount = tasksToRun.filter((value) => this.availableTasks.includes(value)).length;
const allTasksCount = tasksToRun.filter((value) => this.tasks.hasOwnProperty(value)).length;
this.taskLog.addWork(allTasksCount);

let taskChain = Promise.resolve();
for (let i = 0; i < this.availableTasks.length; i++) {
const taskName = this.availableTasks[i];

if (!tasksToRun.includes(taskName)) {
continue;
}

const taskFunction = this.tasks[taskName];
for (const taskName in this.tasks) {
if (this.tasks.hasOwnProperty(taskName) && tasksToRun.includes(taskName)) {
const taskFunction = this.tasks[taskName];

if (typeof taskFunction === "function") {
taskChain = taskChain.then(this.wrapTask(taskName, taskFunction));
if (typeof taskFunction === "function") {
taskChain = taskChain.then(this.wrapTask(taskName, taskFunction));
}
}
}
return taskChain;
Expand Down
18 changes: 1 addition & 17 deletions lib/types/application/ApplicationBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,7 @@ const tasks = { // can't require index.js due to circular dependency
};

class ApplicationBuilder extends AbstractBuilder {
constructor({resourceCollections, project, parentLogger}) {
super({project, parentLogger});

// All available application tasks in execution order
this.availableTasks = [
"replaceCopyright",
"replaceVersion",
"generateFlexChangesBundle",
"generateManifestBundle",
"generateComponentPreload",
"generateStandaloneAppBundle",
"generateBundle",
"createDebugFiles",
"uglify",
"generateVersionInfo"
];

addStandardTasks({resourceCollections, project}) {
this.addTask("replaceCopyright", () => {
return tasks.replaceCopyright({
workspace: resourceCollections.workspace,
Expand Down
17 changes: 1 addition & 16 deletions lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,7 @@ const tasks = { // can't require index.js due to circular dependency
};

class LibraryBuilder extends AbstractBuilder {
constructor({resourceCollections, project, parentLogger}) {
super({project, parentLogger});

// All available library tasks in execution order
this.availableTasks = [
"replaceCopyright",
"replaceVersion",
"generateComponentPreload",
"generateBundle",
"generateLibraryManifest",
"generateLibraryPreload",
"buildThemes",
"createDebugFiles",
"uglify"
];

addStandardTasks({resourceCollections, project, parentLogger}) {
this.addTask("replaceCopyright", () => {
const replaceCopyright = tasks.replaceCopyright;
return replaceCopyright({
Expand Down
7 changes: 3 additions & 4 deletions lib/types/module/ModuleBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ const AbstractBuilder = require("../AbstractBuilder");

class ModuleBuilder extends AbstractBuilder {
constructor({resourceCollections, project, parentLogger}) {
super({project, parentLogger});

// All available library tasks in execution order
this.availableTasks = [];
super({resourceCollections, project, parentLogger});
}

addStandardTasks() {/* nothing to do*/}
}

module.exports = ModuleBuilder;

0 comments on commit f882676

Please sign in to comment.