Skip to content

Commit 9045e11

Browse files
committed
Implement build mechanics II
1 parent 143cb50 commit 9045e11

File tree

22 files changed

+637
-153
lines changed

22 files changed

+637
-153
lines changed

lib/buildDefinitions/ApplicationBuilder.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ const AbstractBuilder = require("./AbstractBuilder");
22

33
class ApplicationBuilder extends AbstractBuilder {
44
addStandardTasks({resourceCollections, project, log, taskUtil, getTask}) {
5+
// TODO: Refactor API to only addTasks() with actual task name and options
6+
// Common parameters "workspace" and "taskUtil" are always passed to all tasks
7+
// Additionally, tasks can request access to dependencies. In that case, a dependency
8+
// reader is provided too and the build ensures that all relevant dependencies
9+
// are built beforehand
10+
511
this.addTask("escapeNonAsciiCharacters", async () => {
612
return getTask("escapeNonAsciiCharacters").task({
713
workspace: resourceCollections.workspace,

lib/buildDefinitions/LibraryBuilder.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ class LibraryBuilder extends AbstractBuilder {
146146
dependencies: resourceCollections.dependencies,
147147
taskUtil,
148148
options: {
149-
project: project,
150149
excludes: project.getLibraryPreloadExcludes()
151150
}
152151
});

lib/buildHelpers/BuildContext.js

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const ResourceTagCollection = require("@ui5/fs").ResourceTagCollection;
2-
const resourceFactory = require("@ui5/fs").resourceFactory;
32
const ProjectBuildContext = require("./ProjectBuildContext");
43

54
// Note: When adding standard tags, always update the public documentation in TaskUtil
@@ -26,9 +25,6 @@ class BuildContext {
2625
allowedTags: Object.values(GLOBAL_TAGS)
2726
});
2827
this.options = options;
29-
30-
this._readerCollectionCache = {};
31-
this._readerCollectionCacheWithTestResources = {};
3228
}
3329

3430
getRootProject() {
@@ -59,28 +55,16 @@ class BuildContext {
5955
return this._resourceTagCollection;
6056
}
6157

62-
async getDependenciesReader({projectName, includeTestResources}) {
63-
if (!includeTestResources && this._readerCollectionCache[projectName]) {
64-
return this._readerCollectionCache[projectName];
65-
} else if (includeTestResources && this._readerCollectionCacheWithTestResources[projectName]) {
66-
return this._readerCollectionCacheWithTestResources[projectName];
67-
}
68-
const readers = [];
69-
await this._graph.traverseBreadthFirst(async function(dep) {
70-
readers.push(dep.getReader({
71-
includeTestResources
72-
}));
73-
}, projectName);
74-
const readerCollection = resourceFactory.createReaderCollection({
75-
name: `Dependency reader collection for project ${projectName}`,
76-
readers
77-
});
7858

79-
if (!includeTestResources) {
80-
return this._readerCollectionCache[projectName] = readerCollection;
81-
} else if (includeTestResources) {
82-
return this._readerCollectionCacheWithTestResources[projectName] = readerCollection;
83-
}
59+
/**
60+
* Retrieve a single project from the dependency graph
61+
*
62+
* @param {string} projectName Name of the project to retrieve
63+
* @returns {module:@ui5/project.specifications.Project|undefined}
64+
* project instance or undefined if the project is unknown to the graph
65+
*/
66+
getProject(projectName) {
67+
return this._graph.getProject(projectName);
8468
}
8569
}
8670

lib/buildHelpers/ProjectBuildContext.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,18 @@ class ProjectBuildContext {
6767
return this._taskUtil;
6868
}
6969

70-
async getDependenciesReader({includeTestResources = false} = {}) {
71-
return this._buildContest.getDependenciesReader({includeTestResources});
70+
/**
71+
* Retrieve a single project from the dependency graph
72+
*
73+
* @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built
74+
* @returns {module:@ui5/project.specifications.Project|undefined}
75+
* project instance or undefined if the project is unknown to the graph
76+
*/
77+
getProject(projectName) {
78+
if (projectName) {
79+
return this._buildContext.getProject(projectName);
80+
}
81+
return this._project;
7282
}
7383
}
7484

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
const log = require("@ui5/logger").getLogger("buildHelpers:composeTaskList");
2+
3+
/**
4+
* Creates an object containing the flattened project dependency tree. Each dependency is defined as an object key while
5+
* its value is an array of all of its transitive dependencies.
6+
*
7+
* @param {module:@ui5/project.graph.ProjectGraph} graph
8+
* @returns {Promise<object<string, string[]>>} A promise resolving to an object with dependency names as
9+
* key and each with an array of its transitive dependencies as value
10+
*/
11+
async function getFlattenedDependencyTree(graph) {
12+
const dependencyMap = {};
13+
const rootName = graph.getRoot().getName();
14+
15+
await graph.traverseDepthFirst(({project, getDependencies}) => {
16+
if (project.getName() === rootName) {
17+
// Skip root project
18+
return;
19+
}
20+
const projectDeps = [];
21+
getDependencies().forEach((dep) => {
22+
const depName = dep.getName();
23+
projectDeps.push(depName);
24+
if (dependencyMap[depName]) {
25+
projectDeps.push(...dependencyMap[depName]);
26+
}
27+
});
28+
dependencyMap[project.getName()] = projectDeps;
29+
});
30+
return dependencyMap;
31+
}
32+
33+
/**
34+
* Creates dependency lists for 'includedDependencies' and 'excludedDependencies'. Regular expressions are directly
35+
* applied to a list of all project dependencies so that they don't need to be evaluated in later processing steps.
36+
* Generally, includes are handled with a higher priority than excludes. Additionally, operations for processing
37+
* transitive dependencies are handled with a lower priority than explicitly mentioned dependencies. The default
38+
* dependencies set in the build settings are appended in the end.
39+
*
40+
* The priority of the various dependency lists is applied in the following order, but note that a later list can't
41+
* overrule earlier ones:
42+
* <ol>
43+
* <li>includeDependency, includeDependencyRegExp</li>
44+
* <li>excludeDependency, excludeDependencyRegExp</li>
45+
* <li>includeDependencyTree</li>
46+
* <li>excludeDependencyTree</li>
47+
* <li>defaultIncludeDependency, defaultIncludeDependencyRegExp, defaultIncludeDependencyTree</li>
48+
* </ol>
49+
*
50+
* @param {object} parameters Parameters
51+
* @param {object} parameters.graph Project tree as generated by the
52+
* [@ui5/project.normalizer]{@link module:@ui5/project.normalizer}
53+
* @param {boolean} parameters.includeAllDependencies Whether all dependencies should be part of the build result
54+
* This has the lowest priority and basically includes all remaining (not excluded) projects as include
55+
* @param {string[]} parameters.includeDependency The dependencies to be considered in 'includedDependencies'; the
56+
* "*" character can be used as wildcard for all dependencies and is an alias for the CLI option "--all"
57+
* @param {string[]} parameters.includeDependencyRegExp Strings which are interpreted as regular expressions
58+
* to describe the selection of dependencies to be considered in 'includedDependencies'
59+
* @param {string[]} parameters.includeDependencyTree The dependencies to be considered in 'includedDependencies';
60+
* transitive dependencies are also appended
61+
* @param {string[]} parameters.excludeDependency The dependencies to be considered in 'excludedDependencies'
62+
* @param {string[]} parameters.excludeDependencyRegExp Strings which are interpreted as regular expressions
63+
* to describe the selection of dependencies to be considered in 'excludedDependencies'
64+
* @param {string[]} parameters.excludeDependencyTree The dependencies to be considered in 'excludedDependencies';
65+
* transitive dependencies are also appended
66+
* @param {string[]} parameters.defaultIncludeDependency Same as 'includeDependency' parameter; used for build
67+
* settings
68+
* @param {string[]} parameters.defaultIncludeDependencyRegExp Same as 'includeDependencyRegExp' parameter; used
69+
* for build settings
70+
* @param {string[]} parameters.defaultIncludeDependencyTree Same as 'includeDependencyTree' parameter; used for
71+
* build settings
72+
* @returns {{includedDependencies:string[],excludedDependencies:string[]}} An object containing the
73+
* 'includedDependencies' and 'excludedDependencies'
74+
*/
75+
async function createDependencyLists({
76+
graph, includeAllDependencies = false,
77+
includeDependency = [], includeDependencyRegExp = [], includeDependencyTree = [],
78+
excludeDependency = [], excludeDependencyRegExp = [], excludeDependencyTree = [],
79+
defaultIncludeDependency = [], defaultIncludeDependencyRegExp = [], defaultIncludeDependencyTree = []
80+
}) {
81+
if (
82+
!includeDependency.length && !includeDependencyRegExp.length && !includeDependencyTree.length &&
83+
!excludeDependency.length && !excludeDependencyRegExp.length && !excludeDependencyTree.length &&
84+
!defaultIncludeDependency.length && !defaultIncludeDependencyRegExp.length &&
85+
!defaultIncludeDependencyTree.length
86+
) {
87+
return {includedDependencies: [], excludedDependencies: []};
88+
}
89+
90+
const flattenedDependencyTree = await getFlattenedDependencyTree(graph);
91+
92+
function isExcluded(excludeList, depName) {
93+
return excludeList && excludeList.has(depName);
94+
}
95+
function processDependencies({targetList, dependencies, dependenciesRegExp = [], excludeList, handleSubtree}) {
96+
if (handleSubtree && dependenciesRegExp.length) {
97+
throw new Error("dependenciesRegExp can't be combined with handleSubtree:true option");
98+
}
99+
dependencies.forEach((depName) => {
100+
if (depName === "*") {
101+
targetList.add(depName);
102+
} else if (flattenedDependencyTree[depName]) {
103+
if (!isExcluded(excludeList, depName)) {
104+
targetList.add(depName);
105+
}
106+
if (handleSubtree) {
107+
flattenedDependencyTree[depName].forEach((dep) => {
108+
if (!isExcluded(excludeList, dep)) {
109+
targetList.add(dep);
110+
}
111+
});
112+
}
113+
} else {
114+
log.warn(
115+
`Could not find dependency "${depName}" for project ${graph.getRoot().getName()}. ` +
116+
`Dependency filter is ignored`);
117+
}
118+
});
119+
dependenciesRegExp.map((exp) => new RegExp(exp)).forEach((regExp) => {
120+
for (const depName in flattenedDependencyTree) {
121+
if (regExp.test(depName) && !isExcluded(excludeList, depName)) {
122+
targetList.add(depName);
123+
}
124+
}
125+
});
126+
}
127+
128+
const includedDependencies = new Set();
129+
const excludedDependencies = new Set();
130+
131+
// add dependencies defined in includeDependency and includeDependencyRegExp to the list of includedDependencies
132+
processDependencies({
133+
targetList: includedDependencies,
134+
dependencies: includeDependency,
135+
dependenciesRegExp: includeDependencyRegExp
136+
});
137+
// add dependencies defined in excludeDependency and excludeDependencyRegExp to the list of excludedDependencies
138+
processDependencies({
139+
targetList: excludedDependencies,
140+
dependencies: excludeDependency,
141+
dependenciesRegExp: excludeDependencyRegExp
142+
});
143+
// add dependencies defined in includeDependencyTree with their transitive dependencies to the list of
144+
// includedDependencies; due to prioritization only those dependencies are added which are not excluded
145+
// by excludedDependencies
146+
processDependencies({
147+
targetList: includedDependencies,
148+
dependencies: includeDependencyTree,
149+
excludeList: excludedDependencies,
150+
handleSubtree: true
151+
});
152+
// add dependencies defined in excludeDependencyTree with their transitive dependencies to the list of
153+
// excludedDependencies; due to prioritization only those dependencies are added which are not excluded
154+
// by includedDependencies
155+
processDependencies({
156+
targetList: excludedDependencies,
157+
dependencies: excludeDependencyTree,
158+
excludeList: includedDependencies,
159+
handleSubtree: true
160+
});
161+
// due to the lower priority only add the dependencies defined in build settings if they are not excluded
162+
// by any other dependency defined in excludedDependencies
163+
processDependencies({
164+
targetList: includedDependencies,
165+
dependencies: defaultIncludeDependency,
166+
dependenciesRegExp: defaultIncludeDependencyRegExp,
167+
excludeList: excludedDependencies
168+
});
169+
processDependencies({
170+
targetList: includedDependencies,
171+
dependencies: defaultIncludeDependencyTree,
172+
excludeList: excludedDependencies,
173+
handleSubtree: true
174+
});
175+
176+
if (includeAllDependencies) {
177+
// If requested, add all dependencies not excluded to include set
178+
Object.keys(flattenedDependencyTree).forEach((depName) => {
179+
if (!isExcluded(excludedDependencies, depName)) {
180+
includedDependencies.add(depName);
181+
}
182+
});
183+
}
184+
185+
return {
186+
includedDependencies: Array.from(includedDependencies),
187+
excludedDependencies: Array.from(excludedDependencies)
188+
};
189+
}
190+
191+
module.exports = createDependencyLists;
192+
module.exports._getFlattenedDependencyTree = getFlattenedDependencyTree;

0 commit comments

Comments
 (0)