Caché de módulos
- Un proyecto generado. La caché de módulos lee el grafo del proyecto que Tuist mantiene tras
Project.swiftpara calcular hashes de los targets y sustituir sus binarios.
Tuist Module Cache ofrece una forma potente de optimizar los tiempos de compilación al almacenar en caché tus módulos como binarios (.xcframeworks) y compartirlos entre distintos entornos. Esta capacidad te permite aprovechar binarios generados previamente, reduciendo la necesidad de recompilar y acelerando el proceso de desarrollo.
La caché de módulos y la caché de Xcode son complementarias porque operan a distintos niveles de granularidad. La caché de módulos reemplaza módulos completos por .xcframeworks precompilados antes de ejecutar la compilación, mientras que la caché de Xcode reutiliza los resultados de compilación durante la compilación.
Los ajustes de la caché de compilación no forman parte de los hashes de la caché de módulos, por lo que activar o desactivar la caché de Xcode conserva los binarios que ya has calentado. En Xcode 27 y versiones posteriores, se incluyen en el hash los ajustes de mapeo de prefijos asociados. Consulta hashes de la caché de módulos.
Calentamiento#
Tuist utiliza hashes de forma eficiente para cada target del grafo de dependencias a fin de detectar cambios. Con estos datos, compila y asigna identificadores únicos a los binarios derivados de esos targets. Durante la generación del grafo, Tuist sustituye sin interrupciones los targets originales por sus versiones binarias correspondientes.
Esta operación, conocida como "calentamiento", genera binarios para uso local o para compartir con compañeros de equipo y entornos de CI a través de Tuist. El proceso de calentar la caché es sencillo y puede iniciarse con un comando simple:
tuist cache
El comando reutiliza binarios para acelerar el proceso.
Si un binario no se puede subir, el comando sube el resto, termina con un estado distinto de cero y enumera los targets que no se subieron. Los binarios en la caché local de una máquina cuentan como almacenados en caché; para volver a subirlos desde la misma máquina, ejecuta tuist clean binaries antes de calentar. Usa --no-upload para guardar los binarios solo en la caché local.
Selección de destino#
Usa --destination simulator o --destination device para almacenar en caché y generar para un único destino:
tuist cache --destination simulator
tuist generate --destination simulator
tuist cache --destination device
tuist generate --destination device
El valor predeterminado es all, que conserva todos los destinos admitidos por tus targets, incluido Mac Catalyst. Una ejecución posterior de tuist cache reutiliza las particiones almacenadas en caché y compila solo los SDK faltantes. La generación normal conserva las fuentes de un módulo si falta alguna partición requerida.
La generación restringida limita los targets de origen al destino seleccionado y conserva los targets nativos de macOS, incluidas las macros. Ninguno de los modos restringidos incluye Mac Catalyst. Vuelve a generar con --destination all para restaurar todos los destinos.
El almacenamiento en caché por particiones admite iOS y macOS con arquitecturas estándar. Otras plataformas, arquitecturas personalizadas y productos no compatibles mantienen el almacenamiento en caché de artefactos completos. La caché de módulos heredada requiere --destination all.
Admisión de subida remota#
La caché de módulos moderna usa REAPI. Cuando una caché saturada rechaza temporalmente una subida con RESOURCE_EXHAUSTED, Tuist aplica retroceso con jitter, respeta los retrasos mínimos estándar de google.rpc.RetryInfo y reintenta dentro de un presupuesto de espera acotado. La admisión de subida permite como máximo seis reintentos por llamada. Cada operación de subida de blobs comparte un único presupuesto acumulado de retroceso de 60 segundos entre sus comprobaciones de presencia, subidas por lotes y escrituras en streaming; las esperas concurrentes consumen el presupuesto, por lo que los rechazos repetidos no conceden a cada blob en cola otro minuto de reintentos. Las transferencias exitosas no consumen este presupuesto y no se cancelan cuando se agota. La publicación de resultados de acciones tiene su propio presupuesto acotado. El retraso exponencial comienza en un segundo y crece hasta 30 segundos antes del jitter proporcional. Si el retraso requerido por el servidor supera el presupuesto restante, Tuist falla en lugar de reintentar antes de lo indicado. La cancelación interrumpe el retroceso y las lecturas de caché conservan reintentos breves para que la sobrecarga no detenga la generación.
Estos valores predeterminados también ayudan a versiones antiguas de Kura sin indicaciones de reintento. Los reintentos más largos absorben contención temporal, no sobrecarga sostenida: reduce el límite de concurrencia de caché en trabajos de calentamiento o aumenta la capacidad del servidor si los rechazos persisten. Consulta dimensionamiento de subidas de Kura para estimaciones de memoria de pods y métricas de admisión.
Selección de configuración#
Al calentar la caché sin pasar --configuration, Tuist selecciona la configuración de compilación a usar en el siguiente orden:
- El
defaultConfigurationestablecido enProject.Options.generationOptionsde tu manifiesto, si existe. - De lo contrario, la primera configuración de compilación de variante
debug, ordenada alfabéticamente por nombre.
Para calentar la caché para una configuración específica, pásala explícitamente:
tuist cache --configuration Release
Uso#
De forma predeterminada, cuando los comandos de Tuist requieren generar el proyecto, sustituyen automáticamente las dependencias por sus equivalentes binarios de la caché, si están disponibles. Además, si especificas una lista de targets en los que centrarte, Tuist también reemplazará cualquier target dependiente por sus binarios almacenados en caché, siempre que estén disponibles. Para quienes prefieren otro enfoque, existe una opción para desactivar por completo este comportamiento mediante una flag específica:
tuist generate # Only dependencies
tuist generate Search # Dependencies + Search dependencies
tuist generate Search Settings # Dependencies, and Search and Settings dependencies
tuist generate --cache-profile none # No cache at all
tuist test
La caché de binarios es una funcionalidad diseñada para flujos de trabajo de desarrollo, como ejecutar la app en un simulador o dispositivo, o ejecutar tests. No está pensada para compilaciones de release. Al archivar la app, genera un proyecto con las fuentes usando --cache-profile none.
Perfiles de caché#
Tuist admite perfiles de caché para controlar con qué agresividad se reemplazan los targets por binarios almacenados en caché al generar proyectos.
- Integrados:
only-external: reemplaza solo dependencias externas (predeterminado del sistema)all-possible: reemplaza tantos targets como sea posible (incluidos targets internos)none: nunca reemplaza por binarios almacenados en caché
Selecciona un perfil con --cache-profile en tuist generate:
# Built-in profiles
tuist generate --cache-profile all-possible
# Custom profiles (defined in Tuist Config)
tuist generate --cache-profile development
# Use config default (no flag)
tuist generate
# Focus on specific targets (implies all-possible)
tuist generate MyModule AnotherTarget
# Disable binary replacement entirely
tuist generate --cache-profile none
La flag --no-binary-cache está obsoleta. Usa --cache-profile none en su lugar. La flag obsoleta sigue funcionando por compatibilidad hacia atrás.
Precedencia al resolver el comportamiento efectivo (de mayor a menor):
--cache-profile none- Enfoque en targets (pasar targets a
generate) → perfilall-possible --cache-profile <value>- Predeterminado de la configuración (si está establecido)
- Predeterminado del sistema (
only-external)
Productos compatibles#
Solo los siguientes productos de target pueden almacenarse en caché con Tuist:
- Frameworks (estáticos y dinámicos), incluidos frameworks de soporte para tests que dependen de XCTest o Swift Testing
- Librerías (estáticas y dinámicas), incluidas librerías de soporte para tests que dependen de XCTest o Swift Testing
- Bundles
- Macros de Swift
Los bundles de test siguen excluidos de la caché de módulos. Esto significa que los targets unitTests y uiTests no se almacenan en caché como binarios, pero los targets regulares de framework y librería de los que dependen los tests sí pueden almacenarse en caché aunque enlacen con XCTest o Swift Testing.
Las .xcframework de librería almacenadas en caché conservan los metadatos necesarios para que los proyectos generados las importen, incluidos los módulos de Swift y las cabeceras públicas de C/Objective-C cuando el target fuente declara cabeceras públicas.
Cuando un target no es cacheable, hace que los targets upstream tampoco lo sean. Por ejemplo, si tienes el grafo de dependencias A > B, donde A depende de B, si B no es cacheable, A tampoco lo será.
Analíticas#
Abre Caché de módulos → Módulos en el panel de tu proyecto para encontrar módulos con fallos frecuentes. Selecciona el entorno que quieres mejorar, como CI, y un intervalo de fechas. La vista general muestra aciertos y fallos de caché, y módulos con fallos; al abrir un módulo se muestra su historial y la razón asignada a cada observación.
El desplegable Fallos selecciona un recuento y una explicación para una razón. El gráfico muestra la distribución de las cuatro razones. En la página de un módulo, usa el filtro Razón del historial para inspeccionar ocurrencias individuales. Pasa el cursor sobre una etiqueta de razón, o enfócala con el teclado, para ver su definición.
Razones de fallo#
| Razón | Qué significa |
|---|---|
| Cambiado | Las entradas comparadas propias del módulo han cambiado. Estas incluyen hashes de archivos, ajustes de compilación, la configuración resuelta y el identificador del compilador. Una edición del código fuente es solo una posible causa. |
| Upstream | Las entradas comparadas propias del módulo se mantienen igual, pero el hash de su dependencia o paquete externo ha cambiado. Los archivos fuente del módulo pueden permanecer intactos. |
| Frío | No hay una observación anterior del módulo con la que comparar, o las entradas reportadas no explican el fallo y no hay evidencia suficiente de disponibilidad remota previa. Frío no prueba que el módulo nunca se haya almacenado en caché. |
| Evictado | La clave de caché exacta tuvo previamente un acierto remoto en el mismo proyecto en el mismo endpoint de caché registrado, pero ahora falla. |
Para los fallos etiquetados como Evictado, el artefacto se descargó previamente de la caché remota pero no pudo reutilizarse esta vez. La evicción es la causa más probable, aunque los fallos de acceso o descarga también pueden explicar el fallo. Tuist solo asigna esta etiqueta cuando encuentra un acierto remoto anterior; los fallos previos y los aciertos locales no son suficientes.
Por ejemplo:
| Escenario | Clasificación |
|---|---|
| Un módulo falla sin observación previa ni acierto remoto válido | Frío |
| A cambia; B depende de A y C depende de B; las tres claves cambian | A es Cambiado; B y C son Upstream |
| La versión del compilador cambia después del calentamiento, con fuentes y ajustes sin cambios | Los fallos afectados son Cambiados cuando ambas observaciones reportan las entradas del compilador |
| Una clave exacta se descargó remotamente y luego falla después de que su artefacto sea evictado | Evictado, cuando el acierto anterior califica como evidencia |
| Una clave falla repetidamente y nunca se calentó con éxito | Puede permanecer Frío |
Las razones describen observaciones; no son etiquetas permanentes asociadas a una clave. Por ejemplo, el primer fallo tras una actualización del compilador puede ser Cambiado, mientras que un fallo posterior para esa nueva clave puede ser Frío si nunca se observó como disponible remotamente.
Mejorar la tasa de aciertos de caché#
- Elige una línea base consistente. Comienza con CI y un rango de fechas representativo. Ordena los módulos por fallos, luego considera sus tasas de acierto, dependientes y coste de compilación. Arreglar una dependencia costosa que falla con frecuencia puede ahorrar más tiempo que mejorar la tasa de acierto de un módulo diminuto. Compara el mismo entorno y cargas de trabajo similares después de realizar un cambio.
- Investiga los fallos Cambiados. Abre las ejecuciones relevantes y compara sus claves completas y entradas reportadas. Alinea los trabajos de calentamiento y consumo en la versión de Xcode/compilador, configuración y destinos de destino previstos. Vuelve a calentar después de cambios intencionados. Si archivos generados volátiles o ajustes dependientes del entorno invalidan módulos otherwise estables, investiga esas entradas. Mantén en el hash las entradas que afectan a la compatibilidad binaria.
- Sigue los fallos Upstream hasta la dependencia cambiada. Inspecciona su historial para encontrar el cambio directo. Calentar las claves resultantes puede restaurar la reutilización. Si una implementación que cambia con frecuencia invalida muchos dependientes costosos, considera módulos más pequeños o interfaces estables como se describe en Eficiencia.
- Comprueba la cobertura de calentamiento para fallos Fríos. Verifica que el calentamiento selecciona los módulos requeridos, usa la configuración y el entorno del trabajo de consumo, y sube los artefactos con éxito. Comprueba las claves completas usadas por los trabajos de CI reales. Si el calentamiento y el consumo solicitan claves diferentes, inspecciona las entradas de hashing antes de asumir que la caché fue evictada. Los fallos repetidos sin una subida exitosa anterior son posibles incluso cuando las fuentes del módulo no han cambiado.
- Usa la evidencia para los fallos Evictados. Confirma la clave y el endpoint de la ejecución de consumo, luego inspecciona las advertencias de caché de esa ejecución y el resultado de la subida del trabajo de calentamiento. Comprueba la retención o evicción cuando corresponda. Vuelve a calentar los artefactos necesarios y verifica un acierto posterior. Si siguen fallando, comparte los enlaces y registros de las ejecuciones relevantes con soporte.
Para una comparación de hashes, ejecuta esto en cada entorno relevante, usando la configuración que pretendes calentar y consumir:
tuist hash cache --configuration Debug --verbose
Compara el hash completo del módulo y el bloque de componentes. Repetir el comando en la misma máquina sin cambios debería producir normalmente el mismo resultado; compara los entornos reales de calentamiento y consumo para investigar una discrepancia. Ninguno de los comandos necesita acertar en la caché. Consulta diagnóstico de hashing para más comprobaciones.
Eficiencia#
El nivel de eficiencia que se puede lograr con la caché binaria depende en gran medida de la estructura del grafo. Para obtener los mejores resultados, recomendamos lo siguiente:
- Evita grafos de dependencias muy anidados. Cuanto más superficial sea el grafo, mejor.
- Define dependencias con objetivos de protocolo/interfaz en lugar de implementación, e inyecta las implementaciones como dependencias desde los objetivos de nivel superior.
- Divide los objetivos modificados con frecuencia en otros más pequeños cuya probabilidad de cambio sea menor.
Las sugerencias anteriores forman parte de La Arquitectura Modular, que proponemos como una forma de estructurar tus proyectos para maximizar los beneficios no solo de la caché binaria, sino también de las capacidades de Xcode.
Configuración recomendada#
Recomendamos tener un trabajo de CI que se ejecute en cada commit en la rama principal para calentar la caché. Esto asegurará que la caché contenga siempre binarios para los cambios en main, de modo que las compilaciones locales y de ramas de CI se basen incrementalmente en ellos.
Ejecuta tuist cache en un paso de CI dedicado sin pasos posteriores que dependan del espacio de trabajo generado. Dado que tuist cache modifica el espacio de trabajo con fines de construcción de caché, cualquier paso de CI que necesite el espacio de trabajo debe ejecutar tuist generate primero para obtener un espacio de trabajo nuevo y utilizable.
El comando tuist cache también hace uso de la caché binaria para acelerar el calentamiento.
A continuación se muestran algunos ejemplos de flujos de trabajo comunes:
Un desarrollador comienza a trabajar en una nueva funcionalidad#
- Crea una nueva rama desde
main. - Ejecuta
tuist generate. - Tuist obtiene los binarios más recientes de
mainy genera el proyecto con ellos.
Un desarrollador envía cambios upstream#
- La pipeline de CI ejecutará
xcodebuild buildotuist testpara compilar o probar el proyecto. - El flujo de trabajo obtendrá los binarios más recientes de
mainy generará el proyecto con ellos. - Luego compilará o probará el proyecto incrementalmente.
Configuración#
Límite de concurrencia de caché#
Establece TUIST_CACHE_CONCURRENCY_LIMIT en un entero positivo para limitar tanto las descargas como las subidas. Por ejemplo, reduce la carga en una caché autoalojada pequeña en trabajos de calentamiento:
TUIST_CACHE_CONCURRENCY_LIMIT=2 tuist cache
Cuando no está establecido, las subidas REAPI usan ocho tareas de transferencia. Las descargas usan ocho cuando cualquier blob requiere ByteStream y 32 para planes solo por lotes. La ruta de caché HTTP heredada tiene por defecto 100; ese valor por defecto no se aplica a REAPI. Los valores inválidos, incluido el documentado anteriormente none, mantienen los valores por defecto en lugar de eliminar los límites. Usa unset TUIST_CACHE_CONCURRENCY_LIMIT para restaurar los valores por defecto.
Los límites se aplican por operación de transferencia, no globalmente entre operaciones o trabajos de CI. Tres calentamientos simultáneos con un límite de dos pueden tener seis subidas activas contra la misma caché. Los trabajos de consumo pueden dejar la variable sin establecer para mantener el paralelismo de descarga por defecto.
Directorio scratch de calentamiento de caché#
Por defecto, tuist cache crea un directorio scratch temporal para intermedios de compilación y artefactos de caché ensamblados, y luego lo elimina cuando finaliza el calentamiento de la caché. Para mantener esos archivos bajo un directorio que tú gestionas, establece TUIST_CACHE_WARM_SCRATCH_DIRECTORY.
La ruta puede ser absoluta o relativa al directorio de trabajo actual. Tuist crea el directorio cuando no existe. Si ya existe, debe ser un directorio vacío.
Cuando esta variable de entorno está establecida, Tuist deja el directorio y su contenido en su lugar después de que finaliza el comando. El llamador es responsable de limpiarlo antes del próximo calentamiento de caché. Cuando la variable no está establecida, Tuist continúa usando y eliminando un directorio temporal.
Tuist rechaza un directorio scratch propiedad del llamador cuando es necesario calentar un objetivo de compilación externo. Los scripts de compilación externos controlan sus propias ubicaciones de salida, por lo que Tuist no puede garantizar que esas salidas permanezcan dentro del directorio scratch.
Cobertura de código#
La cobertura de código con la caché de módulos está en acceso anticipado. Los comandos solo leen y calientan los binarios instrumentados descritos a continuación cuando TUIST_FEATURE_FLAG_COVERAGE=1 está establecido.
Un objetivo de la caché de módulos es un binario precompilado, y una ejecución de tests mide su código solo cuando ese binario se compiló con contadores de cobertura. Activa la cobertura para tu proyecto en Tuist.swift:
import ProjectDescription
let tuist = Tuist(
project: .tuist(
generationOptions: .options(
coverage: .coverage(enabled: true)
)
)
)
Con enabled, cada comando y cada entorno lee una variante instrumentada de la caché para los objetivos propios de tu proyecto, por lo que un único calentamiento en CI sirve tanto para entornos de CI como para máquinas locales:
| Comando | Objetivos de tu proyecto | Paquetes externos |
|---|---|---|
tuist cache (configuración Debug) | Calienta binarios instrumentados | Calienta binarios normales |
tuist cache (configuración Release) | Calienta binarios normales | Calienta binarios normales |
tuist hash cache | Imprime las claves instrumentadas | Imprime las claves normales |
tuist generate | Lee binarios instrumentados | Lee binarios normales |
tuist test | Lee binarios instrumentados | Lee binarios normales |
La opción se aplica a cada configuración que Xcode trata como Debug. Las compilaciones Release nunca están instrumentadas. Tuist también enlaza el runtime de perfilado en esas configuraciones (CLANG_COVERAGE_MAPPING_LINKER_ARGS), por lo que las compilaciones que no recopilan cobertura siguen enlazando los binarios instrumentados, y activa la cobertura en la acción de test de los esquemas que genera automáticamente y de los esquemas que defines dejando coverage sin establecer.
Un esquema que definas sigue la opción a menos que establezca la cobertura por sí mismo: .options(coverage: false) la mantiene desactivada y .options(coverage: true) la mantiene activada independientemente de lo que diga la opción. Los planes de test deciden la cobertura por sí mismos, y -enableCodeCoverage sigue anulando una ejecución, como en Xcode. Una ejecución que lee binarios instrumentados sin recopilar cobertura compila y prueba normalmente. Cuando una ejecución recopila cobertura pero los objetivos de tu proyecto provienen de la caché como binarios normales, Tuist advierte que su cobertura no se mide.
La variable de entorno TUIST_COVERAGE_ENABLED (1 o 0) tiene prioridad sobre coverage: .coverage(enabled:). Establécela para todo un entorno, como un flujo de trabajo de CI, en lugar de para un solo comando, para que los comandos que calientan la caché y los que la lean estén de acuerdo.
prefixMapping (consulta la caché de Xcode) tiene su propia anulación: la variable de entorno TUIST_COVERAGE_PREFIX_MAPPING (1 o 0) tiene prioridad sobre coverage: .coverage(prefixMapping:).
Anular a desactivado lee la variante normal, que una caché calentada con cobertura activada no llena, por lo que esos objetivos se compilan desde el código fuente. Un calentamiento Release siempre calienta binarios normales.
enabled y prefixMapping cambian la configuración de compilación y las claves de caché de los objetivos de tu proyecto, así como los hashes de testing selectivo de sus objetivos de test. Establece el mismo valor en cada máquina que comparta la caché y no los derives del entorno (por ejemplo, de Environment.isCI): un valor diferente entre CI y máquinas locales les da claves distintas. Lo mismo ocurre con las variables de entorno: una ejecución con TUIST_COVERAGE_ENABLED=0 no reutiliza los binarios ni los resultados de testing selectivo de ejecuciones con cobertura activada.
Ten en cuenta:
- Los binarios instrumentados son más grandes: alrededor de un 20–30 % para archivos estáticos y bibliotecas dinámicas. Las compilaciones Debug se ejecutan a la misma velocidad.
- El informe de cobertura de Xcode nunca muestra código que provino de la caché, con o sin esta opción. La subida de cobertura de Tuist sí la mide. Para ver la cobertura de un objetivo en Xcode, céntrate en él con
tuist generate <target>para que se compile desde el código fuente.
Solución de problemas#
No usa binarios para mis objetivos#
Asegúrate de que los hashes sean deterministas entre entornos y ejecuciones. Esto puede ocurrir si el proyecto tiene referencias al entorno, por ejemplo, a través de rutas absolutas. Puedes usar el comando diff para comparar los proyectos generados por dos invocaciones consecutivas de tuist generate o entre entornos o ejecuciones.
Asegúrate también de que el objetivo no dependa, ni directa ni indirectamente, de un objetivo no cacheable.
Símbolos faltantes#
Al usar fuentes, el sistema de compilación de Xcode, a través de Derived Data, puede resolver dependencias que no están declaradas explícitamente. Sin embargo, cuando confías en la caché binaria, las dependencias deben declararse explícitamente; de lo contrario, es probable que veas errores de compilación cuando no se encuentren símbolos. Para depurar esto, recomendamos usar el comando tuist inspect dependencies --only implicit y configurarlo en CI para prevenir regresiones en el enlazado implícito.