Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[INTERNAL] Initial refactoring for introducing a Project class #394

Closed
wants to merge 99 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
153944a
[INTERNAL] Initial refactoring for introducing a Project class
RandomByte Dec 4, 2020
74d0a7d
Refactor Configuration into a single Class and create AbstractSpecifi…
RandomByte Feb 13, 2021
0a697dc
Minor cleanup and fix tests
RandomByte Feb 13, 2021
d35e498
projectGraphFromTree: Allow for node-level configuration parameters
RandomByte Feb 13, 2021
f8d75b4
Upgrade fixtures to specVersion 2.3
RandomByte Feb 13, 2021
db80a02
Limit Module API to specVersions >= 2.0
RandomByte Feb 13, 2021
98854ec
Process modules once per ID
RandomByte Feb 13, 2021
3b59a1b
Add ProjectGraph tests
RandomByte Feb 13, 2021
6bed2f0
Refactor Module. Add support for dependency and collection shims
RandomByte Feb 14, 2021
4f08298
Add extension support to ProjectGraph
RandomByte Feb 14, 2021
035688f
JSdoc and test cleanup
RandomByte Feb 14, 2021
35dd0d7
projectGraphFromTree: Add parameter to use an existing graph
RandomByte Feb 14, 2021
a7a3f55
Enhance normalizer and ui5Framework translator for project graph
RandomByte Feb 14, 2021
4342fc7
Use object-spread operator instead of Object.assign
RandomByte Feb 14, 2021
4295888
Revert "projectGraphFromTree: Add parameter to use an existing graph"
RandomByte Feb 14, 2021
7a7ad31
Add ProjectGraph#getAllExtensions plus tests
RandomByte Feb 14, 2021
668ae9e
Split graph-agnostic ui5Framework implementation into separate file
RandomByte Feb 14, 2021
acd32fd
Introduce projectGraphFromDirectory as normalizer successor
RandomByte Feb 14, 2021
7eb4ce9
Fix collection shim handling
RandomByte Feb 16, 2021
c4926c0
ProjectGraph: Add seal function
RandomByte Feb 17, 2021
e8114f2
ProjectGraph: Projects must not depend on themselves
RandomByte Feb 17, 2021
f841562
Implement optional dependencies, npm provider
RandomByte Feb 17, 2021
518ee89
ProjectGraph: No need to remove resolved optional dependencies from a…
RandomByte Feb 17, 2021
00430ba
Resolve a bunch of npm tests
RandomByte Feb 18, 2021
707004d
npm provider: Improve error message for unresolved modules
RandomByte Feb 18, 2021
db2d45a
Merge Configuration and Specification into one entity
RandomByte Feb 18, 2021
70f00cb
Cleanup
RandomByte Feb 18, 2021
386b568
Add all missing types and adopt most tests
RandomByte Feb 19, 2021
0ddf695
JSDoc cleanup
RandomByte Feb 23, 2021
e827c75
Specifications: Align reader getters
RandomByte Feb 23, 2021
aa832dd
ui5Framework provider: Fix framework dep collection
RandomByte Feb 23, 2021
55242dc
projectGraphBuilder: Fix check for already qualified application proj…
RandomByte Feb 23, 2021
0bf6e0f
Remove obsolete lib/Specifications.js
RandomByte Feb 23, 2021
1e471bd
Collection handling: Ignore projects of collections
RandomByte Feb 23, 2021
9f1ca5a
ProjectGraph: Only resolve optional dependencies if the target can be…
RandomByte Feb 26, 2021
aa6b5ae
Move legacy OpenUI5 theme library handling to graph.Module
RandomByte Mar 12, 2021
1343150
projectGraphBuilder: Wait for async resolution of optional dependencies
RandomByte Mar 12, 2021
9631712
Refactor graph module structure
RandomByte Apr 23, 2022
62714da
Fix typos, add comments
RandomByte Apr 23, 2022
4920de0
First draft for improved resource access
RandomByte Apr 26, 2022
5eda2bd
Align Module and ThemeLibrary with resource access changes, cleanup
RandomByte Apr 26, 2022
7a8d390
Move extension types to separate sub-dir
RandomByte Apr 26, 2022
94f1d63
Implement build mechanics
RandomByte Apr 26, 2022
eb95577
Implement build mechanics II
RandomByte Apr 30, 2022
620e6d1
Refactor AbstractReader#addTask API
RandomByte May 3, 2022
411a509
Dynamically decide which projects to build based on user and task demand
RandomByte May 4, 2022
8acf7d4
Cleanup
RandomByte May 4, 2022
ba2404b
ApplicationBuilder: Build custom bundles in sequence
RandomByte May 4, 2022
453352d
Implement archive metadata handling
RandomByte May 5, 2022
55d6e44
Cleanup: Remove old modules, transfer tests, fix JSDoc
RandomByte May 5, 2022
feea171
Enhance archive integration tests
RandomByte May 6, 2022
9c84e63
Take over tests for modules moved from ui5-builder
RandomByte May 13, 2022
26fe28f
Specification: Allow spec versions 2.4, 2.5 and 2.6
RandomByte May 18, 2022
6d2881c
Apply changes from https://github.com/SAP/ui5-builder/pull/741
RandomByte May 18, 2022
3d2893a
Apply changes from https://github.com/SAP/ui5-builder/pull/745
RandomByte May 18, 2022
6a43242
ProjectGraph: Update JSDoc
RandomByte May 20, 2022
2e980a8
package.json: Bump @ui5/fs alpha version
RandomByte May 20, 2022
cb2fa20
Specifications: Update JSDoc
RandomByte May 20, 2022
03f8b27
[INTERNAL] Project: Add missing #getCustomMiddleware API
RandomByte May 20, 2022
098d4eb
[INTERNAL] Fix several uses of legacy project.metadata.* attributes
RandomByte May 20, 2022
5cdf725
[INTERNAL] AbstractBuilder: Fix specVersion selection for task interface
RandomByte May 20, 2022
5a12078
[INTERNAL] Extensions: Add getters for modules
RandomByte May 23, 2022
cd70fce
[INTERNAL] Transfer formatter tests from ui5-builder I
RandomByte May 23, 2022
2426642
[INTERNAL] generateProjectGraph: Add option to ignore framework deps
RandomByte May 24, 2022
1f838b7
LibraryBuilder: Pass namespace to generateJsdoc task
matz3 May 24, 2022
bc0c0b7
builder: Fix handling of includedDependencies/excludedDependencies
matz3 May 24, 2022
b77d490
ComponentProject: Ensure linking of resources that are not modified
matz3 May 24, 2022
40cc284
[INTERNAL] projectGraphBuilder: Throw if node has top-level configura…
RandomByte May 24, 2022
a736acf
ApplicationBuilder: Fix componentPreload excludes config
matz3 May 25, 2022
717d447
[INTERNAL] Library: Add tests from ui5-builder
RandomByte May 26, 2022
ecda5c1
[INTERNAL] Library: Add missing test for getNamespaceFromManifest
RandomByte May 27, 2022
f2f607f
[INTERNAL] Module: Add tests from ui5-builder
RandomByte May 27, 2022
46968e4
[INTERNAL] Application: Add missing tests for _configureAndValidatePaths
RandomByte May 27, 2022
6bae282
[INTERNAL] ComponentProject: Allow writing resources outside of names…
RandomByte Jun 1, 2022
bae6c21
[INTERNAL] ProjectGraph: getAll* to return flat array
RandomByte Jun 7, 2022
6433eaa
NodePackageDependencies: Fix dependency resolution
matz3 Jun 7, 2022
0ac1569
[INTERNAL] builder: Adapt to ProjectGraph#getAllProjects flat array c…
RandomByte Jun 7, 2022
84abeb6
[INTERNAL] Projects: Pass builder resource excludes to adapters
RandomByte Jun 8, 2022
0eecee5
Rename archive metadata to build description
RandomByte Jun 8, 2022
96e5ae6
Module: Fix build descripton parameter naming
RandomByte Jun 8, 2022
16a6d76
builder: Fix handling for projects with build description
RandomByte Jun 8, 2022
769d759
Adapt tests for refactored build description
RandomByte Jun 8, 2022
29100bd
Module: Fix resources excludes configuration
RandomByte Jun 8, 2022
0ed62d0
[INTERNAL] ui5Framework: Throw in case required dependencies of frame…
RandomByte Jun 8, 2022
ec31cc4
[INTERNAL] Cleanup specifications: Move project specifics into projec…
RandomByte Jun 8, 2022
3fc936b
[INTERNAL] Add basic migration of legacy spec versions
RandomByte Jun 9, 2022
d2a61f3
Rename build description to build manifest
RandomByte Jun 10, 2022
b4f0594
generateProjectGraph: Allow rootConfiguration/rootConfigPath in all v…
matz3 Jun 10, 2022
663099d
Builder: Fix renaming of build manifest
RandomByte Jun 10, 2022
0e8e56b
graph.Module: Fix parameters handling
matz3 Jun 10, 2022
0317cd0
Remove type legacy-library
RandomByte Jun 10, 2022
073418c
Stop passing legacy 'namespace' parameter to tasks
RandomByte Jun 10, 2022
1064c93
Only create build manifest for root project and only for libraries
RandomByte Jun 11, 2022
50d4137
Remove projectContext.STANDARD_TAGS
RandomByte Jun 13, 2022
9503f21
Add missing tests
matz3 Jun 13, 2022
ba1927e
Update log messages
RandomByte Jun 13, 2022
6dfbb45
Fix generateProjectGraph.usingObject test
matz3 Jun 13, 2022
236585b
Implement migration of legacy projects
matz3 Jun 13, 2022
737aa24
Add error handling for failed spec version migration
RandomByte Jun 13, 2022
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
24 changes: 12 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
*/
module.exports = {
/**
* @type {import('./lib/normalizer')}
* @type {import('./lib/builder')}
*/
normalizer: "./lib/normalizer",
builder: "./lib/builder",
/**
* @type {import('./lib/projectPreprocessor')}
* @type {import('./lib/generateProjectGraph')}
*/
projectPreprocessor: "./lib/projectPreprocessor",
generateProjectGraph: "./lib/generateProjectGraph",
/**
* @public
* @alias module:@ui5/project.ui5Framework
Expand Down Expand Up @@ -42,20 +42,20 @@ module.exports = {
ValidationError: "./lib/validation/ValidationError"
},
/**
* @private
* @alias module:@ui5/project.translators
* @public
* @alias module:@ui5/project.graph
* @namespace
*/
translators: {
graph: {
/**
* @type {import('./lib/translators/npm')}
* @type {typeof import('./lib/graph/ProjectGraph')}
*/
npm: "./lib/translators/npm",
ProjectGraph: "./lib/graph/ProjectGraph",
/**
* @type {import('./lib/translators/static')}
* @type {typeof import('./lib/graph/projectGraphBuilder')}
*/
static: "./lib/translators/static"
}
projectGraphBuilder: "./lib/graph/projectGraphBuilder",
},
};

function exportModules(exportRoot, modulePaths) {
Expand Down
327 changes: 327 additions & 0 deletions lib/buildDefinitions/AbstractBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
const {getTask} = require("@ui5/builder").tasks.taskRepository;
const composeTaskList = require("../buildHelpers/composeTaskList");

/**
* Resource collections
*
* @public
* @typedef module:@ui5/builder.BuilderResourceCollections
* @property {module:@ui5/fs.DuplexCollection} workspace Workspace Resource
* @property {module:@ui5/fs.ReaderCollection} dependencies Workspace Resource
*/

/**
* Base class for the builder implementation of a project type
*
* @abstract
*/
class AbstractBuilder {
/**
* Constructor
*
* @param {object} parameters
* @param {object} parameters.graph
* @param {object} parameters.project
* @param {GroupLogger} parameters.parentLogger Logger to use
* @param {object} parameters.taskUtil
*/
constructor({graph, project, parentLogger, taskUtil}) {
if (new.target === AbstractBuilder) {
throw new TypeError("Class 'AbstractBuilder' is abstract");
}

this.project = project;
this.graph = graph;
this.taskUtil = taskUtil;

this.log = parentLogger.createSubLogger(project.getType() + " " + project.getName(), 0.2);
this.taskLog = this.log.createTaskLogger("🔨");

this.tasks = {};
this.taskExecutionOrder = [];

this.addStandardTasks({
project,
taskUtil,
getTask
});
this.addCustomTasks({
graph,
project,
taskUtil
});
}

/**
* Adds all standard tasks to execute
*
* @abstract
* @protected
* @param {object} parameters
* @param {object} parameters.taskUtil
* @param {object} parameters.project
*/
addStandardTasks({project, taskUtil}) {
throw new Error("Function 'addStandardTasks' is not implemented");
}

/**
* Adds custom tasks to execute
*
* @private
* @param {object} parameters
* @param {object} parameters.graph
* @param {object} parameters.project
* @param {object} parameters.taskUtil
*/
addCustomTasks({graph, project, taskUtil}) {
const projectCustomTasks = project.getCustomTasks();
if (!projectCustomTasks || projectCustomTasks.length === 0) {
return; // No custom tasks defined
}
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.getName()} ` +
`at index ${i}`);
}
if (taskDef.beforeTask && taskDef.afterTask) {
throw new Error(`Custom task definition ${taskDef.name} of project ${project.getName()} ` +
`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.getName()} ` +
`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}`;
}
}
const task = graph.getExtension(taskDef.name);
// TODO: Create callback for custom tasks to configure "requiresDependencies" and "enabled"
// Input: task "options" and build mode ("standalone", "preload", etc.)
const requiresDependencies = true; // Default to true for old spec versions
const execTask = function({workspace, dependencies}) {
/* Custom Task Interface
Parameters:
{Object} parameters Parameters
{module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
{module:@ui5/fs.AbstractReader} parameters.dependencies
Reader or Collection to read dependency files
{Object} parameters.taskUtil Specification Version dependent interface to a
[TaskUtil]{@link module:@ui5/builder.tasks.TaskUtil} instance
{Object} parameters.options Options
{string} parameters.options.projectName Project name
{string} [parameters.options.projectNamespace] Project namespace if available
{string} [parameters.options.configuration] Task configuration if given in ui5.yaml
Returns:
{Promise<undefined>} Promise resolving with undefined once data has been written
*/
const params = {
workspace,
options: {
projectName: project.getName(),
projectNamespace: project.getNamespace(),
configuration: taskDef.configuration
}
};

if (requiresDependencies) {
params.dependencies = dependencies;
}

const taskUtilInterface = taskUtil.getInterface(task.getSpecVersion());
// Interface is undefined if specVersion does not support taskUtil
if (taskUtilInterface) {
params.taskUtil = taskUtilInterface;
}
return task.getTask()(params);
};

this.tasks[newTaskName] = {
task: execTask,
requiresDependencies
};

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);
if (refTaskIdx === -1) {
throw new Error(`Could not find task ${refTaskName}, referenced by custom task ${newTaskName}, ` +
`to be scheduled for project ${project.getName()}`);
}
if (taskDef.afterTask) {
// Insert after index of referenced task
refTaskIdx++;
}
this.taskExecutionOrder.splice(refTaskIdx, 0, newTaskName);
} else {
// There is no task configured so far. Just add the custom task
this.taskExecutionOrder.push(newTaskName);
}
}
}

/**
* Adds a executable task to the builder
*
* 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 {object} [parameters]
* @param {boolean} [parameters.requiresDependencies]
* @param {object} [parameters.options]
* @param {Function} [taskFunction]
*/
addTask(taskName, {requiresDependencies = false, options = {}} = {}, taskFunction) {
if (this.tasks[taskName]) {
throw new Error(`Failed to add duplicate task ${taskName} for project ${this.project.getName()}`);
}
if (this.taskExecutionOrder.includes(taskName)) {
throw new Error(`Builder: Failed to add task ${taskName} for project ${this.project.getName()}. ` +
`It has already been scheduled for execution.`);
}

const task = ({workspace, dependencies}) => {
options.projectName = this.project.getName();
// TODO: Deprecate "namespace" in favor of "projectNamespace" as already used for custom tasks?
options.projectNamespace = this.project.getNamespace();

const params = {
workspace,
taskUtil: this.taskUtil,
options
};

if (requiresDependencies) {
params.dependencies = dependencies;
}

if (!taskFunction) {
taskFunction = getTask(taskName).task;
}
return taskFunction(params);
};
this.tasks[taskName] = {
task,
requiresDependencies
};
this.taskExecutionOrder.push(taskName);
}

/**
* Takes a list of tasks which should be executed from the available task list of the current builder
*
* @param {object} buildConfig
* @param {boolean} buildConfig.selfContained
* True if a the build should be self-contained or false for prelead build bundles
* @param {boolean} buildConfig.jsdoc True if a JSDoc build should be executed
* @param {Array} buildConfig.includedTasks Task list to be included from build
* @param {Array} buildConfig.excludedTasks Task list to be excluded from build
* @param {object} buildParams
* @param {module:@ui5/fs.DuplexCollection} buildParams.workspace Workspace of the current project
* @param {module:@ui5/fs.ReaderCollection} buildParams.dependencies Dependencies reader collection
* @returns {Promise} Returns promise chain with tasks
*/
async build(buildConfig, buildParams) {
const tasksToRun = composeTaskList(Object.keys(this.tasks), buildConfig);
const allTasks = this.taskExecutionOrder.filter((taskName) => {
// There might be a numeric suffix in case a custom task is configured multiple times.
// The suffix needs to be removed in order to check against the list of tasks to run.
//
// Note: The 'tasksToRun' parameter only allows to specify the custom task name
// (without suffix), so it executes either all or nothing.
// It's currently not possible to just execute some occurrences of a custom task.
// This would require a more robust contract to identify task executions
// (e.g. via an 'id' that can be assigned to a specific execution in the configuration).
const taskWithoutSuffixCounter = taskName.replace(/--\d+$/, "");
return tasksToRun.includes(taskWithoutSuffixCounter);
});

this.taskLog.addWork(allTasks.length);

for (const taskName of allTasks) {
const taskFunction = this.tasks[taskName].task;

if (typeof taskFunction === "function") {
await this.executeTask(taskName, taskFunction, buildParams);
}
}
}

requiresDependencies(buildConfig) {
const tasksToRun = composeTaskList(Object.keys(this.tasks), buildConfig);
const allTasks = this.taskExecutionOrder.filter((taskName) => {
// There might be a numeric suffix in case a custom task is configured multiple times.
// The suffix needs to be removed in order to check against the list of tasks to run.
//
// Note: The 'tasksToRun' parameter only allows to specify the custom task name
// (without suffix), so it executes either all or nothing.
// It's currently not possible to just execute some occurrences of a custom task.
// This would require a more robust contract to identify task executions
// (e.g. via an 'id' that can be assigned to a specific execution in the configuration).
const taskWithoutSuffixCounter = taskName.replace(/--\d+$/, "");
return tasksToRun.includes(taskWithoutSuffixCounter);
});
return allTasks.some((taskName) => {
if (this.tasks[taskName].requiresDependencies) {
this.log.verbose(`Task ${taskName} for project ${this.project.getName()} requires dependencies`);
return true;
}
return false;
});
}

/**
* Adds progress related functionality to task function.
*
* @private
* @param {string} taskName Name of the task
* @param {Function} taskFunction Function which executed the task
* @param {object} taskParams Base parameters for all tasks
* @returns {Promise} Resolves when task has finished
*/
async executeTask(taskName, taskFunction, taskParams) {
this.taskLog.startWork(`Running task ${taskName}...`);
this._taskStart = performance.now();
await taskFunction(taskParams);
this.taskLog.completeWork(1);
if (process.env.UI5_LOG_TASK_PERF) {
this.taskLog.info(`Task succeeded in ${Math.round((performance.now() - this._taskStart))} ms`);
}
}

/**
* Appends the list of 'excludes' to the list of 'patterns'. To harmonize both lists, the 'excludes'
* are negated and the 'patternPrefix' is added to make them absolute.
*
* @private
* @param {string[]} patterns
* List of absolute default patterns.
* @param {string[]} excludes
* List of relative patterns to be excluded. Excludes with a leading "!" are meant to be re-included.
* @param {string} patternPrefix
* Prefix to be added to the excludes to make them absolute. The prefix must have a leading and a
* trailing "/".
*/
enhancePatternWithExcludes(patterns, excludes, patternPrefix) {
excludes.forEach((exclude) => {
if (exclude.startsWith("!")) {
patterns.push(`${patternPrefix}${exclude.slice(1)}`);
} else {
patterns.push(`!${patternPrefix}${exclude}`);
}
});
}
}

module.exports = AbstractBuilder;
Loading