Exciting news! TCMS official website is live! Offering full-stack software services including enterprise-level custom R&D, App and mini-program development, multi-system integration, AI, blockchain, and embedded development, empowering digital-intelligent transformation across industries. Visit dev.tekin.cn to discuss cooperation!
GitHub Actions has become the go-to CI/CD solution for modern software development, offering seamless integration with GitHub repositories and a wide range of pre-configured environments—including the latest macOS versions. However, a common challenge arises when building applications on these cutting-edge macOS runners (e.g., macOS 14 Sonoma, macOS 13 Ventura) that need to run on older iterations of the operating system (such as macOS 10.15 Catalina or macOS 11 Big Sur).
GitHub Actions has become the go-to CI/CD solution for modern software development, offering seamless integration with GitHub repositories and a wide range of pre-configured environments—including the latest macOS versions. However, a common challenge arises when building applications on these cutting-edge macOS runners (e.g., macOS 14 Sonoma, macOS 13 Ventura) that need to run on older iterations of the operating system (such as macOS 10.15 Catalina or macOS 11 Big Sur).
This incompatibility typically stems from differences in system libraries, framework versions, and compiler toolchains between macOS releases. Without proper configuration, binaries built on newer macOS versions will fail to launch on older ones, throwing errors like dyld: Library not loadedor Symbol not found.
In this comprehensive guide, we’ll walk you through the core principles of cross-version macOS compatibility, step-by-step configuration for GitHub Actions, and practical solutions to resolve common pitfalls—ensuring your builds work reliably across both modern and legacy macOS environments.
Before diving into configuration, it’s critical to grasp the key factors that govern macOS cross-version compatibility:
The macOS Deployment Target(controlled via MACOSX_DEPLOYMENT_TARGETin Xcode or compiler flags) defines the oldest macOS version your application supports. This flag tells the compiler to avoid using APIs or libraries introduced after the specified version.
However, setting the deployment target alone isn’t sufficient—your build environment (the GitHub Actions runner) must have access to the SDK (Software Development Kit) for your target version. Newer macOS runners only ship with the latest SDKs by default, so you’ll need to manually install older SDKs to compile against them.
macOS applications link against system libraries (e.g., libSystem.dylib, CoreFoundation) and third-party frameworks. To ensure compatibility:
Use static linking for third-party dependencies whenever possible (avoids relying on system-installed library versions).
For dynamic libraries, ensure they’re built with the same deployment target as your application.
Avoid using private frameworks or APIs, which are prone to breaking between macOS versions.
GitHub Actions’ macOS runners use the latest Xcode and Clang versions by default. While modern compilers can target older deployment targets, you may need to adjust compiler flags (e.g., -mmacosx-version-min=10.15) to align with your target SDK and avoid unsupported optimizations.
We’ll use a practical example: building an application on a macOS 14 (Sonoma) runner that targets macOS 10.15 (Catalina) and above.
Create a .github/workflows/macos-compatibility.ymlfile in your repository with the following structure:
name: macOS Cross-Version Build
on:
push:
branches: [main,develop ]
pull_request:
branches: [main,develop ]
jobs:
build-compatible:
name: Build for macOS 10.15+ on macOS 14
runs-on: macos-14 # Use the latest macOS runner
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Xcode and Older SDKs
run: |
# Install Xcode (matches the runner's default, but ensures consistency)
sudo xcode-select -switch /Applications/Xcode_15.app
# Install macOS 10.15 SDK (required for targeting older versions)
SDK_URL="https://github.com/phracker/MacOSX-SDKs/releases/download/10.15/MacOSX10.15.sdk.tar.xz"
SDK_PATH="/Applications/Xcode_15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs"
sudo curl -L $SDK_URL | sudo tar -xJ -C $SDK_PATH
# Verify SDK installation
ls $SDK_PATH | grep MacOSX10.15.sdkAdd steps to set the deployment target and compile against the older SDK. The exact commands depend on your build system (Xcode, CMake, Make, etc.):
- name: Build with Xcode (Target macOS 10.15)
run: |
xcodebuild -project YourApp.xcodeproj \
-scheme YourApp \
-configuration Release \
-sdk MacOSX10.15.sdk \
MACOSX_DEPLOYMENT_TARGET=10.15 \
ARCHS="x86_64 arm64" \ # Support both Intel and Apple Silicon
ONLY_ACTIVE_ARCH=NO \
clean build- name: Configure CMake for macOS 10.15+
run: |
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
-DCMAKE_OSX_SYSROOT=/Applications/Xcode_15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
- name: Build with CMake
run: cmake --build build --config ReleaseAdd a step to test the built binary on an older macOS version (using a virtual machine or container, as GitHub Actions doesn’t offer older macOS runners natively):
- name: Test Binary Compatibility (via Docker)
run: |
# Use a Docker image with macOS 10.15 to test the binary
docker pull sickcodes/docker-osx:10.15
docker run -d --name macos-test sickcodes/docker-osx:10.15
docker cp build/Release/YourApp docker-osx:/tmp/YourApp
docker exec docker-osx /tmp/YourApp --version # Verify the binary runsIssue: The build system can’t locate the older SDK (e.g., MacOSX10.15.sdk).
Fix: Double-check the SDK installation path—ensure it’s placed in Xcode’s SDKsdirectory (e.g., /Applications/Xcode_15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs). Use xcodebuild -showsdksto list available SDKs.
Issue: The binary links against a library/API introduced after your deployment target. Fix:
Use otool -L YourAppto inspect dynamic dependencies—ensure all libraries have a deployment target ≤ your specified version.
Replace dynamic linking with static linking for third-party libraries (e.g., use CMAKE_LINK_STATICin CMake).
Avoid using APIs from newer macOS versions (check Apple’s API availability documentation).
Issue: The binary fails on either Intel (x86_64) or Apple Silicon (arm64) devices.
Fix: Build a universal binary by specifying both architectures in your build flags (e.g., ARCHS="x86_64 arm64"for Xcode, -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"for CMake).
Issue: The GitHub Actions runner’s Xcode version is incompatible with the older SDK.
Fix: Use a specific Xcode version that supports your target SDK (e.g., Xcode 15 supports macOS 10.15+). Use xcode-selectto switch to the correct Xcode installation.
Cache Older SDKs: Speed up workflows by caching the installed SDKs using actions/cache(avoids re-downloading on every run).
Use Conditional Targeting: For projects supporting multiple macOS versions, use GitHub Actions matrices to build against different deployment targets in parallel.
Lint for Compatibility: Use tools like clang-tidywith compatibility checks to catch unsupported APIs early in development.
Sign Binaries Properly: Ensure code signing uses a certificate compatible with your deployment target (older macOS versions may not recognize newer signing algorithms).
Building macOS applications that work across both modern and legacy versions requires careful attention to the deployment target, SDK availability, and build configuration—especially when using GitHub Actions’ latest macOS runners. By following the steps outlined in this guide, you’ll be able to compile binaries that run reliably on older macOS versions while leveraging the performance and features of newer build environments.
Whether you’re developing a desktop app, command-line tool, or framework, the key is to align your build toolchain with your target audience’s expected macOS versions. With the right setup, GitHub Actions can seamlessly handle cross-version macOS builds, ensuring your software reaches the widest possible user base.