Registro de build
¿Cómo se ejecutan builds de Unity en CI con un Mac mini propio?
Registra el Mac mini como runner de GitHub Actions con sus propias etiquetas, llama a Unity en modo batch desde un script en lugar de un contenedor y conserva el espacio de trabajo entre ejecuciones para que la Library siga caliente. En GoTasty, un build de desarrollo de Android pasó de 52 minutos en frío a menos de 8 en caliente.
6 min de lectura

En GoTasty, el juego de comida callejera en Unity 6 que creo en GOGOGO LLC, nadie abre el Unity Editor para hacer un build. La CI es el compilador. Hasta noviembre esa CI corría en runners alojados por GitHub: GameCI en Docker sobre Linux para Unity y un runner macOS alojado para el archivo de Xcode. El 3 de noviembre los minutos de pago llegaron al límite de gasto de la organización. El 4 de noviembre los dos flujos se mudaron a un Mac mini M4 en mi mesa. Estas son las notas.
¿Por qué un Mac propio en lugar de runners alojados?
- Coste. Una máquina que ya tienes no factura por minuto, y los builds de Unity son largos.
- Una Library caliente. La carpeta
Libraryde Unity es su caché de importación. Un runner alojado empieza vacío o restaura una caché grande en cada ejecución. Un runner propio simplemente la conserva. - Una máquina para las dos plataformas. El mismo Mac compila el APK o AAB de Android, genera el proyecto de Xcode para iOS y lo archiva con Xcode. Unity 6 guarda las importaciones por plataforma, así que cambiar entre Android e iOS no lo reimporta todo.
El precio es que mantener viva la máquina te toca a ti, y solo corre un build de Unity a la vez.
¿Cómo se elige el runner en cada ejecución?
Los dos flujos aceptan una entrada runner con dos valores: mac, el predeterminado (también para los push), y cloud. El trabajo elige su runner a partir de esa entrada, así que el camino alojado sigue funcionando cuando el Mac está ocupado o apagado:
runs-on: ${{ inputs.runner == 'cloud' && 'gotasty-8core'
|| fromJSON('["self-hosted","macOS","ARM64","gotasty-mac"]') }}La etiqueta propia gotasty-mac importa: evita que otros trabajos self-hosted de la organización caigan en esta máquina por accidente.
¿Cómo se ejecuta Unity sin GameCI?
GameCI envuelve Unity en un contenedor Linux, que es la herramienta adecuada en runners Linux alojados. En un Mac puedes llamar directamente al binario del editor. Un script pequeño (tools/ci_mac_unity.sh en nuestro repositorio) ejecuta Unity 6000.0.85f2, instalado con Unity Hub y los módulos de Android e iOS, en modo batch con nuestro método de build, y hace tres cosas más:
- toma un bloqueo de toda la máquina con
shlock, para que dos trabajos nunca abran el mismo proyecto a la vez; - registra la memoria libre antes y durante el build, y si hay emuladores de Android en marcha, porque un emulador en la misma máquina compite por la RAM;
- escribe los resultados en una carpeta fija del disco,
Builds/GoTasty/<platform>/<run>/, e imprime la ruta en el resumen de la ejecución en lugar de subir artefactos grandes.
La licencia es una licencia normal de Unity Personal activada en la máquina, no un secreto que se pasa a un contenedor.
¿Qué debe persistir entre ejecuciones y qué no?
En el Mac, el paso de checkout no borra el espacio de trabajo. Antes de cada build, un paso de limpieza elimina solo las carpetas generadas que hay que reconstruir (por ejemplo, los ajustes de Addressables) y conserva el resto: la Library, el código generado, los prefabs del kit de ciudades ya preparados y el SDK de Firebase descargado. Ahí es donde se va el tiempo. Nuestras cifras medidas para un APK de desarrollo de Android: unos 52 minutos en frío (43 dentro de Unity, más un checkout con todo el historial) y 7,7 minutos en caliente.
El checkout usa fetch-depth: 0. El versionCode de Android sale del número de commits, como hacía el versionado semántico de GameCI, y un clon superficial daría a todos los builds la misma versión. El número de build de iOS es el número de ejecución del flujo.
¿Cómo es una ejecución, del push al APK?
- Un push a main, o un lanzamiento manual con
runner: mac, pone el trabajo en cola para la etiquetagotasty-mac. - El checkout actualiza el espacio de trabajo existente con todo el historial, y el paso de limpieza borra solo las carpetas generadas.
- El script de build toma el bloqueo de Unity, registra la memoria y ejecuta Unity en modo batch con el método de build.
- El APK o AAB queda en
Builds/GoTasty/Android/<run>/, y el resumen de la ejecución imprime esa ruta. - Para un paquete de tienda, el contenido de ciudades que se carga desde nuestra CDN se sube en el mismo trabajo.
A partir de ahí, los agentes de QA pueden instalar el build en el emulador directamente desde esa carpeta, sin volver a descargar nada de GitHub.
¿Cómo se mantiene vivo el runner tras un reinicio?
El repositorio y los resultados de build viven en un SSD externo, y un servicio de launchd no podía llegar a ese volumen. La solución es menos elegante y funciona: un LaunchAgent abre un pequeño script .command en Terminal al iniciar sesión, y el script reinicia el run.sh del runner cada vez que termina. Tras cada actualización de macOS, comprueba que sigue arrancando.
¿Y Xcode y CocoaPods en el mismo Mac?
El archivo de iOS necesita Xcode 26 para los paquetes actuales de Firebase y para subir a la App Store. El trabajo no necesita sudo: busca el /Applications/Xcode*.app más reciente con versión 26 o superior y fija DEVELOPER_DIR solo para ese trabajo. CocoaPods viene de Homebrew, porque el pod del Ruby del sistema era demasiado antiguo para proyectos de Xcode 26. Las fuentes de los pods quedan en caché en el disco, algo que importó el 8 de noviembre, cuando las descargas de releases de GitHub fallaron durante una media hora. Esa historia tiene su propio texto.
¿Lo volvería a hacer?
Sí, manteniendo vivo el camino en la nube como respaldo. Una sola máquina es un único punto de fallo, y algunas semanas necesitan dos builds a la vez. Pero una Library de Unity caliente en un disco local rápido cambió cuántas veces podemos permitirnos compilar, y eso cambia lo rápido que reciben feedback los agentes y las personas. La visión completa, crear el juego con agentes de programación de IA, está en atakanozalan.com.
Fuentes
- Unity 6 · Unity Technologies
- Mac mini · Apple
- Self-hosted runners · GitHub Docs
- Using labels with self-hosted runners · GitHub Docs
- Configuring the self-hosted runner application as a service · GitHub Docs
- GitHub Actions billing · GitHub Docs
- Getting started · GameCI
- Unity Editor command line arguments reference · Unity Documentation
- actions/checkout · GitHub
- Homebrew Formulae: cocoapods · Homebrew