From 2f1d9e34d09b0b75a7a0012f7413dac0fe61574e Mon Sep 17 00:00:00 2001 From: Burntgogi <224273819+Burntgogi@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:26:41 +0900 Subject: [PATCH 1/2] fix: retain inputs inside multiline Rust attributes --- docs/contrib/local-check.md | 3 +++ tools/recipes.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/contrib/local-check.md b/docs/contrib/local-check.md index e55f35538..a7efd6e6d 100644 --- a/docs/contrib/local-check.md +++ b/docs/contrib/local-check.md @@ -95,6 +95,9 @@ The flowcounts weekly reads its record parser and schema, routing data and stati Use `python3 tools/recipes.py why tools/bloomery/hf.py --recipe weekly-gpu-ds41-flowcounts` to inspect that boundary. The script walk is conservative: a helper's prose must not turn the self-tests that use it into dependencies of the helper itself. The regression holds both the absent hub input and the retained executable inputs. +Includes and repository-path literals on inner lines of a multiline Rust attribute are inputs too. +The recipe self-test checks that changing either selects the compiling recipes; attribute contents +must not bypass input discovery while the module/`cfg` scanner skips their syntax. The gate-batch self-test waits for stopped fixture lanes and their registered items to finish before a case can remove its tree. A delayed TERM-handler case diff --git a/tools/recipes.py b/tools/recipes.py index 30ff8aea6..87840ff4c 100755 --- a/tools/recipes.py +++ b/tools/recipes.py @@ -1245,11 +1245,8 @@ def _walk(self, rel: str, moddir: str, acc: ModuleTree, test: bool, modpath: tup s = line.strip() if not s or s.startswith("//"): continue - if attr_depth > 0: - attr_depth += s.count("[") - s.count("]") - continue indent = len(line) - len(line.lstrip()) - if s == "}" and inline and inline[-1][0] == indent: + if attr_depth == 0 and s == "}" and inline and inline[-1][0] == indent: inline.pop() continue here = inline[-1][2] if inline else test @@ -1267,6 +1264,9 @@ def _walk(self, rel: str, moddir: str, acc: ModuleTree, test: bool, modpath: tup (acc.test_files if here else acc.files).add(p) else: self.unresolved.append(f"{rel}: include of {inc} ({p} not found)") + if attr_depth > 0: + attr_depth += s.count("[") - s.count("]") + continue # `#[cfg(test)] mod x;`, `#[path = "x.rs"] mod x;`, `#[cfg(test)] #[path = "x.rs"]`: each # one-line attribute counts as if on a line of its own, then the item after them is read attrs, rest = self.lead_attrs(s) @@ -9630,12 +9630,14 @@ def module_gate_self_test(expect) -> None: '#[cfg(all(feature = "fy", feature = "fz"))]\n#[allow(dead_code)]\nmod inl {\n mod deep;\n' ' const D: &str = include_str!("../data/d.txt");\n}\n#[cfg(any(test, feature = "fy"))]\nmod either;\n' ), - "crates/g/src/plain.rs": "", + "crates/g/src/plain.rs": '#[doc = concat!(\n include_str!("../../../attribute.txt"),\n "tools/attribute-data.txt"\n)]\npub struct Plain;\n', "crates/g/src/gated.rs": "", "crates/g/src/off.rs": "", "crates/g/src/inl/deep.rs": "", "crates/g/src/either.rs": "", "crates/g/data/d.txt": "", + "attribute.txt": "attribute1\n", + "tools/attribute-data.txt": "literal1\n", "crates/g/src/bin/x.rs": '#[cfg(feature = "fy")]\nmod xg;\nfn main() {}\n', "crates/g/src/bin/xg.rs": "", "crates/g/src/bin/y.rs": "fn main() {}\n", @@ -9661,6 +9663,8 @@ def module_gate_self_test(expect) -> None: fy = {"gate-xy", "gate-yz", "gate-liby", "gate-all"} | whole cases = [ ("an ungated module", "plain.rs", every), + ("an include on an inner attribute line", "../../../attribute.txt", every), + ("a path literal on an inner attribute line", "../../../tools/attribute-data.txt", every), ("a module under feature = fy", "gated.rs", fy), ("a module under not(feature = fy)", "off.rs", {"gate-x", "gate-lib"} | whole), ("a module under any(test, feature = fy), which a test build compiles whatever the features", "either.rs", every), From abc846f8bf382bc95214dbd7a556962515ee0d05 Mon Sep 17 00:00:00 2001 From: Burntgogi <224273819+Burntgogi@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:51:59 +0900 Subject: [PATCH 2/2] test: preserve test-only inputs across attribute braces --- tools/recipes.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/recipes.py b/tools/recipes.py index 87840ff4c..1ce788a1d 100755 --- a/tools/recipes.py +++ b/tools/recipes.py @@ -9629,6 +9629,11 @@ def module_gate_self_test(expect) -> None: 'mod plain;\n#[cfg(feature = "fy")]\nmod gated;\n#[cfg(not(feature = "fy"))]\nmod off;\n' '#[cfg(all(feature = "fy", feature = "fz"))]\n#[allow(dead_code)]\nmod inl {\n mod deep;\n' ' const D: &str = include_str!("../data/d.txt");\n}\n#[cfg(any(test, feature = "fy"))]\nmod either;\n' + '#[cfg(test)]\nmod attr_test {\n' + ' #[doc = concat!(stringify!({\n}\n ),\n' + ' include_str!("../../../attribute-test.txt"),\n' + ' "tools/attribute-test-data.txt"\n )]\n' + ' pub struct Item;\n const AFTER: &str = include_str!("../../../after-attribute.txt");\n}\n' ), "crates/g/src/plain.rs": '#[doc = concat!(\n include_str!("../../../attribute.txt"),\n "tools/attribute-data.txt"\n)]\npub struct Plain;\n', "crates/g/src/gated.rs": "", @@ -9638,6 +9643,9 @@ def module_gate_self_test(expect) -> None: "crates/g/data/d.txt": "", "attribute.txt": "attribute1\n", "tools/attribute-data.txt": "literal1\n", + "attribute-test.txt": "test attribute\n", + "tools/attribute-test-data.txt": "test literal\n", + "after-attribute.txt": "after attribute\n", "crates/g/src/bin/x.rs": '#[cfg(feature = "fy")]\nmod xg;\nfn main() {}\n', "crates/g/src/bin/xg.rs": "", "crates/g/src/bin/y.rs": "fn main() {}\n", @@ -9677,6 +9685,11 @@ def module_gate_self_test(expect) -> None: sels, _, _ = select([os.path.normpath(os.path.join("crates/g/src", rel))], side, side, GATE_PREFIX) got = {x.recipe for x in sels} expect(got == want, f"module gate: {what} selects {sorted(got)}, not {sorted(want)}") + for rel in ("attribute-test.txt", "tools/attribute-test-data.txt", "after-attribute.txt"): + sels, _, _ = select([rel], side, side, GATE_PREFIX) + got = {x.recipe for x in sels} - whole + expect(got == {"gate-lib", "gate-liby", "gate-all"}, + f"module gate: an attribute brace must leave {rel} test-only, selected {sorted(got)}") expect(not side.tree.unresolved, f"module gate: unresolved {side.tree.unresolved[:2]}")