Skip to content

Commit

Permalink
[FEATURE] Implement TaskUtil class
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomByte committed Aug 11, 2020
1 parent 6efffd6 commit a7074ae
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 35 deletions.
6 changes: 5 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ module.exports = {
/**
* @type {import('./lib/tasks/taskRepository')}
*/
taskRepository: "./lib/tasks/taskRepository"
taskRepository: "./lib/tasks/taskRepository",
/**
* @type {import('./lib/tasks/TaskUtil')}
*/
TaskUtil: "./lib/tasks/TaskUtil"
},
/**
* @private
Expand Down
6 changes: 3 additions & 3 deletions lib/builder/ProjectBuildContext.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ResourceTagCollection = require("@ui5/fs").ResourceTagCollection;

const TAGS = Object.freeze({
const STANDARD_TAGS = Object.freeze({
HideFromBuildResult: "ui5:HideFromBuildResult"
});

Expand All @@ -23,10 +23,10 @@ class ProjectBuildContext {
cleanup: []
};

this.TAGS = TAGS;
this.STANDARD_TAGS = STANDARD_TAGS;

this._resourceTagCollection = new ResourceTagCollection({
allowedTags: Object.values(this.TAGS)
allowedTags: Object.values(this.STANDARD_TAGS)
});
}

Expand Down
13 changes: 12 additions & 1 deletion lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ module.exports = {
}
});

const TaskUtil = require("../tasks/TaskUtil");
const taskUtil = new TaskUtil({
projectBuildContext: projectContext
});

if (dev && devExcludeProject.indexOf(project.metadata.name) !== -1) {
projectTasks = composeTaskList({dev: false, selfContained, includedTasks, excludedTasks});
}
Expand All @@ -344,13 +349,19 @@ module.exports = {
tasks: projectTasks,
project,
parentLogger: log,
buildContext: projectContext
taskUtil
}).then(() => {
log.verbose("Finished building project %s. Writing out files...", project.metadata.name);
buildLogger.completeWork(1);

return workspace.byGlob("/**/*.*").then((resources) => {
const tagCollection = projectContext.getResourceTagCollection();
return Promise.all(resources.map((resource) => {
if (tagCollection.getTag(resource, projectContext.STANDARD_TAGS.HideFromBuildResult)) {
log.verbose(`Skipping write of resource tagged as "HideFromBuildResult": ` +
resource.getPath());
return; // Skip target write for this resource
}
if (projectContext.isRootProject() && project.type === "application" && project.metadata.namespace) {
// Root-application projects only: Remove namespace prefix if given
resource.setPath(resource.getPath().replace(
Expand Down
133 changes: 133 additions & 0 deletions lib/tasks/TaskUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Convenience functions for UI5 Builder tasks.
* An instance of this class is passed to every standard UI5 Builder task.
* Custom tasks that define a specification version >= 2.2 will also receive an instance
* of this class when called.
*
* The set of functions that can be accessed by a custom tasks depends on the specification
* version defined for the extension.
*
* @public
* @memberof module:@ui5/builder.tasks
*/
class TaskUtil {
/**
* Standard Build Tags. See UI5 Tooling RFC 0008 for details.
*
* @public
* @typedef {object} StandardBuildTags
* @property {string} HideFromBuildResult
* Setting this tag to true for a resource will prevent it from being written to the build target
*/

/**
* Constructor
*
* @param {object} parameters
* @param {module:@ui5/builder.builder.ProjectBuildContext} parameters.projectBuildContext ProjectBuildContext
* @public
* @hideconstructor
*/
constructor({projectBuildContext}) {
this._projectBuildContext = projectBuildContext;

/**
* @member {StandardBuildTags}
* @public
*/
this.STANDARD_TAGS = this._projectBuildContext.STANDARD_TAGS;
}

/**
* Stores a tag with value for a given resource's path. Note that the tag is independent of the supplied
* resource instance. For two resource instances with the same path, the same tag value is returned.
* If the path of a resource is changed, any tag information previously stored for that resource is lost.
*
* @param {module:@ui5/fs.Resource} resource Resource the tag should be stored for
* @param {string} tag Name of the tag.
* Currently only the [STANDARD_TAGS]{@link module:@ui5/builder.tasks.TaskUtil#STANDARD_TAGS} are allowed
* @param {string|boolean|integer} [value=true] Tag value. Must be primitive
* @public
*/
setTag(resource, tag, value) {
return this._projectBuildContext.getResourceTagCollection().setTag(resource, tag, value);
}

/**
* Retrieves the value for a stored tag. If no value is stored, <code>undefined</code> is returned.
*
* @param {module:@ui5/fs.Resource} resource Resource the tag should be retrieved for
* @param {string} tag Name of the tag
* @returns {string|boolean|integer} Tag value for the given resource.
* <code>undefined</code> if no value is available
* @public
*/
getTag(resource, tag) {
return this._projectBuildContext.getResourceTagCollection().getTag(resource, tag);
}

/**
* Clears the value of a tag stored for the given resource's path.
* It's like the tag was never set for that resource.
*
* @param {module:@ui5/fs.Resource} resource Resource the tag should be cleared for
* @param {string} tag Tag
* @public
*/
clearTag(resource, tag) {
return this._projectBuildContext.getResourceTagCollection().clearTag(resource, tag);
}

/**
* Check whether the project currently being build is the root project.
*
* @returns {boolean} True if the currently built project is the root project
* @public
*/
isRootProject() {
return this._projectBuildContext.isRootProject();
}

/**
* Register a function that must be executed once the build is finished. This can be used to for example
* cleanup files temporarily created on the file system. If the callback returns a Promise, it will be waited for.
*
* @param {Function} callback Callback to register. If it returns a Promise, it will be waited for
* @public
*/
registerCleanupTask(callback) {
return this._projectBuildContext.registerCleanupTask(callback);
}

/**
* Get an interface to an instance of this class that only provides those functions
* that are supported by the given custom middleware extension specification version.
*
* @param {string} specVersion Specification Version of custom middleware extension
* @returns {object} An object with bound instance methods supported by the given specification version
*/
getInterface(specVersion) {
const baseInterface = {
STANDARD_TAGS: this.STANDARD_TAGS,
setTag: this.setTag.bind(this),
clearTag: this.clearTag.bind(this),
getTag: this.getTag.bind(this),
isRootProject: this.isRootProject.bind(this),
registerCleanupTask: this.registerCleanupTask.bind(this)
};
switch (specVersion) {
case "0.1":
case "1.0":
case "1.1":
case "2.0":
case "2.1":
return undefined;
case "2.2":
return baseInterface;
default:
throw new Error(`TaskUtil: Unknown or unsupported specification version ${specVersion}`);
}
}
}

module.exports = TaskUtil;
4 changes: 2 additions & 2 deletions lib/tasks/jsdoc/generateJsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const {resourceFactory} = require("@ui5/fs");
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
const generateJsdoc = async function({
buildContext,
taskUtil,
workspace,
dependencies,
options = {}
Expand All @@ -46,7 +46,7 @@ const generateJsdoc = async function({
const {sourcePath: resourcePath, targetPath, tmpPath, cleanup} =
await generateJsdoc._createTmpDirs(projectName);

buildContext.registerCleanupTask(cleanup);
taskUtil.registerCleanupTask(cleanup);

const [writtenResourcesCount] = await Promise.all([
generateJsdoc._writeResourcesToDir({
Expand Down
32 changes: 24 additions & 8 deletions lib/types/AbstractBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class AbstractBuilder {
* @param {GroupLogger} parameters.parentLogger Logger to use
* @param {object} parameters.buildContext
*/
constructor({resourceCollections, project, parentLogger, buildContext}) {
constructor({resourceCollections, project, parentLogger, taskUtil}) {
if (new.target === AbstractBuilder) {
throw new TypeError("Class 'AbstractBuilder' is abstract");
}
Expand All @@ -35,8 +35,17 @@ class AbstractBuilder {

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

/**
Expand All @@ -50,7 +59,7 @@ class AbstractBuilder {
* @param {object} parameters.project Project configuration
* @param {object} parameters.log <code>@ui5/logger</code> logger instance
*/
addStandardTasks({resourceCollections, project, log, buildContext}) {
addStandardTasks({resourceCollections, project, log, taskUtil}) {
throw new Error("Function 'addStandardTasks' is not implemented");
}

Expand All @@ -63,7 +72,7 @@ class AbstractBuilder {
* @param {object} parameters.buildContext
* @param {object} parameters.project Project configuration
*/
addCustomTasks({resourceCollections, project, buildContext}) {
addCustomTasks({resourceCollections, project, taskUtil}) {
const projectCustomTasks = project.builder && project.builder.customTasks;
if (!projectCustomTasks || projectCustomTasks.length === 0) {
return; // No custom tasks defined
Expand Down Expand Up @@ -96,29 +105,36 @@ class AbstractBuilder {
}
}
// Create custom task if not already done (task might be referenced multiple times, first one wins)
const {task} = taskRepository.getTask(taskDef.name);
const {/* specVersion, */ task} = taskRepository.getTask(taskDef.name);
const execTask = function() {
/* 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} 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
*/
return task({
const params = {
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
projectNamespace: project.metadata.namespace,
configuration: taskDef.configuration
}
});
};
// TODO: Decide whether custom tasks should already get access to TaskUtil
// if (specVersion === "2.2") {
// params.taskUtil = taskUtil.getInterface(specVersion);
// }
return task(params);
};

this.tasks[newTaskName] = execTask;
Expand Down
2 changes: 1 addition & 1 deletion lib/types/application/ApplicationBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const AbstractBuilder = require("../AbstractBuilder");
const {getTask} = require("../../tasks/taskRepository");

class ApplicationBuilder extends AbstractBuilder {
addStandardTasks({resourceCollections, project, log}) {
addStandardTasks({resourceCollections, project, log, taskUtil}) {
if (!project.metadata.namespace) {
// TODO 3.0: Throw here
log.info("Skipping some tasks due to missing application namespace information. If your project contains " +
Expand Down
4 changes: 2 additions & 2 deletions lib/types/application/applicationType.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module.exports = {
format: function(project) {
return new ApplicationFormatter({project}).format();
},
build: function({resourceCollections, tasks, project, parentLogger, buildContext}) {
return new ApplicationBuilder({resourceCollections, project, parentLogger, buildContext}).build(tasks);
build: function({resourceCollections, tasks, project, parentLogger, taskUtil}) {
return new ApplicationBuilder({resourceCollections, project, parentLogger, taskUtil}).build(tasks);
},

// Export type classes for extensibility
Expand Down
8 changes: 4 additions & 4 deletions lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const AbstractBuilder = require("../AbstractBuilder");
const {getTask} = require("../../tasks/taskRepository");

class LibraryBuilder extends AbstractBuilder {
addStandardTasks({resourceCollections, project, log, buildContext}) {
addStandardTasks({resourceCollections, project, log, taskUtil}) {
if (!project.metadata.namespace) {
// TODO 3.0: Throw here
log.info("Skipping some tasks due to missing library namespace information. Your project " +
Expand Down Expand Up @@ -56,7 +56,7 @@ class LibraryBuilder extends AbstractBuilder {
}

return getTask("generateJsdoc").task({
buildContext,
taskUtil,
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
Expand Down Expand Up @@ -153,8 +153,8 @@ class LibraryBuilder extends AbstractBuilder {
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
librariesPattern: !buildContext.isRootProject() ? "/resources/**/*.library" : undefined,
themesPattern: !buildContext.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined,
librariesPattern: !taskUtil.isRootProject() ? "/resources/**/*.library" : undefined,
themesPattern: !taskUtil.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined,
inputPattern: "/resources/**/themes/*/library.source.less"
}
});
Expand Down
4 changes: 2 additions & 2 deletions lib/types/library/libraryType.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module.exports = {
format: function(project) {
return new LibraryFormatter({project}).format();
},
build: function({resourceCollections, tasks, project, parentLogger, buildContext}) {
return new LibraryBuilder({resourceCollections, project, parentLogger, buildContext}).build(tasks);
build: function({resourceCollections, tasks, project, parentLogger, taskUtil}) {
return new LibraryBuilder({resourceCollections, project, parentLogger, taskUtil}).build(tasks);
},

// Export type classes for extensibility
Expand Down
4 changes: 2 additions & 2 deletions lib/types/themeLibrary/themeLibraryType.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module.exports = {
format: function(project) {
return new ThemeLibraryFormatter({project}).format();
},
build: function({resourceCollections, tasks, project, parentLogger, buildContext}) {
return new ThemeLibraryBuilder({resourceCollections, project, parentLogger, buildContext}).build(tasks);
build: function({resourceCollections, tasks, project, parentLogger, taskUtil}) {
return new ThemeLibraryBuilder({resourceCollections, project, parentLogger, taskUtil}).build(tasks);
},

// Export type classes for extensibility
Expand Down
6 changes: 3 additions & 3 deletions test/lib/builder/ProjectBuildContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ test("executeCleanupTasks", (t) => {
t.is(task2.callCount, 1, "my task 2", "Cleanup task 2 got called");
});

test("TAGS constant", (t) => {
test("STANDARD_TAGS constant", (t) => {
const projectBuildContext = new ProjectBuildContext({
buildContext: {
getRootProject: () => "root project"
Expand All @@ -84,9 +84,9 @@ test("TAGS constant", (t) => {
resources: "resources"
});

t.deepEqual(projectBuildContext.TAGS, {
t.deepEqual(projectBuildContext.STANDARD_TAGS, {
HideFromBuildResult: "ui5:HideFromBuildResult"
}, "Exposes correct TAGS constant");
}, "Exposes correct STANDARD_TAGS constant");
});

test.serial("getResourceTagCollection", (t) => {
Expand Down
Loading

0 comments on commit a7074ae

Please sign in to comment.