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

[FEATURE] buildThemes: Add filtering for available themes #419

Merged
merged 8 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions lib/builder/BuildContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
* @memberof module:@ui5/builder.builder
*/
class BuildContext {
constructor() {
constructor({rootProject}) {
this.projectBuildContexts = [];
this.rootProject = rootProject;
}

getRootProject() {
return this.rootProject;
}

createProjectContext({project, resources}) {
Expand Down Expand Up @@ -36,14 +41,18 @@ class BuildContext {
*/
class ProjectBuildContext {
constructor({buildContext, project, resources}) {
// this.buildContext = buildContext;
// this.project = project;
this._buildContext = buildContext;
this._project = project;
// this.resources = resources;
this.queues = {
cleanup: []
};
}

isRootProject() {
return this._project === this._buildContext.getRootProject();
}

registerCleanupTask(callback) {
this.queues.cleanup.push(callback);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ module.exports = {
virBasePath: "/"
});

const buildContext = new BuildContext();
const buildContext = new BuildContext({rootProject: tree});
const cleanupSigHooks = registerCleanupSigHooks(buildContext);

const projects = {}; // Unique project index to prevent building the same project multiple times
Expand Down Expand Up @@ -329,7 +329,7 @@ module.exports = {
});

const projectContext = buildContext.createProjectContext({
// project, // TODO 2.0: Add project facade object/instance here
project, // TODO 2.0: Add project facade object/instance here
resources: {
workspace,
dependencies: resourceCollections.dependencies
Expand All @@ -355,7 +355,7 @@ module.exports = {

return workspace.byGlob("/**/*.*").then((resources) => {
return Promise.all(resources.map((resource) => {
if (project === tree && project.type === "application" && project.metadata.namespace) {
if (projectContext.isRootProject() && project.type === "application" && project.metadata.namespace) {
// Root-application projects only: Remove namespace prefix if given
resource.setPath(resource.getPath().replace(
new RegExp(`^/resources/${project.metadata.namespace}`), ""));
Expand Down
123 changes: 90 additions & 33 deletions lib/tasks/buildThemes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const path = require("path");
const themeBuilder = require("../processors/themeBuilder");
const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;
const fsInterface = require("@ui5/fs").fsInterface;
const log = require("@ui5/logger").getLogger("builder:tasks:buildThemes");

/**
* Task to build a library theme.
Expand All @@ -14,60 +16,115 @@ const fsInterface = require("@ui5/fs").fsInterface;
* @param {string} parameters.options.projectName Project name
* @param {string} parameters.options.inputPattern Search pattern for *.less files to be built
* @param {string} [parameters.options.librariesPattern] Search pattern for .library files
* @param {string} [parameters.options.themesPattern] Search pattern for sap.ui.core theme folders
* @param {boolean} [parameters.options.compress=true]
* @param {boolean} [parameters.options.cssVariables=false]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New optional parameter themesPattern also needs to be documented here

* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = function({workspace, dependencies, options}) {
module.exports = async function({workspace, dependencies, options}) {
const combo = new ReaderCollectionPrioritized({
name: `theme - prioritize workspace over dependencies: ${options.projectName}`,
readers: [workspace, dependencies]
});

const promises = [workspace.byGlob(options.inputPattern)];
const compress = options.compress === undefined ? true : options.compress;

const pAllResources = workspace.byGlob(options.inputPattern);
let pAvailableLibraries;
let pAvailableThemes;
if (options.librariesPattern) {
// If a librariesPattern is given
// we will use it to reduce the set of libraries a theme will be built for
promises.push(combo.byGlob(options.librariesPattern));
pAvailableLibraries = combo.byGlob(options.librariesPattern);
}
if (options.themesPattern) {
// If a themesPattern is given
// we will use it to reduce the set of themes that will be built
pAvailableThemes = combo.byGlob(options.themesPattern, {nodir: false});
}

const compress = options.compress === undefined ? true : options.compress;

return Promise.all(promises).then(([allResources, availableLibraries]) => {
if (!availableLibraries || availableLibraries.length === 0) {
// Try to build all themes
return allResources;
}
/* Don't try to build themes for libraries that are not available
(maybe replace this with something more aware of which dependencies are optional and therefore
legitimately missing and which not (fault case))
/* Don't try to build themes for libraries that are not available
(maybe replace this with something more aware of which dependencies are optional and therefore
legitimately missing and which not (fault case))
*/
const availableLibraryPaths = availableLibraries.map((resource) => {
let availableLibraries;
if (pAvailableLibraries) {
availableLibraries = (await pAvailableLibraries).map((resource) => {
return resource.getPath().replace(/[^/]*\.library/i, "");
});
}
let availableThemes;
if (pAvailableThemes) {
availableThemes = (await pAvailableThemes)
.filter((resource) => resource.getStatInfo().isDirectory())
.map((resource) => {
return path.basename(resource.getPath());
});
}

let allResources = await pAllResources;

const isAvailable = function(resource) {
let libraryAvailable = false;
let themeAvailable = false;
const resourcePath = resource.getPath();
const themeName = path.basename(path.dirname(resourcePath));

const isAvailable = function(resource) {
for (let i = availableLibraryPaths.length - 1; i >= 0; i--) {
if (resource.getPath().indexOf(availableLibraryPaths[i]) === 0) {
return true;
if (!availableLibraries || availableLibraries.length === 0) {
libraryAvailable = true; // If no libraries are found, build themes for all libraries
} else {
for (let i = availableLibraries.length - 1; i >= 0; i--) {
if (resourcePath.startsWith(availableLibraries[i])) {
libraryAvailable = true;
}
}
return false;
};
}

return allResources.filter(isAvailable);
}).then((resources) => {
return themeBuilder({
resources,
fs: fsInterface(combo),
options: {
compress,
cssVariables: !!options.cssVariables
if (!availableThemes || availableThemes.length === 0) {
themeAvailable = true; // If no themes are found, build all themes
} else {
themeAvailable = availableThemes.includes(themeName);
}

if (log.isLevelEnabled("verbose")) {
if (!libraryAvailable) {
log.verbose(`Skipping ${resourcePath}: Library is not available`);
}
});
}).then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
if (!themeAvailable) {
log.verbose(`Skipping ${resourcePath}: sap.ui.core theme '${themeName}' is not available. ` +
"If you experience missing themes, check whether you have added the corresponding theme " +
"library to your projects dependencies and make sure that your custom themes contain " +
"resources for the sap.ui.core namespace.");
}
}

// Only build if library and theme are available
return libraryAvailable && themeAvailable;
};

if (availableLibraries || availableThemes) {
if (log.isLevelEnabled("verbose")) {
log.verbose("Filtering themes to be built:");
if (availableLibraries) {
log.verbose(`Available libraries: ${availableLibraries.join(", ")}`);
}
if (availableThemes) {
log.verbose(`Available sap.ui.core themes: ${availableThemes.join(", ")}`);
}
}
allResources = allResources.filter(isAvailable);
}

const processedResources = await themeBuilder({
resources: allResources,
fs: fsInterface(combo),
options: {
compress,
cssVariables: !!options.cssVariables
}
});

await Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
};
3 changes: 2 additions & 1 deletion lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ class LibraryBuilder extends AbstractBuilder {
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
librariesPattern: "/resources/**/*.library",
librariesPattern: !buildContext.isRootProject() ? "/resources/**/*.library" : undefined,
themesPattern: !buildContext.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined,
inputPattern: "/resources/**/themes/*/library.source.less"
}
});
Expand Down
3 changes: 2 additions & 1 deletion lib/types/themeLibrary/ThemeLibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class ThemeLibraryBuilder extends AbstractBuilder {
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
librariesPattern: "/resources/**/*.library",
librariesPattern: !buildContext.isRootProject() ? "/resources/**/*.library" : undefined,
themesPattern: !buildContext.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined,
inputPattern: "/resources/**/themes/*/library.source.less"
}
});
Expand Down
9 changes: 5 additions & 4 deletions test/lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,9 @@ test("Build theme.j even without an library", (t) => {

test.serial("Cleanup", async (t) => {
const BuildContext = require("../../../lib/builder/BuildContext");
const projectContext = "project context";
const projectContext = {isRootProject: sinon.stub()};
const createProjectContextStub = sinon.stub(BuildContext.prototype, "createProjectContext").returns(projectContext);
const executeCleanupTasksStub = sinon.stub(BuildContext.prototype, "executeCleanupTasks").returns(projectContext);
const executeCleanupTasksStub = sinon.stub(BuildContext.prototype, "executeCleanupTasks").resolves();
const applicationType = require("../../../lib/types/application/applicationType");
const appBuildStub = sinon.stub(applicationType, "build").resolves();

Expand Down Expand Up @@ -547,10 +547,11 @@ test.serial("Cleanup", async (t) => {
t.deepEqual(appBuildStub.callCount, 1, "Build called once");
t.deepEqual(createProjectContextStub.callCount, 1, "One project context got created");
const createProjectContextParams = createProjectContextStub.getCall(0).args[0];
t.truthy(createProjectContextParams.project, "project object provided");
t.truthy(createProjectContextParams.resources.workspace, "resources.workspace object provided");
t.truthy(createProjectContextParams.resources.dependencies, "resources.dependencies object provided");
t.deepEqual(Object.keys(createProjectContextParams), ["resources"],
"resource parameter (and no others) provided");
t.deepEqual(Object.keys(createProjectContextParams), ["project", "resources"],
"resource and project parameters provided");
t.deepEqual(executeCleanupTasksStub.callCount, 1, "Cleanup called once");

// Error case
Expand Down
Loading