diff --git a/index.js b/index.js
index 2c226c362..4ea2a17c2 100644
--- a/index.js
+++ b/index.js
@@ -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
diff --git a/lib/builder/ProjectBuildContext.js b/lib/builder/ProjectBuildContext.js
index f93ebb20f..270a13597 100644
--- a/lib/builder/ProjectBuildContext.js
+++ b/lib/builder/ProjectBuildContext.js
@@ -1,6 +1,6 @@
const ResourceTagCollection = require("@ui5/fs").ResourceTagCollection;
-const TAGS = Object.freeze({
+const STANDARD_TAGS = Object.freeze({
HideFromBuildResult: "ui5:HideFromBuildResult"
});
@@ -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)
});
}
diff --git a/lib/builder/builder.js b/lib/builder/builder.js
index 40129f859..0e0a6d0d6 100644
--- a/lib/builder/builder.js
+++ b/lib/builder/builder.js
@@ -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});
}
@@ -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(
diff --git a/lib/tasks/TaskUtil.js b/lib/tasks/TaskUtil.js
new file mode 100644
index 000000000..3e9261dad
--- /dev/null
+++ b/lib/tasks/TaskUtil.js
@@ -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, undefined
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.
+ * undefined
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;
diff --git a/lib/tasks/jsdoc/generateJsdoc.js b/lib/tasks/jsdoc/generateJsdoc.js
index c74369063..bc979dc22 100644
--- a/lib/tasks/jsdoc/generateJsdoc.js
+++ b/lib/tasks/jsdoc/generateJsdoc.js
@@ -32,7 +32,7 @@ const {resourceFactory} = require("@ui5/fs");
* @returns {Promise} Promise resolving with undefined
once data has been written
*/
const generateJsdoc = async function({
- buildContext,
+ taskUtil,
workspace,
dependencies,
options = {}
@@ -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({
diff --git a/lib/types/AbstractBuilder.js b/lib/types/AbstractBuilder.js
index 93281dd43..593df9ac8 100644
--- a/lib/types/AbstractBuilder.js
+++ b/lib/types/AbstractBuilder.js
@@ -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");
}
@@ -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
+ });
}
/**
@@ -50,7 +59,7 @@ class AbstractBuilder {
* @param {object} parameters.project Project configuration
* @param {object} parameters.log @ui5/logger
logger instance
*/
- addStandardTasks({resourceCollections, project, log, buildContext}) {
+ addStandardTasks({resourceCollections, project, log, taskUtil}) {
throw new Error("Function 'addStandardTasks' is not implemented");
}
@@ -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
@@ -96,13 +105,15 @@ 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
@@ -110,7 +121,7 @@ class AbstractBuilder {
Returns:
{Promise} Promise resolving with undefined once data has been written
*/
- return task({
+ const params = {
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
@@ -118,7 +129,12 @@ class AbstractBuilder {
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;
diff --git a/lib/types/application/ApplicationBuilder.js b/lib/types/application/ApplicationBuilder.js
index 605087c46..4c86ca890 100644
--- a/lib/types/application/ApplicationBuilder.js
+++ b/lib/types/application/ApplicationBuilder.js
@@ -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 " +
diff --git a/lib/types/application/applicationType.js b/lib/types/application/applicationType.js
index 78bb461fb..8f27d5ca9 100644
--- a/lib/types/application/applicationType.js
+++ b/lib/types/application/applicationType.js
@@ -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
diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js
index f6797aa73..a5b4935a4 100644
--- a/lib/types/library/LibraryBuilder.js
+++ b/lib/types/library/LibraryBuilder.js
@@ -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 " +
@@ -56,7 +56,7 @@ class LibraryBuilder extends AbstractBuilder {
}
return getTask("generateJsdoc").task({
- buildContext,
+ taskUtil,
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
@@ -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"
}
});
diff --git a/lib/types/library/libraryType.js b/lib/types/library/libraryType.js
index fa582b1ea..2a9576645 100644
--- a/lib/types/library/libraryType.js
+++ b/lib/types/library/libraryType.js
@@ -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
diff --git a/lib/types/themeLibrary/themeLibraryType.js b/lib/types/themeLibrary/themeLibraryType.js
index f1c28d903..5907e62d9 100644
--- a/lib/types/themeLibrary/themeLibraryType.js
+++ b/lib/types/themeLibrary/themeLibraryType.js
@@ -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
diff --git a/test/lib/builder/ProjectBuildContext.js b/test/lib/builder/ProjectBuildContext.js
index 30dd489d0..5bb4a2903 100644
--- a/test/lib/builder/ProjectBuildContext.js
+++ b/test/lib/builder/ProjectBuildContext.js
@@ -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"
@@ -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) => {
diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js
index 34315c47c..45405ec99 100644
--- a/test/lib/builder/builder.js
+++ b/test/lib/builder/builder.js
@@ -77,6 +77,7 @@ async function checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, des
test.afterEach.always((t) => {
sinon.restore();
+ mock.stopAll();
});
test("Build application.a", (t) => {
@@ -516,8 +517,7 @@ test("Build theme.j even without an library", (t) => {
test.serial("Cleanup", async (t) => {
const BuildContext = require("../../../lib/builder/BuildContext");
- const projectContext = {isRootProject: sinon.stub()};
- const createProjectContextStub = sinon.stub(BuildContext.prototype, "createProjectContext").returns(projectContext);
+ const createProjectContextStub = sinon.spy(BuildContext.prototype, "createProjectContext");
const executeCleanupTasksStub = sinon.stub(BuildContext.prototype, "executeCleanupTasks").resolves();
const applicationType = require("../../../lib/types/application/applicationType");
const appBuildStub = sinon.stub(applicationType, "build").resolves();
diff --git a/test/lib/tasks/jsdoc/generateJsdoc.js b/test/lib/tasks/jsdoc/generateJsdoc.js
index e4c7d58a0..7035018f4 100644
--- a/test/lib/tasks/jsdoc/generateJsdoc.js
+++ b/test/lib/tasks/jsdoc/generateJsdoc.js
@@ -224,11 +224,11 @@ test.serial("generateJsdoc", async (t) => {
};
const registerCleanupTaskStub = sinon.stub();
- const buildContext = {
+ const taskUtil = {
registerCleanupTask: registerCleanupTaskStub
};
await generateJsdoc({
- buildContext,
+ taskUtil,
workspace,
dependencies: "dependencies",
options: {
@@ -306,11 +306,11 @@ test.serial("generateJsdoc with missing resources", async (t) => {
write: writeStub
};
const registerCleanupTaskStub = sinon.stub();
- const buildContext = {
+ const taskUtil = {
registerCleanupTask: registerCleanupTaskStub
};
await generateJsdoc({
- buildContext,
+ taskUtil,
workspace,
dependencies: "dependencies",
options: {