mirror of
https://github.com/wled/WLED.git
synced 2026-03-13 08:29:49 +08:00
Use a custom linker section to hold dynamic arrays such as the usermod list. This provides an extensible solution for wider use in the future (such as FX or Bus drivers), as well as IDF v5 compatibility.
81 lines
3.1 KiB
Python
81 lines
3.1 KiB
Python
import re
|
|
from pathlib import Path # For OS-agnostic path manipulation
|
|
from typing import Iterable
|
|
from click import secho
|
|
from SCons.Script import Action, Exit
|
|
from platformio.builder.tools.piolib import LibBuilderBase
|
|
|
|
|
|
def is_wled_module(env, dep: LibBuilderBase) -> bool:
|
|
"""Returns true if the specified library is a wled module
|
|
"""
|
|
usermod_dir = Path(env["PROJECT_DIR"]).resolve() / "usermods"
|
|
return usermod_dir in Path(dep.src_dir).parents or str(dep.name).startswith("wled-")
|
|
|
|
|
|
def read_lines(p: Path):
|
|
""" Read in the contents of a file for analysis """
|
|
with p.open("r", encoding="utf-8", errors="ignore") as f:
|
|
return f.readlines()
|
|
|
|
|
|
def check_map_file_objects(map_file: list[str], dirs: Iterable[str]) -> set[str]:
|
|
""" Identify which dirs contributed to the final build
|
|
|
|
Returns the (sub)set of dirs that are found in the output ELF
|
|
"""
|
|
# Pattern to match symbols in object directories
|
|
# Join directories into alternation
|
|
usermod_dir_regex = "|".join([re.escape(dir) for dir in dirs])
|
|
# Matches nonzero address, any size, and any path in a matching directory
|
|
object_path_regex = re.compile(r"0x0*[1-9a-f][0-9a-f]*\s+0x[0-9a-f]+\s+\S+[/\\](" + usermod_dir_regex + r")[/\\]\S+\.o")
|
|
|
|
found = set()
|
|
for line in map_file:
|
|
matches = object_path_regex.findall(line)
|
|
for m in matches:
|
|
found.add(m)
|
|
return found
|
|
|
|
|
|
def count_usermod_objects(map_file: list[str]) -> int:
|
|
""" Returns the number of usermod objects in the usermod list """
|
|
# Count the number of entries in the usermods table section
|
|
return len([x for x in map_file if ".dynarray.usermods.1" in x])
|
|
|
|
|
|
def validate_map_file(source, target, env):
|
|
""" Validate that all modules appear in the output build """
|
|
build_dir = Path(env.subst("$BUILD_DIR"))
|
|
map_file_path = build_dir / env.subst("${PROGNAME}.map")
|
|
|
|
if not map_file_path.exists():
|
|
secho(f"ERROR: Map file not found: {map_file_path}", fg="red", err=True)
|
|
Exit(1)
|
|
|
|
# Identify the WLED module builders, set by load_usermods.py
|
|
module_lib_builders = env['WLED_MODULES']
|
|
|
|
# Extract the values we care about
|
|
modules = {Path(builder.build_dir).name: builder.name for builder in module_lib_builders}
|
|
secho(f"INFO: {len(modules)} libraries linked as WLED optional/user modules")
|
|
|
|
# Now parse the map file
|
|
map_file_contents = read_lines(map_file_path)
|
|
usermod_object_count = count_usermod_objects(map_file_contents)
|
|
secho(f"INFO: {usermod_object_count} usermod object entries")
|
|
|
|
confirmed_modules = check_map_file_objects(map_file_contents, modules.keys())
|
|
missing_modules = [modname for mdir, modname in modules.items() if mdir not in confirmed_modules]
|
|
if missing_modules:
|
|
secho(
|
|
f"ERROR: No object files from {missing_modules} found in linked output!",
|
|
fg="red",
|
|
err=True)
|
|
Exit(1)
|
|
return None
|
|
|
|
Import("env")
|
|
env.Append(LINKFLAGS=[env.subst("-Wl,--Map=${BUILD_DIR}/${PROGNAME}.map")])
|
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", Action(validate_map_file, cmdstr='Checking linked optional modules (usermods) in map file'))
|