mirror of
https://github.com/thegatesbrowser/thegates.git
synced 2025-09-05 02:25:51 -04:00
deployment scripts
This commit is contained in:
parent
03fbf12b9a
commit
eff4c36456
2 changed files with 350 additions and 0 deletions
239
deployment/export_project.py
Normal file
239
deployment/export_project.py
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Automated Godot export script.
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- On Linux: exports release build for preset "Linux/X11" (export project)
|
||||||
|
and exports release pack for preset "Windows Desktop" (export pck).
|
||||||
|
- On macOS: exports release build for preset "macOS" (export project).
|
||||||
|
- On Windows: exports release build for preset "Windows Desktop" (export project).
|
||||||
|
|
||||||
|
All exports use the export paths defined in app/export_presets.cfg.
|
||||||
|
For pack export, the output .pck path is derived from the preset's export_path
|
||||||
|
by replacing ".exe" with ".pck" (or appending ".pck" if no extension found).
|
||||||
|
|
||||||
|
Special case: when exporting Windows pack from Linux host, override output path to
|
||||||
|
"/media/common/Projects/thegates-folder/AppBuilds/Windows/TheGates.pck".
|
||||||
|
|
||||||
|
Editor binaries to use:
|
||||||
|
- Linux: godot/bin/godot.linuxbsd.editor.dev.x86_64.llvm
|
||||||
|
- macOS: godot/bin/godot.macos.editor.dev.arm64
|
||||||
|
- Windows: godot/bin/godot.windows.editor.dev.x86_64.exe
|
||||||
|
|
||||||
|
Only release builds are exported (uses --export-release).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
APP_DIR = REPO_ROOT / "app"
|
||||||
|
EXPORT_PRESETS_PATH = APP_DIR / "export_presets.cfg"
|
||||||
|
|
||||||
|
|
||||||
|
LINUX_EDITOR_RELATIVE = Path("godot/bin/godot.linuxbsd.editor.dev.x86_64.llvm")
|
||||||
|
MACOS_EDITOR_RELATIVE = Path("godot/bin/godot.macos.editor.dev.arm64")
|
||||||
|
WINDOWS_EDITOR_RELATIVE = Path("godot/bin/godot.windows.editor.dev.x86_64.exe")
|
||||||
|
|
||||||
|
LINUX_WINDOWS_PCK_OVERRIDE_PATH = Path("/media/common/Projects/thegates-folder/AppBuilds/Windows/TheGates.pck")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExportPreset:
|
||||||
|
index: int
|
||||||
|
name: str
|
||||||
|
platform: Optional[str]
|
||||||
|
export_path: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
def read_export_presets(cfg_path: Path) -> Dict[str, ExportPreset]:
|
||||||
|
"""Parse Godot export_presets.cfg and return presets keyed by name.
|
||||||
|
|
||||||
|
We only care about fields under [preset.N]: name, platform, export_path.
|
||||||
|
"""
|
||||||
|
if not cfg_path.exists():
|
||||||
|
raise FileNotFoundError(f"export presets not found: {cfg_path}")
|
||||||
|
|
||||||
|
presets: Dict[str, ExportPreset] = {}
|
||||||
|
current: Optional[ExportPreset] = None
|
||||||
|
|
||||||
|
with cfg_path.open("r", encoding="utf-8") as f:
|
||||||
|
for raw_line in f:
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line.startswith("["):
|
||||||
|
# Starting a new section.
|
||||||
|
if line.startswith("[preset.") and line.endswith("]"):
|
||||||
|
try:
|
||||||
|
idx_str = line[len("[preset.") : -1]
|
||||||
|
idx = int(idx_str)
|
||||||
|
except ValueError:
|
||||||
|
current = None
|
||||||
|
else:
|
||||||
|
current = ExportPreset(index=idx, name="", platform=None, export_path=None)
|
||||||
|
else:
|
||||||
|
# Any non-preset section ends current preset parsing
|
||||||
|
current = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
if current is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith("name="):
|
||||||
|
current.name = line.split("=", 1)[1].strip().strip('"')
|
||||||
|
elif line.startswith("platform="):
|
||||||
|
current.platform = line.split("=", 1)[1].strip().strip('"')
|
||||||
|
elif line.startswith("export_path="):
|
||||||
|
current.export_path = line.split("=", 1)[1].strip().strip('"')
|
||||||
|
|
||||||
|
# Once we have a name, keep it indexed so later lookups are easy
|
||||||
|
if current.name:
|
||||||
|
presets[current.name] = current
|
||||||
|
|
||||||
|
return presets
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_editor_binary() -> Path:
|
||||||
|
system = platform.system()
|
||||||
|
if system == "Linux":
|
||||||
|
editor_rel = LINUX_EDITOR_RELATIVE
|
||||||
|
elif system == "Darwin":
|
||||||
|
editor_rel = MACOS_EDITOR_RELATIVE
|
||||||
|
elif system == "Windows":
|
||||||
|
editor_rel = WINDOWS_EDITOR_RELATIVE
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unsupported OS for this script: {system}")
|
||||||
|
|
||||||
|
editor_path = (REPO_ROOT / editor_rel).resolve()
|
||||||
|
if not editor_path.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"Godot editor binary not found: {editor_path}\n"
|
||||||
|
f"Expected relative path: {editor_rel}"
|
||||||
|
)
|
||||||
|
return editor_path
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd: List[str], cwd: Path) -> None:
|
||||||
|
print(f"Running: {shlex.join(cmd)}\n in: {cwd}")
|
||||||
|
subprocess.run(cmd, cwd=str(cwd), check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def derive_pck_path_from_export_path(export_path: str) -> Path:
|
||||||
|
path = Path(export_path)
|
||||||
|
if path.suffix.lower() == ".exe":
|
||||||
|
return path.with_suffix(".pck")
|
||||||
|
if path.suffix:
|
||||||
|
# Replace any existing extension with .pck
|
||||||
|
return path.with_suffix(".pck")
|
||||||
|
return path.with_suffix(".pck")
|
||||||
|
|
||||||
|
|
||||||
|
def export_linux_and_windows_pack(editor: Path, presets: Dict[str, ExportPreset]) -> None:
|
||||||
|
# Linux project export (uses export path defined in preset)
|
||||||
|
linux_preset_name = "Linux/X11"
|
||||||
|
if linux_preset_name not in presets:
|
||||||
|
raise KeyError(f"Preset not found: {linux_preset_name}")
|
||||||
|
|
||||||
|
run_cmd(
|
||||||
|
[str(editor), "--headless", "--export-release", linux_preset_name],
|
||||||
|
cwd=APP_DIR,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Windows pack export (override .pck path per requirement when on Linux)
|
||||||
|
windows_preset_name = "Windows Desktop"
|
||||||
|
win_preset = presets.get(windows_preset_name)
|
||||||
|
if win_preset is None:
|
||||||
|
raise KeyError(f"Preset not found: {windows_preset_name}")
|
||||||
|
|
||||||
|
# Use the override path instead of preset-defined path
|
||||||
|
pck_out_path = LINUX_WINDOWS_PCK_OVERRIDE_PATH
|
||||||
|
|
||||||
|
# Ensure output directory exists for the pack
|
||||||
|
pck_out_dir = Path(pck_out_path).parent
|
||||||
|
try:
|
||||||
|
pck_out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
except Exception:
|
||||||
|
# If directory is not creatable (e.g., Windows drive on Linux), let Godot handle/raise.
|
||||||
|
pass
|
||||||
|
|
||||||
|
run_cmd(
|
||||||
|
[
|
||||||
|
str(editor),
|
||||||
|
"--headless",
|
||||||
|
"--export-pack",
|
||||||
|
windows_preset_name,
|
||||||
|
str(pck_out_path),
|
||||||
|
],
|
||||||
|
cwd=APP_DIR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def export_macos(editor: Path, presets: Dict[str, ExportPreset]) -> None:
|
||||||
|
mac_preset_name = "macOS"
|
||||||
|
if mac_preset_name not in presets:
|
||||||
|
raise KeyError(f"Preset not found: {mac_preset_name}")
|
||||||
|
|
||||||
|
run_cmd(
|
||||||
|
[str(editor), "--headless", "--export-release", mac_preset_name],
|
||||||
|
cwd=APP_DIR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def export_windows(editor: Path, presets: Dict[str, ExportPreset]) -> None:
|
||||||
|
windows_preset_name = "Windows Desktop"
|
||||||
|
if windows_preset_name not in presets:
|
||||||
|
raise KeyError(f"Preset not found: {windows_preset_name}")
|
||||||
|
|
||||||
|
run_cmd(
|
||||||
|
[str(editor), "--headless", "--export-release", windows_preset_name],
|
||||||
|
cwd=APP_DIR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def import_project(editor: Path) -> None:
|
||||||
|
# Ensure all assets are imported prior to export
|
||||||
|
run_cmd([str(editor), "--headless", "--import"], cwd=APP_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
try:
|
||||||
|
presets = read_export_presets(EXPORT_PRESETS_PATH)
|
||||||
|
editor = ensure_editor_binary()
|
||||||
|
system = platform.system()
|
||||||
|
|
||||||
|
# Always import project before any export
|
||||||
|
import_project(editor)
|
||||||
|
|
||||||
|
if system == "Linux":
|
||||||
|
export_linux_and_windows_pack(editor, presets)
|
||||||
|
elif system == "Darwin":
|
||||||
|
export_macos(editor, presets)
|
||||||
|
elif system == "Windows":
|
||||||
|
export_windows(editor, presets)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unsupported OS: {system}")
|
||||||
|
|
||||||
|
print("All exports completed successfully.")
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Export command failed with exit code {e.returncode}")
|
||||||
|
return e.returncode
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
111
deployment/zip_builds_linux.py
Executable file
111
deployment/zip_builds_linux.py
Executable file
|
@ -0,0 +1,111 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Zip builder for TheGates builds.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 zip_builds.py 0.17.2
|
||||||
|
|
||||||
|
Creates, in-place:
|
||||||
|
Linux/TheGates_Linux_<version>.zip containing: TheGates.x86_64, sandbox/
|
||||||
|
Windows/TheGates_Windows_<version>.zip containing: TheGates.exe, TheGates.pck, sandbox/
|
||||||
|
|
||||||
|
By default, refuses to overwrite existing zip files. Use --force to overwrite.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from zipfile import ZipFile, ZIP_DEFLATED
|
||||||
|
|
||||||
|
|
||||||
|
def validate_version(version: str) -> None:
|
||||||
|
pattern = re.compile(r"^[0-9]+(\.[0-9]+){1,3}$")
|
||||||
|
if not pattern.match(version):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid version '{version}'. Expected format like 0.17.2"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_exists(path: Path) -> None:
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"Missing required path: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def zip_entries(base_dir: Path, entries: list[str], output_zip: Path, overwrite: bool) -> None:
|
||||||
|
if output_zip.exists():
|
||||||
|
if not overwrite:
|
||||||
|
raise FileExistsError(
|
||||||
|
f"Output already exists: {output_zip}. Use --force to overwrite."
|
||||||
|
)
|
||||||
|
output_zip.unlink()
|
||||||
|
|
||||||
|
# Ensure base directory exists
|
||||||
|
ensure_exists(base_dir)
|
||||||
|
|
||||||
|
with ZipFile(output_zip, mode="w", compression=ZIP_DEFLATED) as zf:
|
||||||
|
for entry_name in entries:
|
||||||
|
entry_path = base_dir / entry_name
|
||||||
|
ensure_exists(entry_path)
|
||||||
|
|
||||||
|
if entry_path.is_file():
|
||||||
|
# Store at top-level inside the archive
|
||||||
|
zf.write(entry_path, arcname=entry_name)
|
||||||
|
else:
|
||||||
|
# Walk directory and add files with relative paths rooted at base_dir
|
||||||
|
for file_path in entry_path.rglob("*"):
|
||||||
|
if file_path.is_file():
|
||||||
|
arcname = file_path.relative_to(base_dir)
|
||||||
|
zf.write(file_path, arcname=str(arcname))
|
||||||
|
|
||||||
|
|
||||||
|
def build_linux_zip(root: Path, version: str, overwrite: bool) -> Path:
|
||||||
|
linux_dir = root / "Linux"
|
||||||
|
output_zip = linux_dir / f"TheGates_Linux_{version}.zip"
|
||||||
|
entries = [
|
||||||
|
"TheGates.x86_64",
|
||||||
|
"sandbox",
|
||||||
|
]
|
||||||
|
zip_entries(linux_dir, entries, output_zip, overwrite)
|
||||||
|
return output_zip
|
||||||
|
|
||||||
|
|
||||||
|
def build_windows_zip(root: Path, version: str, overwrite: bool) -> Path:
|
||||||
|
windows_dir = root / "Windows"
|
||||||
|
output_zip = windows_dir / f"TheGates_Windows_{version}.zip"
|
||||||
|
entries = [
|
||||||
|
"TheGates.exe",
|
||||||
|
"TheGates.pck",
|
||||||
|
"sandbox",
|
||||||
|
]
|
||||||
|
zip_entries(windows_dir, entries, output_zip, overwrite)
|
||||||
|
return output_zip
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Zip Linux and Windows builds.")
|
||||||
|
parser.add_argument("version", help="App version, e.g. 0.17.2")
|
||||||
|
parser.add_argument(
|
||||||
|
"--force",
|
||||||
|
action="store_true",
|
||||||
|
help="Overwrite existing zip files if they exist.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
validate_version(args.version)
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
linux_zip = build_linux_zip(root, args.version, args.force)
|
||||||
|
print(f"Created: {linux_zip}")
|
||||||
|
|
||||||
|
windows_zip = build_windows_zip(root, args.version, args.force)
|
||||||
|
print(f"Created: {windows_zip}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Add table
Add a link
Reference in a new issue