diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index d09a7df1..9348b28d 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -72,15 +72,38 @@ jobs: cp target/release-max/gb-desktop "${package_dir}/" cp README.md LICENSE-MIT LICENSE-APACHE "${package_dir}/" - sdl_libs=(target/release-max/libSDL3*.so*) - if ((${#sdl_libs[@]} == 0)); then - echo "expected SDL3 shared libraries under target/release-max" >&2 - exit 1 + sdl_libs=() + sdl_lib_names=() + while IFS= read -r sdl_lib; do + sdl_name="$(basename "${sdl_lib}")" + sdl_seen=false + for existing_name in "${sdl_lib_names[@]}"; do + if [[ "${existing_name}" == "${sdl_name}" ]]; then + sdl_seen=true + break + fi + done + if [[ "${sdl_seen}" == false ]]; then + sdl_lib_names+=("${sdl_name}") + sdl_libs+=("${sdl_lib}") + fi + done < <( + find target/release-max \( -type f -o -type l \) -name 'libSDL3*.so*' -print | + sort -u + ) + if ((${#sdl_libs[@]} > 0)); then + cp "${sdl_libs[@]}" "${package_dir}/" + patchelf --set-rpath '$ORIGIN' "${package_dir}/gb-desktop" + else + ldd_output="$(ldd "${package_dir}/gb-desktop" 2>&1 || true)" + if grep -q 'libSDL3' <<<"${ldd_output}"; then + printf '%s\n' "${ldd_output}" + echo "gb-desktop dynamically links SDL3 but no bundled libSDL3 shared object was emitted" >&2 + exit 1 + fi fi - cp "${sdl_libs[@]}" "${package_dir}/" chmod +x "${package_dir}/gb-desktop" - patchelf --set-rpath '$ORIGIN' "${package_dir}/gb-desktop" "${package_dir}/gb-desktop" --help tar -C dist -czf "${tar_path}" "${package_name}" diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 05a866f6..8498595e 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -48,26 +48,46 @@ jobs: printf 'APPL????' > "${app_root}/Contents/PkgInfo" cp README.md LICENSE-MIT LICENSE-APACHE "${package_dir}/" - sdl_libs=(target/release-max/libSDL3*.dylib) - if ((${#sdl_libs[@]} == 0)); then - echo "expected SDL3 dylibs under target/release-max" >&2 + sdl_libs=() + sdl_lib_names=() + while IFS= read -r sdl_lib; do + sdl_name="$(basename "${sdl_lib}")" + sdl_seen=false + for existing_name in "${sdl_lib_names[@]}"; do + if [[ "${existing_name}" == "${sdl_name}" ]]; then + sdl_seen=true + break + fi + done + if [[ "${sdl_seen}" == false ]]; then + sdl_lib_names+=("${sdl_name}") + sdl_libs+=("${sdl_lib}") + fi + done < <( + find target/release-max \( -type f -o -type l \) -name 'libSDL3*.dylib' -print | + sort -u + ) + if ((${#sdl_libs[@]} > 0)); then + for dylib in "${sdl_libs[@]}"; do + dylib_name="$(basename "${dylib}")" + cp "${dylib}" "${macos_dir}/${dylib_name}" + chmod +w "${macos_dir}/${dylib_name}" + install_name_tool -id "@executable_path/${dylib_name}" "${macos_dir}/${dylib_name}" + done + + bundled_sdl="$(basename "${sdl_libs[0]}")" + while IFS= read -r dependency; do + case "${dependency}" in + *libSDL3*.dylib*) + install_name_tool -change "${dependency}" "@executable_path/${bundled_sdl}" "${macos_dir}/gb-desktop" + ;; + esac + done < <(otool -L "${macos_dir}/gb-desktop" | tail -n +2 | awk '{print $1}') + elif otool -L "${macos_dir}/gb-desktop" | grep -q 'libSDL3'; then + otool -L "${macos_dir}/gb-desktop" + echo "gb-desktop dynamically links SDL3 but no bundled libSDL3 dylib was emitted" >&2 exit 1 fi - for dylib in "${sdl_libs[@]}"; do - dylib_name="$(basename "${dylib}")" - cp "${dylib}" "${macos_dir}/${dylib_name}" - chmod +w "${macos_dir}/${dylib_name}" - install_name_tool -id "@executable_path/${dylib_name}" "${macos_dir}/${dylib_name}" - done - - bundled_sdl="$(basename "${sdl_libs[0]}")" - while IFS= read -r dependency; do - case "${dependency}" in - *libSDL3*.dylib*) - install_name_tool -change "${dependency}" "@executable_path/${bundled_sdl}" "${macos_dir}/gb-desktop" - ;; - esac - done < <(otool -L "${macos_dir}/gb-desktop" | tail -n +2 | awk '{print $1}') chmod +x "${macos_dir}/gb-desktop" codesign --force --deep --sign - "${app_root}" diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index f1e79a59..968112ae 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -26,9 +26,6 @@ jobs: - name: Build desktop release run: cargo build --profile release-max -p gb-desktop --features gb-desktop/sdl3-build-from-source - - name: Smoke test desktop CLI - run: target\release-max\gb-desktop.exe --help - - name: Package Windows binary shell: pwsh run: | @@ -42,9 +39,15 @@ jobs: Copy-Item -Path "target\release-max\gb-desktop.exe" -Destination $packageDir -Force Copy-Item -Path "README.md", "LICENSE-MIT", "LICENSE-APACHE" -Destination $packageDir -Force - Get-ChildItem -Path "target\release-max" -Recurse -Filter "*.dll" -File | - Sort-Object -Property FullName -Unique | - Copy-Item -Destination $packageDir -Force + $packageDlls = @( + Get-ChildItem -Path "target\release-max" -Recurse -Filter "*.dll" -File | + Sort-Object -Property FullName -Unique + ) + if ($packageDlls.Count -gt 0) { + $packageDlls | Copy-Item -Destination $packageDir -Force + } + + & (Join-Path $packageDir "gb-desktop.exe") --help Compress-Archive -Path (Join-Path $packageDir "*") -DestinationPath $zipPath -Force diff --git a/docs/frontends/DESKTOP.md b/docs/frontends/DESKTOP.md index 5d541e9f..e1b66013 100644 --- a/docs/frontends/DESKTOP.md +++ b/docs/frontends/DESKTOP.md @@ -36,13 +36,13 @@ cargo run --profile release-max -p gb-desktop -- [path/to/rom.gb] ## Release packages -The GitHub release workflows package only the SDL3 desktop frontend. They all build `gb-desktop` with `cargo build --profile release-max -p gb-desktop --features gb-desktop/sdl3-build-from-source`, then run `gb-desktop --help` from the packaged runtime layout so the artifact proves that the executable can find its bundled SDL3 runtime before upload. +The GitHub release workflows package only the SDL3 desktop frontend. They all build `gb-desktop` with `cargo build --profile release-max -p gb-desktop --features gb-desktop/sdl3-build-from-source`, then run `gb-desktop --help` from the packaged runtime layout so the artifact proves that the executable can start with whatever SDL3 runtime form the source-build path emitted before upload. The workflows live under `.github/workflows/`: -- `release-windows.yml` creates `gb-cycle-windows-x86_64.zip` on `windows-latest`, containing `gb-desktop.exe`, `README.md`, both license files, and every SDL3 `.dll` copied by the source-build path under `target/release-max`. -- `release-linux.yml` creates `gb-cycle-linux-x86_64.tar.gz` on `ubuntu-latest`, containing `gb-desktop`, `README.md`, both license files, and `libSDL3*.so*` from `target/release-max`; the binary is patched with an `$ORIGIN` rpath so it loads the bundled SDL3 shared object from the unpacked artifact directory without requiring `LD_LIBRARY_PATH`. -- `release-macos.yml` creates only the Apple Silicon `gb-cycle-macos-aarch64.zip` package on `macos-latest`, containing `GB Cycle.app`, `README.md`, both license files, and `libSDL3*.dylib` inside `Contents/MacOS`; the dylib install names are rewritten to `@executable_path`, the app is ad-hoc signed after that rewrite, and the bundle reuses `crates/gb-desktop/macos/Info.plist` so the release artifact carries the same Pocket Camera usage string as the local macOS launcher. +- `release-windows.yml` creates `gb-cycle-windows-x86_64.zip` on `windows-latest`, containing `gb-desktop.exe`, `README.md`, both license files, and every `.dll` emitted recursively by the source-build path under `target/release-max`. +- `release-linux.yml` creates `gb-cycle-linux-x86_64.tar.gz` on `ubuntu-latest`, containing `gb-desktop`, `README.md`, both license files, and any `libSDL3*.so*` found recursively under `target/release-max` and deduplicated by library filename; when a shared object is present, the binary is patched with an `$ORIGIN` rpath so it loads the bundled SDL3 shared object from the unpacked artifact directory without requiring `LD_LIBRARY_PATH`, and when no shared object is present the workflow fails if `ldd` still reports an SDL3 dynamic dependency. +- `release-macos.yml` creates only the Apple Silicon `gb-cycle-macos-aarch64.zip` package on `macos-latest`, containing `GB Cycle.app`, `README.md`, both license files, and any `libSDL3*.dylib` found recursively under `target/release-max` and deduplicated by library filename inside `Contents/MacOS`; when a dylib is present, the dylib install names are rewritten to `@executable_path` before ad-hoc signing, when no dylib is present the workflow fails if `otool -L` still reports an SDL3 dynamic dependency, and the bundle reuses `crates/gb-desktop/macos/Info.plist` so the release artifact carries the same Pocket Camera usage string as the local macOS launcher. Manual `workflow_dispatch` runs are for artifact validation and upload the package through `actions/upload-artifact`. Tag pushes matching `v*` additionally attach the same package to the GitHub Release via `softprops/action-gh-release`; do not use a non-release branch tag unless you intentionally want to test the release-asset path and then clean up the temporary release/tag afterward.