diff --git a/.github/actions/prepare_macos_tooling/action.yml b/.github/actions/prepare_macos_tooling/action.yml
new file mode 100644
index 000000000..ce199194a
--- /dev/null
+++ b/.github/actions/prepare_macos_tooling/action.yml
@@ -0,0 +1,181 @@
+name: Cache TRX MacOS Dependencies
+
+inputs:
+ FFMPEG_INSTALL_TMP_ARM64:
+ required: false
+ default: /opt/local/install_arm64
+ FFMPEG_INSTALL_TMP_X86_64:
+ required: false
+ default: /opt/local/install_x86_64
+ CACHE_SRC_DIR:
+ required: false
+ default: /opt/local
+ CACHE_DIR:
+ required: true
+
+runs:
+ using: "composite"
+ steps:
+ - name: Select latest stable Xcode
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
+ - name: Install and update MacPorts
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ wget -O ${{ github.workspace }}/macports.pkg https://github.com/macports/macports-base/releases/download/v2.9.2/MacPorts-2.9.2-14-Sonoma.pkg
+ sudo installer -pkg ${{ github.workspace }}/macports.pkg -target /
+
+ - name: Install build and deployment tools
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ # Install Python first to avoid multiple Python in the dep tree later on.
+ sudo port -N install python312 py312-pip
+ sudo port select --set python python312
+ sudo port select --set python3 python312
+ sudo port select --set pip pip312
+ sudo port select --set pip3 pip312
+
+ # Install the rest.
+ sudo port -N install create-dmg meson ninja pkgconfig
+ sudo pip3 install pyjson5
+
+ - name: "Build dependencies: Compression libraries (universal)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ sudo port -N install zlib +universal
+ sudo port -N install bzip2 +universal
+ sudo port -N install xz +universal
+
+ - name: "Build dependency: pcre2 (universal)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: sudo port -N install pcre2 +universal
+
+ - name: "Build dependency: libsdl2 (universal)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: sudo port -N install libsdl2 +universal
+
+ - name: "Build dependency: uthash (universal)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: sudo port -N install uthash +universal
+
+ - name: "Build dependency: ffmpeg (universal)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ # Install to separate staging paths for all architectures in
+ # preparation for fusing universal libraries in a follow-up step.
+ cd "$RUNNER_TEMP"
+ git clone --depth 1 --branch "n4.4.1" https://github.com/FFmpeg/FFmpeg
+ cd FFmpeg
+
+ # Common FFmpeg configure options
+ FFMPEG_CONFIG_OPTIONS=" \
+ --enable-shared \
+ --disable-static \
+ $(cat $GITHUB_WORKSPACE/tools/ffmpeg_flags.txt)"
+
+ # Configure for arm64.
+ ./configure \
+ --arch=arm64 \
+ --prefix=${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }} \
+ --cc='clang' \
+ $FFMPEG_CONFIG_OPTIONS
+
+ # Build and install.
+ make -j$(sysctl -n hw.ncpu)
+ sudo make install
+
+ # Reset build directory.
+ make clean
+
+ # Configure for x86-64.
+ ./configure \
+ --arch=x86_64 \
+ --enable-cross-compile \
+ --prefix=${{ inputs.FFMPEG_INSTALL_TMP_X86_64 }} \
+ --cc='clang -arch x86_64' \
+ $FFMPEG_CONFIG_OPTIONS
+
+ # Build and install.
+ make -j$(sysctl -n hw.ncpu)
+ sudo make install
+
+ - name: "Build dependency: ffmpeg (fuse universal libraries)"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ # Libs
+ FFMPEG_LIBS=(
+ "libavcodec"
+ "libavdevice"
+ "libavfilter"
+ "libavformat"
+ "libavutil"
+ "libpostproc"
+ "libswresample"
+ "libswscale"
+ )
+
+ # Recreate include tree in MacPorts install prefix.
+ sudo rsync -arvL ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/include/ ${{ inputs.CACHE_SRC_DIR }}/include/
+
+ # Recreate library symlinks in MacPorts install prefix.
+ sudo find ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/ -type l -exec cp -P '{}' ${{ inputs.CACHE_SRC_DIR }}/lib/ ';'
+
+ # Fuse platform-specific binaries into a universal binary.
+ for LIB in ${FFMPEG_LIBS[@]}; do
+ RESOLVED_LIB=$(ls -l ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/${LIB}* \
+ | grep -v '^l' \
+ | awk -F'/' '{print $NF}')
+
+ sudo lipo -create \
+ ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/$RESOLVED_LIB \
+ ${{ inputs.FFMPEG_INSTALL_TMP_X86_64 }}/lib/$RESOLVED_LIB \
+ -output ${{ inputs.CACHE_SRC_DIR }}/lib/$RESOLVED_LIB
+
+ sudo ln -s -f \
+ ${{ inputs.CACHE_SRC_DIR }}/lib/$RESOLVED_LIB \
+ ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/$RESOLVED_LIB
+ sudo ln -s -f \
+ ${{ inputs.CACHE_SRC_DIR }}/lib/$RESOLVED_LIB \
+ ${{ inputs.FFMPEG_INSTALL_TMP_X86_64 }}/lib/$RESOLVED_LIB
+ done
+
+ # Update and install pkgconfig files.
+ for file in "${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/pkgconfig"/*.pc; do
+ sudo sed -i '' "s:${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}:${{ inputs.CACHE_SRC_DIR }}:g" "$file"
+ done
+ sudo mv ${{ inputs.FFMPEG_INSTALL_TMP_ARM64 }}/lib/pkgconfig/* ${{ inputs.CACHE_SRC_DIR }}/lib/pkgconfig/
+
+ - name: "Prepare dependencies for caching"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ # Remove MacPorts leftover build and download files
+ sudo rm -rf /opt/local/var/macports/build/*
+ sudo rm -rf /opt/local/var/macports/distfiles/*
+ sudo rm -rf /opt/local/var/macports/packages/*
+
+ # Delete broken symlinks
+ sudo find ${{ inputs.CACHE_SRC_DIR }} -type l ! -exec test -e {} \; -exec rm {} \;
+
+ # Trying to cache the source directory directly leads to permission errors,
+ # so copy it to an intermediate temporary directory. Also, expands all the symlinks to hard copies.
+ sudo rsync -arvqL ${{ inputs.CACHE_SRC_DIR }}/ ${{ inputs.CACHE_DIR }}
+
+ - name: "Save dependencies to cache"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ uses: actions/cache/save@v4
+ with:
+ key: ${{ runner.os }}-tooling-${{ hashFiles('.github/actions/prepare_macos_tooling/action.yml') }}
+ path: |
+ ${{ inputs.CACHE_DIR }}
diff --git a/.github/workflows/job_build_tr1_macos.yml b/.github/workflows/job_build_tr1_macos.yml
index 466943276..08af166de 100644
--- a/.github/workflows/job_build_tr1_macos.yml
+++ b/.github/workflows/job_build_tr1_macos.yml
@@ -59,174 +59,23 @@ jobs:
echo -e "/opt/local/bin" >> $GITHUB_PATH
echo -e "/opt/local/sbin" >> $GITHUB_PATH
- - name: "Restore dependencies from cache"
+ - name: "Try restore dependencies from cache"
id: restore-cache
uses: actions/cache/restore@v4
with:
- key: ${{ runner.os }}-tooling-${{ hashFiles('.github/workflows/job_build_tr1_macos.yml') }}
+ key: ${{ runner.os }}-tooling-${{ hashFiles('.github/actions/prepare_macos_tooling/action.yml') }}
path: |
- ${{ env.CACHE_TMP_DIR }}
- ${{ env.FFMPEG_INSTALL_TMP_ARM64 }}
- ${{ env.FFMPEG_INSTALL_TMP_X86_64 }}
- ${{ env.FFMPEG_INSTALL_TMP_UNIVERSAL }}
-
+ /tmp/opt_local/
+ - name: "Build MacOS dependencies"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ uses: ./.github/actions/prepare_macos_tooling
+ with:
+ CACHE_DIR: /tmp/opt_local/
- name: "Prepare cached dependencies for use"
if: steps.restore-cache.outputs.cache-hit == 'true'
+ shell: bash
run: |
- sudo rsync -arvq ${{ env.CACHE_TMP_DIR }} ${{ env.CACHE_DST_DIR }}
-
- - name: Select latest stable Xcode
- if: steps.restore-cache.outputs.cache-hit != 'true'
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
-
- - name: Install and update MacPorts
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- wget -O ${{ github.workspace }}/macports.pkg https://github.com/macports/macports-base/releases/download/v2.9.2/MacPorts-2.9.2-14-Sonoma.pkg
- sudo installer -pkg ${{ github.workspace }}/macports.pkg -target /
-
- - name: Install build and deployment tools
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- # Install Python first to avoid multiple Python in the dep tree later on.
- sudo port -N install python312 py312-pip
- sudo port select --set python python312
- sudo port select --set python3 python312
- sudo port select --set pip pip312
- sudo port select --set pip3 pip312
-
- # Install the rest.
- sudo port -N install create-dmg meson ninja pkgconfig
- sudo pip3 install pyjson5
-
- - name: "Build dependencies: Compression libraries (universal)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- sudo port -N install zlib +universal
- sudo port -N install bzip2 +universal
- sudo port -N install xz +universal
-
- - name: "Build dependency: pcre2 (universal)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: sudo port -N install pcre2 +universal
-
- - name: "Build dependency: libsdl2 (universal)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: sudo port -N install libsdl2 +universal
-
- - name: "Build dependency: uthash (universal)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: sudo port -N install uthash +universal
-
- - name: "Build dependency: ffmpeg (universal)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- # Install to separate staging paths for all architectures in
- # preparation for fusing universal libraries in a follow-up step.
- cd "$RUNNER_TEMP"
- git clone --depth 1 --branch "n4.4.1" https://github.com/FFmpeg/FFmpeg
- cd FFmpeg
-
- # Common FFmpeg configure options
- FFMPEG_CONFIG_OPTIONS=" \
- --enable-shared \
- --disable-static \
- $(cat $GITHUB_WORKSPACE/tools/ffmpeg_flags.txt)"
-
- # Configure for arm64.
- ./configure \
- --arch=arm64 \
- --prefix=$FFMPEG_INSTALL_TMP_ARM64 \
- --cc='clang' \
- $FFMPEG_CONFIG_OPTIONS
-
- # Build and install.
- make -j$(sysctl -n hw.ncpu)
- sudo make install
-
- # Reset build directory.
- make clean
-
- # Configure for x86-64.
- ./configure \
- --arch=x86_64 \
- --enable-cross-compile \
- --prefix=$FFMPEG_INSTALL_TMP_X86_64 \
- --cc='clang -arch x86_64' \
- $FFMPEG_CONFIG_OPTIONS
-
- # Build and install.
- make -j$(sysctl -n hw.ncpu)
- sudo make install
-
- - name: "Build dependency: ffmpeg (fuse universal libraries)"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- # Libs
- FFMPEG_LIBS=(
- "libavcodec"
- "libavdevice"
- "libavfilter"
- "libavformat"
- "libavutil"
- "libpostproc"
- "libswresample"
- "libswscale"
- )
-
- # Recreate library symlinks in MacPorts install prefix.
- sudo find $FFMPEG_INSTALL_TMP_ARM64/lib -type l -exec cp -P '{}' $FFMPEG_INSTALL_FINAL/LIB ';'
-
- # `lipo` cannot overwrite binaries in place, so we stage the
- # fused binaries in a temporary directory.
- mkdir -p $FFMPEG_INSTALL_TMP_UNIVERSAL
- for LIB in ${FFMPEG_LIBS[@]}; do
- RESOLVED_LIB=$(ls -l $FFMPEG_INSTALL_TMP_ARM64/lib/${LIB}* \
- | grep -v '^l' \
- | awk -F'/' '{print $NF}')
-
- lipo -create \
- $FFMPEG_INSTALL_TMP_ARM64/lib/$RESOLVED_LIB \
- $FFMPEG_INSTALL_TMP_X86_64/lib/$RESOLVED_LIB \
- -output $FFMPEG_INSTALL_TMP_UNIVERSAL/$RESOLVED_LIB
-
- # Replace the arch-specific libraries with links to the universal
- # binary, so `bundle_dylibs` will always gather a universal build.
- sudo ln -s -f $FFMPEG_INSTALL_TMP_UNIVERSAL/$RESOLVED_LIB $FFMPEG_INSTALL_TMP_ARM64/lib/$RESOLVED_LIB
- sudo ln -s -f $FFMPEG_INSTALL_TMP_UNIVERSAL/$RESOLVED_LIB $FFMPEG_INSTALL_TMP_X86_64/lib/$RESOLVED_LIB
- done
-
- # Copy the fused binaries to the MacPorts install prefix.
- sudo cp $FFMPEG_INSTALL_TMP_UNIVERSAL/*.dylib $FFMPEG_INSTALL_FINAL/lib/
-
- # Update and install pkgconfig files.
- for file in "$FFMPEG_INSTALL_TMP_ARM64/lib/pkgconfig"/*.pc; do
- sudo sed -i '' "s|^prefix=.*|prefix=$FFMPEG_INSTALL_FINAL|" "$file"
- sudo sed -i '' "s|^libdir=.*|libdir=$FFMPEG_INSTALL_FINAL/lib|" "$file"
- done
- sudo mv $FFMPEG_INSTALL_TMP_ARM64/lib/pkgconfig/* $FFMPEG_INSTALL_FINAL/lib/pkgconfig/
-
- - name: "Prepare dependencies for caching"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- run: |
- # Remove MacPorts leftover build and download files
- sudo rm -rf /opt/local/var/macports/build/*
- sudo rm -rf /opt/local/var/macports/distfiles/*
- sudo rm -rf /opt/local/var/macports/packages/*
- sudo rsync -arvq ${{ env.CACHE_DST_DIR }} ${{ env.CACHE_TMP_DIR }}
-
- - name: "Save dependencies to cache"
- if: steps.restore-cache.outputs.cache-hit != 'true'
- uses: actions/cache/save@v4
- with:
- key: ${{ steps.restore-cache.outputs.cache-primary-key }}
- path: |
- ${{ env.CACHE_TMP_DIR }}
- ${{ env.FFMPEG_INSTALL_TMP_ARM64 }}
- ${{ env.FFMPEG_INSTALL_TMP_X86_64 }}
- ${{ env.FFMPEG_INSTALL_TMP_UNIVERSAL }}
+ sudo rsync -arvq /tmp/opt_local/ /opt/local/
- name: Setup CA
run: |
@@ -242,7 +91,7 @@ jobs:
- name: Build x86-64
run: |
BUILD_DIR=build-x86-64
- BUILD_OPTIONS="src/tr1 --prefix=/tmp/TR1X.app --bindir=Contents/MacOS --cross-file tools/tr1/mac/x86-64_cross_file.txt --buildtype ${{ inputs.target }}"
+ BUILD_OPTIONS="src/tr1 --prefix=/tmp/TR1X.app --bindir=Contents/MacOS --cross-file tools/shared/mac/x86-64_cross_file.txt --buildtype ${{ inputs.target }}"
meson setup $BUILD_DIR $BUILD_OPTIONS
meson compile -C $BUILD_DIR
@@ -255,7 +104,7 @@ jobs:
mv $BUNDLE_EXEC_DIR/TR1X_universal $BUNDLE_EXEC_DIR/TR1X
# Update dynamic library links in the fused executable.
- ./tools/tr1/mac/bundle_dylibs --links-only
+ ./tools/shared/mac/bundle_dylibs -a TR1X --links-only
- name: Sign app bundle
run: |
@@ -272,7 +121,7 @@ jobs:
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | awk -F'"' '{print $2}')
- tools/tr1/mac/create_installer
+ tools/shared/mac/create_installer -a TR1X -i data/tr1/mac/icon.icns
xattr -cr TR1X-Installer.dmg
/usr/bin/codesign --force --options runtime -s "${IDENTITY}" --keychain $KEYCHAIN_PATH -v TR1X-Installer.dmg
xcrun notarytool submit --wait --apple-id "$MACOS_APPLEID" --password "$MACOS_APP_PWD" --team-id "$MACOS_TEAMID" TR1X-Installer.dmg
diff --git a/.github/workflows/job_build_tr2_macos.yml b/.github/workflows/job_build_tr2_macos.yml
new file mode 100644
index 000000000..2c7e51d21
--- /dev/null
+++ b/.github/workflows/job_build_tr2_macos.yml
@@ -0,0 +1,132 @@
+name: Build TR2X and the installer (macOS)
+
+on:
+ workflow_call:
+ inputs:
+ target:
+ type: string
+ description: "Target to build for"
+ required: true
+ let_mac_fail:
+ type: boolean
+ description: "Do not require Mac builds to pass"
+ required: false
+ default: false
+
+env:
+ C_INCLUDE_PATH: /opt/local/include/uthash/:/opt/local/include/
+
+jobs:
+ build:
+ name: Build release assets (mac)
+ runs-on: macos-14
+ continue-on-error: ${{ inputs.let_mac_fail == true || inputs.let_mac_fail == 'true' }}
+ steps:
+ - name: Set up signing certificate
+ env:
+ MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
+ MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
+ run: |
+ CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ echo -n "$MACOS_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
+ security create-keychain -p "$MACOS_KEYCHAIN_PWD" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security unlock-keychain -p "$MACOS_KEYCHAIN_PWD" $KEYCHAIN_PATH
+ security import $CERTIFICATE_PATH -P "$MACOS_KEYCHAIN_PWD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH -T /usr/bin/codesign
+ security list-keychain -d user -s $KEYCHAIN_PATH
+ security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k $MACOS_KEYCHAIN_PWD $KEYCHAIN_PATH
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: 'true'
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
+
+ - name: Extend PATH for MacPorts
+ run: |
+ echo -e "/opt/local/bin" >> $GITHUB_PATH
+ echo -e "/opt/local/sbin" >> $GITHUB_PATH
+
+ - name: "Try restore dependencies from cache"
+ id: restore-cache
+ uses: actions/cache/restore@v4
+ with:
+ key: ${{ runner.os }}-tooling-${{ hashFiles('.github/actions/prepare_macos_tooling/action.yml') }}
+ path: |
+ /tmp/opt_local/
+ - name: "Build MacOS dependencies"
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ uses: ./.github/actions/prepare_macos_tooling
+ with:
+ CACHE_DIR: /tmp/opt_local/
+ - name: "Prepare cached dependencies for use"
+ if: steps.restore-cache.outputs.cache-hit == 'true'
+ shell: bash
+ run: |
+ sudo rsync -arvq /tmp/opt_local/ /opt/local/
+
+ - name: Setup CA
+ run: |
+ sudo port -N install apple-pki-bundle curl-ca-bundle
+
+ - name: Build arm64 and create app bundle
+ run: |
+ BUILD_DIR=build-arm64
+ BUILD_OPTIONS="src/tr2 --prefix=/tmp/TR2X.app --bindir=Contents/MacOS --buildtype ${{ inputs.target }}"
+ meson setup $BUILD_DIR $BUILD_OPTIONS
+ meson install -C $BUILD_DIR
+
+ - name: Build x86-64
+ run: |
+ BUILD_DIR=build-x86-64
+ BUILD_OPTIONS="src/tr2 --prefix=/tmp/TR2X.app --bindir=Contents/MacOS --cross-file tools/shared/mac/x86-64_cross_file.txt --buildtype ${{ inputs.target }}"
+ meson setup $BUILD_DIR $BUILD_OPTIONS
+ meson compile -C $BUILD_DIR
+
+ - name: Fuse universal executable
+ run: |
+ BUNDLE_EXEC_DIR=/tmp/TR2X.app/Contents/MacOS
+
+ # Fuse executable and move it into the app bundle.
+ lipo -create build-x86-64/TR2X $BUNDLE_EXEC_DIR/TR2X -output $BUNDLE_EXEC_DIR/TR2X_universal
+ mv $BUNDLE_EXEC_DIR/TR2X_universal $BUNDLE_EXEC_DIR/TR2X
+
+ # Update dynamic library links in the fused executable.
+ ./tools/shared/mac/bundle_dylibs -a TR2X --links-only
+
+ - name: Sign app bundle
+ run: |
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | awk -F'"' '{print $2}')
+ xattr -cr /tmp/TR2X.app
+ /usr/bin/codesign --force --deep --options runtime -s "${IDENTITY}" --keychain $KEYCHAIN_PATH -v /tmp/TR2X.app
+
+ - name: Create, sign and notarize disk image
+ env:
+ MACOS_APPLEID: ${{ secrets.MACOS_APPLEID }}
+ MACOS_APP_PWD: ${{ secrets.MACOS_APP_PWD }}
+ MACOS_TEAMID: ${{ secrets.MACOS_TEAMID }}
+ run: |
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | awk -F'"' '{print $2}')
+ tools/shared/mac/create_installer -a TR2X -i data/tr2/mac/icon.icns
+ xattr -cr TR2X-Installer.dmg
+ /usr/bin/codesign --force --options runtime -s "${IDENTITY}" --keychain $KEYCHAIN_PATH -v TR2X-Installer.dmg
+ xcrun notarytool submit --wait --apple-id "$MACOS_APPLEID" --password "$MACOS_APP_PWD" --team-id "$MACOS_TEAMID" TR2X-Installer.dmg
+ xcrun stapler staple -v TR2X-Installer.dmg
+ mv TR2X-Installer.dmg "TR2X-$(tools/get_version 1)-Installer.dmg"
+
+ - id: vars
+ name: Prepare variables
+ run: echo "version=$(tools/get_version 1)" >> $GITHUB_OUTPUT
+
+ - name: Upload signed+notarized installer image
+ uses: actions/upload-artifact@v4
+ with:
+ name: TR2X-${{ steps.vars.outputs.version }}-mac
+ path: |
+ *.dmg
+ compression-level: 0 # .dmg is already compressed.
diff --git a/.github/workflows/pr_builds.yml b/.github/workflows/pr_builds.yml
index 7eb5fe6a6..ba549eff8 100644
--- a/.github/workflows/pr_builds.yml
+++ b/.github/workflows/pr_builds.yml
@@ -33,3 +33,12 @@ jobs:
with:
target: 'debug'
secrets: inherit
+
+ package_tr2_mac:
+ name: Build TR2
+ if: vars.MACOS_ENABLE == 'true'
+ uses: ./.github/workflows/job_build_tr2_macos.yml
+ with:
+ target: 'debug'
+ let_mac_fail: true
+ secrets: inherit
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 8d283f489..b6dbe4548 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -36,6 +36,17 @@ jobs:
target: 'debug'
secrets: inherit
+ package_tr2_mac:
+ name: Build TR2
+ if: |
+ vars.PRERELEASE_ENABLE == 'true' &&
+ vars.MACOS_ENABLE == 'true'
+ uses: ./.github/workflows/job_build_tr2_macos.yml
+ with:
+ target: 'debug'
+ let_mac_fail: true
+ secrets: inherit
+
publish_prerelease:
if: vars.PRERELEASE_ENABLE == 'true'
name: Create a prerelease
@@ -43,6 +54,7 @@ jobs:
- package_tr1_multiplatform
- package_tr1_mac
- package_tr2_multiplatform
+ - package_tr2_mac
with:
draft: false
prerelease: true
diff --git a/data/tr2/mac/Info.plist b/data/tr2/mac/Info.plist
new file mode 100644
index 000000000..c523722d5
--- /dev/null
+++ b/data/tr2/mac/Info.plist
@@ -0,0 +1,12 @@
+
+
+
+
+ CFBundleIdentifier
+ com.lostartifacts.tr1x
+ CFBundleName
+ TR2X
+ CFBundleIconFile
+ icon
+
+
diff --git a/data/tr2/mac/icon.icns b/data/tr2/mac/icon.icns
new file mode 100644
index 000000000..01c5a1b7e
Binary files /dev/null and b/data/tr2/mac/icon.icns differ
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index e5cbd35f2..ed5d29317 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -50,6 +50,28 @@ Subsequent builds:
Run WSL and continue with the instructions from the `Compiling on Ubuntu` section.
+### Compiling on MacOS
+
+MacPorts:
+https://github.com/macports/macports-base/releases
+
+
+
+- **With Docker**:
+
+ Make sure to install Docker and [just](https://github.com/casey/just).
+ To see the list of all possible build targets, run `just -l`. To build the
+ images, use the `just *-build-*` commands relevant to the game and platform
+ you want to build for. The binaries should appear in the `build/`
+ directory.
+
+- **Without Docker**:
+
+ This scenario is not officially supported, but you can see how it's done by
+ examining the files in the `tools/*/docker/` directory for the external
+ dependencies and `meson.build` for the local files, then tailoring your
+ system to match the process.
+
### Supported compilers
diff --git a/docs/tr1/SECRETS.md b/docs/tr1/SECRETS.md
index 97e2a8dbc..0fc711e1b 100644
--- a/docs/tr1/SECRETS.md
+++ b/docs/tr1/SECRETS.md
@@ -75,6 +75,7 @@ MacOS builds require a paid Apple Developer account.
5. Serialize the key in base-64 without spaces - run:
> base64 TR1X.pem|tr -d '\n'
+ > base64 -i BUILD_CERTIFICATE.p12 | pbcopy (macos)
The result is to be put as the value of the `MACOS_CERTIFICATE` secret.
diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md
index 77626fabf..4738b81ed 100644
--- a/docs/tr2/CHANGELOG.md
+++ b/docs/tr2/CHANGELOG.md
@@ -1,5 +1,6 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.8...develop) - ××××-××-××
- added Linux builds and toolchain (#1598)
+- added macOS builds (for both Apple Silicon and Intel) (#2226)
- added pause dialog (#1638)
- added a photo mode feature (#2277)
- added fade-out effect to the demos
diff --git a/docs/tr2/README.md b/docs/tr2/README.md
index 7a7ad3b25..e88da1f44 100644
--- a/docs/tr2/README.md
+++ b/docs/tr2/README.md
@@ -131,6 +131,7 @@ game with new enhancements and features.
#### Miscellaneous
- added Linux builds
+- added macOS builds
- added .jpeg/.png screenshots
- added ability to skip FMVs with both the Action key
- added ability to skip end credits with the Action and Escape keys
diff --git a/src/tr1/meson.build b/src/tr1/meson.build
index 430261feb..30a1a8963 100644
--- a/src/tr1/meson.build
+++ b/src/tr1/meson.build
@@ -301,5 +301,5 @@ if host_machine.system() == 'darwin'
install_subdir('../../data/tr1/ship/shaders', install_dir : 'Contents/Resources')
install_data('../../data/tr1/mac/icon.icns', install_dir : 'Contents/Resources')
install_data('../../data/tr1/mac/Info.plist', install_dir : 'Contents')
- meson.add_install_script('../../tools/tr1/mac/bundle_dylibs')
+ meson.add_install_script('../../tools/shared/mac/bundle_dylibs', '-a', 'TR1X')
endif
diff --git a/src/tr2/game/output.c b/src/tr2/game/output.c
index af4e18d6a..235240537 100644
--- a/src/tr2/game/output.c
+++ b/src/tr2/game/output.c
@@ -824,8 +824,8 @@ int32_t Output_GetObjectBounds(const BOUNDS_16 *const bounds)
return 0;
}
- constexpr int32_t vtx_count = 8;
- const XYZ_32 vtx[vtx_count] = {
+ const int32_t vtx_count = 8;
+ const XYZ_32 vtx[] = {
{ .x = bounds->min.x, .y = bounds->min.y, .z = bounds->min.z },
{ .x = bounds->max.x, .y = bounds->min.y, .z = bounds->min.z },
{ .x = bounds->max.x, .y = bounds->max.y, .z = bounds->min.z },
diff --git a/src/tr2/game/render/common.c b/src/tr2/game/render/common.c
index 6bf23aad7..c52d5270b 100644
--- a/src/tr2/game/render/common.c
+++ b/src/tr2/game/render/common.c
@@ -15,6 +15,9 @@
#include
#include
+#include
+#include
+
static RENDERER m_Renderer_SW = {};
static RENDERER m_Renderer_HW = {};
static RENDERER *m_PreviousRenderer = NULL;
@@ -32,6 +35,7 @@ static struct {
static RENDERER *M_GetRenderer(void);
static void M_ReuploadBackground(void);
static void M_ResetPolyList(void);
+static void M_SetGLBackend(GFX_GL_BACKEND backend);
static RENDERER *M_GetRenderer(void)
{
@@ -69,15 +73,60 @@ static void M_ResetPolyList(void)
}
}
+static void M_SetGLBackend(const GFX_GL_BACKEND backend)
+{
+ switch (backend) {
+ case GFX_GL_21:
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+ break;
+
+ case GFX_GL_33C:
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(
+ SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ break;
+
+ case GFX_GL_INVALID_BACKEND:
+ ASSERT_FAIL();
+ break;
+ }
+}
+
void Render_Init(void)
{
- LOG_DEBUG("");
- GFX_Context_Attach(g_SDLWindow, GFX_GL_33C);
- GFX_Context_SetRenderingMode(GFX_RM_FRAMEBUFFER);
- m_FadeRenderer = GFX_FadeRenderer_Create();
- m_BackgroundRenderer = GFX_2D_Renderer_Create();
- Renderer_SW_Prepare(&m_Renderer_SW);
- Renderer_HW_Prepare(&m_Renderer_HW);
+ // TODO Move to libtrx later and combine with S_Shell_CreateWindow.
+ const GFX_GL_BACKEND backends_to_try[] = {
+ // clang-format off
+ GFX_GL_33C,
+ GFX_GL_21,
+ GFX_GL_INVALID_BACKEND, // guard
+ // clang-format on
+ };
+
+ for (int32_t i = 0; backends_to_try[i] != GFX_GL_INVALID_BACKEND; i++) {
+ const GFX_GL_BACKEND backend = backends_to_try[i];
+
+ M_SetGLBackend(backend);
+
+ int32_t major;
+ int32_t minor;
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
+ LOG_DEBUG("Trying GL backend %d.%d", major, minor);
+ if (GFX_Context_Attach(g_SDLWindow, backend)) {
+ GFX_Context_SetRenderingMode(GFX_RM_FRAMEBUFFER);
+ m_FadeRenderer = GFX_FadeRenderer_Create();
+ m_BackgroundRenderer = GFX_2D_Renderer_Create();
+ Renderer_SW_Prepare(&m_Renderer_SW);
+ Renderer_HW_Prepare(&m_Renderer_HW);
+ return;
+ }
+ }
+
+ Shell_ExitSystem("System Error: cannot attach opengl context");
}
void Render_Shutdown(void)
diff --git a/src/tr2/meson.build b/src/tr2/meson.build
index af7ae420b..2d5ab8086 100644
--- a/src/tr2/meson.build
+++ b/src/tr2/meson.build
@@ -36,6 +36,11 @@ build_opts = [
add_project_arguments(build_opts, language: 'c')
+# Always dynamically link on macOS
+if host_machine.system() == 'darwin'
+ staticdeps = false
+endif
+
null_dep = dependency('', required: false)
dep_trx = trx.get_variable('dep_trx')
dep_sdl2 = dependency('SDL2', static: staticdeps)
@@ -301,4 +306,14 @@ executable(
link_args: link_args,
dependencies: dependencies,
win_subsystem: 'windows',
+ install: true,
)
+
+if host_machine.system() == 'darwin'
+ install_subdir('../../data/tr2/ship/cfg', install_dir : 'Contents/Resources')
+ install_subdir('../../data/tr2/ship/data', install_dir : 'Contents/Resources')
+ install_subdir('../../data/tr2/ship/shaders', install_dir : 'Contents/Resources')
+ install_data('../../data/tr2/mac/icon.icns', install_dir : 'Contents/Resources')
+ install_data('../../data/tr2/mac/Info.plist', install_dir : 'Contents')
+ meson.add_install_script('../../tools/shared/mac/bundle_dylibs', '-a', 'TR2X')
+endif
diff --git a/tools/tr1/mac/bundle_dylibs b/tools/shared/mac/bundle_dylibs
similarity index 61%
rename from tools/tr1/mac/bundle_dylibs
rename to tools/shared/mac/bundle_dylibs
index 6d86e6a3c..69b2f2616 100755
--- a/tools/tr1/mac/bundle_dylibs
+++ b/tools/shared/mac/bundle_dylibs
@@ -2,28 +2,19 @@
import argparse
import re
import shutil
+from collections.abc import Iterable
from pathlib import Path
from subprocess import check_output, run
-# Configuration
-APP_NAME = "TR1X"
-APP_BUNDLE_PATH = Path(f"/tmp/{APP_NAME}.app")
-APP_BINARY_PATH = APP_BUNDLE_PATH / f"Contents/MacOS/{APP_NAME}"
-FRAMEWORKS_PATH = APP_BUNDLE_PATH / "Contents/Frameworks"
IGNORE_LIB_PREFIXES = ("/usr/lib/", "/System/", "@executable_path")
-
-
-# Other global state
-LIBRARY_PATHS: set[Path] = set()
+RPATH_PATTERN = re.compile(r"@rpath/(.*)")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
- description=(
- f"Copies shared libraries into the macOS app bundle "
- "for {APP_NAME}."
- )
+ description="Copies shared libraries into the macOS app bundle."
)
+ parser.add_argument("-a", "--app-name")
parser.add_argument(
"--copy-only",
action="store_true",
@@ -41,32 +32,33 @@ def should_ignore_lib(lib_path: str) -> bool:
return any(lib_path.startswith(prefix) for prefix in IGNORE_LIB_PREFIXES)
-def gather_libs(binary_path: Path) -> None:
+def gather_libs(
+ binary_path: Path, visited_paths: set[Path] | None = None
+) -> Iterable[Path]:
+ if visited_paths is None:
+ visited_paths = set()
+ visited_paths.add(binary_path)
+
parent_path = binary_path.parent
output = check_output(["otool", "-L", str(binary_path)], text=True)
libs = [line.split()[0] for line in output.split("\n")[1:] if line]
- rpath_pattern = re.compile(r"@rpath/(.*)")
-
for lib in libs:
- match = rpath_pattern.match(lib)
+ match = RPATH_PATTERN.match(lib)
lib_path = parent_path / (match.group(1) if match else lib)
- if (
- should_ignore_lib(str(lib_path))
- or lib_path in LIBRARY_PATHS
- or lib_path == binary_path
- ):
+ if should_ignore_lib(str(lib_path)) or lib_path == binary_path:
continue
- LIBRARY_PATHS.add(lib_path)
- gather_libs(lib_path)
+ yield lib_path
+ if lib_path not in visited_paths:
+ yield from gather_libs(lib_path, visited_paths=visited_paths)
-def copy_libs() -> None:
- FRAMEWORKS_PATH.mkdir(parents=True, exist_ok=True)
- for lib_path in LIBRARY_PATHS:
- target_path = FRAMEWORKS_PATH / lib_path.name
+def copy_libs(frameworks_path: Path, library_paths: set[Path]) -> None:
+ frameworks_path.mkdir(parents=True, exist_ok=True)
+ for lib_path in library_paths:
+ target_path = frameworks_path / lib_path.name
if not target_path.exists():
print(f"Copying {lib_path} to {target_path}")
shutil.copy2(lib_path, target_path)
@@ -93,17 +85,21 @@ def update_links(binary_path: Path) -> None:
def main() -> None:
args = parse_args()
+ app_bundle_path = Path(f"/tmp/{args.app_name}.app")
+ app_binary_path = app_bundle_path / f"Contents/MacOS/{args.app_name}"
+ frameworks_path = app_bundle_path / "Contents/Frameworks"
+
if args.copy_only or not args.links_only:
- gather_libs(APP_BINARY_PATH)
- copy_libs()
+ library_paths = set(gather_libs(app_binary_path))
+ copy_libs(frameworks_path, library_paths)
if args.links_only or not args.copy_only:
- for lib_path in FRAMEWORKS_PATH.glob("*"):
+ for lib_path in frameworks_path.glob("*"):
update_links(lib_path)
- update_links(APP_BINARY_PATH)
+ update_links(app_binary_path)
- print(f"Libraries for {APP_NAME} copied and updated.")
+ print(f"Libraries for {args.app_name} copied and updated.")
if __name__ == "__main__":
diff --git a/tools/tr1/mac/create_installer b/tools/shared/mac/create_installer
similarity index 55%
rename from tools/tr1/mac/create_installer
rename to tools/shared/mac/create_installer
index 921a5b054..24345c647 100755
--- a/tools/tr1/mac/create_installer
+++ b/tools/shared/mac/create_installer
@@ -4,27 +4,21 @@ import os
import subprocess
from pathlib import Path
-# Configuration
-APP_NAME = "TR1X"
-APP_BUNDLE_PATH = Path(f"/tmp/{APP_NAME}.app")
-DMG_NAME = Path(f"{APP_NAME}-Installer.dmg")
-
-
def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(
- description=f"Create a DMG installer for {APP_NAME}."
- )
+ parser = argparse.ArgumentParser(description=f"Create a DMG installer.")
+ parser.add_argument("-a", "--app-name")
+ parser.add_argument("-i", "--icon-path", type=Path)
return parser.parse_args()
-def create_dmg(app_name: str, dmg_name: str, app_bundle_path: Path) -> None:
+def create_dmg(app_name: str, dmg_name: str, icon_path: Path, app_bundle_path: Path) -> None:
subprocess.run(
(
"create-dmg",
"--volname",
- f"{APP_NAME} Installer",
+ f"{app_name} Installer",
"--volicon",
- "data/tr1/mac/icon.icns",
+ str(icon_path),
"--window-pos",
"200",
"120",
@@ -34,11 +28,11 @@ def create_dmg(app_name: str, dmg_name: str, app_bundle_path: Path) -> None:
"--icon-size",
"100",
"--icon",
- f"{APP_NAME}.app",
+ f"{app_name}.app",
"200",
"190",
"--hide-extension",
- f"{APP_NAME}.app",
+ f"{app_name}.app",
"--app-drop-link",
"600",
"185",
@@ -51,10 +45,12 @@ def create_dmg(app_name: str, dmg_name: str, app_bundle_path: Path) -> None:
def main() -> None:
args = parse_args()
- if DMG_NAME.is_file():
- DMG_NAME.unlink()
+ dmg_name = Path(f"{args.app_name}-Installer.dmg")
+ if dmg_name.is_file():
+ dmg_name.unlink()
- create_dmg(APP_NAME, DMG_NAME, APP_BUNDLE_PATH)
+ app_bundle_path = Path(f"/tmp/{args.app_name}.app")
+ create_dmg(args.app_name, dmg_name, args.icon_path, app_bundle_path)
if __name__ == "__main__":
diff --git a/tools/tr1/mac/x86-64_cross_file.txt b/tools/shared/mac/x86-64_cross_file.txt
similarity index 100%
rename from tools/tr1/mac/x86-64_cross_file.txt
rename to tools/shared/mac/x86-64_cross_file.txt