Integración continua para iOS y Android con Fastlane + Gitlab CI

0
2972
  1. Introducción
  2. Crear el contenedor docker para compilar Android
  3. Usar GitLab Runner para compilar iOS
  4. El uso de fastlane
  5. Los instrumentos de GitLab CI
  6. Conclusiones
  7. Referencias

Introducción

En este tutorial vamos a aprender cómo se puede montar el entorno de integración continua para aplicaciones móviles iOS y Android utilizando las herramientas que nos proporciona la plataforma GitLab. ¿Para qué sirve la integración continua? Si tienes alguna experiencia en la programación, tal vez hayas tenido una situación en la que al bajar algunos cambios de un repositorio algo dejó de funcionar. En un equipo de múltiples personas es bastante complicado controlar la consistencia y la calidad del código. La integración continua puede prevenir las situaciones de este tipo. Pero si eres la única persona que trabaja en un proyecto las compilaciones automáticas y la ejecución de pruebas después de cada commit pueden ser muy útiles también. Con la integración continua, puedes notar problemas más rápido y solucionarlos más pronto.

Crear docker contenedor para compilar Android

Para compilar el proyecto de Android vamos a utilizar el contenedor de Docker. Es una manera eficaz y bastante sencilla para construir y ejecutar las aplicaciones sin tener que utilizar un equipo real. Los contenedores Docker son una manera segura y consistente de crear un entorno de pruebas.

FROM openjdk:8

ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV PATH $PATH=$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools:$PATH

WORKDIR /home

RUN mkdir "$ANDROID_HOME" .android && \
    cd "$ANDROID_HOME" && \
    curl -o sdk.zip $ANDROID_SDK_URL && \
    unzip sdk.zip && \
    rm sdk.zip && \
# Download Android SDK
yes | sdkmanager --licenses && \
sdkmanager --update && \
sdkmanager "build-tools;29.0.3" && \
sdkmanager "platforms;android-29" && \
sdkmanager "platform-tools" && \
sdkmanager "extras;android;m2repository" && \
sdkmanager "extras;google;m2repository" && \
# Install Additional Packages
apt-get update && \
apt-get --quiet install --no-install-recommends -y --allow-unauthenticated build-essential vim-common git ruby-full openssl libssl-dev
gem install fastlane && \
gem install bundler && \
# Clean up
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
apt-get autoremove -y && \
apt-get clean

En este fichero docker aparte de Android SDK metemos también las gemas de Ruby como Bundler y Fastlane que nos ayudarán en el proceso de la subida de nuestros binarios a las tiendas online de las aplicaciones AppStore (iOS) y PlayStore (Android).

También tenemos que hacer un par de cambios en nuestra configuración básica de Gradle para facilitar la publicación. En el fichero app/build.gradle tenemos que poner las variables que se utilizarán para firmar nuestra aplicación Android. Podemos usar un fichero que contiene todas variables o utilizar las variables de GitLab. Es más seguro el segundo.

// Try reading secrets from file
def secretsPropertiesFile = rootProject.file("secrets.properties")
def secretProperties = new Properties()

if (secretsPropertiesFile.exists()) {
    secretProperties.load(new FileInputStream(secretsPropertiesFile))
}
// Otherwise read from environment variables, this happens in CI
else {
    secretProperties.setProperty("signing_keystore_password", "${System.getenv('signing_keystore_password')}")
    secretProperties.setProperty("signing_key_password", "${System.getenv('signing_key_password')}")
    secretProperties.setProperty("signing_key_alias", "${System.getenv('signing_key_alias')}")
}

También tenemos que subir la versión de código cada vez que subamos el binario. Para ello introducimos pequeños cambios en el mismo fichero:

android {
    defaultConfig {
        applicationId "im.gitter.gitter"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode Integer.valueOf(System.env.VERSION_CODE ?: 1)

Para firmar la versión release inyectamos nuestras variables que hemos introducido antes:

 signingConfigs {
        release {
            // You need to specify either an absolute path or include the
            // keystore file in the same directory as the build.gradle file.
            storeFile file("../android-signing-keystore.jks")
            storePassword "${secretProperties['signing_keystore_password']}"
            keyAlias "${secretProperties['signing_key_alias']}"
            keyPassword "${secretProperties['signing_key_password']}"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            testCoverageEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}

Usar GitLab Runner para compilar iOS

Desafortunadamente para compilar iOS aplicaciones en el entorno Gitlab hace falta un equipo físico. No se puede usar ni los contenedores Docker, ni maquinas virtuales. La única solución es instalar una aplicación GitLab Runner en en equipo físico. Se hace bastante rápido y fácilmente. Aquí puedes ver las instrucciones para todos los sistemas operativos. Después de instalar Gitlab Runner tienes que registrarlo con un token de tu proyecto. También tienes que ponerle un tag para usarlo luego en el script de Gitlab CI. De este modo, las tareas vinculadas al iOS se van a ejecutar en el equipo con el Gitlab Runner instalado.

El uso de fastlane

Para usar Gitlab CI, necesitamos una forma de construir y archivar nuestro proyecto desde terminal. Aunque podemos hacer esto sin herramientas adicionales usando el comando xcodebuild, la mejor solución será usar la herramienta Fastlane. Si no has utilizado Fastlane antes, en resumen, es una herramienta que, basada en un script simple preparado por nosotros, puede realizar automáticamente varias operaciones relacionadas con todo el proceso de desarrollo de la aplicación. Por ejemplo: crear un proyecto, ejecutar pruebas, generar informes de cobertura de código, enviar compilaciones a TestFlight, HockeyApp, etc., generar automáticamente capturas de pantalla de la aplicación, enviar notificaciones de compilación y muchas otras operaciones.

Para asegurarnos de que usamos las mismas versiones de todas las herramientas necesarias en cada ordenador, podemos usar la herramienta Bundler. Se puede decir que Bundler es algo similar a Cocoapods, pero en lugar de un Podfile tenemos un Gemfile y en lugar de administrar versiones de bibliotecas de iOS, se usa para administrar versiones de gemas Ruby, por ejemplo, CocoaPods y Fastlane. Por lo tanto, es otra herramienta que en algunos casos facilita la vida y ayuda a evitar problemas de incompatibilidad entre versiones particulares de gemas de Ruby.

El problema con las diferentes versiones de CocoaPods está relacionado con la situación en la que los Pods no se guardan en el repositorio de git. Si todo el directorio de Pods se mantiene en el repositorio de git y no tienes que descargar bibliotecas externas para compilar su proyecto, entonces el problema no se produce y quizás no necesites Bundler en este caso. Dependiendo de tus preferencias y necesidades, puedes decidir si vale la pena usar Bundler en tu caso o tal vez valga la pena mantener Pods en git. Configuremos Fastlane para que podamos construir y exportar el binario de nuestro proyecto desde la terminal.

De acuerdo con la descripción en https://docs.fastlane.tools/getting-started/ios/setup/ puedes instalar Fastlane con este comando:

# Using RubyGems
sudo gem install fastlane -NV

# Alternatively using Homebrew
brew install fastlane

El uso de «gem install fastlane -NV» puede requerir sudo si no se usa RVM en tu ordenador ni has cambiado la ruta de instalación de gemas configurando la variable de entorno GEM_HOME. Cuando finalice la instalación de Fastlane, puedes ir al directorio con tu proyecto en la terminal e inicializar Fastlane con el siguiente comando:

fastlane init

Después de eso, se creará un nuevo directorio fastlane con un archivo llamado Fastfile. Alternativamente, en lugar de fastlane init, simplemente puedes crear ese directorio y archivo manualmente. Abre ese archivo en cualquier editor de texto y cambia su contenido a lo siguiente para iOS:

platform :ios do
  desc "Build the application release version"
  lane :buildRelease do
    build_app(scheme: "App",
            workspace: "ios/App/App.xcworkspace",
            clean: true,
            include_bitcode: true,
            output_directory: "."
      )
  end
end

Y para Android:

platform :android do
  desc "Builds the debug code"
  lane :buildDebug do
    gradle(task: "assembleDebug",
          project_dir: "./android")
  end

  desc "Builds the release code"
  lane :buildRelease do |options|
    gradle(task: "assemble",
          flavor: options[:flavor],
          build_type: "Release",
          project_dir: "./android")
  end
end

Ahora puedes probar desde la terminal de tu equipo si todo está bien definido.

fastlane iOS buildRelease
fastlane android buildRelease

Fastlane también nos puede ayudar en subir los binarios a las tiendas de aplicaciones (markets). Para eso hay que utilizar en el caso de Android el comando upload_to_play_store:

desc "Submit a new Internal Build to Play Store"
lane :internal do |options|
 upload_to_play_store(track: 'internal', apk: options[:apkPath], version_name: '1.0', package_name: options[:packageName])
end

y en el caso de iOS upload_to_test_flight:

desc "Upload build to the testflight"
  lane :uploadToTestFlight do |options|
    upload_to_testflight(
      uses_non_exempt_encryption: false,
      distribute_external: true,
      changelog: (options[:changeLog] ? options[:changeLog]: "Nueva versión"))
  end

Ambos tienen muchos parámetros pero los principales son la ubicación del binario (en el caso de iOS lo puedes definir en el fichero Gymfile), los parámetros de las pruebas internas o externas, la información para los testers etc.

Los instrumentos de Gitlab CI

Ya hemos configurado Gitlab Runner y podemos construir nuestro proyecto desde la terminal. Es hora de hacer que nuestro proyecto se construya automáticamente después de cada cambio en git. Necesitamos un archivo de configuración .gitlab-ci.yml que debemos crear en el directorio principal de nuestro repositorio. Entonces deberíamos poner en él el siguiente contenido:

.upload-iOS-app:
  stage: publish-apps
  allow_failure: true
  tags:
    - ios
  before_script:
    - export LC_ALL=en_US.UTF-8
    - export LANG=en_US.UTF-8
  when: manual
  only:
    - master

upload-iOS-dev-app:
  extends: .upload-iOS-app
  script:
    - bundle exec fastlane ios incrementBuildNumber
    - bundle exec fastlane ios buildRelease
    - bundle exec fastlane ios uploadToTestFlight changeLog:"La versión Dev"

Este job utiliza el template .upload-iOS-app con los ajustes previos de fastlane como export LC_ALL=en_US.UTF-8 y export LANG=en_US.UTF-8 y se ejecutan los comandos de fastlane para incrementar el numero de build, compilar la versión release y subir el fichero ipa (binario) a TestFlight.

Para Android el script es diferente. Utilizamos el contenedor de Docker que ya hemos preparado antes. Tenemos que preparar las variables de GitLab para que funcione nuestro job. $signing_jks_file_hex es un hexdump de nuestro almacenamiento de claves de Android. Luego lo convertimos con el comando «echo «$signing_jks_file_hex» | xxd -r -p – > ./android/key.keystore» en el fichero inicial. Subimos la versión del código de la app utilizando la variable $CI_PIPELINE_IID (el id de nuestro pipeline). Sacamos de otra variable GitLab $google_play_api_key_json la clave de Google Play Api que se utiliza para acceder al Google Play Store. Al final, borramos los ficheros con las claves.

.upload-android-app:
  image: docker.eienergia.com/cicd/android-container:0.1.0
  stage: publish-apps
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules
    policy: pull
  allow_failure: true
  when: manual
  before_script:
    # We store this binary file in a variable as hex with this command, `xxd -p gitter-android-app.jks`
    # Then we convert the hex back to a binary file
    - echo "$signing_jks_file_hex" | xxd -r -p - > ./android/key.keystore
    # We add 1 to get this high enough above current versionCodes that are published
    - 'export VERSION_CODE=$((100 + $CI_PIPELINE_IID)) && echo $VERSION_CODE'
    - echo $google_play_api_key_json > ./fastlane/playstore_key.json
    - bundle update --all
  after_script:
    - rm ./fastlane/playstore_key.json
    - rm ./android/key.keystore
  only:
    - master

upload-android-dev-app:
  extends: .upload-android-app
  script:
    - bundle exec fastlane android buildRelease flavor:dev
    - bundle exec fastlane android internal apkPath:"./android/app/build/outputs/apk/dev/release/app-dev-release.apk" packageName:"com.app"

El job «upload-android-dev-app» lanza el comando «bundle exec fastlane android buildRelease flavor:dev» para compilar la aplicación con el flavor (un variante de compilación) «dev». Después se utiliza bundle exec fastlane android internal para subir el binario a Play Store.

Conclusiones

Hay que tener en cuenta que por defecto GitLab ofrece 2 000 minutos de integración continua gratuitos y luego si se te acabarán los minutos tienes que pagar. Aun así para los proyectos pequeños es una gran ayuda. No tienes que montar tu propio entorno de integración continua comprando y manteniendo los equipos propios. Y si ya los tienes puedes usarlos instalando GitLab Runner y gestionando todo el proceso desde la pagina web de GitLab. El equipo de GitLab promete implementar los runners compartidos con Mac OS en el futuro: GitLab issues. Su principal competidor GitHub ya tiene esta funcionalidad. Esperemos que sea pronto y podemos evitar así instalar GitLab Runner en una máquina real. Como veis montar un entorno de pruebas con GitLab CI y Fastlane es bastante sencillo y no requiere mucho tiempo.

Referencias

  1. Android Publishing with Fastlane
  2. iOS CI with fastlane

 

 

 

 

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad