-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathbuild.py
More file actions
142 lines (105 loc) · 4.22 KB
/
Copy pathbuild.py
File metadata and controls
142 lines (105 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env python3
"""
Build the OptiScaler-GUI portable Windows distribution.
This script intentionally has one build path:
1. clean build outputs,
2. run PyInstaller with build_executable.spec,
3. copy runtime files that must live next to the executable,
4. optionally create the release ZIP.
"""
from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
DIST_DIR = PROJECT_ROOT / "dist"
BUILD_DIR = PROJECT_ROOT / "build"
PORTABLE_DIR = DIST_DIR / "OptiScaler-GUI-Portable"
SPEC_FILE = PROJECT_ROOT / "build_executable.spec"
def run(command: list[str]) -> None:
subprocess.run(command, cwd=PROJECT_ROOT, check=True)
def ensure_pyinstaller() -> None:
check = subprocess.run(
[sys.executable, "-m", "PyInstaller", "--version"],
cwd=PROJECT_ROOT,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if check.returncode == 0:
return
run([sys.executable, "-m", "pip", "install", "--upgrade", "--force-reinstall", "pyinstaller"])
check = subprocess.run(
[sys.executable, "-m", "PyInstaller", "--version"],
cwd=PROJECT_ROOT,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if check.returncode != 0:
raise RuntimeError("PyInstaller is installed but cannot be run with this Python interpreter")
def clean() -> None:
for path in (DIST_DIR, BUILD_DIR):
if path.exists():
shutil.rmtree(path)
for pycache in PROJECT_ROOT.rglob("__pycache__"):
if pycache.is_dir():
shutil.rmtree(pycache)
def find_7z() -> Path | None:
candidates = [
PROJECT_ROOT / "7z.exe",
Path(r"C:\Program Files\7-Zip\7z.exe"),
Path(r"C:\Program Files (x86)\7-Zip\7z.exe"),
]
for candidate in candidates:
if candidate.exists():
return candidate
path_match = shutil.which("7z")
return Path(path_match) if path_match else None
def build() -> None:
if not SPEC_FILE.exists():
raise FileNotFoundError(f"Missing PyInstaller spec: {SPEC_FILE}")
ensure_pyinstaller()
run([sys.executable, "-m", "PyInstaller", str(SPEC_FILE), "--clean", "--noconfirm"])
if not PORTABLE_DIR.exists():
raise FileNotFoundError(f"Expected portable output was not created: {PORTABLE_DIR}")
(PORTABLE_DIR / "cache").mkdir(exist_ok=True)
for filename in ("README.md", "CHANGELOG.md", "requirements.txt"):
src = PROJECT_ROOT / filename
if src.exists():
shutil.copy2(src, PORTABLE_DIR / filename)
seven_zip = find_7z()
if not seven_zip:
raise FileNotFoundError(
"7z.exe is required for current OptiScaler .7z archives. "
"Install 7-Zip or place 7z.exe in the repository root before building."
)
shutil.copy2(seven_zip, PORTABLE_DIR / "7z.exe")
def make_zip(version: str | None) -> Path:
tag = version or "dev"
archive_base = DIST_DIR / f"OptiScaler-GUI-{tag}-Portable"
archive_path = Path(shutil.make_archive(str(archive_base), "zip", DIST_DIR, PORTABLE_DIR.name))
return archive_path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Build OptiScaler-GUI portable distribution")
parser.add_argument("--no-clean", action="store_true", help="Do not remove build/ and dist/ first")
parser.add_argument("--zip", action="store_true", help="Create a portable ZIP after building")
parser.add_argument("--version", help="Version/tag for ZIP filename, e.g. v0.4.3")
return parser.parse_args()
def main() -> None:
args = parse_args()
if not (PROJECT_ROOT / "src" / "main.py").exists():
raise FileNotFoundError("Run build.py from the OptiScaler-GUI repository root")
if not args.no_clean:
clean()
build()
print(f"Portable build: {PORTABLE_DIR}")
print(f"Executable: {PORTABLE_DIR / 'OptiScaler-GUI.exe'}")
print(f"Bundled 7z.exe: {(PORTABLE_DIR / '7z.exe').exists()}")
if args.zip:
archive = make_zip(args.version)
size_mb = archive.stat().st_size / (1024 * 1024)
print(f"ZIP: {archive} ({size_mb:.1f} MB)")
if __name__ == "__main__":
main()