blob: dc4aaa23084b480f73c2c271591d49bb7ab553e4 [file] [edit]
#!/usr/bin/env python3
# Copyright (c) 2025 Arduino SA
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
import pickle
from pathlib import Path
from tabulate import tabulate
KIND_TO_COL = {
"unset": "not set",
"default": "default",
"assign": "assigned",
"select": "selected",
"imply": "implied",
}
def is_cross_drive(path1: str | os.PathLike, path2: str | os.PathLike) -> bool:
"""Check if two paths are on different drives (Windows only)"""
if os.name != 'nt':
return False
# handles "C:" and UNC like "\\SERVER\SHARE"
d1 = Path(path1).drive.upper()
d2 = Path(path2).drive.upper()
return bool(d1 and d2 and d1 != d2)
def source_link(refpath, fn, ln, use_absolute):
full_path = Path(fn).absolute().as_posix().replace("_", "\\_")
if use_absolute:
return f"{full_path}:{ln}"
try:
fn = os.path.relpath(fn, refpath)
except ValueError:
# fallback to absolute path
return f"{full_path}:{ln}"
link_fn = Path(fn).as_posix()
disp_fn = link_fn.replace("../", "").replace("_", "\\_")
return f"[{disp_fn}:{ln}](<{link_fn}#L{ln}>)"
def write_markdown(trace_data, output):
sections = {"user": [], "hidden": [], "unset": []}
refpath = os.path.dirname(os.path.abspath(output))
# Check if we need to use absolute paths (cross-drive on Windows)
use_absolute = False
if os.name == 'nt' and is_cross_drive(refpath, __file__):
use_absolute = True
for sym in trace_data:
sym_name, sym_vis, sym_type, sym_value, sym_src, sym_loc = sym
if sym_vis == "n":
section = "hidden"
elif sym_src == "unset":
section = "unset"
else:
section = "user"
if section == "user":
sym_name = f"`{sym_name}`"
elif section == "hidden":
sym_name = f"`{sym_name}` (h)"
if sym_type == "string" and sym_value is not None:
sym_value = f'"{sym_value}"'.replace("_", "\\_")
if isinstance(sym_loc, tuple):
sym_loc = source_link(refpath, *sym_loc, use_absolute)
elif isinstance(sym_loc, list):
sym_loc = " || <br> ".join(f"`{loc}`" for loc in sym_loc)
sym_loc = sym_loc.replace("|", "\\|")
elif sym_loc is None and sym_src == "default":
sym_loc = "_(implicit)_"
sym_src = KIND_TO_COL[sym_src]
sections[section].append((sym_type, sym_name, sym_value, sym_src, sym_loc))
lines = []
add = lines.append
headers = ["Type", "Name", "Value", "Source", "Location"]
colaligns = ["right", "left", "right", "center", "left"]
add("\n## Visible symbols\n\n")
add(
tabulate(
sorted(sections["user"], key=lambda x: x[1]),
headers=headers,
tablefmt='pipe',
colalign=colaligns,
)
)
add("\n\n## Invisible symbols\n\n")
add(
tabulate(
sorted(sections["hidden"], key=lambda x: x[1]),
headers=headers,
tablefmt='pipe',
colalign=colaligns,
)
)
add("\n\n## Unset symbols\n\n")
for sym_name in sorted(x[1] for x in sections["unset"]):
add(f" # {sym_name} is not set\n")
with open(output, "w") as f:
f.writelines(lines)
def main():
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("dotconfig_file", help="Input merged .config file")
parser.add_argument("output_file", help="Output Markdown file")
parser.add_argument("kconfig_file", help="Top-level Kconfig file", nargs="?")
args = parser.parse_args()
with open(args.dotconfig_file + '-trace.pickle', 'rb') as f:
trace_data = pickle.load(f)
write_markdown(trace_data, args.output_file)
print(f"\nTraceconfig generated to: {args.output_file}")
if __name__ == '__main__':
main()