Saltar al contenido
Development

Fijar la versión de SwiftLint en CI cuando Homebrew no lo permite

Por Victor Da Luz
iosswiftcidev-logdeep-cut-atlas

Esta app luego se renombró a Deep Cut Atlas. Se la llama “Discoverer” en todo lo que sigue, porque así se llamaba el día en que pasó esto.

Un problema pequeño, una solución rápida, pero del tipo que resulta obvio una vez que se sabe y fácil de pasar por alto si no.

Problema

El CI de Discoverer instala SwiftLint con brew install swiftlint antes de correr swiftlint lint --strict. Esa línea parece completamente inofensiva, solo instala una herramienta, pero en realidad instala lo que sea que Homebrew tenga como versión estable actual el día en que corre el job. El pre-commit hook local estaba validado contra la 0.63.3. Para cuando revisé esto, la versión estable de Homebrew ya había pasado a la 0.65.0.

Eso significa que CI y la máquina local podían estar corriendo, en silencio, dos linters distintos. Si una versión nueva de SwiftLint trae reglas por defecto más estrictas, o cambia cómo una regla existente cuenta las violaciones, una corrida de CI puede fallar sin ningún cambio de código de por medio, y este CI solo corre en tags de release, así que esa falla aparece justo cuando se intenta publicar un build, no durante el trabajo cotidiano normal.

Por qué este enfoque

El primer instinto fue simplemente fijar una versión de fórmula de Homebrew, como se haría con brew install node@18. Eso no existe para SwiftLint, no hay una fórmula swiftlint@0.63.3, solo la única fórmula swiftlint de rodadura continua que siempre sigue la última estable.

Sin embargo, SwiftLint sí publica un portable_swiftlint.zip en cada release de GitHub, un paquete binario autocontenido, sin ninguna relación con el ritmo de publicación de Homebrew. Ese es el verdadero punto de anclaje.

Implementación

- name: Install SwiftLint (pinned)
  env:
    SWIFTLINT_VERSION: "0.63.3"
  run: |
    curl -sSL -o swiftlint.zip \
      "https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/portable_swiftlint.zip"
    unzip -q swiftlint.zip -d "$RUNNER_TEMP/swiftlint"
    echo "$RUNNER_TEMP/swiftlint" >> "$GITHUB_PATH"

Descargar el asset de la versión exacta, descomprimirlo en algún lugar del espacio temporal del runner, agregar ese directorio al PATH. Sin sudo, sin tocar directorios del sistema. Elegí la 0.63.3 específicamente porque es la versión que ya venía corriendo contra este código base todo este tiempo, fijar la más nueva solo habría cambiado un tipo de deriva por otro, con la diferencia de que ahora sería una deriva que todavía no había verificado contra mi propia configuración de lint.

Mientras estaba en el archivo de CI, también agregué -resultBundlePath al paso de pruebas y un paso de actions/upload-artifact condicionado a if: failure(). Antes de esto, una corrida fallida disparada por un tag me dejaba solo con el log de consola crudo para averiguar qué se rompió. Ahora el bundle xcresult completo, detalle de aserciones, capturas si falló una prueba de interfaz, se sube automáticamente, pero solo cuando algo falla de verdad, así que no llena de artefactos innecesarios cada corrida exitosa.

Trampas

El lado local de esto no se puede forzar de la misma manera sin agregar fricción real. Sigue sin haber una fórmula versionada de Homebrew, así que la instalación local de un colaborador se queda “lo que sea que esté actual” a menos que también vaya a descargar un binario portátil a mano, no vale la pena ese costo de configuración para lo que hoy es un proyecto en solitario. En vez de eso, dejé un comentario de una línea junto a la línea brew install swiftlint del README apuntando a la versión exacta que fija CI, para acordarme de actualizar una cuando actualice la otra a propósito y no por accidente.

actions/upload-artifact también resultó estar ya en la v7, habría apostado por la v4 de memoria, lo cual es un buen recordatorio de revisar realmente la versión actual de una herramienta en vez de confiar en lo que “suena actual.”

Resultados

Disparé el workflow manualmente contra la rama antes de mergear (el CI de este repo corre solo por tag o dispatch, no en cada push, para controlar el costo de los runners de macOS) y confirmé que swiftlint --version en el log de CI imprimió exactamente 0.63.3, no lo que fuera que Homebrew tuviera como estable ese día. Una solución pequeña, pero cierra una brecha real: la corrida de CI que más importa (un tag de release) es la que estaba más expuesta a que una actualización de dependencia sin revisar la rompiera por razones que no tienen nada que ver con el código que en realidad se está publicando.

Lecturas relacionadas