-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] Support to only build certain dependencies (#442)
* [FEATURE] Support to only build certain dependencies Projects can be built with a selection of dependencies that have to be included into the build result. The following CLI parameters can be used flexibly to configure the selection of the dependencies to be built: --include-dependency, --include-dependency-regexp, --include-dependency-tree, --exclude-dependency, --exclude-dependency-regexp, --exclude-dependency-tree JIRA: CPOUI5FOUNDATION-208 * [INTERNAL] Apply suggestions from code review * [INTERNAL] Fix ESlint issue * [INTERNAL] Improve creation of dependency lists, enhance documentation * [INTERNAL] Fix broken test * [INTERNAL] Apply suggestions from code review * [INTERNAL] Improve build command description for option "include-dependency" * [INTERNAL] Improve JSDoc param types * [INTERNAL] Fix JSDoc return types
- Loading branch information
1 parent
e984269
commit 5f941f1
Showing
4 changed files
with
917 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
const log = require("@ui5/logger").getLogger("cli:utils:buildHelper"); | ||
|
||
/** | ||
* Creates an object containing the flattened project dependency tree. Each dependency is defined as an object key while | ||
* its value is an array of all of its transitive dependencies. | ||
* | ||
* @param {object} tree Project tree as generated by the [@ui5/project.normalizer]{@link module:@ui5/project.normalizer} | ||
* @returns {object<string, string[]>} An object with dependency names as key and each with an array of its transitive | ||
* dependencies as value | ||
*/ | ||
function getFlattenedDependencyTree(tree) { | ||
const dependencyInfo = {}; | ||
|
||
function _getTransitiveDependencies(project, dependencies) { | ||
project.dependencies.forEach((dep) => { | ||
if (!dependencies.includes(dep.metadata.name)) { | ||
dependencies.push(dep.metadata.name); | ||
_getTransitiveDependencies(dep, dependencies); | ||
} | ||
}); | ||
return dependencies; | ||
} | ||
function _processDependencies(project) { | ||
project.dependencies.forEach((dep) => { | ||
if (!dependencyInfo[dep.metadata.name]) { | ||
dependencyInfo[dep.metadata.name] = _getTransitiveDependencies(dep, []); | ||
_processDependencies(dep); | ||
} | ||
}); | ||
} | ||
|
||
_processDependencies(tree); | ||
return dependencyInfo; | ||
} | ||
|
||
/** | ||
* Creates dependency lists for 'includedDependencies' and 'excludedDependencies'. Regular expressions are directly | ||
* applied to a list of all project dependencies so that they don't need to be evaluated in later processing steps. | ||
* Generally, includes are handled with a higher priority than excludes. Additionally, operations for processing | ||
* transitive dependencies are handled with a lower priority than explicitly mentioned dependencies. The default | ||
* dependencies set in the build settings are appended in the end. | ||
* | ||
* The priority of the various dependency lists is applied in the following order, but note that a later list can't | ||
* overrule earlier ones: | ||
* <ol> | ||
* <li>includeDependency, includeDependencyRegExp</li> | ||
* <li>excludeDependency, excludeDependencyRegExp</li> | ||
* <li>includeDependencyTree</li> | ||
* <li>excludeDependencyTree</li> | ||
* <li>defaultIncludeDependency, defaultIncludeDependencyRegExp, defaultIncludeDependencyTree</li> | ||
* </ol> | ||
* | ||
* @param {object} parameters Parameters | ||
* @param {object} parameters.tree Project tree as generated by the | ||
* [@ui5/project.normalizer]{@link module:@ui5/project.normalizer} | ||
* @param {string[]} parameters.includeDependency The dependencies to be considered in 'includedDependencies'; the | ||
* "*" character can be used as wildcard for all dependencies and is an alias for the CLI option "--all" | ||
* @param {string[]} parameters.includeDependencyRegExp Strings which are interpreted as regular expressions | ||
* to describe the selection of dependencies to be considered in 'includedDependencies' | ||
* @param {string[]} parameters.includeDependencyTree The dependencies to be considered in 'includedDependencies'; | ||
* transitive dependencies are also appended | ||
* @param {string[]} parameters.excludeDependency The dependencies to be considered in 'excludedDependencies' | ||
* @param {string[]} parameters.excludeDependencyRegExp Strings which are interpreted as regular expressions | ||
* to describe the selection of dependencies to be considered in 'excludedDependencies' | ||
* @param {string[]} parameters.excludeDependencyTree The dependencies to be considered in 'excludedDependencies'; | ||
* transitive dependencies are also appended | ||
* @param {string[]} parameters.defaultIncludeDependency Same as 'includeDependency' parameter; used for build | ||
* settings | ||
* @param {string[]} parameters.defaultIncludeDependencyRegExp Same as 'includeDependencyRegExp' parameter; used | ||
* for build settings | ||
* @param {string[]} parameters.defaultIncludeDependencyTree Same as 'includeDependencyTree' parameter; used for | ||
* build settings | ||
* @returns {{includedDependencies:string[],excludedDependencies:string[]}} An object containing the | ||
* 'includedDependencies' and 'excludedDependencies' | ||
*/ | ||
function createDependencyLists({ | ||
tree, | ||
includeDependency = [], includeDependencyRegExp = [], includeDependencyTree = [], | ||
excludeDependency = [], excludeDependencyRegExp = [], excludeDependencyTree = [], | ||
defaultIncludeDependency = [], defaultIncludeDependencyRegExp = [], defaultIncludeDependencyTree = [] | ||
}) { | ||
if ( | ||
!includeDependency.length && !includeDependencyRegExp.length && !includeDependencyTree.length && | ||
!excludeDependency.length && !excludeDependencyRegExp.length && !excludeDependencyTree.length && | ||
!defaultIncludeDependency.length && !defaultIncludeDependencyRegExp.length && | ||
!defaultIncludeDependencyTree.length | ||
) { | ||
return {includedDependencies: [], excludedDependencies: []}; | ||
} | ||
|
||
const flattenedDependencyTree = getFlattenedDependencyTree(tree); | ||
|
||
function isExcluded(excludeList, depName) { | ||
return excludeList && excludeList.has(depName); | ||
} | ||
function processDependencies({targetList, dependencies, dependenciesRegExp = [], excludeList, handleSubtree}) { | ||
if (handleSubtree && dependenciesRegExp.length) { | ||
throw new Error("dependenciesRegExp can't be combined with handleSubtree:true option"); | ||
} | ||
dependencies.forEach((depName) => { | ||
if (depName === "*") { | ||
targetList.add(depName); | ||
} else if (flattenedDependencyTree[depName]) { | ||
if (!isExcluded(excludeList, depName)) { | ||
targetList.add(depName); | ||
} | ||
if (handleSubtree) { | ||
flattenedDependencyTree[depName].forEach((dep) => { | ||
if (!isExcluded(excludeList, dep)) { | ||
targetList.add(dep); | ||
} | ||
}); | ||
} | ||
} else { | ||
log.warn( | ||
`Could not find dependency "${depName}" for project ${tree.metadata.name}. Dependency filter is ` + | ||
`ignored`); | ||
} | ||
}); | ||
dependenciesRegExp.map((exp) => new RegExp(exp)).forEach((regExp) => { | ||
for (const depName in flattenedDependencyTree) { | ||
if (regExp.test(depName) && !isExcluded(excludeList, depName)) { | ||
targetList.add(depName); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
const includedDependencies = new Set(); | ||
const excludedDependencies = new Set(); | ||
|
||
// add dependencies defined in includeDependency and includeDependencyRegExp to the list of includedDependencies | ||
processDependencies({ | ||
targetList: includedDependencies, | ||
dependencies: includeDependency, | ||
dependenciesRegExp: includeDependencyRegExp | ||
}); | ||
// add dependencies defined in excludeDependency and excludeDependencyRegExp to the list of excludedDependencies | ||
processDependencies({ | ||
targetList: excludedDependencies, | ||
dependencies: excludeDependency, | ||
dependenciesRegExp: excludeDependencyRegExp | ||
}); | ||
// add dependencies defined in includeDependencyTree with their transitive dependencies to the list of | ||
// includedDependencies; due to prioritization only those dependencies are added which are not excluded | ||
// by excludedDependencies | ||
processDependencies({ | ||
targetList: includedDependencies, | ||
dependencies: includeDependencyTree, | ||
excludeList: excludedDependencies, | ||
handleSubtree: true | ||
}); | ||
// add dependencies defined in excludeDependencyTree with their transitive dependencies to the list of | ||
// excludedDependencies; due to prioritization only those dependencies are added which are not excluded | ||
// by includedDependencies | ||
processDependencies({ | ||
targetList: excludedDependencies, | ||
dependencies: excludeDependencyTree, | ||
excludeList: includedDependencies, | ||
handleSubtree: true | ||
}); | ||
// due to the lowest priority only add the dependencies defined in build settings if they are not excluded | ||
// by any other dependency defined in excludedDependencies | ||
processDependencies({ | ||
targetList: includedDependencies, | ||
dependencies: defaultIncludeDependency, | ||
dependenciesRegExp: defaultIncludeDependencyRegExp, | ||
excludeList: excludedDependencies | ||
}); | ||
processDependencies({ | ||
targetList: includedDependencies, | ||
dependencies: defaultIncludeDependencyTree, | ||
excludeList: excludedDependencies, | ||
handleSubtree: true | ||
}); | ||
|
||
return { | ||
includedDependencies: Array.from(includedDependencies), | ||
excludedDependencies: Array.from(excludedDependencies) | ||
}; | ||
} | ||
|
||
/** | ||
* Returns whether project dependencies have to be built influenced by <code>includedDependencies</code> and | ||
* <code>excludedDependencies</code>. | ||
* If only selected dependencies (via <code>includedDependencies</code>) have to be built, the "*" character | ||
* is added to the <code>excludedDependencies</code> to make sure that all other dependencies are | ||
* excluded. | ||
* In case a "*" character is included in <code>includedDependencies</code>, it is removed and the | ||
* <code>buildAll</code> flag is set to <code>true</code> as it behaves as an alias. | ||
* | ||
* @param {boolean} buildAll The value of the <code>all</code> command line parameter to decide if project | ||
* dependencies have to be built | ||
* @param {string[]} includedDependencies The list of included dependencies | ||
* @param {string[]} excludedDependencies The list of excluded dependencies | ||
* @returns {boolean} Whether it is required to build project dependencies | ||
*/ | ||
function alignWithBuilderApi(buildAll, includedDependencies, excludedDependencies) { | ||
if ((!buildAll && !includedDependencies.includes("*")) && includedDependencies.length) { | ||
excludedDependencies.push("*"); | ||
} | ||
if (includedDependencies.includes("*")) { | ||
buildAll = true; | ||
includedDependencies.splice(includedDependencies.indexOf("*"), 1); | ||
} | ||
if (!buildAll && includedDependencies.length) { | ||
buildAll = true; | ||
} | ||
return buildAll; | ||
} | ||
|
||
module.exports = { | ||
getFlattenedDependencyTree, | ||
createDependencyLists, | ||
alignWithBuilderApi | ||
}; |
Oops, something went wrong.