microchip-devtools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,14 @@
1
+ """Project context helpers — read from environment vars set by Makefile.py."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+
7
+ def project_name() -> str:
8
+ """Return project name from env var or current directory name."""
9
+ return os.environ.get("VOLTU_PROJECT_NAME", Path.cwd().name)
10
+
11
+
12
+ def project_root() -> Path:
13
+ """Return project root from env var or current working directory."""
14
+ return Path(os.environ.get("VOLTU_PROJECT_ROOT", str(Path.cwd())))
File without changes
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ microchip_devtools.format.uncrustify — Python wrapper for uncrustify.
4
+
5
+ Walks one or more source roots, collects C/H files, runs uncrustify,
6
+ and reports which files were actually reformatted.
7
+
8
+ CLI usage:
9
+ mchp-format firmware/src -c uncrustifyVoltuC.cfg -x config mcc build
10
+
11
+ Python usage:
12
+ from microchip_devtools.format.uncrustify import format_files
13
+ rc = format_files(roots=["firmware/src"], config="style.cfg", exclude=["config"])
14
+ """
15
+
16
+ import argparse
17
+ import subprocess
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ DEFAULT_EXTENSIONS: list[str] = [".c", ".h"]
22
+ DEFAULT_CONFIG: str = "uncrustifyVoltuC.cfg"
23
+
24
+
25
+ def _collect_files(
26
+ roots: list[Path],
27
+ extensions: list[str],
28
+ exclude: list[str],
29
+ ) -> list[Path]:
30
+ found: list[Path] = []
31
+ for root in roots:
32
+ if not root.is_dir():
33
+ print(f"uncrustify: warning: root not found: {root}", file=sys.stderr)
34
+ continue
35
+ for ext in extensions:
36
+ for path in sorted(root.rglob(f"*{ext}")):
37
+ rel = path.as_posix()
38
+ if not any(pat in rel for pat in exclude):
39
+ found.append(path)
40
+ return found
41
+
42
+
43
+ def _run_uncrustify(path: Path, config: str) -> tuple[bool, str]:
44
+ mtime_before = path.stat().st_mtime
45
+ result = subprocess.run(
46
+ [
47
+ "uncrustify",
48
+ "-c", config,
49
+ "--replace",
50
+ "--no-backup",
51
+ "--if-changed",
52
+ str(path),
53
+ ],
54
+ capture_output=True,
55
+ text=True,
56
+ )
57
+ if result.returncode != 0:
58
+ msg = (result.stderr or result.stdout).strip()
59
+ return False, msg
60
+
61
+ changed = path.stat().st_mtime != mtime_before
62
+ return changed, ""
63
+
64
+
65
+ def format_files(
66
+ roots: list[str | Path],
67
+ config: str = DEFAULT_CONFIG,
68
+ extensions: list[str] | None = None,
69
+ exclude: list[str] | None = None,
70
+ ) -> int:
71
+ if extensions is None:
72
+ extensions = DEFAULT_EXTENSIONS
73
+ if exclude is None:
74
+ exclude = []
75
+
76
+ root_paths = [Path(r) for r in roots]
77
+ files = _collect_files(root_paths, extensions, exclude)
78
+
79
+ if not files:
80
+ print("uncrustify: no files matched — check roots and exclude patterns")
81
+ return 0
82
+
83
+ formatted: list[Path] = []
84
+ errors: list[tuple[Path, str]] = []
85
+
86
+ for path in files:
87
+ changed, err = _run_uncrustify(path, config)
88
+ if err:
89
+ errors.append((path, err))
90
+ elif changed:
91
+ formatted.append(path)
92
+
93
+ for f in formatted:
94
+ print(f" FM {f}")
95
+
96
+ if errors:
97
+ print(f"\n [{len(errors)} error(s)]", file=sys.stderr)
98
+ for f, msg in errors:
99
+ print(f" ERROR {f}\n {msg}", file=sys.stderr)
100
+ return 1
101
+
102
+ return 0
103
+
104
+
105
+ def main() -> None:
106
+ parser = argparse.ArgumentParser(
107
+ description="Run uncrustify on a source tree and report reformatted files.",
108
+ )
109
+ parser.add_argument("roots", nargs="+", metavar="ROOT")
110
+ parser.add_argument("-c", "--config", default=DEFAULT_CONFIG, metavar="FILE")
111
+ parser.add_argument("-e", "--ext", nargs="+", default=DEFAULT_EXTENSIONS, metavar="EXT")
112
+ parser.add_argument("-x", "--exclude", nargs="+", default=[], metavar="PATTERN")
113
+
114
+ args = parser.parse_args()
115
+ sys.exit(format_files(args.roots, args.config, args.ext, args.exclude))
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
@@ -0,0 +1,31 @@
1
+ from rich.console import Console
2
+ from rich.table import Table
3
+ from rich.theme import Theme
4
+
5
+ _THEME = Theme(
6
+ {
7
+ "cmd": "bold cyan",
8
+ "desc": "default",
9
+ }
10
+ )
11
+
12
+ COMMANDS = [
13
+ ("mchp-list", "List all available mchp commands"),
14
+ ("mchp-setup", "Check prerequisites and install project deps"),
15
+ ("mchp-xc32", "Detect XC32 fmt=3 compiler bug in ELF/object files"),
16
+ ("mchp-hex", "Merge bootloader + app HEX into a single image"),
17
+ ("mchp-fmt", "Format C/H source files with uncrustify"),
18
+ ("mchp-mcc", "Force full MCC regeneration workflow"),
19
+ ("mchp-periph", "Validate MCC-generated peripheral config files"),
20
+ ("mchp-hw", "Show hardware config parsed from Harmony YML files"),
21
+ ]
22
+
23
+
24
+ def main():
25
+ console = Console(theme=_THEME, highlight=False)
26
+ table = Table(box=None, show_header=False, padding=(0, 2, 0, 0))
27
+ table.add_column(style="cmd", no_wrap=True)
28
+ table.add_column(style="desc")
29
+ for cmd, desc in COMMANDS:
30
+ table.add_row(cmd, desc)
31
+ console.print(table)
File without changes
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ microchip_devtools.mcc.check_peripheral — Validate MCC-generated peripheral config files.
4
+
5
+ Prevents two silent hardware bugs:
6
+
7
+ Bug 1 — MIPS interrupt vector stub missing in interrupts_a.S for a handler
8
+ declared in interrupts.c. Without a stub, the ISR never fires.
9
+
10
+ Bug 3 — Peripheral clock gated in PMD (plib_clk.c) while enabled in MCC
11
+ (core.yml). PMD registers are write-once per reset.
12
+
13
+ Usage:
14
+ mchp-check-peripheral [--root PATH] [--project-name NAME] [--stubs-file PATH]
15
+
16
+ Exit code:
17
+ 0 — all checks passed
18
+ 1 — one or more checks failed
19
+ """
20
+
21
+ import argparse
22
+ import json
23
+ import re
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ from microchip_devtools._project import project_name as _env_project_name
28
+ from microchip_devtools._project import project_root as _env_project_root
29
+
30
+
31
+ def _read(path: Path) -> str:
32
+ try:
33
+ return path.read_text(encoding="utf-8")
34
+ except FileNotFoundError:
35
+ print(f"[ERROR] File not found: {path}", file=sys.stderr)
36
+ sys.exit(1)
37
+
38
+
39
+ def _fail(msg: str) -> None:
40
+ print(f"[FAIL] {msg}", file=sys.stderr)
41
+
42
+
43
+ def _ok(msg: str) -> None:
44
+ print(f"[OK] {msg}")
45
+
46
+
47
+ def _parse_handler_declarations(text: str) -> list[str]:
48
+ return re.findall(r'^void\s+(\w+_Handler)\s*\(void\)\s*;', text, re.MULTILINE)
49
+
50
+
51
+ def _parse_extern_handlers(text: str) -> set[str]:
52
+ return set(re.findall(r'^\s+\.extern\s+(\w+_Handler)', text, re.MULTILINE))
53
+
54
+
55
+ def check_vector_stubs(
56
+ interrupts_c_text: str,
57
+ interrupts_as_text: str,
58
+ known_missing: dict[str, str] | None = None,
59
+ ) -> int:
60
+ """Bug 1 check: every handler in interrupts.c must have a stub in interrupts_a.S."""
61
+ if known_missing is None:
62
+ known_missing = {}
63
+
64
+ declared = _parse_handler_declarations(interrupts_c_text)
65
+ externed = _parse_extern_handlers(interrupts_as_text)
66
+
67
+ print(f"[INFO] interrupts.c: {len(declared)} handler declaration(s)")
68
+ print(f"[INFO] interrupts_a.S: {len(externed)} .extern handler reference(s)\n")
69
+
70
+ failures = 0
71
+ for handler in sorted(declared):
72
+ if handler in known_missing:
73
+ print(f"[SKIP] {handler}: no stub (accepted — {known_missing[handler]})")
74
+ continue
75
+ if handler not in externed:
76
+ _fail(
77
+ f"{handler} is declared in interrupts.c "
78
+ f"but has NO dispatch stub in interrupts_a.S — "
79
+ f"this interrupt will NEVER fire."
80
+ )
81
+ failures += 1
82
+ else:
83
+ _ok(f"{handler}: stub present in interrupts_a.S")
84
+ return failures
85
+
86
+
87
+ def _parse_pmd_from_core_yml(text: str) -> dict[int, int]:
88
+ pmd_expected: dict[int, int] = {}
89
+ current_reg: int | None = None
90
+ for line in text.splitlines():
91
+ m = re.match(r'\s+PMD(\d+)_REG_VALUE:', line)
92
+ if m:
93
+ current_reg = int(m.group(1))
94
+ continue
95
+ if current_reg is not None:
96
+ mv = re.search(r"value:\s+'(\d+)'", line)
97
+ if mv:
98
+ pmd_expected[current_reg] = int(mv.group(1))
99
+ current_reg = None
100
+ continue
101
+ if re.match(r'\s{4}\w', line) and not re.match(r'\s{6,}', line):
102
+ current_reg = None
103
+ return pmd_expected
104
+
105
+
106
+ def _parse_pmd_from_plib_clk(text: str) -> dict[int, int]:
107
+ pmd_actual: dict[int, int] = {}
108
+ for m in re.finditer(r'\bPMD(\d+)\s*=\s*(0x[0-9a-fA-F]+)[Uu]?\s*;', text):
109
+ pmd_actual[int(m.group(1))] = int(m.group(2), 16)
110
+ return pmd_actual
111
+
112
+
113
+ def check_pmd_registers(core_yml_text: str, plib_clk_text: str) -> int:
114
+ """Bug 3 check: PMD register values in core.yml must match plib_clk.c."""
115
+ pmd_expected = _parse_pmd_from_core_yml(core_yml_text)
116
+ pmd_actual = _parse_pmd_from_plib_clk(plib_clk_text)
117
+
118
+ print(f"[INFO] core.yml: {len(pmd_expected)} PMD register(s) declared")
119
+ print(f"[INFO] plib_clk.c: {len(pmd_actual)} PMD register(s) found\n")
120
+
121
+ failures = 0
122
+ for reg in sorted(pmd_expected.keys()):
123
+ expected = pmd_expected[reg]
124
+ if reg not in pmd_actual:
125
+ _fail(
126
+ f"PMD{reg} is declared in core.yml "
127
+ f"but not found in plib_clk.c — cannot verify clock gate."
128
+ )
129
+ failures += 1
130
+ continue
131
+ actual = pmd_actual[reg]
132
+ if actual != expected:
133
+ diff = actual ^ expected
134
+ _fail(
135
+ f"PMD{reg} mismatch — "
136
+ f"plib_clk.c=0x{actual:08X} core.yml=0x{expected:08X} "
137
+ f"diff_bits=0x{diff:08X} "
138
+ f"(MCC regeneration likely reverted a manual clock-enable fix)"
139
+ )
140
+ failures += 1
141
+ else:
142
+ _ok(f"PMD{reg}: 0x{actual:08X} matches core.yml")
143
+ return failures
144
+
145
+
146
+ def main() -> int:
147
+ parser = argparse.ArgumentParser(
148
+ description="Validate MCC-generated peripheral config files (Bug 1 + Bug 3)."
149
+ )
150
+ parser.add_argument(
151
+ "--root", type=Path, default=None,
152
+ help="Project root (default: $VOLTU_PROJECT_ROOT or cwd)",
153
+ )
154
+ parser.add_argument(
155
+ "--project-name", default=None,
156
+ help="Project name (default: $VOLTU_PROJECT_NAME or folder name)",
157
+ )
158
+ parser.add_argument(
159
+ "--stubs-file", type=Path, default=None,
160
+ help="JSON file with known-missing stubs: {HandlerName: reason}",
161
+ )
162
+ args = parser.parse_args()
163
+
164
+ root = args.root or _env_project_root()
165
+ name = args.project_name or _env_project_name()
166
+
167
+ interrupts_c = root / "firmware/src/config/default/interrupts.c"
168
+ interrupts_as = root / "firmware/src/config/default/interrupts_a.S"
169
+ core_yml = root / f"firmware/{name}.X/{name}_default/components/core.yml"
170
+ plib_clk_c = root / "firmware/src/config/default/peripheral/clk/plib_clk.c"
171
+
172
+ known_missing: dict[str, str] = {}
173
+ if args.stubs_file and args.stubs_file.exists():
174
+ known_missing = json.loads(args.stubs_file.read_text(encoding="utf-8"))
175
+ else:
176
+ default_stubs = root / "pymake" / "check_peripheral_stubs.json"
177
+ if default_stubs.exists():
178
+ known_missing = json.loads(default_stubs.read_text(encoding="utf-8"))
179
+
180
+ print("--- Peripheral Configuration Check ---\n")
181
+
182
+ interrupts_c_text = _read(interrupts_c)
183
+ interrupts_as_text = _read(interrupts_as)
184
+ core_yml_text = _read(core_yml)
185
+ plib_clk_text = _read(plib_clk_c)
186
+
187
+ print("== Bug 1: Vector stubs (interrupts.c vs interrupts_a.S) ==")
188
+ failures = check_vector_stubs(interrupts_c_text, interrupts_as_text, known_missing)
189
+
190
+ print("\n== Bug 3: PMD clock gates (core.yml vs plib_clk.c) ==")
191
+ failures += check_pmd_registers(core_yml_text, plib_clk_text)
192
+
193
+ print()
194
+ if failures:
195
+ print(
196
+ f"[RESULT] FAILED — {failures} check(s) failed. "
197
+ f"Fix the generated files or re-run MCC.",
198
+ file=sys.stderr,
199
+ )
200
+ return 1
201
+
202
+ print("[RESULT] PASSED — all peripheral configuration checks OK.")
203
+ return 0
204
+
205
+
206
+ if __name__ == "__main__":
207
+ sys.exit(main())