mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
fix: print the released pack counts, not main's
This commit is contained in:
1 parent
d0279fe4d5
commit
243e95725b
11 files changed
+450
-89
No files matched your search
+19
-23
@@ -29,6 +29,7 @@ from common import (
|
||||
unique_emulator_profiles,
|
||||
write_if_changed,
|
||||
)
|
||||
import release_record
|
||||
from verify import verify_platform
|
||||
|
||||
|
||||
@@ -94,37 +95,28 @@ def compute_coverage(
|
||||
}
|
||||
|
||||
|
||||
def manifest_totals(
|
||||
platform_name: str, install_dir: str = "install"
|
||||
) -> tuple[int | None, int | None]:
|
||||
"""Files and bytes a platform's pack holds once extracted.
|
||||
def release_totals(platform_name: str, record: dict) -> tuple[int | None, int | None]:
|
||||
"""Files and bytes of the pack the latest release serves for a platform.
|
||||
|
||||
The install manifest lists what the installer fetches, and states apart
|
||||
what the pack adds to that: the data directories and its own documents.
|
||||
The pack figures are the ones a reader can check against an extraction.
|
||||
Returns (None, None) when no manifest exists yet.
|
||||
The Download link gives that release, so these are the figures a reader
|
||||
can check against an extraction. The install manifests describe what
|
||||
main would build today, which is a different pack as soon as a commit
|
||||
lands after the release. Returns (None, None) for a platform the release
|
||||
carries no pack for.
|
||||
"""
|
||||
path = os.path.join(install_dir, f"{platform_name}.json")
|
||||
if not os.path.exists(path):
|
||||
pack = release_record.pack_for(platform_name, record)
|
||||
if not pack:
|
||||
return None, None
|
||||
try:
|
||||
with open(path) as f:
|
||||
manifest = json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return None, None
|
||||
return (
|
||||
manifest.get("pack_files", manifest.get("total_files")),
|
||||
manifest.get("pack_size", manifest.get("total_size")),
|
||||
)
|
||||
return pack["files"], pack["extracted_size"]
|
||||
|
||||
|
||||
def download_table(
|
||||
coverages: dict,
|
||||
archived: set[str],
|
||||
extract_paths: dict[str, str],
|
||||
install_dir: str = "install",
|
||||
record: dict,
|
||||
) -> list[str]:
|
||||
"""One row per platform: what its pack holds and where it extracts."""
|
||||
"""One row per platform: what its released pack holds and where it extracts."""
|
||||
lines = [
|
||||
"| Platform | Files | Extracted size | Extract to | Download |",
|
||||
"|----------|------:|---------------:|-----------|----------|",
|
||||
@@ -133,7 +125,7 @@ def download_table(
|
||||
display = cov["platform"]
|
||||
if name in archived:
|
||||
display = f"{display} *"
|
||||
files, size = manifest_totals(name, install_dir)
|
||||
files, size = release_totals(name, record)
|
||||
lines.append(
|
||||
f"| {display} | {f'{files:,}' if files else '-'} |"
|
||||
f" {format_size(size) if size else '-'} |"
|
||||
@@ -384,7 +376,11 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
if entry.get("status") == "archived"
|
||||
}
|
||||
|
||||
lines.extend(download_table(coverages, archived, extract_paths))
|
||||
lines.extend(
|
||||
download_table(
|
||||
coverages, archived, extract_paths, release_record.load_record()
|
||||
)
|
||||
)
|
||||
|
||||
if archived:
|
||||
lines.extend(
|
||||
|
||||
@@ -51,9 +51,10 @@ from nativemode import reads_file_contents
|
||||
|
||||
|
||||
yaml = require_yaml()
|
||||
from generate_readme import compute_coverage, manifest_totals
|
||||
from generate_readme import compute_coverage, release_totals
|
||||
from profile_sync import source_ref_values, split_source_ref
|
||||
from provenance_report import build_report
|
||||
import release_record
|
||||
import upstream
|
||||
|
||||
DOCS_DIR = "docs"
|
||||
@@ -281,7 +282,7 @@ def generate_home(
|
||||
|
||||
lines.append(
|
||||
f"| {logo_md} | [{display}](platforms/{name}.md) | "
|
||||
f"{manifest_totals(name)[0] or cov['present']:,} | {mode_label} | "
|
||||
f"{_released_files(name)} | {mode_label} | "
|
||||
f"[Pack]({RELEASE_URL}){{ .md-button .md-button--primary }} |"
|
||||
)
|
||||
|
||||
@@ -410,6 +411,12 @@ def compute_stats(db: dict, coverages: dict, profiles: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _released_files(platform_name: str) -> str:
|
||||
"""File count of the pack the latest release serves, as a table cell."""
|
||||
files, _size = release_totals(platform_name, release_record.load_record())
|
||||
return f"{files:,}" if files else "-"
|
||||
|
||||
|
||||
# The home tile counts the whole collection. Labelled "Files" above a table
|
||||
# of pack buttons, it read as what one pack contains.
|
||||
COLLECTION_FILES_LABEL = "Files collected"
|
||||
@@ -1181,7 +1188,7 @@ def generate_platform_index(coverages: dict, registry: dict | None = None) -> st
|
||||
|
||||
lines.append(
|
||||
f"| [{display}]({name}.md) | "
|
||||
f"{cov['present']:,} | {manifest_totals(name)[0] or cov['present']:,} | "
|
||||
f"{cov['present']:,} | {_released_files(name)} | "
|
||||
f"{mode_html} | {status_html} | "
|
||||
f"[Pack]({RELEASE_URL}){{ .md-button .md-button--primary }} |"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Record what the packs of a release hold.
|
||||
|
||||
The README and the site print, beside each download link, how many files a
|
||||
pack holds and what they weigh once extracted. Those figures have to be the
|
||||
ones of the release the link serves. Read from the install manifests they
|
||||
described what main would build today, and drifted from the published pack
|
||||
with every commit made since the release.
|
||||
|
||||
The record is written from the archives themselves when a release is cut,
|
||||
committed with it, and read by the generators.
|
||||
|
||||
Usage:
|
||||
python scripts/release_record.py dist/ --tag v2026.10.04
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import split_pack
|
||||
|
||||
RECORD = "release.json"
|
||||
|
||||
|
||||
def _match_key(value: str) -> str:
|
||||
"""Letters and digits only: the platform is `misterfpga`, the pack MiSTer_FPGA."""
|
||||
return re.sub(r"[^a-z0-9]", "", value.lower())
|
||||
|
||||
|
||||
def build_record(dist: Path, tag: str) -> dict:
|
||||
"""Read every pack of a directory, parts counted once across them."""
|
||||
assets: dict[str, list[Path]] = {}
|
||||
for path in sorted(Path(dist).glob("*_BIOS_Pack*.zip")):
|
||||
assets.setdefault(split_pack.pack_of(path.name), []).append(path)
|
||||
|
||||
packs = {}
|
||||
for name, paths in sorted(assets.items()):
|
||||
members: dict[str, int] = {}
|
||||
for path in paths:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
for info in archive.infolist():
|
||||
members[info.filename] = info.file_size
|
||||
packs[name] = {
|
||||
"files": len(members),
|
||||
"extracted_size": sum(members.values()),
|
||||
"download_size": sum(path.stat().st_size for path in paths),
|
||||
"assets": [path.name for path in paths],
|
||||
}
|
||||
return {"tag": tag, "packs": packs}
|
||||
|
||||
|
||||
def load_record(path: str | Path = RECORD) -> dict:
|
||||
"""The committed record, empty when no release has written one."""
|
||||
try:
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def pack_for(platform: str, record: dict) -> dict | None:
|
||||
"""The pack of the release that serves a platform, if it has one."""
|
||||
needle = _match_key(platform)
|
||||
if not needle:
|
||||
return None
|
||||
for name, pack in sorted(record.get("packs", {}).items()):
|
||||
if needle in _match_key(name):
|
||||
return pack
|
||||
return None
|
||||
|
||||
|
||||
def manifest_mismatches(record: dict, install_dir: str | Path) -> list[str]:
|
||||
"""Packs whose archive does not hold what their install manifest expects."""
|
||||
found = []
|
||||
for manifest_path in sorted(Path(install_dir).glob("*.json")):
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
expected = manifest.get("pack_files")
|
||||
if expected is None:
|
||||
continue
|
||||
for name, pack in sorted(record.get("packs", {}).items()):
|
||||
if _match_key(manifest_path.stem) not in _match_key(name):
|
||||
continue
|
||||
if pack["files"] != expected:
|
||||
found.append(
|
||||
f"{name} holds {pack['files']} files, "
|
||||
f"install/{manifest_path.name} expects {expected}"
|
||||
)
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def _size(count: int) -> str:
|
||||
if count >= 1024**3:
|
||||
return f"{count / 1024**3:.1f} GB"
|
||||
return f"{count / 1024**2:.0f} MB"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Record what a release's packs hold")
|
||||
parser.add_argument("dist", type=Path, help="directory of the packs to publish")
|
||||
parser.add_argument("--tag", required=True, help="release tag, e.g. v2026.10.04")
|
||||
parser.add_argument("--install-dir", default="install", type=Path)
|
||||
parser.add_argument("--output", default=RECORD, type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
record = build_record(args.dist, args.tag)
|
||||
if not record["packs"]:
|
||||
print(f"Error: no pack in {args.dist}", file=sys.stderr)
|
||||
return 1
|
||||
for name, pack in record["packs"].items():
|
||||
print(
|
||||
f"{name:<44} download {_size(pack['download_size']):>8}"
|
||||
f" extracted {_size(pack['extracted_size']):>8}"
|
||||
f" {pack['files']:,} files {len(pack['assets'])} asset(s)"
|
||||
)
|
||||
mismatches = manifest_mismatches(record, args.install_dir)
|
||||
for line in mismatches:
|
||||
print(f"Error: {line}", file=sys.stderr)
|
||||
if mismatches:
|
||||
return 1
|
||||
args.output.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"Wrote {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -45,6 +45,12 @@ def is_part(name: str) -> bool:
|
||||
return _PART_NAME.match(name) is not None
|
||||
|
||||
|
||||
def pack_of(name: str) -> str:
|
||||
"""The pack a published file belongs to, a part folded into its archive."""
|
||||
part = _PART_NAME.match(name)
|
||||
return f"{part['stem']}.zip" if part else name
|
||||
|
||||
|
||||
def part_name(pack_name: str, index: int, count: int) -> str:
|
||||
return f"{pack_name[: -len('.zip')]}.part{index}of{count}.zip"
|
||||
|
||||
|
||||
@@ -259,6 +259,10 @@ def main() -> int:
|
||||
_validate_json_files(target_paths, "target-manifest.schema.json")
|
||||
)
|
||||
|
||||
release_path = ROOT / "release.json"
|
||||
if release_path.exists():
|
||||
errors.extend(_validate_json_files([release_path], "release.schema.json"))
|
||||
|
||||
stats_path = ROOT / "docs" / "stats.json"
|
||||
if stats_path.exists():
|
||||
errors.extend(_validate_json_files([stats_path], "stats.schema.json"))
|
||||
|
||||
Reference in new issue
Block a user