# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# SPDX-License-Identifier: curl

name: 'macOS'

'on':
  push:
    branches:
      - master
      - '*/ci'
    paths-ignore:
      - '**/*.md'
      - '.circleci/**'
      - 'appveyor.*'
      - 'Dockerfile'
      - 'projects/**'
  pull_request:
    branches:
      - master
    paths-ignore:
      - '**/*.md'
      - '.circleci/**'
      - 'appveyor.*'
      - 'Dockerfile'
      - 'projects/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: true

permissions: {}

# Apple APIs and the macos-version-min value required to avoid deprecation
# warnings with llvm/clang, and/or the feature getting enabled at build-time
# or runtime:
#
# - 10.7   Lion (2011)          - GSS (build-time, deprecated MIT Kerberos shim)
# - 10.9   Mavericks (2013)     - LDAP (build-time, deprecated), OCSP (runtime)
# - 10.11  El Capitan (2015)    - connectx() (runtime)
# - 10.12  Sierra (2016)        - clock_gettime() (build-time, runtime)
# - 10.14  Mojave (2018)        - SecTrustEvaluateWithError() (runtime)

env:
  CURL_CI: github
  CURL_TEST_MIN: 1700
  MAKEFLAGS: -j 4
  LDFLAGS: -w  # suppress 'object file was built for newer macOS version than being linked' warnings

jobs:
  ios:
    name: "iOS, ${{ (matrix.build.generator && format('CM-{0}', matrix.build.generator)) || (matrix.build.generate && 'CM' || 'AM' )}} ${{ matrix.build.name }} arm64"
    runs-on: macos-latest
    timeout-minutes: 10
    env:
      DEVELOPER_DIR: "/Applications/Xcode${{ matrix.build.xcode && format('_{0}', matrix.build.xcode) || '' }}.app/Contents/Developer"
      CC: 'clang'
      LDFLAGS: ''
      MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }}
      MATRIX_OPTIONS: ${{ matrix.build.options }}
      # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com
      LIBRESSL_VERSION: 4.2.1
    strategy:
      fail-fast: false
      matrix:
        build:
          - name: 'libressl'
            install_steps: libressl
            configure: --with-openssl=/Users/runner/libressl --without-libpsl

          - name: 'libressl'
            install_steps: libressl
            # FIXME: Could not make OPENSSL_ROOT_DIR work. CMake seems to prepend sysroot to it.
            generator: Xcode
            xcode: ''  # default Xcode. Set it once to silence actionlint.
            options: --config Debug
            generate: >-
              -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=OFF
              -DMACOSX_BUNDLE_GUI_IDENTIFIER=se.curl
              -DOPENSSL_INCLUDE_DIR=/Users/runner/libressl/include
              -DOPENSSL_SSL_LIBRARY=/Users/runner/libressl/lib/libssl.a
              -DOPENSSL_CRYPTO_LIBRARY=/Users/runner/libressl/lib/libcrypto.a
              -DCURL_USE_LIBPSL=OFF -DCURL_ENABLE_NTLM=ON

    steps:
      - name: 'brew install'
        if: ${{ matrix.build.configure }}
        timeout-minutes: 5
        run: |
          # shellcheck disable=SC2181
          while [[ $? == 0 ]]; do
            for i in 1 2 3; do
              if brew update && brew install automake libtool; then
                break 2
              else
                echo "Error: wait to try again: $i"
                sleep 10
              fi
            done
            false Too many retries
          done

      - name: 'toolchain versions'
        run: |
          command -v "${CC}"; "${CC}" --version || true
          xcodebuild -version || true
          xcodebuild -sdk -version | grep '^Path:' || true
          xcrun --sdk iphoneos --show-sdk-path 2>/dev/null || true
          xcrun --sdk iphoneos --show-sdk-version || true
          echo '::group::compiler defaults'; echo 'int main(void) {}' | "${CC}" -v -x c -; echo '::endgroup::'
          echo '::group::macros predefined'; "${CC}" -dM -E - < /dev/null | sort || true; echo '::endgroup::'
          echo '::group::brew packages installed'; ls -l "$(brew --prefix)"/opt; echo '::endgroup::'

      - name: 'cache libressl'
        if: ${{ contains(matrix.build.install_steps, 'libressl') }}
        uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        id: cache-libressl
        env:
          cache-name: cache-libressl
        with:
          path: ~/libressl
          key: iOS-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}

      - name: 'build libressl'
        if: ${{ contains(matrix.build.install_steps, 'libressl') && steps.cache-libressl.outputs.cache-hit != 'true' }}
        run: |
          curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \
            --location "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin
          sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin
          cd "libressl-${LIBRESSL_VERSION}"
          cmake -B . -G Ninja \
            -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \
            -DCMAKE_SYSTEM_NAME=iOS \
            -DCMAKE_SYSTEM_PROCESSOR=aarch64 \
            -DBUILD_SHARED_LIBS=OFF \
            -DLIBRESSL_APPS=OFF \
            -DLIBRESSL_TESTS=OFF
          cmake --build .
          cmake --install . --verbose

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: 'autoreconf'
        if: ${{ matrix.build.configure }}
        run: autoreconf -fi

      - name: 'configure'
        env:
          MATRIX_CONFIGURE: '${{ matrix.build.configure }}'
          MATRIX_GENERATE: '${{ matrix.build.generate }}'
          MATRIX_GENERATOR: '${{ matrix.build.generator }}'
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            # https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos
            [ -n "${MATRIX_GENERATOR}" ] && options="-G ${MATRIX_GENERATOR}"
            cmake -B bld -G Ninja -D_CURL_PREFILL=ON \
              -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
              -DCMAKE_SYSTEM_NAME=iOS \
              -DUSE_APPLE_IDN=ON \
              ${MATRIX_GENERATE} ${options}
          else
            mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror \
              --disable-dependency-tracking --enable-option-checking=fatal \
              CFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null)" \
              --host=aarch64-apple-darwin \
              --with-apple-idn \
              ${MATRIX_CONFIGURE}
          fi

      - name: 'configure log'
        if: ${{ !cancelled() }}
        run: cat bld/config.log bld/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true

      - name: 'curl_config.h'
        run: |
          echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::'
          grep -F '#define' bld/lib/curl_config.h | sort || true

      - name: 'build'
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld ${MATRIX_OPTIONS} --parallel 4 --verbose
          else
            make -C bld V=1
          fi

      - name: 'curl info'
        run: |
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file --
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' --

      - name: 'build tests'
        if: ${{ matrix.build.generate }}  # skip for autotools to save time
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld ${MATRIX_OPTIONS} --parallel 4 --target testdeps --verbose
          else
            make -C bld V=1 -C tests
          fi

      - name: 'build examples'
        if: ${{ matrix.build.generate }}  # skip for autotools to save time
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld ${MATRIX_OPTIONS} --parallel 4 --target curl-examples-build --verbose
          else
            make -C bld examples V=1
          fi

  macos:
    name: "${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.compiler }} ${{ matrix.build.name }}"
    runs-on: ${{ matrix.build.image || 'macos-15' }}
    timeout-minutes: 15
    env:
      DEVELOPER_DIR: "/Applications/Xcode${{ matrix.build.xcode && format('_{0}', matrix.build.xcode) || '' }}.app/Contents/Developer"
      CC: '${{ matrix.build.compiler }}'
      MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }}
      MATRIX_COMPILER: '${{ matrix.build.compiler }}'
      MATRIX_INSTALL: '${{ matrix.build.install }}'
      MATRIX_INSTALL_STEPS: '${{ matrix.build.install_steps }}'
      MATRIX_MACOS_VERSION_MIN: '${{ matrix.build.macos-version-min }}'
    strategy:
      fail-fast: false
      matrix:
        build:
          - name: '!ssl !debug brotli zstd'
            compiler: gcc-13
            configure: --without-ssl --with-brotli --with-zstd --with-apple-idn
            tflags: '--min=1450'
            xcode: ''  # default Xcode. Set it once to silence actionlint.

          - name: '!ssl libssh2 AppleIDN'
            compiler: clang
            generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_SSL=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF
            tflags: '--min=1550'

          - name: 'OpenSSL libssh c-ares'
            compiler: clang
            install: libssh
            configure: --enable-debug --with-libssh --with-openssl=/opt/homebrew/opt/openssl --enable-ares --with-fish-functions-dir --with-zsh-functions-dir

          - name: 'OpenSSL libssh'
            compiler: llvm@18
            install: libssh libnghttp3
            generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF

          - name: '!ssl HTTP-only c-ares'
            macos-version-min: '10.15'  # Catalina (2019)
            compiler: clang
            tflags: '--min=930'
            generate: >-
              -DENABLE_DEBUG=ON -DENABLE_ARES=ON
              -DCURL_ENABLE_SSL=OFF -DHTTP_ONLY=ON
              -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF
              -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF -DUSE_NGHTTP2=OFF
              -DCURL_USE_GSSAPI=OFF -DUSE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF
              -DCURL_BROTLI=OFF -DCURL_ZLIB=OFF -DCURL_ZSTD=OFF
              -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF

          - name: 'LibreSSL !ldap +examples'
            compiler: clang
            install: libressl
            install_steps: pytest
            generate: >-
              -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DCURL_DISABLE_LDAP=ON -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF
              -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON

          - name: 'OpenSSL 10.15 C89'
            macos-version-min: '10.15'
            compiler: clang
            install: libnghttp3 libngtcp2
            install_steps: pytest
            generate: >-
              -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_NGTCP2=ON -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_USE_LIBSSH2=OFF
              -DCMAKE_C_STANDARD=90 -DCURL_ENABLE_NTLM=ON

          - name: 'OpenSSL SecTrust'
            compiler: clang
            install: libnghttp3 libngtcp2
            install_steps: pytest
            configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust --enable-ntlm

          - name: 'OpenSSL event-based'
            compiler: clang
            generate: -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON
            tflags: '--test-event --min=1300'

          - name: 'OpenSSL gsasl AppleIDN SecTrust +examples'
            compiler: clang
            install: libnghttp3 libngtcp2 gsasl
            generate: >-
              -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_USE_GSASL=ON -DUSE_APPLE_IDN=ON -DUSE_NGTCP2=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON
              -DUSE_APPLE_SECTRUST=ON -DCURL_ENABLE_NTLM=ON

          - name: 'MultiSSL AppleIDN clang-tidy +examples'
            image: macos-26
            compiler: clang
            install: llvm gnutls nettle libressl krb5 mbedtls gsasl rustls-ffi libssh fish
            install_steps: skiprun
            chkprefill: _chkprefill
            generate: >-
              -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DCURL_DEFAULT_SSL_BACKEND=openssl
              -DCURL_USE_GNUTLS=ON -DCURL_USE_MBEDTLS=ON -DCURL_USE_RUSTLS=ON -DENABLE_ARES=ON -DCURL_USE_GSASL=ON
              -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DUSE_APPLE_IDN=ON -DUSE_SSLS_EXPORT=ON
              -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5
              -DCURL_BROTLI=ON -DCURL_ZSTD=ON
              -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/opt/homebrew/opt/llvm/bin/clang-tidy
              -DCURL_COMPLETION_FISH=ON -DCURL_COMPLETION_ZSH=ON
              -DCURL_ENABLE_NTLM=ON

          - name: 'HTTP/3 clang-tidy'
            image: macos-26
            compiler: clang
            install: llvm libnghttp3 libngtcp2 openldap krb5
            install_steps: skipall
            generate: >-
              -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_NGTCP2=ON
              -DLDAP_INCLUDE_DIR=/opt/homebrew/opt/openldap/include
              -DLDAP_LIBRARY=/opt/homebrew/opt/openldap/lib/libldap.dylib
              -DLDAP_LBER_LIBRARY=/opt/homebrew/opt/openldap/lib/liblber.dylib
              -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5
              -DCURL_BROTLI=ON -DCURL_ZSTD=ON
              -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/opt/homebrew/opt/llvm/bin/clang-tidy
              -DCURL_ENABLE_NTLM=ON

          - name: 'LibreSSL openldap krb5 c-ares +examples'
            compiler: clang
            install: libressl krb5 openldap
            generate: >-
              -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DENABLE_ARES=ON -DCURL_USE_GSSAPI=ON
              -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5
              -DLDAP_INCLUDE_DIR=/opt/homebrew/opt/openldap/include
              -DLDAP_LIBRARY=/opt/homebrew/opt/openldap/lib/libldap.dylib
              -DLDAP_LBER_LIBRARY=/opt/homebrew/opt/openldap/lib/liblber.dylib

          - name: 'wolfSSL !ldap brotli zstd'
            compiler: clang
            install: brotli wolfssl zstd
            install_steps: pytest
            generate: -DCURL_USE_WOLFSSL=ON -DCURL_DISABLE_LDAP=ON -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON

          - name: 'mbedTLS !ldap brotli zstd MultiSSL AppleIDN'
            compiler: llvm@18
            install: brotli mbedtls zstd
            install_steps: codeset-test
            generate: -DCURL_USE_MBEDTLS=ON -DCURL_DISABLE_LDAP=ON -DCURL_DEFAULT_SSL_BACKEND=mbedtls -DCURL_USE_OPENSSL=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_NTLM=ON

          - name: 'GnuTLS !ldap krb5 +examples'
            compiler: clang
            install: gnutls nettle krb5
            generate: >-
              -DENABLE_DEBUG=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=OFF
              -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5
              -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON -DCURL_ENABLE_NTLM=ON

          - name: 'aws-lc +analyzer'
            compiler: gcc-15
            install: aws-lc
            generate: >-
              -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/aws-lc -DUSE_ECH=ON
              -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON -DCURL_GCC_ANALYZER=ON

          - name: 'Rustls'
            compiler: clang
            install: rustls-ffi
            generate: -DENABLE_DEBUG=ON -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DCURL_DISABLE_LDAP=ON -DCURL_ENABLE_NTLM=ON
            tflags: '--min=1650'

          - name: 'OpenSSL torture 1'
            compiler: clang
            install: libnghttp3
            install_steps: torture
            generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_ENABLE_NTLM=ON
            tflags: '-t --shallow=25 --min=480 1 to 500'

          - name: 'OpenSSL torture 2'
            compiler: clang
            install: libnghttp3
            install_steps: torture
            generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_ENABLE_NTLM=ON
            tflags: '-t --shallow=25 --min=730 501 to 1250'

          - name: 'OpenSSL torture 3'
            compiler: clang
            install: libnghttp3
            install_steps: torture
            generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_ENABLE_NTLM=ON
            tflags: '-t --shallow=25 --min=628 1251 to 9999'

    steps:
      - name: 'brew install'
        timeout-minutes: 5
        # Run this command with retries because of spurious failures seen
        # while running the tests, for example
        # https://github.com/curl/curl/runs/4095721123?check_suite_focus=true
        env:
          INSTALL_PACKAGES: >-
            ${{ matrix.build.generate && 'ninja' || 'automake libtool' }}
            ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'nghttp2 stunnel' || '' }}
            ${{ contains(matrix.build.install_steps, 'pytest') && 'caddy httpd vsftpd' || '' }}

        run: |
          # shellcheck disable=SC2181
          while [[ $? == 0 ]]; do
            for i in 1 2 3; do
              if brew update && brew install pkgconf libpsl libssh2 ${INSTALL_PACKAGES} ${MATRIX_INSTALL}; then
                break 2
              else
                echo "Error: wait to try again: $i"
                sleep 10
              fi
            done
            false Too many retries
          done

      - name: 'brew unlink openssl'
        if: ${{ contains(matrix.build.install, 'aws-lc') || contains(matrix.build.install, 'libressl') }}
        run: |
          if [ -d "$(brew --prefix)"/include/openssl ]; then
            brew unlink openssl
          fi

      - name: 'toolchain versions'
        run: |
          [[ "${MATRIX_COMPILER}" = 'llvm'* ]] && CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang"
          [[ "${MATRIX_COMPILER}" = 'gcc'* ]] && "${CC}" --print-sysroot
          command -v "${CC}"; "${CC}" --version || true
          xcodebuild -version || true
          xcrun --sdk macosx --show-sdk-path 2>/dev/null || true
          xcrun --sdk macosx --show-sdk-version || true
          ls -l /Library/Developer/CommandLineTools/SDKs || true
          echo '::group::macros predefined'; "${CC}" -dM -E - < /dev/null | sort || true; echo '::endgroup::'
          echo '::group::brew packages installed'; ls -l "$(brew --prefix)"/opt; echo '::endgroup::'

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: 'autoreconf'
        if: ${{ matrix.build.configure }}
        run: autoreconf -fi

      - name: 'configure'
        env:
          MATRIX_CHKPREFILL: '${{ matrix.build.chkprefill }}'
          MATRIX_CONFIGURE: '${{ matrix.build.configure }}'
          MATRIX_GENERATE: '${{ matrix.build.generate }}'
        run: |
          if [[ "${MATRIX_COMPILER}" = 'gcc'* ]]; then
            sysroot="$("${CC}" --print-sysroot)"  # Must match the SDK gcc was built for
          else
            sysroot="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null)"
          fi

          if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then
            CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang"
            CC+=" --sysroot=${sysroot}"
            CC+=" --target=$(uname -m)-apple-darwin"
          fi

          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            for _chkprefill in '' ${MATRIX_CHKPREFILL}; do
              options=''
              [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && options+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MATRIX_MACOS_VERSION_MIN}"
              [[ "${MATRIX_INSTALL_STEPS}" = *'pytest'* ]] && options+=' -DVSFTPD=NO'  # Skip ~20 tests that stretch run time by 7x on macOS
              [ "${_chkprefill}" = '_chkprefill' ] && options+=' -D_CURL_PREFILL=OFF'
              cmake -B "bld${_chkprefill}" -G Ninja -D_CURL_PREFILL=ON \
                -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \
                -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
                -DCMAKE_OSX_SYSROOT="${sysroot}" \
                -DCMAKE_C_COMPILER_TARGET="$(uname -m | sed 's/arm64/aarch64e/')-apple-darwin$(uname -r)" \
                ${MATRIX_GENERATE} ${options}
            done
            if [ -d bld_chkprefill ] && ! diff -u bld/lib/curl_config.h bld_chkprefill/lib/curl_config.h; then
              echo '::group::reference configure log'; cat bld_chkprefill/CMakeFiles/CMake*.yaml 2>/dev/null || true; echo '::endgroup::'
              false
            fi
          else
            export CFLAGS
            if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then
              options+=" --target=$(uname -m)-apple-darwin"
            fi
            if [ "${MATRIX_COMPILER}" != 'clang' ]; then
              options+=" --with-sysroot=${sysroot}"
              CFLAGS+=" --sysroot=${sysroot}"
            fi
            [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}"
            [[ "${MATRIX_INSTALL_STEPS}" = *'pytest'* ]] && options+=' --with-test-vsftpd=no'  # Skip ~20 tests that stretch run time by 7x on macOS
            mkdir bld && cd bld && ../configure --prefix="$PWD"/curl-install --enable-unity --enable-warnings --enable-werror --disable-static \
              --disable-dependency-tracking --enable-option-checking=fatal \
              --with-libpsl="$(brew --prefix libpsl)" \
              ${MATRIX_CONFIGURE} ${options}
          fi

      - name: 'configure log'
        if: ${{ !cancelled() }}
        run: cat bld/config.log bld/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true

      - name: 'curl_config.h'
        run: |
          echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::'
          grep -F '#define' bld/lib/curl_config.h | sort || true

      - name: 'test configs'
        run: grep -H -v '^#' bld/tests/config bld/tests/http/config.ini || true

      - name: 'build'
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose
          else
            make -C bld V=1
          fi

      - name: 'curl -V'
        run: |
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file --
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' --
          bld/src/curl --disable --version

      - name: 'curl install'
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --install bld --strip
          else
            make -C bld V=1 install
          fi

      - name: 'build tests'
        if: ${{ !contains(matrix.build.install_steps, 'skipall') }}
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose --target testdeps
          else
            make -C bld V=1 -C tests
          fi

      - name: 'install test prereqs'
        if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }}
        run: |
          python3 -m venv ~/venv
          if bld/src/curl --disable -V 2>/dev/null | grep smb; then
            ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt
          fi

      - name: 'run tests'
        if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }}
        timeout-minutes: ${{ contains(matrix.build.install_steps, 'torture') && 20 || 10 }}
        env:
          TEST_TARGET: ${{ contains(matrix.build.install_steps, 'torture') && 'test-torture' || 'test-ci' }}
          TFLAGS: '${{ matrix.build.tflags }}'
        run: |
          TFLAGS="-j20 ${TFLAGS}"
          if [ "${TEST_TARGET}" != 'test-ci' ]; then
            TFLAGS+=' --buildinfo'  # only test-ci sets this by default, set it manually for test-torture
          fi
          source ~/venv/bin/activate
          if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test'* ]]; then
            locale || true
            export LC_ALL=C
            export LC_CTYPE=C
            export LC_NUMERIC=fr_FR.UTF-8
          fi
          rm -f ~/.curlrc
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose --target "${TEST_TARGET}"
          else
            make -C bld V=1 "${TEST_TARGET}"
          fi

      - name: 'install pytest prereqs'
        if: ${{ contains(matrix.build.install_steps, 'pytest') }}
        run: |
          [ -d ~/venv ] || python3 -m venv ~/venv
          ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt

      - name: 'run pytest'
        if: ${{ contains(matrix.build.install_steps, 'pytest') }}
        env:
          PYTEST_ADDOPTS: '--color=yes'
          PYTEST_XDIST_AUTO_NUM_WORKERS: 4
        run: |
          source ~/venv/bin/activate
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose --target curl-pytest-ci
          else
            make -C bld V=1 pytest-ci
          fi

      - name: 'build examples'
        if: ${{ contains(matrix.build.name, '+examples') }}
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose --target curl-examples-build
          else
            make -C bld examples V=1
          fi

  combinations:  # Test buildability with host OS, Xcode / SDK, compiler, target-OS, built tool, combinations
    name: "${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }} ${{ matrix.image }} ${{ matrix.xcode }}"
    runs-on: ${{ matrix.image }}
    timeout-minutes: 10
    env:
      DEVELOPER_DIR: "/Applications/Xcode${{ matrix.xcode && format('_{0}', matrix.xcode) || '' }}.app/Contents/Developer"
      CC: '${{ matrix.compiler }}'
      MATRIX_BUILD: '${{ matrix.build }}'
      MATRIX_COMPILER: '${{ matrix.compiler }}'
      MATRIX_IMAGE: '${{ matrix.image }}'
      MATRIX_MACOS_VERSION_MIN: '${{ matrix.macos-version-min }}'
    strategy:
      fail-fast: false
      matrix:
        # Sources:
        # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md
        # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
        # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
        compiler: [gcc-13, gcc-14, gcc-15, llvm@15, llvm@18, llvm@20, clang]
        # Xcode support matrix as of 2025-10, with default macOS SDK versions and OS names, years:
        # * = default Xcode on the runner.
        # macos-14: 15.0.1, 15.1, 15.2, 15.3,*15.4
        # macos-15:                                 16.0, 16.1, 16.2, 16.3,*16.4, 26.0
        # macos-26:                                                         16.4 *26.0
        # macOSSDK: 14.0,   14.2, 14.2, 14.4, 14.5, 15.0, 15.1, 15.2, 15.4, 15.5, 26.0
        #           Sonoma (2023)                   Sequoia (2024)                Tahoe (2025)
        # https://github.com/actions/runner-images/tree/main/images/macos
        # https://en.wikipedia.org/wiki/MacOS_version_history
        image: [macos-14, macos-15, macos-26]
        xcode: ['']  # default Xcodes
        macos-version-min: ['']
        build: [autotools, cmake]
        exclude:
          # Combinations not covered by runner images:
          - { image: macos-14, compiler: 'llvm@18' }
          - { image: macos-14, compiler: 'llvm@20' }
          - { image: macos-15, compiler: 'llvm@15' }
          - { image: macos-15, compiler: 'llvm@20' }
          - { image: macos-26, compiler: 'llvm@15' }
          - { image: macos-26, compiler: 'llvm@18' }
          # Covered by the main workflow
          - { image: macos-15, compiler: 'gcc-13' }
          - { image: macos-15, compiler: 'llvm@18' }
          - { image: macos-15, compiler: 'clang' }
          # Reduce build combinations, by dropping less interesting ones
          - { image: macos-26, compiler: 'gcc-13' }
          - { compiler: 'gcc-14' , build: cmake }
          # Reduce autotools to just one job that is also build with cmake
          - { compiler: 'gcc-13' , build: autotools }
          - { compiler: 'gcc-14' , build: autotools }
          - { compiler: 'gcc-15' , build: autotools }
          - { compiler: 'llvm@15', build: autotools }
          - { compiler: 'llvm@18', build: autotools }
          - { compiler: 'llvm@20', build: autotools }
          - { image: macos-14,     build: autotools }
          - { image: macos-15,     build: autotools }
    steps:
      - name: 'install autotools'
        if: ${{ matrix.build == 'autotools' }}
        run: |
          # shellcheck disable=SC2181
          while [[ $? == 0 ]]; do
            for i in 1 2 3; do
              if brew update && brew install automake libtool; then
                break 2
              else
                echo "Error: wait to try again: $i"
                sleep 10
              fi
            done
            false Too many retries
          done

      - name: 'toolchain versions'
        run: |
          [[ "${MATRIX_COMPILER}" = 'llvm'* ]] && CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang"
          [[ "${MATRIX_COMPILER}" = 'gcc'* ]] && "${CC}" --print-sysroot
          command -v "${CC}"; "${CC}" --version || true
          xcodebuild -version || true
          xcrun --sdk macosx --show-sdk-path 2>/dev/null || true
          xcrun --sdk macosx --show-sdk-version || true
          ls -l /Library/Developer/CommandLineTools/SDKs || true
          echo '::group::compiler defaults'; echo 'int main(void) {}' | "${CC}" -v -x c -; echo '::endgroup::'
          echo '::group::macros predefined'; "${CC}" -dM -E - < /dev/null | sort || true; echo '::endgroup::'
          echo '::group::brew packages preinstalled'; ls -l "$(brew --prefix)"/opt; echo '::endgroup::'

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: 'autoreconf'
        if: ${{ matrix.build == 'autotools' }}
        run: autoreconf -fi

      - name: 'configure / ${{ matrix.build }}'
        run: |
          if [ "${MATRIX_COMPILER}" = 'gcc-13' ] && [ "${MATRIX_IMAGE}" = 'macos-15' ]; then
            # Ref: https://github.com/Homebrew/homebrew-core/issues/194778#issuecomment-2793243409
            "$(brew --prefix gcc@13)"/libexec/gcc/aarch64-apple-darwin24/13/install-tools/mkheaders
          fi

          if [[ "${MATRIX_COMPILER}" = 'gcc'* ]]; then
            sysroot="$("${CC}" --print-sysroot)"  # Must match the SDK gcc was built for
          else
            sysroot="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null)"
          fi

          if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then
            CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang"
            CC+=" --sysroot=${sysroot}"
            CC+=" --target=$(uname -m)-apple-darwin"
          fi

          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && options+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MATRIX_MACOS_VERSION_MIN}"
            # would pick up nghttp2, libidn2, and libssh2
            cmake -B bld -G Ninja -D_CURL_PREFILL=ON \
              -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
              -DCMAKE_OSX_SYSROOT="${sysroot}" \
              -DCMAKE_C_COMPILER_TARGET="$(uname -m | sed 's/arm64/aarch64e/')-apple-darwin$(uname -r)" \
              -DCMAKE_IGNORE_PREFIX_PATH="$(brew --prefix)" \
              -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF \
              -DCURL_USE_OPENSSL=ON \
              -DUSE_NGHTTP2=OFF -DUSE_LIBIDN2=OFF \
              -DCURL_USE_LIBPSL=OFF -DCURL_USE_LIBSSH2=OFF \
              -DUSE_APPLE_IDN=ON -DUSE_APPLE_SECTRUST=ON \
              ${options}
          else
            export CFLAGS
            if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then
              options+=" --target=$(uname -m)-apple-darwin"
            fi
            if [ "${MATRIX_COMPILER}" != 'clang' ]; then
              options+=" --with-sysroot=${sysroot}"
              CFLAGS+=" --sysroot=${sysroot}"
            fi
            [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}"
            # would pick up nghttp2, libidn2, but libssh2 is disabled by default
            mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror --disable-static \
              --disable-dependency-tracking --enable-option-checking=fatal \
              --disable-docs --disable-manual \
              --with-openssl="$(brew --prefix openssl)" \
              --without-nghttp2 --without-libidn2 \
              --without-libpsl \
              --with-apple-idn --with-apple-sectrust \
              ${options}
          fi

      - name: 'configure log'
        if: ${{ !cancelled() }}
        run: cat bld/config.log bld/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true

      - name: 'curl_config.h'
        run: |
          echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::'
          grep -F '#define' bld/lib/curl_config.h | sort || true

      - name: 'build / ${{ matrix.build }}'
        run: |
          if [ "${MATRIX_BUILD}" = 'cmake' ]; then
            cmake --build bld --verbose
          else
            make -C bld V=1
          fi

      - name: 'curl -V'
        run: |
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file --
          find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' --
          bld/src/curl --disable --version
