- Introducción
- Crear el contenedor docker para compilar Android
- Usar GitLab Runner para compilar iOS
- El uso de fastlane
- Los instrumentos de GitLab CI
- Conclusiones
- 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