UI5 / UI5/cli

Build cache: buildThemes cache is invalidated by irrelevant library.js / .library content changes

Open
#1,523 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

module/ui5-builder module/ui5-fs module/ui5-project
Dominant language
JavaScript
Stars
511
Forks
83
Avg merge
1d 5h
Merged PRs (30d)
55

Description

Expected Behavior

The buildThemes task should only be re-executed by the incremental (delta) build system
when an input that actually affects its output changes (e.g. a library.source.less file, or
a .library / library.js file being added or removed, which changes the set of
directories a theme is built for).

A change to the content of a .library or library.js file — while the file continues to
exist in the same directory — must not invalidate the buildThemes cache, because the task
does not read the content of these files.

Current Behavior

The buildThemes task is re-executed on any content change to a .library or library.js
file within the project (or its dependencies), even though the theme output cannot possibly
differ.

Root cause: For non-root library projects, buildThemes is configured with a
librariesPattern glob (packages/project/lib/build/definitions/library.js and
themeLibrary.js):

librariesPattern: !taskUtil.isRootProject() ? "/resources/**/(*.library|library.js)" : undefined,

The task requests these files via combo.byGlob(librariesPattern)
(packages/builder/lib/tasks/buildThemes.js) and uses them only to derive the set of
directories that contain a library
:

(await pAvailableLibraries).forEach((resource) => {
    const library = path.dirname(resource.getPath()); // only the PATH is used
    if (!availableLibraries.includes(library)) {
        availableLibraries.push(library);
    }
});

The resource content is never read. However, the incremental build cache tracks every
resource a task requests by its content hash (integrity), so the buildThemes stage
signature incorporates the content of every matched .library / library.js file. Any content
change to such a file therefore changes the stage signature and forces buildThemes to
re-execute — a needless cache miss that slows down incremental rebuilds in watch/serve mode.

Note: The over-tracking is by content hash, so a change that is later stripped (e.g. a JS
comment removed by minify, whose output buildThemes reads) does not trigger the rebuild.
Only changes that survive into the workspace buildThemes reads (e.g. a changed return value in
library.js, or any content change in the non-minified .library) cause the spurious
re-execution.

Steps to Reproduce the Issue

  1. Serve an application with a non-root library dependency that has a theme
    (fixture application.alibrary.a, which contains
    themes/base/library.source.less).
  2. Add a library.js next to the library's .library file and warm the build cache by
    requesting a resource.
  3. Change only the content of library.js in a way that survives minification (e.g. change
    a returned value), and request a theme resource
    (/resources/library/a/themes/base/library.css).
  4. Observe the emitted ui5.project-build-status events for library.a: buildThemes fires
    task-start / task-end (re-executed) instead of task-skip.

The following integration test (using the BuildServer harness with a mocked @parcel/watcher)
reproduces the issue deterministically, independent of the OS-level file watcher:

test.serial("Serve application.a, library.js content change must not re-run buildThemes", async (t) => {
	const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a");

	await fixtureTester.serveProject();

	// Add a library.js to library.a. It is matched by the buildThemes `librariesPattern` glob, but
	// buildThemes uses only its path, not its content.
	const libraryJsPath =
		`${fixtureTester.fixturePath}/node_modules/collection/library.a/src/library/a/library.js`;
	await fs.writeFile(libraryJsPath, `sap.ui.define([], function() {\n\t"use strict";\n\treturn 1;\n});\n`);
	await fixtureTester.fireWatcherEvent("create", libraryJsPath);

	// #1 initial request builds library.a (and its dependencies), warming the cache. Request the
	// theme output itself so it is unambiguous that the later request (#3) is a rebuild of an
	// already-built resource rather than a first build.
	await fixtureTester.requestResource({resource: "/resources/library/a/themes/base/library.css"});

	// #2 request again with a warm cache — nothing rebuilds.
	await fixtureTester.requestResource({
		resource: "/resources/library/a/themes/base/library.css",
		assertions: {projects: {}},
	});

	// Change ONLY the content of library.js — buildThemes never reads this content. Use a change
	// that survives minification (a returned value) so the resource buildThemes tracks really differs.
	await fs.writeFile(libraryJsPath, `sap.ui.define([], function() {\n\t"use strict";\n\treturn 42;\n});\n`);
	await fixtureTester.fireWatcherEvent("update", libraryJsPath);

	// #3 rebuild after the change. buildThemes must be skipped for library.a — the changed file's
	// content is irrelevant to it. This assertion FAILS today: buildThemes is missing from the
	// skipped set because it is spuriously re-executed.
	await fixtureTester.requestResource({
		resource: "/resources/library/a/themes/base/library.css",
		assertions: {
			projects: {
				"library.a": {
					skippedTasks: [
						"buildThemes",
						"enhanceManifest",
						"escapeNonAsciiCharacters",
						"replaceBuildtime",
					]
				}
			}
		}
	});
});

To run it:

npm ci --engine-strict
npx ava test/lib/build/BuildServer.integration.js \
  --match="*library.js content change must not re-run buildThemes*"

(run from packages/project)

Context

  • UI5 Module Version (output of ui5 --version when using the CLI): 5.0.0-alpha.7
  • Node.js Version: 26.5.0
  • npm Version: 11.19.0
  • OS/Platform: macOS 26.6.1 (darwin)
  • Browser (if relevant): unknown
  • Other information regarding your environment (optional): Reproduced via the
    BuildServer integration test harness (mocked @parcel/watcher), so it is independent of
    the OS-level file watcher.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/builder/lib/tasks/buildThemes.js, especially its librariesPattern and combo.byGlob usage, then run the named integration test in packages/project/test/lib/build/BuildServer.integration.js. Trace how requested resources contribute to the incremental stage signature. Done means the test passes and content-only changes to .library or library.js skip buildThemes while additions or removals still invalidate it.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
build-system
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.