diff --git a/3rdparty/Makefile b/3rdparty/Makefile index 537f9811..366cf142 100644 --- a/3rdparty/Makefile +++ b/3rdparty/Makefile @@ -14,6 +14,8 @@ NC := \033[0m # Optional audiofile: only the legacy `audiofile-patched/` tree (from zip), not the # submodule path, so default Windows deps stay minimal and build with current GCC. AUDIOFILE_SRC := $(shell if [ -d "audiofile-patched" ]; then echo "audiofile-patched"; else echo ""; fi) +HOST_CONFIGURE_ARG := $(if $(MACOS_MINGW_CROSS),--host=$(MINGW_HOST),) +CMAKE_CROSS_ARGS := $(if $(MACOS_MINGW_CROSS),-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=$(CC),) .PHONY: all clean distclean libspectrum audiofile libxml2 mbedtls libssh2 check-tools check-libspectrum check-libssh2 extract-audiofile extract-libxml2 libspectrum-with-audiofile @@ -100,7 +102,7 @@ libspectrum: dist-dirs check-libspectrum if [ ! -f configure ]; then \ ./autogen.sh; \ fi && \ - ./configure --with-fake-glib --without-libaudiofile --without-libgcrypt \ + ./configure $(HOST_CONFIGURE_ARG) --with-fake-glib --without-libaudiofile --without-libgcrypt \ --without-zlib --without-bzip2 --prefix="$(DIST_PREFIX)" && \ $(MAKE) && \ $(MAKE) install @@ -181,6 +183,7 @@ libssh2: mbedtls dist-dirs check-libssh2 cd libssh2/src/build && \ cmake .. \ -G "Unix Makefiles" \ + $(CMAKE_CROSS_ARGS) \ -DCMAKE_INSTALL_PREFIX="$(DIST_PREFIX)" \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ diff --git a/Makefile b/Makefile index f14fee4b..d9b80ae5 100644 --- a/Makefile +++ b/Makefile @@ -174,9 +174,6 @@ archive: 3rdparty fusepb -scheme FuseX \ -configuration Deployment \ -archivePath "$(CURDIR)/$(ARCHIVE_PATH)" \ - SYMROOT=build \ - BUILD_DIR=build \ - CONFIGURATION_BUILD_DIR=build/Deployment \ $(XCODEBUILD_SIGN_ARGS) @echo "Archive created at: $(ARCHIVE_PATH)" diff --git a/build_win32.sh b/build_win32.sh index 3d22fdc8..24b93d73 100644 --- a/build_win32.sh +++ b/build_win32.sh @@ -1,6 +1,6 @@ #!/bin/bash -# FuseX — Windows distribution build (MSYS2 MinGW 64-bit). -# Prerequisite: MSYS2 with mingw-w64-x86_64 toolchain, perl, zip, 7zip (optional), NSIS (optional). +# FuseX — Windows distribution build (MSYS2 or a macOS MinGW cross-toolchain). +# Prerequisite: MinGW-w64, perl, zip, 7zip (optional), NSIS (optional). set -e @@ -11,18 +11,39 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' -if [[ -z "${MSYSTEM:-}" ]]; then - echo -e "${RED}Error: run this from MSYS2 (e.g. \"MSYS2 MinGW 64-bit\"), not plain PowerShell.${NC}" +HOST_OS="$(uname -s)" +if [[ "$HOST_OS" == "Darwin" ]]; then + MACOS_MINGW_CROSS=1 + export MACOS_MINGW_CROSS + MINGW_HOST="${MINGW_HOST:-x86_64-w64-mingw32}" + export MINGW_HOST + export CC="${MINGW_CC:-${MINGW_HOST}-gcc}" + export CXX="${MINGW_CXX:-${MINGW_HOST}-g++}" + export AR="${MINGW_AR:-${MINGW_HOST}-ar}" + export RANLIB="${MINGW_RANLIB:-${MINGW_HOST}-ranlib}" + export STRIP="${MINGW_STRIP:-${MINGW_HOST}-strip}" + export WINDRES="${MINGW_WINDRES:-${MINGW_HOST}-windres}" + echo -e "${GREEN}Using macOS MinGW cross-toolchain: ${MINGW_HOST}${NC}" +elif [[ -z "${MSYSTEM:-}" ]]; then + echo -e "${RED}Error: run this from MSYS2 or from macOS with MinGW-w64 installed.${NC}" exit 1 -fi - -if [[ "$MSYSTEM" != "MINGW64" && "$MSYSTEM" != "UCRT64" ]]; then +elif [[ "$MSYSTEM" != "MINGW64" && "$MSYSTEM" != "UCRT64" ]]; then echo -e "${YELLOW}Warning: MINGW64 or UCRT64 is recommended (current: $MSYSTEM).${NC}" fi SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$SCRIPT_DIR" +if [[ "$HOST_OS" == "Darwin" ]]; then + # Homebrew users often export native include/library flags globally. Those + # paths must not enter Windows objects during a cross-build. + export CFLAGS="${MINGW_CFLAGS:-}" + export CPPFLAGS="${MINGW_CPPFLAGS:-}" + export LDFLAGS="${MINGW_LDFLAGS:-}" + export PKG_CONFIG_PATH="$SCRIPT_DIR/3rdparty/dist/lib/pkgconfig" + export PKG_CONFIG_LIBDIR="$PKG_CONFIG_PATH" +fi + # Windows NSIS installs makensis.exe here but does not add it to MSYS PATH — match typical fuse dev setups. for _nsis in "/c/Program Files/NSIS" "/c/Program Files (x86)/NSIS"; do if [[ -d "$_nsis" ]] && [[ -x "$_nsis/makensis.exe" || -x "$_nsis/makensis" ]]; then @@ -32,24 +53,42 @@ for _nsis in "/c/Program Files/NSIS" "/c/Program Files (x86)/NSIS"; do fi done +if [[ "$HOST_OS" == "Darwin" ]]; then + export MINGW_RUNTIME_DIR="${MINGW_RUNTIME_DIR:-$($CC -print-sysroot)/${MINGW_HOST}/bin}" +fi + # Code generators assume Unix line endings on option/menu data (avoid CRLF breakage under Windows). for f in ui/options.dat menu_data.dat settings.dat keysyms.dat \ z80/opcodes_base.dat z80/opcodes_cb.dat z80/opcodes_ddfd.dat \ z80/opcodes_ddfdcb.dat z80/opcodes_ed.dat; do if [[ -f "$f" ]]; then - sed -i 's/\r$//' "$f" 2>/dev/null || true + if [[ "$HOST_OS" == "Darwin" ]]; then + perl -pi -e 's/\r$//' "$f" + else + sed -i 's/\r$//' "$f" 2>/dev/null || true + fi fi done echo -e "${GREEN}Directory: $SCRIPT_DIR${NC}" echo -e "\n${GREEN}Checking tools...${NC}" -for tool in gcc make pkg-config perl; do +TOOLS=( gcc make pkg-config perl ) +if [[ "$HOST_OS" == "Darwin" ]]; then + TOOLS=( "$CC" make pkg-config perl ) +fi +for tool in "${TOOLS[@]}"; do if ! command -v "$tool" &> /dev/null; then echo -e "${RED}Missing: $tool${NC}" case "$tool" in perl) echo " pacman -S mingw-w64-x86_64-perl" ;; - *) echo " pacman -S mingw-w64-x86_64-$tool" ;; + *) + if [[ "$HOST_OS" == "Darwin" ]]; then + echo " Install the MinGW-w64 build tool containing: $tool" + else + echo " pacman -S mingw-w64-x86_64-$tool" + fi + ;; esac exit 1 fi @@ -71,13 +110,22 @@ export PKG_CONFIG_PATH="$DIST_PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" # Link the static archive explicitly (avoids picking /usr/local libspectrum.dll.a via libtool). PC="$DIST_PREFIX/lib/pkgconfig/libspectrum.pc" if [[ -f "$PC" ]]; then - sed -i 's|^Libs:.*|Libs: ${libdir}/libspectrum.a|' "$PC" 2>/dev/null || true + if [[ "$HOST_OS" == "Darwin" ]]; then + perl -pi -e 's|^Libs:.*|Libs: \${libdir}/libspectrum.a|' "$PC" + else + sed -i 's|^Libs:.*|Libs: ${libdir}/libspectrum.a|' "$PC" 2>/dev/null || true + fi fi # Static libspectrum from 3rdparty: strip dllimport so MinGW links the .a correctly. if [[ -f "$DIST_PREFIX/include/libspectrum.h" ]]; then - sed -i 's/#define LIBSPECTRUM_API __declspec( dllimport )/#define LIBSPECTRUM_API/g' \ - "$DIST_PREFIX/include/libspectrum.h" 2>/dev/null || true + if [[ "$HOST_OS" == "Darwin" ]]; then + perl -pi -e 's/#define LIBSPECTRUM_API __declspec\( dllimport \)/#define LIBSPECTRUM_API/g' \ + "$DIST_PREFIX/include/libspectrum.h" + else + sed -i 's/#define LIBSPECTRUM_API __declspec( dllimport )/#define LIBSPECTRUM_API/g' \ + "$DIST_PREFIX/include/libspectrum.h" 2>/dev/null || true + fi fi # Prefer the static archive: libspectrum.dll.a can make -lspectrum import from a DLL. if [[ -f "$DIST_PREFIX/lib/libspectrum.a" && -f "$DIST_PREFIX/lib/libspectrum.dll.a" ]]; then @@ -86,7 +134,11 @@ if [[ -f "$DIST_PREFIX/lib/libspectrum.a" && -f "$DIST_PREFIX/lib/libspectrum.dl fi echo -e "\n${GREEN}Generating configure if needed...${NC}" -if [[ ! -f configure ]]; then +if [[ "$HOST_OS" == "Darwin" ]]; then + # Building libspectrum refreshes the shared libtool support files in this + # source tree. Regenerate Fuse's configure files with that same version. + ./autogen.sh +elif [[ ! -f configure ]]; then ./autogen.sh else echo " configure present" @@ -95,7 +147,18 @@ fi echo -e "\n${GREEN}Configuring FuseX...${NC}" # Sockets enabled (default): Spectranet, gdbserver remote, etc. require Winsock on Windows. -CONFIGURE_OPTS=( --with-win32 --without-zlib --without-png --prefix=/usr/local ) +CONFIGURE_PREFIX="/usr/local" +if [[ "$HOST_OS" == "Darwin" ]]; then + # Avoid an unrelated native /usr/local/include/libspectrum.h taking + # precedence over the Windows library built above. + CONFIGURE_PREFIX="$DIST_PREFIX" +fi +CONFIGURE_OPTS=( --with-win32 --without-zlib --without-png --prefix="$CONFIGURE_PREFIX" ) +if [[ "$HOST_OS" == "Darwin" ]]; then + CONFIGURE_OPTS=( --host="$MINGW_HOST" "${CONFIGURE_OPTS[@]}" ) + # Never allow pkg-config to fall back to native macOS libraries. + export PKG_CONFIG_LIBDIR="$DIST_PREFIX/lib/pkgconfig" +fi if pkg-config --exists libxml-2.0 2>/dev/null; then echo " with libxml2 (pkg-config)" else @@ -105,10 +168,18 @@ fi ./configure "${CONFIGURE_OPTS[@]}" +if [[ "${WIN32_CLEAN:-0}" == "1" ]]; then + echo -e "\n${GREEN}Cleaning the configured Windows build...${NC}" + make clean +fi + echo -e "\n${GREEN}Building and creating Windows distribution (zip, 7z, installer if NSIS is available)...${NC}" # Same as upstream fuse build_win32.sh: one target builds zip + 7z + setup.exe (see data/win32/distribution.mk). if command -v makensis &>/dev/null || command -v makensis.exe &>/dev/null; then - make dist-win32 -j"$(nproc 2>/dev/null || echo 4)" + make "${WIN32_DIST_TARGET:-dist-win32}" -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" +elif [[ "${REQUIRE_NSIS:-0}" == "1" ]]; then + echo -e "${RED}NSIS is required, but makensis was not found.${NC}" + exit 1 else echo -e "${YELLOW}makensis not on PATH — building zip/7z only. Add NSIS to PATH or install to:${NC}" echo " C:\\Program Files\\NSIS or C:\\Program Files (x86)\\NSIS" @@ -133,7 +204,7 @@ if [[ -z "$SETUP_EXE" ]]; then SETUP_EXE="$( ls "$SCRIPT_DIR"/fusex-*-win32-setup.exe 2>/dev/null | sort -V | tail -1 )" fi APPCAST_SRC="$SCRIPT_DIR/build/appcast-windows/appcast.xml" -if [[ -n "$SETUP_EXE" ]]; then +if [[ -n "$SETUP_EXE" && "${SKIP_APPCAST:-0}" != "1" ]]; then echo -e "\n${GREEN}Generating WinSparkle appcast...${NC}" bash "$SCRIPT_DIR/data/win32/generate-appcast.sh" "$SETUP_EXE" APPCAST_PUSH_DIR="$SCRIPT_DIR/../speccytools.github.io/updates" @@ -143,6 +214,6 @@ if [[ -n "$SETUP_EXE" ]]; then cp -f "$APPCAST_SRC" "$APPCAST_DEST/appcast.xml" echo -e "${GREEN}Copied appcast to ${APPCAST_DEST}/appcast.xml${NC}" fi -else +elif [[ -z "$SETUP_EXE" ]]; then echo -e "\n${YELLOW}Skipping appcast generation (no setup.exe).${NC}" fi diff --git a/data/win32/distribution.mk b/data/win32/distribution.mk index c24df1fb..3a8a8a7e 100644 --- a/data/win32/distribution.mk +++ b/data/win32/distribution.mk @@ -110,6 +110,9 @@ dist-win32-dir: 3rdparty-dist elif test -z "$$MINGW_BIN" && test -d "/mingw32/bin"; then \ MINGW_BIN="/mingw32/bin"; \ fi; \ + if test "$$MACOS_MINGW_CROSS" = "1" && test -n "$$MINGW_RUNTIME_DIR"; then \ + MINGW_BIN="$$MINGW_RUNTIME_DIR"; \ + fi; \ if test -n "$$MINGW_BIN"; then \ echo "Copying MinGW runtime DLLs from $$MINGW_BIN ..."; \ for dll in libwinpthread-1.dll libstdc++-6.dll libgcc_s_seh-1.dll libgcc_s_dw2-1.dll; do \ diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 00000000..28fdfeeb --- /dev/null +++ b/deploy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Build the complete FuseX macOS and Windows release from a Mac. + +set -euo pipefail + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "Error: deploy.sh must be run on macOS." >&2 + exit 1 +fi + +echo "==> Starting macOS deployment" +bash "$SCRIPT_DIR/deploy_mac.sh" + +echo "==> Starting Windows deployment" +bash "$SCRIPT_DIR/deploy_win.sh" + +echo "==> Complete FuseX deployment finished" diff --git a/deploy_mac.sh b/deploy_mac.sh new file mode 100755 index 00000000..f96ea45b --- /dev/null +++ b/deploy_mac.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash + +# Build both the Spectranext-ready and stock FuseX macOS distributions. +# Xcode uses the signing team and identities configured in FuseX.xcodeproj. + +set -euo pipefail + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_DIR="$SCRIPT_DIR/fusepb" +readonly PROJECT_PATH="$PROJECT_DIR/FuseX.xcodeproj" +readonly SETTINGS_PATH="$SCRIPT_DIR/settings.dat" +readonly BUILD_DIR="$SCRIPT_DIR/build" +readonly DIST_APP_PATH="$SCRIPT_DIR/dist/FuseX.app" +readonly APPCAST_REPOSITORY="$SCRIPT_DIR/../speccytools.github.io" +readonly APPCAST_DESTINATION="$APPCAST_REPOSITORY/appcast.xml" +readonly SPARKLE_APPCAST_TOOL="${SPARKLE_GENERATE_APPCAST:-$(find "$HOME/Library/Developer/Xcode/DerivedData" -path '*/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_appcast' -type f -print -quit 2>/dev/null)}" +readonly NOTARIZATION_TIMEOUT="${NOTARIZATION_TIMEOUT:-21600}" +readonly NOTARIZATION_POLL_INTERVAL="${NOTARIZATION_POLL_INTERVAL:-60}" + +restore_settings() { + git -C "$SCRIPT_DIR" restore --source=HEAD -- settings.dat +} + +handle_interrupt() { + exit 130 +} + +trap restore_settings EXIT +trap handle_interrupt INT TERM HUP + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "Error: deploy_mac.sh must be run on macOS." >&2 + exit 1 +fi + +if ! command -v git >/dev/null 2>&1; then + echo "Error: required command not found: git" >&2 + exit 1 +fi + +# Always begin from the committed settings, including when a later preflight +# check fails. The EXIT trap also guarantees that the file is restored. +restore_settings + +if [[ ! -d "$APPCAST_REPOSITORY/.git" ]]; then + echo "Error: appcast repository not found: $APPCAST_REPOSITORY" >&2 + exit 1 +fi + +if [[ -z "$SPARKLE_APPCAST_TOOL" || ! -x "$SPARKLE_APPCAST_TOOL" ]]; then + echo "Error: Sparkle generate_appcast tool not found." >&2 + echo "Build or resolve the FuseX Sparkle package first, or set SPARKLE_GENERATE_APPCAST." >&2 + exit 1 +fi + +for command_name in make perl plutil xcodebuild create-dmg; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "Error: required command not found: $command_name" >&2 + exit 1 + fi +done + +if ! [[ "$NOTARIZATION_TIMEOUT" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: NOTARIZATION_TIMEOUT must be a positive number of seconds." >&2 + exit 1 +fi + +if ! [[ "$NOTARIZATION_POLL_INTERVAL" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: NOTARIZATION_POLL_INTERVAL must be a positive number of seconds." >&2 + exit 1 +fi + +make_spectranext_settings() { + restore_settings + + perl -pi -e \ + 's/^spectranet,[ \t]*boolean,[ \t]*[01][ \t]*$/spectranet, boolean, 1/; s/^start_machine,[ \t]*string,[ \t]*"[^"]*",,[ \t]*'"'"'m'"'"',[ \t]*machine[ \t]*$/start_machine, string, "128",, '"'"'m'"'"', machine/' \ + "$SETTINGS_PATH" + + if ! grep -Fqx 'spectranet, boolean, 1' "$SETTINGS_PATH" || \ + ! grep -Fqx 'start_machine, string, "128",, '\''m'\'', machine' "$SETTINGS_PATH"; then + echo "Error: failed to apply the Spectranext defaults to settings.dat." >&2 + exit 1 + fi +} + +clean_xcode_project() { + echo "==> Cleaning FuseX.xcodeproj" + ( + cd "$PROJECT_DIR" + xcodebuild clean \ + -project "$PROJECT_PATH" \ + -scheme FuseX \ + -configuration Deployment + ) +} + +create_archive() { + local archive_name="$1" + local archive_path="$BUILD_DIR/$archive_name.xcarchive" + + rm -rf "$archive_path" + make -C "$SCRIPT_DIR" archive "ARCHIVE_PATH=build/$archive_name.xcarchive" + + if [[ ! -d "$archive_path" ]]; then + echo "Error: Xcode did not create $archive_path" >&2 + exit 1 + fi + + CREATED_ARCHIVE_PATH="$archive_path" +} + +upload_for_direct_distribution() { + local archive_path="$1" + local variant_name="$2" + local export_options="$BUILD_DIR/ExportOptions-${variant_name}.plist" + local upload_path="$BUILD_DIR/${variant_name}-upload" + + rm -rf "$upload_path" + rm -f "$export_options" + plutil -create xml1 "$export_options" + plutil -insert method -string developer-id "$export_options" + plutil -insert destination -string upload "$export_options" + + echo "==> Uploading $variant_name archive for Direct Distribution" + xcodebuild -exportArchive \ + -archivePath "$archive_path" \ + -exportPath "$upload_path" \ + -exportOptionsPlist "$export_options" \ + -allowProvisioningUpdates +} + +wait_and_export_notarized_app() { + local archive_path="$1" + local variant_name="$2" + local started_at="$SECONDS" + local attempt_log="$BUILD_DIR/export-notarized-${variant_name}.log" + + echo "==> Waiting for Apple to notarize $variant_name" + while (( SECONDS - started_at < NOTARIZATION_TIMEOUT )); do + rm -rf "$DIST_APP_PATH" + mkdir -p "$SCRIPT_DIR/dist" + + if xcodebuild -exportNotarizedApp \ + -archivePath "$archive_path" \ + -exportPath "$SCRIPT_DIR/dist" >"$attempt_log" 2>&1; then + if [[ ! -d "$DIST_APP_PATH" ]]; then + echo "Error: notarized export completed without creating $DIST_APP_PATH" >&2 + exit 1 + fi + echo "==> Exported notarized app to $DIST_APP_PATH" + return + fi + + echo " Notarization is not ready; checking again in ${NOTARIZATION_POLL_INTERVAL}s" + sleep "$NOTARIZATION_POLL_INTERVAL" + done + + echo "Error: notarization of $variant_name did not finish within ${NOTARIZATION_TIMEOUT}s." >&2 + echo "Last Xcode response:" >&2 + tail -n 30 "$attempt_log" >&2 + exit 1 +} + +make_dmg() { + local output_name="$1" + local standard_name="FuseX-${FUSEX_VERSION}.dmg" + + make -C "$SCRIPT_DIR" dmg + if [[ ! -f "$SCRIPT_DIR/$standard_name" ]]; then + echo "Error: make dmg did not create $standard_name" >&2 + exit 1 + fi + + if [[ "$output_name" != "$standard_name" ]]; then + mv -f "$SCRIPT_DIR/$standard_name" "$SCRIPT_DIR/$output_name" + fi + echo "==> Created $SCRIPT_DIR/$output_name" +} + +generate_appcast() { + local dmg_name="FuseX-${FUSEX_VERSION}.dmg" + + echo "==> Generating macOS Sparkle appcast" + make -C "$SCRIPT_DIR" appcast \ + "APPCAST_ARCHIVE=./$dmg_name" \ + "SPARKLE_GENERATE_APPCAST=$SPARKLE_APPCAST_TOOL" + + if [[ ! -f "$BUILD_DIR/appcast/appcast.xml" ]]; then + echo "Error: appcast generation did not create $BUILD_DIR/appcast/appcast.xml" >&2 + exit 1 + fi + + cp -f "$BUILD_DIR/appcast/appcast.xml" "$APPCAST_DESTINATION" + echo "==> Updated $APPCAST_DESTINATION" +} + +FUSEX_VERSION="$(awk -F'= ' '/MARKETING_VERSION = / { gsub(/[";]/, "", $2); print $2; exit }' "$PROJECT_PATH/project.pbxproj")" +if [[ -z "$FUSEX_VERSION" ]]; then + echo "Error: could not determine MARKETING_VERSION from FuseX.xcodeproj." >&2 + exit 1 +fi +readonly FUSEX_VERSION + +mkdir -p "$BUILD_DIR" + +echo "==> Building FuseX $FUSEX_VERSION distributions" +make_spectranext_settings +clean_xcode_project + +create_archive FuseX-spectranext +spectranext_archive="$CREATED_ARCHIVE_PATH" +upload_for_direct_distribution "$spectranext_archive" spectranext +wait_and_export_notarized_app "$spectranext_archive" spectranext +make_dmg "FuseX-spectranext-${FUSEX_VERSION}.dmg" + +echo "==> Restoring stock FuseX settings" +restore_settings + +create_archive FuseX +stock_archive="$CREATED_ARCHIVE_PATH" +upload_for_direct_distribution "$stock_archive" stock +wait_and_export_notarized_app "$stock_archive" stock +make_dmg "FuseX-${FUSEX_VERSION}.dmg" +generate_appcast + +echo "==> Deployment complete" +echo " $SCRIPT_DIR/FuseX-spectranext-${FUSEX_VERSION}.dmg" +echo " $SCRIPT_DIR/FuseX-${FUSEX_VERSION}.dmg" +echo " $APPCAST_DESTINATION" diff --git a/deploy_win.sh b/deploy_win.sh new file mode 100755 index 00000000..c6b35d92 --- /dev/null +++ b/deploy_win.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +# Build both the Spectranext-ready and stock FuseX Windows installers on macOS. + +set -euo pipefail + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SETTINGS_PATH="$SCRIPT_DIR/settings.dat" +readonly APPCAST_REPOSITORY="$SCRIPT_DIR/../speccytools.github.io" +readonly APPCAST_DESTINATION="$APPCAST_REPOSITORY/updates/windows/appcast.xml" +readonly MAKEFILE_BACKUP="$(mktemp "${TMPDIR:-/tmp}/fusex-deploy-win-makefile.XXXXXX")" +readonly TEST_DRIVER_PATH="$SCRIPT_DIR/test-driver" +TEST_DRIVER_EXISTED=0 +if [[ -e "$TEST_DRIVER_PATH" ]]; then + TEST_DRIVER_EXISTED=1 +fi + +cp -p "$SCRIPT_DIR/Makefile" "$MAKEFILE_BACKUP" + +restore_settings() { + git -C "$SCRIPT_DIR" restore --source=HEAD -- settings.dat +} + +handle_interrupt() { + exit 130 +} + +cleanup() { + restore_settings || true + cp -p "$MAKEFILE_BACKUP" "$SCRIPT_DIR/Makefile" + rm -f "$MAKEFILE_BACKUP" + if [[ "$TEST_DRIVER_EXISTED" == "0" ]]; then + rm -f "$TEST_DRIVER_PATH" + fi +} + +trap cleanup EXIT +trap handle_interrupt INT TERM HUP + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "Error: deploy_win.sh must be run on macOS." >&2 + exit 1 +fi + +if [[ ! -d "$APPCAST_REPOSITORY/.git" ]]; then + echo "Error: appcast repository not found: $APPCAST_REPOSITORY" >&2 + exit 1 +fi + +for command_name in git perl make pkg-config cmake makensis autoreconf automake glibtoolize \ + x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++ \ + x86_64-w64-mingw32-ar x86_64-w64-mingw32-ranlib \ + x86_64-w64-mingw32-strip x86_64-w64-mingw32-windres; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "Error: required command not found: $command_name" >&2 + echo "Install the macOS build tools with: brew install mingw-w64 nsis autoconf automake libtool" >&2 + exit 1 + fi +done + +# Begin from the committed defaults and restore them even if either build fails. +restore_settings + +make_spectranext_settings() { + restore_settings + + perl -pi -e \ + 's/^spectranet,[ \t]*boolean,[ \t]*[01][ \t]*$/spectranet, boolean, 1/; s/^start_machine,[ \t]*string,[ \t]*"[^"]*",,[ \t]*'"'"'m'"'"',[ \t]*machine[ \t]*$/start_machine, string, "128",, '"'"'m'"'"', machine/' \ + "$SETTINGS_PATH" + + if ! grep -Fqx 'spectranet, boolean, 1' "$SETTINGS_PATH" || \ + ! grep -Fqx 'start_machine, string, "128",, '\''m'\'', machine' "$SETTINGS_PATH"; then + echo "Error: failed to apply the Spectranext defaults to settings.dat." >&2 + exit 1 + fi +} + +build_installer() { + local variant_name="$1" + local skip_appcast="$2" + + echo "==> Building the $variant_name Windows installer" + WIN32_CLEAN=1 \ + WIN32_DIST_TARGET=dist-win32-exe \ + REQUIRE_NSIS=1 \ + SKIP_APPCAST="$skip_appcast" \ + bash "$SCRIPT_DIR/build_win32.sh" + + PACKAGE_VERSION="$(sed -n 's/^#define PACKAGE_VERSION "\(.*\)"/\1/p' "$SCRIPT_DIR/config.h" | head -1)" + if [[ -z "$PACKAGE_VERSION" ]]; then + echo "Error: could not determine PACKAGE_VERSION from config.h." >&2 + exit 1 + fi + + BUILT_INSTALLER="$SCRIPT_DIR/fusex-${PACKAGE_VERSION}-win32-setup.exe" + if [[ ! -f "$BUILT_INSTALLER" ]]; then + echo "Error: NSIS did not create $BUILT_INSTALLER" >&2 + exit 1 + fi +} + +make_spectranext_settings +build_installer Spectranext 1 +spectranext_installer="$SCRIPT_DIR/fusex-spectranext-${PACKAGE_VERSION}-win32-setup.exe" +mv -f "$BUILT_INSTALLER" "$spectranext_installer" + +echo "==> Restoring stock FuseX settings" +restore_settings +build_installer stock 0 +stock_installer="$BUILT_INSTALLER" + +if [[ ! -f "$APPCAST_DESTINATION" ]]; then + echo "Error: Windows appcast was not created at $APPCAST_DESTINATION" >&2 + exit 1 +fi + +echo "==> Deployment complete" +echo " $spectranext_installer" +echo " $stock_installer" +echo " $APPCAST_DESTINATION"