Blog DevOps GitHub Actions Xcode macOS CI/CD Apple

Xcode 27 en GitHub-hosted runners: qué cambia para CI/CD de apps Apple

Flujo de integración continua para aplicaciones Apple ejecutándose sobre runners macOS con Xcode 27 en GitHub Actions

GitHub ha puesto en public preview una nueva imagen de runner para GitHub Actions con Xcode 27. Para equipos que desarrollan aplicaciones iOS, macOS, watchOS, tvOS o visionOS, esto permite compilar y probar contra el nuevo toolchain de Apple desde runners hospedados por GitHub, sin mantener infraestructura macOS propia para esa primera validación.

El cambio no es solo una actualización de versión. En el ecosistema Apple, Xcode agrupa compiladores, SDKs, simuladores, herramientas de firma, versiones de Swift y comportamiento de xcodebuild. Por eso, disponer de una imagen específica con Xcode 27 en CI/CD ayuda a detectar antes incompatibilidades de compilación, dependencias que aún no están preparadas o tests que cambian de comportamiento con el nuevo entorno.

Hay dos detalles importantes del anuncio oficial:

  • Las etiquetas disponibles para la nueva imagen son xcode-27 y xcode-27-xlarge.
  • La imagen de Xcode 27 solo está disponible en runners macOS arm64. No está soportada en runners Intel.

Nota: Al estar en public preview, la imagen puede cambiar y puede incluir versiones de herramientas distintas a las de imágenes macOS anteriores. Conviene introducirla como validación paralela antes de convertirla en requisito de release.

Qué es una runner image en GitHub Actions

En GitHub Actions, un runner es la máquina que ejecuta los jobs definidos en un workflow. Cuando se usa un runner hospedado por GitHub, la plataforma aprovisiona el entorno de ejecución con un sistema operativo y un conjunto de herramientas preinstaladas.

Una runner image es la plantilla de software usada para crear esas máquinas. En el caso de macOS, esa imagen incluye el sistema operativo, shells, herramientas de línea de comandos y el toolchain necesario para compilar proyectos Apple. Con la nueva imagen de Xcode 27, GitHub ofrece un entorno gestionado para validar aplicaciones contra esa versión concreta de Xcode.

Esto reduce parte de la carga operativa habitual en equipos Apple: mantener máquinas macOS, actualizar toolchains, alinear versiones de SDKs y evitar diferencias entre entornos locales y CI. No elimina todas las decisiones de configuración, especialmente en firma y publicación, pero sí simplifica la primera capa de compatibilidad.

Nuevo modelo de soporte: imágenes basadas en Xcode

Uno de los puntos más relevantes del anuncio es el cambio de modelo para las imágenes macOS. GitHub indica que, con esta publicación, las imágenes pasan a estar basadas en una versión mayor de Xcode en lugar de estar definidas principalmente por el sistema operativo subyacente.

La idea es que cada imagen soporte una versión mayor de Xcode. Esto debería facilitar que un workflow declare de forma explícita el toolchain que necesita, por ejemplo:

runs-on: xcode-27

Para equipos que gestionan varios proyectos o varias ramas de producto, este modelo es más claro que depender únicamente de etiquetas genéricas como macos-latest, cuyo significado puede cambiar con el tiempo.

La contrapartida es que cada imagen puede traer diferencias en herramientas preinstaladas y versiones auxiliares. Por eso, al adoptar xcode-27, no basta con asumir que todo lo que funcionaba en una imagen macOS anterior seguirá comportándose igual.

Cómo activar Xcode 27 en un workflow

Para usar la imagen de Xcode 27, el cambio principal está en el campo runs-on del job. Según el changelog de GitHub, las etiquetas soportadas son:

  • xcode-27
  • xcode-27-xlarge

Un ejemplo mínimo de build podría quedar así:

name: iOS CI

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  build-xcode-27:
    name: Build con Xcode 27
    runs-on: xcode-27

    steps:
      - name: Descargar código
        uses: actions/checkout@v4

      - name: Mostrar versión de Xcode
        run: xcodebuild -version

      - name: Compilar esquema principal
        run: |
          xcodebuild \
            -scheme "App" \
            -destination "generic/platform=iOS Simulator" \
            clean build

En un repositorio real, el esquema, workspace, proyecto y destino deberán ajustarse a la aplicación. Si el proyecto usa un workspace de Xcode, por ejemplo, normalmente habrá que añadir -workspace "App.xcworkspace".

Usarlo primero como job paralelo

Aunque la etiqueta xcode-27 ya está disponible en public preview, no es recomendable sustituir directamente el pipeline estable. Una estrategia más prudente es mantener el job actual y añadir otro job paralelo con Xcode 27.

name: Xcode compatibility

on:
  pull_request:

jobs:
  build-stable:
    name: Build en entorno estable
    runs-on: macos-latest

    steps:
      - name: Descargar código
        uses: actions/checkout@v4

      - name: Mostrar versión de Xcode
        run: xcodebuild -version

      - name: Compilar
        run: |
          xcodebuild \
            -scheme "App" \
            -destination "generic/platform=iOS Simulator" \
            clean build

  build-xcode-27:
    name: Build con Xcode 27 preview
    runs-on: xcode-27
    continue-on-error: true

    steps:
      - name: Descargar código
        uses: actions/checkout@v4

      - name: Mostrar versión de Xcode
        run: xcodebuild -version

      - name: Compilar con Xcode 27
        run: |
          xcodebuild \
            -scheme "App" \
            -destination "generic/platform=iOS Simulator" \
            clean build

continue-on-error: true permite que el job aporte señal de compatibilidad sin bloquear inmediatamente los pull requests. Es una opción útil durante la fase de exploración, especialmente si aún hay dependencias o tests que no están preparados para el nuevo toolchain.

Cuando el equipo haya resuelto los problemas detectados y decida que el resultado debe ser obligatorio, ese flag puede eliminarse.

Matriz para comparar entornos

Otra opción es usar una matriz. Esto evita duplicar pasos y facilita comparar el mismo flujo contra varias imágenes.

name: Xcode compatibility matrix

on:
  pull_request:

jobs:
  build:
    name: Build en $
    runs-on: $
    continue-on-error: $

    strategy:
      fail-fast: false
      matrix:
        environment:
          - name: stable
            runner: macos-latest
            preview: false
          - name: xcode-27
            runner: xcode-27
            preview: true

    steps:
      - name: Descargar código
        uses: actions/checkout@v4

      - name: Mostrar toolchain activo
        run: |
          xcodebuild -version
          swift --version
          xcode-select -p

      - name: Compilar
        run: |
          xcodebuild \
            -scheme "App" \
            -destination "generic/platform=iOS Simulator" \
            clean build

fail-fast: false evita que un fallo en una combinación cancele las demás. Esto es importante cuando se quiere saber si el problema afecta solo a Xcode 27 o también al entorno estable.

Validar siempre la versión real del toolchain

Con imágenes gestionadas, especialmente en preview, conviene imprimir información del entorno en cada ejecución. Esto ayuda a diagnosticar fallos y a correlacionarlos con actualizaciones de la imagen.

Un paso útil al inicio del job puede ser:

echo "Versión de Xcode:"
xcodebuild -version

echo "Ruta activa de developer tools:"
xcode-select -p

echo "Versión de Swift:"
swift --version

echo "SDKs disponibles:"
xcodebuild -showsdks

echo "Simuladores disponibles:"
xcrun simctl list devices available

La lista de simuladores es especialmente útil antes de fijar un destino concreto en xcodebuild test. No todos los runners tienen por qué exponer exactamente el mismo conjunto de dispositivos o runtimes que una máquina local.

Limitación relevante: solo arm64

La imagen de Xcode 27 anunciada por GitHub no está disponible en runners Intel. Esto afecta a equipos que todavía dependen de compilaciones o pruebas en arquitectura x64.

Si un workflow usa etiquetas Intel, no basta con cambiar la versión de Xcode: hay que revisar la estrategia de arquitectura. Para muchas apps Apple modernas, validar en Apple Silicon será suficiente para la mayoría de builds de CI. Pero si el proyecto tiene dependencias nativas, binarios precompilados o herramientas internas que se comportan distinto en x64 y arm64, esa diferencia debe tratarse explícitamente.

La recomendación práctica es separar las validaciones:

  • Mantener los jobs Intel existentes si siguen siendo necesarios.
  • Añadir xcode-27 como validación arm64.
  • No asumir que un fallo o éxito en una arquitectura equivale automáticamente al mismo resultado en la otra.

Dependencias, caché y reproducibilidad

Los proyectos Apple suelen combinar Swift Package Manager, CocoaPods, Carthage, dependencias binarias o herramientas internas. Al cambiar de versión de Xcode, cualquiera de esas piezas puede comportarse de forma distinta.

La caché merece especial atención. Reutilizar artefactos generados por otra versión de Xcode puede producir errores difíciles de reproducir. Si el workflow usa actions/cache, conviene incluir la versión de Xcode en la clave.

Ejemplo para separar caché de Swift Package Manager por versión de Xcode:

- name: Obtener versión de Xcode para caché
  id: xcode
  run: |
    VERSION="$(xcodebuild -version | tr '\n' ' ' | sed 's/[[:space:]]\+/-/g')"
    echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Restaurar caché de Swift Package Manager
  uses: actions/cache@v4
  with:
    path: |
      ~/Library/Caches/org.swift.swiftpm
      .build
    key: spm-$-$-$
    restore-keys: |
      spm-$-$-

Esto no garantiza que todos los problemas desaparezcan, pero reduce el riesgo de mezclar productos de compilación entre toolchains incompatibles.

Tests y artefactos de diagnóstico

Para pruebas, es recomendable capturar un .xcresult cuando falle el job. Ese bundle facilita revisar resultados de tests, logs y diagnósticos de Xcode sin depender solo de la salida textual.

- name: Ejecutar tests
  run: |
    xcodebuild test \
      -scheme "App" \
      -destination "platform=iOS Simulator,name=iPhone 16" \
      -resultBundlePath TestResults.xcresult

- name: Subir resultados de tests
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: xcode-test-results
    path: TestResults.xcresult

Antes de fijar name=iPhone 16, conviene comprobar que ese simulador está disponible en la imagen mediante:

xcrun simctl list devices available

En equipos con muchos repositorios, una buena práctica es estandarizar el destino de test o parametrizarlo mediante variables de workflow.

Firma y secretos: no empezar por release

La validación inicial de Xcode 27 debería centrarse en compilar, resolver dependencias y ejecutar tests. No es necesario exponer certificados de distribución ni credenciales de publicación si el objetivo es comprobar compatibilidad.

Los pipelines Apple suelen manejar material sensible: certificados, perfiles de aprovisionamiento, claves de App Store Connect y tokens de terceros. GitHub documenta cómo instalar certificados Apple en runners macOS para desarrollo con Xcode, pero eso no significa que todos los jobs deban tener acceso a esos secretos.

Una separación razonable sería:

  • Job de compatibilidad con Xcode 27: sin secretos de distribución.
  • Job de tests: acceso mínimo a lo necesario.
  • Job de release: protegido por ramas, entornos, aprobaciones y secretos específicos.

Esto reduce superficie de ataque y evita que una imagen en preview forme parte del camino crítico de publicación antes de tiempo.

Qué probar primero

Para obtener valor pronto sin sobredimensionar la migración, el orden de validación puede ser:

  1. Resolver dependencias desde cero.
  2. Compilar el esquema principal.
  3. Ejecutar tests unitarios.
  4. Revisar warnings nuevos del compilador.
  5. Ejecutar tests de interfaz si existen.
  6. Validar archivado.
  7. Revisar firma y distribución interna.
  8. Plantear publicación solo cuando el entorno esté suficientemente estabilizado.

Los warnings merecen atención. Un nuevo compilador puede detectar usos obsoletos, cambios de inferencia o APIs que conviene migrar antes de que se conviertan en un bloqueo para futuras versiones.

Riesgos de adoptar la preview demasiado rápido

El principal riesgo no es que Xcode 27 falle, sino convertir una preview en una dependencia crítica sin una fase de observación. Puede haber cambios en herramientas preinstaladas, diferencias de rendimiento, simuladores no esperados o incidencias temporales de la imagen.

Conviene evitar tres errores:

  • Reemplazar macos-latest por xcode-27 en todos los workflows sin comparación previa.
  • Hacer obligatorio el job de preview para todos los pull requests desde el primer día.
  • Mezclar validación de compatibilidad con firma o publicación de producción.

La adopción temprana tiene sentido si se usa como radar. El objetivo es descubrir problemas antes, no trasladar riesgo al proceso de release.

Recomendación práctica

Para empezar con Xcode 27 en GitHub Actions, lo más equilibrado es crear un workflow o job paralelo que:

  • Use runs-on: xcode-27.
  • Se ejecute en pull requests o de forma programada.
  • Muestre versiones de Xcode, Swift, SDKs y simuladores disponibles.
  • Compile el esquema principal.
  • Ejecute una selección representativa de tests.
  • Separe cachés por versión de Xcode.
  • No tenga acceso a secretos de publicación.
  • Marque los fallos como no bloqueantes durante la fase inicial.

Después, el equipo puede clasificar los fallos por tipo: dependencias, cambios de Swift, configuración de proyecto, simuladores, tests frágiles o firma. Cuando el volumen de incidencias baje y la organización decida adoptar Xcode 27 como baseline, el job puede pasar de señal informativa a requisito de calidad.

La preview de Xcode 27 en GitHub-hosted macOS runners no obliga a migrar de inmediato, pero sí permite preparar la transición con menos incertidumbre. Para equipos Apple con pipelines críticos, probar pronto y de forma controlada suele ser más seguro que esperar a que el cambio sea urgente.