mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
project studio
This commit is contained in:
@@ -128,3 +128,6 @@ runtime/tests/test_*
|
||||
# transient split-gen build dirs
|
||||
recompiler/build-splitgen/
|
||||
recompiler/build-*/
|
||||
|
||||
# Project Studio GUI venv (customtkinter)
|
||||
tools/new_project_layout/.venv/
|
||||
|
||||
@@ -14,7 +14,14 @@ See [`docs/GAME_PROJECT_SETUP.md`](../../docs/GAME_PROJECT_SETUP.md).
|
||||
|
||||
## Project Studio (migrate / update)
|
||||
|
||||
Shared Python library under `project_studio/` with CLI + tkinter GUI.
|
||||
Shared Python library under `project_studio/` with CLI + CustomTkinter GUI.
|
||||
CLI stays stdlib-only. First GUI launch auto-creates
|
||||
`tools/new_project_layout/.venv` and installs `requirements-gui.txt`
|
||||
(network once); later launches are silent.
|
||||
|
||||
```bash
|
||||
python3 tools/new_project_layout/project_studio_gui.py
|
||||
```
|
||||
|
||||
```bash
|
||||
# Audit layout gaps
|
||||
@@ -34,12 +41,31 @@ python3 tools/new_project_layout/migrate_project.py apply \
|
||||
--root /path/to/ApeEscapeRecomp \
|
||||
--disc /path/to/game.cue
|
||||
|
||||
# GUI
|
||||
# GUI (auto-bootstraps .venv + customtkinter on first run)
|
||||
python3 tools/new_project_layout/migrate_project.py gui
|
||||
# or
|
||||
python3 tools/new_project_layout/project_studio_gui.py
|
||||
|
||||
# Git / GitHub (also available as the GUI "Git / GitHub" tab)
|
||||
python3 tools/new_project_layout/migrate_project.py git status \
|
||||
--root /path/to/ApeEscapeRecomp
|
||||
python3 tools/new_project_layout/migrate_project.py git ensure-submodules \
|
||||
--root /path/to/ApeEscapeRecomp \
|
||||
--psxrecomp-branch master --recomp-ui-branch master
|
||||
python3 tools/new_project_layout/migrate_project.py git set-branch \
|
||||
--root /path/to/ApeEscapeRecomp --submodule psxrecomp --branch master
|
||||
python3 tools/new_project_layout/migrate_project.py git update-submodules \
|
||||
--root /path/to/ApeEscapeRecomp --remote
|
||||
python3 tools/new_project_layout/migrate_project.py git commit \
|
||||
--root /path/to/ApeEscapeRecomp -m "chore: bump submodules"
|
||||
python3 tools/new_project_layout/migrate_project.py git push \
|
||||
--root /path/to/ApeEscapeRecomp
|
||||
python3 tools/new_project_layout/migrate_project.py git release \
|
||||
--root /path/to/ApeEscapeRecomp --bump patch
|
||||
```
|
||||
|
||||
Git ops shell out to `git` / `gh` (no force-push, amend, or hook skips). Submodule **branch** in `.gitmodules` is for `update --remote`; CI builds the committed **gitlink SHAs**.
|
||||
|
||||
### Ops (subset)
|
||||
|
||||
| Op | Purpose |
|
||||
|
||||
@@ -5,6 +5,7 @@ Examples:
|
||||
python3 tools/new_project_layout/migrate_project.py audit --root ~/src/ApeEscapeRecomp
|
||||
python3 tools/new_project_layout/migrate_project.py plan --root ~/src/ApeEscapeRecomp
|
||||
python3 tools/new_project_layout/migrate_project.py apply --root ~/src/ApeEscapeRecomp --dry-run
|
||||
python3 tools/new_project_layout/migrate_project.py git status --root ~/src/ApeEscapeRecomp
|
||||
python3 tools/new_project_layout/migrate_project.py gui
|
||||
"""
|
||||
|
||||
|
||||
@@ -157,6 +157,168 @@ def cmd_gui(args: argparse.Namespace) -> int:
|
||||
return run_gui(initial_root=root)
|
||||
|
||||
|
||||
def _root_or_die(args: argparse.Namespace) -> Path | None:
|
||||
root = Path(args.root).expanduser().resolve()
|
||||
if not root.is_dir():
|
||||
print(f"error: not a directory: {root}", file=sys.stderr)
|
||||
return None
|
||||
return root
|
||||
|
||||
|
||||
def cmd_git_status(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import repo_status
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
st = repo_status(root)
|
||||
if args.json:
|
||||
print(json.dumps(st.to_dict(), indent=2))
|
||||
return 0 if st.is_git else 1
|
||||
print(f"Project Studio git v{__version__}")
|
||||
print(f" root: {st.root}")
|
||||
print(f" git: {st.is_git}")
|
||||
if not st.is_git:
|
||||
return 1
|
||||
print(f" branch: {st.branch}" + (f" → {st.upstream}" if st.upstream else ""))
|
||||
print(f" ahead/behind: {st.ahead}/{st.behind}")
|
||||
print(f" dirty: {st.dirty} (staged={st.staged} unstaged={st.unstaged} untracked={st.untracked})")
|
||||
print(f" origin: {st.remote_url or '(none)'}")
|
||||
print(f" gh: {st.gh_repo or ('available' if st.gh_available else 'missing')}")
|
||||
print()
|
||||
print("Submodules:")
|
||||
for s in st.submodules:
|
||||
mark = "OK" if s.present else "MISSING"
|
||||
print(
|
||||
f" [{mark}] {s.path:<12} branch={s.branch or '-':<16} "
|
||||
f"sha={s.sha or '-':<12} {s.url}"
|
||||
)
|
||||
if st.short_status:
|
||||
print()
|
||||
print("Status:")
|
||||
print(st.short_status)
|
||||
for n in st.notes:
|
||||
print(f"note: {n}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_git_ensure_submodules(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import ensure_known_submodules
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
results = ensure_known_submodules(
|
||||
root,
|
||||
psxrecomp_branch=args.psxrecomp_branch,
|
||||
recomp_ui_branch=args.recomp_ui_branch,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
failed = 0
|
||||
for r in results:
|
||||
print(f" [{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(f" {r.detail}")
|
||||
if not r.ok:
|
||||
failed += 1
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
def cmd_git_set_branch(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import set_repo_branch, set_submodule_branch
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
if args.submodule:
|
||||
r = set_submodule_branch(
|
||||
root, args.submodule, args.branch, dry_run=args.dry_run
|
||||
)
|
||||
else:
|
||||
r = set_repo_branch(
|
||||
root, args.branch, create=args.create, dry_run=args.dry_run
|
||||
)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def cmd_git_update_submodules(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import update_submodules
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
paths = [p.strip() for p in (args.paths or "").split(",") if p.strip()] or None
|
||||
r = update_submodules(
|
||||
root, paths=paths, remote=args.remote, dry_run=args.dry_run
|
||||
)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def cmd_git_pull(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import pull
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
r = pull(root, dry_run=args.dry_run)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def cmd_git_commit(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import commit_all
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
r = commit_all(root, args.message, dry_run=args.dry_run)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def cmd_git_push(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import push
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
r = push(root, dry_run=args.dry_run)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def cmd_git_release(args: argparse.Namespace) -> int:
|
||||
from project_studio.gitops import run_release_workflow
|
||||
|
||||
root = _root_or_die(args)
|
||||
if root is None:
|
||||
return 2
|
||||
r = run_release_workflow(
|
||||
root,
|
||||
version=args.version or "",
|
||||
bump=args.bump,
|
||||
publish=not args.no_publish,
|
||||
reuse_cached_emitters=not args.no_reuse_cached_emitters,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
print(f"[{'OK' if r.ok else 'FAIL'}] {r.message}")
|
||||
if r.detail:
|
||||
print(r.detail)
|
||||
return 0 if r.ok else 1
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
ap = argparse.ArgumentParser(
|
||||
prog="migrate_project",
|
||||
@@ -223,6 +385,75 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
p_gui.add_argument("--root", default=None, help="Optional initial game root")
|
||||
p_gui.set_defaults(func=cmd_gui)
|
||||
|
||||
# --- git / GitHub ---
|
||||
p_git = sub.add_parser("git", help="Git / GitHub ops on a game repo")
|
||||
git_sub = p_git.add_subparsers(dest="git_cmd", required=True)
|
||||
|
||||
def add_git_root(p: argparse.ArgumentParser) -> None:
|
||||
p.add_argument("--root", required=True, help="Game repository root")
|
||||
p.add_argument("--dry-run", action="store_true")
|
||||
|
||||
p_gs = git_sub.add_parser("status", help="Repo + submodule status")
|
||||
add_git_root(p_gs)
|
||||
p_gs.add_argument("--json", action="store_true")
|
||||
p_gs.set_defaults(func=cmd_git_status)
|
||||
|
||||
p_ge = git_sub.add_parser("ensure-submodules", help="Add psxrecomp + recomp-ui")
|
||||
add_git_root(p_ge)
|
||||
p_ge.add_argument("--psxrecomp-branch", default="master")
|
||||
p_ge.add_argument("--recomp-ui-branch", default="master")
|
||||
p_ge.set_defaults(func=cmd_git_ensure_submodules)
|
||||
|
||||
p_gb = git_sub.add_parser(
|
||||
"set-branch",
|
||||
help="Set game branch (checkout) or submodule tracking branch",
|
||||
)
|
||||
add_git_root(p_gb)
|
||||
p_gb.add_argument("--branch", required=True)
|
||||
p_gb.add_argument(
|
||||
"--submodule",
|
||||
help="Submodule path (e.g. psxrecomp). Omit to checkout game branch.",
|
||||
)
|
||||
p_gb.add_argument(
|
||||
"--create",
|
||||
action="store_true",
|
||||
help="Create/reset game branch (-B) when not using --submodule",
|
||||
)
|
||||
p_gb.set_defaults(func=cmd_git_set_branch)
|
||||
|
||||
p_gu = git_sub.add_parser("update-submodules", help="git submodule update")
|
||||
add_git_root(p_gu)
|
||||
p_gu.add_argument(
|
||||
"--remote",
|
||||
action="store_true",
|
||||
help="Update to remote tracking branch tip (then commit gitlinks)",
|
||||
)
|
||||
p_gu.add_argument("--paths", help="Comma-separated submodule paths")
|
||||
p_gu.set_defaults(func=cmd_git_update_submodules)
|
||||
|
||||
p_gpull = git_sub.add_parser("pull", help="git pull --ff-only")
|
||||
add_git_root(p_gpull)
|
||||
p_gpull.set_defaults(func=cmd_git_pull)
|
||||
|
||||
p_gc = git_sub.add_parser("commit", help="git add -A && git commit")
|
||||
add_git_root(p_gc)
|
||||
p_gc.add_argument("-m", "--message", required=True)
|
||||
p_gc.set_defaults(func=cmd_git_commit)
|
||||
|
||||
p_gpush = git_sub.add_parser("push", help="git push -u origin HEAD")
|
||||
add_git_root(p_gpush)
|
||||
p_gpush.set_defaults(func=cmd_git_push)
|
||||
|
||||
p_gr = git_sub.add_parser("release", help="gh workflow run release.yml")
|
||||
add_git_root(p_gr)
|
||||
p_gr.add_argument("--version", default="", help="Empty = auto-bump")
|
||||
p_gr.add_argument(
|
||||
"--bump", choices=("patch", "minor", "major"), default="patch"
|
||||
)
|
||||
p_gr.add_argument("--no-publish", action="store_true")
|
||||
p_gr.add_argument("--no-reuse-cached-emitters", action="store_true")
|
||||
p_gr.set_defaults(func=cmd_git_release)
|
||||
|
||||
return ap
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Launch the Project Studio GUI."""
|
||||
"""Launch the Project Studio GUI (CustomTkinter).
|
||||
|
||||
First run auto-bootstraps ``.venv`` from ``requirements-gui.txt``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user