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!

Complete Guide to Compiling for Older macOS Versions on Newer GitHub Actions Environments

2025-12-17 10 mins read

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).

Introduction

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.

Core Concepts: Understanding macOS Compatibility Constraints

Before diving into configuration, it’s critical to grasp the key factors that govern macOS cross-version compatibility:

1. Deployment Target vs. Build Environment

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.

2. Library and Framework Dependencies

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.

3. Compiler Toolchain Consistency

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.

Step-by-Step GitHub Actions Configuration

We’ll use a practical example: building an application on a macOS 14 (Sonoma) runner that targets macOS 10.15 (Catalina) and above.

1. Define the GitHub Actions Workflow File

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.sdk

2. Configure Build Flags for Compatibility

Add steps to set the deployment target and compile against the older SDK. The exact commands depend on your build system (Xcode, CMake, Make, etc.):

For Xcode Projects

- 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

For CMake Projects

- 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 Release

3. Verify Compatibility

Add 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 runs

Common Pitfalls and Solutions

1. "SDK Not Found" Error

Issue: 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.

2. "Symbol Not Found" or "Library Not Loaded"

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).

3. Apple Silicon vs. Intel Compatibility

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).

4. Xcode Version Mismatch

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.

Advanced Optimization Tips

  • 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).

Conclusion

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.

Image NewsLetter
Icon primary
Newsletter

Subscribe our newsletter

Please enter your email address below and click the subscribe button. By doing so, you agree to our Terms and Conditions.

Your experience on this site will be improved by allowing cookies Cookie Policy