Skip to content

Commit

Permalink
[FEATURE] ProjectBuilder: Add outputStyle option to request flat bu…
Browse files Browse the repository at this point in the history
…ild output (#624)

New option `outputStyle`. Whether to omit the `/resources/<namespace>`
directory structure in the build output.

Resolves SAP/ui5-tooling#507

---------

Co-authored-by: Yavor Ivanov <yavor.ivanov@sap.com>
Co-authored-by: Yavor Ivanov <d3xter666@users.noreply.github.com>
Co-authored-by: Günter Klatt <57760635+KlattG@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 12, 2023
1 parent 1ddfd37 commit 79312fc
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 52 deletions.
69 changes: 62 additions & 7 deletions lib/build/ProjectBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BuildLogger from "@ui5/logger/internal/loggers/Build";
import composeProjectList from "./helpers/composeProjectList.js";
import BuildContext from "./helpers/BuildContext.js";
import prettyHrtime from "pretty-hrtime";
import OutputStyleEnum from "./helpers/ProjectBuilderOutputStyle.js";

/**
* @public
Expand All @@ -24,6 +25,8 @@ class ProjectBuilder {
* Whether to create a build manifest file for the root project.
* This is currently only supported for projects of type 'library' and 'theme-library'
* No other dependencies can be included in the build result.
* @property {module:@ui5/project/build/ProjectBuilderOutputStyle} [outputStyle=Default]
* Processes build results into a specific directory structure.
* @property {Array.<string>} [includedTasks=[]] List of tasks to be included
* @property {Array.<string>} [excludedTasks=[]] List of tasks to be excluded.
* If the wildcard '*' is provided, only the included tasks will be executed.
Expand Down Expand Up @@ -165,10 +168,13 @@ class ProjectBuilder {
return filterProject(projectName);
});

if (this._buildContext.getBuildConfig().createBuildManifest && requestedProjects.length > 1) {
throw new Error(
`It is currently not supported to request the creation of a build manifest ` +
`while including any dependencies into the build result`);
if (requestedProjects.length > 1) {
const {createBuildManifest} = this._buildContext.getBuildConfig();
if (createBuildManifest) {
throw new Error(
`It is currently not supported to request the creation of a build manifest ` +
`while including any dependencies into the build result`);
}
}

const projectBuildContexts = await this._createRequiredBuildContexts(requestedProjects);
Expand Down Expand Up @@ -360,13 +366,25 @@ class ProjectBuilder {
const project = projectBuildContext.getProject();
const taskUtil = projectBuildContext.getTaskUtil();
const buildConfig = this._buildContext.getBuildConfig();
const {createBuildManifest, outputStyle} = buildConfig;
// Output styles are applied only for the root project
const isRootProject = taskUtil.isRootProject();

let readerStyle = "dist";
if (createBuildManifest ||
(isRootProject && outputStyle === OutputStyleEnum.Namespace && project.getType() === "application")) {
// Ensure buildtime (=namespaced) style when writing with a build manifest or when explicitly requested
readerStyle = "buildtime";
} else if (isRootProject && outputStyle === OutputStyleEnum.Flat) {
readerStyle = "flat";
}

const reader = project.getReader({
// Force buildtime (=namespaced) style when writing with a build manifest
style: taskUtil.isRootProject() && buildConfig.createBuildManifest ? "buildtime" : "dist"
style: readerStyle
});
const resources = await reader.byGlob("/**/*");

if (taskUtil.isRootProject() && buildConfig.createBuildManifest) {
if (createBuildManifest) {
// Create and write a build manifest metadata file
const {
default: createBuildManifest
Expand All @@ -386,6 +404,43 @@ class ProjectBuilder {
}
return target.write(resource);
}));

if (isRootProject &&
outputStyle === OutputStyleEnum.Flat &&
project.getType() !== "application" /* application type is with a default flat build output structure */) {
const namespace = project.getNamespace();
const libraryResourcesPrefix = `/resources/${namespace}/`;
const testResourcesPrefix = "/test-resources/";
const namespacedRegex = new RegExp(`/(resources|test-resources)/${namespace}`);
const processedResourcesSet = resources.reduce((acc, resource) => acc.add(resource.getPath()), new Set());

// If outputStyle === "Flat", then the FlatReader would have filtered
// some resources. We now need to get all of the available resources and
// do an intersection with the processed/bundled ones.
const defaultReader = project.getReader();
const defaultResources = await defaultReader.byGlob("/**/*");
const flatDefaultResources = defaultResources.map((resource) => ({
flatResource: resource.getPath().replace(namespacedRegex, ""),
originalPath: resource.getPath(),
}));

const skippedResources = flatDefaultResources.filter((resource) => {
return processedResourcesSet.has(resource.flatResource) === false;
});

skippedResources.forEach((resource) => {
if (resource.originalPath.startsWith(testResourcesPrefix)) {
this.#log.verbose(
`Omitting ${resource.originalPath} from build result. File is part of ${testResourcesPrefix}.`
);
} else if (!resource.originalPath.startsWith(libraryResourcesPrefix)) {
this.#log.warn(
`Omitting ${resource.originalPath} from build result. ` +
`File is not within project namespace '${namespace}'.`
);
}
});
}
}

async _executeCleanupTasks(force) {
Expand Down
30 changes: 28 additions & 2 deletions lib/build/helpers/BuildContext.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ProjectBuildContext from "./ProjectBuildContext.js";
import OutputStyleEnum from "./ProjectBuilderOutputStyle.js";

/**
* Context of a build process
Expand All @@ -12,6 +13,7 @@ class BuildContext {
cssVariables = false,
jsdoc = false,
createBuildManifest = false,
outputStyle = OutputStyleEnum.Default,
includedTasks = [], excludedTasks = [],
} = {}) {
if (!graph) {
Expand All @@ -21,10 +23,12 @@ class BuildContext {
throw new Error(`Missing parameter 'taskRepository'`);
}

if (createBuildManifest && !["library", "theme-library"].includes(graph.getRoot().getType())) {
const rootProjectType = graph.getRoot().getType();

if (createBuildManifest && !["library", "theme-library"].includes(rootProjectType)) {
throw new Error(
`Build manifest creation is currently not supported for projects of type ` +
graph.getRoot().getType());
rootProjectType);
}

if (createBuildManifest && selfContained) {
Expand All @@ -33,12 +37,34 @@ class BuildContext {
`self-contained builds`);
}

if (createBuildManifest && outputStyle === OutputStyleEnum.Flat) {
throw new Error(
`Build manifest creation is not supported in conjunction with flat build output`);
}
if (outputStyle !== OutputStyleEnum.Default) {
if (rootProjectType === "theme-library") {
throw new Error(
`${outputStyle} build output style is currently not supported for projects of type` +
`theme-library since they commonly have more than one namespace. ` +
`Currently only the Default output style is supported for this project type.`
);
}
if (rootProjectType === "module") {
throw new Error(
`${outputStyle} build output style is currently not supported for projects of type` +
`module. Their path mappings configuration can't be mapped to any namespace.` +
`Currently only the Default output style is supported for this project type.`
);
}
}

this._graph = graph;
this._buildConfig = {
selfContained,
cssVariables,
jsdoc,
createBuildManifest,
outputStyle,
includedTasks,
excludedTasks,
};
Expand Down
18 changes: 18 additions & 0 deletions lib/build/helpers/ProjectBuilderOutputStyle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Processes build results into a specific directory structure.
*
* @public
* @readonly
* @enum {string}
* @property {string} Default The default directory structure for every project type.
* For applications this is identical to "Flat" and for libraries to "Namespace".
* Other types have a more distinct default output style.
* @property {string} Flat Omits the project namespace and the "resources" directory.
* @property {string} Namespace Respects project namespace and the "resources" directory.
* @module @ui5/project/build/ProjectBuilderOutputStyle
*/
export default {
Default: "Default",
Flat: "Flat",
Namespace: "Namespace"
};
10 changes: 7 additions & 3 deletions lib/graph/ProjectGraph.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import OutputStyleEnum from "../build/helpers/ProjectBuilderOutputStyle.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("graph:ProjectGraph");


/**
* A rooted, directed graph representing a UI5 project, its dependencies and available extensions.
* <br><br>
Expand Down Expand Up @@ -540,15 +542,17 @@ class ProjectGraph {
* This is currently only supported for projects of type 'library' and 'theme-library'
* @param {Array.<string>} [parameters.includedTasks=[]] List of tasks to be included
* @param {Array.<string>} [parameters.excludedTasks=[]] List of tasks to be excluded.
* If the wildcard '*' is provided, only the included tasks will be executed.
* @param {module:@ui5/project/build/ProjectBuilderOutputStyle} [parameters.outputStyle=Default]
* Processes build results into a specific directory structure.
* @returns {Promise} Promise resolving to <code>undefined</code> once build has finished
*/
async build({
destPath, cleanDest = false,
includedDependencies = [], excludedDependencies = [],
dependencyIncludes,
selfContained = false, cssVariables = false, jsdoc = false, createBuildManifest = false,
includedTasks = [], excludedTasks = []
includedTasks = [], excludedTasks = [],
outputStyle = OutputStyleEnum.Default
}) {
this.seal(); // Do not allow further changes to the graph
if (this._built) {
Expand All @@ -566,7 +570,7 @@ class ProjectGraph {
buildConfig: {
selfContained, cssVariables, jsdoc,
createBuildManifest,
includedTasks, excludedTasks,
includedTasks, excludedTasks, outputStyle,
}
});
await builder.build({
Expand Down
Loading

0 comments on commit 79312fc

Please sign in to comment.