Skip to content

Commit 70c262b

Browse files
netmindzwillmmiles
authored andcommitted
IDF fixes
1 parent e3c93f2 commit 70c262b

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

pio-scripts/fix_nodelist.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
Import("env")
2+
# ── fix_nodelist.py ───────────────────────────────────────────────────────────
3+
# Pre-build monkey-patch that prevents a clean-build crash in dual-framework
4+
# (arduino + espidf) PlatformIO builds.
5+
#
6+
# Root cause
7+
# ----------
8+
# platformio/builder/tools/piobuild.py::CollectBuildFiles() calls
9+
# env.File(os.path.join(_var_dir, os.path.basename(item)))
10+
# for each matched source file. When multiple VariantDir mappings overlap
11+
# (which happens with libraries that have sub-directories such as the
12+
# Arduino WebServer library's detail/, middleware/, uri/ sub-trees), SCons
13+
# can return a NodeList instead of a single Node.
14+
#
15+
# Additionally, middlewares such as arduino.py's smart_include_length_shorten
16+
# call env.Object(node) which also returns a NodeList (SCons Object builder
17+
# returns a list). When the next middleware (_skip_prj_source_files from
18+
# espidf.py) receives a NodeList, it calls node.srcnode().get_path() which
19+
# fails with:
20+
# AttributeError: 'NodeList' object has no attribute 'srcnode'
21+
# (or produces a wrong result when NodeList.__getattr__ proxies the call).
22+
#
23+
# Fix
24+
# ---
25+
# We monkey-patch CollectBuildFiles to:
26+
# 1. Flatten NodeList items produced by env.File() BEFORE the middleware loop.
27+
# 2. Flatten NodeList values returned BY middleware callbacks during the loop.
28+
# When a callback returns a NodeList, all elements pass the remaining
29+
# middlewares individually.
30+
# ─────────────────────────────────────────────────────────────────────────────
31+
32+
import fnmatch as _fnmatch
33+
import os as _os
34+
35+
from platformio.builder.tools import piobuild as _piobuild
36+
from platformio.builder.tools.piobuild import SRC_BUILD_EXT
37+
38+
39+
def _is_nodelist(node):
40+
"""Return True if node is a SCons NodeList (a UserList subclass that
41+
proxies attribute access but does not have get_path() directly)."""
42+
return hasattr(node, 'data') and not hasattr(node, 'get_path')
43+
44+
45+
def _iter_nodes(node):
46+
"""Yield individual SCons nodes from node, recursively unwrapping NodeList."""
47+
if _is_nodelist(node):
48+
for child in node.data:
49+
yield from _iter_nodes(child)
50+
else:
51+
yield node
52+
53+
54+
def _run_middlewares(env, node, middlewares):
55+
"""Run all middlewares on a single node. Returns a list of result nodes
56+
(empty if the node was filtered out, >1 if a middleware returned a NodeList).
57+
"""
58+
current_nodes = [node]
59+
for callback, pattern in middlewares:
60+
next_nodes = []
61+
for n in current_nodes:
62+
if pattern and not _fnmatch.fnmatch(n.srcnode().get_path(), pattern):
63+
next_nodes.append(n) # pattern didn't match – pass through unchanged
64+
continue
65+
if callback.__code__.co_argcount == 2:
66+
result = callback(env, n)
67+
else:
68+
result = callback(n)
69+
if result is None:
70+
pass # filtered out
71+
elif _is_nodelist(result):
72+
next_nodes.extend(_iter_nodes(result))
73+
else:
74+
next_nodes.append(result)
75+
current_nodes = next_nodes
76+
if not current_nodes:
77+
break # all nodes filtered out
78+
return current_nodes
79+
80+
81+
def _patched_CollectBuildFiles(env, variant_dir, src_dir, src_filter=None, duplicate=False):
82+
"""Drop-in replacement for piobuild.CollectBuildFiles that handles NodeList
83+
nodes both from env.File() and from middleware callbacks."""
84+
from platformio.builder.tools.piobuild import SRC_BUILD_EXT as _SRC_BUILD_EXT
85+
86+
sources = []
87+
variants = []
88+
89+
src_dir = env.subst(src_dir)
90+
if src_dir.endswith(_os.sep):
91+
src_dir = src_dir[:-1]
92+
93+
for item in env.MatchSourceFiles(src_dir, src_filter, _SRC_BUILD_EXT):
94+
_reldir = _os.path.dirname(item)
95+
_src_dir = _os.path.join(src_dir, _reldir) if _reldir else src_dir
96+
_var_dir = _os.path.join(variant_dir, _reldir) if _reldir else variant_dir
97+
98+
if _var_dir not in variants:
99+
variants.append(_var_dir)
100+
env.VariantDir(_var_dir, _src_dir, duplicate)
101+
102+
raw_node = env.File(_os.path.join(_var_dir, _os.path.basename(item)))
103+
# Flatten: a NodeList becomes individual nodes; a plain Node stays as-is
104+
for n in _iter_nodes(raw_node):
105+
sources.append(n)
106+
107+
middlewares = env.get("__PIO_BUILD_MIDDLEWARES")
108+
if not middlewares:
109+
return sources
110+
111+
new_sources = []
112+
for node in sources:
113+
for result_node in _run_middlewares(env, node, middlewares):
114+
new_sources.append(result_node)
115+
116+
return new_sources
117+
118+
119+
# Replace the function in the piobuild module so all subsequent callers
120+
# (espidf.py, etc.) pick up the patched version.
121+
_piobuild.CollectBuildFiles = _patched_CollectBuildFiles
122+
# Also replace the env-bound method so env.CollectBuildFiles() uses our version.
123+
env.AddMethod(_patched_CollectBuildFiles, "CollectBuildFiles")
124+
125+
print("[fix_nodelist] CollectBuildFiles patched – NodeList flattening enabled")

platformio.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ extra_scripts =
144144
post:pio-scripts/strip-floats.py
145145
post:pio-scripts/dynarray.py
146146
pre:pio-scripts/user_config_copy.py
147+
pre:pio-scripts/fix_nodelist.py
147148
pre:pio-scripts/load_usermods.py
148149
pre:pio-scripts/build_ui.py
149150
post:pio-scripts/fastled_cxx_workaround.py

0 commit comments

Comments
 (0)