Skip to content

Commit 158da96

Browse files
committed
fix(app): stabilize packaging, updates, cancel flow, and tray ui
1 parent d75f139 commit 158da96

16 files changed

Lines changed: 1091 additions & 952 deletions

.github/workflows/build.yml

Lines changed: 37 additions & 185 deletions
Large diffs are not rendered by default.

.github/workflows/release.yml

Lines changed: 45 additions & 182 deletions
Large diffs are not rendered by default.

build.py

Lines changed: 89 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@
77
python build.py # Build with defaults
88
APP_VERSION=1.5 python build.py # Build with specific version
99
"""
10-
import sys
1110
import os
1211
import platform
1312
import shutil
1413
import subprocess
14+
import sys
15+
import textwrap
1516
from pathlib import Path
1617

1718
# Project root
1819
PROJECT_ROOT = Path(__file__).parent
1920
BUILD_DIR = PROJECT_ROOT / "build_nuitka"
2021
DIST_DIR = PROJECT_ROOT / "dist"
2122
OUTPUT_NAME = "TranslatorHoi4"
23+
BUILD_META_FILE = PROJECT_ROOT / "translatorhoi4" / "_build_meta.py"
2224

2325
# Get version from environment or git
2426
APP_VERSION = os.environ.get("APP_VERSION", "dev")
@@ -33,7 +35,7 @@
3335
"aiohttp",
3436
"requests",
3537
"qfluentwidgets",
36-
"jinja2", # Prevent Nuitka inline copy conflict with pkg_resources
38+
"jinja2",
3739
]
3840

3941
# Packages to follow imports (optional/lazily loaded - Nuitka includes only what's actually used)
@@ -75,20 +77,16 @@
7577

7678

7779
def parse_version_tuple(version_str):
78-
"""Convert version string to tuple for Nuitka --file-version.
79-
80-
Nuitka requires file-version to be a numeric tuple like 1.5.0.0
81-
"""
80+
"""Convert version string to tuple for Nuitka --file-version."""
8281
parts = version_str.replace("v", "").split(".")
8382
numeric_parts = []
84-
for p in parts:
83+
for part in parts:
8584
try:
86-
numeric_parts.append(int(p))
85+
numeric_parts.append(int(part))
8786
except ValueError:
8887
break
8988
if not numeric_parts:
90-
return None # Invalid version like "dev"
91-
# Pad to max 4 parts
89+
return None
9290
while len(numeric_parts) < 4:
9391
numeric_parts.append(0)
9492
return ".".join(str(x) for x in numeric_parts[:4])
@@ -107,19 +105,15 @@ def get_nuitka_command():
107105
"--include-data-dir=assets=assets",
108106
]
109107

110-
# Include packages
111108
for pkg in INCLUDE_PACKAGES:
112109
cmd.append(f"--include-package={pkg}")
113110

114-
# Follow imports (only include actually used modules from these packages)
115111
for pkg in FOLLOW_IMPORTS:
116112
cmd.append(f"--follow-import-to={pkg}")
117113

118-
# Exclude modules
119114
for mod in EXCLUDE_MODULES:
120115
cmd.append(f"--nofollow-import-to={mod}")
121116

122-
# Version info - only include if we have a valid numeric version
123117
file_version = parse_version_tuple(APP_VERSION)
124118
if file_version:
125119
cmd.extend(
@@ -133,31 +127,25 @@ def get_nuitka_command():
133127
]
134128
)
135129

136-
# Parallel compilation (use all available CPU cores)
137130
cpu_count = os.cpu_count() or 1
138131
cmd.append(f"--jobs={cpu_count}")
139132

140-
# macOS: Use clang directly as compiler
141133
if sys.platform == "darwin":
142134
cmd.append("--clang")
143135

144-
# Optimization
145136
cmd.extend(
146137
[
147138
"--assume-yes-for-downloads",
148139
"--remove-output",
149140
]
150141
)
151142

152-
# Platform-specific options
153143
if sys.platform == "win32":
154144
cmd.extend(
155145
[
156146
"--windows-icon-from-ico=assets/icon.png",
157147
"--windows-console-mode=disable",
158-
# Windows: disable LTO for faster builds (MSVC LTO is very slow)
159148
"--lto=no",
160-
# Disable clcache - it fails with paths containing spaces on CI
161149
"--disable-ccache",
162150
]
163151
)
@@ -167,67 +155,51 @@ def get_nuitka_command():
167155
"--macos-create-app-bundle",
168156
"--macos-app-icon=assets/icon.png",
169157
"--macos-app-name=TranslatorHoi4",
170-
# macOS: let Nuitka picks the best LTO for clang
171158
"--lto=auto",
172-
# ccache works automatically via clang symlinks set up in CI
173159
]
174160
)
175161
elif sys.platform == "linux":
176162
cmd.extend(
177163
[
178164
"--linux-icon=assets/icon.png",
179-
# Linux: keep full LTO (gcc handles it well)
180165
"--lto=yes",
181-
# ccache works automatically via gcc symlinks set up in CI
182166
]
183167
)
184168

185-
# Add the entry point
186-
# Use --module-name-base to ensure .dist folder is named after OUTPUT_NAME
187169
cmd.append(str(PROJECT_ROOT / "translatorhoi4" / "app.py"))
188-
189170
return cmd
190171

191172

192173
def find_nuitka_output():
193-
"""Find the actual Nuitka output directory.
194-
195-
Nuitka names the .dist folder after the entry point script (e.g. app.dist),
196-
not after --output-filename. This function searches for the correct path.
197-
"""
174+
"""Find the actual Nuitka output directory."""
198175
if not BUILD_DIR.exists():
199176
return None
200177

201-
# Platform-specific expected outputs
202178
if sys.platform == "darwin":
203-
# macOS: look for .app bundle
204179
candidates = [
205180
BUILD_DIR / f"{OUTPUT_NAME}.app",
206181
BUILD_DIR / "app.app",
207182
]
208-
# Also check inside any *.dist folder for .app
209-
for d in BUILD_DIR.iterdir():
210-
if d.is_dir() and d.name.endswith(".dist"):
211-
app_in_dist = d / f"{OUTPUT_NAME}.app"
183+
for directory in BUILD_DIR.iterdir():
184+
if directory.is_dir() and directory.name.endswith(".dist"):
185+
app_in_dist = directory / f"{OUTPUT_NAME}.app"
212186
if app_in_dist.exists():
213187
return app_in_dist
214-
for c in candidates:
215-
if c.exists():
216-
return c
188+
for candidate in candidates:
189+
if candidate.exists():
190+
return candidate
217191
else:
218-
# Windows/Linux: look for .dist folder
219192
candidates = [
220193
BUILD_DIR / f"{OUTPUT_NAME}.dist",
221194
BUILD_DIR / "app.dist",
222195
]
223-
for c in candidates:
224-
if c.exists() and c.is_dir():
225-
return c
196+
for candidate in candidates:
197+
if candidate.exists() and candidate.is_dir():
198+
return candidate
226199

227-
# Fallback: find any *.dist or *.app in BUILD_DIR
228-
for d in BUILD_DIR.iterdir():
229-
if d.is_dir() and (d.name.endswith(".dist") or d.name.endswith(".app")):
230-
return d
200+
for directory in BUILD_DIR.iterdir():
201+
if directory.is_dir() and (directory.name.endswith(".dist") or directory.name.endswith(".app")):
202+
return directory
231203

232204
return None
233205

@@ -238,17 +210,52 @@ def clean_build_dirs():
238210
shutil.rmtree(BUILD_DIR)
239211
BUILD_DIR.mkdir(exist_ok=True)
240212

241-
# Clean old dist
242213
dist_path = DIST_DIR / OUTPUT_NAME
243214
if dist_path.exists():
244215
shutil.rmtree(dist_path)
245216

246-
# Clean old macOS .app bundle if present
247217
app_path = DIST_DIR / f"{OUTPUT_NAME}.app"
248218
if app_path.exists():
249219
shutil.rmtree(app_path)
250220

251221

222+
def _normalize_arch(machine: str) -> str:
223+
machine = machine.lower()
224+
if machine in {"amd64", "x86_64", "x64"}:
225+
return "x64"
226+
if machine in {"arm64", "aarch64"}:
227+
return "arm64"
228+
return machine
229+
230+
231+
def write_build_metadata() -> str | None:
232+
"""Write temporary embedded build metadata for packaged runs."""
233+
previous = None
234+
if BUILD_META_FILE.exists():
235+
previous = BUILD_META_FILE.read_text(encoding="utf-8")
236+
237+
content = textwrap.dedent(
238+
f"""\
239+
# Auto-generated by build.py
240+
BUILD_VERSION = {APP_VERSION!r}
241+
BUILD_CHANNEL = {"release" if APP_VERSION != "dev" else "dev"!r}
242+
BUILD_PLATFORM = {sys.platform!r}
243+
BUILD_ARCH = {_normalize_arch(platform.machine())!r}
244+
"""
245+
)
246+
BUILD_META_FILE.write_text(content, encoding="utf-8")
247+
return previous
248+
249+
250+
def restore_build_metadata(previous: str | None) -> None:
251+
"""Restore build metadata file to its original state."""
252+
if previous is None:
253+
if BUILD_META_FILE.exists():
254+
BUILD_META_FILE.unlink()
255+
return
256+
BUILD_META_FILE.write_text(previous, encoding="utf-8")
257+
258+
252259
def check_macos_openssl():
253260
if sys.platform != "darwin":
254261
return
@@ -274,7 +281,6 @@ def check_macos_openssl():
274281

275282

276283
def main():
277-
# Fix Windows console encoding issues
278284
if sys.platform == "win32" and hasattr(sys.stdout, "reconfigure"):
279285
sys.stdout.reconfigure(encoding="utf-8")
280286

@@ -284,48 +290,37 @@ def main():
284290

285291
check_macos_openssl()
286292
clean_build_dirs()
287-
288-
cmd = get_nuitka_command()
289-
print(f"\nRunning Nuitka...")
290-
291-
# Execute Nuitka with proper argument handling
292-
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
293-
294-
if result.returncode != 0:
295-
print("ERROR: Build failed!", file=sys.stderr)
296-
sys.exit(1)
297-
298-
# Move output to dist directory
299-
DIST_DIR.mkdir(exist_ok=True)
300-
301-
# Find actual Nuitka output (handles app.dist vs TranslatorHoi4.dist naming)
302-
output_path = find_nuitka_output()
303-
304-
if output_path is None:
305-
print("ERROR: Build output not found!", file=sys.stderr)
306-
print(f"Searched in: {BUILD_DIR}", file=sys.stderr)
307-
if BUILD_DIR.exists():
308-
print(f"Build dir contents: {list(BUILD_DIR.iterdir())}", file=sys.stderr)
309-
sys.exit(1)
310-
311-
# Determine final path based on platform
312-
if sys.platform == "darwin":
313-
final_path = DIST_DIR / f"{OUTPUT_NAME}.app"
314-
else:
315-
final_path = DIST_DIR / OUTPUT_NAME
316-
317-
# Remove existing output if present
318-
if final_path.exists():
319-
shutil.rmtree(final_path)
320-
shutil.move(str(output_path), str(final_path))
321-
print(f"\n✓ Build successful!")
322-
print(f"Output: {final_path}")
323-
324-
# Print size info
325-
total_size = sum(
326-
f.stat().st_size for f in final_path.rglob("*") if f.is_file()
327-
)
328-
print(f"Total size: {total_size / (1024 * 1024):.1f} MB")
293+
previous_build_meta = write_build_metadata()
294+
295+
try:
296+
cmd = get_nuitka_command()
297+
print("\nRunning Nuitka...")
298+
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
299+
300+
if result.returncode != 0:
301+
print("ERROR: Build failed!", file=sys.stderr)
302+
sys.exit(1)
303+
304+
DIST_DIR.mkdir(exist_ok=True)
305+
output_path = find_nuitka_output()
306+
if output_path is None:
307+
print("ERROR: Build output not found!", file=sys.stderr)
308+
print(f"Searched in: {BUILD_DIR}", file=sys.stderr)
309+
if BUILD_DIR.exists():
310+
print(f"Build dir contents: {list(BUILD_DIR.iterdir())}", file=sys.stderr)
311+
sys.exit(1)
312+
313+
final_path = DIST_DIR / (f"{OUTPUT_NAME}.app" if sys.platform == "darwin" else OUTPUT_NAME)
314+
if final_path.exists():
315+
shutil.rmtree(final_path)
316+
shutil.move(str(output_path), str(final_path))
317+
318+
print("\nBuild successful!")
319+
print(f"Output: {final_path}")
320+
total_size = sum(file.stat().st_size for file in final_path.rglob("*") if file.is_file())
321+
print(f"Total size: {total_size / (1024 * 1024):.1f} MB")
322+
finally:
323+
restore_build_metadata(previous_build_meta)
329324

330325

331326
if __name__ == "__main__":

packaging/build-setup.ps1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
param(
2+
[Parameter(Mandatory = $true)][string]$Version,
3+
[Parameter(Mandatory = $true)][string]$Architecture,
4+
[Parameter(Mandatory = $true)][string]$SourceDir
5+
)
6+
7+
$ErrorActionPreference = "Stop"
8+
9+
choco install innosetup --no-progress
10+
python -m pip install Pillow
11+
12+
python -c @"
13+
from PIL import Image
14+
img = Image.open('assets/icon.png')
15+
img.save('assets/icon.ico', format='ICO', sizes=[(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)])
16+
"@
17+
18+
if (-not (Test-Path "assets\icon.ico")) {
19+
throw "Icon file assets\icon.ico not found after conversion"
20+
}
21+
22+
$outputStem = if ($Architecture -eq "arm64") { "TranslatorHoi4_Setup_arm64" } else { "TranslatorHoi4_Setup" }
23+
& iscc `
24+
"/DAPP_VERSION=$Version" `
25+
"/DAPP_ARCH=$Architecture" `
26+
"/DAPP_SOURCE_DIR=$SourceDir" `
27+
"/DAPP_OUTPUT_STEM=$outputStem" `
28+
packaging\translatorhoi4-setup.iss

packaging/create-rpm.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ mkdir -p "$RPMBUILD"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
3434

3535
# Create source tarball
3636
SOURCE_NAME="${PACKAGE_NAME}-${VERSION}"
37-
tar -czf "$RPMBUILD/SOURCES/${SOURCE_NAME}.tar.gz" -C "$(dirname "$DIST_PATH")" "$(basename "$DIST_PATH")"
37+
SOURCE_ROOT="$RPMBUILD/${SOURCE_NAME}"
38+
mkdir -p "$SOURCE_ROOT"
39+
cp -a "$DIST_PATH"/. "$SOURCE_ROOT/"
40+
tar -czf "$RPMBUILD/SOURCES/${SOURCE_NAME}.tar.gz" -C "$RPMBUILD" "${SOURCE_NAME}"
3841

3942
# Create spec file
4043
cat > "$RPMBUILD/SPECS/${PACKAGE_NAME}.spec" << EOF
@@ -61,6 +64,9 @@ Supported games:
6164
- Europa Universalis 4 (EU4)
6265
- Stellaris
6366
67+
%prep
68+
%setup -q
69+
6470
%install
6571
mkdir -p %{buildroot}/opt/translatorhoi4
6672
mkdir -p %{buildroot}/usr/bin
@@ -110,6 +116,8 @@ EOF
110116
# Build RPM
111117
rpmbuild --define "_topdir $RPMBUILD" \
112118
--define "_builddir $RPMBUILD/BUILD" \
119+
--define "_target_cpu $RPM_ARCH" \
120+
--target "$RPM_ARCH" \
113121
-bb "$RPMBUILD/SPECS/${PACKAGE_NAME}.spec"
114122

115123
# Copy result

0 commit comments

Comments
 (0)