From a741edc8d61a3e914f76fcb2d662cfa921fed44a Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Fri, 2 Oct 2026 23:44:53 -0400 Subject: [PATCH] The Windows build names its product and version, as SignPath requires SignPath Foundation signs a file only when it carries a product name and version, and their signing configuration checks both. The .exe had neither: its Properties showed no Details at all. The spec now builds a version resource from the package's own __version__ (MeshTerm, 0.10.2, the author, the copyright line), on Windows only. Checked on a local freeze: Windows reads every field back, and the binary still runs. Co-Authored-By: Claude Opus 5.5 (1M context) --- packaging/meshterm.spec | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/packaging/meshterm.spec b/packaging/meshterm.spec index 144a998..f57b7c9 100644 --- a/packaging/meshterm.spec +++ b/packaging/meshterm.spec @@ -9,6 +9,7 @@ # Built on the machine it targets. There is no cross-compiling here — the Windows build # comes off a Windows runner, the macOS one off macOS. That is what the workflow is for. +import re import sys from PyInstaller.utils.hooks import collect_submodules @@ -34,6 +35,61 @@ third_party_notices = notices.write_third_party_notices( os.path.join(workpath, "THIRD-PARTY-NOTICES.txt") ) +# The Windows build's version resource: the Details tab of the file's Properties, and the +# name and version Windows shows for it. Without one the .exe is just "meshterm.exe" with +# no product behind it, and SignPath refuses to sign it — their signing configuration +# checks the product name and version written here, so a file that doesn't carry them +# can't be passed off as a MeshTerm release. Built from the package's own `__version__` +# rather than kept as a second copy that would drift. The other platforms have no such +# resource, and PyInstaller warns if handed one there. +version_info = None +if sys.platform == "win32": + from PyInstaller.utils.win32.versioninfo import ( + FixedFileInfo, + StringFileInfo, + StringStruct, + StringTable, + VarFileInfo, + VarStruct, + VSVersionInfo, + ) + + # The checkout being frozen, not whichever MeshTerm the interpreter happens to have + # installed: `Analysis` below reads its sources from here too. + sys.path.insert(0, os.path.dirname(SPECPATH)) + import meshterm # noqa: E402 (import must follow the sys.path edit above) + + # The resource's fixed half holds exactly four numbers: 0.10.2 → (0, 10, 2, 0). A + # suffix such as `rc1` has nowhere to go there and is dropped; the text fields below + # keep the version exactly as written. + numbers = [int(n) for n in re.match(r"\d+(?:\.\d+)*", meshterm.__version__)[0].split(".")] + numbers = tuple((numbers + [0, 0, 0, 0])[:4]) + version_info = VSVersionInfo( + ffi=FixedFileInfo(filevers=numbers, prodvers=numbers), + kids=[ + StringFileInfo( + [ + # 0409 is US English and 04B0 says the strings are Unicode — the + # pairing nearly every Windows program declares. + StringTable( + "040904B0", + [ + StringStruct("CompanyName", meshterm.__author__), + StringStruct("FileDescription", "MeshTerm"), + StringStruct("FileVersion", meshterm.__version__), + StringStruct("InternalName", "meshterm"), + StringStruct("LegalCopyright", meshterm.copyright_notice()), + StringStruct("OriginalFilename", "meshterm.exe"), + StringStruct("ProductName", "MeshTerm"), + StringStruct("ProductVersion", meshterm.__version__), + ], + ) + ] + ), + VarFileInfo([VarStruct("Translation", [0x0409, 1200])]), + ], + ) + # The assets have to land at `meshterm/assets`, because `ui/about.py` finds them with # `Path(__file__).parent.parent / "assets"` and that path has to keep resolving inside # the bundle exactly as it does in a checkout. @@ -105,4 +161,5 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, + version=version_info, )