Skip to content

Expand GitHub workflow to build multi platform release - #1

Merged
XenuIsWatching merged 21 commits into
XenuIsWatching:masterfrom
XargonWan:master
Sep 14, 2026
Merged

XenuIsWatching merged 21 commits into
XenuIsWatching:masterfrom
XargonWan:master

Conversation

@XargonWan

@XargonWan XargonWan commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Features

New plaforms added

These platform are added as experimental releases:

  • linux_x64-86 (tested)
  • linux_arm64 (untested)
  • macos_arm64 (tested)

Publishing

Release is published as a draft release and includes a templated do be manually edidte before push.
(based on 0.3.0 release notes).

Key generation

Android: if the signing key the workflow won't stuck: a key will be generated on the fly: the application will appear as a different one, good for testing for contributors to don't mess with the original installation.
A warning in the workflow summary will be posted.
Same for MacOS.

SideQuest

SideQuest is no more blocking: a warning in the workflow summary will be posted instead.

Examples

Example release

Can be found here

Workflow summary

image

NOTES

  • Without cache the workflow will take 1h17 minutes circa, after that the entire process will take around 7 minutes.
    The cache will be generated at first successful workflow (release is not needed for cache generation).

@XenuIsWatching

Copy link
Copy Markdown
Owner

can you rebase and remove the merge commit

@XargonWan

Copy link
Copy Markdown
Contributor Author

can you rebase and remove the merge commit

Should be done

@XargonWan
XargonWan marked this pull request as ready for review August 24, 2026 07:00
@XenuIsWatching

Copy link
Copy Markdown
Owner

We've also got some fork'ed (vendored) libretro cores that have some custom patches on top of them like (mgba, dolphin, azahar. pcsx_rearmed) those will probably need a build as well as they just build android/windows for now. I can easily build them on Window's WSL for linux x86 and maybe linux arm as well. I'll have to look in to how those can be build for mac as well

@XargonWan

Copy link
Copy Markdown
Contributor Author

We've also got some fork'ed (vendored) libretro cores that have some custom patches on top of them like (mgba, dolphin, azahar. pcsx_rearmed) those will probably need a build as well as they just build android/windows for now. I can easily build them on Window's WSL for linux x86 and maybe linux arm as well. I'll have to look in to how those can be build for mac as well

Yeah I flagged some systems, like Mac, as experimental because I knew were unchecked/incomplete.
So I guess in this case we can integrate this build later on.

@XenuIsWatching
XenuIsWatching force-pushed the master branch 2 times, most recently from 2166445 to 2d11629 Compare September 2, 2026 06:09
XargonWan and others added 21 commits September 14, 2026 14:04
… in release.yml

Extend the upstream release workflow (preflight/quest/windows/release) with two
new jobs, porting the platform tricks from the old implementation but fixing the
parts that were broken there:

- linux job (blocking, matrix): x86_64 -> ubuntu-24.04 (AppImage), arm64 ->
  ubuntu-24.04-arm (tar.gz). Builds both debug+release so the editor can load the
  classes, downloads the arch-correct Godot editor, builds SDL3 3.2.16 from source
  for arm64 (the vendored SDL3 is a Windows dev package with no source), fetches
  the arm64 libpdfium, patches libretro-godot and godot-pdfium SConscripts to be
  arch-aware, and patches godot-cpp for long ar command lines (TEMPFILE).
- macos job (experimental, continue-on-error): builds arm64 + x86_64 dylibs and
  lipos them into universal binaries, exports a universal .app, ad-hoc codesigns
  with entitlements (disable-library-validation, allow-unsigned-executable-memory,
  allow-jit), packages a zip.
- release job now needs [preflight, quest, windows, linux, macos].

Also:
- export_presets.cfg: add Linux and macOS presets (macOS universal, s3tc+etc2,
  codesign+entitlements), update runnable_presets.
- entitlements/macos.entitlements: new plist for ad-hoc signing.
- .gdextension files: add linux.arm64 and macos.universal entries (universal
  listed before per-arch so Godot's tie-break picks it).
- README: document the new platforms and the Linux libvlc (vlc-libs) runtime
  dependency for DVD/VHS playback.
…t runs

- checkout v4->v7, setup-java v4->v5, cache v4->v6, upload-artifact v4->v7,
  download-artifact v4->v8: clears the 'Node.js 20 is deprecated' warnings.
- quest: a missing ANDROID_KEYSTORE_BASE64 now only hard-fails tag/publish
  runs; a plain dispatch warns and exports an unsigned APK so the pipeline
  can be tested on forks without the signing secrets.
SDL3 3.2.16's CMake hard-errors when neither X11 nor Wayland development
headers are found, and the ubuntu-24.04-arm runner ships neither - the
configure step died with 'SDL could not find X11 or Wayland development
libraries'. Install the usual desktop dev set first so the built libSDL3
matches what distros ship.
…bc++

Two failures from run 32435542472:

linux (both arches): the editor could not dlopen libretro_godot - it links
SDL3 by soname with no rpath and expects the host to provide libSDL3.so.0,
which a bare runner does not have - so every script naming Libretro died
with 'could not find type'. Both arches now build SDL3 3.2.16 from source
(the committed x86_64 stand-in wants GLIBC_2.43, newer than ubuntu-24.04's
own 2.39), stage it next to the built .so where its $ORIGIN RUNPATH finds
it, and the gdextension [dependencies] entries bundle it into exports.
Also install the GL/Vulkan runtime libs the extension records as needed.

macos: Apple clang's libc++ construct_at lacks C++20 P0960 parenthesized
aggregate init, so OptionsHandler.cpp's emplace_back() on the aggregate
OptionValue failed to compile. Patched to brace init at build time until
the fix lands upstream (this account cannot push to
XenuIsWatching/libretro-godot).
… patch

- The staging step looked for libSDL3.so.0 under RetroXR/libretro-godot,
  but the SDL3 build writes it into the repo-root submodule tree - the
  arm64 job died on cp after 'all 10 builds OK'.
- Bundle SDL3 into build_out right after export instead of via
  [dependencies]: those entries would demand the file in every working
  tree and break local exports whose hosts already have SDL3.
- Replace the OptionsHandler.cpp call-site patch with one constructor
  patch on OptionsHandler.hpp: there are three emplace_back sites and
  libc++ rejects them all; constructors satisfy every one.
Three failures from run 32440283851:

- linux-arm64: the preset awk reset its in-Linux flag on [preset.N.options]
  too - and binary_format/architecture lives in exactly that options block,
  so the x86_64->arm64 substitution silently missed and export died copying
  a nonexistent x86_64 .so. Anchor the reset on bare [preset.N] headers.
- quest: --export-release refuses to run with no keystore at all ('Code
  Signing: Could not find release keystore'), so warn-and-export-unsigned
  cannot work. Test dispatches now mint a throwaway keystore instead;
  tag/publish runs still hard-require the real secret.
- linux-arm64: the freshly built extensions carry an unversioned DT_NEEDED
  libdl.so, which glibc-based distros stopped shipping when dl merged into
  libc. patchelf --replace-needed to libdl.so.2 after the build.
The ::warning annotation scrolls away with the log; write the notice to
GITHUB_STEP_SUMMARY too so the run report itself says the Quest APK is
signed with a one-off keystore. Upstream never takes this branch (the
secrets exist there), so PRing this file changes nothing upstream.
A publish dispatch without ANDROID_KEYSTORE_BASE64 died in the export
step ('secret ANDROID_KEYSTORE_BASE64 is unset'). Mint the throwaway
keystore whatever the run kind instead - the ::warning and the Summary
tab entry already say the APK is not upgrade-compatible with a real
release - and drop the now-unused PUBLISHING env.
A dispatch that rebuilds the current version (code=6 after the v0.3.1
tag also carried 6) died in preflight before any build started. Like the
keystore and SideQuest steps, the check now lands a ::warning plus a
Summary tab entry - bump version/code before tagging or SideQuest will
reject the build - and lets the pipeline proceed.
- The release job ran only on tags or publish dispatches, so a plain
  dispatch threw every built artifact away. Run it whenever the builds
  succeed: create the release, or refresh the existing one's assets
  (--clobber) when the tag already exists.
- The upstream merge brought its own SCons cache steps back as
  actions/cache@v4 in quest and windows - bump both to v6 like the rest.
… feature/bug/known-issue sections) to release notes
The PR demoted the preflight version/code comparison to a ::warning, so a
tag whose code matched or trailed the previous tag's built for half an hour
and then shipped an APK SideQuest will refuse. README promises preflight
fails before the build, so a tag push errors again.

A publishing workflow_dispatch keeps the warning: it cuts no tag and the
listing is not at stake, and a dispatch that deliberately rebuilds the
current version has to stay runnable.
The fallback keystore made a test dispatch runnable on a fork with no
secrets, but it applied to a tag push too, so a repository that lost its
ANDROID_KEYSTORE_BASE64 would ship a release APK signed with a 30-day
key minted on the runner. That installs cleanly and then refuses to
upgrade over every player's existing install, and nothing before the
store would catch it.

A run that publishes now errors when the secret is unset; a run that only
uploads artifacts keeps the fallback and the warning.
The PR dropped the release job's `if: github.ref_type == 'tag' ||
inputs.publish` so the job could refresh an existing release's assets
rather than throw the artifacts away. What it actually does on a manual
run with publish unticked is resolve TAG from the preset's version/name,
find the live release of that version, and `gh release upload --clobber`
the test build over it - including a Quest APK signed with the throwaway
CI keystore.

README says a manual run creates no release and touches no listing, and
that is the way to test a build, so the guard is back. The artifacts are
still on the run itself, which is where that README points.
Two faults in one branch of the same conditional.

A tag push created the release with --draft. README says a tag push
publishes a GitHub Release and points the SideQuest listing at it, and a
draft is neither: it is invisible to the listing the SideQuest step reads
back, invisible to anyone following a download link, and the pipeline
reports success while no release exists. Staging one by hand is still a
click away in the other direction.

The other half is `gh release edit "$TAG" --notes-file notes.md` on the
existing-release branch. notes.md is the placeholder template - "## Feature
name / description / bug 1 / bug 2" - so re-running a tag to replace a bad
asset wiped whatever release notes had been written. The template is for a
release that does not exist yet; a rerun now refreshes assets only.
Both macOS dependency entries had an empty target, and Godot's macOS
exporter copies an empty-target shared object to
Contents/Frameworks/<basename>. Both are called libpdfium.dylib, so one
overwrote the other - the exported bundle carried a single arm64
libpdfium - and the extension's own load command, set by install_name_tool
in godot-pdfium/SConscript, is @loader_path/mac-<arch>/libpdfium.dylib,
which resolves inside Contents/Frameworks and found nothing at all.

Measured before the change, exporting the macOS preset with Godot 4.7.2
and running the .app:

  Can't open dynamic library: godot-pdfium/libgodot-pdfium.macos.template_release.universal.dylib
  Library not loaded: @loader_path/mac-arm64/libpdfium.dylib
  Reason: tried: '.../Contents/Frameworks/mac-arm64/libpdfium.dylib' (no such file)
  Error loading extension: 'res://godot-pdfium/godot-pdfium.gdextension'

so PDFRenderer never reached ClassDB and books were dead in every
exported build.

A non-empty target is copied to <app>/<target>/<basename> instead, so
naming the two subfolders puts each architecture's library exactly where
its own slice asks for it and neither can clobber the other. After the
change the bundle carries Contents/Frameworks/mac-arm64/libpdfium.dylib
and .../mac-x64/libpdfium.dylib, arm64 and x86_64 respectively, and the
app logs "[PDFRenderer] PDFium library initialized."

This is the exported bundle only; an editor or non-exported run still
loads the two out of RetroXR/godot-pdfium/mac-*/ as before. The android
and linux entries are untouched, so the Android libpdfium.so stays in the
output root where its [dependencies] block and Quest exports expect it.
The preset set codesign/enable and codesign/entitlements, and neither is
an option EditorExportPlatformMacOS declares, so Godot stored them and
read nothing. macos.entitlements was never applied to a local export at
all; CI hid it by re-signing the bundle with that file afterwards. The
real names, from platform/macos/export/export_plugin.cpp at 4.7.2-stable,
are codesign/codesign and a set of codesign/entitlements/<name> booleans
that Godot renders into an entitlements plist itself.

Measured on an exported .app with `codesign -d --entitlements -`. Before,
one key, and that one only because Godot force-adds it when an ad-hoc
signature meets embedded libraries:

  com.apple.security.cs.disable-library-validation

After, the three the app needs - unsigned downloaded cores, executable
callback trampolines, and JIT for dynarec cores:

  com.apple.security.cs.allow-jit
  com.apple.security.cs.allow-unsigned-executable-memory
  com.apple.security.cs.disable-library-validation

codesign/entitlements/custom_file is deliberately left empty rather than
pointed at RetroXR/entitlements/macos.entitlements: the value is handed
to codesign verbatim, with no globalize_path, so a res:// path cannot
work and only an absolute host path would - which is not something a
committed preset can carry. The committed file stays as the input to the
workflow's own re-sign step and lists the same three keys.

codesign/codesign=3 (Xcode codesign, ad-hoc while codesign/identity is
empty), not 1. Godot's built-in signer produces a bundle this machine
kills on exec - SIGKILL, no output, though `codesign --verify --deep
--strict` passes - and re-signing the same bundle with /usr/bin/codesign
makes it run.

Two more dead keys corrected the same way. application/min_macos_version
is not an option either; the real ones are per-architecture, and without
them the x86_64 slice declared LSMinimumSystemVersion 11.00 while its own
dylibs are built with macos_deployment_target=13.0. Both now say 13.0.
The rest of the removed lines - texture formats, modify_resources,
angle_libraries, bundle_name, export_type, embed_pck - are Windows or
Android options that macOS does not declare; macOS takes its texture
formats from binary_format/architecture.
@XenuIsWatching
XenuIsWatching merged commit cfd74c1 into XenuIsWatching:master Sep 14, 2026
@XenuIsWatching

Copy link
Copy Markdown
Owner

ok, i rebased it and fixed a few small issue with it... but other than that, it looked great, thanks!! Now we have automated linux and mac builds 🥳

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.

2 participants