Skip to content

Commit d6f5a94

Browse files
committed
fix(windows): bundle Tcl/Tk data in PyInstaller builds
Pin the Windows build toolchain and verify bundled Tcl/Tk data to prevent the configurator from failing at startup.
1 parent 6980784 commit d6f5a94

3 files changed

Lines changed: 39 additions & 7 deletions

File tree

.github/workflows/build_windows_macos.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ jobs:
2525
runs-on: 'windows-latest'
2626
strategy:
2727
matrix:
28-
python-version: ['3.14']
28+
# An exact version keeps the packaged Windows artifact reproducible.
29+
python-version: ['3.14.6']
2930

3031
outputs:
3132
hashes: ${{ steps.hash.outputs.hashes }}
@@ -85,6 +86,10 @@ jobs:
8586
cd ardupilot_methodic_configurator
8687
copy ..\\windows\\ardupilot_methodic_configurator.spec
8788
pyinstaller --clean ardupilot_methodic_configurator.spec
89+
if (-not (Test-Path "dist\\ardupilot_methodic_configurator\\_internal\\_tcl_data\\init.tcl") -or
90+
-not (Test-Path "dist\\ardupilot_methodic_configurator\\_internal\\_tk_data\\tk.tcl")) {
91+
throw "PyInstaller did not bundle the Tcl/Tk library data."
92+
}
8893
del ardupilot_methodic_configurator.spec
8994
9095
- name: Write the git commit hash to file
@@ -192,6 +197,8 @@ jobs:
192197
run: |
193198
python - <<'PY'
194199
import tkinter
200+
interpreter = tkinter.Tcl()
201+
print(f"Tcl library={interpreter.eval('info library')}")
195202
print(f"tkinter OK, TkVersion={tkinter.TkVersion}")
196203
PY
197204

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,21 @@ win_dist = [
103103
"pip==26.2",
104104
"pywin32==312",
105105
"pyinstaller==6.20.0",
106+
"pyinstaller-hooks-contrib==2026.6",
106107
"packaging==26.2",
107108
]
108109

109110
mac_dist = [
110111
"pip==26.2",
111112
"pyinstaller==6.20.0",
113+
"pyinstaller-hooks-contrib==2026.6",
112114
"packaging==26.2",
113115
]
114116

115117
linux_dist = [
116118
"pip==26.2",
117119
"pyinstaller==6.20.0",
120+
"pyinstaller-hooks-contrib==2026.6",
118121
"packaging==26.2",
119122
]
120123

@@ -143,7 +146,7 @@ download = "https://github.com/ArduPilot/MethodicConfigurator/releases"
143146
changelog = "https://github.com/ArduPilot/MethodicConfigurator/releases"
144147

145148
[tool.uv]
146-
required-version = ">=0.10.9"
149+
required-version = "==0.12.5"
147150

148151
[tool.setuptools]
149152
# Rely on discovery for just the top-level package and specify data via package-data

windows/ardupilot_methodic_configurator.spec

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,33 @@
22
# spec file for pyinstaller to build ardupilot_methodic_configurator for windows
33

44
from PyInstaller.utils.hooks import collect_submodules
5-
import certifi
6-
import os
7-
8-
# Path to certifi's CA bundle
9-
certifi_cacert = certifi.where()
5+
import certifi
6+
import os
7+
from pathlib import Path
8+
import sys
9+
10+
11+
def _find_tcl_tk_library(directory: Path, prefix: str) -> Path:
12+
"""Return the Tcl/Tk library directory shipped with the build Python."""
13+
library_file = "init.tcl" if prefix == "tcl" else "tk.tcl"
14+
candidates = sorted(
15+
(path for path in directory.glob(f"{prefix}*") if path.is_dir() and (path / library_file).is_file()),
16+
reverse=True,
17+
)
18+
if not candidates:
19+
raise SystemExit(f"Could not find {prefix} library data below {directory}")
20+
return candidates[0]
21+
22+
23+
# Set both variables before PyInstaller analyses tkinter so its built-in hook
24+
# collects the library data into _tcl_data and _tk_data. Without this, the
25+
# frozen application contains the DLLs but fails during the tkinter runtime hook.
26+
tcl_root = Path(sys.base_prefix) / "tcl"
27+
os.environ["TCL_LIBRARY"] = str(_find_tcl_tk_library(tcl_root, "tcl"))
28+
os.environ["TK_LIBRARY"] = str(_find_tcl_tk_library(tcl_root, "tk"))
29+
30+
# Path to certifi's CA bundle
31+
certifi_cacert = certifi.where()
1032
datas = [(certifi_cacert, "certifi")]
1133

1234
ardupilot_methodic_configuratorAny = Analysis(['__main__.py'],

0 commit comments

Comments
 (0)