Skip to content

Commit fb389fe

Browse files
committed
validate_sof_install.py: add VERSION column after STATUS in summary table
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent e9b56e0 commit fb389fe

1 file changed

Lines changed: 52 additions & 19 deletions

File tree

‎validate_sof_install.py‎

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Offline rootfs / NFS rootfs validation (--target-root /srv/nfs/spider-rootfs)
1212
- Remote SSH target validation (--ssh root@spider)
1313
- Mismatch detection against historical SOF releases
14+
- Displays version of installed files in summary table
1415
- Fix mode (--fix, --dry-run) to automatically sync/repair installed files
1516
- Platform filtering (-p tgl, -p mtl, -p ptl, etc.)
1617
- Component filtering (-c fw, -c llext, -c tplg, -c tools)
@@ -51,6 +52,8 @@ class ValidationRecord:
5152
component: str # fw, llext, tplg, tools
5253
platform: str # tgl, mtl, ptl, generic, etc.
5354
rel_path: str # relative to fw_dest (or tools_dest)
55+
expected_version: str = "" # target version requested e.g. v2.14.1
56+
installed_version: Optional[str] = None # version found on host e.g. v2.12, v2.14.1, unknown
5457
source_path: Optional[str] = None # in sof-bin
5558
target_path: Optional[str] = None # on host
5659
is_symlink: bool = False
@@ -71,6 +74,8 @@ def to_dict(self) -> Dict[str, Any]:
7174
"component": self.component,
7275
"platform": self.platform,
7376
"rel_path": self.rel_path,
77+
"expected_version": self.expected_version,
78+
"installed_version": self.installed_version,
7479
"source_path": self.source_path,
7580
"target_path": self.target_path,
7681
"is_symlink": self.is_symlink,
@@ -298,7 +303,6 @@ def lookup_md5(self, md5_hash: Optional[str], target_filename: Optional[str] = N
298303
if not matches:
299304
return None
300305

301-
# Prioritize matching filename if provided
302306
filtered = matches
303307
if target_filename:
304308
same_name = [m for m in matches if m["filename"] == target_filename]
@@ -307,13 +311,12 @@ def lookup_md5(self, md5_hash: Optional[str], target_filename: Optional[str] = N
307311

308312
descriptions = []
309313
for m in filtered:
310-
# Extract clean release name e.g. v2.12 or v2.11.1
311314
comp = m["component"]
312315
ver_match = re.search(r"v\d+(\.\d+)*(-[a-zA-Z0-9]+)?", comp)
313316
ver_label = ver_match.group(0) if ver_match else m["version_dir"]
314317
descriptions.append(f"{ver_label} ({comp})")
315318

316-
return ", ".join(dict.fromkeys(descriptions)) # unique preserved
319+
return ", ".join(dict.fromkeys(descriptions))
317320

318321

319322
class HostTarget:
@@ -333,7 +336,6 @@ def fix_file(
333336
symlink_target: Optional[str],
334337
dry_run: bool = False,
335338
) -> Tuple[bool, str]:
336-
"""Fix a file or symlink on the target host."""
337339
raise NotImplementedError
338340

339341

@@ -403,7 +405,6 @@ def fix_file(
403405

404406
try:
405407
real_tgt.parent.mkdir(parents=True, exist_ok=True)
406-
# Remove existing file or broken link if it exists
407408
if real_tgt.exists() or real_tgt.is_symlink():
408409
real_tgt.unlink()
409410

@@ -653,14 +654,16 @@ def should_include(self, component: str, platform: str, rel_path: str) -> bool:
653654

654655
return True
655656

656-
def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationRecord]:
657+
def scan_source_components(self, comp_dirs: Dict[str, Path], target_version: str) -> List[ValidationRecord]:
657658
records: List[ValidationRecord] = []
658659

659660
# 1. Firmware
660661
if "fw" in comp_dirs and "fw" in self.component_filter:
661662
fw_dir = comp_dirs["fw"]
662663
is_ipc4 = "ipc4" in fw_dir.name
663664
radix = "sof-ipc4" if is_ipc4 else "sof"
665+
ver_match = re.search(r"v\d+(\.\d+)*(-[a-zA-Z0-9]+)?", fw_dir.name)
666+
fw_ver = ver_match.group(0) if ver_match else target_version
664667

665668
for root, _, files in os.walk(fw_dir):
666669
for f in files:
@@ -682,6 +685,7 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
682685
component="fw",
683686
platform=platform,
684687
rel_path=rel_install,
688+
expected_version=fw_ver,
685689
source_path=str(src_p),
686690
is_symlink=is_sym,
687691
symlink_target_expected=sym_tgt,
@@ -692,6 +696,9 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
692696
# 2. LLEXT
693697
if "llext" in comp_dirs and "llext" in self.component_filter:
694698
lib_dir = comp_dirs["llext"]
699+
ver_match = re.search(r"v\d+(\.\d+)*(-[a-zA-Z0-9]+)?", lib_dir.name)
700+
lib_ver = ver_match.group(0) if ver_match else target_version
701+
695702
for root, _, files in os.walk(lib_dir):
696703
for f in files:
697704
src_p = Path(root) / f
@@ -712,6 +719,7 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
712719
component="llext",
713720
platform=platform,
714721
rel_path=rel_install,
722+
expected_version=lib_ver,
715723
source_path=str(src_p),
716724
is_symlink=is_sym,
717725
symlink_target_expected=sym_tgt,
@@ -724,6 +732,8 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
724732
tplg_dir = comp_dirs["tplg"]
725733
is_ipc4 = "ipc4" in tplg_dir.name or "ace" in tplg_dir.name
726734
radix = "sof-ipc4-tplg" if is_ipc4 else "sof-tplg"
735+
ver_match = re.search(r"v\d+(\.\d+)*(-[a-zA-Z0-9]+)?", tplg_dir.name)
736+
tplg_ver = ver_match.group(0) if ver_match else target_version
727737

728738
for root, _, files in os.walk(tplg_dir):
729739
for f in files:
@@ -745,6 +755,7 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
745755
component="tplg",
746756
platform=platform,
747757
rel_path=rel_install,
758+
expected_version=tplg_ver,
748759
source_path=str(src_p),
749760
is_symlink=is_sym,
750761
symlink_target_expected=sym_tgt,
@@ -755,6 +766,9 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
755766
# 4. Tools
756767
if "tools" in comp_dirs and "tools" in self.component_filter:
757768
tools_dir = comp_dirs["tools"]
769+
ver_match = re.search(r"v\d+(\.\d+)*(-[a-zA-Z0-9]+)?", tools_dir.name)
770+
tools_ver = ver_match.group(0) if ver_match else target_version
771+
758772
for root, _, files in os.walk(tools_dir):
759773
for f in files:
760774
src_p = Path(root) / f
@@ -775,6 +789,7 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
775789
component="tools",
776790
platform=platform,
777791
rel_path=rel_install,
792+
expected_version=tools_ver,
778793
source_path=str(src_p),
779794
is_symlink=is_sym,
780795
symlink_target_expected=sym_tgt,
@@ -786,7 +801,7 @@ def scan_source_components(self, comp_dirs: Dict[str, Path]) -> List[ValidationR
786801

787802
def validate_installation(self, version: str) -> List[ValidationRecord]:
788803
comp_dirs = self.repo.resolve_version_components(version)
789-
records = self.scan_source_components(comp_dirs)
804+
records = self.scan_source_components(comp_dirs, target_version=version)
790805

791806
target_path_map: Dict[str, str] = {}
792807
for rec in records:
@@ -812,19 +827,21 @@ def validate_installation(self, version: str) -> List[ValidationRecord]:
812827

813828
if not exists:
814829
rec.status = Status.MISSING
830+
rec.installed_version = "-"
815831
rec.message = f"File not found on target: {tgt_p}"
816832
continue
817833

818834
if rec.is_symlink:
819835
if not is_sym:
820836
if actual_md5 and rec.expected_md5 and actual_md5.lower() == rec.expected_md5.lower():
821837
rec.status = Status.MATCH
838+
rec.installed_version = rec.expected_version
822839
rec.message = f"Installed as regular file matching MD5 (expected symlink -> {rec.symlink_target_expected})"
823840
else:
824841
rec.status = Status.MISMATCH
825-
# Match against historical releases
826842
matched_rel = self.repo.lookup_md5(actual_md5, Path(rec.rel_path).name)
827843
rec.matched_release = matched_rel
844+
rec.installed_version = matched_rel.split()[0] if matched_rel else "unknown"
828845
match_info = f" (installed file matches {matched_rel})" if matched_rel else ""
829846
rec.message = f"Expected symlink -> {rec.symlink_target_expected}, found regular file with MD5 mismatch{match_info}"
830847
else:
@@ -833,43 +850,54 @@ def validate_installation(self, version: str) -> List[ValidationRecord]:
833850
if norm_exp and norm_act and (norm_exp == norm_act or norm_exp.endswith(norm_act) or norm_act.endswith(norm_exp)):
834851
if actual_md5 and rec.expected_md5 and actual_md5.lower() == rec.expected_md5.lower():
835852
rec.status = Status.SYMLINK_MATCH
853+
rec.installed_version = rec.expected_version
836854
rec.message = f"Symlink matches -> {sym_target} (resolved MD5 OK)"
837855
elif actual_md5 is None:
838856
rec.status = Status.BROKEN_SYMLINK
857+
rec.installed_version = "broken"
839858
rec.message = f"Broken symlink on target: {tgt_p} -> {sym_target} (target missing)"
840859
else:
841860
rec.status = Status.MISMATCH
842861
matched_rel = self.repo.lookup_md5(actual_md5, Path(rec.rel_path).name)
843862
rec.matched_release = matched_rel
863+
rec.installed_version = matched_rel.split()[0] if matched_rel else "unknown"
844864
match_info = f" (target matches {matched_rel})" if matched_rel else ""
845865
rec.message = f"Symlink target {sym_target} has MD5 mismatch: expected {rec.expected_md5}, got {actual_md5}{match_info}"
846866
else:
847867
rec.status = Status.SYMLINK_MISMATCH
868+
matched_rel = self.repo.lookup_md5(actual_md5, Path(rec.rel_path).name)
869+
rec.matched_release = matched_rel
870+
rec.installed_version = matched_rel.split()[0] if matched_rel else "target mismatch"
848871
rec.message = f"Symlink target mismatch: expected '{rec.symlink_target_expected}', got '{sym_target}'"
849872
else:
850873
if is_sym:
851874
if actual_md5 and rec.expected_md5 and actual_md5.lower() == rec.expected_md5.lower():
852875
rec.status = Status.MATCH
876+
rec.installed_version = rec.expected_version
853877
rec.message = f"Symlink resolved to valid file (MD5 match: {actual_md5})"
854878
else:
855879
rec.status = Status.MISMATCH
856880
matched_rel = self.repo.lookup_md5(actual_md5, Path(rec.rel_path).name)
857881
rec.matched_release = matched_rel
882+
rec.installed_version = matched_rel.split()[0] if matched_rel else "unknown"
858883
match_info = f" (target matches {matched_rel})" if matched_rel else ""
859884
rec.message = f"Symlink resolved to mismatched file: expected {rec.expected_md5}, got {actual_md5}{match_info}"
860885
else:
861886
if actual_md5 and rec.expected_md5:
862887
if actual_md5.lower() == rec.expected_md5.lower():
863888
rec.status = Status.MATCH
889+
rec.installed_version = rec.expected_version
864890
rec.message = "MD5 checksum matched"
865891
else:
866892
rec.status = Status.MISMATCH
867893
matched_rel = self.repo.lookup_md5(actual_md5, Path(rec.rel_path).name)
868894
rec.matched_release = matched_rel
895+
rec.installed_version = matched_rel.split()[0] if matched_rel else "unknown"
869896
match_info = f" (installed file matches {matched_rel})" if matched_rel else ""
870897
rec.message = f"MD5 mismatch: expected {rec.expected_md5}, got {actual_md5}{match_info}"
871898
else:
872899
rec.status = Status.ERROR
900+
rec.installed_version = "error"
873901
rec.message = "Could not compute MD5 checksum on target"
874902

875903
return records
@@ -904,38 +932,43 @@ def format_table(records: List[ValidationRecord], colorize: bool = True) -> str:
904932
RESET = "\033[0m" if colorize else ""
905933

906934
lines = []
907-
lines.append(f"{BOLD}{'COMPONENT':<8} {'PLATFORM':<12} {'STATUS':<16} {'MD5 / MATCHED VERSION':<38} {'FILE':<40}{RESET}")
908-
lines.append("-" * 120)
935+
lines.append(f"{BOLD}{'COMPONENT':<8} {'PLATFORM':<10} {'STATUS':<16} {'VERSION':<14} {'MD5 CHECKSUM':<34} {'FILE':<40}{RESET}")
936+
lines.append("-" * 125)
909937

910938
status_counts: Dict[str, int] = {}
911939

912940
for r in records:
913941
status_name = r.status.value
914942
status_counts[status_name] = status_counts.get(status_name, 0) + 1
915943

944+
ver_display = r.installed_version or "-"
945+
916946
if r.status in (Status.MATCH, Status.SYMLINK_MATCH):
917947
c_status = f"{GREEN}{status_name:<16}{RESET}"
948+
c_ver = f"{GREEN}{ver_display:<14}{RESET}"
918949
md5_display = (r.actual_md5 or "--------------------------------")[:32]
919-
elif r.status in (Status.MISMATCH, Status.SYMLINK_MISMATCH, Status.BROKEN_SYMLINK):
950+
elif r.status in (Status.MISMATCH, Status.SYMLINK_MISMATCH):
920951
c_status = f"{RED}{status_name:<16}{RESET}"
921-
if r.matched_release:
922-
md5_display = f"{MAGENTA}matches {r.matched_release[:26]}{RESET}"
923-
elif r.expected_md5 and r.actual_md5:
924-
md5_display = f"{r.actual_md5[:8]} != {r.expected_md5[:8]}"
925-
else:
926-
md5_display = (r.actual_md5 or "--------------------------------")[:32]
952+
c_ver = f"{MAGENTA}{ver_display:<14}{RESET}"
953+
md5_display = (r.actual_md5 or "--------------------------------")[:32]
954+
elif r.status == Status.BROKEN_SYMLINK:
955+
c_status = f"{RED}{status_name:<16}{RESET}"
956+
c_ver = f"{RED}{ver_display:<14}{RESET}"
957+
md5_display = (r.actual_md5 or "--------------------------------")[:32]
927958
elif r.status == Status.MISSING:
928959
c_status = f"{YELLOW}{status_name:<16}{RESET}"
960+
c_ver = f"{YELLOW}{'-':<14}{RESET}"
929961
md5_display = f"{YELLOW}MISSING{RESET}"
930962
else:
931963
c_status = f"{CYAN}{status_name:<16}{RESET}"
964+
c_ver = f"{CYAN}{ver_display:<14}{RESET}"
932965
md5_display = (r.actual_md5 or "--------------------------------")[:32]
933966

934967
lines.append(
935-
f"{r.component:<8} {r.platform:<12} {c_status} {md5_display:<38} {r.rel_path}"
968+
f"{r.component:<8} {r.platform:<10} {c_status} {c_ver} {md5_display:<34} {r.rel_path}"
936969
)
937970

938-
lines.append("=" * 120)
971+
lines.append("=" * 125)
939972
total = len(records)
940973
passed = status_counts.get(Status.MATCH.value, 0) + status_counts.get(Status.SYMLINK_MATCH.value, 0)
941974
failed = status_counts.get(Status.MISMATCH.value, 0) + status_counts.get(Status.SYMLINK_MISMATCH.value, 0) + status_counts.get(Status.BROKEN_SYMLINK.value, 0)

0 commit comments

Comments
 (0)