mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 20:43:50 -06:00
chore: merge origin/main
This commit is contained in:
commit
025fd08a72
16 files changed
+484
-9
No files matched your search
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the `retrobios` package PyPI serves: install.py as a command.
|
||||
|
||||
The wheel and the sdist hold install.py, byte for byte the copy the
|
||||
bootstraps pin, a launcher that runs it as a script (`retrobios` and
|
||||
`python -m retrobios`), the metadata, README.md and LICENSE. Nothing of the
|
||||
collection. packaging/pypi/ holds the project file, the README and
|
||||
`__main__.py`; this script stages them with install.py and LICENSE into a
|
||||
build folder, writes `__init__.py` with the version, and runs `uv build`
|
||||
there with the flit_core backend pinned in the project file.
|
||||
|
||||
The version is the UTC date of the last commit that changed install.py,
|
||||
with the zeros PEP 440 drops left out: 2026-11-09 gives 2026.11.9. When
|
||||
install.py changed several times that day, the newest gets .N, its rank in
|
||||
the day: the third change gives 2026.11.9.3. The same install.py always
|
||||
gives the same version, so PyPI refuses a republish of unchanged code.
|
||||
SOURCE_DATE_EPOCH is that commit's time, so two builds of one commit give
|
||||
the same bytes.
|
||||
|
||||
Usage:
|
||||
python scripts/build_pypi.py
|
||||
python scripts/build_pypi.py --output-dir tmp/pypi-dist
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from sitebootstrap import PIN, SH_NAME
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
PACKAGE_DIR = ROOT / "packaging" / "pypi"
|
||||
DEFAULT_OUTPUT = ROOT / "tmp" / "pypi-dist"
|
||||
MODULE = "retrobios"
|
||||
|
||||
# The launcher. runpy runs install.py as __main__, so its own __main__ block
|
||||
# keeps the exit codes it gives a reader that left (141) and Ctrl+C (130).
|
||||
LAUNCHER = '''"""The RetroBIOS installer, install.py, run as the retrobios command."""
|
||||
|
||||
import runpy
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "{version}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
runpy.run_path(str(Path(__file__).with_name("install.py")), run_name="__main__")
|
||||
'''
|
||||
|
||||
|
||||
def package_version(stamps: list[int]) -> str:
|
||||
"""The version of install.py from its commit times, newest first (UTC)."""
|
||||
if not stamps:
|
||||
raise ValueError("install.py has no commit to date the package by")
|
||||
day = datetime.datetime.fromtimestamp(stamps[0], datetime.timezone.utc).date()
|
||||
rank = sum(1 for stamp in stamps
|
||||
if datetime.datetime.fromtimestamp(stamp, datetime.timezone.utc).date() == day)
|
||||
base = f"{day.year}.{day.month}.{day.day}"
|
||||
return base if rank == 1 else f"{base}.{rank}"
|
||||
|
||||
|
||||
def install_py_commits() -> list[int]:
|
||||
"""Commit times of install.py, newest first, from git."""
|
||||
out = subprocess.run(["git", "log", "--format=%ct", "--", "install.py"], cwd=ROOT,
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
return [int(line) for line in out.split()]
|
||||
|
||||
|
||||
def installer_bytes() -> bytes:
|
||||
"""install.py, refused unless it is the one install.sh pins: the package
|
||||
then runs the installer the one-line command runs, under the same id."""
|
||||
data = (ROOT / "install.py").read_bytes()
|
||||
pins = PIN[SH_NAME].findall((ROOT / "install.sh").read_text(encoding="utf-8"))
|
||||
if pins != [hashlib.sha256(data).hexdigest()]:
|
||||
raise ValueError("install.py is not the installer install.sh pins: recompute the pin")
|
||||
return data
|
||||
|
||||
|
||||
def stage(folder: Path, version: str) -> None:
|
||||
"""The project as the backend reads it."""
|
||||
module = folder / MODULE
|
||||
module.mkdir(parents=True)
|
||||
for name in ("pyproject.toml", "README.md"):
|
||||
shutil.copyfile(PACKAGE_DIR / name, folder / name)
|
||||
shutil.copyfile(ROOT / "LICENSE", folder / "LICENSE")
|
||||
shutil.copyfile(PACKAGE_DIR / MODULE / "__main__.py", module / "__main__.py")
|
||||
(module / "__init__.py").write_text(LAUNCHER.format(version=version), encoding="utf-8")
|
||||
(module / "install.py").write_bytes(installer_bytes())
|
||||
|
||||
|
||||
def build(output_dir: Path) -> tuple[Path, Path]:
|
||||
"""The wheel and the sdist in output_dir, replacing earlier builds there."""
|
||||
stamps = install_py_commits()
|
||||
version = package_version(stamps)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
for pattern in ("*.whl", "*.tar.gz"):
|
||||
for old in output_dir.glob(f"{MODULE}-{pattern}"):
|
||||
old.unlink()
|
||||
scratch = ROOT / "tmp"
|
||||
scratch.mkdir(exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="pypi-stage-", dir=scratch) as name:
|
||||
folder = Path(name) / "retrobios"
|
||||
stage(folder, version)
|
||||
# Without a flag, uv builds the sdist, then the wheel from that sdist:
|
||||
# what a user building from the sdist gets is what is published.
|
||||
subprocess.run(
|
||||
["uv", "build", "--quiet", "--out-dir", str(output_dir.resolve()), str(folder)],
|
||||
check=True, env={**os.environ, "SOURCE_DATE_EPOCH": str(stamps[0])})
|
||||
wheel = output_dir / f"{MODULE}-{version}-py3-none-any.whl"
|
||||
sdist = output_dir / f"{MODULE}-{version}.tar.gz"
|
||||
for path in (wheel, sdist):
|
||||
if not path.is_file():
|
||||
raise RuntimeError(f"uv build wrote no {path.name}")
|
||||
return wheel, sdist
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__.splitlines()[0])
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT,
|
||||
help="where the wheel and the sdist go (default: tmp/pypi-dist)")
|
||||
args = parser.parse_args()
|
||||
for path in build(args.output_dir):
|
||||
print(path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from common import run_cli
|
||||
|
||||
run_cli(main)
|
||||
@@ -39,7 +39,7 @@ from common import (
|
||||
)
|
||||
from nativemode import check_label
|
||||
import release_record
|
||||
from siteguides import install_command
|
||||
from siteguides import PACKAGE_COMMAND, install_command
|
||||
from validate_pr import LARGE_FILE_ISSUE_URL
|
||||
from largefiles import load_registered_paths
|
||||
import pr_checks
|
||||
@@ -649,6 +649,9 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json"
|
||||
install_command("sh"),
|
||||
"```",
|
||||
"",
|
||||
f"**With Python 3.8 or newer**: `{PACKAGE_COMMAND}` runs the same installer, and so"
|
||||
" does `pipx run retrobios`.",
|
||||
"",
|
||||
f"**A handheld, an SD card, or not sure?** [Pick the device]({SITE_URL}which-pack/):"
|
||||
" its page gives the folder and the command. A muOS, Onion, spruceOS, MinUI, KNULLI,"
|
||||
" ROCKNIX, Lakka or MiSTer SD card plugged into the computer is found by itself.",
|
||||
|
||||
@@ -112,6 +112,7 @@ from siteguides import (
|
||||
INSTALL_BASE,
|
||||
INSTALL_PS1,
|
||||
INSTALL_SH,
|
||||
PACKAGE_COMMAND,
|
||||
TERMINALS,
|
||||
TRUST_ITEMS,
|
||||
detected_systems as _detected_systems,
|
||||
@@ -11951,6 +11952,11 @@ def generate_which_pack() -> str:
|
||||
# handheld recording (sitesteps).
|
||||
box_tabs = (*all_tabs, "device")
|
||||
box_steps, box_demos = sitesteps.install_box(box_tabs)
|
||||
# Python is on most Linux and macOS machines: the package runs the same
|
||||
# installer there without the bootstrap.
|
||||
for tab in ("macos", "linux"):
|
||||
box_demos[tab] = [*box_demos[tab],
|
||||
f"With Python 3.8 or newer, `{PACKAGE_COMMAND}` does the same.", ""]
|
||||
lines = [
|
||||
"---",
|
||||
f"terms: {', '.join(IOS_TERMS)}",
|
||||
|
||||
@@ -393,6 +393,8 @@ INSTALL_BASE: str = _LOADED["install_base"]
|
||||
# The bootstraps the site serves (sitebootstrap.py writes them).
|
||||
INSTALL_SH: str = f"{INSTALL_BASE}/{SH_NAME}"
|
||||
INSTALL_PS1: str = f"{INSTALL_BASE}/{PS1_NAME}"
|
||||
# The same installer from PyPI (scripts/build_pypi.py builds the package).
|
||||
PACKAGE_COMMAND = "uvx retrobios"
|
||||
DEVICE_GROUPS: dict[str, tuple[str, str]] = _LOADED["device_groups"]
|
||||
GROUP_ANCHORS: dict[str, str] = _LOADED["group_anchors"]
|
||||
ALSO_RUNS: dict[str, tuple[tuple[str, str, str, str, str], ...]] = _LOADED["also_runs"]
|
||||
|
||||
Reference in new issue
Block a user