diff options
| author | hathach <[email protected]> | 2026-08-21 23:14:25 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-08-21 23:14:25 +0700 |
| commit | 6ff0ef97702c0e6b6d17b7a8fe856b31164efe57 (patch) | |
| tree | ae456b01bc223be69db574be753c191c92abb83a /tools | |
| parent | 03a329eeda09e073d7de9be84df55d65392e4013 (diff) | |
build_utils: key the caches on the tree, not just the argumentsclaude/ci-select-rule17
The eight lru_cache layers take repo-RELATIVE paths - 'hw/bsp/<fam>',
'examples/<ex>/skip.txt', the literal 'hw/bsp' glob - while ci_select._in_repo()
chdirs around every call so one process can classify more than one tree. With no
cwd in the key the second tree gets the first tree's answers.
Reproduced: skip_example('host/bare_api','metro_m0_express') is False at the repo
root and STILL False after chdir into a tree where that board does not exist; only
cache_clear() gave the right answer. It bites the code-size skill's base-vs-branch
worktree compare, /pre-pr, and the first test that points classify_build at a
fixture tree. Master had no caching here, so the hazard arrived with it.
_cwd_cache puts os.getcwd() in the key. The 199-test suite passed before only
because every test happens to pass the real REPO; the new
TestCachesAreKeyedOnTheTree crosses trees deliberately.
Also adds the drift guard the class rule was missing. Ports, hw/mcu, get_deps
tokens and bsp families each have one; the class rule had only a comment claiming
vendor_host.c was the sole "enabled by no example config" case until its removal -
which src/class/bth falsifies today. TestClassesWithNoEnablingExample pins the set
to {bth}, so a class added before its first example, or an example config flipped
to 0, fails here instead of silently selecting nothing on both axes. Verified it
fires by adding a class dir nothing enables.
Diffstat (limited to 'tools')
| -rwxr-xr-x | tools/build_utils.py | 40 |
1 files changed, 32 insertions, 8 deletions
diff --git a/tools/build_utils.py b/tools/build_utils.py index 1eeef0269..1b81335e0 100755 --- a/tools/build_utils.py +++ b/tools/build_utils.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import functools +import os import subprocess import pathlib import re @@ -24,7 +25,30 @@ _CMAKE_VAR_RE = re.compile(r'\$\{([A-Za-z_]\w*)\}') _CMAKE_CASE_RE = re.compile(r'string\s*\(\s*(TOUPPER|TOLOWER)\s+(\S+)\s+([A-Za-z_]\w*)\s*\)') [email protected]_cache(maxsize=None) + +def _cwd_cache(fn): + """lru_cache, keyed on the working directory as well as the arguments. + + Every cached helper below takes repo-RELATIVE paths ('hw/bsp/<fam>', + 'examples/<ex>/skip.txt', or the literal 'hw/bsp' glob), while ci_select._in_repo() + chdirs around each call so one process can classify more than one tree - the + code-size skill's base-vs-branch worktrees, /pre-pr, a test pointing at a fixture. + Without the cwd in the key the second tree silently gets the first tree's + skip.txt/only.txt and FAMILY_MCUS answers. Master had no caching here, so this + hazard arrived with it.""" + cache = {} + + @functools.wraps(fn) + def wrapper(*args): + key = (os.getcwd(), args) + if key not in cache: + cache[key] = fn(*args) + return cache[key] + + wrapper.cache_clear = cache.clear + return wrapper + +@_cwd_cache def _cmake_sets(path): """One cmake file's variable assignments as NAME -> first definition seen, as either a literal value or an ('TOUPPER'|'TOLOWER', source) pair. Only used to @@ -87,7 +111,7 @@ def _cmake_expand(value, files, depth=0): return None if '${' in out else out [email protected]_cache(maxsize=None) +@_cwd_cache def _board_dirs(board): """(board_dir, family_dir) for a board name, or (None, None). Cached: skip_example is asked (board x example) times - 566k lstat calls per selector run without this, @@ -98,7 +122,7 @@ def _board_dirs(board): return hits[0], hits[0].parent.parent [email protected]_cache(maxsize=None) +@_cwd_cache def _family_mcus(family_dir, board_dir): """The MCU names CMake's family_filter iterates. family_support.cmake:176/190 loop `foreach(MCU IN LISTS FAMILY_MCUS)`, so a family-wide list (broadcom_64bit @@ -175,7 +199,7 @@ def _family_mcus(family_dir, board_dir): return frozenset(out) [email protected]_cache(maxsize=None) +@_cwd_cache def _scrape_mcu(family_dir, board_dir, family): """(CFG_TUSB_MCU token of this board, the text it was read from), master's algorithm verbatim: family.mk (family.cmake when there is none) first, falling @@ -215,7 +239,7 @@ def _scrape_mcu(family_dir, board_dir, family): return mcu, mk_contents [email protected]_cache(maxsize=None) +@_cwd_cache def _board_mcu(board_dir, family_dir, family): """(CFG_TUSB_MCU of this board, MAX3421_HOST enabled by its cmake BSP). @@ -254,7 +278,7 @@ def _board_mcu(board_dir, family_dir, family): return mcu, max3421_enabled [email protected]_cache(maxsize=None) +@_cwd_cache def _filter_tokens(path): """skip.txt / only.txt as a token set, or None when the file does not exist.""" f = pathlib.Path(path) @@ -285,7 +309,7 @@ def skip_example(example, board, extra_defines=(), build_system='cmake'): return _skip_example(example, board, tuple(extra_defines), build_system) [email protected]_cache(maxsize=None) +@_cwd_cache def _skip_example_make(example, board): """master's skip_example, verbatim (tools/build_utils.py @ 9c202e8c6): the make build's own answer, derived from family.mk/board.mk with the single @@ -333,7 +357,7 @@ def _skip_example_make(example, board): return False [email protected]_cache(maxsize=None) +@_cwd_cache def _skip_example(example, board, extra_defines, build_system): if build_system == 'make': return _skip_example_make(example, board) |
