From bb1aa260bedbcf52804b839bc9d37fdf2edb2390 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 16 Nov 2020 17:29:58 +0100 Subject: [PATCH 01/41] [INTERNAL][WIP] VersionInfo: with manifest infos --- lib/processors/versionInfoGenerator.js | 22 +++++++----- lib/tasks/generateVersionInfo.js | 50 ++++++++++++++++---------- test/lib/tasks/generateVersionInfo.js | 47 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 27 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 39189b5d8..b94dedad1 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -33,6 +33,17 @@ module.exports = async function({options}) { throw new Error("[versionInfoGenerator]: Missing options parameters"); } + const manifestHints = []; + const components = []; + const libraries = options.libraryInfos.map(function(libraryInfo) { + return { + name: libraryInfo.name, + version: libraryInfo.version, + buildTimestamp: buildTimestamp, + scmRevision: "" // TODO: insert current library scm revision here + }; + }); + const buildTimestamp = getTimestamp(); const versionJson = { name: options.rootProjectName, @@ -40,14 +51,9 @@ module.exports = async function({options}) { buildTimestamp: buildTimestamp, scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here - libraries: options.libraryInfos.map(function(libraryInfo) { - return { - name: libraryInfo.name, - version: libraryInfo.version, - buildTimestamp: buildTimestamp, - scmRevision: "" // TODO: insert current library scm revision here - }; - }) + libraries, + manifestHints, + components }; return [resourceFactory.createResource({ diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index f60a69ced..9755756f7 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -1,5 +1,7 @@ const versionInfoGenerator = require("../processors/versionInfoGenerator"); +const DESCRIPTOR = "manifest.json"; + /** * Task to create sap-ui-version.json * @@ -10,26 +12,36 @@ const versionInfoGenerator = require("../processors/versionInfoGenerator"); * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files * @param {object} parameters.options Options * @param {string} parameters.options.pattern Glob pattern for .library resources + * @param {string} parameters.options.namespace Namespace of the project * @param {object} parameters.options.rootProject DuplexCollection to read and write files * @returns {Promise} Promise resolving with undefined once data has been written */ -module.exports = function({workspace, dependencies, options: {rootProject, pattern}}) { - return dependencies.byGlob(pattern) - .then((resources) => { - return versionInfoGenerator({ - options: { - rootProjectName: rootProject.metadata.name, - rootProjectVersion: rootProject.version, - libraryInfos: resources.map((dotLibResource) => { - return { - name: dotLibResource._project.metadata.name, - version: dotLibResource._project.version - }; - }) - } - }); - }) - .then(([versionInfoResource]) => { - return workspace.write(versionInfoResource); - }); +module.exports = async ({workspace, dependencies, options: {rootProject, pattern, namespace}}) => { + + const resources = await dependencies.byGlob(pattern); + const manifestResourcesPromises = resources.map((dotLibResource) => { + const libraryNamespacePattern = /^\/resources\/(.*)\/\.library$/; + const libraryIndicatorPath = dotLibResource.getPath(); + const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); + if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { + const namespace = libraryNamespaceMatch[1]; + return workspace.byGlob(`/resources/${namespace}/**/${DESCRIPTOR}`); + } + }); + //TODO align libraryInfos and manifestInfos + const manifestResources = await Promise.all(manifestResourcesPromises); + const [versionInfoResource] = await versionInfoGenerator({ + options: { + rootProjectName: rootProject.metadata.name, + rootProjectVersion: rootProject.version, + libraryInfos: resources.map((dotLibResource) => { + return { + name: dotLibResource._project.metadata.name, + version: dotLibResource._project.version + }; + }), + manifestInfos: manifestResources + } + }); + return workspace.write(versionInfoResource); }; diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index c09c7326b..668ffa647 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -156,3 +156,50 @@ test("integration: Library without i18n bundle file failure", async (t) => { t.is(error.message, "[versionInfoGenerator]: Missing options parameters"); }); }); + + +test("integration: Library without i18n bundle with manifest", async (t) => { + t.context.workspace = createWorkspace(); + t.context.dependencies = createDependencies(); + + t.context.resources = []; + t.context.resources.push(resourceFactory.createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + `, + project: t.context.workspace._project + })); + + t.context.resources.push(resourceFactory.createResource({ + path: "/resources/pony/manifest.json", + string: ` + { + "sap.app": {} + } + `, + project: t.context.workspace._project + })); + const oOptions = await createOptions(t); + oOptions.options.namespace = "pony"; + await assertCreatedVersionInfo(t, { + "libraries": [{ + "name": "test.lib3", + "scmRevision": "", + "version": "3.0.0" + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions.options); +}); From 93424fc310c5eb34c89de3991d21db2f534faed9 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 18 Nov 2020 14:57:23 +0100 Subject: [PATCH 02/41] [INTERNAL][WIP] VersionInfo: with manifest infos improve code by passing correct arguments --- lib/processors/versionInfoGenerator.js | 55 +++++++++++++++++++++----- lib/tasks/generateVersionInfo.js | 28 +++++++------ test/lib/tasks/generateVersionInfo.js | 28 +++++++++++-- 3 files changed, 87 insertions(+), 24 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index b94dedad1..556462944 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -14,6 +14,30 @@ function getTimestamp() { return year + month + day + hours + minutes; } + +/** + * + * @param {module:@ui5/fs.Resource} manifestResource + * @returns {Promise} + */ +const processManifest = async (manifestResource) => { + const manifestContent = await manifestResource.getString(); + const manifestObject = JSON.parse(manifestContent); + // TODO extract manifestHints + return manifestObject; +}; + +/** + * Library Info object + * + * @public + * @typedef {object} LibraryInfo + * @property {string} name The library name + * @property {string} version The library version + * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources + */ + + /** * Creates sap-ui-version.json. * @@ -33,17 +57,31 @@ module.exports = async function({options}) { throw new Error("[versionInfoGenerator]: Missing options parameters"); } - const manifestHints = []; const components = []; - const libraries = options.libraryInfos.map(function(libraryInfo) { - return { - name: libraryInfo.name, - version: libraryInfo.version, - buildTimestamp: buildTimestamp, - scmRevision: "" // TODO: insert current library scm revision here - }; + const librariesPromises = options.libraryInfos.map(function(libraryInfo) { + const manifestHintsPromise = libraryInfo.manifestResources.map((manifestResource) => { + return processManifest(manifestResource); + }); + + return Promise.all(manifestHintsPromise).then((manifestHintsArray) => { + // TODO from manifestHintsArray to manifestHintsObject + const manifestHintsObject = { + + }; + return { + name: libraryInfo.name, + version: libraryInfo.version, + buildTimestamp: buildTimestamp, + scmRevision: "", // TODO: insert current library scm revision here + manifestHints: manifestHintsObject + }; + }); }); + // TODO enrich components + + const libraries = await Promise.all(librariesPromises); + const buildTimestamp = getTimestamp(); const versionJson = { name: options.rootProjectName, @@ -52,7 +90,6 @@ module.exports = async function({options}) { scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here libraries, - manifestHints, components }; diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 9755756f7..661f57c5c 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -19,28 +19,34 @@ const DESCRIPTOR = "manifest.json"; module.exports = async ({workspace, dependencies, options: {rootProject, pattern, namespace}}) => { const resources = await dependencies.byGlob(pattern); - const manifestResourcesPromises = resources.map((dotLibResource) => { + + const libraryInfosPromises = resources.map((dotLibResource) => { const libraryNamespacePattern = /^\/resources\/(.*)\/\.library$/; const libraryIndicatorPath = dotLibResource.getPath(); const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { const namespace = libraryNamespaceMatch[1]; - return workspace.byGlob(`/resources/${namespace}/**/${DESCRIPTOR}`); + // use /**/ for nested manifests + return dependencies.byGlob(`/resources/${namespace}/**/${DESCRIPTOR}`).then((manifestResources) => { + return { + manifestResources, + name: dotLibResource._project.metadata.name, + version: dotLibResource._project.version + }; + }); } + return { + name: dotLibResource._project.metadata.name, + version: dotLibResource._project.version + }; }); - //TODO align libraryInfos and manifestInfos - const manifestResources = await Promise.all(manifestResourcesPromises); + const libraryInfos = await Promise.all(libraryInfosPromises); + const [versionInfoResource] = await versionInfoGenerator({ options: { rootProjectName: rootProject.metadata.name, rootProjectVersion: rootProject.version, - libraryInfos: resources.map((dotLibResource) => { - return { - name: dotLibResource._project.metadata.name, - version: dotLibResource._project.version - }; - }), - manifestInfos: manifestResources + libraryInfos } }); return workspace.write(versionInfoResource); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 668ffa647..7cd219540 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -37,6 +37,18 @@ function createDependencies() { }); } +function createDependenciesWithManifest() { + return resourceFactory.createAdapter({ + fsBasePath: path.join(__dirname, "..", "..", "fixtures", "library.e", "src"), + virBasePath: "/resources/", + project: { + metadata: { + name: "library.e" + }, + version: "3.0.0"} + }); +} + async function createOptions(t, options) { const {workspace, dependencies, resources} = t.context; @@ -160,7 +172,7 @@ test("integration: Library without i18n bundle file failure", async (t) => { test("integration: Library without i18n bundle with manifest", async (t) => { t.context.workspace = createWorkspace(); - t.context.dependencies = createDependencies(); + t.context.dependencies = createDependenciesWithManifest(); t.context.resources = []; t.context.resources.push(resourceFactory.createResource({ @@ -182,7 +194,7 @@ test("integration: Library without i18n bundle with manifest", async (t) => { })); t.context.resources.push(resourceFactory.createResource({ - path: "/resources/pony/manifest.json", + path: "/resources/test/lib/manifest.json", string: ` { "sap.app": {} @@ -190,8 +202,16 @@ test("integration: Library without i18n bundle with manifest", async (t) => { `, project: t.context.workspace._project })); - const oOptions = await createOptions(t); - oOptions.options.namespace = "pony"; + const oOptions = await createOptions(t, { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }); await assertCreatedVersionInfo(t, { "libraries": [{ "name": "test.lib3", From 32b1d3e64884bce0547044efe76bdef0882ecd3e Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 27 Nov 2020 15:52:20 +0100 Subject: [PATCH 03/41] [INTERNAL][WIP] VersionInfo: with manifest infos adjusted logic to resolve dependencies --- lib/builder/builder.js | 2 + lib/processors/versionInfoGenerator.js | 149 ++++++++++-- lib/tasks/generateVersionInfo.js | 42 ++-- test/lib/tasks/generateVersionInfo.js | 317 ++++++++++++++++++++++--- 4 files changed, 442 insertions(+), 68 deletions(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 4a74bc029..27de77b30 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -75,6 +75,8 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.generateLibraryPreload = false; } + // TODO exclude generateVersionInfo if not --all is used? + if (jsdoc) { // Include JSDoc tasks selectedTasks.generateJsdoc = true; diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 556462944..cf97c1ed0 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -18,15 +18,42 @@ function getTimestamp() { /** * * @param {module:@ui5/fs.Resource} manifestResource - * @returns {Promise} + * @returns {Promise} */ const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); const manifestObject = JSON.parse(manifestContent); // TODO extract manifestHints - return manifestObject; + const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; + const result = { + embeds: [], + libs: {} + }; + if (manifestDependencies) { + Object.keys(manifestDependencies.libs).forEach((libKey) => { + result.libs[libKey] = {}; + if (manifestDependencies.libs[libKey].lazy) { + result.libs[libKey].lazy = true; + } + }); + } + + // there for components + const manifestEmbeds = manifestObject["sap.app"]["embeds"]; + if (manifestEmbeds) { + result.embeds = manifestEmbeds; + } + return result; }; +/** + * Manifest Hint + * + * @public + * @typedef {object} ManifestHint + * @property {Object} libs The library object + */ + /** * Library Info object * @@ -34,10 +61,66 @@ const processManifest = async (manifestResource) => { * @typedef {object} LibraryInfo * @property {string} name The library name * @property {string} version The library version + * @property {module:@ui5/fs.Resource} mainManifest main manifest resources * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources */ +const getManifestPath = (filePath, subPath) => { + if (filePath.endsWith("manifest.json")) { + return filePath.substr(0, filePath.length - "manifest.json".length) + subPath + "/manifest.json"; + } + return filePath; +}; + +/** + * Dependency Mapping + * + * @type {Map>} + */ +const dependencyMap = new Map(); + +/** + * + * @param {Map} manifestHints + */ +const resolveDependencies = (manifestHints) => { + manifestHints.forEach((manifestHint) => { + Object.keys(manifestHint.libs).forEach((libName) => { + const resolved = resolve(libName, manifestHints); + Object.keys(resolved).forEach((nestedKey) => { + manifestHint.libs[nestedKey] = resolved[nestedKey]; + }); + }); + }); +}; + +/** + * + * @param {string} libName + * @param {Map} manifestHints + */ +const resolve = (libName, manifestHints) => { + const manifestHint = manifestHints.get(libName); + const resolvedLibs = {}; + Object.keys(manifestHint.libs).forEach((childLibName) => { + resolvedLibs[childLibName] = manifestHint.libs[childLibName]; + const nested = resolve(childLibName, manifestHints); + Object.keys(nested).forEach((nestedKey) => { + if (nested[nestedKey].lazy) { + if (!resolvedLibs[nestedKey] || resolvedLibs[nestedKey].lazy) { + resolvedLibs[nestedKey] = { + lazy: true + }; + } + } + resolvedLibs[nestedKey] = resolvedLibs[nestedKey] || {}; + }); + }); + + return resolvedLibs; +}; + /** * Creates sap-ui-version.json. * @@ -57,32 +140,62 @@ module.exports = async function({options}) { throw new Error("[versionInfoGenerator]: Missing options parameters"); } + const buildTimestamp = getTimestamp(); + // TODO filter manifest.json if sap/embeds (we expect it contains the correct information) + const components = []; - const librariesPromises = options.libraryInfos.map(function(libraryInfo) { - const manifestHintsPromise = libraryInfo.manifestResources.map((manifestResource) => { - return processManifest(manifestResource); + /** + * + * @type {Map} + */ + const manifestHints = new Map(); + + // gather all manifestHints + const librariesPromises = options.libraryInfos.map((libraryInfo) => { + return processManifest(libraryInfo.mainManifest).then((manifestHint) => { + manifestHints.set(libraryInfo.name, manifestHint); }); + }); - return Promise.all(manifestHintsPromise).then((manifestHintsArray) => { - // TODO from manifestHintsArray to manifestHintsObject - const manifestHintsObject = { + await Promise.all(librariesPromises); + console.log("before"); + manifestHints.forEach((manifestHint, key) => { + console.log(`${key} => ${Object.keys(manifestHint.libs).join(", ")}`); + }); + + // resolve nested dependencies + resolveDependencies(manifestHints); + + console.log("after"); + manifestHints.forEach((manifestHint, key) => { + console.log(`${key} => ${Object.keys(manifestHint.libs).join(", ")}`); + }); + + + const libraries = options.libraryInfos.map((libraryInfo) => { + const result = { + name: libraryInfo.name, + version: libraryInfo.version, + buildTimestamp: buildTimestamp, + scmRevision: ""// TODO: insert current library scm revision here + }; + + const libs = manifestHints.get(libraryInfo.name).libs; + if (Object.keys(libs).length) { + result.manifestHints = { + dependencies: { + libs: libs + } }; - return { - name: libraryInfo.name, - version: libraryInfo.version, - buildTimestamp: buildTimestamp, - scmRevision: "", // TODO: insert current library scm revision here - manifestHints: manifestHintsObject - }; - }); + } + return result; }); + // TODO enrich components - const libraries = await Promise.all(librariesPromises); - const buildTimestamp = getTimestamp(); const versionJson = { name: options.rootProjectName, version: options.rootProjectVersion, // TODO: insert current application version here diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 661f57c5c..76634c530 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -1,6 +1,6 @@ const versionInfoGenerator = require("../processors/versionInfoGenerator"); -const DESCRIPTOR = "manifest.json"; +const MANIFEST_JSON = "manifest.json"; /** * Task to create sap-ui-version.json @@ -17,28 +17,32 @@ const DESCRIPTOR = "manifest.json"; * @returns {Promise} Promise resolving with undefined once data has been written */ module.exports = async ({workspace, dependencies, options: {rootProject, pattern, namespace}}) => { - const resources = await dependencies.byGlob(pattern); + // app always builds all dependencies -> therefore glob all manifest.json + // build with --all should not use the version-info.json + // logic needs to be adjusted once selective dependencies are built + // TODO: transient resources (not part of the build result) ( -> skipped for now) + // exclude task if not build --all + const libraryInfosPromises = resources.map((dotLibResource) => { - const libraryNamespacePattern = /^\/resources\/(.*)\/\.library$/; - const libraryIndicatorPath = dotLibResource.getPath(); - const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); - if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { - const namespace = libraryNamespaceMatch[1]; - // use /**/ for nested manifests - return dependencies.byGlob(`/resources/${namespace}/**/${DESCRIPTOR}`).then((manifestResources) => { - return { - manifestResources, - name: dotLibResource._project.metadata.name, - version: dotLibResource._project.version - }; + const namespace = dotLibResource._project.metadata.namespace; + // TODO favor manifest.json over .library (check first manifest.json then as fallback .library) + // long-term goal: get rid of .library files + // TODO: compare the two + // use /**/ for nested manifests + // use /sap.app/embeds + return dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`).then((manifestResources) => { + const mainManifest = manifestResources.find((manifestResource) => { + return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; }); - } - return { - name: dotLibResource._project.metadata.name, - version: dotLibResource._project.version - }; + return { + mainManifest, + manifestResources, + name: dotLibResource._project.metadata.name, + version: dotLibResource._project.version + }; + }); }); const libraryInfos = await Promise.all(libraryInfosPromises); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 7cd219540..388e09c22 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -37,18 +37,6 @@ function createDependencies() { }); } -function createDependenciesWithManifest() { - return resourceFactory.createAdapter({ - fsBasePath: path.join(__dirname, "..", "..", "fixtures", "library.e", "src"), - virBasePath: "/resources/", - project: { - metadata: { - name: "library.e" - }, - version: "3.0.0"} - }); -} - async function createOptions(t, options) { const {workspace, dependencies, resources} = t.context; @@ -71,8 +59,12 @@ async function createOptions(t, options) { } -async function assertCreatedVersionInfo(t, oExpectedVersionInfo, options) { +async function assertCreatedVersionInfoAndCreateOptions(t, oExpectedVersionInfo, options) { const oOptions = await createOptions(t, options); + await assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions); +} + +async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { await generateVersionInfo(oOptions); const resource = await oOptions.workspace.byPath("/resources/sap-ui-version.json"); @@ -91,6 +83,12 @@ async function assertCreatedVersionInfo(t, oExpectedVersionInfo, options) { t.is(lib.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); delete lib.buildTimestamp; // removing to allow deep comparison }); + + currentVersionInfo.libraries.sort((libraryA, libraryB) => { + + return libraryA.name.localeCompare(libraryB.name); + }); + t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); } @@ -117,7 +115,7 @@ test("integration: Library without i18n bundle file", async (t) => { project: t.context.workspace._project })); - await assertCreatedVersionInfo(t, { + await assertCreatedVersionInfoAndCreateOptions(t, { "libraries": [{ "name": "test.lib3", "scmRevision": "", @@ -171,11 +169,28 @@ test("integration: Library without i18n bundle file failure", async (t) => { test("integration: Library without i18n bundle with manifest", async (t) => { - t.context.workspace = createWorkspace(); - t.context.dependencies = createDependenciesWithManifest(); + const workspace = resourceFactory.createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "test.lib", + namespace: "test/lib" + }, + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] + } + }); - t.context.resources = []; - t.context.resources.push(resourceFactory.createResource({ + // TODO .library should not be required + // only use .library if manifest.json is not there + await workspace.write(resourceFactory.createResource({ path: "/resources/test/lib/.library", string: ` @@ -190,36 +205,276 @@ test("integration: Library without i18n bundle with manifest", async (t) => { `, - project: t.context.workspace._project + project: workspace._project })); - t.context.resources.push(resourceFactory.createResource({ + await workspace.write(resourceFactory.createResource({ path: "/resources/test/lib/manifest.json", string: ` { - "sap.app": {} + "sap.app": { + "embeds": ["subcomp"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.a": { + "minVersion": "1.84.0" + }, + "lib.b": { + "minVersion": "1.84.0", + "lazy": true + } + } + } + } + } + `, + project: workspace._project + })); + + await workspace.write(resourceFactory.createResource({ + path: "/resources/test/lib/subcomp/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "libY": { + "minVersion": "1.84.0" + }, + "libX": { + "minVersion": "1.84.0", + "lazy": true + } + } + } + } } `, - project: t.context.workspace._project + project: workspace._project })); - const oOptions = await createOptions(t, { - projectName: "Test Lib", - pattern: "/resources/**/.library", - rootProject: { + + // dependencies + const createProjectMetadata = (nameArray) => { + return { metadata: { - name: "myname" + name: nameArray.join("."), + namespace: nameArray.join("/") + } + }; + }; + const dependencies = resourceFactory.createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "lib.a", + namespace: "lib/a" }, - version: "1.33.7" + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] } }); + + // lib.a + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/.library", + string: ` + + + lib.a + SAP SE + + 2.0.0 + + Library A + + `, + project: createProjectMetadata(["lib", "a"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.c": { + "minVersion": "1.84.0" + }, + "lib.b": { + "minVersion": "1.84.0" + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "a"]) + })); + + // lib.c + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/c/.library", + string: ` + + + lib.c + SAP SE + + 2.0.0 + + Library C + + `, + project: createProjectMetadata(["lib", "c"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/c/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + } + } + } + } + `, + project: createProjectMetadata(["lib", "c"]) + })); + + // lib.b + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/b/.library", + string: ` + + + lib.b + SAP SE + + 2.0.0 + + Library B + + `, + project: createProjectMetadata(["lib", "b"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/b/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.d": { + "minVersion": "1.84.0" + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "b"]) + })); + + // lib.d + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/d/.library", + string: ` + + + lib.d + SAP SE + + 2.0.0 + + Library D + + `, + project: createProjectMetadata(["lib", "d"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/d/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + } + } + } + } + `, + project: createProjectMetadata(["lib", "d"]) + })); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; await assertCreatedVersionInfo(t, { + "components": [], "libraries": [{ - "name": "test.lib3", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "name": "lib.b", + "scmRevision": "", + }, + { + "name": "lib.c", "scmRevision": "", - "version": "3.0.0" }], "name": "myname", "scmRevision": "", "version": "1.33.7", - }, oOptions.options); + }, oOptions); }); From da9b12c0978bf4cbf57392bea618748e24404279 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 30 Nov 2020 15:48:33 +0100 Subject: [PATCH 04/41] [INTERNAL][WIP] VersionInfo: with manifest infos adjusted logic to resolve dependencies correctly with regards to lazy --- lib/processors/versionInfoGenerator.js | 109 ++++--- test/lib/tasks/generateVersionInfo.js | 423 ++++++++++++++++++++++++- 2 files changed, 478 insertions(+), 54 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index cf97c1ed0..36ed17d15 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -18,7 +18,7 @@ function getTimestamp() { /** * * @param {module:@ui5/fs.Resource} manifestResource - * @returns {Promise} + * @returns {Promise} */ const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); @@ -50,10 +50,21 @@ const processManifest = async (manifestResource) => { * Manifest Hint * * @public - * @typedef {object} ManifestHint - * @property {Object} libs The library object + * @typedef {object} DependencyInfos + * @property {object} libs The library object + * + * + * @example + * { + * sap.chart: { + * lazy: true + * }, + * sap.f: { }, + * } + * */ + /** * Library Info object * @@ -66,28 +77,16 @@ const processManifest = async (manifestResource) => { */ -const getManifestPath = (filePath, subPath) => { - if (filePath.endsWith("manifest.json")) { - return filePath.substr(0, filePath.length - "manifest.json".length) + subPath + "/manifest.json"; - } - return filePath; -}; - /** - * Dependency Mapping * - * @type {Map>} + * @param {Map} manifestHints */ -const dependencyMap = new Map(); - -/** - * - * @param {Map} manifestHints - */ -const resolveDependencies = (manifestHints) => { - manifestHints.forEach((manifestHint) => { +const resolveTransitiveDependencies = (manifestHints) => { + manifestHints.forEach((manifestHint, key) => { + const resolvedLibs = {}; Object.keys(manifestHint.libs).forEach((libName) => { - const resolved = resolve(libName, manifestHints); + resolvedLibs[libName] = manifestHint.libs[libName]; + const resolved = resolve(libName, manifestHints, resolvedLibs); Object.keys(resolved).forEach((nestedKey) => { manifestHint.libs[nestedKey] = resolved[nestedKey]; }); @@ -95,25 +94,27 @@ const resolveDependencies = (manifestHints) => { }); }; +const isLazy = (obj1, obj2) => { + return obj1.lazy && obj2.lazy; +}; + /** * * @param {string} libName - * @param {Map} manifestHints + * @param {Map} manifestHints + * @param {object} resolvedLibs */ -const resolve = (libName, manifestHints) => { +const resolve = (libName, manifestHints, resolvedLibs) => { const manifestHint = manifestHints.get(libName); - const resolvedLibs = {}; Object.keys(manifestHint.libs).forEach((childLibName) => { - resolvedLibs[childLibName] = manifestHint.libs[childLibName]; - const nested = resolve(childLibName, manifestHints); + if (resolvedLibs[childLibName] && !isLazy(manifestHint.libs[childLibName], resolvedLibs[childLibName])) { + resolvedLibs[childLibName] = {}; + } else { + resolvedLibs[childLibName] = manifestHint.libs[childLibName]; + } + + const nested = resolve(childLibName, manifestHints, resolvedLibs); Object.keys(nested).forEach((nestedKey) => { - if (nested[nestedKey].lazy) { - if (!resolvedLibs[nestedKey] || resolvedLibs[nestedKey].lazy) { - resolvedLibs[nestedKey] = { - lazy: true - }; - } - } resolvedLibs[nestedKey] = resolvedLibs[nestedKey] || {}; }); }); @@ -121,6 +122,17 @@ const resolve = (libName, manifestHints) => { return resolvedLibs; }; + +const out = (libs) => { + const res = Object.keys(libs).map((libName) => { + if (libs[libName].lazy) { + return libName + " (" + libs[libName].lazy + ")"; + } + return libName; + }); + return res; +}; + /** * Creates sap-ui-version.json. * @@ -145,31 +157,38 @@ module.exports = async function({options}) { const components = []; /** + * @example + * "sap.ui.integration": { + * sap.chart: { + * lazy: true + * }, + * sap.f: { }, + * } * - * @type {Map} + * @type {Map} */ - const manifestHints = new Map(); + const dependencyInfoMap = new Map(); // gather all manifestHints const librariesPromises = options.libraryInfos.map((libraryInfo) => { return processManifest(libraryInfo.mainManifest).then((manifestHint) => { - manifestHints.set(libraryInfo.name, manifestHint); + dependencyInfoMap.set(libraryInfo.name, manifestHint); }); }); await Promise.all(librariesPromises); - console.log("before"); - manifestHints.forEach((manifestHint, key) => { - console.log(`${key} => ${Object.keys(manifestHint.libs).join(", ")}`); + console.log("before:"); + dependencyInfoMap.forEach((manifestHint, key) => { + console.log(`${key} => ${out(manifestHint.libs).join(", ")}`); }); - // resolve nested dependencies - resolveDependencies(manifestHints); + // resolve nested dependencies (transitive) + resolveTransitiveDependencies(dependencyInfoMap); - console.log("after"); - manifestHints.forEach((manifestHint, key) => { - console.log(`${key} => ${Object.keys(manifestHint.libs).join(", ")}`); + console.log("\nafter:"); + dependencyInfoMap.forEach((manifestHint, key) => { + console.log(`${key} => ${out(manifestHint.libs).join(", ")}`); }); @@ -181,7 +200,7 @@ module.exports = async function({options}) { scmRevision: ""// TODO: insert current library scm revision here }; - const libs = manifestHints.get(libraryInfo.name).libs; + const libs = dependencyInfoMap.get(libraryInfo.name).libs; if (Object.keys(libs).length) { result.manifestHints = { dependencies: { diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 388e09c22..83ed77c14 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -234,6 +234,18 @@ test("integration: Library without i18n bundle with manifest", async (t) => { project: workspace._project })); + // lib.a => lib.c, lib.b + // lib.b => lib.d + // lib.c => lib.e, lib.b (true) + // lib.d => lib.e (true) + // lib.e => + + // lib.b => lib.d, lib.e (true) + // lib.c => + // lib.a => lib.c, lib.b, lib.d, lib.e (true) + // lib.d => lib.e (true) + // lib.e => + await workspace.write(resourceFactory.createResource({ path: "/resources/test/lib/subcomp/manifest.json", string: ` @@ -356,6 +368,329 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "dependencies": { "minUI5Version": "1.84", "libs": { + "lib.b": { + "minVersion": "1.84.0", + "lazy": true + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "c"]) + })); + + // lib.b + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/b/.library", + string: ` + + + lib.b + SAP SE + + 2.0.0 + + Library B + + `, + project: createProjectMetadata(["lib", "b"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/b/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + } + } + } + } + `, + project: createProjectMetadata(["lib", "b"]) + })); + + + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": [], + "libraries": [{ + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.d": {}, + "lib.c": {} + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {} + } + } + }, + "name": "lib.b", + "scmRevision": "", + }, + { + "name": "lib.c", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + lazy: true + } + } + } + }, + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); +}); + +test("integration: Library without i18n bundle with manifest max", async (t) => { + const workspace = resourceFactory.createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "test.lib", + namespace: "test/lib" + }, + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] + } + }); + + // TODO .library should not be required + // only use .library if manifest.json is not there + await workspace.write(resourceFactory.createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + `, + project: workspace._project + })); + + await workspace.write(resourceFactory.createResource({ + path: "/resources/test/lib/manifest.json", + string: ` + { + "sap.app": { + "embeds": ["subcomp"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.a": { + "minVersion": "1.84.0" + }, + "lib.b": { + "minVersion": "1.84.0", + "lazy": true + } + } + } + } + } + `, + project: workspace._project + })); + + // lib.a => lib.c, lib.b + // lib.b => lib.d + // lib.c => lib.e, lib.b (true) + // lib.d => lib.e (true) + // lib.e => + + // lib.b => lib.d, lib.e (true) + // lib.c => + // lib.a => lib.c, lib.b, lib.d, lib.e (true) + // lib.d => lib.e (true) + // lib.e => + + await workspace.write(resourceFactory.createResource({ + path: "/resources/test/lib/subcomp/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "libY": { + "minVersion": "1.84.0" + }, + "libX": { + "minVersion": "1.84.0", + "lazy": true + } + } + } + } + } + `, + project: workspace._project + })); + + // dependencies + const createProjectMetadata = (nameArray) => { + return { + metadata: { + name: nameArray.join("."), + namespace: nameArray.join("/") + } + }; + }; + const dependencies = resourceFactory.createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "lib.a", + namespace: "lib/a" + }, + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] + } + }); + + // lib.a + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/.library", + string: ` + + + lib.a + SAP SE + + 2.0.0 + + Library A + + `, + project: createProjectMetadata(["lib", "a"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.c": { + "minVersion": "1.84.0" + }, + "lib.b": { + "minVersion": "1.84.0" + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "a"]) + })); + + // lib.c + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/c/.library", + string: ` + + + lib.c + SAP SE + + 2.0.0 + + Library C + + `, + project: createProjectMetadata(["lib", "c"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/c/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.e": { + "minVersion": "1.84.0" + }, + "lib.b": { + "minVersion": "1.84.0", + "lazy": true + } } } } @@ -429,6 +764,10 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "dependencies": { "minUI5Version": "1.84", "libs": { + "lib.e": { + "minVersion": "1.84.0", + "lazy": true + } } } } @@ -437,6 +776,41 @@ test("integration: Library without i18n bundle with manifest", async (t) => { project: createProjectMetadata(["lib", "d"]) })); + // lib.e + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/e/.library", + string: ` + + + lib.e + SAP SE + + 2.0.0 + + Library E + + `, + project: createProjectMetadata(["lib", "e"]) + })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/e/manifest.json", + string: ` + { + "sap.app": { + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + } + } + } + } + `, + project: createProjectMetadata(["lib", "e"]) + })); + const oOptions = { options: { projectName: "Test Lib", @@ -458,21 +832,52 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "dependencies": { "libs": { "lib.b": {}, - "lib.c": {} + "lib.d": {}, + "lib.c": {}, + "lib.e": { + lazy: true + } } } }, "name": "lib.a", "scmRevision": "", }, - { - "name": "lib.b", - "scmRevision": "", - }, - { - "name": "lib.c", - "scmRevision": "", - }], + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": { + lazy: true + } + } + } + }, + "name": "lib.b", + "scmRevision": "", + }, + { + "name": "lib.c", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + lazy: true + } + } + } + }, + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", + }], "name": "myname", "scmRevision": "", "version": "1.33.7", From 58fea7defc0ee71b22449fb3db6961680c8734e2 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 1 Dec 2020 17:10:12 +0100 Subject: [PATCH 05/41] [INTERNAL][WIP] VersionInfo: with manifest infos added logic to use info for embeds --- lib/processors/versionInfoGenerator.js | 160 ++++++++++++++++++++----- test/lib/tasks/generateVersionInfo.js | 115 ++++++++++-------- 2 files changed, 191 insertions(+), 84 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 36ed17d15..9116f6c99 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -43,6 +43,7 @@ const processManifest = async (manifestResource) => { if (manifestEmbeds) { result.embeds = manifestEmbeds; } + result.id = manifestObject["sap.app"]["id"]; return result; }; @@ -52,6 +53,8 @@ const processManifest = async (manifestResource) => { * @public * @typedef {object} DependencyInfos * @property {object} libs The library object + * @property {string[]} embeds embedded components + * @property {string} id id * * * @example @@ -76,50 +79,98 @@ const processManifest = async (manifestResource) => { * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources */ +const getManifestPath = (filePath, subPath) => { + if (filePath.endsWith("manifest.json")) { + return filePath.substr(0, filePath.length - "manifest.json".length) + subPath + "/manifest.json"; + } + return filePath; +}; /** * * @param {Map} manifestHints */ const resolveTransitiveDependencies = (manifestHints) => { - manifestHints.forEach((manifestHint, key) => { - const resolvedLibs = {}; - Object.keys(manifestHint.libs).forEach((libName) => { - resolvedLibs[libName] = manifestHint.libs[libName]; - const resolved = resolve(libName, manifestHints, resolvedLibs); - Object.keys(resolved).forEach((nestedKey) => { - manifestHint.libs[nestedKey] = resolved[nestedKey]; - }); - }); + // top level libraries + // // lib.a => lib.c, lib.b + // // lib.b => lib.d + // // lib.c => lib.e, lib.b (true) + // // lib.d => lib.e (true) + // // lib.e => + // TODO optimize duplicate resolve (e.g. cache) + + // lib.c => lib.e, lib.b (true), lib.d + // lib.a => lib.c, lib.b, lib.d, lib.e + // lib.b => lib.d, lib.e (true) + // lib.d => lib.e (true) + // lib.e => + const keys = [...manifestHints.keys()]; + keys.sort(); + const resolvedCache = new Map(); + keys.forEach((libName) => { + resolve(libName, manifestHints, resolvedCache); }); }; -const isLazy = (obj1, obj2) => { - return obj1.lazy && obj2.lazy; +const clone = (obj) => { + return JSON.parse(JSON.stringify(obj)); }; /** * - * @param {string} libName * @param {Map} manifestHints - * @param {object} resolvedLibs + * @param {string} libName + * @param {object} newObject */ -const resolve = (libName, manifestHints, resolvedLibs) => { - const manifestHint = manifestHints.get(libName); - Object.keys(manifestHint.libs).forEach((childLibName) => { - if (resolvedLibs[childLibName] && !isLazy(manifestHint.libs[childLibName], resolvedLibs[childLibName])) { - resolvedLibs[childLibName] = {}; - } else { - resolvedLibs[childLibName] = manifestHint.libs[childLibName]; - } +const setManifestHints = (manifestHints, libName, newObject) => { + const existingEntry = manifestHints.get(libName); + const newLibs = merge(existingEntry && existingEntry, newObject); + console.log(` setting ${libName} ==> ${Object.keys(newLibs).join(", ")}`); + manifestHints.set(libName, newLibs); +}; - const nested = resolve(childLibName, manifestHints, resolvedLibs); - Object.keys(nested).forEach((nestedKey) => { - resolvedLibs[nestedKey] = resolvedLibs[nestedKey] || {}; +const merge = (existingEntry, newObject) => { + const newLibs = clone(newObject); + if (existingEntry) { + Object.keys(existingEntry).forEach((libName) => { + if (!existingEntry[libName].lazy && newLibs[libName] && newLibs[libName].lazy) { + newLibs[libName] = {}; + } + if (!newLibs[libName]) { + newLibs[libName] = existingEntry[libName]; + } }); - }); + } + return newLibs; +}; - return resolvedLibs; +/** + * + * @param {string} libName + * @param {Map} manifestHints + * @param {Map} resolvedCache + * @returns {DependencyInfos} resolved dependencies + */ +const resolve = (libName, manifestHints, resolvedCache) => { + // lib.c get entries + // lib.c => lib.b (true) + // lib.b => + if ( resolvedCache.has(libName)) { + return resolvedCache.get(libName); + } + const manifestHint = manifestHints.get(libName); // lib.c + console.log(`:processing: ${libName}`); + const keys = Object.keys(manifestHint); // [lib.b] + let resolved = {}; + keys.forEach((childLibName) => { + const childResolved = resolve(childLibName, manifestHints, resolvedCache); + resolved = merge(resolved, childResolved); + console.log(`resolved ${childLibName} with ${Object.keys(resolved).join(", ")}`); + }); + resolved = merge(resolved, manifestHint); + setManifestHints(manifestHints, libName, resolved); + resolvedCache.set(libName, resolved); + return resolved; }; @@ -155,7 +206,7 @@ module.exports = async function({options}) { const buildTimestamp = getTimestamp(); // TODO filter manifest.json if sap/embeds (we expect it contains the correct information) - const components = []; + const components = {}; /** * @example * "sap.ui.integration": { @@ -168,19 +219,44 @@ module.exports = async function({options}) { * @type {Map} */ const dependencyInfoMap = new Map(); + const embeddedInfoMap = new Map(); // gather all manifestHints const librariesPromises = options.libraryInfos.map((libraryInfo) => { + // TODO use proper async await! return processManifest(libraryInfo.mainManifest).then((manifestHint) => { - dependencyInfoMap.set(libraryInfo.name, manifestHint); + dependencyInfoMap.set(libraryInfo.name, manifestHint.libs); + return manifestHint.embeds; + }).then((embeds) => { + // filter + embeds.forEach((embed) => { + embeddedInfoMap.set(embed, { + library: libraryInfo.name + }); + }); + const embeddedPaths = embeds.map((embed) => { + return getManifestPath(libraryInfo.mainManifest.getPath(), embed); + }); + const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { + return embeddedPaths.includes(manifestResource.getPath()); + }); + + // get all embeds manifests + return Promise.all(relevantManifests.map((relevantManifest) => { + return processManifest(relevantManifest).then((result) => { + dependencyInfoMap.set(result.id, result.libs); + }); + })); }); }); + // gather embeds' manifest and do the same + await Promise.all(librariesPromises); console.log("before:"); dependencyInfoMap.forEach((manifestHint, key) => { - console.log(`${key} => ${out(manifestHint.libs).join(", ")}`); + console.log(`${key} => ${out(manifestHint).join(", ")}`); }); // resolve nested dependencies (transitive) @@ -188,7 +264,7 @@ module.exports = async function({options}) { console.log("\nafter:"); dependencyInfoMap.forEach((manifestHint, key) => { - console.log(`${key} => ${out(manifestHint.libs).join(", ")}`); + console.log(`${key} => ${out(manifestHint).join(", ")}`); }); @@ -200,7 +276,8 @@ module.exports = async function({options}) { scmRevision: ""// TODO: insert current library scm revision here }; - const libs = dependencyInfoMap.get(libraryInfo.name).libs; + const libs = dependencyInfoMap.get(libraryInfo.name); + // TODO: sort the libs if (Object.keys(libs).length) { result.manifestHints = { dependencies: { @@ -211,10 +288,29 @@ module.exports = async function({options}) { return result; }); + // TODO sort! + embeddedInfoMap.forEach((embeddedInfo, libName) => { + components[libName] = { + library: embeddedInfo.library + }; + const libs = dependencyInfoMap.get(libName); + if (libs && Object.keys(libs).length) { + components[libName].manifestHints = { + dependencies: { + libs: libs + } + }; + } + }); + + // sort alphabetically + libraries.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + // TODO enrich components - const versionJson = { name: options.rootProjectName, version: options.rootProjectVersion, // TODO: insert current application version here diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 83ed77c14..7e5f92896 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -416,7 +416,6 @@ test("integration: Library without i18n bundle with manifest", async (t) => { })); - const oOptions = { options: { projectName: "Test Lib", @@ -432,13 +431,12 @@ test("integration: Library without i18n bundle with manifest", async (t) => { dependencies }; await assertCreatedVersionInfo(t, { - "components": [], + "components": {}, "libraries": [{ "manifestHints": { "dependencies": { "libs": { "lib.b": {}, - "lib.d": {}, "lib.c": {} } } @@ -447,35 +445,20 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "scmRevision": "", }, { - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {} - } - } - }, "name": "lib.b", "scmRevision": "", }, - { - "name": "lib.c", - "scmRevision": "", - }, { "manifestHints": { "dependencies": { "libs": { - "lib.e": { - lazy: true + "lib.b": { + "lazy": true } } } }, - "name": "lib.d", - "scmRevision": "", - }, - { - "name": "lib.e", + "name": "lib.c", "scmRevision": "", }], "name": "myname", @@ -485,6 +468,25 @@ test("integration: Library without i18n bundle with manifest", async (t) => { }); test("integration: Library without i18n bundle with manifest max", async (t) => { + // // top level libraries + // // // lib.a => lib.c, lib.b + // // // lib.b => lib.d + // // // lib.c => lib.e, lib.b (true) + // // // lib.d => lib.e (true) + // // // lib.e => + // // TODO optimize duplicate resolve (e.g. cache) + // + // // lib.a + // // :processing: lib.c + // // :processing: lib.e + // // setting lib.e ==> + // // :processing: lib.b + // // :processing: lib.d + // // :processing: lib.e + // // setting lib.e ==> + // // setting lib.d ==> lib.e + // // setting lib.b ==> lib.d + // // setting lib.c ==> lib.e, lib.b const workspace = resourceFactory.createAdapter({ virBasePath: "/", project: { @@ -826,7 +828,7 @@ test("integration: Library without i18n bundle with manifest max", async (t) => dependencies }; await assertCreatedVersionInfo(t, { - "components": [], + "components": {}, "libraries": [{ "manifestHints": { "dependencies": { @@ -834,50 +836,59 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "lib.b": {}, "lib.d": {}, "lib.c": {}, + "lib.e": {} + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, "lib.e": { lazy: true } } } }, - "name": "lib.a", + "name": "lib.b", "scmRevision": "", }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": { - lazy: true - } + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {}, + "lib.d": {}, + "lib.b": { + lazy: true } } - }, - "name": "lib.b", - "scmRevision": "", - }, - { - "name": "lib.c", - "scmRevision": "", + } }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": { - lazy: true - } + "name": "lib.c", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + lazy: true } } - }, - "name": "lib.d", - "scmRevision": "", + } }, - { - "name": "lib.e", - "scmRevision": "", - }], + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", + }], "name": "myname", "scmRevision": "", "version": "1.33.7", From 475ef287014f925db5079995b4b1e0d05609d3c8 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 10:44:27 +0100 Subject: [PATCH 06/41] [INTERNAL][WIP] VersionInfo: with manifest infos improved components structure --- lib/processors/versionInfoGenerator.js | 70 +++++---------------- test/lib/tasks/generateVersionInfo.js | 85 ++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 59 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 9116f6c99..fc38c576d 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -27,7 +27,8 @@ const processManifest = async (manifestResource) => { const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; const result = { embeds: [], - libs: {} + libs: {}, + id: undefined }; if (manifestDependencies) { Object.keys(manifestDependencies.libs).forEach((libKey) => { @@ -91,19 +92,6 @@ const getManifestPath = (filePath, subPath) => { * @param {Map} manifestHints */ const resolveTransitiveDependencies = (manifestHints) => { - // top level libraries - // // lib.a => lib.c, lib.b - // // lib.b => lib.d - // // lib.c => lib.e, lib.b (true) - // // lib.d => lib.e (true) - // // lib.e => - // TODO optimize duplicate resolve (e.g. cache) - - // lib.c => lib.e, lib.b (true), lib.d - // lib.a => lib.c, lib.b, lib.d, lib.e - // lib.b => lib.d, lib.e (true) - // lib.d => lib.e (true) - // lib.e => const keys = [...manifestHints.keys()]; keys.sort(); const resolvedCache = new Map(); @@ -125,7 +113,6 @@ const clone = (obj) => { const setManifestHints = (manifestHints, libName, newObject) => { const existingEntry = manifestHints.get(libName); const newLibs = merge(existingEntry && existingEntry, newObject); - console.log(` setting ${libName} ==> ${Object.keys(newLibs).join(", ")}`); manifestHints.set(libName, newLibs); }; @@ -152,20 +139,22 @@ const merge = (existingEntry, newObject) => { * @returns {DependencyInfos} resolved dependencies */ const resolve = (libName, manifestHints, resolvedCache) => { - // lib.c get entries - // lib.c => lib.b (true) - // lib.b => + // check cache first if ( resolvedCache.has(libName)) { return resolvedCache.get(libName); } const manifestHint = manifestHints.get(libName); // lib.c - console.log(`:processing: ${libName}`); - const keys = Object.keys(manifestHint); // [lib.b] + // console.log(`:processing: ${libName}`); let resolved = {}; + if (!manifestHint) { + console.error(`no manifest information in dependencies for ${libName}`); + resolvedCache.set(libName, resolved); + return resolved; + } + const keys = Object.keys(manifestHint); // [lib.b] keys.forEach((childLibName) => { const childResolved = resolve(childLibName, manifestHints, resolvedCache); resolved = merge(resolved, childResolved); - console.log(`resolved ${childLibName} with ${Object.keys(resolved).join(", ")}`); }); resolved = merge(resolved, manifestHint); setManifestHints(manifestHints, libName, resolved); @@ -174,16 +163,6 @@ const resolve = (libName, manifestHints, resolvedCache) => { }; -const out = (libs) => { - const res = Object.keys(libs).map((libName) => { - if (libs[libName].lazy) { - return libName + " (" + libs[libName].lazy + ")"; - } - return libName; - }); - return res; -}; - /** * Creates sap-ui-version.json. * @@ -204,7 +183,6 @@ module.exports = async function({options}) { } const buildTimestamp = getTimestamp(); - // TODO filter manifest.json if sap/embeds (we expect it contains the correct information) const components = {}; /** @@ -229,11 +207,6 @@ module.exports = async function({options}) { return manifestHint.embeds; }).then((embeds) => { // filter - embeds.forEach((embed) => { - embeddedInfoMap.set(embed, { - library: libraryInfo.name - }); - }); const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.mainManifest.getPath(), embed); }); @@ -245,6 +218,9 @@ module.exports = async function({options}) { return Promise.all(relevantManifests.map((relevantManifest) => { return processManifest(relevantManifest).then((result) => { dependencyInfoMap.set(result.id, result.libs); + embeddedInfoMap.set(result.id, { + library: libraryInfo.name + }); }); })); }); @@ -254,19 +230,9 @@ module.exports = async function({options}) { await Promise.all(librariesPromises); - console.log("before:"); - dependencyInfoMap.forEach((manifestHint, key) => { - console.log(`${key} => ${out(manifestHint).join(", ")}`); - }); - // resolve nested dependencies (transitive) resolveTransitiveDependencies(dependencyInfoMap); - console.log("\nafter:"); - dependencyInfoMap.forEach((manifestHint, key) => { - console.log(`${key} => ${out(manifestHint).join(", ")}`); - }); - const libraries = options.libraryInfos.map((libraryInfo) => { const result = { @@ -277,7 +243,6 @@ module.exports = async function({options}) { }; const libs = dependencyInfoMap.get(libraryInfo.name); - // TODO: sort the libs if (Object.keys(libs).length) { result.manifestHints = { dependencies: { @@ -290,9 +255,7 @@ module.exports = async function({options}) { // TODO sort! embeddedInfoMap.forEach((embeddedInfo, libName) => { - components[libName] = { - library: embeddedInfo.library - }; + components[libName] = embeddedInfo; const libs = dependencyInfoMap.get(libName); if (libs && Object.keys(libs).length) { components[libName].manifestHints = { @@ -303,14 +266,11 @@ module.exports = async function({options}) { } }); - // sort alphabetically + // sort libraries alphabetically libraries.sort((a, b) => { return a.name.localeCompare(b.name); }); - - // TODO enrich components - const versionJson = { name: options.rootProjectName, version: options.rootProjectVersion, // TODO: insert current application version here diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 7e5f92896..e7ba2fb97 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -321,7 +321,7 @@ test("integration: Library without i18n bundle with manifest", async (t) => { string: ` { "sap.app": { - "embeds": [] + "embeds": ["sub/fold"] }, "sap.ui5": { "dependencies": { @@ -341,6 +341,30 @@ test("integration: Library without i18n bundle with manifest", async (t) => { project: createProjectMetadata(["lib", "a"]) })); + // sub + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/sub/fold/manifest.json", + string: ` + { + "sap.app": { + "id": "lib.a.sub.fold", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.c": { + "minVersion": "1.84.0" + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "a", "sub", "fold"]) + })); + // lib.c await dependencies.write(resourceFactory.createResource({ path: "/resources/lib/c/.library", @@ -431,7 +455,21 @@ test("integration: Library without i18n bundle with manifest", async (t) => { dependencies }; await assertCreatedVersionInfo(t, { - "components": {}, + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": {} + } + } + } + } + }, "libraries": [{ "manifestHints": { "dependencies": { @@ -639,7 +677,7 @@ test("integration: Library without i18n bundle with manifest max", async (t) => string: ` { "sap.app": { - "embeds": [] + "embeds": ["sub"] }, "sap.ui5": { "dependencies": { @@ -659,6 +697,29 @@ test("integration: Library without i18n bundle with manifest max", async (t) => project: createProjectMetadata(["lib", "a"]) })); + await dependencies.write(resourceFactory.createResource({ + path: "/resources/lib/a/sub/manifest.json", + string: ` + { + "sap.app": { + "embeds": [], + "id": "lib.a.sub" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "lib.c": { + "minVersion": "1.84.0" + } + } + } + } + } + `, + project: createProjectMetadata(["lib", "a", "sub"]) + })); + // lib.c await dependencies.write(resourceFactory.createResource({ path: "/resources/lib/c/.library", @@ -828,7 +889,23 @@ test("integration: Library without i18n bundle with manifest max", async (t) => dependencies }; await assertCreatedVersionInfo(t, { - "components": {}, + "components": { + "lib.a.sub": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.d": {}, + "lib.c": {}, + "lib.e": {} + } + } + } + } + }, "libraries": [{ "manifestHints": { "dependencies": { From 3b20dbd6c99a69ebbbe40423e8c928b07d5bf065 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 11:21:38 +0100 Subject: [PATCH 07/41] [INTERNAL][WIP] VersionInfo: with manifest infos minor adjustments --- lib/processors/versionInfoGenerator.js | 105 ++++++++++++++----------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index fc38c576d..078b784a5 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -1,4 +1,6 @@ +const log = require("@ui5/logger").getLogger("builder:processors:versionInfogenerator"); const resourceFactory = require("@ui5/fs").resourceFactory; +const path = require("path"); function pad(v) { return String(v).padStart(2, "0"); @@ -18,52 +20,66 @@ function getTimestamp() { /** * * @param {module:@ui5/fs.Resource} manifestResource - * @returns {Promise} + * @returns {Promise} */ const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); const manifestObject = JSON.parse(manifestContent); - // TODO extract manifestHints - const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; const result = { embeds: [], libs: {}, id: undefined }; - if (manifestDependencies) { - Object.keys(manifestDependencies.libs).forEach((libKey) => { - result.libs[libKey] = {}; - if (manifestDependencies.libs[libKey].lazy) { - result.libs[libKey].lazy = true; - } - }); + + // sap.ui5/dependencies is used for the manifestHints + if (manifestObject["sap.ui5"]) { + const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; + if (manifestDependencies) { + Object.keys(manifestDependencies.libs).forEach((libKey) => { + result.libs[libKey] = {}; + if (manifestDependencies.libs[libKey].lazy) { + result.libs[libKey].lazy = true; + } + }); + } } - // there for components - const manifestEmbeds = manifestObject["sap.app"]["embeds"]; - if (manifestEmbeds) { - result.embeds = manifestEmbeds; + // sap.app/embeds is required for "components" in sap-ui-version.json + if (manifestObject["sap.app"]) { + const manifestEmbeds = manifestObject["sap.app"]["embeds"]; + if (manifestEmbeds) { + result.embeds = manifestEmbeds; + } + result.id = manifestObject["sap.app"]["id"]; } - result.id = manifestObject["sap.app"]["id"]; return result; }; +/** + * Library Info + * + * @typedef {object} DependencyInfos + */ + /** * Manifest Hint * - * @public - * @typedef {object} DependencyInfos - * @property {object} libs The library object - * @property {string[]} embeds embedded components - * @property {string} id id + * @typedef {object} ManifestInfos + * @property {DependencyInfos} libs The library object + * @property {string[]} embeds embedded components, e.g. "sub/fold" (only relative path) + * @property {string} id the app id, e.g. "lib.a" * * * @example * { - * sap.chart: { - * lazy: true + * libs: { + * sap.chart: { + * lazy: true + * }, + * sap.f: { }, * }, - * sap.f: { }, + * id: "lib.a", + * embeds: ["sub"] * } * */ @@ -72,7 +88,6 @@ const processManifest = async (manifestResource) => { /** * Library Info object * - * @public * @typedef {object} LibraryInfo * @property {string} name The library name * @property {string} version The library version @@ -82,21 +97,22 @@ const processManifest = async (manifestResource) => { const getManifestPath = (filePath, subPath) => { if (filePath.endsWith("manifest.json")) { - return filePath.substr(0, filePath.length - "manifest.json".length) + subPath + "/manifest.json"; + const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; + return path.posix.resolve(folderPathOfManifest + "/manifest.json"); } return filePath; }; /** * - * @param {Map} manifestHints + * @param {Map} libraryInfosMap */ -const resolveTransitiveDependencies = (manifestHints) => { - const keys = [...manifestHints.keys()]; +const resolveTransitiveDependencies = (libraryInfosMap) => { + const keys = [...libraryInfosMap.keys()]; keys.sort(); const resolvedCache = new Map(); keys.forEach((libName) => { - resolve(libName, manifestHints, resolvedCache); + resolve(libName, libraryInfosMap, resolvedCache); }); }; @@ -106,14 +122,14 @@ const clone = (obj) => { /** * - * @param {Map} manifestHints + * @param {Map} libraryInfosMap * @param {string} libName * @param {object} newObject */ -const setManifestHints = (manifestHints, libName, newObject) => { - const existingEntry = manifestHints.get(libName); - const newLibs = merge(existingEntry && existingEntry, newObject); - manifestHints.set(libName, newLibs); +const setManifestHints = (libraryInfosMap, libName, newObject) => { + const existingEntry = libraryInfosMap.get(libName); + const newLibs = merge(existingEntry, newObject); + libraryInfosMap.set(libName, newLibs); }; const merge = (existingEntry, newObject) => { @@ -121,7 +137,7 @@ const merge = (existingEntry, newObject) => { if (existingEntry) { Object.keys(existingEntry).forEach((libName) => { if (!existingEntry[libName].lazy && newLibs[libName] && newLibs[libName].lazy) { - newLibs[libName] = {}; + delete newLibs[libName].lazy; } if (!newLibs[libName]) { newLibs[libName] = existingEntry[libName]; @@ -134,30 +150,29 @@ const merge = (existingEntry, newObject) => { /** * * @param {string} libName - * @param {Map} manifestHints + * @param {Map} libraryInfosMap * @param {Map} resolvedCache * @returns {DependencyInfos} resolved dependencies */ -const resolve = (libName, manifestHints, resolvedCache) => { +const resolve = (libName, libraryInfosMap, resolvedCache) => { // check cache first if ( resolvedCache.has(libName)) { return resolvedCache.get(libName); } - const manifestHint = manifestHints.get(libName); // lib.c - // console.log(`:processing: ${libName}`); + const manifestHint = libraryInfosMap.get(libName); let resolved = {}; if (!manifestHint) { - console.error(`no manifest information in dependencies for ${libName}`); + log.error(`no manifest information in dependencies for ${libName}`); resolvedCache.set(libName, resolved); return resolved; } - const keys = Object.keys(manifestHint); // [lib.b] + const keys = Object.keys(manifestHint); keys.forEach((childLibName) => { - const childResolved = resolve(childLibName, manifestHints, resolvedCache); + const childResolved = resolve(childLibName, libraryInfosMap, resolvedCache); resolved = merge(resolved, childResolved); }); resolved = merge(resolved, manifestHint); - setManifestHints(manifestHints, libName, resolved); + setManifestHints(libraryInfosMap, libName, resolved); resolvedCache.set(libName, resolved); return resolved; }; @@ -214,7 +229,7 @@ module.exports = async function({options}) { return embeddedPaths.includes(manifestResource.getPath()); }); - // get all embeds manifests + // get all embedded manifests return Promise.all(relevantManifests.map((relevantManifest) => { return processManifest(relevantManifest).then((result) => { dependencyInfoMap.set(result.id, result.libs); @@ -226,8 +241,6 @@ module.exports = async function({options}) { }); }); - // gather embeds' manifest and do the same - await Promise.all(librariesPromises); // resolve nested dependencies (transitive) From a445beefd0e843bf78da16e198492aebc3d93335 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 11:51:19 +0100 Subject: [PATCH 08/41] [INTERNAL][WIP] VersionInfo: with manifest infos minor adjustments --- lib/processors/versionInfoGenerator.js | 33 ++++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 078b784a5..a354ce16a 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -31,7 +31,7 @@ const processManifest = async (manifestResource) => { id: undefined }; - // sap.ui5/dependencies is used for the manifestHints + // sap.ui5/dependencies is used for the "manifestHints/libs" if (manifestObject["sap.ui5"]) { const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; if (manifestDependencies) { @@ -44,7 +44,7 @@ const processManifest = async (manifestResource) => { } } - // sap.app/embeds is required for "components" in sap-ui-version.json + // sap.app/embeds is used for "components" if (manifestObject["sap.app"]) { const manifestEmbeds = manifestObject["sap.app"]["embeds"]; if (manifestEmbeds) { @@ -59,6 +59,14 @@ const processManifest = async (manifestResource) => { * Library Info * * @typedef {object} DependencyInfos + * + * * @example + * { + * sap.chart: { + * lazy: true + * }, + * sap.f: { } + * } */ /** @@ -120,20 +128,7 @@ const clone = (obj) => { return JSON.parse(JSON.stringify(obj)); }; -/** - * - * @param {Map} libraryInfosMap - * @param {string} libName - * @param {object} newObject - */ -const setManifestHints = (libraryInfosMap, libName, newObject) => { - const existingEntry = libraryInfosMap.get(libName); - const newLibs = merge(existingEntry, newObject); - libraryInfosMap.set(libName, newLibs); -}; - -const merge = (existingEntry, newObject) => { - const newLibs = clone(newObject); +const merge = (existingEntry, newLibs) => { if (existingEntry) { Object.keys(existingEntry).forEach((libName) => { if (!existingEntry[libName].lazy && newLibs[libName] && newLibs[libName].lazy) { @@ -171,8 +166,10 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { const childResolved = resolve(childLibName, libraryInfosMap, resolvedCache); resolved = merge(resolved, childResolved); }); - resolved = merge(resolved, manifestHint); - setManifestHints(libraryInfosMap, libName, resolved); + resolved = merge(resolved, libraryInfosMap.get(libName)); + + // set a copy of the resolved libraries to avoid modifying it while iterating (recursively) + libraryInfosMap.set(libName, clone(resolved)); resolvedCache.set(libName, resolved); return resolved; }; From f4ea6b794e94e9991c096e6dc9e84a381307931b Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 12:07:20 +0100 Subject: [PATCH 09/41] [INTERNAL][WIP] VersionInfo: with manifest infos simplified merge logic --- lib/processors/versionInfoGenerator.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index a354ce16a..c41c57ea9 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -155,7 +155,7 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { return resolvedCache.get(libName); } const manifestHint = libraryInfosMap.get(libName); - let resolved = {}; + let resolved = manifestHint; if (!manifestHint) { log.error(`no manifest information in dependencies for ${libName}`); resolvedCache.set(libName, resolved); @@ -164,12 +164,11 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { const keys = Object.keys(manifestHint); keys.forEach((childLibName) => { const childResolved = resolve(childLibName, libraryInfosMap, resolvedCache); - resolved = merge(resolved, childResolved); + // set a copy of the resolved libraries to avoid modifying it while iterating (recursively) + resolved = merge(resolved, clone(childResolved)); }); - resolved = merge(resolved, libraryInfosMap.get(libName)); - // set a copy of the resolved libraries to avoid modifying it while iterating (recursively) - libraryInfosMap.set(libName, clone(resolved)); + libraryInfosMap.set(libName, resolved); resolvedCache.set(libName, resolved); return resolved; }; From 6d6af3bdca4ad0500622fd9e246b00cee9739267 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 14:44:20 +0100 Subject: [PATCH 10/41] [INTERNAL][WIP] VersionInfo: with manifest infos improved test sort keys of objects --- lib/processors/versionInfoGenerator.js | 115 +++++---- lib/tasks/generateVersionInfo.js | 16 +- test/lib/tasks/generateVersionInfo.js | 328 ++++++++++--------------- 3 files changed, 201 insertions(+), 258 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index c41c57ea9..f51bb10c2 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -158,8 +158,8 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { let resolved = manifestHint; if (!manifestHint) { log.error(`no manifest information in dependencies for ${libName}`); - resolvedCache.set(libName, resolved); - return resolved; + resolvedCache.set(libName, {}); + return {}; } const keys = Object.keys(manifestHint); keys.forEach((childLibName) => { @@ -173,6 +173,56 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { return resolved; }; +/** + * Sorts the keys of a given object + * + * @param {object} obj the object + * @returns {{}} + */ +const sortObjectKeys = (obj) => { + const sortedObject = {}; + const keys = Object.keys(obj); + keys.sort(); + keys.forEach((key) => { + sortedObject[key] = obj[key]; + }); + return sortedObject; +}; + +const addManifestHints = (result, libs) => { + if (Object.keys(libs).length) { + const sortedLibs = sortObjectKeys(libs); + result.manifestHints = { + dependencies: { + libs: sortedLibs + } + }; + } +}; + +const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { + const manifestInfo = await processManifest(libraryInfo.mainManifest); + dependencyInfoMap.set(libraryInfo.name, manifestInfo.libs); + const embeds = manifestInfo.embeds; + // filter + const embeddedPaths = embeds.map((embed) => { + return getManifestPath(libraryInfo.mainManifest.getPath(), embed); + }); + const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { + return embeddedPaths.includes(manifestResource.getPath()); + }); + + // get all embedded manifests + const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => { + const result = await processManifest(relevantManifest); + dependencyInfoMap.set(result.id, result.libs); + embeddedInfoMap.set(result.id, { + library: libraryInfo.name + }); + }); + + await Promise.all(embeddedManifestPromises); +}; /** * Creates sap-ui-version.json. @@ -183,8 +233,8 @@ const resolve = (libName, libraryInfosMap, resolvedCache) => { * @param {object} parameters.options Options * @param {string} parameters.options.rootProjectName Name of the root project * @param {string} parameters.options.rootProjectVersion Version of the root project - * @param {Array} parameters.options.libraryInfos Array of objects representing libraries, - * e.g. {name: "library.xy", version: "1.0.0"} + * @param {LibraryInfo[]} parameters.options.libraryInfos Array of objects representing libraries, + * e.g. {name: "library.xy", version: "1.0.0", manifests: module:@ui5/fs.Resource[]} * @returns {Promise} Promise resolving with an array containing the versioninfo resource */ @@ -198,11 +248,13 @@ module.exports = async function({options}) { const components = {}; /** * @example - * "sap.ui.integration": { - * sap.chart: { - * lazy: true - * }, - * sap.f: { }, + * { + * "sap.ui.integration": { + * "sap.chart": { + * "lazy": true + * }, + * "sap.f": { }, + * } * } * * @type {Map} @@ -212,29 +264,7 @@ module.exports = async function({options}) { // gather all manifestHints const librariesPromises = options.libraryInfos.map((libraryInfo) => { - // TODO use proper async await! - return processManifest(libraryInfo.mainManifest).then((manifestHint) => { - dependencyInfoMap.set(libraryInfo.name, manifestHint.libs); - return manifestHint.embeds; - }).then((embeds) => { - // filter - const embeddedPaths = embeds.map((embed) => { - return getManifestPath(libraryInfo.mainManifest.getPath(), embed); - }); - const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { - return embeddedPaths.includes(manifestResource.getPath()); - }); - - // get all embedded manifests - return Promise.all(relevantManifests.map((relevantManifest) => { - return processManifest(relevantManifest).then((result) => { - dependencyInfoMap.set(result.id, result.libs); - embeddedInfoMap.set(result.id, { - library: libraryInfo.name - }); - }); - })); - }); + return processLibraryInfo(libraryInfo, dependencyInfoMap, embeddedInfoMap); }); await Promise.all(librariesPromises); @@ -252,28 +282,17 @@ module.exports = async function({options}) { }; const libs = dependencyInfoMap.get(libraryInfo.name); - if (Object.keys(libs).length) { - result.manifestHints = { - dependencies: { - libs: libs - } - }; - } + addManifestHints(result, libs); return result; }); - // TODO sort! + // sort keys embeddedInfoMap.forEach((embeddedInfo, libName) => { components[libName] = embeddedInfo; const libs = dependencyInfoMap.get(libName); - if (libs && Object.keys(libs).length) { - components[libName].manifestHints = { - dependencies: { - libs: libs - } - }; - } + addManifestHints(components[libName], libs); }); + const sortedComponents = sortObjectKeys(components); // sort libraries alphabetically libraries.sort((a, b) => { @@ -287,7 +306,7 @@ module.exports = async function({options}) { scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here libraries, - components + components: sortedComponents }; return [resourceFactory.createResource({ diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 76634c530..97bf42e15 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -12,26 +12,16 @@ const MANIFEST_JSON = "manifest.json"; * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files * @param {object} parameters.options Options * @param {string} parameters.options.pattern Glob pattern for .library resources - * @param {string} parameters.options.namespace Namespace of the project * @param {object} parameters.options.rootProject DuplexCollection to read and write files * @returns {Promise} Promise resolving with undefined once data has been written */ -module.exports = async ({workspace, dependencies, options: {rootProject, pattern, namespace}}) => { +module.exports = async ({workspace, dependencies, options: {rootProject, pattern}}) => { const resources = await dependencies.byGlob(pattern); - // app always builds all dependencies -> therefore glob all manifest.json - // build with --all should not use the version-info.json - // logic needs to be adjusted once selective dependencies are built - // TODO: transient resources (not part of the build result) ( -> skipped for now) - // exclude task if not build --all - const libraryInfosPromises = resources.map((dotLibResource) => { const namespace = dotLibResource._project.metadata.namespace; - // TODO favor manifest.json over .library (check first manifest.json then as fallback .library) - // long-term goal: get rid of .library files - // TODO: compare the two - // use /**/ for nested manifests - // use /sap.app/embeds + // pass all required resources to the processor + // the processor will then filter return dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`).then((manifestResources) => { const mainManifest = manifestResources.find((manifestResource) => { return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index e7ba2fb97..a3d212713 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -85,7 +85,6 @@ async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { }); currentVersionInfo.libraries.sort((libraryA, libraryB) => { - return libraryA.name.localeCompare(libraryB.name); }); @@ -235,42 +234,12 @@ test("integration: Library without i18n bundle with manifest", async (t) => { })); // lib.a => lib.c, lib.b - // lib.b => lib.d - // lib.c => lib.e, lib.b (true) - // lib.d => lib.e (true) - // lib.e => - - // lib.b => lib.d, lib.e (true) + // lib.b => lib.c (true) // lib.c => - // lib.a => lib.c, lib.b, lib.d, lib.e (true) - // lib.d => lib.e (true) - // lib.e => - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/subcomp/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "libY": { - "minVersion": "1.84.0" - }, - "libX": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: workspace._project - })); + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => // dependencies const createProjectMetadata = (nameArray) => { @@ -506,25 +475,28 @@ test("integration: Library without i18n bundle with manifest", async (t) => { }); test("integration: Library without i18n bundle with manifest max", async (t) => { - // // top level libraries - // // // lib.a => lib.c, lib.b - // // // lib.b => lib.d - // // // lib.c => lib.e, lib.b (true) - // // // lib.d => lib.e (true) - // // // lib.e => - // // TODO optimize duplicate resolve (e.g. cache) + // top level libraries + + + // haus (a) => dach (c), Wände (b) + // Wände (b) => grundplatte (d) + // grundplatte (d) => grundstück (e) + // dach (c) => wände (b), grundstück(e) + // grundstück (e) => + + // lib.house => lib.roof, lib.walls + // lib.walls => lib.baseplate + // lib.roof => lib.land, lib.walls (true) + // lib.baseplate => lib.land (true) + // lib.land => + + // lib.house => lib.roof, lib.walls, lib.baseplate, lib.land (true) + // lib.walls => lib.baseplate, lib.land (true) + // lib.roof => lib.walls, lib.land + // lib.baseplate => lib.land (true) + // lib.land => // - // // lib.a - // // :processing: lib.c - // // :processing: lib.e - // // setting lib.e ==> - // // :processing: lib.b - // // :processing: lib.d - // // :processing: lib.e - // // setting lib.e ==> - // // setting lib.d ==> lib.e - // // setting lib.b ==> lib.d - // // setting lib.c ==> lib.e, lib.b + const workspace = resourceFactory.createAdapter({ virBasePath: "/", project: { @@ -566,44 +538,6 @@ test("integration: Library without i18n bundle with manifest max", async (t) => await workspace.write(resourceFactory.createResource({ path: "/resources/test/lib/manifest.json", - string: ` - { - "sap.app": { - "embeds": ["subcomp"] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.a": { - "minVersion": "1.84.0" - }, - "lib.b": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: workspace._project - })); - - // lib.a => lib.c, lib.b - // lib.b => lib.d - // lib.c => lib.e, lib.b (true) - // lib.d => lib.e (true) - // lib.e => - - // lib.b => lib.d, lib.e (true) - // lib.c => - // lib.a => lib.c, lib.b, lib.d, lib.e (true) - // lib.d => lib.e (true) - // lib.e => - - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/subcomp/manifest.json", string: ` { "sap.app": { @@ -613,10 +547,10 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "dependencies": { "minUI5Version": "1.84", "libs": { - "libY": { + "lib.house": { "minVersion": "1.84.0" }, - "libX": { + "lib.walls": { "minVersion": "1.84.0", "lazy": true } @@ -641,8 +575,8 @@ test("integration: Library without i18n bundle with manifest max", async (t) => virBasePath: "/", project: { metadata: { - name: "lib.a", - namespace: "lib/a" + name: "lib.house", + namespace: "lib/house" }, version: "2.0.0", dependencies: [ @@ -656,37 +590,37 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } }); - // lib.a + // lib.house await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/.library", + path: "/resources/lib/house/.library", string: ` - lib.a + lib.house SAP SE 2.0.0 - Library A + Library House `, - project: createProjectMetadata(["lib", "a"]) + project: createProjectMetadata(["lib", "house"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/manifest.json", + path: "/resources/lib/house/manifest.json", string: ` { "sap.app": { - "embeds": ["sub"] + "embeds": ["garden"] }, "sap.ui5": { "dependencies": { "minUI5Version": "1.84", "libs": { - "lib.c": { + "lib.roof": { "minVersion": "1.84.0" }, - "lib.b": { + "lib.walls": { "minVersion": "1.84.0" } } @@ -694,22 +628,22 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "a"]) + project: createProjectMetadata(["lib", "house"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/sub/manifest.json", + path: "/resources/lib/house/garden/manifest.json", string: ` { "sap.app": { "embeds": [], - "id": "lib.a.sub" + "id": "lib.house.garden" }, "sap.ui5": { "dependencies": { "minUI5Version": "1.84", "libs": { - "lib.c": { + "lib.baseplate": { "minVersion": "1.84.0" } } @@ -717,27 +651,27 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "a", "sub"]) + project: createProjectMetadata(["lib", "house", "garden"]) })); - // lib.c + // lib.roof await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/c/.library", + path: "/resources/lib/roof/.library", string: ` - lib.c + lib.roof SAP SE 2.0.0 - Library C + Library Roof `, - project: createProjectMetadata(["lib", "c"]) + project: createProjectMetadata(["lib", "roof"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/c/manifest.json", + path: "/resources/lib/roof/manifest.json", string: ` { "sap.app": { @@ -747,10 +681,10 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "dependencies": { "minUI5Version": "1.84", "libs": { - "lib.e": { + "lib.land": { "minVersion": "1.84.0" }, - "lib.b": { + "lib.walls": { "minVersion": "1.84.0", "lazy": true } @@ -759,16 +693,16 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "c"]) + project: createProjectMetadata(["lib", "roof"]) })); - // lib.b + // lib.walls await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/b/.library", + path: "/resources/lib/walls/.library", string: ` - lib.b + lib.walls SAP SE 2.0.0 @@ -776,10 +710,10 @@ test("integration: Library without i18n bundle with manifest max", async (t) => Library B `, - project: createProjectMetadata(["lib", "b"]) + project: createProjectMetadata(["lib", "walls"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/b/manifest.json", + path: "/resources/lib/walls/manifest.json", string: ` { "sap.app": { @@ -789,7 +723,7 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "dependencies": { "minUI5Version": "1.84", "libs": { - "lib.d": { + "lib.baseplate": { "minVersion": "1.84.0" } } @@ -797,27 +731,27 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "b"]) + project: createProjectMetadata(["lib", "walls"]) })); - // lib.d + // lib.baseplate await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/d/.library", + path: "/resources/lib/baseplate/.library", string: ` - lib.d + lib.baseplate SAP SE 2.0.0 - Library D + Library Baseplate `, - project: createProjectMetadata(["lib", "d"]) + project: createProjectMetadata(["lib", "baseplate"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/d/manifest.json", + path: "/resources/lib/baseplate/manifest.json", string: ` { "sap.app": { @@ -827,7 +761,7 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "dependencies": { "minUI5Version": "1.84", "libs": { - "lib.e": { + "lib.land": { "minVersion": "1.84.0", "lazy": true } @@ -836,27 +770,27 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "d"]) + project: createProjectMetadata(["lib", "baseplate"]) })); - // lib.e + // lib.land await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/e/.library", + path: "/resources/lib/land/.library", string: ` - lib.e + lib.land SAP SE 2.0.0 - Library E + Library Land `, - project: createProjectMetadata(["lib", "e"]) + project: createProjectMetadata(["lib", "land"]) })); await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/e/manifest.json", + path: "/resources/lib/land/manifest.json", string: ` { "sap.app": { @@ -871,7 +805,7 @@ test("integration: Library without i18n bundle with manifest max", async (t) => } } `, - project: createProjectMetadata(["lib", "e"]) + project: createProjectMetadata(["lib", "land"]) })); const oOptions = { @@ -890,82 +824,82 @@ test("integration: Library without i18n bundle with manifest max", async (t) => }; await assertCreatedVersionInfo(t, { "components": { - "lib.a.sub": { - "library": "lib.a", + "lib.house.garden": { + "library": "lib.house", "manifestHints": { "dependencies": { "libs": { - "lib.b": { + "lib.baseplate": {}, + "lib.land": { "lazy": true - }, - "lib.d": {}, - "lib.c": {}, - "lib.e": {} + } } } } } }, - "libraries": [{ - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.d": {}, - "lib.c": {}, - "lib.e": {} + "libraries": [ + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.land": { + lazy: true + } + } } - } + }, + "name": "lib.baseplate", + "scmRevision": "", }, - "name": "lib.a", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": { - lazy: true + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.walls": {}, + "lib.baseplate": {}, + "lib.roof": {}, + "lib.land": {} } } - } + }, + "name": "lib.house", + "scmRevision": "", }, - "name": "lib.b", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": {}, - "lib.d": {}, - "lib.b": { - lazy: true + { + "name": "lib.land", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.land": {}, + "lib.baseplate": {}, + "lib.walls": { + lazy: true + } } } - } + }, + "name": "lib.roof", + "scmRevision": "", }, - "name": "lib.c", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": { - lazy: true + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.baseplate": {}, + "lib.land": { + lazy: true + } } } - } - }, - "name": "lib.d", - "scmRevision": "", - }, - { - "name": "lib.e", - "scmRevision": "", - }], + }, + "name": "lib.walls", + "scmRevision": "", + } + ], "name": "myname", "scmRevision": "", "version": "1.33.7", From b89fff21e6666e9f682e234642a9b3b231628740 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 2 Dec 2020 14:54:41 +0100 Subject: [PATCH 11/41] [INTERNAL][WIP] VersionInfo: with manifest infos adjusted jsdoc of libraryInfos --- lib/processors/versionInfoGenerator.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index f51bb10c2..c550fe232 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -234,7 +234,14 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa * @param {string} parameters.options.rootProjectName Name of the root project * @param {string} parameters.options.rootProjectVersion Version of the root project * @param {LibraryInfo[]} parameters.options.libraryInfos Array of objects representing libraries, - * e.g. {name: "library.xy", version: "1.0.0", manifests: module:@ui5/fs.Resource[]} + * e.g. + * { + * name: "library.xy", + * version: "1.0.0", + * mainManifest: module:@ui5/fs.Resource, + * manifestResources: module:@ui5/fs.Resource[] + * } + * * @returns {Promise} Promise resolving with an array containing the versioninfo resource */ From e2b8c84652e345513b217ee43a6e3fb5a493dc0c Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Thu, 3 Dec 2020 10:53:05 +0100 Subject: [PATCH 12/41] [INTERNAL][WIP] VersionInfo: with manifest infos added TODOs --- lib/processors/versionInfoGenerator.js | 66 +++++++++++++++++++------- lib/tasks/generateVersionInfo.js | 2 +- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index c550fe232..f90a739dd 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -1,4 +1,4 @@ -const log = require("@ui5/logger").getLogger("builder:processors:versionInfogenerator"); +const log = require("@ui5/logger").getLogger("builder:processors:versionInfoGenerator"); const resourceFactory = require("@ui5/fs").resourceFactory; const path = require("path"); @@ -119,7 +119,7 @@ const resolveTransitiveDependencies = (libraryInfosMap) => { const keys = [...libraryInfosMap.keys()]; keys.sort(); const resolvedCache = new Map(); - keys.forEach((libName) => { + keys.forEach((libName) => { // e.g. sap.ui.documentation resolve(libName, libraryInfosMap, resolvedCache); }); }; @@ -146,31 +146,63 @@ const merge = (existingEntry, newLibs) => { * * @param {string} libName * @param {Map} libraryInfosMap - * @param {Map} resolvedCache + * @param {Map} alreadyProcessed * @returns {DependencyInfos} resolved dependencies */ -const resolve = (libName, libraryInfosMap, resolvedCache) => { - // check cache first - if ( resolvedCache.has(libName)) { - return resolvedCache.get(libName); +const resolve = (libName, libraryInfosMap, alreadyProcessed, isLazy) => { + // check already processed first + if ( alreadyProcessed.has(libName)) { + return alreadyProcessed.get(libName); } const manifestHint = libraryInfosMap.get(libName); - let resolved = manifestHint; + let mergedDependencies = manifestHint; + // cache + alreadyProcessed.set(libName, mergedDependencies); if (!manifestHint) { - log.error(`no manifest information in dependencies for ${libName}`); - resolvedCache.set(libName, {}); + log.error(`no manifest information in dependencies for ${libName}`); // TODO check + alreadyProcessed.set(libName, {}); return {}; } const keys = Object.keys(manifestHint); keys.forEach((childLibName) => { - const childResolved = resolve(childLibName, libraryInfosMap, resolvedCache); + const childResolved = resolve(childLibName, libraryInfosMap, alreadyProcessed, isLazy); // set a copy of the resolved libraries to avoid modifying it while iterating (recursively) - resolved = merge(resolved, clone(childResolved)); + mergedDependencies = merge(mergedDependencies, clone(childResolved), isLazy); + // TODO add childResolved to resolved + // TODO check cacles + + + // TODO lib a (lazy) --> all its dependencies must be lazy + // input + // a -> b (lazy) -> c + // output + // a -> b (lazy), c (lazy) + + + // a -> c, b (lazy) + // b -> c (lazy) + + + // a -> c, b (lazy) + + + // a -> c (lazy), b (lazy) + // b -> c + + // kette gewinnt lazy --> alle dependencies von einer lazy dep sind auch lazy + // merge gewinnt eager + + + // TODO put this into a classes to better structure the code + // TODO instead of using a "global" map, have a Dependency as a class with a name + // and the functionality to resolve its dependencies + // ManifestHints -> resolve }); - libraryInfosMap.set(libName, resolved); - resolvedCache.set(libName, resolved); - return resolved; + libraryInfosMap.set(libName, mergedDependencies); + + + return mergedDependencies; }; /** @@ -202,12 +234,14 @@ const addManifestHints = (result, libs) => { const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { const manifestInfo = await processManifest(libraryInfo.mainManifest); + // gather shallow library information dependencyInfoMap.set(libraryInfo.name, manifestInfo.libs); - const embeds = manifestInfo.embeds; + const embeds = manifestInfo.embeds; // sdk // filter const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.mainManifest.getPath(), embed); }); + // sap.ui.documentation.sdk const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { return embeddedPaths.includes(manifestResource.getPath()); }); diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 97bf42e15..a29b70fdd 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -27,7 +27,7 @@ module.exports = async ({workspace, dependencies, options: {rootProject, pattern return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; }); return { - mainManifest, + mainManifest, // TODO rename libraryManifest manifestResources, name: dotLibResource._project.metadata.name, version: dotLibResource._project.version From 058479fa152fa1545aad8ca6c4ac63dcc0993124 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Wed, 23 Dec 2020 16:00:28 +0100 Subject: [PATCH 13/41] [FEATURE] manifestCreator: i18n section v22 refactoring of dependency resolution logic --- lib/processors/versionInfoGenerator.js | 174 +++++++++++++++---------- 1 file changed, 105 insertions(+), 69 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index f90a739dd..e36fbb06d 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -118,92 +118,107 @@ const getManifestPath = (filePath, subPath) => { const resolveTransitiveDependencies = (libraryInfosMap) => { const keys = [...libraryInfosMap.keys()]; keys.sort(); - const resolvedCache = new Map(); keys.forEach((libName) => { // e.g. sap.ui.documentation - resolve(libName, libraryInfosMap, resolvedCache); + const libraryInfo = libraryInfosMap.get(libName); + libraryInfo.resolve(libraryInfosMap); }); }; -const clone = (obj) => { - return JSON.parse(JSON.stringify(obj)); -}; +class DependencyInfoObject { + /** + * + * @param {string} name + * @param {boolean} lazy + */ + constructor(name, lazy) { + this.name = name; + this.lazy = lazy; + } +} -const merge = (existingEntry, newLibs) => { - if (existingEntry) { - Object.keys(existingEntry).forEach((libName) => { - if (!existingEntry[libName].lazy && newLibs[libName] && newLibs[libName].lazy) { - delete newLibs[libName].lazy; - } - if (!newLibs[libName]) { - newLibs[libName] = existingEntry[libName]; - } - }); +class DependencyInfo { + /** + * + * @param {DependencyInfoObject[]} libs + */ + constructor(libs) { + this.libs = libs; + + /** + * + * @type {string[]} + */ + this.resolved = []; + /** + * + * @type {DependencyInfoObject[]} + */ + this.libsResolved = []; + this.wasResolved = false; } - return newLibs; -}; -/** - * - * @param {string} libName - * @param {Map} libraryInfosMap - * @param {Map} alreadyProcessed - * @returns {DependencyInfos} resolved dependencies - */ -const resolve = (libName, libraryInfosMap, alreadyProcessed, isLazy) => { - // check already processed first - if ( alreadyProcessed.has(libName)) { - return alreadyProcessed.get(libName); + isResolved(libName) { + return this.libsResolved.some((libResolved) => { + return libResolved.name === libName; + }); } - const manifestHint = libraryInfosMap.get(libName); - let mergedDependencies = manifestHint; - // cache - alreadyProcessed.set(libName, mergedDependencies); - if (!manifestHint) { - log.error(`no manifest information in dependencies for ${libName}`); // TODO check - alreadyProcessed.set(libName, {}); - return {}; + + addResolvedLibDependency(libName, lazy) { + if (!this.isResolved(libName) || !lazy) { + this.libsResolved.push(new DependencyInfoObject(libName, lazy)); + } } - const keys = Object.keys(manifestHint); - keys.forEach((childLibName) => { - const childResolved = resolve(childLibName, libraryInfosMap, alreadyProcessed, isLazy); - // set a copy of the resolved libraries to avoid modifying it while iterating (recursively) - mergedDependencies = merge(mergedDependencies, clone(childResolved), isLazy); - // TODO add childResolved to resolved - // TODO check cacles + /** + * + * @param {Map} dependencyInfoMap + */ + resolve(dependencyInfoMap) { + if (!this.wasResolved) { + this.libs.forEach((depInfoObject) => { + this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); + const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); + dependencyInfo.resolve(dependencyInfoMap); + + dependencyInfo.libsResolved.forEach((resolvedLib) => { + this.addResolvedLibDependency(resolvedLib.name, resolvedLib.lazy); + }); + }); + this.wasResolved = true; + } + } +} - // TODO lib a (lazy) --> all its dependencies must be lazy - // input - // a -> b (lazy) -> c - // output - // a -> b (lazy), c (lazy) +// TODO add childResolved to resolved +// TODO check cacles - // a -> c, b (lazy) - // b -> c (lazy) +// TODO lib a (lazy) --> all its dependencies must be lazy +// input +// a -> b (lazy) -> c +// output +// a -> b (lazy), c (lazy) - // a -> c, b (lazy) +// a -> c, b (lazy) +// b -> c (lazy) - // a -> c (lazy), b (lazy) - // b -> c +// a -> c, b (lazy) - // kette gewinnt lazy --> alle dependencies von einer lazy dep sind auch lazy - // merge gewinnt eager +// a -> c (lazy), b (lazy) +// b -> c - // TODO put this into a classes to better structure the code - // TODO instead of using a "global" map, have a Dependency as a class with a name - // and the functionality to resolve its dependencies - // ManifestHints -> resolve - }); +// kette gewinnt lazy --> alle dependencies von einer lazy dep sind auch lazy +// merge gewinnt eager - libraryInfosMap.set(libName, mergedDependencies); +// TODO put this into a classes to better structure the code +// TODO instead of using a "global" map, have a Dependency as a class with a name +// and the functionality to resolve its dependencies +// ManifestHints -> resolve - return mergedDependencies; -}; /** * Sorts the keys of a given object @@ -221,21 +236,41 @@ const sortObjectKeys = (obj) => { return sortedObject; }; +/** + * + * @param {object} result + * @param {DependencyInfo} libs + */ const addManifestHints = (result, libs) => { - if (Object.keys(libs).length) { - const sortedLibs = sortObjectKeys(libs); + if (libs.libs.length) { + // const sortedLibs = sortObjectKeys(libs.libs); + const libsObject = {}; + libs.libs.forEach((sortedLib) => { + libsObject[sortedLib.name] = {}; + if (sortedLib.lazy) { + libsObject[sortedLib.name].lazy = true; + } + }); result.manifestHints = { dependencies: { - libs: sortedLibs + libs: libsObject } }; } }; +const convertToDependencyInfoObjects = (libs) => { + return Object.keys(libs).map((name) => { + const lazy = libs[name].lazy === true; + return new DependencyInfoObject(name, lazy); + }); +}; + const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { const manifestInfo = await processManifest(libraryInfo.mainManifest); // gather shallow library information - dependencyInfoMap.set(libraryInfo.name, manifestInfo.libs); + const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); + dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects)); const embeds = manifestInfo.embeds; // sdk // filter const embeddedPaths = embeds.map((embed) => { @@ -249,7 +284,8 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa // get all embedded manifests const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => { const result = await processManifest(relevantManifest); - dependencyInfoMap.set(result.id, result.libs); + const dependencyInfoObjects = convertToDependencyInfoObjects(result.libs); + dependencyInfoMap.set(result.id, new DependencyInfo(dependencyInfoObjects)); embeddedInfoMap.set(result.id, { library: libraryInfo.name }); @@ -298,7 +334,7 @@ module.exports = async function({options}) { * } * } * - * @type {Map} + * @type {Map} */ const dependencyInfoMap = new Map(); const embeddedInfoMap = new Map(); From 765bc978ac92e4122ba588486bd04ee5c48a882d Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 4 Jan 2021 13:56:04 +0100 Subject: [PATCH 14/41] [FEATURE] manifestCreator: i18n section v22 fix recursive logic such that lazy property correctly gets interpreted: * dependencies of lazy dependency are also lazy * having the same dependency lazy and non-lazy results in a non-lazy dependency --- lib/processors/versionInfoGenerator.js | 97 +-- test/lib/tasks/generateVersionInfo.js | 1054 ++++++++++++------------ 2 files changed, 576 insertions(+), 575 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index e36fbb06d..9a813d68f 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -127,7 +127,7 @@ const resolveTransitiveDependencies = (libraryInfosMap) => { class DependencyInfoObject { /** * - * @param {string} name + * @param {string} name name of the dependency, e.g. sap.ui.documentation * @param {boolean} lazy */ constructor(name, lazy) { @@ -140,9 +140,11 @@ class DependencyInfo { /** * * @param {DependencyInfoObject[]} libs + * @param {string} name */ - constructor(libs) { + constructor(libs, name) { this.libs = libs; + this.name = name; /** * @@ -158,67 +160,60 @@ class DependencyInfo { } isResolved(libName) { - return this.libsResolved.some((libResolved) => { + return this.libsResolved.find((libResolved) => { return libResolved.name === libName; }); } + /** + * + * @param {string} libName + * @param {boolean} lazy + * @returns {DependencyInfoObject} + */ addResolvedLibDependency(libName, lazy) { - if (!this.isResolved(libName) || !lazy) { - this.libsResolved.push(new DependencyInfoObject(libName, lazy)); + if (log.isLevelEnabled("verbose")) { + log.verbose(`${this.name} add: ${libName}${lazy?" (lazy)":""}`); + } + let alreadyResolved = this.isResolved(libName); + if (!alreadyResolved) { + alreadyResolved = new DependencyInfoObject(libName, lazy); + this.libsResolved.push(alreadyResolved); + } else { + if (!alreadyResolved.lazy || !lazy) { + delete alreadyResolved.lazy; + } } + return alreadyResolved; } /** * * @param {Map} dependencyInfoMap + * @param {boolean} [lazy] */ - resolve(dependencyInfoMap) { - if (!this.wasResolved) { + resolve(dependencyInfoMap, lazy) { + if (!this.wasResolved || lazy) { + log.verbose(`resolving ${this.name}`); this.libs.forEach((depInfoObject) => { - this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); + const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); - dependencyInfo.resolve(dependencyInfoMap); + dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy); dependencyInfo.libsResolved.forEach((resolvedLib) => { - this.addResolvedLibDependency(resolvedLib.name, resolvedLib.lazy); + this.addResolvedLibDependency(resolvedLib.name, resolvedLib.lazy || dependencyInfoObjectAdded.lazy); }); }); this.wasResolved = true; + if (log.isLevelEnabled("verbose")) { + log.verbose(`resolved ${this.name}: ${this.libsResolved.map((lib) => { + return `${this.name}: ${lib.name}${lib.lazy ? " (lazy)" : ""}`; + }).join(", ")}`); + } } } } -// TODO add childResolved to resolved -// TODO check cacles - - -// TODO lib a (lazy) --> all its dependencies must be lazy -// input -// a -> b (lazy) -> c -// output -// a -> b (lazy), c (lazy) - - -// a -> c, b (lazy) -// b -> c (lazy) - - -// a -> c, b (lazy) - - -// a -> c (lazy), b (lazy) -// b -> c - -// kette gewinnt lazy --> alle dependencies von einer lazy dep sind auch lazy -// merge gewinnt eager - - -// TODO put this into a classes to better structure the code -// TODO instead of using a "global" map, have a Dependency as a class with a name -// and the functionality to resolve its dependencies -// ManifestHints -> resolve - /** * Sorts the keys of a given object @@ -239,13 +234,13 @@ const sortObjectKeys = (obj) => { /** * * @param {object} result - * @param {DependencyInfo} libs + * @param {DependencyInfo} dependencyInfo */ -const addManifestHints = (result, libs) => { - if (libs.libs.length) { +const addManifestHints = (result, dependencyInfo) => { + if (dependencyInfo && dependencyInfo.libs.length) { // const sortedLibs = sortObjectKeys(libs.libs); const libsObject = {}; - libs.libs.forEach((sortedLib) => { + dependencyInfo.libsResolved.forEach((sortedLib) => { libsObject[sortedLib.name] = {}; if (sortedLib.lazy) { libsObject[sortedLib.name].lazy = true; @@ -267,10 +262,14 @@ const convertToDependencyInfoObjects = (libs) => { }; const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { + if (!libraryInfo.mainManifest) { + log.error(`library manifest not found for ${libraryInfo.name}`); + return; + } const manifestInfo = await processManifest(libraryInfo.mainManifest); // gather shallow library information const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); - dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects)); + dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); const embeds = manifestInfo.embeds; // sdk // filter const embeddedPaths = embeds.map((embed) => { @@ -285,7 +284,7 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => { const result = await processManifest(relevantManifest); const dependencyInfoObjects = convertToDependencyInfoObjects(result.libs); - dependencyInfoMap.set(result.id, new DependencyInfo(dependencyInfoObjects)); + dependencyInfoMap.set(result.id, new DependencyInfo(dependencyInfoObjects, result.id)); embeddedInfoMap.set(result.id, { library: libraryInfo.name }); @@ -382,9 +381,11 @@ module.exports = async function({options}) { buildTimestamp: buildTimestamp, scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here - libraries, - components: sortedComponents + libraries }; + if (Object.keys(sortedComponents).length) { + versionJson.components = sortedComponents; + } return [resourceFactory.createResource({ path: "/resources/sap-ui-version.json", diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index a3d212713..287adccf8 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -25,16 +25,18 @@ function createWorkspace() { }); } -function createDependencies() { - return resourceFactory.createAdapter({ - fsBasePath: path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo", "main", "src"), - virBasePath: "/resources", +function createDependencies(oOptions = { + virBasePath: "/resources", + fsBasePath: path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo", "main", "src") +}) { + oOptions = Object.assign(oOptions, { project: { metadata: { name: "test.lib3" }, version: "3.0.0"} }); + return resourceFactory.createAdapter(oOptions); } async function createOptions(t, options) { @@ -166,248 +168,122 @@ test("integration: Library without i18n bundle file failure", async (t) => { }); }); +const createProjectMetadata = (nameArray) => { + return { + metadata: { + name: nameArray.join("."), + namespace: nameArray.join("/") + } + }; +}; -test("integration: Library without i18n bundle with manifest", async (t) => { - const workspace = resourceFactory.createAdapter({ - virBasePath: "/", - project: { - metadata: { - name: "test.lib", - namespace: "test/lib" - }, - version: "2.0.0", - dependencies: [ - { - metadata: { - name: "sap.ui.core" - }, - version: "1.0.0" - } - ] +const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds) => { + const content = { + "sap.app": { + "id": names.join("."), + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }; + + const libs = {}; + deps.forEach((dep) => { + libs[dep.name] = { + "minVersion": "1.84.0" + }; + if (dep.lazy) { + libs[dep.name].lazy = true; } }); + content["sap.ui5"]["dependencies"]["libs"] = libs; + if (embeds) { + content["sap.app"]["embeds"] = embeds; + } + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/manifest.json`, + string: JSON.stringify(content, null, 2), + project: createProjectMetadata(names) + })); +}; - // TODO .library should not be required - // only use .library if manifest.json is not there - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/.library", +async function createDotLibrary(dependencies, resourceFactory, names) { + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/.library`, string: ` - - test.lib + ${names.join(".")} SAP SE 2.0.0 - Test Lib - + Library ${names.slice(1).join(".").toUpperCase()} `, - project: workspace._project + project: createProjectMetadata(names) })); +} - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/manifest.json", - string: ` - { - "sap.app": { - "embeds": ["subcomp"] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.a": { - "minVersion": "1.84.0" - }, - "lib.b": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: workspace._project - })); +/** + * + * @param {object} dependencies + * @param {object} resourceFactory + * @param {string[]} names + * @param {object[]} deps + * @param {string[]} [embeds] + */ +const createResources = async (dependencies, resourceFactory, names, deps, embeds) => { + await createDotLibrary(dependencies, resourceFactory, names); + await createManifestResource(dependencies, resourceFactory, names, deps, embeds); +}; - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => +test("integration: Library without i18n bundle with manifest minimal", async (t) => { + const workspace = createWorkspace(); + + // only use .library + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + + // input // lib.a => lib.c, lib.b // lib.b => lib.c (true) - // lib.c => - - // dependencies - const createProjectMetadata = (nameArray) => { - return { - metadata: { - name: nameArray.join("."), - namespace: nameArray.join("/") - } - }; - }; - const dependencies = resourceFactory.createAdapter({ - virBasePath: "/", - project: { - metadata: { - name: "lib.a", - namespace: "lib/a" - }, - version: "2.0.0", - dependencies: [ - { - metadata: { - name: "sap.ui.core" - }, - version: "1.0.0" - } - ] - } - }); + // lib.c => lib.d + // lib.d => - // lib.a - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/.library", - string: ` - - - lib.a - SAP SE - - 2.0.0 + // lib.a.sub.fold => lib.c - Library A - - `, - project: createProjectMetadata(["lib", "a"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/manifest.json", - string: ` - { - "sap.app": { - "embeds": ["sub/fold"] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.c": { - "minVersion": "1.84.0" - }, - "lib.b": { - "minVersion": "1.84.0" - } - } - } - } - } - `, - project: createProjectMetadata(["lib", "a"]) - })); + // outcome + // lib.a => lib.c, lib.b, lib.d + // lib.b => lib.c (true), lib.d (true) + // lib.c => lib.d + // lib.d => - // sub - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/a/sub/fold/manifest.json", - string: ` - { - "sap.app": { - "id": "lib.a.sub.fold", - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.c": { - "minVersion": "1.84.0" - } - } - } - } - } - `, - project: createProjectMetadata(["lib", "a", "sub", "fold"]) - })); + // dependencies - // lib.c - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/c/.library", - string: ` - - - lib.c - SAP SE - - 2.0.0 + const dependencies = createDependencies({virBasePath: "/"}); - Library C - - `, - project: createProjectMetadata(["lib", "c"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/c/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.b": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: createProjectMetadata(["lib", "c"]) - })); + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); // lib.b - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/b/.library", - string: ` - - - lib.b - SAP SE - - 2.0.0 + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); - Library B - - `, - project: createProjectMetadata(["lib", "b"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/b/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - } - } - } - } - `, - project: createProjectMetadata(["lib", "b"]) - })); + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); const oOptions = { options: { @@ -430,10 +306,11 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "manifestHints": { "dependencies": { "libs": { - "lib.b": { + "lib.c": {}, + "lib.d": {}, + "lib.e": { "lazy": true - }, - "lib.c": {} + } } } } @@ -444,7 +321,11 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "dependencies": { "libs": { "lib.b": {}, - "lib.c": {} + "lib.c": {}, + "lib.d": {}, + "lib.e": { + "lazy": true + } } } }, @@ -452,6 +333,21 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "scmRevision": "", }, { + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, "name": "lib.b", "scmRevision": "", }, @@ -459,7 +355,8 @@ test("integration: Library without i18n bundle with manifest", async (t) => { "manifestHints": { "dependencies": { "libs": { - "lib.b": { + "lib.d": {}, + "lib.e": { "lazy": true } } @@ -467,6 +364,23 @@ test("integration: Library without i18n bundle with manifest", async (t) => { }, "name": "lib.c", "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + "lazy": true + } + } + } + }, + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", }], "name": "myname", "scmRevision": "", @@ -474,339 +388,423 @@ test("integration: Library without i18n bundle with manifest", async (t) => { }, oOptions); }); -test("integration: Library without i18n bundle with manifest max", async (t) => { - // top level libraries +test("integration: Library without i18n bundle with manifest minimal1", async (t) => { + const workspace = createWorkspace(); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - // haus (a) => dach (c), Wände (b) - // Wände (b) => grundplatte (d) - // grundplatte (d) => grundstück (e) - // dach (c) => wände (b), grundstück(e) - // grundstück (e) => + // input + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => lib.d + // lib.d => - // lib.house => lib.roof, lib.walls - // lib.walls => lib.baseplate - // lib.roof => lib.land, lib.walls (true) - // lib.baseplate => lib.land (true) - // lib.land => + // lib.a.sub.fold => lib.c - // lib.house => lib.roof, lib.walls, lib.baseplate, lib.land (true) - // lib.walls => lib.baseplate, lib.land (true) - // lib.roof => lib.walls, lib.land - // lib.baseplate => lib.land (true) - // lib.land => - // + // outcome + // lib.a => lib.c, lib.b, lib.d + // lib.b => lib.c (true), lib.d (true) + // lib.c => lib.d + // lib.d => - const workspace = resourceFactory.createAdapter({ - virBasePath: "/", - project: { - metadata: { - name: "test.lib", - namespace: "test/lib" - }, - version: "2.0.0", - dependencies: [ - { - metadata: { - name: "sap.ui.core" - }, - version: "1.0.0" - } - ] - } - }); + // dependencies - // TODO .library should not be required - // only use .library if manifest.json is not there - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/.library", - string: ` - - + const dependencies = createDependencies({virBasePath: "/"}); - test.lib - SAP SE - - 2.0.0 + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b"}, {name: "lib.c"}, {name: "lib.e"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, + ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); - Test Lib + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); - - `, - project: workspace._project - })); + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); - await workspace.write(resourceFactory.createResource({ - path: "/resources/test/lib/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.house": { - "minVersion": "1.84.0" - }, - "lib.walls": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: workspace._project - })); + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); - // dependencies - const createProjectMetadata = (nameArray) => { - return { - metadata: { - name: nameArray.join("."), - namespace: nameArray.join("/") + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" } - }; + }, + workspace, + dependencies }; - const dependencies = resourceFactory.createAdapter({ - virBasePath: "/", - project: { - metadata: { - name: "lib.house", - namespace: "lib/house" - }, - version: "2.0.0", - dependencies: [ - { - metadata: { - name: "sap.ui.core" - }, - version: "1.0.0" - } - ] - } - }); - - // lib.house - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/house/.library", - string: ` - - - lib.house - SAP SE - - 2.0.0 - - Library House - - `, - project: createProjectMetadata(["lib", "house"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/house/manifest.json", - string: ` - { - "sap.app": { - "embeds": ["garden"] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.roof": { - "minVersion": "1.84.0" - }, - "lib.walls": { - "minVersion": "1.84.0" - } - } - } + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": { + "lazy": true + } + } + } } } - `, - project: createProjectMetadata(["lib", "house"]) - })); - - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/house/garden/manifest.json", - string: ` - { - "sap.app": { - "embeds": [], - "id": "lib.house.garden" - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.baseplate": { - "minVersion": "1.84.0" - } - } - } + }, + "libraries": [{ + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } } - } - `, - project: createProjectMetadata(["lib", "house", "garden"]) - })); + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, + "name": "lib.b", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": { + "lazy": true + } + } + } + }, + "name": "lib.c", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + "lazy": true + } + } + } + }, + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); +}); - // lib.roof - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/roof/.library", - string: ` - - - lib.roof - SAP SE - - 2.0.0 +test("integration: Library without i18n bundle with manifest minimal2", async (t) => { + const workspace = createWorkspace(); - Library Roof - - `, - project: createProjectMetadata(["lib", "roof"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/roof/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.land": { - "minVersion": "1.84.0" - }, - "lib.walls": { - "minVersion": "1.84.0", - "lazy": true - } - } - } - } - } - `, - project: createProjectMetadata(["lib", "roof"]) - })); + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - // lib.walls - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/walls/.library", - string: ` - - - lib.walls - SAP SE - - 2.0.0 + // input + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => lib.d + // lib.d => - Library B - - `, - project: createProjectMetadata(["lib", "walls"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/walls/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] - }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.baseplate": { - "minVersion": "1.84.0" - } - } - } - } - } - `, - project: createProjectMetadata(["lib", "walls"]) - })); + // lib.a.sub.fold => lib.c - // lib.baseplate - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/baseplate/.library", - string: ` - - - lib.baseplate - SAP SE - - 2.0.0 + // outcome + // lib.a => lib.c, lib.b, lib.d + // lib.b => lib.c (true), lib.d (true) + // lib.c => lib.d + // lib.d => - Library Baseplate - - `, - project: createProjectMetadata(["lib", "baseplate"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/baseplate/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] + // dependencies + + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); + + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - "lib.land": { - "minVersion": "1.84.0", - "lazy": true - } - } - } + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } } } - `, - project: createProjectMetadata(["lib", "baseplate"]) - })); + }, + "libraries": [{ + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {}, + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, + "name": "lib.b", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + "name": "lib.c", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + "name": "lib.d", + "scmRevision": "", + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); +}); - // lib.land - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/land/.library", - string: ` - - - lib.land - SAP SE - - 2.0.0 - Library Land - - `, - project: createProjectMetadata(["lib", "land"]) - })); - await dependencies.write(resourceFactory.createResource({ - path: "/resources/lib/land/manifest.json", - string: ` - { - "sap.app": { - "embeds": [] +test("integration: Library without i18n bundle with manifest simple", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => + + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => + + // dependencies + + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.b", lazy: true}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" }, - "sap.ui5": { - "dependencies": { - "minUI5Version": "1.84", - "libs": { - } - } + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": {} + } + } } } - `, - project: createProjectMetadata(["lib", "land"]) - })); + }, + "libraries": [{ + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "name": "lib.b", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + } + } + } + }, + "name": "lib.c", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); +}); + +test("integration: Library without i18n bundle with manifest house sample", async (t) => { + // top level libraries + + // lib.house => lib.roof, lib.walls + // lib.walls => lib.baseplate + // lib.roof => lib.land, lib.walls (true) + // lib.baseplate => lib.land (true) + // lib.land => + + // lib.house => lib.roof, lib.walls, lib.baseplate, lib.land (true) + // lib.walls => lib.baseplate, lib.land (true) + // lib.roof => lib.walls (true), lib.land, lib.baseplate (true) + // lib.baseplate => lib.land (true) + // lib.land => + // + + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.house + const embeds = ["garden"]; + await createResources(dependencies, resourceFactory, ["lib", "house"], + [{name: "lib.roof"}, {name: "lib.walls"}], embeds); + // sub garden + await createManifestResource(dependencies, resourceFactory, ["lib", "house", "garden"], [{name: "lib.baseplate"}]); + + // lib.roof + await createResources(dependencies, resourceFactory, ["lib", "roof"], + [{name: "lib.land"}, {name: "lib.walls", lazy: true}]); + + // lib.walls + await createResources(dependencies, resourceFactory, ["lib", "walls"], [{name: "lib.baseplate"}]); + + // lib.baseplate + await createResources(dependencies, resourceFactory, ["lib", "baseplate"], [{name: "lib.land", lazy: true}]); + + // lib.land + await createResources(dependencies, resourceFactory, ["lib", "land"], []); const oOptions = { options: { @@ -875,7 +873,9 @@ test("integration: Library without i18n bundle with manifest max", async (t) => "dependencies": { "libs": { "lib.land": {}, - "lib.baseplate": {}, + "lib.baseplate": { + lazy: true + }, "lib.walls": { lazy: true } From f41a6212f9df56b5bef010284a74e566cb86fc68 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 4 Jan 2021 15:18:04 +0100 Subject: [PATCH 15/41] [FEATURE] manifestCreator: i18n section v22 Rename types to avoid confusion --- lib/processors/versionInfoGenerator.js | 62 ++++++++++++++++---------- lib/tasks/generateVersionInfo.js | 4 +- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 9a813d68f..c3705b0a5 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -20,7 +20,7 @@ function getTimestamp() { /** * * @param {module:@ui5/fs.Resource} manifestResource - * @returns {Promise} + * @returns {Promise} */ const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); @@ -58,7 +58,7 @@ const processManifest = async (manifestResource) => { /** * Library Info * - * @typedef {object} DependencyInfos + * @typedef {object} ManifestLibs * * * @example * { @@ -72,8 +72,8 @@ const processManifest = async (manifestResource) => { /** * Manifest Hint * - * @typedef {object} ManifestInfos - * @property {DependencyInfos} libs The library object + * @typedef {object} ManifestInfo + * @property {ManifestLibs} libs The library object * @property {string[]} embeds embedded components, e.g. "sub/fold" (only relative path) * @property {string} id the app id, e.g. "lib.a" * @@ -94,12 +94,14 @@ const processManifest = async (manifestResource) => { /** - * Library Info object + * Library Info + * + * contains information about the name the version of the library and its manifest, as well as the nested manifests. * * @typedef {object} LibraryInfo * @property {string} name The library name * @property {string} version The library version - * @property {module:@ui5/fs.Resource} mainManifest main manifest resources + * @property {module:@ui5/fs.Resource} libraryManifest main manifest resources * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources */ @@ -112,15 +114,16 @@ const getManifestPath = (filePath, subPath) => { }; /** + * Resolves the dependencies recursively * - * @param {Map} libraryInfosMap + * @param {Map} dependencyInfoMap */ -const resolveTransitiveDependencies = (libraryInfosMap) => { - const keys = [...libraryInfosMap.keys()]; +const resolveTransitiveDependencies = (dependencyInfoMap) => { + const keys = [...dependencyInfoMap.keys()]; keys.sort(); keys.forEach((libName) => { // e.g. sap.ui.documentation - const libraryInfo = libraryInfosMap.get(libName); - libraryInfo.resolve(libraryInfosMap); + const libraryInfo = dependencyInfoMap.get(libName); + libraryInfo.resolve(dependencyInfoMap); }); }; @@ -128,7 +131,7 @@ class DependencyInfoObject { /** * * @param {string} name name of the dependency, e.g. sap.ui.documentation - * @param {boolean} lazy + * @param {boolean} lazy lazy dependency */ constructor(name, lazy) { this.name = name; @@ -146,11 +149,6 @@ class DependencyInfo { this.libs = libs; this.name = name; - /** - * - * @type {string[]} - */ - this.resolved = []; /** * * @type {DependencyInfoObject[]} @@ -190,7 +188,8 @@ class DependencyInfo { /** * * @param {Map} dependencyInfoMap - * @param {boolean} [lazy] + * @param {boolean} [lazy] whether or not the dependency is lazy dependency which means + * all its dependencies should be treated as lazy */ resolve(dependencyInfoMap, lazy) { if (!this.wasResolved || lazy) { @@ -238,7 +237,6 @@ const sortObjectKeys = (obj) => { */ const addManifestHints = (result, dependencyInfo) => { if (dependencyInfo && dependencyInfo.libs.length) { - // const sortedLibs = sortObjectKeys(libs.libs); const libsObject = {}; dependencyInfo.libsResolved.forEach((sortedLib) => { libsObject[sortedLib.name] = {}; @@ -261,19 +259,27 @@ const convertToDependencyInfoObjects = (libs) => { }); }; +/** + * Processes the library info and fills the maps dependencyInfoMap and embeddedInfoMap. + * + * @param {LibraryInfo} libraryInfo + * @param {Map} dependencyInfoMap + * @param {Map} embeddedInfoMap + * @returns {Promise} + */ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { - if (!libraryInfo.mainManifest) { + if (!libraryInfo.libraryManifest) { log.error(`library manifest not found for ${libraryInfo.name}`); return; } - const manifestInfo = await processManifest(libraryInfo.mainManifest); + const manifestInfo = await processManifest(libraryInfo.libraryManifest); // gather shallow library information const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); const embeds = manifestInfo.embeds; // sdk // filter const embeddedPaths = embeds.map((embed) => { - return getManifestPath(libraryInfo.mainManifest.getPath(), embed); + return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); }); // sap.ui.documentation.sdk const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { @@ -307,7 +313,7 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa * { * name: "library.xy", * version: "1.0.0", - * mainManifest: module:@ui5/fs.Resource, + * libraryManifest: module:@ui5/fs.Resource, * manifestResources: module:@ui5/fs.Resource[] * } * @@ -336,6 +342,15 @@ module.exports = async function({options}) { * @type {Map} */ const dependencyInfoMap = new Map(); + /** + * @example + * { + * "sap.ui.integration.sdk": { + * "library": "sap.ui.integration" + * } + * + * @type {Map} + */ const embeddedInfoMap = new Map(); // gather all manifestHints @@ -362,7 +377,6 @@ module.exports = async function({options}) { return result; }); - // sort keys embeddedInfoMap.forEach((embeddedInfo, libName) => { components[libName] = embeddedInfo; const libs = dependencyInfoMap.get(libName); diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index a29b70fdd..73f7b8536 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -23,11 +23,11 @@ module.exports = async ({workspace, dependencies, options: {rootProject, pattern // pass all required resources to the processor // the processor will then filter return dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`).then((manifestResources) => { - const mainManifest = manifestResources.find((manifestResource) => { + const libraryManifest = manifestResources.find((manifestResource) => { return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; }); return { - mainManifest, // TODO rename libraryManifest + libraryManifest, manifestResources, name: dotLibResource._project.metadata.name, version: dotLibResource._project.version From fa54efc34d7177d94d2fb7513196d6f6acf95c17 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 4 Jan 2021 15:26:35 +0100 Subject: [PATCH 16/41] [FEATURE] manifestCreator: i18n section v22 Rename variable to componentName because it is not the library name --- lib/processors/versionInfoGenerator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index c3705b0a5..8651ae530 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -377,10 +377,10 @@ module.exports = async function({options}) { return result; }); - embeddedInfoMap.forEach((embeddedInfo, libName) => { - components[libName] = embeddedInfo; - const libs = dependencyInfoMap.get(libName); - addManifestHints(components[libName], libs); + embeddedInfoMap.forEach((embeddedInfo, componentName) => { + components[componentName] = embeddedInfo; + const libs = dependencyInfoMap.get(componentName); + addManifestHints(components[componentName], libs); }); const sortedComponents = sortObjectKeys(components); From 8dad6ab29768127f44b005850581f2cafe997a7e Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 5 Jan 2021 16:19:42 +0100 Subject: [PATCH 17/41] [FEATURE] manifestCreator: i18n section v22 Support embeddedBy property of subcomponents. hasOwnPreload is set if child and parent component point to each other via embeds and embeddedBy properties. * the parent component uses embeds to point to its children * the child component uses embeddedBy to point to its parent both paths are relative - AppIndex expects all nested components to be listed in the "sap.app"/"embeds" array of a library's manifest.json, no matter whether the corresponding component is bundled with the library or in an individual Component-preload bundle - UI5 runtime needs to know whether a component is bundled as part of the library-preload of the containing library. Up to now, it assumed this to be the case when the component is listed in sap-ui-version.json - as these two requirements don't match, a new flag (hasOwnPreload) is introduced per component in the sap-ui-version.json which indicates whether the component has an own bundle or not The value of the flag is based on the 'embeddedBy' property as declared in the component's manifest. It is not based on build configuration. This is in line with the AppIndex's implementation. --- lib/processors/versionInfoGenerator.js | 78 +++++++++++++++++-- test/lib/tasks/generateVersionInfo.js | 102 ++++++++++++++++++++++++- 2 files changed, 171 insertions(+), 9 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 8651ae530..a9978d06a 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -1,6 +1,6 @@ const log = require("@ui5/logger").getLogger("builder:processors:versionInfoGenerator"); const resourceFactory = require("@ui5/fs").resourceFactory; -const path = require("path"); +const posixPath = require("path").posix; function pad(v) { return String(v).padStart(2, "0"); @@ -28,6 +28,7 @@ const processManifest = async (manifestResource) => { const result = { embeds: [], libs: {}, + embeddedBy: undefined, id: undefined }; @@ -50,11 +51,50 @@ const processManifest = async (manifestResource) => { if (manifestEmbeds) { result.embeds = manifestEmbeds; } + + const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; + if (manifestEmbeddedBy) { + result.embeddedBy = manifestEmbeddedBy; + } result.id = manifestObject["sap.app"]["id"]; } return result; }; +const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { + if (typeof embeddedBy === "undefined") { + log.verbose(" component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); + return false; + } + if (typeof embeddedBy !== "string") { + log.error( + " component '%s': property 'sap.app/embeddedBy' is of type '%s' (expected 'string'), " + + "it won't be listed as 'embedded'", componentPath, typeof embeddedBy + ); + return false; + } + if ( !embeddedBy.length ) { + log.error( + " component '%s': property 'sap.app/embeddedBy' has an empty string value (which is invalid), " + + "it won't be listed as 'embedded'", componentPath + ); + return false; + } + let resolvedEmbeddedBy = posixPath.resolve(componentPath, embeddedBy); + if ( resolvedEmbeddedBy && !resolvedEmbeddedBy.endsWith("/") ) { + resolvedEmbeddedBy = resolvedEmbeddedBy + "/"; + } + if ( libraryPathPrefix === resolvedEmbeddedBy ) { + log.verbose(" component's 'sap.app/embeddedBy' property points to library, list it as 'embedded'"); + return true; + } else { + log.verbose( + " component's 'sap.app/embeddedBy' points to '%s', don't list it as 'embedded'", resolvedEmbeddedBy + ); + return false; + } +}; + /** * Library Info * @@ -75,6 +115,7 @@ const processManifest = async (manifestResource) => { * @typedef {object} ManifestInfo * @property {ManifestLibs} libs The library object * @property {string[]} embeds embedded components, e.g. "sub/fold" (only relative path) + * @property {string} embeddedBy relative path to the component which embeds this component * @property {string} id the app id, e.g. "lib.a" * * @@ -108,7 +149,7 @@ const processManifest = async (manifestResource) => { const getManifestPath = (filePath, subPath) => { if (filePath.endsWith("manifest.json")) { const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; - return path.posix.resolve(folderPathOfManifest + "/manifest.json"); + return posixPath.resolve(folderPathOfManifest + "/manifest.json"); } return filePath; }; @@ -265,9 +306,10 @@ const convertToDependencyInfoObjects = (libs) => { * @param {LibraryInfo} libraryInfo * @param {Map} dependencyInfoMap * @param {Map} embeddedInfoMap + * @param {Map>} bundledComponentsMap * @returns {Promise} */ -const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap) => { +const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap, bundledComponentsMap) => { if (!libraryInfo.libraryManifest) { log.error(`library manifest not found for ${libraryInfo.name}`); return; @@ -276,6 +318,10 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa // gather shallow library information const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); + + const bundledComponents = new Set(); + bundledComponentsMap.set(libraryInfo.name, bundledComponents); + const embeds = manifestInfo.embeds; // sdk // filter const embeddedPaths = embeds.map((embed) => { @@ -288,12 +334,19 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa // get all embedded manifests const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => { + const fullManifestPath = posixPath.dirname(relevantManifest.getPath()); + const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath()); + const result = await processManifest(relevantManifest); const dependencyInfoObjects = convertToDependencyInfoObjects(result.libs); - dependencyInfoMap.set(result.id, new DependencyInfo(dependencyInfoObjects, result.id)); - embeddedInfoMap.set(result.id, { + const componentName = result.id; + dependencyInfoMap.set(componentName, new DependencyInfo(dependencyInfoObjects, componentName)); + embeddedInfoMap.set(componentName, { library: libraryInfo.name }); + if (isBundledWithLibrary(result.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { + bundledComponents.add(componentName); + } }); await Promise.all(embeddedManifestPromises); @@ -352,10 +405,15 @@ module.exports = async function({options}) { * @type {Map} */ const embeddedInfoMap = new Map(); + /** + * + * @type {Map>} + */ + const bundledComponentsMap = new Map(); // gather all manifestHints const librariesPromises = options.libraryInfos.map((libraryInfo) => { - return processLibraryInfo(libraryInfo, dependencyInfoMap, embeddedInfoMap); + return processLibraryInfo(libraryInfo, dependencyInfoMap, embeddedInfoMap, bundledComponentsMap); }); await Promise.all(librariesPromises); @@ -379,8 +437,12 @@ module.exports = async function({options}) { embeddedInfoMap.forEach((embeddedInfo, componentName) => { components[componentName] = embeddedInfo; - const libs = dependencyInfoMap.get(componentName); - addManifestHints(components[componentName], libs); + const dependencyInfo = dependencyInfoMap.get(componentName); + addManifestHints(components[componentName], dependencyInfo); + const bundledComponents = bundledComponentsMap.get(embeddedInfo.library); + if (bundledComponents && bundledComponents.has(componentName)) { + components[componentName]["hasOwnPreload"] = true; + } }); const sortedComponents = sortObjectKeys(components); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 287adccf8..b19a5d328 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -177,7 +177,7 @@ const createProjectMetadata = (nameArray) => { }; }; -const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds) => { +const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds, embeddedBy) => { const content = { "sap.app": { "id": names.join("."), @@ -204,6 +204,9 @@ const createManifestResource = async (dependencies, resourceFactory, names, deps if (embeds) { content["sap.app"]["embeds"] = embeds; } + if (embeddedBy) { + content["sap.app"]["embeddedBy"] = embeddedBy; + } await dependencies.write(resourceFactory.createResource({ path: `/resources/${names.join("/")}/manifest.json`, string: JSON.stringify(content, null, 2), @@ -762,6 +765,103 @@ test("integration: Library without i18n bundle with manifest simple", async (t) }, oOptions); }); +test("integration: Library without i18n bundle with manifest simple embeddedBy", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => + + // lib.a => lib.c, lib.b + // lib.b => lib.c (true) + // lib.c => + + // dependencies + + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}], + undefined, "../../"); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.b", lazy: true}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": {} + } + } + } + } + }, + "libraries": [{ + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + "name": "lib.a", + "scmRevision": "", + }, + { + "name": "lib.b", + "scmRevision": "", + }, + { + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + } + } + } + }, + "name": "lib.c", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); +}); + test("integration: Library without i18n bundle with manifest house sample", async (t) => { // top level libraries From 97485924d10a4e1f7910d40ca688a18621ed6fd0 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Thu, 7 Jan 2021 17:13:37 +0100 Subject: [PATCH 18/41] give the code a better structure by using explicit classes --- lib/processors/versionInfoGenerator.js | 282 +++++++++++++++++-------- 1 file changed, 192 insertions(+), 90 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index a9978d06a..1c293d58f 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -16,8 +16,51 @@ function getTimestamp() { return year + month + day + hours + minutes; } +class ManifestInfo { + constructor() { + this.libs = {}; + this.embeds = []; + } + + /** + * The library object + * + * @param {ManifestLibs} libs + */ + setLibs(libs) { + this.libs = libs; + } + + /** + * embedded components, e.g. "sub/fold" (only relative path) + * + * @param {string[]} embeds + */ + setEmbeds(embeds) { + this.embeds = embeds; + } + + /** + * relative path to the component which embeds this component + * + * @param {string} embeddedBy + */ + setEmbeddedBy(embeddedBy) { + this.embeddedBy = embeddedBy; + } + + /** + * the app id, e.g. "lib.a" + * + * @param {string} id + */ + setId(id) { + this.id = id; + } +} /** + * Processes manifest resource and extracts information * * @param {module:@ui5/fs.Resource} manifestResource * @returns {Promise} @@ -25,23 +68,20 @@ function getTimestamp() { const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); const manifestObject = JSON.parse(manifestContent); - const result = { - embeds: [], - libs: {}, - embeddedBy: undefined, - id: undefined - }; + const result = new ManifestInfo(); // sap.ui5/dependencies is used for the "manifestHints/libs" if (manifestObject["sap.ui5"]) { const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; if (manifestDependencies) { + const libs = {}; Object.keys(manifestDependencies.libs).forEach((libKey) => { - result.libs[libKey] = {}; + libs[libKey] = {}; if (manifestDependencies.libs[libKey].lazy) { - result.libs[libKey].lazy = true; + libs[libKey].lazy = true; } }); + result.setLibs(libs); } } @@ -49,14 +89,14 @@ const processManifest = async (manifestResource) => { if (manifestObject["sap.app"]) { const manifestEmbeds = manifestObject["sap.app"]["embeds"]; if (manifestEmbeds) { - result.embeds = manifestEmbeds; + result.setEmbeds(manifestEmbeds); } const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; if (manifestEmbeddedBy) { - result.embeddedBy = manifestEmbeddedBy; + result.setEmbeddedBy(manifestEmbeddedBy); } - result.id = manifestObject["sap.app"]["id"]; + result.setId(manifestObject["sap.app"]["id"]); } return result; }; @@ -96,7 +136,7 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { }; /** - * Library Info + * Manifest libraries * * @typedef {object} ManifestLibs * @@ -109,30 +149,6 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { * } */ -/** - * Manifest Hint - * - * @typedef {object} ManifestInfo - * @property {ManifestLibs} libs The library object - * @property {string[]} embeds embedded components, e.g. "sub/fold" (only relative path) - * @property {string} embeddedBy relative path to the component which embeds this component - * @property {string} id the app id, e.g. "lib.a" - * - * - * @example - * { - * libs: { - * sap.chart: { - * lazy: true - * }, - * sap.f: { }, - * }, - * id: "lib.a", - * embeds: ["sub"] - * } - * - */ - /** * Library Info @@ -146,6 +162,12 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources */ +/** + * + * @param {string} filePath + * @param {string} subPath + * @returns {string} manifest path + */ const getManifestPath = (filePath, subPath) => { if (filePath.endsWith("manifest.json")) { const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; @@ -273,10 +295,10 @@ const sortObjectKeys = (obj) => { /** * - * @param {object} result * @param {DependencyInfo} dependencyInfo + * @returns {object} manifestHints */ -const addManifestHints = (result, dependencyInfo) => { +const getManifestHints = (dependencyInfo) => { if (dependencyInfo && dependencyInfo.libs.length) { const libsObject = {}; dependencyInfo.libsResolved.forEach((sortedLib) => { @@ -285,7 +307,7 @@ const addManifestHints = (result, dependencyInfo) => { libsObject[sortedLib.name].lazy = true; } }); - result.manifestHints = { + return { dependencies: { libs: libsObject } @@ -300,27 +322,107 @@ const convertToDependencyInfoObjects = (libs) => { }); }; +class ArtifactInfo { + /** + * + * @param {string} componentName + */ + constructor(componentName) { + this.componentName = componentName; + } + + /** + * + * @param {DependencyInfo} dependencyInfo + */ + setDependencyInfo(dependencyInfo) { + this.dependencyInfo = dependencyInfo; + } + + /** + * + * @param {Set} bundledComponents + */ + setBundledComponents(bundledComponents) { + this.bundledComponents = bundledComponents; + } + + /** + * + * @param {ArtifactInfo[]} artifactInfos + */ + setEmbeds(artifactInfos) { + this.artifactInfos = artifactInfos; + this.artifactInfos.forEach((artifactInfo) => { + artifactInfo._setParent(this); + }); + } + + /** + * + * @returns {ArtifactInfo[]} + */ + getEmbeds() { + if (this.artifactInfos) { + return this.artifactInfos; + } + return []; + } + + /** + * + * @returns {Set} bundledComponents + */ + getParentBundledComponents() { + if (this.parent && this.parent.bundledComponents) { + return this.parent.bundledComponents; + } + return new Set(); + } + + /** + * + * @returns {string} + */ + getParentComponentName() { + if (this.parent) { + return this.parent.componentName; + } + } + + /** + * + * @param {ArtifactInfo} parent + * @private + */ + _setParent(parent) { + this.parent = parent; + } +} + /** * Processes the library info and fills the maps dependencyInfoMap and embeddedInfoMap. * * @param {LibraryInfo} libraryInfo - * @param {Map} dependencyInfoMap - * @param {Map} embeddedInfoMap - * @param {Map>} bundledComponentsMap - * @returns {Promise} + * @returns {Promise} */ -const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMap, bundledComponentsMap) => { +const processLibraryInfo = async (libraryInfo) => { + const artifactInfos = []; if (!libraryInfo.libraryManifest) { log.error(`library manifest not found for ${libraryInfo.name}`); - return; + return artifactInfos; } + const manifestInfo = await processManifest(libraryInfo.libraryManifest); // gather shallow library information const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); - dependencyInfoMap.set(libraryInfo.name, new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); + + const mainArtifactInfo = new ArtifactInfo(libraryInfo.name); + mainArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); + artifactInfos.push(mainArtifactInfo); const bundledComponents = new Set(); - bundledComponentsMap.set(libraryInfo.name, bundledComponents); + mainArtifactInfo.setBundledComponents(bundledComponents); const embeds = manifestInfo.embeds; // sdk // filter @@ -340,16 +442,19 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa const result = await processManifest(relevantManifest); const dependencyInfoObjects = convertToDependencyInfoObjects(result.libs); const componentName = result.id; - dependencyInfoMap.set(componentName, new DependencyInfo(dependencyInfoObjects, componentName)); - embeddedInfoMap.set(componentName, { - library: libraryInfo.name - }); + const componentArtifactInfo = new ArtifactInfo(componentName); + componentArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, componentName)); + if (isBundledWithLibrary(result.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { bundledComponents.add(componentName); } + return componentArtifactInfo; }); - await Promise.all(embeddedManifestPromises); + const embeddedArtifactInfos = await Promise.all(embeddedManifestPromises); + mainArtifactInfo.setEmbeds(embeddedArtifactInfos); + + return artifactInfos; }; /** @@ -370,7 +475,7 @@ const processLibraryInfo = async (libraryInfo, dependencyInfoMap, embeddedInfoMa * manifestResources: module:@ui5/fs.Resource[] * } * - * @returns {Promise} Promise resolving with an array containing the versioninfo resource + * @returns {Promise} Promise resolving with an array containing the versionInfo resource */ module.exports = async function({options}) { @@ -382,41 +487,26 @@ module.exports = async function({options}) { const components = {}; /** - * @example - * { - * "sap.ui.integration": { - * "sap.chart": { - * "lazy": true - * }, - * "sap.f": { }, - * } - * } + * componentName to dependency info * * @type {Map} */ const dependencyInfoMap = new Map(); - /** - * @example - * { - * "sap.ui.integration.sdk": { - * "library": "sap.ui.integration" - * } - * - * @type {Map} - */ - const embeddedInfoMap = new Map(); - /** - * - * @type {Map>} - */ - const bundledComponentsMap = new Map(); + // gather all manifestHints const librariesPromises = options.libraryInfos.map((libraryInfo) => { - return processLibraryInfo(libraryInfo, dependencyInfoMap, embeddedInfoMap, bundledComponentsMap); + return processLibraryInfo(libraryInfo); }); - await Promise.all(librariesPromises); + const artifactInfosPromises = await Promise.all(librariesPromises); + const artifactInfos = [].concat(...artifactInfosPromises); + artifactInfos.forEach((artifactInfo) => { + dependencyInfoMap.set(artifactInfo.componentName, artifactInfo.dependencyInfo); + artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { + dependencyInfoMap.set(embeddedArtifactInfo.componentName, embeddedArtifactInfo.dependencyInfo); + }); + }); // resolve nested dependencies (transitive) resolveTransitiveDependencies(dependencyInfoMap); @@ -430,20 +520,32 @@ module.exports = async function({options}) { scmRevision: ""// TODO: insert current library scm revision here }; - const libs = dependencyInfoMap.get(libraryInfo.name); - addManifestHints(result, libs); + const dependencyInfo = dependencyInfoMap.get(libraryInfo.name); + const manifestHints = getManifestHints(dependencyInfo); + if (manifestHints) { + result.manifestHints = manifestHints; + } return result; }); - - embeddedInfoMap.forEach((embeddedInfo, componentName) => { - components[componentName] = embeddedInfo; - const dependencyInfo = dependencyInfoMap.get(componentName); - addManifestHints(components[componentName], dependencyInfo); - const bundledComponents = bundledComponentsMap.get(embeddedInfo.library); - if (bundledComponents && bundledComponents.has(componentName)) { - components[componentName]["hasOwnPreload"] = true; - } + artifactInfos.forEach((artifactInfo) => { + artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { + const componentObject = { + library: embeddedArtifactInfo.getParentComponentName() + }; + const componentName = embeddedArtifactInfo.componentName; + const dependencyInfo = dependencyInfoMap.get(componentName); + const manifestHints = getManifestHints(dependencyInfo); + if (manifestHints) { + componentObject.manifestHints = manifestHints; + } + const bundledComponents = embeddedArtifactInfo.getParentBundledComponents(); + if (bundledComponents.has(componentName)) { + componentObject.hasOwnPreload = true; + } + components[componentName] = componentObject; + }); }); + const sortedComponents = sortObjectKeys(components); // sort libraries alphabetically From e885412676a290e18149e6309ed300b277a80531 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 11 Jan 2021 13:43:19 +0100 Subject: [PATCH 19/41] add tests for embeddedBy to increase test coverage --- lib/processors/versionInfoGenerator.js | 9 +- test/lib/tasks/generateVersionInfo.js | 301 +++++++++++++++++++------ 2 files changed, 239 insertions(+), 71 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 1c293d58f..27a2579fd 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -88,14 +88,11 @@ const processManifest = async (manifestResource) => { // sap.app/embeds is used for "components" if (manifestObject["sap.app"]) { const manifestEmbeds = manifestObject["sap.app"]["embeds"]; - if (manifestEmbeds) { - result.setEmbeds(manifestEmbeds); - } + result.setEmbeds(manifestEmbeds); const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; - if (manifestEmbeddedBy) { - result.setEmbeddedBy(manifestEmbeddedBy); - } + result.setEmbeddedBy(manifestEmbeddedBy); + result.setId(manifestObject["sap.app"]["id"]); } return result; diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index b19a5d328..188fc6ac0 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -1,9 +1,12 @@ const test = require("ava"); -const generateVersionInfo = require("../../../lib/tasks/generateVersionInfo"); +let generateVersionInfo = require("../../../lib/tasks/generateVersionInfo"); const path = require("path"); const ui5Fs = require("@ui5/fs"); const resourceFactory = ui5Fs.resourceFactory; +const sinon = require("sinon"); +const mock = require("mock-require"); +const logger = require("@ui5/logger"); function createWorkspace() { return resourceFactory.createAdapter({ @@ -93,7 +96,24 @@ async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); } -test("integration: Library without i18n bundle file", async (t) => { +test.beforeEach((t) => { + t.context.verboseLogStub = sinon.stub(); + t.context.errorLogStub = sinon.stub(); + sinon.stub(logger, "getLogger").returns({ + verbose: t.context.verboseLogStub, + error: t.context.errorLogStub, + isLevelEnabled: () => true + }); + mock.reRequire("../../../lib/processors/versionInfoGenerator"); + generateVersionInfo = mock.reRequire("../../../lib/tasks/generateVersionInfo"); +}); + +test.afterEach.always((t) => { + mock.stopAll(); + sinon.restore(); +}); + +test.serial("integration: Library without i18n bundle file", async (t) => { t.context.workspace = createWorkspace(); t.context.dependencies = createDependencies(); @@ -128,7 +148,7 @@ test("integration: Library without i18n bundle file", async (t) => { }); }); -test("integration: Library without i18n bundle file failure", async (t) => { +test.serial("integration: Library without i18n bundle file failure", async (t) => { t.context.workspace = createWorkspace(); t.context.dependencies = createDependencies(); @@ -201,10 +221,10 @@ const createManifestResource = async (dependencies, resourceFactory, names, deps } }); content["sap.ui5"]["dependencies"]["libs"] = libs; - if (embeds) { + if (embeds !== undefined) { content["sap.app"]["embeds"] = embeds; } - if (embeddedBy) { + if (embeddedBy !== undefined) { content["sap.app"]["embeddedBy"] = embeddedBy; } await dependencies.write(resourceFactory.createResource({ @@ -246,7 +266,7 @@ const createResources = async (dependencies, resourceFactory, names, deps, embed }; -test("integration: Library without i18n bundle with manifest minimal", async (t) => { +test.serial("integration: Library without i18n bundle with manifest minimal", async (t) => { const workspace = createWorkspace(); // only use .library @@ -391,7 +411,7 @@ test("integration: Library without i18n bundle with manifest minimal", async (t) }, oOptions); }); -test("integration: Library without i18n bundle with manifest minimal1", async (t) => { +test.serial("integration: Library without i18n bundle with manifest minimal1", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -534,7 +554,7 @@ test("integration: Library without i18n bundle with manifest minimal1", async (t }, oOptions); }); -test("integration: Library without i18n bundle with manifest minimal2", async (t) => { +test.serial("integration: Library without i18n bundle with manifest minimal2", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -670,7 +690,7 @@ test("integration: Library without i18n bundle with manifest minimal2", async (t }); -test("integration: Library without i18n bundle with manifest simple", async (t) => { +test.serial("integration: Library without i18n bundle with manifest simple", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -765,37 +785,21 @@ test("integration: Library without i18n bundle with manifest simple", async (t) }, oOptions); }); -test("integration: Library without i18n bundle with manifest simple embeddedBy", async (t) => { +test.serial("integration: Library without i18n bundle with manifest simple embeddedBy", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => - - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); // lib.a const embeds = ["sub/fold"]; - await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}], + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], undefined, "../../"); - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.b", lazy: true}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], []); - const oOptions = { options: { projectName: "Test Lib", @@ -814,47 +818,12 @@ test("integration: Library without i18n bundle with manifest simple embeddedBy", "components": { "lib.a.sub.fold": { "hasOwnPreload": true, - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": { - "lazy": true - }, - "lib.c": {} - } - } - } + "library": "lib.a" } }, "libraries": [{ - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": {} - } - } - }, "name": "lib.a", "scmRevision": "", - }, - { - "name": "lib.b", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": { - "lazy": true - } - } - } - }, - "name": "lib.c", - "scmRevision": "", }], "name": "myname", "scmRevision": "", @@ -862,7 +831,7 @@ test("integration: Library without i18n bundle with manifest simple embeddedBy", }, oOptions); }); -test("integration: Library without i18n bundle with manifest house sample", async (t) => { +test.serial("integration: Library without i18n bundle with manifest house sample", async (t) => { // top level libraries // lib.house => lib.roof, lib.walls @@ -1005,3 +974,205 @@ test("integration: Library without i18n bundle with manifest house sample", asyn "version": "1.33.7", }, oOptions); }); + +test.serial("integration: Library without i18n bundle with manifest simple embeddedBy undefined", async (t) => { + const {verboseLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, undefined); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a" + } + }, + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(verboseLogStub.callCount, 5); + t.is(verboseLogStub.firstCall.args[0], + " component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); +}); + +test.serial("integration: Library without i18n bundle with manifest simple embeddedBy not a string", async (t) => { + const {errorLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, {}); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a" + } + }, + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(errorLogStub.callCount, 1); + t.is(errorLogStub.firstCall.args[0], + " component '%s': property 'sap.app/embeddedBy' is of type '%s' (expected 'string'), " + + "it won't be listed as 'embedded'"); +}); + +test.serial("integration: Library without i18n bundle with manifest simple embeddedBy empty string", async (t) => { + const {errorLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, ""); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a" + } + }, + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(errorLogStub.callCount, 1); + t.is(errorLogStub.firstCall.args[0], + " component '%s': property 'sap.app/embeddedBy' has an empty string value (which is invalid), " + + "it won't be listed as 'embedded'"); +}); + +test.serial("integration: Library without i18n bundle with manifest simple embeddedBy path not correct", async (t) => { + const {verboseLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, "../"); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "components": { + "lib.a.sub.fold": { + "library": "lib.a" + } + }, + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(verboseLogStub.callCount, 5); + t.is(verboseLogStub.firstCall.args[0], + " component's 'sap.app/embeddedBy' points to '%s', don't list it as 'embedded'"); +}); From d2f023acbe6cffeba73fbcd9ff3b16c13a1ecbfd Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 11 Jan 2021 13:59:52 +0100 Subject: [PATCH 20/41] Use constructor to set values as well as setters instead of dynamically resolving it using getters --- lib/processors/versionInfoGenerator.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 27a2579fd..7c08f9209 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -326,6 +326,8 @@ class ArtifactInfo { */ constructor(componentName) { this.componentName = componentName; + this.artifactInfos = []; + this.parentBundledComponents = new Set(); } /** @@ -360,10 +362,7 @@ class ArtifactInfo { * @returns {ArtifactInfo[]} */ getEmbeds() { - if (this.artifactInfos) { - return this.artifactInfos; - } - return []; + return this.artifactInfos; } /** @@ -371,10 +370,7 @@ class ArtifactInfo { * @returns {Set} bundledComponents */ getParentBundledComponents() { - if (this.parent && this.parent.bundledComponents) { - return this.parent.bundledComponents; - } - return new Set(); + return this.parentBundledComponents; } /** @@ -394,6 +390,7 @@ class ArtifactInfo { */ _setParent(parent) { this.parent = parent; + this.parentBundledComponents = this.parent.bundledComponents; } } From cd30ac6f81f2244754d4257f02e198c075cda940 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 11 Jan 2021 14:08:10 +0100 Subject: [PATCH 21/41] Improve jsdoc Remove non-reachable code the manifest resources always end with "manifest.json" --- lib/processors/versionInfoGenerator.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 7c08f9209..ecd30b5de 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -160,17 +160,15 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { */ /** + * Retrieves the manifest path * - * @param {string} filePath - * @param {string} subPath - * @returns {string} manifest path + * @param {string} filePath path to the manifest, e.g. lib/a/manifest.json + * @param {string} subPath relative sub path, e.g. sdk + * @returns {string} manifest path, e.g. lib/a/sdk/manifest.json */ const getManifestPath = (filePath, subPath) => { - if (filePath.endsWith("manifest.json")) { - const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; - return posixPath.resolve(folderPathOfManifest + "/manifest.json"); - } - return filePath; + const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; + return posixPath.resolve(folderPathOfManifest + "/manifest.json"); }; /** @@ -418,7 +416,7 @@ const processLibraryInfo = async (libraryInfo) => { const bundledComponents = new Set(); mainArtifactInfo.setBundledComponents(bundledComponents); - const embeds = manifestInfo.embeds; // sdk + const embeds = manifestInfo.embeds; // e.g. ["sdk"] // filter const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); From 31eb6b333bf9e853c5afa849c76bcb1a18448746 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 11 Jan 2021 17:52:27 +0100 Subject: [PATCH 22/41] Sort libs object in manifestHints, to have a consistent result created For dependencies which cannot be resolved log an error --- lib/processors/versionInfoGenerator.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index ecd30b5de..95d785ce9 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -255,11 +255,16 @@ class DependencyInfo { this.libs.forEach((depInfoObject) => { const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); - dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy); - - dependencyInfo.libsResolved.forEach((resolvedLib) => { - this.addResolvedLibDependency(resolvedLib.name, resolvedLib.lazy || dependencyInfoObjectAdded.lazy); - }); + if (dependencyInfo) { + dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy); + + dependencyInfo.libsResolved.forEach((resolvedLib) => { + this.addResolvedLibDependency(resolvedLib.name, + resolvedLib.lazy || dependencyInfoObjectAdded.lazy); + }); + } else { + log.error(`Cannot find dependency '${depInfoObject.name}' for '${this.name}'`); + } }); this.wasResolved = true; if (log.isLevelEnabled("verbose")) { @@ -296,15 +301,15 @@ const sortObjectKeys = (obj) => { const getManifestHints = (dependencyInfo) => { if (dependencyInfo && dependencyInfo.libs.length) { const libsObject = {}; - dependencyInfo.libsResolved.forEach((sortedLib) => { - libsObject[sortedLib.name] = {}; - if (sortedLib.lazy) { - libsObject[sortedLib.name].lazy = true; + dependencyInfo.libsResolved.forEach((dependencyInfoObject) => { + libsObject[dependencyInfoObject.name] = {}; + if (dependencyInfoObject.lazy) { + libsObject[dependencyInfoObject.name].lazy = true; } }); return { dependencies: { - libs: libsObject + libs: sortObjectKeys(libsObject) } }; } From 7cccfc06f7117eb4f1e642b846fca22ea4b4db63 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 11 Jan 2021 18:04:23 +0100 Subject: [PATCH 23/41] Add test for invalid dependency --- test/lib/tasks/generateVersionInfo.js | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 188fc6ac0..29643b9e9 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -1176,3 +1176,52 @@ test.serial("integration: Library without i18n bundle with manifest simple embed t.is(verboseLogStub.firstCall.args[0], " component's 'sap.app/embeddedBy' points to '%s', don't list it as 'embedded'"); }); + +test.serial("integration: manifest with invalid dependency", async (t) => { + const {errorLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "non.existing"}]); + + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "non.existing": {}, + }, + }, + } + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(errorLogStub.callCount, 1); + t.is(errorLogStub.firstCall.args[0], + "Cannot find dependency 'non.existing' for 'lib.a'"); +}); From 4685a7103b0f328f83015abefc9669a9b721beb6 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 12 Jan 2021 11:29:47 +0100 Subject: [PATCH 24/41] improve JSDoc and samples add TODO for 3.0 because excluding a standard task for certain cases is incompatible --- lib/builder/builder.js | 2 +- lib/processors/versionInfoGenerator.js | 141 ++++++++++++------------- 2 files changed, 68 insertions(+), 75 deletions(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 27de77b30..9e45cd893 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -75,7 +75,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.generateLibraryPreload = false; } - // TODO exclude generateVersionInfo if not --all is used? + // TODO 3.0: exclude generateVersionInfo if not --all is used if (jsdoc) { // Include JSDoc tasks diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 95d785ce9..ddf3628f4 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -16,6 +16,23 @@ function getTimestamp() { return year + month + day + hours + minutes; } +/** + * Manifest libraries as defined in the manifest.json file + * + * @typedef {object} ManifestLibraries + * + * * @example + * { + * sap.chart: { + * lazy: true + * }, + * sap.f: { } + * } + */ + +/** + * Extracted information from a manifest's sap.app and sap.ui5 sections. + */ class ManifestInfo { constructor() { this.libs = {}; @@ -25,14 +42,14 @@ class ManifestInfo { /** * The library object * - * @param {ManifestLibs} libs + * @param {ManifestLibraries} libs */ setLibs(libs) { this.libs = libs; } /** - * embedded components, e.g. "sub/fold" (only relative path) + * embedded components, e.g. ["sub/fold"] (relative paths) * * @param {string[]} embeds */ @@ -41,7 +58,7 @@ class ManifestInfo { } /** - * relative path to the component which embeds this component + * relative path to the component which embeds this component, e.g. "../" * * @param {string} embeddedBy */ @@ -50,7 +67,7 @@ class ManifestInfo { } /** - * the app id, e.g. "lib.a" + * the app id, e.g. "sap.x" * * @param {string} id */ @@ -60,7 +77,7 @@ class ManifestInfo { } /** - * Processes manifest resource and extracts information + * Processes manifest resource and extracts information. * * @param {module:@ui5/fs.Resource} manifestResource * @returns {Promise} @@ -68,7 +85,7 @@ class ManifestInfo { const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); const manifestObject = JSON.parse(manifestContent); - const result = new ManifestInfo(); + const manifestInfo = new ManifestInfo(); // sap.ui5/dependencies is used for the "manifestHints/libs" if (manifestObject["sap.ui5"]) { @@ -81,23 +98,31 @@ const processManifest = async (manifestResource) => { libs[libKey].lazy = true; } }); - result.setLibs(libs); + manifestInfo.setLibs(libs); } } - // sap.app/embeds is used for "components" + // sap.app/embeds, sap.app/embeddedBy and sap.app/id is used for "components" if (manifestObject["sap.app"]) { const manifestEmbeds = manifestObject["sap.app"]["embeds"]; - result.setEmbeds(manifestEmbeds); + manifestInfo.setEmbeds(manifestEmbeds); const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; - result.setEmbeddedBy(manifestEmbeddedBy); + manifestInfo.setEmbeddedBy(manifestEmbeddedBy); - result.setId(manifestObject["sap.app"]["id"]); + const id = manifestObject["sap.app"]["id"]; + manifestInfo.setId(id); } - return result; + return manifestInfo; }; +/** + * + * @param {string} embeddedBy e.g. "../" + * @param {string} componentPath e.g. "sap/x/sub" + * @param {string} libraryPathPrefix e.g. "sap/x" + * @returns {boolean} whether or not this component is bundled with the library + */ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { if (typeof embeddedBy === "undefined") { log.verbose(" component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); @@ -132,39 +157,12 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { } }; -/** - * Manifest libraries - * - * @typedef {object} ManifestLibs - * - * * @example - * { - * sap.chart: { - * lazy: true - * }, - * sap.f: { } - * } - */ - - -/** - * Library Info - * - * contains information about the name the version of the library and its manifest, as well as the nested manifests. - * - * @typedef {object} LibraryInfo - * @property {string} name The library name - * @property {string} version The library version - * @property {module:@ui5/fs.Resource} libraryManifest main manifest resources - * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources - */ - /** * Retrieves the manifest path * - * @param {string} filePath path to the manifest, e.g. lib/a/manifest.json - * @param {string} subPath relative sub path, e.g. sdk - * @returns {string} manifest path, e.g. lib/a/sdk/manifest.json + * @param {string} filePath path to the manifest, e.g. "sap/x/manifest.json" + * @param {string} subPath relative sub path, e.g. "sdk" + * @returns {string} manifest path, e.g. "sap/x/sdk/manifest.json" */ const getManifestPath = (filePath, subPath) => { const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; @@ -172,15 +170,12 @@ const getManifestPath = (filePath, subPath) => { }; /** - * Resolves the dependencies recursively + * Resolves the transitive dependencies recursively. * * @param {Map} dependencyInfoMap */ const resolveTransitiveDependencies = (dependencyInfoMap) => { - const keys = [...dependencyInfoMap.keys()]; - keys.sort(); - keys.forEach((libName) => { // e.g. sap.ui.documentation - const libraryInfo = dependencyInfoMap.get(libName); + dependencyInfoMap.forEach((libraryInfo) => { libraryInfo.resolve(dependencyInfoMap); }); }; @@ -188,7 +183,7 @@ const resolveTransitiveDependencies = (dependencyInfoMap) => { class DependencyInfoObject { /** * - * @param {string} name name of the dependency, e.g. sap.ui.documentation + * @param {string} name name of the dependency, e.g. "sap.x" * @param {boolean} lazy lazy dependency */ constructor(name, lazy) { @@ -368,24 +363,6 @@ class ArtifactInfo { return this.artifactInfos; } - /** - * - * @returns {Set} bundledComponents - */ - getParentBundledComponents() { - return this.parentBundledComponents; - } - - /** - * - * @returns {string} - */ - getParentComponentName() { - if (this.parent) { - return this.parent.componentName; - } - } - /** * * @param {ArtifactInfo} parent @@ -394,6 +371,7 @@ class ArtifactInfo { _setParent(parent) { this.parent = parent; this.parentBundledComponents = this.parent.bundledComponents; + this.parentComponentName = this.parent.componentName; } } @@ -426,7 +404,7 @@ const processLibraryInfo = async (libraryInfo) => { const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); }); - // sap.ui.documentation.sdk + // sap.x.sdk const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { return embeddedPaths.includes(manifestResource.getPath()); }); @@ -454,6 +432,18 @@ const processLibraryInfo = async (libraryInfo) => { return artifactInfos; }; +/** + * Library Info + * + * contains information about the name the version of the library and its manifest, as well as the nested manifests. + * + * @typedef {object} LibraryInfo + * @property {string} name The library name + * @property {string} version The library version + * @property {module:@ui5/fs.Resource} libraryManifest main manifest resources + * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources + */ + /** * Creates sap-ui-version.json. * @@ -524,10 +514,17 @@ module.exports = async function({options}) { } return result; }); + + // sort libraries alphabetically + libraries.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + + // components artifactInfos.forEach((artifactInfo) => { artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { const componentObject = { - library: embeddedArtifactInfo.getParentComponentName() + library: embeddedArtifactInfo.parentComponentName }; const componentName = embeddedArtifactInfo.componentName; const dependencyInfo = dependencyInfoMap.get(componentName); @@ -535,7 +532,7 @@ module.exports = async function({options}) { if (manifestHints) { componentObject.manifestHints = manifestHints; } - const bundledComponents = embeddedArtifactInfo.getParentBundledComponents(); + const bundledComponents = embeddedArtifactInfo.parentBundledComponents; if (bundledComponents.has(componentName)) { componentObject.hasOwnPreload = true; } @@ -543,13 +540,9 @@ module.exports = async function({options}) { }); }); + // sort components alphabetically const sortedComponents = sortObjectKeys(components); - // sort libraries alphabetically - libraries.sort((a, b) => { - return a.name.localeCompare(b.name); - }); - const versionJson = { name: options.rootProjectName, version: options.rootProjectVersion, // TODO: insert current application version here From bf1980ae12318f265d096ef66ff704235ce9263c Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 12 Jan 2021 14:00:00 +0100 Subject: [PATCH 25/41] improve code structure --- lib/processors/versionInfoGenerator.js | 33 +++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index ddf3628f4..7be6d9a82 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -379,25 +379,23 @@ class ArtifactInfo { * Processes the library info and fills the maps dependencyInfoMap and embeddedInfoMap. * * @param {LibraryInfo} libraryInfo - * @returns {Promise} + * @returns {Promise} */ const processLibraryInfo = async (libraryInfo) => { - const artifactInfos = []; if (!libraryInfo.libraryManifest) { log.error(`library manifest not found for ${libraryInfo.name}`); - return artifactInfos; + return; } const manifestInfo = await processManifest(libraryInfo.libraryManifest); // gather shallow library information const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); - const mainArtifactInfo = new ArtifactInfo(libraryInfo.name); - mainArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); - artifactInfos.push(mainArtifactInfo); + const libraryArtifactInfo = new ArtifactInfo(libraryInfo.name); + libraryArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); const bundledComponents = new Set(); - mainArtifactInfo.setBundledComponents(bundledComponents); + libraryArtifactInfo.setBundledComponents(bundledComponents); const embeds = manifestInfo.embeds; // e.g. ["sdk"] // filter @@ -427,9 +425,9 @@ const processLibraryInfo = async (libraryInfo) => { }); const embeddedArtifactInfos = await Promise.all(embeddedManifestPromises); - mainArtifactInfo.setEmbeds(embeddedArtifactInfos); + libraryArtifactInfo.setEmbeds(embeddedArtifactInfos); - return artifactInfos; + return libraryArtifactInfo; }; /** @@ -472,7 +470,6 @@ module.exports = async function({options}) { const buildTimestamp = getTimestamp(); - const components = {}; /** * componentName to dependency info * @@ -486,8 +483,10 @@ module.exports = async function({options}) { return processLibraryInfo(libraryInfo); }); - const artifactInfosPromises = await Promise.all(librariesPromises); - const artifactInfos = [].concat(...artifactInfosPromises); + let artifactInfos = await Promise.all(librariesPromises); + artifactInfos = artifactInfos.filter(Boolean); + + // fill dependencyInfoMap artifactInfos.forEach((artifactInfo) => { dependencyInfoMap.set(artifactInfo.componentName, artifactInfo.dependencyInfo); artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { @@ -521,6 +520,7 @@ module.exports = async function({options}) { }); // components + let components; artifactInfos.forEach((artifactInfo) => { artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { const componentObject = { @@ -536,12 +536,13 @@ module.exports = async function({options}) { if (bundledComponents.has(componentName)) { componentObject.hasOwnPreload = true; } + components = components || {}; components[componentName] = componentObject; }); }); // sort components alphabetically - const sortedComponents = sortObjectKeys(components); + components = components && sortObjectKeys(components); const versionJson = { name: options.rootProjectName, @@ -549,11 +550,9 @@ module.exports = async function({options}) { buildTimestamp: buildTimestamp, scmRevision: "", // TODO: insert current application scm revision here // gav: "", // TODO: insert current application id + version here - libraries + libraries, + components }; - if (Object.keys(sortedComponents).length) { - versionJson.components = sortedComponents; - } return [resourceFactory.createResource({ path: "/resources/sap-ui-version.json", From 066b040bfa50ec5993dfb84806b20f5e25b5f909 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 12 Jan 2021 14:06:36 +0100 Subject: [PATCH 26/41] rename variable instead of deleting lazy property set it to undefined --- lib/processors/versionInfoGenerator.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 7be6d9a82..21ba3c174 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -232,7 +232,7 @@ class DependencyInfo { this.libsResolved.push(alreadyResolved); } else { if (!alreadyResolved.lazy || !lazy) { - delete alreadyResolved.lazy; + alreadyResolved.lazy = undefined; } } return alreadyResolved; @@ -412,13 +412,13 @@ const processLibraryInfo = async (libraryInfo) => { const fullManifestPath = posixPath.dirname(relevantManifest.getPath()); const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath()); - const result = await processManifest(relevantManifest); - const dependencyInfoObjects = convertToDependencyInfoObjects(result.libs); - const componentName = result.id; + const embeddedManifestInfo = await processManifest(relevantManifest); + const dependencyInfoObjects = convertToDependencyInfoObjects(embeddedManifestInfo.libs); + const componentName = embeddedManifestInfo.id; const componentArtifactInfo = new ArtifactInfo(componentName); componentArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, componentName)); - if (isBundledWithLibrary(result.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { + if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { bundledComponents.add(componentName); } return componentArtifactInfo; From 45d8c6bf6b2c0b262375d8f942b888cc63ac4ebb Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 12 Jan 2021 16:04:37 +0100 Subject: [PATCH 27/41] adjust comments and test message remove duplicate irrelevant tests --- test/lib/tasks/generateVersionInfo.js | 398 +++++--------------------- 1 file changed, 68 insertions(+), 330 deletions(-) diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 29643b9e9..b0da75f01 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -63,12 +63,6 @@ async function createOptions(t, options) { return oOptions; } - -async function assertCreatedVersionInfoAndCreateOptions(t, oExpectedVersionInfo, options) { - const oOptions = await createOptions(t, options); - await assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions); -} - async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { await generateVersionInfo(oOptions); @@ -136,7 +130,8 @@ test.serial("integration: Library without i18n bundle file", async (t) => { project: t.context.workspace._project })); - await assertCreatedVersionInfoAndCreateOptions(t, { + const oOptions = await createOptions(t); + await assertCreatedVersionInfo(t, { "libraries": [{ "name": "test.lib3", "scmRevision": "", @@ -145,7 +140,7 @@ test.serial("integration: Library without i18n bundle file", async (t) => { "name": "myname", "scmRevision": "", "version": "1.33.7", - }); + }, oOptions); }); test.serial("integration: Library without i18n bundle file failure", async (t) => { @@ -188,15 +183,30 @@ test.serial("integration: Library without i18n bundle file failure", async (t) = }); }); -const createProjectMetadata = (nameArray) => { +/** + * + * @param {string[]} names e.g. ["lib", "a"] + * @returns {{metadata: {name, namespace}}} + */ +const createProjectMetadata = (names) => { return { metadata: { - name: nameArray.join("."), - namespace: nameArray.join("/") + name: names.join("."), + namespace: names.join("/") } }; }; +/** + * + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @param {object[]} deps + * @param {string[]} [embeds] + * @param {string} [embeddedBy] + * @returns {Promise} + */ const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds, embeddedBy) => { const content = { "sap.app": { @@ -234,6 +244,12 @@ const createManifestResource = async (dependencies, resourceFactory, names, deps })); }; +/** + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @returns {Promise} + */ async function createDotLibrary(dependencies, resourceFactory, names) { await dependencies.write(resourceFactory.createResource({ path: `/resources/${names.join("/")}/.library`, @@ -254,9 +270,9 @@ async function createDotLibrary(dependencies, resourceFactory, names) { /** * - * @param {object} dependencies - * @param {object} resourceFactory - * @param {string[]} names + * @param {module:@ui5/fs.DuplexCollection} dependencies + * @param {module:@ui5/fs.resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] * @param {object[]} deps * @param {string[]} [embeds] */ @@ -266,29 +282,28 @@ const createResources = async (dependencies, resourceFactory, names, deps, embed }; -test.serial("integration: Library without i18n bundle with manifest minimal", async (t) => { +test.serial("integration: Library with dependencies and subcomponent", async (t) => { const workspace = createWorkspace(); - // only use .library await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - // input // lib.a => lib.c, lib.b // lib.b => lib.c (true) // lib.c => lib.d - // lib.d => - + // lib.d => lib.e + // lib.e => // lib.a.sub.fold => lib.c - // outcome + // expected outcome // lib.a => lib.c, lib.b, lib.d // lib.b => lib.c (true), lib.d (true) // lib.c => lib.d - // lib.d => + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e // dependencies - const dependencies = createDependencies({virBasePath: "/"}); // lib.a @@ -305,6 +320,7 @@ test.serial("integration: Library without i18n bundle with manifest minimal", as // lib.d await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); + // lib.e await createResources(dependencies, resourceFactory, ["lib", "e"], []); @@ -411,24 +427,26 @@ test.serial("integration: Library without i18n bundle with manifest minimal", as }, oOptions); }); -test.serial("integration: Library without i18n bundle with manifest minimal1", async (t) => { +test.serial("integration: Library with dependencies and subcomponent mixed", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); // input - // lib.a => lib.c, lib.b + // lib.a => lib.b, lib.c, lib.e // lib.b => lib.c (true) // lib.c => lib.d - // lib.d => - + // lib.d => lib.e (true) + // lib.e => // lib.a.sub.fold => lib.c // outcome - // lib.a => lib.c, lib.b, lib.d - // lib.b => lib.c (true), lib.d (true) - // lib.c => lib.d - // lib.d => + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) + // lib.c => lib.d, lib.e (true) + // lib.d => lib.e (true) + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e (true) // dependencies @@ -554,24 +572,19 @@ test.serial("integration: Library without i18n bundle with manifest minimal1", a }, oOptions); }); -test.serial("integration: Library without i18n bundle with manifest minimal2", async (t) => { +test.serial("integration: Library with simple dependencies and subcomponent", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - // input - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => lib.d - // lib.d => - // lib.a.sub.fold => lib.c + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => - // outcome - // lib.a => lib.c, lib.b, lib.d - // lib.b => lib.c (true), lib.d (true) - // lib.c => lib.d - // lib.d => + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => // dependencies @@ -581,18 +594,13 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a const embeds = ["sub/fold"]; await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.b"}]); // lib.b await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); - - // lib.d - await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); - // lib.e - await createResources(dependencies, resourceFactory, ["lib", "e"], []); + await createResources(dependencies, resourceFactory, ["lib", "c"], []); const oOptions = { options: { @@ -615,9 +623,10 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a "manifestHints": { "dependencies": { "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": {} + "lib.b": {}, + "lib.c": { + "lazy": true + } } } } @@ -628,9 +637,7 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a "dependencies": { "libs": { "lib.b": {}, - "lib.c": {}, - "lib.d": {}, - "lib.e": {}, + "lib.c": {} } } }, @@ -643,12 +650,6 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a "libs": { "lib.c": { "lazy": true - }, - "lib.d": { - "lazy": true - }, - "lib.e": { - "lazy": true } } } @@ -657,31 +658,8 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a "scmRevision": "", }, { - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": {} - } - } - }, "name": "lib.c", "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": {} - } - } - }, - "name": "lib.d", - "scmRevision": "", - }, - { - "name": "lib.e", - "scmRevision": "", }], "name": "myname", "scmRevision": "", @@ -689,103 +667,7 @@ test.serial("integration: Library without i18n bundle with manifest minimal2", a }, oOptions); }); - -test.serial("integration: Library without i18n bundle with manifest simple", async (t) => { - const workspace = createWorkspace(); - - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => - - // lib.a => lib.c, lib.b - // lib.b => lib.c (true) - // lib.c => - - // dependencies - - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.a - const embeds = ["sub/fold"]; - await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); - // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); - - // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.b", lazy: true}]); - - // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], []); - - const oOptions = { - options: { - projectName: "Test Lib", - pattern: "/resources/**/.library", - rootProject: { - metadata: { - name: "myname" - }, - version: "1.33.7" - } - }, - workspace, - dependencies - }; - await assertCreatedVersionInfo(t, { - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": { - "lazy": true - }, - "lib.c": {} - } - } - } - } - }, - "libraries": [{ - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": {} - } - } - }, - "name": "lib.a", - "scmRevision": "", - }, - { - "name": "lib.b", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": { - "lazy": true - } - } - } - }, - "name": "lib.c", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - }, oOptions); -}); - -test.serial("integration: Library without i18n bundle with manifest simple embeddedBy", async (t) => { +test.serial("integration: Library without dependencies and embeds and embeddedBy", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -831,151 +713,7 @@ test.serial("integration: Library without i18n bundle with manifest simple embed }, oOptions); }); -test.serial("integration: Library without i18n bundle with manifest house sample", async (t) => { - // top level libraries - - // lib.house => lib.roof, lib.walls - // lib.walls => lib.baseplate - // lib.roof => lib.land, lib.walls (true) - // lib.baseplate => lib.land (true) - // lib.land => - - // lib.house => lib.roof, lib.walls, lib.baseplate, lib.land (true) - // lib.walls => lib.baseplate, lib.land (true) - // lib.roof => lib.walls (true), lib.land, lib.baseplate (true) - // lib.baseplate => lib.land (true) - // lib.land => - // - - const workspace = createWorkspace(); - - await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - - // dependencies - const dependencies = createDependencies({virBasePath: "/"}); - - // lib.house - const embeds = ["garden"]; - await createResources(dependencies, resourceFactory, ["lib", "house"], - [{name: "lib.roof"}, {name: "lib.walls"}], embeds); - // sub garden - await createManifestResource(dependencies, resourceFactory, ["lib", "house", "garden"], [{name: "lib.baseplate"}]); - - // lib.roof - await createResources(dependencies, resourceFactory, ["lib", "roof"], - [{name: "lib.land"}, {name: "lib.walls", lazy: true}]); - - // lib.walls - await createResources(dependencies, resourceFactory, ["lib", "walls"], [{name: "lib.baseplate"}]); - - // lib.baseplate - await createResources(dependencies, resourceFactory, ["lib", "baseplate"], [{name: "lib.land", lazy: true}]); - - // lib.land - await createResources(dependencies, resourceFactory, ["lib", "land"], []); - - const oOptions = { - options: { - projectName: "Test Lib", - pattern: "/resources/**/.library", - rootProject: { - metadata: { - name: "myname" - }, - version: "1.33.7" - } - }, - workspace, - dependencies - }; - await assertCreatedVersionInfo(t, { - "components": { - "lib.house.garden": { - "library": "lib.house", - "manifestHints": { - "dependencies": { - "libs": { - "lib.baseplate": {}, - "lib.land": { - "lazy": true - } - } - } - } - } - }, - "libraries": [ - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.land": { - lazy: true - } - } - } - }, - "name": "lib.baseplate", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.walls": {}, - "lib.baseplate": {}, - "lib.roof": {}, - "lib.land": {} - } - } - }, - "name": "lib.house", - "scmRevision": "", - }, - { - "name": "lib.land", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.land": {}, - "lib.baseplate": { - lazy: true - }, - "lib.walls": { - lazy: true - } - } - } - }, - "name": "lib.roof", - "scmRevision": "", - }, - { - "manifestHints": { - "dependencies": { - "libs": { - "lib.baseplate": {}, - "lib.land": { - lazy: true - } - } - } - }, - "name": "lib.walls", - "scmRevision": "", - } - ], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", - }, oOptions); -}); - -test.serial("integration: Library without i18n bundle with manifest simple embeddedBy undefined", async (t) => { +test.serial("integration: Library without dependencies and embeddedBy undefined", async (t) => { const {verboseLogStub} = t.context; const workspace = createWorkspace(); @@ -1025,7 +763,7 @@ test.serial("integration: Library without i18n bundle with manifest simple embed " component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); }); -test.serial("integration: Library without i18n bundle with manifest simple embeddedBy not a string", async (t) => { +test.serial("integration: Library without dependencies and embeddedBy not a string", async (t) => { const {errorLogStub} = t.context; const workspace = createWorkspace(); @@ -1076,7 +814,7 @@ test.serial("integration: Library without i18n bundle with manifest simple embed "it won't be listed as 'embedded'"); }); -test.serial("integration: Library without i18n bundle with manifest simple embeddedBy empty string", async (t) => { +test.serial("integration: Library without dependencies and embeddedBy empty string", async (t) => { const {errorLogStub} = t.context; const workspace = createWorkspace(); @@ -1127,7 +865,7 @@ test.serial("integration: Library without i18n bundle with manifest simple embed "it won't be listed as 'embedded'"); }); -test.serial("integration: Library without i18n bundle with manifest simple embeddedBy path not correct", async (t) => { +test.serial("integration: Library without dependencies and embeddedBy path not correct", async (t) => { const {verboseLogStub} = t.context; const workspace = createWorkspace(); @@ -1177,7 +915,7 @@ test.serial("integration: Library without i18n bundle with manifest simple embed " component's 'sap.app/embeddedBy' points to '%s', don't list it as 'embedded'"); }); -test.serial("integration: manifest with invalid dependency", async (t) => { +test.serial("integration: Library with manifest with invalid dependency", async (t) => { const {errorLogStub} = t.context; const workspace = createWorkspace(); From 300689548b6b6ad1696bea0bfa74bc1e31129d5e Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 12 Jan 2021 17:58:14 +0100 Subject: [PATCH 28/41] Removed verbose log --- lib/processors/versionInfoGenerator.js | 9 --------- test/lib/tasks/generateVersionInfo.js | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 21ba3c174..100635caa 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -223,9 +223,6 @@ class DependencyInfo { * @returns {DependencyInfoObject} */ addResolvedLibDependency(libName, lazy) { - if (log.isLevelEnabled("verbose")) { - log.verbose(`${this.name} add: ${libName}${lazy?" (lazy)":""}`); - } let alreadyResolved = this.isResolved(libName); if (!alreadyResolved) { alreadyResolved = new DependencyInfoObject(libName, lazy); @@ -246,7 +243,6 @@ class DependencyInfo { */ resolve(dependencyInfoMap, lazy) { if (!this.wasResolved || lazy) { - log.verbose(`resolving ${this.name}`); this.libs.forEach((depInfoObject) => { const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); @@ -262,11 +258,6 @@ class DependencyInfo { } }); this.wasResolved = true; - if (log.isLevelEnabled("verbose")) { - log.verbose(`resolved ${this.name}: ${this.libsResolved.map((lib) => { - return `${this.name}: ${lib.name}${lib.lazy ? " (lazy)" : ""}`; - }).join(", ")}`); - } } } } diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index b0da75f01..442e7e7b7 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -758,7 +758,7 @@ test.serial("integration: Library without dependencies and embeddedBy undefined" "version": "1.33.7", }, oOptions); - t.is(verboseLogStub.callCount, 5); + t.is(verboseLogStub.callCount, 1); t.is(verboseLogStub.firstCall.args[0], " component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); }); @@ -910,7 +910,7 @@ test.serial("integration: Library without dependencies and embeddedBy path not c "version": "1.33.7", }, oOptions); - t.is(verboseLogStub.callCount, 5); + t.is(verboseLogStub.callCount, 1); t.is(verboseLogStub.firstCall.args[0], " component's 'sap.app/embeddedBy' points to '%s', don't list it as 'embedded'"); }); From 2526ebd6b8c4bd56e87736b84395503e61ee44af Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Thu, 14 Jan 2021 18:05:10 +0100 Subject: [PATCH 29/41] Add test to ensure versionInfoGenerator remains backward compatible Leaving out optional parameters like libraryManifest and manifestResources must produce the same result as before --- lib/processors/versionInfoGenerator.js | 3 +- test/lib/processors/versionInfoGenerator.js | 77 +++++++++++++++++++++ test/lib/tasks/generateVersionInfo.js | 6 ++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 test/lib/processors/versionInfoGenerator.js diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 100635caa..df0df0eb0 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -374,7 +374,8 @@ class ArtifactInfo { */ const processLibraryInfo = async (libraryInfo) => { if (!libraryInfo.libraryManifest) { - log.error(`library manifest not found for ${libraryInfo.name}`); + log.warn( + `Cannot add meta information for library '${libraryInfo.name}'. The manifest.json file cannot be found`); return; } diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js new file mode 100644 index 000000000..4741567ac --- /dev/null +++ b/test/lib/processors/versionInfoGenerator.js @@ -0,0 +1,77 @@ +const test = require("ava"); +const sinon = require("sinon"); + +const mock = require("mock-require"); +const logger = require("@ui5/logger"); + +let versionInfoGenerator = require("../../../lib/processors/versionInfoGenerator"); + + +test("versionInfoGenerator missing parameters", async (t) => { + const error = await t.throwsAsync(versionInfoGenerator({options: {}})); + t.deepEqual(error.message, "[versionInfoGenerator]: Missing options parameters"); +}); + +test.beforeEach((t) => { + t.context.clock = sinon.useFakeTimers(1610642400000); + t.context.warnLogStub = sinon.stub(); + sinon.stub(logger, "getLogger").returns({ + warn: t.context.warnLogStub, + isLevelEnabled: () => true + }); + versionInfoGenerator = mock.reRequire("../../../lib/processors/versionInfoGenerator"); +}); + +test.afterEach.always((t) => { + t.context.clock.restore(); + mock.stopAll(); + sinon.restore(); +}); + +test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => { + const versionInfos = await versionInfoGenerator({options: { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: []}}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const expected = `{ + "name": "myname", + "version": "1.33.7", + "buildTimestamp": "202101141740", + "scmRevision": "", + "libraries": [] +}`; + t.is(result, expected); +}); + + +test.serial("versionInfoGenerator simple library infos", async (t) => { + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + {name: "my.lib", version: "1.2.3"} + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const expected = `{ + "name": "myname", + "version": "1.33.7", + "buildTimestamp": "202101141740", + "scmRevision": "", + "libraries": [ + { + "name": "my.lib", + "version": "1.2.3", + "buildTimestamp": "202101141740", + "scmRevision": "" + } + ] +}`; + t.is(result, expected); + t.is(t.context.warnLogStub.callCount, 1); + t.is(t.context.warnLogStub.getCall(0).args[0], + "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); +}); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 442e7e7b7..a3d40ed43 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -93,9 +93,11 @@ async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { test.beforeEach((t) => { t.context.verboseLogStub = sinon.stub(); t.context.errorLogStub = sinon.stub(); + t.context.warnLogStub = sinon.stub(); sinon.stub(logger, "getLogger").returns({ verbose: t.context.verboseLogStub, error: t.context.errorLogStub, + warn: t.context.warnLogStub, isLevelEnabled: () => true }); mock.reRequire("../../../lib/processors/versionInfoGenerator"); @@ -141,6 +143,10 @@ test.serial("integration: Library without i18n bundle file", async (t) => { "scmRevision": "", "version": "1.33.7", }, oOptions); + + t.is(t.context.warnLogStub.callCount, 1); + t.is(t.context.warnLogStub.getCall(0).args[0], + "Cannot add meta information for library 'test.lib3'. The manifest.json file cannot be found"); }); test.serial("integration: Library without i18n bundle file failure", async (t) => { From 1fe9c25ecbf6f1d5ecf906f57ce6e890b914e1de Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Thu, 14 Jan 2021 18:41:12 +0100 Subject: [PATCH 30/41] fix jsdoc example use typedef instead of own class for a data container do embedded component dependencies resolving after library dependencies resolving in separate step to avoid mixing them up. --- lib/processors/versionInfoGenerator.js | 88 +++++++------------------- 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index df0df0eb0..ad37edd7a 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -21,7 +21,7 @@ function getTimestamp() { * * @typedef {object} ManifestLibraries * - * * @example + * @example * { * sap.chart: { * lazy: true @@ -32,49 +32,15 @@ function getTimestamp() { /** * Extracted information from a manifest's sap.app and sap.ui5 sections. + * + * @typedef {object} ManifestInfo + * + * @property {string} id The library name + * @property {string} embeddedBy the library this component is embedded in + * @property {string[]} embeds the embedded component names + * @property {ManifestLibraries} libs the dependencies */ -class ManifestInfo { - constructor() { - this.libs = {}; - this.embeds = []; - } - /** - * The library object - * - * @param {ManifestLibraries} libs - */ - setLibs(libs) { - this.libs = libs; - } - - /** - * embedded components, e.g. ["sub/fold"] (relative paths) - * - * @param {string[]} embeds - */ - setEmbeds(embeds) { - this.embeds = embeds; - } - - /** - * relative path to the component which embeds this component, e.g. "../" - * - * @param {string} embeddedBy - */ - setEmbeddedBy(embeddedBy) { - this.embeddedBy = embeddedBy; - } - - /** - * the app id, e.g. "sap.x" - * - * @param {string} id - */ - setId(id) { - this.id = id; - } -} /** * Processes manifest resource and extracts information. @@ -85,7 +51,7 @@ class ManifestInfo { const processManifest = async (manifestResource) => { const manifestContent = await manifestResource.getString(); const manifestObject = JSON.parse(manifestContent); - const manifestInfo = new ManifestInfo(); + const manifestInfo = {}; // sap.ui5/dependencies is used for the "manifestHints/libs" if (manifestObject["sap.ui5"]) { @@ -98,20 +64,20 @@ const processManifest = async (manifestResource) => { libs[libKey].lazy = true; } }); - manifestInfo.setLibs(libs); + manifestInfo.libs = libs; } } // sap.app/embeds, sap.app/embeddedBy and sap.app/id is used for "components" if (manifestObject["sap.app"]) { const manifestEmbeds = manifestObject["sap.app"]["embeds"]; - manifestInfo.setEmbeds(manifestEmbeds); + manifestInfo.embeds = manifestEmbeds; const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; - manifestInfo.setEmbeddedBy(manifestEmbeddedBy); + manifestInfo.embeddedBy = manifestEmbeddedBy; const id = manifestObject["sap.app"]["id"]; - manifestInfo.setId(id); + manifestInfo.id = id; } return manifestInfo; }; @@ -169,17 +135,6 @@ const getManifestPath = (filePath, subPath) => { return posixPath.resolve(folderPathOfManifest + "/manifest.json"); }; -/** - * Resolves the transitive dependencies recursively. - * - * @param {Map} dependencyInfoMap - */ -const resolveTransitiveDependencies = (dependencyInfoMap) => { - dependencyInfoMap.forEach((libraryInfo) => { - libraryInfo.resolve(dependencyInfoMap); - }); -}; - class DependencyInfoObject { /** * @@ -481,14 +436,19 @@ module.exports = async function({options}) { // fill dependencyInfoMap artifactInfos.forEach((artifactInfo) => { dependencyInfoMap.set(artifactInfo.componentName, artifactInfo.dependencyInfo); - artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { - dependencyInfoMap.set(embeddedArtifactInfo.componentName, embeddedArtifactInfo.dependencyInfo); - }); }); - // resolve nested dependencies (transitive) - resolveTransitiveDependencies(dependencyInfoMap); + // resolve library dependencies (transitive) + dependencyInfoMap.forEach((dependencyInfo) => { + dependencyInfo.resolve(dependencyInfoMap); + }); + // resolve dependencies of embedded components + artifactInfos.forEach((artifactInfo) => { + artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { + embeddedArtifactInfo.dependencyInfo.resolve(dependencyInfoMap); + }); + }); const libraries = options.libraryInfos.map((libraryInfo) => { const result = { @@ -519,7 +479,7 @@ module.exports = async function({options}) { library: embeddedArtifactInfo.parentComponentName }; const componentName = embeddedArtifactInfo.componentName; - const dependencyInfo = dependencyInfoMap.get(componentName); + const dependencyInfo = embeddedArtifactInfo.dependencyInfo; const manifestHints = getManifestHints(dependencyInfo); if (manifestHints) { componentObject.manifestHints = manifestHints; From ea58d087332bdf8f25e54732d6df7fc779798d71 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 15 Jan 2021 11:22:27 +0100 Subject: [PATCH 31/41] fix sample in jsdoc use tag
 in jsdoc to preserve
 formatting

Use the same sample across the code e.g. lib.x.sub
---
 lib/processors/versionInfoGenerator.js | 46 ++++++++++++++------------
 1 file changed, 25 insertions(+), 21 deletions(-)

diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js
index ad37edd7a..48c8353ab 100644
--- a/lib/processors/versionInfoGenerator.js
+++ b/lib/processors/versionInfoGenerator.js
@@ -21,13 +21,15 @@ function getTimestamp() {
  *
  * @typedef {object} ManifestLibraries
  *
- * @example
+ * sample:
+ * 
  * {
- *   sap.chart: {
- *       lazy: true
- *   },
- *   sap.f: { }
+ * 	"sap.chart": {
+ * 		"lazy": true
+ * 	},
+ * 	"sap.f": { }
  * }
+ * 
*/ /** @@ -85,8 +87,8 @@ const processManifest = async (manifestResource) => { /** * * @param {string} embeddedBy e.g. "../" - * @param {string} componentPath e.g. "sap/x/sub" - * @param {string} libraryPathPrefix e.g. "sap/x" + * @param {string} componentPath e.g. "lib/x/sub" + * @param {string} libraryPathPrefix e.g. "lib/x" * @returns {boolean} whether or not this component is bundled with the library */ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { @@ -126,9 +128,9 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { /** * Retrieves the manifest path * - * @param {string} filePath path to the manifest, e.g. "sap/x/manifest.json" - * @param {string} subPath relative sub path, e.g. "sdk" - * @returns {string} manifest path, e.g. "sap/x/sdk/manifest.json" + * @param {string} filePath path to the manifest, e.g. "lib/x/manifest.json" + * @param {string} subPath relative sub path, e.g. "sub" + * @returns {string} manifest path, e.g. "lib/x/sub/manifest.json" */ const getManifestPath = (filePath, subPath) => { const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; @@ -138,7 +140,7 @@ const getManifestPath = (filePath, subPath) => { class DependencyInfoObject { /** * - * @param {string} name name of the dependency, e.g. "sap.x" + * @param {string} name name of the dependency, e.g. "lib.x" * @param {boolean} lazy lazy dependency */ constructor(name, lazy) { @@ -344,12 +346,12 @@ const processLibraryInfo = async (libraryInfo) => { const bundledComponents = new Set(); libraryArtifactInfo.setBundledComponents(bundledComponents); - const embeds = manifestInfo.embeds; // e.g. ["sdk"] + const embeds = manifestInfo.embeds; // e.g. ["sub"] // filter const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); }); - // sap.x.sdk + // e.g. manifest resource with lib/x/sub/manifest.json const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => { return embeddedPaths.includes(manifestResource.getPath()); }); @@ -380,13 +382,15 @@ const processLibraryInfo = async (libraryInfo) => { /** * Library Info * - * contains information about the name the version of the library and its manifest, as well as the nested manifests. + * contains information about the name and the version of the library and its manifest, as well as the nested manifests. * * @typedef {object} LibraryInfo - * @property {string} name The library name - * @property {string} version The library version - * @property {module:@ui5/fs.Resource} libraryManifest main manifest resources - * @property {module:@ui5/fs.Resource[]} manifestResources list of corresponding manifest resources + * @property {string} name The library name, e.g. "lib.x" + * @property {string} version The library version, e.g. "1.0.0" + * @property {module:@ui5/fs.Resource} libraryManifest library manifest resource, + * e.g. resource with path "lib/x/manifest.json" + * @property {module:@ui5/fs.Resource[]} manifestResources list of embedded manifest resources, + * e.g. resource with path "lib/x/sub/manifest.json" */ /** @@ -399,14 +403,14 @@ const processLibraryInfo = async (libraryInfo) => { * @param {string} parameters.options.rootProjectName Name of the root project * @param {string} parameters.options.rootProjectVersion Version of the root project * @param {LibraryInfo[]} parameters.options.libraryInfos Array of objects representing libraries, - * e.g. + * e.g.
  *   {
- *      name: "library.xy",
+ *      name: "lib.x",
  *      version: "1.0.0",
  *      libraryManifest: module:@ui5/fs.Resource,
  *      manifestResources: module:@ui5/fs.Resource[]
  *   }
- * 
+ * 
* @returns {Promise} Promise resolving with an array containing the versionInfo resource */ From 874b16056983c250e73ac8be7a6182956bf1862d Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 15 Jan 2021 11:44:38 +0100 Subject: [PATCH 32/41] manifestResources parameter contains only embedded manifests and not the library manifest anymore in the tests adjust expected version info content the same order of properties as the json string result would contain for better readability. --- lib/tasks/generateVersionInfo.js | 4 +- test/lib/tasks/generateVersionInfo.js | 228 +++++++++++++------------- 2 files changed, 117 insertions(+), 115 deletions(-) diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 73f7b8536..24f8b5ca0 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -26,9 +26,11 @@ module.exports = async ({workspace, dependencies, options: {rootProject, pattern const libraryManifest = manifestResources.find((manifestResource) => { return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; }); + const embeddedManifests = + manifestResources.filter((manifestResource) => manifestResource !== libraryManifest); return { libraryManifest, - manifestResources, + manifestResources: embeddedManifests, name: dotLibResource._project.metadata.name, version: dotLibResource._project.version }; diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index a3d40ed43..5e1048b6f 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -345,23 +345,12 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) dependencies }; await assertCreatedVersionInfo(t, { - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": { - "lazy": true - } - } - } - } - } - }, + "name": "myname", + "scmRevision": "", + "version": "1.33.7", "libraries": [{ + "name": "lib.a", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -374,10 +363,10 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) } } }, - "name": "lib.a", - "scmRevision": "", }, { + "name": "lib.b", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -393,10 +382,10 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) } } }, - "name": "lib.b", - "scmRevision": "", }, { + "name": "lib.c", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -407,10 +396,10 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) } } }, - "name": "lib.c", - "scmRevision": "", }, { + "name": "lib.d", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -420,16 +409,27 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) } } }, - "name": "lib.d", - "scmRevision": "", }, { "name": "lib.e", "scmRevision": "", }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": { + "lazy": true + } + } + } + } + } + }, }, oOptions); }); @@ -492,23 +492,12 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy dependencies }; await assertCreatedVersionInfo(t, { - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": { - "lazy": true - } - } - } - } - } - }, + "name": "myname", + "scmRevision": "", + "version": "1.33.7", "libraries": [{ + "name": "lib.a", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -519,10 +508,10 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy } } }, - "name": "lib.a", - "scmRevision": "", }, { + "name": "lib.b", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -538,10 +527,10 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy } } }, - "name": "lib.b", - "scmRevision": "", }, { + "name": "lib.c", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -552,10 +541,10 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy } } }, - "name": "lib.c", - "scmRevision": "", }, { + "name": "lib.d", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -565,16 +554,27 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy } } }, - "name": "lib.d", - "scmRevision": "", }, { "name": "lib.e", "scmRevision": "", }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": { + "lazy": true + } + } + } + } + } + }, }, oOptions); }); @@ -623,22 +623,12 @@ test.serial("integration: Library with simple dependencies and subcomponent", as dependencies }; await assertCreatedVersionInfo(t, { - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.b": {}, - "lib.c": { - "lazy": true - } - } - } - } - } - }, + "name": "myname", + "scmRevision": "", + "version": "1.33.7", "libraries": [{ + "name": "lib.a", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -647,10 +637,10 @@ test.serial("integration: Library with simple dependencies and subcomponent", as } } }, - "name": "lib.a", - "scmRevision": "", }, { + "name": "lib.b", + "scmRevision": "", "manifestHints": { "dependencies": { "libs": { @@ -660,16 +650,26 @@ test.serial("integration: Library with simple dependencies and subcomponent", as } } }, - "name": "lib.b", - "scmRevision": "", }, { "name": "lib.c", "scmRevision": "", }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": { + "lazy": true + } + } + } + } + } + }, }, oOptions); }); @@ -703,19 +703,19 @@ test.serial("integration: Library without dependencies and embeds and embeddedBy dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], "components": { "lib.a.sub.fold": { "hasOwnPreload": true, "library": "lib.a" } }, - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); }); @@ -750,18 +750,18 @@ test.serial("integration: Library without dependencies and embeddedBy undefined" dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], "components": { "lib.a.sub.fold": { "library": "lib.a" } }, - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); t.is(verboseLogStub.callCount, 1); @@ -800,18 +800,18 @@ test.serial("integration: Library without dependencies and embeddedBy not a stri dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], "components": { "lib.a.sub.fold": { "library": "lib.a" } }, - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); t.is(errorLogStub.callCount, 1); @@ -851,18 +851,18 @@ test.serial("integration: Library without dependencies and embeddedBy empty stri dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], "components": { "lib.a.sub.fold": { "library": "lib.a" } }, - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); t.is(errorLogStub.callCount, 1); @@ -902,18 +902,18 @@ test.serial("integration: Library without dependencies and embeddedBy path not c dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + }], "components": { "lib.a.sub.fold": { "library": "lib.a" } }, - "libraries": [{ - "name": "lib.a", - "scmRevision": "", - }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); t.is(verboseLogStub.callCount, 1); @@ -949,6 +949,9 @@ test.serial("integration: Library with manifest with invalid dependency", async dependencies }; await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", "libraries": [{ "name": "lib.a", "scmRevision": "", @@ -960,9 +963,6 @@ test.serial("integration: Library with manifest with invalid dependency", async }, } }], - "name": "myname", - "scmRevision": "", - "version": "1.33.7", }, oOptions); t.is(errorLogStub.callCount, 1); From 57acbcbfc55011bd8ada588de3c7add3da4477de Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 15 Jan 2021 13:09:50 +0100 Subject: [PATCH 33/41] when comparing the version info json created compare objects and omit the exact buildTimestamp --- test/lib/processors/versionInfoGenerator.js | 60 ++++++++++++--------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js index 4741567ac..3b94de68b 100644 --- a/test/lib/processors/versionInfoGenerator.js +++ b/test/lib/processors/versionInfoGenerator.js @@ -13,7 +13,6 @@ test("versionInfoGenerator missing parameters", async (t) => { }); test.beforeEach((t) => { - t.context.clock = sinon.useFakeTimers(1610642400000); t.context.warnLogStub = sinon.stub(); sinon.stub(logger, "getLogger").returns({ warn: t.context.warnLogStub, @@ -23,11 +22,25 @@ test.beforeEach((t) => { }); test.afterEach.always((t) => { - t.context.clock.restore(); mock.stopAll(); sinon.restore(); }); +const assertVersionInfoContent = (t, oExpectedVersionInfo, sActualContent) => { + const currentVersionInfo = JSON.parse(sActualContent); + + t.is(currentVersionInfo.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + + delete currentVersionInfo.buildTimestamp; // removing to allow deep comparison + currentVersionInfo.libraries.forEach((lib) => { + t.is(lib.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + delete lib.buildTimestamp; // removing to allow deep comparison + }); + + + t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); +}; + test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => { const versionInfos = await versionInfoGenerator({options: { rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: []}}); @@ -35,14 +48,13 @@ test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => { const resource = versionInfos[0]; const result = await resource.getString(); - const expected = `{ - "name": "myname", - "version": "1.33.7", - "buildTimestamp": "202101141740", - "scmRevision": "", - "libraries": [] -}`; - t.is(result, expected); + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [] + }; + assertVersionInfoContent(t, oExpected, result); }); @@ -56,21 +68,19 @@ test.serial("versionInfoGenerator simple library infos", async (t) => { const resource = versionInfos[0]; const result = await resource.getString(); - const expected = `{ - "name": "myname", - "version": "1.33.7", - "buildTimestamp": "202101141740", - "scmRevision": "", - "libraries": [ - { - "name": "my.lib", - "version": "1.2.3", - "buildTimestamp": "202101141740", - "scmRevision": "" - } - ] -}`; - t.is(result, expected); + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "my.lib", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); t.is(t.context.warnLogStub.callCount, 1); t.is(t.context.warnLogStub.getCall(0).args[0], "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); From a7e1623e5d0994f7c5bbaad9b631546c5c5940ba Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 15 Jan 2021 13:51:40 +0100 Subject: [PATCH 34/41] add more tests document parent lazy flag better --- lib/processors/versionInfoGenerator.js | 22 +- test/lib/tasks/generateVersionInfo.js | 277 ++++++++++++++++++++++++- 2 files changed, 295 insertions(+), 4 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 48c8353ab..1f09b90c2 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -195,11 +195,27 @@ class DependencyInfo { /** * * @param {Map} dependencyInfoMap - * @param {boolean} [lazy] whether or not the dependency is lazy dependency which means + * @param {boolean} [isParentLazy] whether or not the dependency is lazy dependency which means * all its dependencies should be treated as lazy */ - resolve(dependencyInfoMap, lazy) { - if (!this.wasResolved || lazy) { + resolve(dependencyInfoMap, isParentLazy) { + // it was resolved skip, or if the parent is lazy, because then all its children must be lazy + if (isParentLazy) { + this.libs.forEach((depInfoObject) => { + const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, isParentLazy); + const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); + if (dependencyInfo) { + dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy || isParentLazy); + + dependencyInfo.libsResolved.forEach((resolvedLib) => { + this.addResolvedLibDependency(resolvedLib.name, + isParentLazy); + }); + } else { + log.error(`Cannot find dependency '${depInfoObject.name}' for '${this.name}'`); + } + }); + } else if (!this.wasResolved) { this.libs.forEach((depInfoObject) => { const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 5e1048b6f..307bb1fb5 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -433,6 +433,145 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) }, oOptions); }); +test.serial("integration: Library with dependencies and subcomponent3", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c, lib.e (true) + // lib.b => lib.c (true), lib.e + // lib.c => lib.d, lib.e + // lib.d => lib.e (true) + // lib.e => + // lib.a.sub.fold => lib.c, lib.e (true) + + // expected outcome + // lib.a => lib.c, lib.b, lib.d, lib.e (true) + // lib.b => lib.c (true), lib.d (true), lib.e + // lib.c => lib.d, lib.e + // lib.d => lib.e (true) + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b"}, {name: "lib.c"}, {name: "lib.e", lazy: true}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], + [{name: "lib.e", lazy: true}, {name: "lib.c"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}, {name: "lib.e"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e"}]); + + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); + + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + } + } + }, + }, oOptions); +}); + test.serial("integration: Library with dependencies and subcomponent mixed", async (t) => { const workspace = createWorkspace(); @@ -578,7 +717,7 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy }, oOptions); }); -test.serial("integration: Library with simple dependencies and subcomponent", async (t) => { +test.serial("integration: Library with simple dependencies and subcomponent simple", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -673,6 +812,142 @@ test.serial("integration: Library with simple dependencies and subcomponent", as }, oOptions); }); + +test.serial("integration: Library with simple dependencies and subcomponent special case", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + + // lib.a => lib.b (true), lib.d + // lib.b => lib.c + // lib.c => lib.d + // lib.d => lib.e + // lib.e => + + // lib.a => lib.b (true), lib.c (true), lib.d, lib.e + // lib.b => lib.c, lib.d, lib.e + // lib.c => lib.d, lib.e + // lib.d => lib.e + // lib.e => + + // dependencies + + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b", lazy: true}, {name: "lib.d"}], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.b"}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); + + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": { + "lazy": true + }, + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + }, + { + "name": "lib.e", + "scmRevision": "", + }], + "components": { + "lib.a.sub.fold": { + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + } + } + }, + }, oOptions); +}); + test.serial("integration: Library without dependencies and embeds and embeddedBy", async (t) => { const workspace = createWorkspace(); From f14cba5e247ffc1d80091d0c5ee3e47b5c3e862e Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 15 Jan 2021 18:06:40 +0100 Subject: [PATCH 35/41] improve error message when dependency was found in manifest but not in project dependencies improve recursive resolve logic improve jsdoc for ArtifactInfo use better structures instead of classes for * libs and libsResolved * DependencyInfoObject Simplify tests --- lib/processors/versionInfoGenerator.js | 144 ++++---- test/lib/tasks/generateVersionInfo.js | 444 +++++++++++-------------- 2 files changed, 247 insertions(+), 341 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 1f09b90c2..8f0c449b5 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -137,22 +137,20 @@ const getManifestPath = (filePath, subPath) => { return posixPath.resolve(folderPathOfManifest + "/manifest.json"); }; -class DependencyInfoObject { - /** - * - * @param {string} name name of the dependency, e.g. "lib.x" - * @param {boolean} lazy lazy dependency - */ - constructor(name, lazy) { - this.name = name; - this.lazy = lazy; - } -} +/** + * Contains name and lazy property. + * + * @typedef {object} DependencyInfoObject + * + * @property {string} name The library name + * @property {boolean} lazy whether or not the library is lazy or eager + */ + class DependencyInfo { /** * - * @param {DependencyInfoObject[]} libs + * @param {object} libs * @param {string} name */ constructor(libs, name) { @@ -160,19 +158,14 @@ class DependencyInfo { this.name = name; /** + * contains as key the lirbary name and as value the lazy property * - * @type {DependencyInfoObject[]} + * @type {object} */ - this.libsResolved = []; + this.libsResolved = Object.create(null); this.wasResolved = false; } - isResolved(libName) { - return this.libsResolved.find((libResolved) => { - return libResolved.name === libName; - }); - } - /** * * @param {string} libName @@ -180,57 +173,52 @@ class DependencyInfo { * @returns {DependencyInfoObject} */ addResolvedLibDependency(libName, lazy) { - let alreadyResolved = this.isResolved(libName); + let alreadyResolved = this.libsResolved[libName]; if (!alreadyResolved) { - alreadyResolved = new DependencyInfoObject(libName, lazy); - this.libsResolved.push(alreadyResolved); - } else { - if (!alreadyResolved.lazy || !lazy) { - alreadyResolved.lazy = undefined; + alreadyResolved = Object.create(null); + if (lazy) { + alreadyResolved.lazy = true; } + this.libsResolved[libName] = alreadyResolved; + } else { + // siblings if sibling is eager only if one other sibling eager + alreadyResolved.lazy = alreadyResolved.lazy && lazy; } return alreadyResolved; } /** + * Resolves dependencies recursively and stores them in libsResolved + * with + * - resolved siblings a lazy and a eager dependency becomes eager + * - resolved children become lazy if their parent is lazy * * @param {Map} dependencyInfoMap - * @param {boolean} [isParentLazy] whether or not the dependency is lazy dependency which means - * all its dependencies should be treated as lazy */ - resolve(dependencyInfoMap, isParentLazy) { - // it was resolved skip, or if the parent is lazy, because then all its children must be lazy - if (isParentLazy) { - this.libs.forEach((depInfoObject) => { - const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, isParentLazy); - const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); - if (dependencyInfo) { - dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy || isParentLazy); - - dependencyInfo.libsResolved.forEach((resolvedLib) => { - this.addResolvedLibDependency(resolvedLib.name, - isParentLazy); - }); - } else { - log.error(`Cannot find dependency '${depInfoObject.name}' for '${this.name}'`); - } - }); - } else if (!this.wasResolved) { - this.libs.forEach((depInfoObject) => { + resolve(dependencyInfoMap) { + if (!this.wasResolved) { + // early set if there is a potential cycle + this.wasResolved = true; + Object.keys(this.libs).forEach((theLibName) => { + const depInfoObject = {name: theLibName, lazy: this.libs[theLibName].lazy}; const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); if (dependencyInfo) { - dependencyInfo.resolve(dependencyInfoMap, dependencyInfoObjectAdded.lazy); + dependencyInfo.resolve(dependencyInfoMap); - dependencyInfo.libsResolved.forEach((resolvedLib) => { - this.addResolvedLibDependency(resolvedLib.name, + // children if parent is lazy children become lazy + Object.keys(dependencyInfo.libsResolved).forEach((libName) => { + const resolvedLib = dependencyInfo.libsResolved[libName]; + this.addResolvedLibDependency(libName, resolvedLib.lazy || dependencyInfoObjectAdded.lazy); }); } else { - log.error(`Cannot find dependency '${depInfoObject.name}' for '${this.name}'`); + log.info(`Cannot find dependency '${depInfoObject.name}' `+ + `defined in the manifest.json or .library file of project '${this.name}'. ` + + "This might prevent some UI5 runtime performance optimizations from taking effect. " + + "Please double check your project's dependency configuration."); } }); - this.wasResolved = true; } } } @@ -258,33 +246,22 @@ const sortObjectKeys = (obj) => { * @returns {object} manifestHints */ const getManifestHints = (dependencyInfo) => { - if (dependencyInfo && dependencyInfo.libs.length) { - const libsObject = {}; - dependencyInfo.libsResolved.forEach((dependencyInfoObject) => { - libsObject[dependencyInfoObject.name] = {}; - if (dependencyInfoObject.lazy) { - libsObject[dependencyInfoObject.name].lazy = true; - } - }); + if (dependencyInfo && Object.keys(dependencyInfo.libsResolved).length) { return { dependencies: { - libs: sortObjectKeys(libsObject) + libs: sortObjectKeys(dependencyInfo.libsResolved) } }; } }; -const convertToDependencyInfoObjects = (libs) => { - return Object.keys(libs).map((name) => { - const lazy = libs[name].lazy === true; - return new DependencyInfoObject(name, lazy); - }); -}; - +/** + * Common type for Library and Component + * embeds and bundled components makes only sense for library + */ class ArtifactInfo { /** - * - * @param {string} componentName + * @param {string} componentName e.g. lib.x */ constructor(componentName) { this.componentName = componentName; @@ -301,16 +278,18 @@ class ArtifactInfo { } /** + * The embedded components which have a embeddedBy reference to the library * - * @param {Set} bundledComponents + * @param {Set} bundledComponents e.g. ["lib.x.sub"] */ setBundledComponents(bundledComponents) { this.bundledComponents = bundledComponents; } /** + * Set the embedded components of the library * - * @param {ArtifactInfo[]} artifactInfos + * @param {ArtifactInfo[]} artifactInfos embedded components */ setEmbeds(artifactInfos) { this.artifactInfos = artifactInfos; @@ -320,16 +299,14 @@ class ArtifactInfo { } /** - * - * @returns {ArtifactInfo[]} + * @returns {ArtifactInfo[]} get embedded components of this library */ getEmbeds() { return this.artifactInfos; } /** - * - * @param {ArtifactInfo} parent + * @param {ArtifactInfo} parent set the parent library * @private */ _setParent(parent) { @@ -354,16 +331,14 @@ const processLibraryInfo = async (libraryInfo) => { const manifestInfo = await processManifest(libraryInfo.libraryManifest); // gather shallow library information - const dependencyInfoObjects = convertToDependencyInfoObjects(manifestInfo.libs); - const libraryArtifactInfo = new ArtifactInfo(libraryInfo.name); - libraryArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, libraryInfo.name)); + libraryArtifactInfo.setDependencyInfo(new DependencyInfo(manifestInfo.libs, libraryInfo.name)); const bundledComponents = new Set(); libraryArtifactInfo.setBundledComponents(bundledComponents); const embeds = manifestInfo.embeds; // e.g. ["sub"] - // filter + // filter only embedded manifests const embeddedPaths = embeds.map((embed) => { return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); }); @@ -378,10 +353,9 @@ const processLibraryInfo = async (libraryInfo) => { const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath()); const embeddedManifestInfo = await processManifest(relevantManifest); - const dependencyInfoObjects = convertToDependencyInfoObjects(embeddedManifestInfo.libs); const componentName = embeddedManifestInfo.id; const componentArtifactInfo = new ArtifactInfo(componentName); - componentArtifactInfo.setDependencyInfo(new DependencyInfo(dependencyInfoObjects, componentName)); + componentArtifactInfo.setDependencyInfo(new DependencyInfo(embeddedManifestInfo.libs, componentName)); if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { bundledComponents.add(componentName); @@ -471,7 +445,7 @@ module.exports = async function({options}) { }); const libraries = options.libraryInfos.map((libraryInfo) => { - const result = { + const library = { name: libraryInfo.name, version: libraryInfo.version, buildTimestamp: buildTimestamp, @@ -481,9 +455,9 @@ module.exports = async function({options}) { const dependencyInfo = dependencyInfoMap.get(libraryInfo.name); const manifestHints = getManifestHints(dependencyInfo); if (manifestHints) { - result.manifestHints = manifestHints; + library.manifestHints = manifestHints; } - return result; + return library; }); // sort libraries alphabetically diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index 307bb1fb5..a524809c3 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -94,10 +94,12 @@ test.beforeEach((t) => { t.context.verboseLogStub = sinon.stub(); t.context.errorLogStub = sinon.stub(); t.context.warnLogStub = sinon.stub(); + t.context.infoLogStub = sinon.stub(); sinon.stub(logger, "getLogger").returns({ verbose: t.context.verboseLogStub, error: t.context.errorLogStub, warn: t.context.warnLogStub, + info: t.context.infoLogStub, isLevelEnabled: () => true }); mock.reRequire("../../../lib/processors/versionInfoGenerator"); @@ -287,48 +289,32 @@ const createResources = async (dependencies, resourceFactory, names, deps, embed await createManifestResource(dependencies, resourceFactory, names, deps, embeds); }; - -test.serial("integration: Library with dependencies and subcomponent", async (t) => { +test.serial("integration: sibling eager to lazy", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); // input - // lib.a => lib.c, lib.b + // lib.a => lib.b, lib.c // lib.b => lib.c (true) - // lib.c => lib.d - // lib.d => lib.e - // lib.e => - // lib.a.sub.fold => lib.c + // lib.c => // expected outcome - // lib.a => lib.c, lib.b, lib.d - // lib.b => lib.c (true), lib.d (true) - // lib.c => lib.d - // lib.d => lib.e - // lib.e => - // lib.a.sub.fold => lib.c, lib.d, lib.e + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => // dependencies const dependencies = createDependencies({virBasePath: "/"}); // lib.a - const embeds = ["sub/fold"]; - await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); - // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}]); // lib.b await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); - - // lib.d - await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); - - // lib.e - await createResources(dependencies, resourceFactory, ["lib", "e"], []); + await createResources(dependencies, resourceFactory, ["lib", "c"], []); const oOptions = { options: { @@ -355,11 +341,7 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) "dependencies": { "libs": { "lib.b": {}, - "lib.c": {}, - "lib.d": {}, - "lib.e": { - "lazy": true - } + "lib.c": {} } } }, @@ -372,12 +354,6 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) "libs": { "lib.c": { "lazy": true - }, - "lib.d": { - "lazy": true - }, - "lib.e": { - "lazy": true } } } @@ -385,97 +361,38 @@ test.serial("integration: Library with dependencies and subcomponent", async (t) }, { "name": "lib.c", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.d", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.e", - "scmRevision": "", + "scmRevision": "" }], - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": { - "lazy": true - } - } - } - } - } - }, }, oOptions); }); -test.serial("integration: Library with dependencies and subcomponent3", async (t) => { +test.serial("integration: sibling lazy to eager", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); // input - // lib.a => lib.b, lib.c, lib.e (true) - // lib.b => lib.c (true), lib.e - // lib.c => lib.d, lib.e - // lib.d => lib.e (true) - // lib.e => - // lib.a.sub.fold => lib.c, lib.e (true) + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c + // lib.c => // expected outcome - // lib.a => lib.c, lib.b, lib.d, lib.e (true) - // lib.b => lib.c (true), lib.d (true), lib.e - // lib.c => lib.d, lib.e - // lib.d => lib.e (true) - // lib.e => - // lib.a.sub.fold => lib.c, lib.d, lib.e + // lib.a => lib.b, lib.c + // lib.b => lib.c + // lib.c => // dependencies const dependencies = createDependencies({virBasePath: "/"}); // lib.a - const embeds = ["sub/fold"]; await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b"}, {name: "lib.c"}, {name: "lib.e", lazy: true}], embeds); - // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], - [{name: "lib.e", lazy: true}, {name: "lib.c"}]); + [{name: "lib.b"}, {name: "lib.c", lazy: true}]); // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}, {name: "lib.e"}]); + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e"}]); - - // lib.d - await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); - - // lib.e - await createResources(dependencies, resourceFactory, ["lib", "e"], []); + await createResources(dependencies, resourceFactory, ["lib", "c"], []); const oOptions = { options: { @@ -502,9 +419,7 @@ test.serial("integration: Library with dependencies and subcomponent3", async (t "dependencies": { "libs": { "lib.b": {}, - "lib.c": {}, - "lib.d": {}, - "lib.e": {} + "lib.c": {} } } }, @@ -515,106 +430,46 @@ test.serial("integration: Library with dependencies and subcomponent3", async (t "manifestHints": { "dependencies": { "libs": { - "lib.c": { - "lazy": true - }, - "lib.d": { - "lazy": true - }, - "lib.e": {} + "lib.c": {} } } }, }, { "name": "lib.c", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.d": {}, - "lib.e": {} - } - } - }, - }, - { - "name": "lib.d", - "scmRevision": "", - "manifestHints": { - "dependencies": { - "libs": { - "lib.e": { - "lazy": true - } - } - } - }, - }, - { - "name": "lib.e", - "scmRevision": "", + "scmRevision": "" }], - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": {} - } - } - } - } - }, }, oOptions); }); -test.serial("integration: Library with dependencies and subcomponent mixed", async (t) => { +test.serial("integration: children eager to lazy", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); // input - // lib.a => lib.b, lib.c, lib.e + // lib.a => lib.b // lib.b => lib.c (true) - // lib.c => lib.d - // lib.d => lib.e (true) - // lib.e => - // lib.a.sub.fold => lib.c + // lib.c => - // outcome - // lib.a => lib.b, lib.c, lib.d, lib.e - // lib.b => lib.c (true), lib.d (true), lib.e (true) - // lib.c => lib.d, lib.e (true) - // lib.d => lib.e (true) - // lib.e => - // lib.a.sub.fold => lib.c, lib.d, lib.e (true) + // expected outcome + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c (true) + // lib.c => // dependencies - const dependencies = createDependencies({virBasePath: "/"}); // lib.a - const embeds = ["sub/fold"]; await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b"}, {name: "lib.c"}, {name: "lib.e"}], embeds); - // sub - await createManifestResource(dependencies, resourceFactory, - ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + [{name: "lib.b"}]); // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + await createResources(dependencies, resourceFactory, ["lib", "b"], + [{name: "lib.c", lazy: true}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); - - // lib.d - await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e", lazy: true}]); - // lib.e - await createResources(dependencies, resourceFactory, ["lib", "e"], []); + await createResources(dependencies, resourceFactory, ["lib", "c"], []); const oOptions = { options: { @@ -641,9 +496,9 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy "dependencies": { "libs": { "lib.b": {}, - "lib.c": {}, - "lib.d": {}, - "lib.e": {} + "lib.c": { + "lazy": true + } } } }, @@ -656,12 +511,6 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy "libs": { "lib.c": { "lazy": true - }, - "lib.d": { - "lazy": true - }, - "lib.e": { - "lazy": true } } } @@ -669,12 +518,68 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy }, { "name": "lib.c", + "scmRevision": "" + }], + }, oOptions); +}); + +test.serial("integration: children lazy to eager", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b (true) + // lib.b => lib.c + // lib.c => + + // expected outcome + // lib.a => lib.b (true), lib.c (true) + // lib.b => lib.c + // lib.c => + + // dependencies + const dependencies = createDependencies({virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], + [{name: "lib.b", lazy: true}]); + + // lib.b + await createResources(dependencies, resourceFactory, ["lib", "b"], + [{name: "lib.c"}]); + + // lib.c + await createResources(dependencies, resourceFactory, ["lib", "c"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: { + metadata: { + name: "myname" + }, + version: "1.33.7" + } + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", "scmRevision": "", "manifestHints": { "dependencies": { "libs": { - "lib.d": {}, - "lib.e": { + "lib.b": { + "lazy": true + }, + "lib.c": { "lazy": true } } @@ -682,70 +587,64 @@ test.serial("integration: Library with dependencies and subcomponent mixed", asy }, }, { - "name": "lib.d", + "name": "lib.b", "scmRevision": "", "manifestHints": { "dependencies": { "libs": { - "lib.e": { - "lazy": true - } + "lib.c": {} } } }, }, { - "name": "lib.e", - "scmRevision": "", + "name": "lib.c", + "scmRevision": "" }], - "components": { - "lib.a.sub.fold": { - "library": "lib.a", - "manifestHints": { - "dependencies": { - "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": { - "lazy": true - } - } - } - } - } - }, }, oOptions); }); -test.serial("integration: Library with simple dependencies and subcomponent simple", async (t) => { +test.serial("integration: Library with dependencies and subcomponent complex scenario", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - + // input // lib.a => lib.b, lib.c // lib.b => lib.c (true) - // lib.c => + // lib.c => lib.d, lib.e (true) + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c - // lib.a => lib.b, lib.c - // lib.b => lib.c (true) - // lib.c => + // expected outcome + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) + // lib.c => lib.d, lib.e + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e // dependencies - const dependencies = createDependencies({virBasePath: "/"}); // lib.a const embeds = ["sub/fold"]; await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.b"}]); + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); // lib.b await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], []); + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e", lazy: true}]); + + // lib.d + await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + + // lib.e + await createResources(dependencies, resourceFactory, ["lib", "e"], []); const oOptions = { options: { @@ -772,7 +671,9 @@ test.serial("integration: Library with simple dependencies and subcomponent simp "dependencies": { "libs": { "lib.b": {}, - "lib.c": {} + "lib.c": {}, + "lib.d": {}, + "lib.e": {} } } }, @@ -785,6 +686,12 @@ test.serial("integration: Library with simple dependencies and subcomponent simp "libs": { "lib.c": { "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true } } } @@ -793,6 +700,29 @@ test.serial("integration: Library with simple dependencies and subcomponent simp { "name": "lib.c", "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + }, + { + "name": "lib.e", + "scmRevision": "", }], "components": { "lib.a.sub.fold": { @@ -800,10 +730,9 @@ test.serial("integration: Library with simple dependencies and subcomponent simp "manifestHints": { "dependencies": { "libs": { - "lib.b": {}, - "lib.c": { - "lazy": true - } + "lib.c": {}, + "lib.d": {}, + "lib.e": {} } } } @@ -812,41 +741,41 @@ test.serial("integration: Library with simple dependencies and subcomponent simp }, oOptions); }); - -test.serial("integration: Library with simple dependencies and subcomponent special case", async (t) => { +test.serial("integration: Library with dependencies and subcomponent bigger scenario", async (t) => { const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); - - // lib.a => lib.b (true), lib.d - // lib.b => lib.c - // lib.c => lib.d + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => lib.d, lib.e (true) // lib.d => lib.e // lib.e => + // lib.a.sub.fold => lib.c - // lib.a => lib.b (true), lib.c (true), lib.d, lib.e - // lib.b => lib.c, lib.d, lib.e + // expected outcome + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) // lib.c => lib.d, lib.e // lib.d => lib.e // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e // dependencies - const dependencies = createDependencies({virBasePath: "/"}); // lib.a const embeds = ["sub/fold"]; - await createResources(dependencies, resourceFactory, ["lib", "a"], - [{name: "lib.b", lazy: true}, {name: "lib.d"}], embeds); + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); // sub - await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.b"}]); + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); // lib.b - await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); + await createResources(dependencies, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); // lib.c - await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}]); + await createResources(dependencies, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e", lazy: true}]); // lib.d await createResources(dependencies, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); @@ -878,12 +807,8 @@ test.serial("integration: Library with simple dependencies and subcomponent spec "manifestHints": { "dependencies": { "libs": { - "lib.b": { - "lazy": true - }, - "lib.c": { - "lazy": true - }, + "lib.b": {}, + "lib.c": {}, "lib.d": {}, "lib.e": {} } @@ -896,9 +821,15 @@ test.serial("integration: Library with simple dependencies and subcomponent spec "manifestHints": { "dependencies": { "libs": { - "lib.c": {}, - "lib.d": {}, - "lib.e": {} + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } } } }, @@ -936,7 +867,6 @@ test.serial("integration: Library with simple dependencies and subcomponent spec "manifestHints": { "dependencies": { "libs": { - "lib.b": {}, "lib.c": {}, "lib.d": {}, "lib.e": {} @@ -1197,7 +1127,7 @@ test.serial("integration: Library without dependencies and embeddedBy path not c }); test.serial("integration: Library with manifest with invalid dependency", async (t) => { - const {errorLogStub} = t.context; + const {infoLogStub} = t.context; const workspace = createWorkspace(); await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); @@ -1240,7 +1170,9 @@ test.serial("integration: Library with manifest with invalid dependency", async }], }, oOptions); - t.is(errorLogStub.callCount, 1); - t.is(errorLogStub.firstCall.args[0], - "Cannot find dependency 'non.existing' for 'lib.a'"); + t.is(infoLogStub.callCount, 1); + t.is(infoLogStub.firstCall.args[0], + "Cannot find dependency 'non.existing' defined in the manifest.json or .library file of project 'lib.a'. " + + "This might prevent some UI5 runtime performance optimizations from taking effect. " + + "Please double check your project's dependency configuration."); }); From 666b3cb049cb6e908fed1d06c84acd8f42e3c0bf Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Mon, 18 Jan 2021 10:07:13 +0100 Subject: [PATCH 36/41] remove typedef for DependencyInfoObject refactor duplicate code to common method processManifestAndGetArtifactInfo --- lib/processors/versionInfoGenerator.js | 53 ++++++++++++-------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 8f0c449b5..c6cfaa341 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -137,15 +137,6 @@ const getManifestPath = (filePath, subPath) => { return posixPath.resolve(folderPathOfManifest + "/manifest.json"); }; -/** - * Contains name and lazy property. - * - * @typedef {object} DependencyInfoObject - * - * @property {string} name The library name - * @property {boolean} lazy whether or not the library is lazy or eager - */ - class DependencyInfo { /** @@ -170,7 +161,7 @@ class DependencyInfo { * * @param {string} libName * @param {boolean} lazy - * @returns {DependencyInfoObject} + * @returns {{lazy: boolean}} */ addResolvedLibDependency(libName, lazy) { let alreadyResolved = this.libsResolved[libName]; @@ -199,21 +190,21 @@ class DependencyInfo { if (!this.wasResolved) { // early set if there is a potential cycle this.wasResolved = true; - Object.keys(this.libs).forEach((theLibName) => { - const depInfoObject = {name: theLibName, lazy: this.libs[theLibName].lazy}; - const dependencyInfoObjectAdded = this.addResolvedLibDependency(depInfoObject.name, depInfoObject.lazy); - const dependencyInfo = dependencyInfoMap.get(depInfoObject.name); + Object.keys(this.libs).forEach((libName) => { + const lazy = this.libs[libName].lazy; + const dependencyInfoObjectAdded = this.addResolvedLibDependency(libName, lazy); + const dependencyInfo = dependencyInfoMap.get(libName); if (dependencyInfo) { dependencyInfo.resolve(dependencyInfoMap); // children if parent is lazy children become lazy - Object.keys(dependencyInfo.libsResolved).forEach((libName) => { - const resolvedLib = dependencyInfo.libsResolved[libName]; - this.addResolvedLibDependency(libName, + Object.keys(dependencyInfo.libsResolved).forEach((resolvedLibName) => { + const resolvedLib = dependencyInfo.libsResolved[resolvedLibName]; + this.addResolvedLibDependency(resolvedLibName, resolvedLib.lazy || dependencyInfoObjectAdded.lazy); }); } else { - log.info(`Cannot find dependency '${depInfoObject.name}' `+ + log.info(`Cannot find dependency '${libName}' `+ `defined in the manifest.json or .library file of project '${this.name}'. ` + "This might prevent some UI5 runtime performance optimizations from taking effect. " + "Please double check your project's dependency configuration."); @@ -316,6 +307,14 @@ class ArtifactInfo { } } +async function processManifestAndGetArtifactInfo(libraryManifest, name) { + const manifestInfo = await processManifest(libraryManifest); + name = name || manifestInfo.id; + const libraryArtifactInfo = new ArtifactInfo(name); + libraryArtifactInfo.setDependencyInfo(new DependencyInfo(manifestInfo.libs, name)); + return {manifestInfo, libraryArtifactInfo}; +} + /** * Processes the library info and fills the maps dependencyInfoMap and embeddedInfoMap. * @@ -329,10 +328,8 @@ const processLibraryInfo = async (libraryInfo) => { return; } - const manifestInfo = await processManifest(libraryInfo.libraryManifest); - // gather shallow library information - const libraryArtifactInfo = new ArtifactInfo(libraryInfo.name); - libraryArtifactInfo.setDependencyInfo(new DependencyInfo(manifestInfo.libs, libraryInfo.name)); + const {manifestInfo, libraryArtifactInfo} = + await processManifestAndGetArtifactInfo(libraryInfo.libraryManifest, libraryInfo.name); const bundledComponents = new Set(); libraryArtifactInfo.setBundledComponents(bundledComponents); @@ -349,18 +346,18 @@ const processLibraryInfo = async (libraryInfo) => { // get all embedded manifests const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => { - const fullManifestPath = posixPath.dirname(relevantManifest.getPath()); - const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath()); + const {manifestInfo: embeddedManifestInfo, libraryArtifactInfo: embeddedArtifactInfo} = + await processManifestAndGetArtifactInfo(relevantManifest); - const embeddedManifestInfo = await processManifest(relevantManifest); const componentName = embeddedManifestInfo.id; - const componentArtifactInfo = new ArtifactInfo(componentName); - componentArtifactInfo.setDependencyInfo(new DependencyInfo(embeddedManifestInfo.libs, componentName)); + + const fullManifestPath = posixPath.dirname(relevantManifest.getPath()); + const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath()); if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) { bundledComponents.add(componentName); } - return componentArtifactInfo; + return embeddedArtifactInfo; }); const embeddedArtifactInfos = await Promise.all(embeddedManifestPromises); From b9da72db5dec100900c85b27ce69c2cee73a1a88 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 19 Jan 2021 10:54:55 +0100 Subject: [PATCH 37/41] improve jsdoc and reuse types add tests for versionInfoGenerator with new parameters * libraryManifest * embeddedManifests --- lib/processors/versionInfoGenerator.js | 65 ++++-- lib/tasks/generateVersionInfo.js | 2 +- test/lib/processors/versionInfoGenerator.js | 233 ++++++++++++++++++++ 3 files changed, 275 insertions(+), 25 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index c6cfaa341..4fbb42ec7 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -19,7 +19,7 @@ function getTimestamp() { /** * Manifest libraries as defined in the manifest.json file * - * @typedef {object} ManifestLibraries + * @typedef {object} ManifestLibraries * * sample: *
@@ -37,10 +37,10 @@ function getTimestamp() {
  *
  * @typedef {object} ManifestInfo
  *
- * @property {string} id The library name
- * @property {string} embeddedBy the library this component is embedded in
- * @property {string[]} embeds the embedded component names
- * @property {ManifestLibraries} libs the dependencies
+ * @property {string} id The library name, e.g. "lib.x"
+ * @property {string} embeddedBy the library this component is embedded in, e.g. "lib.x"
+ * @property {string[]} embeds the embedded component names, e.g. ["lib.x.sub"]
+ * @property {ManifestLibraries} libs the dependencies, e.g. {"sap.chart":{"lazy": true}, "sap.f":{}}
  */
 
 
@@ -85,6 +85,7 @@ const processManifest = async (manifestResource) => {
 };
 
 /**
+ * Checks if a component (componentPath) is bundled with the library (embeddedBy)
  *
  * @param {string} embeddedBy e.g. "../"
  * @param {string} componentPath e.g. "lib/x/sub"
@@ -126,7 +127,7 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => {
 };
 
 /**
- * Retrieves the manifest path
+ * Retrieves the manifest path of a subcomponent
  *
  * @param {string} filePath path to the manifest, e.g. "lib/x/manifest.json"
  * @param {string} subPath relative sub path, e.g. "sub"
@@ -137,31 +138,37 @@ const getManifestPath = (filePath, subPath) => {
 	return posixPath.resolve(folderPathOfManifest + "/manifest.json");
 };
 
-
+/**
+ * Represents dependency information for a library.
+ * Dependencies can be resolved recursively using #resolve
+ * and are stored then in libsResolved
+ */
 class DependencyInfo {
 	/**
 	 *
-	 * @param {object} libs
-	 * @param {string} name
+	 * @param {ManifestLibraries} libs
+	 * @param {string} name library name, e.g. "lib.x"
 	 */
 	constructor(libs, name) {
 		this.libs = libs;
 		this.name = name;
 
 		/**
-		 * contains as key the lirbary name and as value the lazy property
+		 * contains as key the library name and as value an object with an optional lazy property
 		 *
-		 * @type {object}
+		 * @type {ManifestLibraries}
 		 */
 		this.libsResolved = Object.create(null);
 		this.wasResolved = false;
 	}
 
 	/**
+	 * Add library to libsResolved and if already present
+	 * merge lazy property
 	 *
-	 * @param {string} libName
+	 * @param {string} libName library name, e.g. "lib.x"
 	 * @param {boolean} lazy
-	 * @returns {{lazy: boolean}}
+	 * @returns {{lazy: boolean}} the added library
 	 */
 	addResolvedLibDependency(libName, lazy) {
 		let alreadyResolved = this.libsResolved[libName];
@@ -219,7 +226,7 @@ class DependencyInfo {
  * Sorts the keys of a given object
  *
  * @param {object} obj the object
- * @returns {{}}
+ * @returns {{}} the object with sorted keys
  */
 const sortObjectKeys = (obj) => {
 	const sortedObject = {};
@@ -232,9 +239,10 @@ const sortObjectKeys = (obj) => {
 };
 
 /**
+ * Builds the manifestHints object from the dependencyInfo
  *
  * @param {DependencyInfo} dependencyInfo
- * @returns {object} manifestHints
+ * @returns {{dependencies: {libs: ManifestLibraries}}} manifestHints
  */
 const getManifestHints = (dependencyInfo) => {
 	if (dependencyInfo && Object.keys(dependencyInfo.libsResolved).length) {
@@ -307,6 +315,13 @@ class ArtifactInfo {
 	}
 }
 
+/**
+ * Processes the manifest and creates a ManifestInfo and an ArtifactInfo.
+ *
+ * @param {module:@ui5/fs.Resource} libraryManifest
+ * @param {string} [name] library name, if not provided using the ManifestInfo's id
+ * @returns {Promise<{manifestInfo: ManifestInfo, libraryArtifactInfo: ArtifactInfo}>}
+ */
 async function processManifestAndGetArtifactInfo(libraryManifest, name) {
 	const manifestInfo = await processManifest(libraryManifest);
 	name = name || manifestInfo.id;
@@ -340,21 +355,23 @@ const processLibraryInfo = async (libraryInfo) => {
 		return getManifestPath(libraryInfo.libraryManifest.getPath(), embed);
 	});
 	// e.g. manifest resource with lib/x/sub/manifest.json
-	const relevantManifests = libraryInfo.manifestResources.filter((manifestResource) => {
+	let embeddedManifests = libraryInfo.embeddedManifests || [];
+	embeddedManifests = embeddedManifests.filter((manifestResource) => {
 		return embeddedPaths.includes(manifestResource.getPath());
 	});
 
 	// get all embedded manifests
-	const embeddedManifestPromises = relevantManifests.map(async (relevantManifest) => {
+	const embeddedManifestPromises = embeddedManifests.map(async (embeddedManifest) => {
 		const {manifestInfo: embeddedManifestInfo, libraryArtifactInfo: embeddedArtifactInfo} =
-			await processManifestAndGetArtifactInfo(relevantManifest);
+			await processManifestAndGetArtifactInfo(embeddedManifest);
 
 		const componentName = embeddedManifestInfo.id;
 
-		const fullManifestPath = posixPath.dirname(relevantManifest.getPath());
-		const libraryPathPrefix = posixPath.dirname(libraryInfo.libraryManifest.getPath());
+		const embeddedManifestDirName = posixPath.dirname(embeddedManifest.getPath());
+		const libraryManifestDirName = posixPath.dirname(libraryInfo.libraryManifest.getPath());
 
-		if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, fullManifestPath, libraryPathPrefix + "/")) {
+		if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, embeddedManifestDirName,
+			libraryManifestDirName + "/")) {
 			bundledComponents.add(componentName);
 		}
 		return embeddedArtifactInfo;
@@ -376,7 +393,7 @@ const processLibraryInfo = async (libraryInfo) => {
  * @property {string} version The library version, e.g. "1.0.0"
  * @property {module:@ui5/fs.Resource} libraryManifest library manifest resource,
  *  e.g. resource with path "lib/x/manifest.json"
- * @property {module:@ui5/fs.Resource[]} manifestResources list of embedded manifest resources,
+ * @property {module:@ui5/fs.Resource[]} embeddedManifests list of embedded manifest resources,
  *  e.g. resource with path "lib/x/sub/manifest.json"
  */
 
@@ -395,7 +412,7 @@ const processLibraryInfo = async (libraryInfo) => {
  *      name: "lib.x",
  *      version: "1.0.0",
  *      libraryManifest: module:@ui5/fs.Resource,
- *      manifestResources: module:@ui5/fs.Resource[]
+ *      embeddedManifests: module:@ui5/fs.Resource[]
  *   }
  * 
* @returns {Promise} Promise resolving with an array containing the versionInfo resource @@ -416,7 +433,7 @@ module.exports = async function({options}) { const dependencyInfoMap = new Map(); - // gather all manifestHints + // process library infos const librariesPromises = options.libraryInfos.map((libraryInfo) => { return processLibraryInfo(libraryInfo); }); diff --git a/lib/tasks/generateVersionInfo.js b/lib/tasks/generateVersionInfo.js index 24f8b5ca0..03e34e309 100644 --- a/lib/tasks/generateVersionInfo.js +++ b/lib/tasks/generateVersionInfo.js @@ -30,7 +30,7 @@ module.exports = async ({workspace, dependencies, options: {rootProject, pattern manifestResources.filter((manifestResource) => manifestResource !== libraryManifest); return { libraryManifest, - manifestResources: embeddedManifests, + embeddedManifests, name: dotLibResource._project.metadata.name, version: dotLibResource._project.version }; diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js index 3b94de68b..a5b25ba81 100644 --- a/test/lib/processors/versionInfoGenerator.js +++ b/test/lib/processors/versionInfoGenerator.js @@ -14,8 +14,12 @@ test("versionInfoGenerator missing parameters", async (t) => { test.beforeEach((t) => { t.context.warnLogStub = sinon.stub(); + t.context.infoLogStub = sinon.stub(); + t.context.verboseLogStub = sinon.stub(); sinon.stub(logger, "getLogger").returns({ warn: t.context.warnLogStub, + info: t.context.infoLogStub, + verbose: t.context.verboseLogStub, isLevelEnabled: () => true }); versionInfoGenerator = mock.reRequire("../../../lib/processors/versionInfoGenerator"); @@ -85,3 +89,232 @@ test.serial("versionInfoGenerator simple library infos", async (t) => { t.is(t.context.warnLogStub.getCall(0).args[0], "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); }); + +test.serial("versionInfoGenerator library infos with dependencies", async (t) => { + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "my.dep": { + "minVersion": "1.84.0" + } + } + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + const myDepManifest = { + getPath: () => { + return "/resources/my/dep/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "my.dep", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const myDep = {name: "my.dep", version: "1.2.3", libraryManifest: myDepManifest}; + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA, myDep + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "my.dep": {} + } + } + } + }, + { + "name": "my.dep", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with embeds", async (t) => { + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": ["sub"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const subManifest = { + getPath: () => { + return "/resources/lib/a/sub/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a.sub", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest, embeddedManifests: [subManifest]}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ], + "components": { + "lib.a.sub": { + "library": "lib.a" + } + } + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with embeds and embeddedBy (hasOwnPreload)", async (t) => { + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": ["sub"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const subManifest = { + getPath: () => { + return "/resources/lib/a/sub/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a.sub", + "embeds": [], + "embeddedBy": "../" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest, embeddedManifests: [subManifest]}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ], + "components": { + "lib.a.sub": { + "hasOwnPreload": true, + "library": "lib.a" + } + } + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); From fd6a8e9075b97f0afd38ae3895cedb716ef59987 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Fri, 22 Jan 2021 13:12:44 +0100 Subject: [PATCH 38/41] improve code use typedef for ArtifactInfo instead of class because functionality could be extracted and it served then only as a data container. replaced Object.keys().forEach loops which Object.entries loop Use getResolvedLibraries for directly resolving transitive dependencies --- lib/processors/versionInfoGenerator.js | 162 ++++++-------------- test/lib/processors/versionInfoGenerator.js | 3 +- 2 files changed, 51 insertions(+), 114 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 4fbb42ec7..b30918689 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -60,12 +60,12 @@ const processManifest = async (manifestResource) => { const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; if (manifestDependencies) { const libs = {}; - Object.keys(manifestDependencies.libs).forEach((libKey) => { + for (const [libKey, libValue] of Object.entries(manifestDependencies.libs)) { libs[libKey] = {}; - if (manifestDependencies.libs[libKey].lazy) { + if (libValue.lazy) { libs[libKey].lazy = true; } - }); + } manifestInfo.libs = libs; } } @@ -140,8 +140,8 @@ const getManifestPath = (filePath, subPath) => { /** * Represents dependency information for a library. - * Dependencies can be resolved recursively using #resolve - * and are stored then in libsResolved + * Dependencies can be retrieved using #getResolvedLibraries + * and with that are resolved recursively */ class DependencyInfo { /** @@ -152,14 +152,6 @@ class DependencyInfo { constructor(libs, name) { this.libs = libs; this.name = name; - - /** - * contains as key the library name and as value an object with an optional lazy property - * - * @type {ManifestLibraries} - */ - this.libsResolved = Object.create(null); - this.wasResolved = false; } /** @@ -171,13 +163,13 @@ class DependencyInfo { * @returns {{lazy: boolean}} the added library */ addResolvedLibDependency(libName, lazy) { - let alreadyResolved = this.libsResolved[libName]; + let alreadyResolved = this._libsResolved[libName]; if (!alreadyResolved) { alreadyResolved = Object.create(null); if (lazy) { alreadyResolved.lazy = true; } - this.libsResolved[libName] = alreadyResolved; + this._libsResolved[libName] = alreadyResolved; } else { // siblings if sibling is eager only if one other sibling eager alreadyResolved.lazy = alreadyResolved.lazy && lazy; @@ -186,38 +178,38 @@ class DependencyInfo { } /** - * Resolves dependencies recursively and stores them in libsResolved - * with + * Resolves dependencies recursively and retrieves them with * - resolved siblings a lazy and a eager dependency becomes eager * - resolved children become lazy if their parent is lazy * * @param {Map} dependencyInfoMap + * @returns {ManifestLibraries} resolved libraries */ - resolve(dependencyInfoMap) { - if (!this.wasResolved) { + getResolvedLibraries(dependencyInfoMap) { + if (!this._libsResolved) { // early set if there is a potential cycle - this.wasResolved = true; - Object.keys(this.libs).forEach((libName) => { - const lazy = this.libs[libName].lazy; + this._libsResolved = Object.create(null); + for (const [libName, libValue] of Object.entries(this.libs)) { + const lazy = libValue.lazy; const dependencyInfoObjectAdded = this.addResolvedLibDependency(libName, lazy); const dependencyInfo = dependencyInfoMap.get(libName); if (dependencyInfo) { - dependencyInfo.resolve(dependencyInfoMap); + const childLibsResolved = dependencyInfo.getResolvedLibraries(dependencyInfoMap); // children if parent is lazy children become lazy - Object.keys(dependencyInfo.libsResolved).forEach((resolvedLibName) => { - const resolvedLib = dependencyInfo.libsResolved[resolvedLibName]; + for (const [resolvedLibName, resolvedLib] of Object.entries(childLibsResolved)) { this.addResolvedLibDependency(resolvedLibName, resolvedLib.lazy || dependencyInfoObjectAdded.lazy); - }); + } } else { log.info(`Cannot find dependency '${libName}' `+ `defined in the manifest.json or .library file of project '${this.name}'. ` + "This might prevent some UI5 runtime performance optimizations from taking effect. " + "Please double check your project's dependency configuration."); } - }); + } } + return this._libsResolved; } } @@ -226,7 +218,7 @@ class DependencyInfo { * Sorts the keys of a given object * * @param {object} obj the object - * @returns {{}} the object with sorted keys + * @returns {object} the object with sorted keys */ const sortObjectKeys = (obj) => { const sortedObject = {}; @@ -242,78 +234,33 @@ const sortObjectKeys = (obj) => { * Builds the manifestHints object from the dependencyInfo * * @param {DependencyInfo} dependencyInfo + * @param {Map} dependencyInfoMap * @returns {{dependencies: {libs: ManifestLibraries}}} manifestHints */ -const getManifestHints = (dependencyInfo) => { - if (dependencyInfo && Object.keys(dependencyInfo.libsResolved).length) { - return { - dependencies: { - libs: sortObjectKeys(dependencyInfo.libsResolved) - } - }; +const getManifestHints = (dependencyInfo, dependencyInfoMap) => { + if (dependencyInfo) { + const libsResolved = dependencyInfo.getResolvedLibraries(dependencyInfoMap); + if (libsResolved && Object.keys(libsResolved).length) { + return { + dependencies: { + libs: sortObjectKeys(libsResolved) + } + }; + } } }; /** * Common type for Library and Component * embeds and bundled components makes only sense for library + * + * @typedef {object} ArtifactInfo + * @property {string} componentName The library name, e.g. "lib.x" + * @property {Set} bundledComponents The embedded components which have a embeddedBy reference to the library + * @property {DependencyInfo} dependencyInfo The dependency info object + * @property {ArtifactInfo[]} embeds The embedded artifact infos */ -class ArtifactInfo { - /** - * @param {string} componentName e.g. lib.x - */ - constructor(componentName) { - this.componentName = componentName; - this.artifactInfos = []; - this.parentBundledComponents = new Set(); - } - - /** - * - * @param {DependencyInfo} dependencyInfo - */ - setDependencyInfo(dependencyInfo) { - this.dependencyInfo = dependencyInfo; - } - /** - * The embedded components which have a embeddedBy reference to the library - * - * @param {Set} bundledComponents e.g. ["lib.x.sub"] - */ - setBundledComponents(bundledComponents) { - this.bundledComponents = bundledComponents; - } - - /** - * Set the embedded components of the library - * - * @param {ArtifactInfo[]} artifactInfos embedded components - */ - setEmbeds(artifactInfos) { - this.artifactInfos = artifactInfos; - this.artifactInfos.forEach((artifactInfo) => { - artifactInfo._setParent(this); - }); - } - - /** - * @returns {ArtifactInfo[]} get embedded components of this library - */ - getEmbeds() { - return this.artifactInfos; - } - - /** - * @param {ArtifactInfo} parent set the parent library - * @private - */ - _setParent(parent) { - this.parent = parent; - this.parentBundledComponents = this.parent.bundledComponents; - this.parentComponentName = this.parent.componentName; - } -} /** * Processes the manifest and creates a ManifestInfo and an ArtifactInfo. @@ -325,8 +272,9 @@ class ArtifactInfo { async function processManifestAndGetArtifactInfo(libraryManifest, name) { const manifestInfo = await processManifest(libraryManifest); name = name || manifestInfo.id; - const libraryArtifactInfo = new ArtifactInfo(name); - libraryArtifactInfo.setDependencyInfo(new DependencyInfo(manifestInfo.libs, name)); + const libraryArtifactInfo = Object.create(null); + libraryArtifactInfo.componentName = name; + libraryArtifactInfo.dependencyInfo = new DependencyInfo(manifestInfo.libs, name); return {manifestInfo, libraryArtifactInfo}; } @@ -347,7 +295,7 @@ const processLibraryInfo = async (libraryInfo) => { await processManifestAndGetArtifactInfo(libraryInfo.libraryManifest, libraryInfo.name); const bundledComponents = new Set(); - libraryArtifactInfo.setBundledComponents(bundledComponents); + libraryArtifactInfo.bundledComponents = bundledComponents; const embeds = manifestInfo.embeds; // e.g. ["sub"] // filter only embedded manifests @@ -378,7 +326,7 @@ const processLibraryInfo = async (libraryInfo) => { }); const embeddedArtifactInfos = await Promise.all(embeddedManifestPromises); - libraryArtifactInfo.setEmbeds(embeddedArtifactInfos); + libraryArtifactInfo.embeds = embeddedArtifactInfos; return libraryArtifactInfo; }; @@ -434,11 +382,11 @@ module.exports = async function({options}) { // process library infos - const librariesPromises = options.libraryInfos.map((libraryInfo) => { + const libraryInfosProcessPromises = options.libraryInfos.map((libraryInfo) => { return processLibraryInfo(libraryInfo); }); - let artifactInfos = await Promise.all(librariesPromises); + let artifactInfos = await Promise.all(libraryInfosProcessPromises); artifactInfos = artifactInfos.filter(Boolean); // fill dependencyInfoMap @@ -446,17 +394,6 @@ module.exports = async function({options}) { dependencyInfoMap.set(artifactInfo.componentName, artifactInfo.dependencyInfo); }); - // resolve library dependencies (transitive) - dependencyInfoMap.forEach((dependencyInfo) => { - dependencyInfo.resolve(dependencyInfoMap); - }); - - // resolve dependencies of embedded components - artifactInfos.forEach((artifactInfo) => { - artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { - embeddedArtifactInfo.dependencyInfo.resolve(dependencyInfoMap); - }); - }); const libraries = options.libraryInfos.map((libraryInfo) => { const library = { @@ -467,7 +404,7 @@ module.exports = async function({options}) { }; const dependencyInfo = dependencyInfoMap.get(libraryInfo.name); - const manifestHints = getManifestHints(dependencyInfo); + const manifestHints = getManifestHints(dependencyInfo, dependencyInfoMap); if (manifestHints) { library.manifestHints = manifestHints; } @@ -482,17 +419,16 @@ module.exports = async function({options}) { // components let components; artifactInfos.forEach((artifactInfo) => { - artifactInfo.getEmbeds().forEach((embeddedArtifactInfo) => { + artifactInfo.embeds.forEach((embeddedArtifactInfo) => { const componentObject = { - library: embeddedArtifactInfo.parentComponentName + library: artifactInfo.componentName }; const componentName = embeddedArtifactInfo.componentName; - const dependencyInfo = embeddedArtifactInfo.dependencyInfo; - const manifestHints = getManifestHints(dependencyInfo); + const manifestHints = getManifestHints(embeddedArtifactInfo.dependencyInfo, dependencyInfoMap); if (manifestHints) { componentObject.manifestHints = manifestHints; } - const bundledComponents = embeddedArtifactInfo.parentBundledComponents; + const bundledComponents = artifactInfo.bundledComponents; if (bundledComponents.has(componentName)) { componentObject.hasOwnPreload = true; } diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js index a5b25ba81..d85aff3e1 100644 --- a/test/lib/processors/versionInfoGenerator.js +++ b/test/lib/processors/versionInfoGenerator.js @@ -106,7 +106,8 @@ test.serial("versionInfoGenerator library infos with dependencies", async (t) => "minUI5Version": "1.84", "libs": { "my.dep": { - "minVersion": "1.84.0" + "minVersion": "1.84.0", + "lazy": false } } } From 8eeab50ab781e19ca0264ccf28a01c3d4eb2b72e Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 26 Jan 2021 10:40:22 +0100 Subject: [PATCH 39/41] secure looping by checking libs existence in manifest simplify manifest path resolving --- lib/processors/versionInfoGenerator.js | 8 ++-- test/lib/processors/versionInfoGenerator.js | 47 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index b30918689..4d66d14a1 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -58,7 +58,7 @@ const processManifest = async (manifestResource) => { // sap.ui5/dependencies is used for the "manifestHints/libs" if (manifestObject["sap.ui5"]) { const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; - if (manifestDependencies) { + if (manifestDependencies && manifestDependencies.libs) { const libs = {}; for (const [libKey, libValue] of Object.entries(manifestDependencies.libs)) { libs[libKey] = {}; @@ -134,8 +134,7 @@ const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { * @returns {string} manifest path, e.g. "lib/x/sub/manifest.json" */ const getManifestPath = (filePath, subPath) => { - const folderPathOfManifest = filePath.substr(0, filePath.length - "manifest.json".length) + subPath; - return posixPath.resolve(folderPathOfManifest + "/manifest.json"); + return posixPath.resolve(posixPath.dirname(filePath), subPath, "manifest.json"); }; /** @@ -189,6 +188,9 @@ class DependencyInfo { if (!this._libsResolved) { // early set if there is a potential cycle this._libsResolved = Object.create(null); + if (!this.libs) { + return this._libsResolved; + } for (const [libName, libValue] of Object.entries(this.libs)) { const lazy = libValue.lazy; const dependencyInfoObjectAdded = this.addResolvedLibDependency(libName, lazy); diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js index d85aff3e1..67ba49efc 100644 --- a/test/lib/processors/versionInfoGenerator.js +++ b/test/lib/processors/versionInfoGenerator.js @@ -90,6 +90,53 @@ test.serial("versionInfoGenerator simple library infos", async (t) => { "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); }); +test.serial("versionInfoGenerator manifest without libs", async (t) => { + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84" + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + test.serial("versionInfoGenerator library infos with dependencies", async (t) => { const libAManifest = { getPath: () => { From 42ce003558ff447c32cfc5502f661fbe771a6235 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 26 Jan 2021 18:31:40 +0100 Subject: [PATCH 40/41] Change logging for missing libraryManifest to verbose With regards to backward compatibility the libraryManifest can be missing. When --all is omitted the libraryManifest can also be missing. --- lib/processors/versionInfoGenerator.js | 2 +- test/lib/processors/versionInfoGenerator.js | 4 ++-- test/lib/tasks/generateVersionInfo.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 4d66d14a1..25f176ce1 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -288,7 +288,7 @@ async function processManifestAndGetArtifactInfo(libraryManifest, name) { */ const processLibraryInfo = async (libraryInfo) => { if (!libraryInfo.libraryManifest) { - log.warn( + log.verbose( `Cannot add meta information for library '${libraryInfo.name}'. The manifest.json file cannot be found`); return; } diff --git a/test/lib/processors/versionInfoGenerator.js b/test/lib/processors/versionInfoGenerator.js index 67ba49efc..6f09b14f5 100644 --- a/test/lib/processors/versionInfoGenerator.js +++ b/test/lib/processors/versionInfoGenerator.js @@ -85,8 +85,8 @@ test.serial("versionInfoGenerator simple library infos", async (t) => { ] }; assertVersionInfoContent(t, oExpected, result); - t.is(t.context.warnLogStub.callCount, 1); - t.is(t.context.warnLogStub.getCall(0).args[0], + t.is(t.context.verboseLogStub.callCount, 1); + t.is(t.context.verboseLogStub.getCall(0).args[0], "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); }); diff --git a/test/lib/tasks/generateVersionInfo.js b/test/lib/tasks/generateVersionInfo.js index a524809c3..4d8984363 100644 --- a/test/lib/tasks/generateVersionInfo.js +++ b/test/lib/tasks/generateVersionInfo.js @@ -146,8 +146,8 @@ test.serial("integration: Library without i18n bundle file", async (t) => { "version": "1.33.7", }, oOptions); - t.is(t.context.warnLogStub.callCount, 1); - t.is(t.context.warnLogStub.getCall(0).args[0], + t.is(t.context.verboseLogStub.callCount, 1); + t.is(t.context.verboseLogStub.getCall(0).args[0], "Cannot add meta information for library 'test.lib3'. The manifest.json file cannot be found"); }); From daf56107051850c3398a09780198d63f6bfbbc86 Mon Sep 17 00:00:00 2001 From: Tobias Sorn Date: Tue, 26 Jan 2021 18:35:57 +0100 Subject: [PATCH 41/41] make LibraryInfo typedef public It is used in the versionInfoGenerator's public JSDoc. --- lib/processors/versionInfoGenerator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/processors/versionInfoGenerator.js b/lib/processors/versionInfoGenerator.js index 25f176ce1..052e65e8a 100644 --- a/lib/processors/versionInfoGenerator.js +++ b/lib/processors/versionInfoGenerator.js @@ -345,6 +345,7 @@ const processLibraryInfo = async (libraryInfo) => { * e.g. resource with path "lib/x/manifest.json" * @property {module:@ui5/fs.Resource[]} embeddedManifests list of embedded manifest resources, * e.g. resource with path "lib/x/sub/manifest.json" + * @public */ /**