Skip to content

Commit f0d4429

Browse files
RandomBytetobiasso85
authored andcommitted
[FEATURE] Builder: Add handling for custom task configurations
As per RFC0004: SAP/ui5-tooling#54
1 parent e960468 commit f0d4429

File tree

7 files changed

+174
-72
lines changed

7 files changed

+174
-72
lines changed

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const ui5Builder = {
2323
generateVersionInfo: require("./lib/tasks/generateVersionInfo"),
2424
replaceCopyright: require("./lib/tasks/replaceCopyright"),
2525
replaceVersion: require("./lib/tasks/replaceVersion"),
26-
uglify: require("./lib/tasks/uglify")
26+
uglify: require("./lib/tasks/uglify"),
27+
taskRepository: require("./lib/tasks/taskRepository")
2728
},
2829
types: {
2930
AbstractBuilder: require("./lib/types/AbstractBuilder"),

lib/builder/builder.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
11
const log = require("@ui5/logger").getGroupLogger("builder:builder");
22
const resourceFactory = require("@ui5/fs").resourceFactory;
33
const typeRepository = require("../types/typeRepository");
4+
const taskRepository = require("../tasks/taskRepository");
45

5-
const definedTasks = {
6-
replaceCopyright: require("../tasks/replaceCopyright"),
7-
replaceVersion: require("../tasks/replaceVersion"),
8-
createDebugFiles: require("../tasks/createDebugFiles"),
9-
uglify: require("../tasks/uglify"),
10-
buildThemes: require("../tasks/buildThemes"),
11-
generateLibraryManifest: require("../tasks/generateLibraryManifest"),
12-
generateVersionInfo: require("../tasks/generateVersionInfo"),
13-
generateManifestBundle: require("../tasks/bundlers/generateManifestBundle"),
14-
generateFlexChangesBundle: require("../tasks/bundlers/generateFlexChangesBundle"),
15-
generateComponentPreload: require("../tasks/bundlers/generateComponentPreload"),
16-
generateStandaloneAppBundle: require("../tasks/bundlers/generateStandaloneAppBundle"),
17-
generateBundle: require("../tasks/bundlers/generateBundle"),
18-
generateLibraryPreload: require("../tasks/bundlers/generateLibraryPreload")
19-
};
20-
6+
const definedTasks = taskRepository.getAllTasks();
217

228
// Set of tasks for development
239
const devTasks = [

lib/tasks/taskRepository.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const tasks = {
2+
replaceCopyright: require("./replaceCopyright"),
3+
replaceVersion: require("./replaceVersion"),
4+
createDebugFiles: require("./createDebugFiles"),
5+
uglify: require("./uglify"),
6+
buildThemes: require("./buildThemes"),
7+
generateLibraryManifest: require("./generateLibraryManifest"),
8+
generateVersionInfo: require("./generateVersionInfo"),
9+
generateManifestBundle: require("./bundlers/generateManifestBundle"),
10+
generateFlexChangesBundle: require("./bundlers/generateFlexChangesBundle"),
11+
generateComponentPreload: require("./bundlers/generateComponentPreload"),
12+
generateStandaloneAppBundle: require("./bundlers/generateStandaloneAppBundle"),
13+
generateBundle: require("./bundlers/generateBundle"),
14+
generateLibraryPreload: require("./bundlers/generateLibraryPreload")
15+
};
16+
17+
function getTask(taskName) {
18+
const task = tasks[taskName];
19+
20+
if (!task) {
21+
throw new Error(`taskRepository: Unkown Task ${taskName}`);
22+
}
23+
return task;
24+
}
25+
26+
function addTask(name, task) {
27+
if (tasks[name]) {
28+
throw new Error(`taskRepository: Task ${name} already registered`);
29+
}
30+
tasks[name] = task;
31+
}
32+
33+
function getAllTasks() {
34+
return tasks;
35+
}
36+
37+
module.exports = {
38+
getTask: getTask,
39+
addTask: addTask,
40+
getAllTasks: getAllTasks
41+
};

lib/types/AbstractBuilder.js

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,140 @@ class AbstractBuilder {
77
/**
88
* Constructor
99
*
10+
* @param {object} resourceCollections Resource collections
11+
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
12+
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
1013
* @param {object} project Project configuration
1114
* @param {GroupLogger} parentLogger Logger to use
1215
*/
13-
constructor({project, parentLogger}) {
14-
this.tasks = {};
16+
constructor({resourceCollections, project, parentLogger}) {
17+
if (new.target === AbstractBuilder) {
18+
throw new TypeError("Class 'AbstractBuilder' is abstract");
19+
}
20+
21+
this.project = project;
22+
1523
this.log = parentLogger.createSubLogger(project.type + " " + project.metadata.name, 0.2);
1624
this.taskLog = this.log.createTaskLogger("🔨");
17-
this.availableTasks = [];
25+
26+
this.tasks = {};
27+
this.taskExecutionOrder = [];
28+
this.addStandardTasks({resourceCollections, project});
29+
this.addCustomTasks({resourceCollections, project});
30+
}
31+
32+
/**
33+
* Adds all standard tasks to execute
34+
*
35+
* @abstract
36+
* @protected
37+
* @param {object} resourceCollections Resource collections
38+
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
39+
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
40+
* @param {object} project Project configuration
41+
*/
42+
addStandardTasks() {
43+
throw new Error("Function 'addStandardTasks' is not implemented");
44+
}
45+
46+
/**
47+
* Adds custom tasks to execute
48+
*
49+
* @private
50+
* @param {object} resourceCollections Resource collections
51+
* @param {DuplexCollection} resourceCollections.workspace Workspace Resource
52+
* @param {ReaderCollection} resourceCollections.dependencies Workspace Resource
53+
* @param {object} project Project configuration
54+
*/
55+
addCustomTasks({resourceCollections, project}) {
56+
const projectCustomTasks = project.builder && project.builder.customTasks;
57+
if (!projectCustomTasks || projectCustomTasks.length === 0) {
58+
return; // No custom tasks defined
59+
}
60+
const taskRepository = require("../tasks/taskRepository");
61+
for (let i = 0; i < projectCustomTasks.length; i++) {
62+
const taskDef = projectCustomTasks[i];
63+
if (!taskDef.name) {
64+
throw new Error(`Missing name for custom task definition of project ${project.metadata.name} ` +
65+
`at index ${i}`);
66+
}
67+
if (taskDef.beforeTask && taskDef.afterTask) {
68+
throw new Error(`Custom task definition ${taskDef.name} of project ${project.metadata.name} ` +
69+
`defines both "beforeTask" and "afterTask" parameters. Only one must be defined.`);
70+
}
71+
if (!taskDef.beforeTask && !taskDef.afterTask) {
72+
throw new Error(`Custom task definition ${taskDef.name} of project ${project.metadata.name} ` +
73+
`defines neither a "beforeTask" nor an "afterTask" parameter. One must be defined.`);
74+
}
75+
76+
let newTaskName = taskDef.name;
77+
if (this.tasks[newTaskName]) {
78+
// Task is already known
79+
// => add a suffix to allow for multiple configurations of the same task
80+
let suffixCounter = 0;
81+
while (this.tasks[newTaskName]) {
82+
suffixCounter++; // Start at 1
83+
newTaskName = `${newTaskName}--${suffixCounter}`;
84+
}
85+
}
86+
// Create custom task if not already done (task might be referenced multiple times, first one wins)
87+
const task = taskRepository.getTask(taskDef.name);
88+
const execTask = function() {
89+
/* Custom Task Interface
90+
Parameters:
91+
{Object} parameters Parameters
92+
{DuplexCollection} parameters.workspace DuplexCollection to read and write files
93+
{AbstractReader} parameters.dependencies Reader or Collection to read dependency files
94+
{Object} parameters.options Options
95+
{string} parameters.options.projectName Project name
96+
{string} [parameters.options.configuration] Task configuration if given in ui5.yaml
97+
Returns:
98+
{Promise<undefined>} Promise resolving with undefined once data has been written
99+
*/
100+
return task({
101+
workspace: resourceCollections.workspace,
102+
dependencies: resourceCollections.dependencies,
103+
options: {
104+
projectName: project.metadata.name,
105+
configuration: taskDef.configuration
106+
}
107+
});
108+
};
109+
110+
this.tasks[newTaskName] = execTask;
111+
112+
const refTaskName = taskDef.beforeTask || taskDef.afterTask;
113+
let refTaskIdx = this.taskExecutionOrder.indexOf(refTaskName);
114+
if (refTaskIdx === -1) {
115+
throw new Error(`Could not find task ${refTaskName}, referenced by custom task ${newTaskName}, ` +
116+
`to be scheduled for project ${project.metadata.name}`);
117+
}
118+
if (taskDef.afterTask) {
119+
// Insert after index of referenced task
120+
refTaskIdx++;
121+
}
122+
this.taskExecutionOrder.splice(refTaskIdx, 0, newTaskName);
123+
}
18124
}
19125

20126
/**
21127
* Adds a executable task to the builder
22128
*
23-
* This does not ensure the correct build order. The order is maintained through the property
24-
* [availableTasks]{@link AbstractBuilder#availableTasks}
129+
* The order this function is being called defines the build order. FIFO.
25130
*
26131
* @param {string} taskName Name of the task which should be in the list availableTasks.
27132
* @param {function} taskFunction
28133
*/
29134
addTask(taskName, taskFunction) {
30-
if (this.availableTasks.indexOf(taskName) === -1) {
31-
throw new Error(`Task "${taskName}" does not exist.`);
135+
if (this.tasks[taskName]) {
136+
throw new Error(`Failed to add duplicative task ${taskName} for project ${this.project.metadata.name}`);
137+
}
138+
if (this.taskExecutionOrder.includes(taskName)) {
139+
throw new Error(`Builder: Failed ot add task ${taskName} for project ${this.project.metadata.name}. ` +
140+
`It has already been scheduled for execution.`);
32141
}
33142
this.tasks[taskName] = taskFunction;
143+
this.taskExecutionOrder.push(taskName);
34144
}
35145

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

46156
let taskChain = Promise.resolve();
47-
for (let i = 0; i < this.availableTasks.length; i++) {
48-
const taskName = this.availableTasks[i];
49-
50-
if (!tasksToRun.includes(taskName)) {
51-
continue;
52-
}
53-
54-
const taskFunction = this.tasks[taskName];
157+
for (const taskName in this.tasks) {
158+
if (this.tasks.hasOwnProperty(taskName) && tasksToRun.includes(taskName)) {
159+
const taskFunction = this.tasks[taskName];
55160

56-
if (typeof taskFunction === "function") {
57-
taskChain = taskChain.then(this.wrapTask(taskName, taskFunction));
161+
if (typeof taskFunction === "function") {
162+
taskChain = taskChain.then(this.wrapTask(taskName, taskFunction));
163+
}
58164
}
59165
}
60166
return taskChain;

lib/types/application/ApplicationBuilder.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,7 @@ const tasks = { // can't require index.js due to circular dependency
1515
};
1616

1717
class ApplicationBuilder extends AbstractBuilder {
18-
constructor({resourceCollections, project, parentLogger}) {
19-
super({project, parentLogger});
20-
21-
// All available application tasks in execution order
22-
this.availableTasks = [
23-
"replaceCopyright",
24-
"replaceVersion",
25-
"generateFlexChangesBundle",
26-
"generateManifestBundle",
27-
"generateComponentPreload",
28-
"generateStandaloneAppBundle",
29-
"generateBundle",
30-
"createDebugFiles",
31-
"uglify",
32-
"generateVersionInfo"
33-
];
34-
18+
addStandardTasks({resourceCollections, project}) {
3519
this.addTask("replaceCopyright", () => {
3620
return tasks.replaceCopyright({
3721
workspace: resourceCollections.workspace,

lib/types/library/LibraryBuilder.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,7 @@ const tasks = { // can't require index.js due to circular dependency
1616
};
1717

1818
class LibraryBuilder extends AbstractBuilder {
19-
constructor({resourceCollections, project, parentLogger}) {
20-
super({project, parentLogger});
21-
22-
// All available library tasks in execution order
23-
this.availableTasks = [
24-
"replaceCopyright",
25-
"replaceVersion",
26-
"generateComponentPreload",
27-
"generateBundle",
28-
"generateLibraryManifest",
29-
"generateLibraryPreload",
30-
"buildThemes",
31-
"createDebugFiles",
32-
"uglify"
33-
];
34-
19+
addStandardTasks({resourceCollections, project, parentLogger}) {
3520
this.addTask("replaceCopyright", () => {
3621
const replaceCopyright = tasks.replaceCopyright;
3722
return replaceCopyright({

lib/types/module/ModuleBuilder.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ const AbstractBuilder = require("../AbstractBuilder");
22

33
class ModuleBuilder extends AbstractBuilder {
44
constructor({resourceCollections, project, parentLogger}) {
5-
super({project, parentLogger});
6-
7-
// All available library tasks in execution order
8-
this.availableTasks = [];
5+
super({resourceCollections, project, parentLogger});
96
}
7+
8+
addStandardTasks() {/* nothing to do*/}
109
}
1110

1211
module.exports = ModuleBuilder;

0 commit comments

Comments
 (0)