summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-08-21 23:14:25 +0700
committerhathach <[email protected]>2026-08-21 23:14:25 +0700
commit6ff0ef97702c0e6b6d17b7a8fe856b31164efe57 (patch)
treeae456b01bc223be69db574be753c191c92abb83a /tools
parent03a329eeda09e073d7de9be84df55d65392e4013 (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-xtools/build_utils.py40
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)