Skip to content

Github workflow for building binaries for windows-x86, macos-x86, macos-arm64; resolves #608#668

Merged
ilius merged 73 commits into
ilius:masterfrom
glowinthedark:nuitka-release
Aug 27, 2025
Merged

Github workflow for building binaries for windows-x86, macos-x86, macos-arm64; resolves #608#668
ilius merged 73 commits into
ilius:masterfrom
glowinthedark:nuitka-release

Conversation

@glowinthedark

@glowinthedark glowinthedark commented Aug 22, 2025

Copy link
Copy Markdown
Contributor

resolves #608

  • Nuitka binary builds for windows-x64, macos-x64, macos-arm64

  • for mac python-lzo and pyicu are compiled from sources in the github runners;

  • for windows precompiled WHL wheels are used; compiling dependencies inside the runner worked on windows with vcpkg for python-lzo, but failed for pyicu because the original pyicu sources break on windows and require patching as can be seen in https://github.com/cgohlke/pyicu-build/blob/main/PyICU.diff — overall this made the entire process too complicated and ugly, and for the moment decided to abandon the vcpkg approach and just use WHLs.

@ilius: the workflow uploads, and then downloads the artifacts and creates a release using the latest tag, if that is not desired then the release step can be disabled, for example by adding if:false after line:123, right below uses: ncipollo/release-action@v1.

The workflow is supposed to be invoked manually via workflow dispatch, and will use the latest tag (via git describe --abbrev=0).

  • @ilius: while testing the PR using ref: nuitka-release for action actions/checkout@v4 (it's used in 3 places!) since by default the action will use whatever is set as the default branch, i.e. master, etc. For testing the workflow in a different branch ref: should point to the correct branch name, e.g. ref: nuitka-release. If not run on the correct branch then the macOS runners will fail because of the missing icon res/pyglossary.icns and the scripts.

glwnd and others added 2 commits August 22, 2025 15:48
- nuitka binary builds for windows-x64, macos-x64, macos-arm64

- for mac python-lzo and pyicu are compiled from sources in the runners; for windows binary wheels are used; compiling on windows with vcpkg worked for python-lzo but failed for pyicu because the original pyicu sources break for windows and require patching as can be seen on https://github.com/cgohlke/pyicu-build/blob/main/PyICU.diff
Comment thread .github/workflows/binary-release.yml Outdated
Comment thread .github/workflows/binary-release.yml Outdated
Comment thread .github/workflows/binary-release.yml Outdated
Comment thread .github/workflows/binary-release.yml Outdated
Comment thread .github/workflows/binary-release.yml Outdated
@ilius

ilius commented Aug 22, 2025

Copy link
Copy Markdown
Owner

How can I confirm that your lzo builds are safe?
Linking to it in docs is one thing, but including it in the final binary is different.

I'm afraid you will have to build everything here in the action, except what you download from pypi.

@ilius

ilius commented Aug 22, 2025

Copy link
Copy Markdown
Owner

Also please move all pip commands to a new script.

@glowinthedark

glowinthedark commented Aug 22, 2025

Copy link
Copy Markdown
Contributor Author

How can I confirm that your lzo builds are safe? Linking to it in docs is one thing, but including it in the final binary is different.

they were built by forking https://github.com/jd-boyd/python-lzo:

Ideally I would rather prefer you to have your own forks for both python-lzo and pyciu and set up github actions that you fully control so that I can safely link to them rather than fiddling with the YAMLs and having to build them myself.

For macOS both python-lzo and pyicu are already compiled in the runner, it's only on windows where the things become tricky. The working windows WHL were generated using https://github.com/cgohlke/pyicu-build as the baseline. Some WHLs such as those for raspberrypi aarch64 I compiled on bare metal using pip wheel . since it was too tricky to crosscompile for aarch64.

@glowinthedark

glowinthedark commented Aug 22, 2025

Copy link
Copy Markdown
Contributor Author

How can I confirm that your lzo builds are safe? Linking to it in docs is one thing, but including it in the final binary is different.

I'm afraid you will have to build everything here in the action, except what you download from pypi.

@ilius in case you want to take up the full 100% source compilation for windows, here is the failed experiment which does successfully build python-lzo on windows, but fails with pyicu

vcpkg beat me, so I gave up, and I'm not a windows user anyway. My motivation was only to give something back to the community which happen to be windows users.

@ilius

ilius commented Aug 22, 2025

Copy link
Copy Markdown
Owner

Then please move the windows build to another branch completely.

@glowinthedark

Copy link
Copy Markdown
Contributor Author

Then please move the windows build to another branch completely.

@ilius: split and parked it as .github/workflows/build-win64.yml — I'd say this can be considered on-hold until you provide your own compiled WHLs or incorporate the entire logic from https://github.com/cgohlke/pyicu-build, since opaque binary modules from random repos like mine or anyone else's will always be a security risk, and no security guarantee is possible in this case; imo not being paranoid about this would be unwise.

@glowinthedark

glowinthedark commented Aug 23, 2025

Copy link
Copy Markdown
Contributor Author

@ilius please let me know what else do you think might be missing or needs removing; if you wish I can remove the windows build entirely until you find a way to handle the compiled modules

@glowinthedark

glowinthedark commented Aug 23, 2025

Copy link
Copy Markdown
Contributor Author

Update: turns out nuitka already fixed the tkinter plugin version, and the author added 9.0 so the plugin patch for TkinterPlugin.py is no longer needed as uv/pip will pull the latest develop branch:

@glowinthedark

glowinthedark commented Aug 23, 2025

Copy link
Copy Markdown
Contributor Author

@ilius: regarding pyicu and python-lzo it would be really be great if you could set up your own controlled WHLs also for the those who run pyglossary from source — currently these two dependencies are pretty much a show-stopper for those who do not have at hand the WHLs for their specific architecture and the matching python version, or don't have a properly configured msvc/gcc environment and need to follow steps from https://github.com/ilius/pyglossary/blob/master/doc/pyicu.md and https://github.com/ilius/pyglossary/blob/master/doc/lzo.md — I'd suspect that this is were many of them give up, and I've seen reports of users who complained about it in some dictionary-related forum threads which I moderate.

This would be helpful even for people running linux as not all of them have or want to install packages like build-essential, the required *-dev packages and headers, etc.

python-lzo

pyicu

@ilius

ilius commented Aug 23, 2025

Copy link
Copy Markdown
Owner

So when I push a tag to master, then create the release as before, then push to certain branch that creates 2 additional releases after that release?
So every release will be followed by extra 2 releases?
That's not good.

Can we add the dmg/exe file to the last existing release?

@ilius

ilius commented Aug 26, 2025

Copy link
Copy Markdown
Owner

Thanks.
Let's avoid unneeded vars like runner.os.

@ilius

ilius commented Aug 26, 2025

Copy link
Copy Markdown
Owner

Somehow I broke the Mac action.
Can you fix it?

@glowinthedark

glowinthedark commented Aug 26, 2025

Copy link
Copy Markdown
Contributor Author

Somehow I broke the Mac action. Can you fix it?

added a Makefile to build on an actual mac using the scripts for the workflow from .github/scripts/mac and it built fine and created the DMG, but 🤔 then on subsequent runs it started failing with the same error as on github (and then realized it happened after I renamed the executable to match the name PyGlossary same as in the github action)

&tldr;

  • root cause: app name cannot be PyGlossary (case doesn't matter) as this creates a collision between the binary executable PyGlossary and the pyglossary folder which in the macos bundle should be in the same location under /Contents/MacOS/pyglossary.
  • the solution was to revert to PyGlossaryTk or whichever you like better but not pyglossary — I remember getting burnt on this but that was many months ago and I already forgot 🤦 , this was in fact the reason why I had to set the app name to PyGlossaryTk at that time.

now the builds are fine for both windows and mac

@ilius

ilius commented Aug 26, 2025

Copy link
Copy Markdown
Owner

root cause: app name cannot be PyGlossary (case doesn't matter) as this creates a collision between the binary executable PyGlossary and the pyglossary folder which in the macos bundle should be in the same location under /Contents/MacOS/pyglossary.

Can't we just cd / change directory before creating the binary?!

@glowinthedark

glowinthedark commented Aug 27, 2025

Copy link
Copy Markdown
Contributor Author

Can't we just cd / change directory before creating the binary?!

It's about the structure of mac app bundles. The DIST_DIR contains three folders:

drwxr-xr-x 718636 staff 160 B  Tue Aug 26 17:03:34 2025  .
drwxr-xr-x 718636 staff 2.2 KB Tue Aug 26 22:01:12 2025  ..
drwxr-xr-x 718636 staff  96 B  Tue Aug 26 17:04:56 2025  PyGlossaryTK.app
drwxr-xr-x 718636 staff  49 KB Tue Aug 26 17:04:56 2025  PyGlossaryTK.build
drwxr-xr-x 718636 staff  64 B  Tue Aug 26 17:03:34 2025  PyGlossaryTK.dist

PyGlossaryTK.app is the macos app bundle — it's a regular folder, but on mac the .app suffix gets special treatment; it can be opened from Finder via right-click > Show Package Contents.

Inside the Contents/MacOS subfolder inside the .app bundle there is the pyglossary folder with resources and the PyGlossaryTK executable which requires that folder to be there.

PyGlossaryTK.app
└── Contents
    ├── _CodeSignature
    │   └── CodeResources
    ├── Info.plist
    ├── MacOS
    │   ├── pyglossary <<<<< ❗️#### FOLDER ####
    │       ├── langs
    │       ├── plugins
    │   ├── PyGlossaryTK <<<<< ❗️#### mac executable ####
    │   ├── Python
    │   ├── res
    └── Resources

Since macOS file system is case-insensitive there is no way to have both a pyglossary folder AND the PyGlossary executable file under PyGlossary.app/Contents. And this is what causes the mysterious error when manually copying assets that langs is not a folder.. And if the assets are not copied manually then the executable will not find them and the app won't work.

I suppose pyglossary could be refactored in order to avoid this situation and implement some special logic for accessing resources when run from a mac bundle, but that would probably be too much effort that doesn't really buy much. Normally, for native mac apps, Contents/MacOS would contain just the binary executables, and all resources would be under Resources folder, but in this case of python + nuitka the default logic is different, and it's probably the binary that needs to know how and where to access its resources.

You can get a rough idea of how mac bundles are organized by using Show Package Contents on some native Apple apps, but also on non-native apps that use Electron (e.g. vscode), Java (pycharm), etc.. Btw, specifically for python, Calibre might be a very good example of how they do packaging, although their project is too complex with lots of dependencies, and I do actually see a Python runtime in the bundle. With nuitka python only exists as a DLL and everything gets transpiled to cpp and then compiled to native code.

@glowinthedark

glowinthedark commented Aug 27, 2025

Copy link
Copy Markdown
Contributor Author

@ilius: some findings from grok prompting about how bigger projects organize resources:

  • in the simplest form the mac scenario for getting resources is using importlib.resources or something like os.path.abspath(os.path.join(exec_dir, '..', 'Resources'))

what some apps do

  • Calibre (e-book manager, 10M+ downloads): Uses environment variables like CALIBRE_RESOURCES_PATH (set via calibre-debug --paths) to point to resources, allowing overrides for dev/prod. Code in src/calibre/utils/resources.py loads icons/fonts via these paths. For cross-platform, it modularizes (e.g., devices/ for hardware, gui2/ for UI) and uses init_calibre to bootstrap paths. In bundles, it relies on the installer to set paths correctly.
  • Electrum (Bitcoin wallet): Defines a get_resources_path() in electrum/util.py that checks sys.frozen and falls back to _MEIPASS or package-relative (os.path.join(os.path.dirname(__file__), '..')). Resources like icons are in electrum/data/, bundled via PyInstaller.
  • Kivy (UI framework, used in apps like TouchTracer): In kivy/utils.py, resource_find() searches multiple paths, including env vars (KIVY_DATA_DIR), package data via importlib.resources, and bundle adjustments for macOS/iOS. It supports PyInstaller via _MEIPASS.
  • BeeWare (Toga/Briefcase for native apps): Leverages importlib.resources for package assets and Briefcase's bundler for platform-specific paths (e.g., macOS Resources via plist configs). Their docs emphasize abstracting via helpers to avoid OS-specific code.
  • General GitHub patterns: Searches for "python cross platform resource path function" show 1000s of repos (e.g., games, tools) using the sys.frozen + _MEIPASS + macOS tweak combo. High-star repos like youtube-dl-gui (wxPython-based) use similar for icons/downloads.

specifically for nuitka

  • Nuitka places resources in Contents/MacOS/ by default, not Contents/Resources/. This breaks macOS conventions and can cause issues.

  • adding resources:

    • --include-data-files = for single resource files
    • --include-data-dir = for entire folders
    • --include-package-data = for entire package (everything that is not python source or library)
  • also might be important: Nuitka scans the package's MANIFEST.in, setup.py, and pyproject.toml to identify data files using pkg_resources or importlib.resources patterns, so in theory, for an app that follows all conventions and defines the resources in its pyproject.toml it should be enough (ideally 😆) to call nuitka --standalone main.py --macos-create-app-bundle and it will figure out the rest. Another option could be omitting --macos-create-app-bundle and then put together the binaries and resources into an .app folder by hand with scripts 😅.

@ilius

ilius commented Aug 27, 2025

Copy link
Copy Markdown
Owner

Got it.
Thanks.
I think we can put the pyglossary folder in Resources directory and change the main.py file to append it to sys.path.
But this PR is getting too long, so I'm gonna wrap up and merge it. Let's continue in another PR.

@ilius ilius merged commit 51dbb5c into ilius:master Aug 27, 2025
9 checks passed
@glowinthedark

Copy link
Copy Markdown
Contributor Author

@ilius: made a pr against the 'official' python-lzo repo which adds the release step in the windows workflow: https://github.com/jd-boyd/python-lzo/pull/94/files — if jd-boyd accepts to merge it and use it to release the WHLs then pyglossary could use them for the windows build.

Or alternatively, maybe you would be interested in forking jd-boyd's repo and generate the WHLs in your own fork with the modified workflow https://github.com/glowinthedark/python-lzo/blob/patch-2/.github/workflows/build-windows.yml

@glowinthedark

Copy link
Copy Markdown
Contributor Author

@ilius jd-boyd accepted the pr for python-lzo WHLs so now the workflow for releasing the windows WHLs is in the main branch https://github.com/jd-boyd/python-lzo/blob/main/.github/workflows/build-windows.yml — this simplifies the steps for windows:

  1. fork https://github.com/jd-boyd/python-lzo
  2. run build-windows.yml via manual Run from Actions — it will prompt for the tag (will be created/update); the latest tag pushed to pypi was v1.16

@ilius ilius mentioned this pull request Nov 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Binary builds for Mac and Windows

2 participants