--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 [qemu-uae-source] [output-plugin]
+
+Builds the QEMU-UAE plugin from the sibling qemu-uae-v11.0 tree.
+
+Environment:
+ WINUAE_MACOS_DEPLOYMENT_TARGET macOS deployment target for the plugin.
+ QEMU_UAE_DEPS_PREFIX Private GLib/libslirp dependency prefix.
+ QEMU_UAE_NINJA ninja executable. Defaults to ninja in PATH.
+ QEMU_UAE_FORCE_CONFIGURE=1 Run configure-qemu-uae even when an
+ existing build/build.ninja is present.
+ QEMU_UAE_SKIP_CONFIGURE=1 Skip configure-qemu-uae. By default,
+ configure only runs when build/build.ninja
+ is missing.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source_dir="$(cd "${script_dir}/.." && pwd)"
+qemu_source="${1:-${WINUAE_QEMU_UAE_SOURCE_DIR:-${source_dir}/../qemu-uae-v11.0}}"
+output_plugin="${2:-${WINUAE_QEMU_UAE_OUTPUT_PLUGIN:-}}"
+deps_prefix="${QEMU_UAE_DEPS_PREFIX:-${WINUAE_QEMU_UAE_DEPS_PREFIX:-}}"
+
+if [[ ! -x "${qemu_source}/configure-qemu-uae" ]]; then
+ echo "error: configure-qemu-uae not found in ${qemu_source}" >&2
+ exit 1
+fi
+qemu_source="$(cd "${qemu_source}" && pwd)"
+
+if [[ "$(uname -s)" == "Darwin" ]]; then
+ export MACOSX_DEPLOYMENT_TARGET="${WINUAE_MACOS_DEPLOYMENT_TARGET:-${MACOSX_DEPLOYMENT_TARGET:-13.0}}"
+fi
+
+if [[ -n "${deps_prefix}" ]]; then
+ if [[ ! -d "${deps_prefix}" ]]; then
+ echo "error: QEMU_UAE_DEPS_PREFIX does not exist: ${deps_prefix}" >&2
+ exit 1
+ fi
+ deps_prefix="$(cd "${deps_prefix}" && pwd)"
+ export PATH="${deps_prefix}/bin:${PATH}"
+ export PKG_CONFIG_LIBDIR="${deps_prefix}/lib/pkgconfig:${deps_prefix}/share/pkgconfig${PKG_CONFIG_LIBDIR:+:${PKG_CONFIG_LIBDIR}}"
+ if [[ "$(uname -s)" == "Darwin" ]]; then
+ export DYLD_LIBRARY_PATH="${deps_prefix}/lib${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}"
+ fi
+fi
+
+ninja_executable="${QEMU_UAE_NINJA:-$(command -v ninja || true)}"
+if [[ -z "${ninja_executable}" && -x "${qemu_source}/build/pyvenv/bin/ninja" ]]; then
+ ninja_executable="${qemu_source}/build/pyvenv/bin/ninja"
+fi
+if [[ -z "${ninja_executable}" ]]; then
+ echo "error: ninja not found; set QEMU_UAE_NINJA" >&2
+ exit 1
+fi
+
+if [[ "${QEMU_UAE_SKIP_CONFIGURE:-0}" != "1" && ( "${QEMU_UAE_FORCE_CONFIGURE:-0}" == "1" || ! -f "${qemu_source}/build/build.ninja" ) ]]; then
+ (cd "${qemu_source}" && ./configure-qemu-uae --ninja="${ninja_executable}")
+fi
+
+"${ninja_executable}" -C "${qemu_source}/build" qemu-uae.so
+
+plugin="${qemu_source}/build/qemu-uae.so"
+if [[ ! -f "${plugin}" ]]; then
+ echo "error: qemu-uae.so was not produced" >&2
+ exit 1
+fi
+
+if [[ -n "${output_plugin}" ]]; then
+ mkdir -p "$(dirname "${output_plugin}")"
+ cp "${plugin}" "${output_plugin}"
+ plugin="${output_plugin}"
+fi
+
+echo "${plugin}"
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 [prefix]
+
+Build macOS dependencies into a private prefix with a fixed deployment target.
+Use the resulting prefix as CMAKE_PREFIX_PATH when configuring WinUAE.
+
+Arguments:
+ prefix Install prefix for SDL3, Qt, and optional media libraries.
+ Defaults to WINUAE_DEPS_PREFIX or <repo>/../winuae-macos-deps.
+
+Environment:
+ WINUAE_MACOS_DEPLOYMENT_TARGET Minimum macOS version. Defaults to 13.0.
+ WINUAE_DEPS_BUILD_DIR Build directory. Defaults to <prefix>/build.
+ WINUAE_DEPS_JOBS Parallel build jobs. Defaults to hw.ncpu.
+ WINUAE_SDL3_SOURCE SDL3 source tree. Required unless
+ WINUAE_SKIP_SDL3=1.
+ WINUAE_QT_SOURCE Qt 6 source tree, or a qtbase CMake source
+ tree. Required unless WINUAE_SKIP_QT=1.
+ WINUAE_LIBPNG_SOURCE libpng source tree. Optional unless
+ WINUAE_REQUIRE_LIBPNG=1.
+ WINUAE_FLAC_SOURCE FLAC source tree. Optional unless
+ WINUAE_REQUIRE_FLAC=1.
+ WINUAE_LIBMPEG2_SOURCE libmpeg2 source tree. Optional unless
+ WINUAE_REQUIRE_LIBMPEG2=1.
+ WINUAE_MT32EMU_SOURCE Munt source tree or mt32emu source tree.
+ Optional unless WINUAE_REQUIRE_MT32EMU=1.
+ WINUAE_SKIP_SDL3=1 Do not build SDL3.
+ WINUAE_SKIP_QT=1 Do not build Qt.
+ WINUAE_SKIP_LIBPNG=1 Do not build libpng.
+ WINUAE_SKIP_FLAC=1 Do not build libFLAC.
+ WINUAE_SKIP_LIBMPEG2=1 Do not build libmpeg2.
+ WINUAE_SKIP_MT32EMU=1 Do not build libmt32emu.
+ WINUAE_REQUIRE_LIBPNG=1 Fail if WINUAE_LIBPNG_SOURCE is missing.
+ WINUAE_REQUIRE_FLAC=1 Fail if WINUAE_FLAC_SOURCE is missing.
+ WINUAE_REQUIRE_LIBMPEG2=1 Fail if WINUAE_LIBMPEG2_SOURCE is missing.
+ WINUAE_REQUIRE_MT32EMU=1 Fail if WINUAE_MT32EMU_SOURCE is missing.
+ WINUAE_SDL3_CMAKE_ARGS Extra arguments passed to SDL3 CMake.
+ WINUAE_QT_CONFIGURE_ARGS Extra arguments passed to Qt configure,
+ in addition to the release-safe defaults.
+ WINUAE_QT_CMAKE_ARGS Extra arguments passed to Qt CMake.
+ WINUAE_LIBPNG_CMAKE_ARGS Extra arguments passed to libpng CMake.
+ WINUAE_FLAC_CMAKE_ARGS Extra arguments passed to FLAC CMake.
+ WINUAE_LIBMPEG2_CONFIGURE_ARGS Extra arguments passed to libmpeg2 configure.
+ WINUAE_MT32EMU_CMAKE_ARGS Extra arguments passed to mt32emu CMake.
+
+Extra argument variables are whitespace-separated argv fragments.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source_dir="$(cd "${script_dir}/.." && pwd)"
+target="${WINUAE_MACOS_DEPLOYMENT_TARGET:-13.0}"
+prefix="${1:-${WINUAE_DEPS_PREFIX:-${source_dir}/../winuae-macos-deps}}"
+build_dir="${WINUAE_DEPS_BUILD_DIR:-${prefix}/build}"
+sdl_source="${WINUAE_SDL3_SOURCE:-}"
+qt_source="${WINUAE_QT_SOURCE:-}"
+libpng_source="${WINUAE_LIBPNG_SOURCE:-}"
+flac_source="${WINUAE_FLAC_SOURCE:-}"
+libmpeg2_source="${WINUAE_LIBMPEG2_SOURCE:-}"
+mt32emu_source="${WINUAE_MT32EMU_SOURCE:-}"
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS dependency builds require Darwin/macOS" >&2
+ exit 1
+fi
+
+jobs="${WINUAE_DEPS_JOBS:-}"
+if [[ -z "${jobs}" ]]; then
+ jobs="$(sysctl -n hw.ncpu 2>/dev/null || echo 8)"
+fi
+export MACOSX_DEPLOYMENT_TARGET="${target}"
+
+require_source() {
+ local name="$1"
+ local path="$2"
+ if [[ -z "${path}" ]]; then
+ echo "error: ${name} source path is required" >&2
+ usage >&2
+ exit 1
+ fi
+ if [[ ! -d "${path}" ]]; then
+ echo "error: ${name} source directory not found: ${path}" >&2
+ exit 1
+ fi
+}
+
+run_cmake_build() {
+ local src="$1"
+ local bld="$2"
+ shift 2
+ cmake -S "${src}" -B "${bld}" "$@"
+ cmake --build "${bld}" -j "${jobs}"
+ cmake --install "${bld}"
+}
+
+run_autotools_build() {
+ local src="$1"
+ local bld="$2"
+ shift 2
+ if [[ -z "${bld}" || "${bld}" == "/" ]]; then
+ echo "error: refusing unsafe autotools build directory: ${bld}" >&2
+ exit 1
+ fi
+ rm -rf "${bld}"
+ mkdir -p "${bld}/src"
+ cp -R "${src}/." "${bld}/src/"
+ (
+ cd "${bld}/src"
+ if [[ -f Makefile ]]; then
+ make distclean >/dev/null 2>&1 || true
+ fi
+ rm -f config.log config.status
+ env \
+ CFLAGS="${CFLAGS:-} -mmacosx-version-min=${target}" \
+ CXXFLAGS="${CXXFLAGS:-} -mmacosx-version-min=${target}" \
+ LDFLAGS="${LDFLAGS:-} -mmacosx-version-min=${target}" \
+ ./configure "$@"
+ make -j "${jobs}"
+ make install
+ )
+}
+
+patch_qtbase_source() {
+ local header="${qt_source}/src/corelib/thread/qyieldcpu.h"
+ if [[ -f "${header}" ]] && grep -q "__yield();" "${header}" && ! grep -q "arm_acle.h" "${header}"; then
+ perl -0pi -e 's/(#include <QtCore\/qtconfigmacros\.h>\n)/$1\n#if defined(__has_include)\n# if __has_include(<arm_acle.h>)\n# include <arm_acle.h>\n# endif\n#endif\n/' "${header}"
+ fi
+
+ local simd="${qt_source}/src/corelib/global/qsimd.cpp"
+ if [[ -f "${simd}" ]] && grep -q 'sysctlbyname("hw.optional.neon"' "${simd}" && ! grep -q "AArch64 includes Advanced SIMD" "${simd}"; then
+ perl -0pi -e 's/#elif defined\(Q_OS_DARWIN\) && defined\(Q_PROCESSOR_ARM\)\n unsigned feature;\n size_t len = sizeof\(feature\);\n if \(sysctlbyname\("hw\.optional\.neon", &feature, &len, nullptr, 0\) == 0\)\n features \|= feature \? CpuFeatureNEON : 0;/#elif defined(Q_OS_DARWIN) \&\& defined(Q_PROCESSOR_ARM)\n unsigned feature;\n size_t len = sizeof(feature);\n# if defined(Q_PROCESSOR_ARM_64)\n \/\/ AArch64 includes Advanced SIMD; some macOS versions no longer\n \/\/ expose the legacy hw.optional.neon sysctl that Qt probes here.\n features |= CpuFeatureNEON;\n# else\n if (sysctlbyname("hw.optional.neon", \&feature, \&len, nullptr, 0) == 0)\n features |= feature ? CpuFeatureNEON : 0;\n# endif/' "${simd}"
+ fi
+}
+
+split_extra_args() {
+ extra_args=()
+ if [[ -n "${1:-}" ]]; then
+ # Extra-arg variables are whitespace-separated argv fragments.
+ # Split once here and pass the result as an array at call sites.
+ # shellcheck disable=SC2206
+ extra_args=($1)
+ fi
+}
+
+mkdir -p "${prefix}" "${build_dir}"
+
+if [[ "${WINUAE_SKIP_LIBPNG:-0}" != "1" ]]; then
+ if [[ -n "${libpng_source}" ]]; then
+ require_source "libpng" "${libpng_source}"
+ libpng_cmake_args=(
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX="${prefix}"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}"
+ -DPNG_SHARED=ON
+ -DPNG_STATIC=OFF
+ -DPNG_TESTS=OFF
+ -DPNG_TOOLS=OFF
+ )
+ split_extra_args "${WINUAE_LIBPNG_CMAKE_ARGS:-}"
+ libpng_cmake_args+=(${extra_args[@]+"${extra_args[@]}"})
+ run_cmake_build "${libpng_source}" "${build_dir}/libpng" \
+ "${libpng_cmake_args[@]}"
+ elif [[ "${WINUAE_REQUIRE_LIBPNG:-0}" == "1" ]]; then
+ echo "error: libpng source path is required when WINUAE_REQUIRE_LIBPNG=1" >&2
+ usage >&2
+ exit 1
+ else
+ echo "note: WINUAE_LIBPNG_SOURCE not set; skipping private libpng build" >&2
+ fi
+fi
+
+if [[ "${WINUAE_SKIP_FLAC:-0}" != "1" ]]; then
+ if [[ -n "${flac_source}" ]]; then
+ require_source "FLAC" "${flac_source}"
+ flac_cmake_args=(
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX="${prefix}"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}"
+ -DBUILD_SHARED_LIBS=ON
+ -DBUILD_CXXLIBS=OFF
+ -DBUILD_PROGRAMS=OFF
+ -DBUILD_EXAMPLES=OFF
+ -DBUILD_DOCS=OFF
+ -DBUILD_TESTING=OFF
+ -DINSTALL_MANPAGES=OFF
+ -DINSTALL_PKGCONFIG_MODULES=ON
+ -DWITH_OGG=OFF
+ )
+ split_extra_args "${WINUAE_FLAC_CMAKE_ARGS:-}"
+ flac_cmake_args+=(${extra_args[@]+"${extra_args[@]}"})
+ run_cmake_build "${flac_source}" "${build_dir}/flac" \
+ "${flac_cmake_args[@]}"
+ elif [[ "${WINUAE_REQUIRE_FLAC:-0}" == "1" ]]; then
+ echo "error: FLAC source path is required when WINUAE_REQUIRE_FLAC=1" >&2
+ usage >&2
+ exit 1
+ else
+ echo "note: WINUAE_FLAC_SOURCE not set; skipping private libFLAC build" >&2
+ fi
+fi
+
+if [[ "${WINUAE_SKIP_LIBMPEG2:-0}" != "1" ]]; then
+ if [[ -n "${libmpeg2_source}" ]]; then
+ require_source "libmpeg2" "${libmpeg2_source}"
+ libmpeg2_configure_args=(
+ --prefix="${prefix}"
+ --disable-sdl
+ --without-x
+ --enable-shared
+ --disable-static
+ --disable-dependency-tracking
+ )
+ if [[ -n "${WINUAE_LIBMPEG2_CONFIGURE_ARGS:-}" ]]; then
+ split_extra_args "${WINUAE_LIBMPEG2_CONFIGURE_ARGS}"
+ libmpeg2_configure_args+=(${extra_args[@]+"${extra_args[@]}"})
+ fi
+ run_autotools_build "${libmpeg2_source}" "${build_dir}/libmpeg2" \
+ "${libmpeg2_configure_args[@]}"
+ (
+ cd "${build_dir}/libmpeg2/src"
+ make -C libmpeg2 install
+ make -C include install
+ )
+ elif [[ "${WINUAE_REQUIRE_LIBMPEG2:-0}" == "1" ]]; then
+ echo "error: libmpeg2 source path is required when WINUAE_REQUIRE_LIBMPEG2=1" >&2
+ usage >&2
+ exit 1
+ else
+ echo "note: WINUAE_LIBMPEG2_SOURCE not set; skipping private libmpeg2 build" >&2
+ fi
+fi
+
+if [[ "${WINUAE_SKIP_MT32EMU:-0}" != "1" ]]; then
+ if [[ -n "${mt32emu_source}" ]]; then
+ require_source "Munt/mt32emu" "${mt32emu_source}"
+ mt32emu_cmake_source="${mt32emu_source}"
+ if [[ -f "${mt32emu_source}/mt32emu/CMakeLists.txt" ]]; then
+ mt32emu_cmake_source="${mt32emu_source}/mt32emu"
+ fi
+ if [[ ! -f "${mt32emu_cmake_source}/CMakeLists.txt" ]]; then
+ echo "error: mt32emu CMakeLists.txt not found under ${mt32emu_source}" >&2
+ exit 1
+ fi
+ mt32emu_cmake_args=(
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX="${prefix}"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}"
+ -DBUILD_TESTING=OFF
+ -Dlibmt32emu_SHARED=ON
+ -Dlibmt32emu_C_INTERFACE=ON
+ -Dlibmt32emu_CPP_INTERFACE=OFF
+ -Dlibmt32emu_WITH_INTERNAL_RESAMPLER=ON
+ )
+ split_extra_args "${WINUAE_MT32EMU_CMAKE_ARGS:-}"
+ mt32emu_cmake_args+=(${extra_args[@]+"${extra_args[@]}"})
+ run_cmake_build "${mt32emu_cmake_source}" "${build_dir}/mt32emu" \
+ "${mt32emu_cmake_args[@]}"
+ elif [[ "${WINUAE_REQUIRE_MT32EMU:-0}" == "1" ]]; then
+ echo "error: Munt/mt32emu source path is required when WINUAE_REQUIRE_MT32EMU=1" >&2
+ usage >&2
+ exit 1
+ else
+ echo "note: WINUAE_MT32EMU_SOURCE not set; skipping private libmt32emu build" >&2
+ fi
+fi
+
+if [[ "${WINUAE_SKIP_SDL3:-0}" != "1" ]]; then
+ require_source "SDL3" "${sdl_source}"
+ sdl3_cmake_args=(
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX="${prefix}"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}"
+ -DSDL_SHARED=ON
+ -DSDL_STATIC=OFF
+ -DSDL_TESTS=OFF
+ -DSDL_EXAMPLES=OFF
+ -DSDL_INSTALL_TESTS=OFF
+ )
+ split_extra_args "${WINUAE_SDL3_CMAKE_ARGS:-}"
+ sdl3_cmake_args+=(${extra_args[@]+"${extra_args[@]}"})
+ run_cmake_build "${sdl_source}" "${build_dir}/sdl3" \
+ "${sdl3_cmake_args[@]}"
+fi
+
+if [[ "${WINUAE_SKIP_QT:-0}" != "1" ]]; then
+ require_source "Qt" "${qt_source}"
+ qt_build="${build_dir}/qt"
+ mkdir -p "${qt_build}"
+ patch_qtbase_source
+
+ qt_configure_args=(
+ -force-bundled-libs
+ -no-dbus
+ -no-openssl
+ -no-glib
+ -no-icu
+ -no-cups
+ -no-fontconfig
+ -no-gtk
+ -qt-doubleconversion
+ -qt-pcre
+ -qt-zlib
+ -qt-libpng
+ -qt-libjpeg
+ -qt-freetype
+ -qt-harfbuzz
+ )
+ if [[ -n "${WINUAE_QT_CONFIGURE_ARGS:-}" ]]; then
+ split_extra_args "${WINUAE_QT_CONFIGURE_ARGS}"
+ qt_configure_args+=(${extra_args[@]+"${extra_args[@]}"})
+ fi
+
+ qt_cmake_args=()
+ if ! xcodebuild -version >/dev/null 2>&1 && xcrun --show-sdk-path >/dev/null 2>&1; then
+ qt_cmake_args+=(-DQT_NO_XCODE_MIN_VERSION_CHECK=ON)
+ fi
+ if [[ -n "${WINUAE_QT_CMAKE_ARGS:-}" ]]; then
+ split_extra_args "${WINUAE_QT_CMAKE_ARGS}"
+ qt_cmake_args+=(${extra_args[@]+"${extra_args[@]}"})
+ fi
+
+ qt_submodule_args=()
+ if [[ -d "${qt_source}/qtbase" || -f "${qt_source}/init-repository" ]]; then
+ qt_submodule_args=(-submodules qtbase)
+ fi
+
+ if [[ -x "${qt_source}/configure" && ! -d "${qt_source}/src/corelib" ]]; then
+ (
+ cd "${qt_build}"
+ "${qt_source}/configure" \
+ -prefix "${prefix}" \
+ -release \
+ -opensource \
+ -confirm-license \
+ -nomake examples \
+ -nomake tests \
+ ${qt_submodule_args[@]+"${qt_submodule_args[@]}"} \
+ "${qt_configure_args[@]}" \
+ -- \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}" \
+ ${qt_cmake_args[@]+"${qt_cmake_args[@]}"}
+ )
+ cmake --build "${qt_build}" -j "${jobs}"
+ cmake --install "${qt_build}"
+ else
+ run_cmake_build "${qt_source}" "${qt_build}" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="${prefix}" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}" \
+ -DQT_BUILD_EXAMPLES=OFF \
+ -DQT_BUILD_TESTS=OFF \
+ -DQT_FEATURE_dbus=OFF \
+ -DQT_FEATURE_openssl=OFF \
+ -DQT_FEATURE_glib=OFF \
+ -DQT_FEATURE_icu=OFF \
+ -DQT_FEATURE_cups=OFF \
+ -DQT_FEATURE_fontconfig=OFF \
+ -DQT_FEATURE_gtk=OFF \
+ -DQT_FEATURE_opengl=OFF \
+ -DQT_FEATURE_opengles2=OFF \
+ ${qt_cmake_args[@]+"${qt_cmake_args[@]}"}
+ fi
+fi
+
+"${script_dir}/macos-check-deployment-target.sh" "${prefix}" "${target}"
+
+env_file="${prefix}/winuae-macos-deps-env.sh"
+cat > "${env_file}" <<EOF
+export CMAKE_PREFIX_PATH="${prefix}\${CMAKE_PREFIX_PATH:+:\${CMAKE_PREFIX_PATH}}"
+export PKG_CONFIG_PATH="${prefix}/lib/pkgconfig:${prefix}/share/pkgconfig\${PKG_CONFIG_PATH:+:\${PKG_CONFIG_PATH}}"
+export PATH="${prefix}/bin:\${PATH}"
+export WINUAE_MACOS_DEPLOYMENT_TARGET="${target}"
+EOF
+
+cat <<EOF
+macOS dependencies installed to: ${prefix}
+Deployment target verified: ${target}
+
+Use:
+ source "${env_file}"
+ cmake -S "${source_dir}" -B /tmp/winuae_cmake_macos \\
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \\
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${target}" \\
+ -DCMAKE_PREFIX_PATH="${prefix}"
+EOF
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 [prefix]
+
+Build QEMU-UAE macOS dependencies into a private prefix with a fixed
+deployment target. The resulting prefix is intended for the QEMU-UAE plugin
+build and keeps the plugin from linking against newer Homebrew libraries.
+
+Arguments:
+ prefix Install prefix for GLib. Defaults to
+ WINUAE_QEMU_DEPS_PREFIX, WINUAE_DEPS_PREFIX, or
+ <repo>/../winuae-macos-deps.
+
+Environment:
+ WINUAE_MACOS_DEPLOYMENT_TARGET Minimum macOS version. Defaults to 13.0.
+ WINUAE_QEMU_DEPS_BUILD_DIR Build directory. Defaults to
+ <prefix>/build/qemu-deps.
+ WINUAE_DEPS_JOBS Parallel build jobs. Defaults to hw.ncpu.
+ WINUAE_GLIB_SOURCE GLib source tree. Required.
+ WINUAE_MESON meson executable. Defaults to meson in PATH
+ or ../qemu-uae-v11.0/build/pyvenv/bin/meson.
+ WINUAE_NINJA ninja executable. Defaults to ninja in PATH.
+ WINUAE_QEMU_BUILD_TOOLS_DIR Optional tools prefix. If set,
+ <prefix>/bin/ninja is used as fallback.
+ WINUAE_GLIB_MESON_ARGS Extra arguments passed to GLib Meson.
+
+Extra argument variables are whitespace-separated argv fragments.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source_dir="$(cd "${script_dir}/.." && pwd)"
+target="${WINUAE_MACOS_DEPLOYMENT_TARGET:-13.0}"
+prefix="${1:-${WINUAE_QEMU_DEPS_PREFIX:-${WINUAE_DEPS_PREFIX:-${source_dir}/../winuae-macos-deps}}}"
+build_dir="${WINUAE_QEMU_DEPS_BUILD_DIR:-${prefix}/build/qemu-deps}"
+glib_source="${WINUAE_GLIB_SOURCE:-}"
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS QEMU dependency builds require Darwin/macOS" >&2
+ exit 1
+fi
+
+jobs="${WINUAE_DEPS_JOBS:-}"
+if [[ -z "${jobs}" ]]; then
+ jobs="$(sysctl -n hw.ncpu 2>/dev/null || echo 8)"
+fi
+
+require_source() {
+ local name="$1"
+ local path="$2"
+ if [[ -z "${path}" ]]; then
+ echo "error: ${name} source path is required" >&2
+ usage >&2
+ exit 1
+ fi
+ if [[ ! -d "${path}" ]]; then
+ echo "error: ${name} source directory not found: ${path}" >&2
+ exit 1
+ fi
+}
+
+find_tool() {
+ local env_value="$1"
+ local tool_name="$2"
+ local fallback="$3"
+
+ if [[ -n "${env_value}" ]]; then
+ echo "${env_value}"
+ return
+ fi
+ if command -v "${tool_name}" >/dev/null 2>&1; then
+ command -v "${tool_name}"
+ return
+ fi
+ if [[ -n "${fallback}" && -x "${fallback}" ]]; then
+ echo "${fallback}"
+ return
+ fi
+
+ echo "error: ${tool_name} not found" >&2
+ exit 1
+}
+
+split_extra_args() {
+ extra_args=()
+ if [[ -n "${1:-}" ]]; then
+ # Extra-arg variables are whitespace-separated argv fragments.
+ # Split once here and pass the result as an array at call sites.
+ # shellcheck disable=SC2206
+ extra_args=($1)
+ fi
+}
+
+run_meson_build() {
+ local src="$1"
+ local bld="$2"
+ shift 2
+
+ if [[ -f "${bld}/build.ninja" ]]; then
+ "${meson_executable}" setup --reconfigure "${bld}" "$@"
+ else
+ "${meson_executable}" setup "${bld}" "${src}" "$@"
+ fi
+ "${ninja_executable}" -C "${bld}" -j "${jobs}"
+ "${ninja_executable}" -C "${bld}" install
+}
+
+require_source "GLib" "${glib_source}"
+
+meson_fallback="${source_dir}/../qemu-uae-v11.0/build/pyvenv/bin/meson"
+ninja_fallback=""
+if [[ -n "${WINUAE_QEMU_BUILD_TOOLS_DIR:-}" ]]; then
+ ninja_fallback="${WINUAE_QEMU_BUILD_TOOLS_DIR}/bin/ninja"
+fi
+meson_executable="$(find_tool "${WINUAE_MESON:-}" meson "${meson_fallback}")"
+ninja_executable="$(find_tool "${WINUAE_NINJA:-}" ninja "${ninja_fallback}")"
+
+mkdir -p "${prefix}" "${build_dir}"
+
+export MACOSX_DEPLOYMENT_TARGET="${target}"
+export PATH="$(dirname "${ninja_executable}"):${prefix}/bin:${PATH}"
+export PKG_CONFIG_LIBDIR="${prefix}/lib/pkgconfig:${prefix}/share/pkgconfig"
+
+common_meson_args=(
+ --prefix="${prefix}"
+ --libdir=lib
+ --buildtype=release
+ -Ddefault_library=shared
+)
+
+glib_args=(
+ "${common_meson_args[@]}"
+ --force-fallback-for=libpcre2-8,libffi,intl
+ -Dtests=false
+ -Dinstalled_tests=false
+ -Dglib_debug=disabled
+ -Dglib_assert=false
+ -Dglib_checks=false
+ -Dman-pages=disabled
+ -Ddocumentation=false
+ -Dgtk_doc=false
+ -Dnls=disabled
+ -Dselinux=disabled
+ -Dxattr=false
+ -Dlibmount=disabled
+ -Dsysprof=disabled
+ -Dintrospection=disabled
+ -Ddtrace=disabled
+ -Dsystemtap=disabled
+)
+if [[ -n "${WINUAE_GLIB_MESON_ARGS:-}" ]]; then
+ split_extra_args "${WINUAE_GLIB_MESON_ARGS}"
+ glib_args+=(${extra_args[@]+"${extra_args[@]}"})
+fi
+
+run_meson_build "${glib_source}" "${build_dir}/glib" "${glib_args[@]}"
+
+"${script_dir}/macos-check-deployment-target.sh" "${prefix}" "${target}"
+
+env_file="${prefix}/winuae-macos-deps-env.sh"
+cat > "${env_file}" <<EOF
+export CMAKE_PREFIX_PATH="${prefix}\${CMAKE_PREFIX_PATH:+:\${CMAKE_PREFIX_PATH}}"
+export PKG_CONFIG_PATH="${prefix}/lib/pkgconfig:${prefix}/share/pkgconfig\${PKG_CONFIG_PATH:+:\${PKG_CONFIG_PATH}}"
+export QEMU_UAE_DEPS_PREFIX="${prefix}"
+export WINUAE_MACOS_DEPLOYMENT_TARGET="${target}"
+export PATH="${prefix}/bin:\${PATH}"
+EOF
+
+cat <<EOF
+macOS QEMU-UAE dependencies installed to: ${prefix}
+Deployment target verified: ${target}
+
+Use:
+ source "${env_file}"
+ cmake --build /tmp/winuae_cmake_macos \\
+ --target winuae_unix_qemu_uae_plugin -j
+EOF
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 [build-dir] [output-dir]
+
+Creates a local WinUAE.app bundle from an existing macOS build tree.
+
+Arguments:
+ build-dir CMake build directory containing winuae_unix.
+ Defaults to WINUAE_BUILD_DIR or the current directory.
+ output-dir Directory that will receive WinUAE.app.
+ Defaults to <build-dir>/package.
+
+Environment:
+ WINUAE_SKIP_MACDEPLOYQT=1 Do not run macdeployqt even if it is available.
+ WINUAE_SKIP_MACOS_DEPLOYMENT_CHECK=1
+ Do not check bundled Mach-O deployment targets.
+ WINUAE_SKIP_CODESIGN=1 Do not ad-hoc codesign the bundle.
+ WINUAE_CODESIGN_IDENTITY codesign identity. Defaults to "-" for ad-hoc.
+ WINUAE_CODESIGN_OPTIONS Extra options passed to codesign, for example
+ "--options runtime --timestamp".
+ WINUAE_CODESIGN_ENTITLEMENTS
+ Optional entitlements plist passed to codesign.
+ WINUAE_QEMU_UAE_PLUGIN Optional qemu-uae.so path to copy into
+ Contents/PlugIns.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source_dir="$(cd "${script_dir}/.." && pwd)"
+build_dir="${1:-${WINUAE_BUILD_DIR:-$(pwd)}}"
+output_dir="${2:-${build_dir}/package}"
+executable="${build_dir}/winuae_unix"
+app_dir="${output_dir}/WinUAE.app"
+contents_dir="${app_dir}/Contents"
+macos_dir="${contents_dir}/MacOS"
+resources_dir="${contents_dir}/Resources"
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS app bundling requires Darwin/macOS" >&2
+ exit 1
+fi
+
+if [[ ! -x "${executable}" ]]; then
+ echo "error: executable not found: ${executable}" >&2
+ echo "hint: build winuae_unix first, or pass the CMake build directory" >&2
+ exit 1
+fi
+
+major="$(awk '/^#define UAEMAJOR / { print $3; exit }' "${source_dir}/include/options.h")"
+minor="$(awk '/^#define UAEMINOR / { print $3; exit }' "${source_dir}/include/options.h")"
+revision="$(awk '/^#define UAESUBREV / { print $3; exit }' "${source_dir}/include/options.h")"
+version="${major:-0}.${minor:-0}.${revision:-0}"
+deployment_target="${WINUAE_MACOS_DEPLOYMENT_TARGET:-}"
+if [[ -z "${deployment_target}" && -f "${build_dir}/CMakeCache.txt" ]]; then
+ deployment_target="$(awk -F= '/^CMAKE_OSX_DEPLOYMENT_TARGET:/ { print $2; exit }' "${build_dir}/CMakeCache.txt")"
+fi
+if [[ -z "${deployment_target}" ]]; then
+ deployment_target="13.0"
+fi
+
+cmake_cache_value() {
+ local key="$1"
+ if [[ -f "${build_dir}/CMakeCache.txt" ]]; then
+ awk -F= -v key="${key}" '$1 ~ "^" key ":" { print $2; exit }' "${build_dir}/CMakeCache.txt"
+ fi
+}
+
+append_qt_plugin_candidate() {
+ local candidate="$1"
+ if [[ -n "${candidate}" && -f "${candidate}/platforms/libqcocoa.dylib" ]]; then
+ qt_plugin_candidates+=("${candidate}")
+ fi
+}
+
+split_extra_args() {
+ if [[ -n "${1:-}" ]]; then
+ # Intentionally split user-provided extra flags the same way a shell would.
+ # shellcheck disable=SC2206
+ extra_args=($1)
+ else
+ extra_args=()
+ fi
+}
+
+path_in_list() {
+ local needle="$1"
+ shift
+ local item
+ for item in "$@"; do
+ if [[ "${item}" == "${needle}" ]]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+copy_private_dylib_deps() {
+ local root_binary="$1"
+ local install_prefix="$2"
+ local frameworks_dir="${contents_dir}/Frameworks"
+ local queue=("${root_binary}")
+ local visited=()
+
+ mkdir -p "${frameworks_dir}"
+ while [[ ${#queue[@]} -gt 0 ]]; do
+ local binary="${queue[0]}"
+ queue=("${queue[@]:1}")
+ if path_in_list "${binary}" ${visited[@]+"${visited[@]}"}; then
+ continue
+ fi
+ visited+=("${binary}")
+
+ local dep
+ while IFS= read -r dep; do
+ case "${dep}" in
+ ""|@*|/usr/lib/*|/System/Library/*)
+ continue
+ ;;
+ esac
+ if [[ ! -f "${dep}" ]]; then
+ continue
+ fi
+
+ local name target
+ name="$(basename "${dep}")"
+ target="${frameworks_dir}/${name}"
+ if [[ ! -f "${target}" ]]; then
+ cp "${dep}" "${target}"
+ chmod u+w "${target}" 2>/dev/null || true
+ install_name_tool -id "@rpath/${name}" "${target}" \
+ 2>/dev/null || true
+ fi
+ install_name_tool -change "${dep}" "${install_prefix}/${name}" \
+ "${binary}" 2>/dev/null || true
+ if ! path_in_list "${target}" ${visited[@]+"${visited[@]}"} \
+ && ! path_in_list "${target}" ${queue[@]+"${queue[@]}"}; then
+ queue+=("${target}")
+ fi
+ done < <(otool -L "${binary}" | awk 'NR > 1 { print $1 }')
+ done
+}
+
+rm -rf "${app_dir}"
+mkdir -p "${macos_dir}" "${resources_dir}/od-win32/resources"
+
+cp "${executable}" "${macos_dir}/WinUAE"
+find "${source_dir}/od-win32/resources" -maxdepth 1 -type f \
+ ! -name '*.rc' \
+ ! -name '*.manifest' \
+ ! -name 'resource.h' \
+ -exec cp '{}' "${resources_dir}/od-win32/resources/" ';'
+cp "${source_dir}/README_unix.md" "${resources_dir}/README_unix.md"
+
+if [[ -f "${source_dir}/od-win32/resources/winuae.ico" ]]; then
+ cp "${source_dir}/od-win32/resources/winuae.ico" "${resources_dir}/winuae.ico"
+ if command -v sips >/dev/null 2>&1; then
+ sips -s format icns "${source_dir}/od-win32/resources/winuae.ico" --out "${resources_dir}/WinUAE.icns" >/dev/null
+ fi
+fi
+
+cat > "${contents_dir}/Info.plist" <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>WinUAE</string>
+ <key>CFBundleExecutable</key>
+ <string>WinUAE</string>
+ <key>CFBundleIdentifier</key>
+ <string>net.winuae.unix</string>
+ <key>CFBundleIconFile</key>
+ <string>WinUAE.icns</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>WinUAE</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${version}</string>
+ <key>CFBundleVersion</key>
+ <string>${version}</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${deployment_target}</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>uae</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>WinUAE Configuration</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ </array>
+</dict>
+</plist>
+EOF
+
+qemu_uae_plugin=""
+for candidate in \
+ "${WINUAE_QEMU_UAE_PLUGIN:-}" \
+ "${build_dir}/qemu-uae.so" \
+ "${build_dir}/plugins/qemu-uae.so" \
+ "${source_dir}/../qemu-uae-v11.0/build/qemu-uae.so"
+do
+ if [[ -n "${candidate}" && -f "${candidate}" ]]; then
+ qemu_uae_plugin="${candidate}"
+ break
+ fi
+done
+
+copy_qemu_uae_plugin() {
+ if [[ -n "${qemu_uae_plugin}" ]]; then
+ mkdir -p "${contents_dir}/PlugIns"
+ cp "${qemu_uae_plugin}" "${contents_dir}/PlugIns/qemu-uae.so"
+ install_name_tool -add_rpath "@loader_path/../Frameworks" \
+ "${contents_dir}/PlugIns/qemu-uae.so" 2>/dev/null || true
+ copy_private_dylib_deps \
+ "${contents_dir}/PlugIns/qemu-uae.so" \
+ "@loader_path/../Frameworks"
+ fi
+}
+
+macdeployqt_executable="${WINUAE_MACDEPLOYQT:-}"
+if [[ -z "${macdeployqt_executable}" ]]; then
+ macdeployqt_executable="$(cmake_cache_value MACDEPLOYQT_EXECUTABLE)"
+fi
+if [[ -z "${macdeployqt_executable}" ]]; then
+ macdeployqt_executable="$(command -v macdeployqt || true)"
+fi
+
+if [[ "${WINUAE_SKIP_MACDEPLOYQT:-0}" != "1" && -n "${macdeployqt_executable}" && -x "${macdeployqt_executable}" ]]; then
+ macdeployqt_args=("${app_dir}" -always-overwrite -no-plugins -verbose=0)
+ if "${macdeployqt_executable}" -help 2>&1 | grep -q -- "-no-codesign"; then
+ macdeployqt_args+=(-no-codesign)
+ fi
+ "${macdeployqt_executable}" "${macdeployqt_args[@]}"
+
+ qt_plugin_root=""
+ qt_plugin_candidates=()
+ append_qt_plugin_candidate "${WINUAE_QT_PLUGIN_ROOT:-}"
+
+ qt6_dir="$(cmake_cache_value Qt6_DIR)"
+ if [[ -n "${qt6_dir}" && -d "${qt6_dir}" ]]; then
+ qt_prefix="$(cd "${qt6_dir}/../../.." && pwd)"
+ append_qt_plugin_candidate "${qt_prefix}/plugins"
+ append_qt_plugin_candidate "${qt_prefix}/share/qt/plugins"
+ append_qt_plugin_candidate "${qt_prefix}/share/qt6/plugins"
+ if [[ -x "${qt_prefix}/bin/qtpaths" ]]; then
+ append_qt_plugin_candidate "$("${qt_prefix}/bin/qtpaths" --plugin-dir 2>/dev/null || true)"
+ fi
+ fi
+
+ macdeployqt_dir="$(cd "$(dirname "${macdeployqt_executable}")" && pwd)"
+ if [[ -x "${macdeployqt_dir}/qtpaths" ]]; then
+ append_qt_plugin_candidate "$("${macdeployqt_dir}/qtpaths" --plugin-dir 2>/dev/null || true)"
+ fi
+
+ for candidate in "${qt_plugin_candidates[@]}" \
+ /opt/homebrew/share/qt/plugins \
+ /opt/homebrew/opt/qt/share/qt/plugins \
+ /opt/homebrew/opt/qt6/share/qt/plugins \
+ /opt/homebrew/opt/qt@6/share/qt/plugins \
+ /usr/local/share/qt/plugins \
+ /usr/local/opt/qt/share/qt/plugins \
+ /usr/local/opt/qt6/share/qt/plugins \
+ /usr/local/opt/qt@6/share/qt/plugins
+ do
+ if [[ -n "${candidate}" && -f "${candidate}/platforms/libqcocoa.dylib" ]]; then
+ qt_plugin_root="${candidate}"
+ break
+ fi
+ done
+
+ if [[ -n "${qt_plugin_root}" ]]; then
+ copy_qt_plugin() {
+ local relative="$1"
+ local source="${qt_plugin_root}/${relative}"
+ local target="${contents_dir}/PlugIns/${relative}"
+ if [[ -f "${source}" ]]; then
+ mkdir -p "$(dirname "${target}")"
+ cp "${source}" "${target}"
+ install_name_tool -add_rpath "@loader_path/../../Frameworks" "${target}" 2>/dev/null || true
+ fi
+ }
+ copy_qt_plugin "platforms/libqcocoa.dylib"
+ copy_qt_plugin "imageformats/libqico.dylib"
+ copy_qt_plugin "styles/libqmacstyle.dylib"
+ fi
+fi
+
+copy_qemu_uae_plugin
+
+if [[ "${WINUAE_SKIP_MACOS_DEPLOYMENT_CHECK:-0}" != "1" ]]; then
+ "${script_dir}/macos-check-deployment-target.sh" "${app_dir}" "${deployment_target}" >&2
+fi
+
+if [[ "${WINUAE_SKIP_CODESIGN:-0}" != "1" ]] && command -v codesign >/dev/null 2>&1; then
+ codesign_identity="${WINUAE_CODESIGN_IDENTITY:--}"
+ codesign_args=(--force --deep --sign "${codesign_identity}")
+ split_extra_args "${WINUAE_CODESIGN_OPTIONS:-}"
+ codesign_args+=(${extra_args[@]+"${extra_args[@]}"})
+ if [[ -n "${WINUAE_CODESIGN_ENTITLEMENTS:-}" ]]; then
+ codesign_args+=(--entitlements "${WINUAE_CODESIGN_ENTITLEMENTS}")
+ fi
+ codesign "${codesign_args[@]}" "${app_dir}"
+fi
+
+echo "${app_dir}"
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 app-or-binary-path deployment-target
+
+Checks Mach-O files under a macOS app bundle, or a single Mach-O file, and
+fails if any file requires a macOS version newer than the requested deployment
+target.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+root="${1:-}"
+target="${2:-}"
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: deployment-target checking requires Darwin/macOS" >&2
+ exit 1
+fi
+if [[ -z "${root}" || -z "${target}" ]]; then
+ usage >&2
+ exit 1
+fi
+if [[ ! -e "${root}" ]]; then
+ echo "error: path not found: ${root}" >&2
+ exit 1
+fi
+
+version_gt() {
+ local a="$1"
+ local b="$2"
+ local ai bi
+ IFS=. read -r -a av <<< "${a}"
+ IFS=. read -r -a bv <<< "${b}"
+ for i in 0 1 2; do
+ ai="${av[$i]:-0}"
+ bi="${bv[$i]:-0}"
+ if ((10#${ai} > 10#${bi})); then
+ return 0
+ fi
+ if ((10#${ai} < 10#${bi})); then
+ return 1
+ fi
+ done
+ return 1
+}
+
+mach_o_minos() {
+ otool -l "$1" 2>/dev/null | awk '
+ /LC_BUILD_VERSION/ { build = 1; old = 0; next }
+ build && /minos/ { print $2; exit }
+ /LC_VERSION_MIN_MACOSX/ { old = 1; build = 0; next }
+ old && /version/ { print $2; exit }
+ '
+}
+
+check_file() {
+ local file="$1"
+ local info minos
+
+ info="$(file -b "${file}" 2>/dev/null || true)"
+ case "${info}" in
+ *Mach-O*) ;;
+ *) return 0 ;;
+ esac
+
+ minos="$(mach_o_minos "${file}")"
+ if [[ -n "${minos}" ]] && version_gt "${minos}" "${target}"; then
+ echo "error: ${file} requires macOS ${minos}, newer than deployment target ${target}" >&2
+ return 1
+ fi
+ return 0
+}
+
+failed=0
+if [[ -f "${root}" ]]; then
+ check_file "${root}" || failed=1
+else
+ while IFS= read -r -d '' file_path; do
+ check_file "${file_path}" || failed=1
+ done < <(find "${root}" -type f -print0)
+fi
+
+if ((failed)); then
+ exit 1
+fi
+echo "verified macOS deployment target ${target}: ${root}"
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 [build-dir] [output-dir]
+
+Creates a drag-install WinUAE DMG from an existing macOS build tree.
+
+Arguments:
+ build-dir CMake build directory containing winuae_unix.
+ Defaults to WINUAE_BUILD_DIR or the current directory.
+ output-dir Directory that will receive WinUAE.app and the final DMG.
+ Defaults to <build-dir>/package.
+
+Environment:
+ WINUAE_DMG_CODESIGN_IDENTITY codesign identity for the final DMG.
+ Defaults to WINUAE_CODESIGN_IDENTITY when set.
+ WINUAE_DMG_CODESIGN_OPTIONS Extra options passed to codesign for the DMG.
+ WINUAE_NOTARY_PROFILE notarytool keychain profile. When set, submit
+ the final DMG and staple the ticket.
+ WINUAE_SKIP_NOTARIZATION=1 Do not submit/staple even if a profile is set.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source_dir="$(cd "${script_dir}/.." && pwd)"
+build_dir="${1:-${WINUAE_BUILD_DIR:-$(pwd)}}"
+output_dir="${2:-${build_dir}/package}"
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS DMG creation requires Darwin/macOS" >&2
+ exit 1
+fi
+
+major="$(awk '/^#define UAEMAJOR / { print $3; exit }' "${source_dir}/include/options.h")"
+minor="$(awk '/^#define UAEMINOR / { print $3; exit }' "${source_dir}/include/options.h")"
+revision="$(awk '/^#define UAESUBREV / { print $3; exit }' "${source_dir}/include/options.h")"
+version="${major:-0}.${minor:-0}.${revision:-0}"
+
+app_dir="$("${script_dir}/macos-bundle.sh" "${build_dir}" "${output_dir}" | awk 'NF { line = $0 } END { print line }')"
+staging_dir="${output_dir}/dmg-root"
+volume_name="WinUAE"
+rw_dmg="${output_dir}/WinUAE-${version}.rw.dmg"
+final_dmg="${output_dir}/WinUAE-${version}.dmg"
+mount_dir=""
+
+cleanup() {
+ if [[ -n "${mount_dir}" && -d "${mount_dir}" ]]; then
+ hdiutil detach "${mount_dir}" -quiet >/dev/null 2>&1 || true
+ rmdir "${mount_dir}" >/dev/null 2>&1 || true
+ fi
+}
+trap cleanup EXIT
+
+split_extra_args() {
+ if [[ -n "${1:-}" ]]; then
+ # Intentionally split user-provided extra flags the same way a shell would.
+ # shellcheck disable=SC2206
+ extra_args=($1)
+ else
+ extra_args=()
+ fi
+}
+
+rm -rf "${staging_dir}" "${rw_dmg}" "${final_dmg}"
+mkdir -p "${staging_dir}/.background"
+cp -R "${app_dir}" "${staging_dir}/WinUAE.app"
+ln -s /Applications "${staging_dir}/Applications"
+
+volume_icon="${app_dir}/Contents/Resources/WinUAE.icns"
+apply_volume_icon() {
+ local target_dir="$1"
+
+ if [[ ! -f "${volume_icon}" ]]; then
+ return
+ fi
+
+ cp "${volume_icon}" "${target_dir}/.VolumeIcon.icns"
+ if command -v SetFile >/dev/null 2>&1; then
+ SetFile -a C "${target_dir}" || true
+ SetFile -a V "${target_dir}/.VolumeIcon.icns" || true
+ fi
+}
+
+apply_volume_icon "${staging_dir}"
+
+background_tiff="${staging_dir}/.background/background.tiff"
+background_source="${source_dir}/od-unix/graphics/dmg_background.tiff"
+if [[ ! -f "${background_source}" ]]; then
+ echo "error: missing DMG background: ${background_source}" >&2
+ exit 1
+fi
+cp "${background_source}" "${background_tiff}"
+
+hdiutil create -volname "${volume_name}" -srcfolder "${staging_dir}" -fs HFS+ -format UDRW -ov "${rw_dmg}" >/dev/null
+mount_dir="$(hdiutil attach "${rw_dmg}" -readwrite -noverify -noautoopen | awk -F '\t' '/\/Volumes\// { print $NF; exit }')"
+if [[ -z "${mount_dir}" || ! -d "${mount_dir}" ]]; then
+ echo "error: failed to mount ${rw_dmg}" >&2
+ exit 1
+fi
+
+apply_volume_icon "${mount_dir}"
+
+require_finder_layout_records() {
+ local ds_store="$1"
+ local missing=0
+
+ for record in Iloc bwsp icvp; do
+ if ! LC_ALL=C grep -aq "${record}" "${ds_store}"; then
+ echo "error: Finder layout metadata is missing ${record} record in ${ds_store}" >&2
+ missing=1
+ fi
+ done
+
+ return "${missing}"
+}
+
+if ! command -v osascript >/dev/null 2>&1; then
+ echo "error: osascript is required to write the DMG Finder layout" >&2
+ exit 1
+fi
+
+osascript <<EOF
+tell application "Finder"
+ set dmgFolder to POSIX file "${mount_dir}" as alias
+ set backgroundPicture to POSIX file "${mount_dir}/.background/background.tiff" as alias
+ open dmgFolder
+ delay 5
+ set dmgWindow to container window of dmgFolder
+ set current view of dmgWindow to icon view
+ set viewOptions to icon view options of dmgWindow
+ set background picture of viewOptions to backgroundPicture
+ set arrangement of viewOptions to not arranged
+ set icon size of viewOptions to 96
+ update dmgFolder
+ delay 5
+ try
+ close dmgWindow
+ end try
+
+ open dmgFolder
+ delay 1
+ set dmgWindow to container window of dmgFolder
+ set current view of dmgWindow to icon view
+ try
+ set sidebar width of dmgWindow to 0
+ end try
+ try
+ set toolbar visible of dmgWindow to false
+ end try
+ try
+ set statusbar visible of dmgWindow to false
+ end try
+ set bounds of dmgWindow to {100, 100, 740, 500}
+ set viewOptions to icon view options of dmgWindow
+ set arrangement of viewOptions to not arranged
+ set icon size of viewOptions to 96
+ set background picture of viewOptions to backgroundPicture
+ set position of item "WinUAE.app" of dmgFolder to {178, 200}
+ set position of item "Applications" of dmgFolder to {462, 200}
+ update dmgFolder
+ delay 5
+ try
+ close dmgWindow
+ end try
+
+ open dmgFolder
+ delay 1
+ try
+ close container window of dmgFolder
+ end try
+end tell
+EOF
+for _ in {1..20}; do
+ if [[ -f "${mount_dir}/.DS_Store" ]] && require_finder_layout_records "${mount_dir}/.DS_Store" >/dev/null 2>&1; then
+ break
+ fi
+ sleep 0.5
+done
+if [[ ! -f "${mount_dir}/.DS_Store" ]]; then
+ echo "error: Finder did not write ${mount_dir}/.DS_Store; DMG background layout was not saved" >&2
+ exit 1
+fi
+require_finder_layout_records "${mount_dir}/.DS_Store"
+
+apply_volume_icon "${mount_dir}"
+if [[ -f "${volume_icon}" ]] && command -v SetFile >/dev/null 2>&1 && command -v GetFileInfo >/dev/null 2>&1; then
+ volume_attrs="$(GetFileInfo -a "${mount_dir}" 2>/dev/null || true)"
+ case "${volume_attrs}" in
+ *C*) ;;
+ *)
+ echo "error: custom volume icon attribute was not set on ${mount_dir}" >&2
+ exit 1
+ ;;
+ esac
+fi
+
+sync
+hdiutil detach "${mount_dir}" -quiet
+mount_dir=""
+hdiutil convert "${rw_dmg}" -format UDZO -imagekey zlib-level=9 -o "${final_dmg}" -ov >/dev/null
+
+dmg_codesign_identity="${WINUAE_DMG_CODESIGN_IDENTITY:-${WINUAE_CODESIGN_IDENTITY:-}}"
+if [[ -n "${dmg_codesign_identity}" && "${dmg_codesign_identity}" != "-" ]] && command -v codesign >/dev/null 2>&1; then
+ dmg_codesign_args=(--force --sign "${dmg_codesign_identity}")
+ split_extra_args "${WINUAE_DMG_CODESIGN_OPTIONS:-}"
+ dmg_codesign_args+=(${extra_args[@]+"${extra_args[@]}"})
+ codesign "${dmg_codesign_args[@]}" "${final_dmg}"
+fi
+
+hdiutil verify "${final_dmg}" >/dev/null
+
+if [[ "${WINUAE_SKIP_NOTARIZATION:-0}" != "1" && -n "${WINUAE_NOTARY_PROFILE:-}" ]]; then
+ if ! command -v xcrun >/dev/null 2>&1; then
+ echo "error: notarization requires xcrun/notarytool" >&2
+ exit 1
+ fi
+ xcrun notarytool submit "${final_dmg}" --keychain-profile "${WINUAE_NOTARY_PROFILE}" --wait
+ xcrun stapler staple "${final_dmg}"
+fi
+
+"${script_dir}/macos-verify-dmg.sh" "${final_dmg}" >/dev/null
+rm -rf "${rw_dmg}" "${staging_dir}"
+
+echo "${final_dmg}"
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 app-path
+
+Launches a packaged WinUAE.app from an isolated HOME and verifies that the
+Qt configuration window appears. This is a release smoke test, not a normal
+build step.
+
+Environment:
+ WINUAE_MACOS_SMOKE_TIMEOUT Seconds to wait. Defaults to 20.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+app_path="${1:-}"
+smoke_home=""
+smoke_log=""
+timeout_file=""
+launched=0
+
+cleanup() {
+ if [[ "${launched}" == "1" && -n "${bundle_id:-}" ]]; then
+ osascript - "${bundle_id}" <<'APPLESCRIPT' >/dev/null 2>&1 || true
+on run argv
+ tell application id (item 1 of argv) to quit
+end run
+APPLESCRIPT
+ fi
+ if [[ -n "${smoke_home}" && -d "${smoke_home}" ]]; then
+ rm -rf "${smoke_home}"
+ fi
+ if [[ -n "${smoke_log}" && -f "${smoke_log}" ]]; then
+ rm -f "${smoke_log}"
+ fi
+ if [[ -n "${timeout_file}" && -f "${timeout_file}" ]]; then
+ rm -f "${timeout_file}"
+ fi
+}
+trap cleanup EXIT
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS app smoke testing requires Darwin/macOS" >&2
+ exit 1
+fi
+
+if [[ -z "${app_path}" ]]; then
+ usage >&2
+ exit 1
+fi
+
+if [[ -d "${app_path}/WinUAE.app" ]]; then
+ app_path="${app_path}/WinUAE.app"
+fi
+
+executable="${app_path}/Contents/MacOS/WinUAE"
+if [[ ! -x "${executable}" ]]; then
+ echo "error: app executable not found: ${executable}" >&2
+ exit 1
+fi
+
+bundle_id="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "${app_path}/Contents/Info.plist" 2>/dev/null || true)"
+if [[ -z "${bundle_id}" ]]; then
+ echo "error: CFBundleIdentifier missing from ${app_path}/Contents/Info.plist" >&2
+ exit 1
+fi
+
+timeout="${WINUAE_MACOS_SMOKE_TIMEOUT:-20}"
+smoke_home="$(mktemp -d -t winuae-app-smoke-home.XXXXXX)"
+smoke_log="$(mktemp -t winuae-app-smoke-log.XXXXXX)"
+timeout_file="$(mktemp -t winuae-app-smoke-timeout.XXXXXX)"
+rm -f "${timeout_file}"
+
+open -n -F -W \
+ --env "HOME=${smoke_home}" \
+ --env "WINUAE_MACOS_APP_SMOKE=1" \
+ -o "${smoke_log}" \
+ --stderr "${smoke_log}" \
+ "${app_path}" &
+open_pid="$!"
+launched=1
+
+(
+ sleep "${timeout}"
+ if kill -0 "${open_pid}" >/dev/null 2>&1; then
+ : > "${timeout_file}"
+ osascript - "${bundle_id}" <<'APPLESCRIPT' >/dev/null 2>&1 || true
+on run argv
+ tell application id (item 1 of argv) to quit
+end run
+APPLESCRIPT
+ fi
+) &
+watchdog_pid="$!"
+
+open_status=0
+wait "${open_pid}" || open_status="$?"
+kill "${watchdog_pid}" >/dev/null 2>&1 || true
+wait "${watchdog_pid}" >/dev/null 2>&1 || true
+
+if [[ -f "${timeout_file}" ]]; then
+ echo "error: timed out waiting for packaged Qt app smoke mode to finish" >&2
+ sed -n '1,120p' "${smoke_log}" >&2
+ exit 1
+fi
+if [[ "${open_status}" != "0" ]]; then
+ echo "error: packaged app launch failed with status ${open_status}" >&2
+ sed -n '1,120p' "${smoke_log}" >&2
+ exit 1
+fi
+if ! grep -q '^WINUAE_QT_SMOKE_WINDOW_VISIBLE$' "${smoke_log}"; then
+ echo "error: packaged app did not report a visible Qt configuration window" >&2
+ sed -n '1,120p' "${smoke_log}" >&2
+ exit 1
+fi
+
+echo "verified packaged Qt app launch: ${app_path}"
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+
+usage() {
+ cat <<EOF
+Usage: $0 dmg-path
+
+Verifies a WinUAE macOS drag-install DMG.
+
+The check mounts the image, validates the app/layout resources, and runs the
+bundled executable with -h from an isolated HOME to catch missing runtime
+libraries without starting emulation.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+dmg_path="${1:-}"
+mount_dir=""
+launch_home=""
+launch_log=""
+
+cleanup() {
+ if [[ -n "${launch_home}" && -d "${launch_home}" ]]; then
+ rm -rf "${launch_home}"
+ fi
+ if [[ -n "${launch_log}" && -f "${launch_log}" ]]; then
+ rm -f "${launch_log}"
+ fi
+ if [[ -n "${mount_dir}" && -d "${mount_dir}" ]]; then
+ hdiutil detach "${mount_dir}" -quiet >/dev/null 2>&1 || true
+ fi
+}
+trap cleanup EXIT
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ echo "error: macOS DMG verification requires Darwin/macOS" >&2
+ exit 1
+fi
+
+if [[ -z "${dmg_path}" ]]; then
+ usage >&2
+ exit 1
+fi
+
+if [[ ! -f "${dmg_path}" ]]; then
+ echo "error: DMG not found: ${dmg_path}" >&2
+ exit 1
+fi
+
+hdiutil verify "${dmg_path}" >/dev/null
+mount_dir="$(hdiutil attach "${dmg_path}" -readonly -noverify -noautoopen | awk -F '\t' '/\/Volumes\// { print $NF; exit }')"
+if [[ -z "${mount_dir}" || ! -d "${mount_dir}" ]]; then
+ echo "error: failed to mount ${dmg_path}" >&2
+ exit 1
+fi
+
+app_dir="${mount_dir}/WinUAE.app"
+info_plist="${app_dir}/Contents/Info.plist"
+
+require_path() {
+ local path="$1"
+ local description="$2"
+ if [[ ! -e "${path}" ]]; then
+ echo "error: missing ${description}: ${path}" >&2
+ exit 1
+ fi
+}
+
+require_file() {
+ local path="$1"
+ local description="$2"
+ if [[ ! -f "${path}" ]]; then
+ echo "error: missing ${description}: ${path}" >&2
+ exit 1
+ fi
+}
+
+require_path "${app_dir}" "app bundle"
+require_file "${info_plist}" "Info.plist"
+require_file "${app_dir}/Contents/MacOS/WinUAE" "bundle executable"
+if [[ ! -x "${app_dir}/Contents/MacOS/WinUAE" ]]; then
+ echo "error: bundle executable is not executable: ${app_dir}/Contents/MacOS/WinUAE" >&2
+ exit 1
+fi
+require_file "${app_dir}/Contents/Resources/WinUAE.icns" "application icon"
+require_file "${app_dir}/Contents/Resources/README_unix.md" "bundled README"
+
+if [[ ! -L "${mount_dir}/Applications" ]]; then
+ echo "error: missing /Applications symlink" >&2
+ exit 1
+fi
+if [[ "$(readlink "${mount_dir}/Applications")" != "/Applications" ]]; then
+ echo "error: Applications symlink does not point to /Applications" >&2
+ exit 1
+fi
+
+require_file "${mount_dir}/.DS_Store" "Finder layout metadata"
+require_file "${mount_dir}/.background/background.tiff" "Finder background image"
+require_file "${mount_dir}/.VolumeIcon.icns" "volume icon"
+
+for record in Iloc bwsp icvp; do
+ if ! LC_ALL=C grep -aq "${record}" "${mount_dir}/.DS_Store"; then
+ echo "error: Finder layout metadata is missing ${record} record in ${mount_dir}/.DS_Store" >&2
+ exit 1
+ fi
+done
+
+if command -v GetFileInfo >/dev/null 2>&1; then
+ volume_attrs="$(GetFileInfo -a "${mount_dir}" 2>/dev/null || true)"
+ case "${volume_attrs}" in
+ *C*) ;;
+ *)
+ echo "error: custom volume icon attribute is not set on ${mount_dir}" >&2
+ exit 1
+ ;;
+ esac
+fi
+
+plist_get() {
+ /usr/libexec/PlistBuddy -c "Print $1" "${info_plist}" 2>/dev/null || true
+}
+
+if [[ "$(plist_get ':CFBundleExecutable')" != "WinUAE" ]]; then
+ echo "error: CFBundleExecutable is not WinUAE" >&2
+ exit 1
+fi
+if [[ "$(plist_get ':CFBundleIconFile')" != "WinUAE.icns" ]]; then
+ echo "error: CFBundleIconFile is not WinUAE.icns" >&2
+ exit 1
+fi
+if [[ "$(plist_get ':CFBundleDocumentTypes:0:CFBundleTypeExtensions:0')" != "uae" ]]; then
+ echo "error: .uae document type is not registered in Info.plist" >&2
+ exit 1
+fi
+
+launch_home="$(mktemp -d -t winuae-dmg-home.XXXXXX)"
+launch_log="$(mktemp -t winuae-dmg-launch.XXXXXX)"
+if ! HOME="${launch_home}" QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy "${app_dir}/Contents/MacOS/WinUAE" -h >"${launch_log}" 2>&1; then
+ echo "error: bundled executable did not start successfully from the mounted DMG" >&2
+ sed -n '1,120p' "${launch_log}" >&2
+ exit 1
+fi
+
+echo "verified ${dmg_path}"