En el primer artículo vimos los pasos iniciales necesarios para configurar Microsoft AppCenter y Azure DevOps para compilar y desplegar aplicaciones Flutter.
En esta segunda parte vamos a ver cómo realizar la compilación y el despliegue.
Compilar nuestra aplicación Flutter
Para crear un nuevo pipeline, accederemos a los pipelines en el menú lateral izquierdo y presionaremos el botón azul New pipeline en la parte superior derecha.
El primer paso será indicar donde está el código de nuestra aplicación:
Si lo tenemos en Azure DevOps seleccionaremos la primera opción Azure Repos Git y nos pedirá que seleccionemos el repositorio donde está nuestro código.
A continuación nos pedirá que configuremos por defecto nuestro pipeline:
En nuestro caso, seleccionaremos la opción Starter pipeline que nos creará un pipeline vacío para que empecemos a trabajar.
Podemos borrar el ejemplo que nos incluye Azure DevOps por defecto, ya que no necesitaremos nada de esto.
Creación de variables del pipeline
Antes de comenzar a escribir código, vamos a crear algunas variables dentro del propio pipeline, para poder almacenar contraseñas de forma segura. En la esquina superior derecha, al lado del botón azul Save and run, tenemos un botón gris: Variables. Al pulsarlo se abre un panel lateral donde veremos las variables ya creadas y añadir nuevas.
Al pulsar sobre New Variable o el botón +, introducimos la clave, el valor y si la variable es secreta:
De esta forma podemos configurar variables secretas, como el alias y password del keystore de Android, el password de certificado de iOS o variables no secretas como el directorio del proyecto que queremos compilar.
Para nuestro ejemplo necesitaremos definir 4 variables:
- AndroidKSAlias: La marcaremos como secreta e introduciremos el alias del keystore que subimos a los archivos seguros.
- AndroidKSPassword: La marcaremos como secreta e introduciremos la contraseña el keystore que subimos a los archivos seguros.
- IOSPassword: La marcaremos como secreta e introduciremos la contraseña del certificado que subimos a los archivos seguros.
- projectDirectory: Introduciremos el directorio raiz donde tenemos los archivos yml de nuestro proyecto Flutter.
Trigger, agente y acceso a variables
Ya podemos comenzar a escribir nuestro YAML. En primer lugar comenzaremos por definir la rama que lanzará el proceso de forma automática al registrar algún cambio con la opción trigger:
trigger: -main
Con esta opción, indicamos que queremos que se lance la compilación cada vez que se realice un cambio en la rama ***main*** de nuestro repositorio.
A continuación, vamos a indicar que queremos un agente en concreto. Como queremos compilar tanto iOS como Android, necesitaremos usar una imagen de macOS que sea la más actualizada:
pool: vmImage: 'macos-latest'
Tenemos que indicarle al pipeline que queremos que use un grupo de variables (que hemos creado anteriormente):
variables: - group: build-test-variable-group
Y ya podemos comenzar con los pasos de la compilación, indicando la instrucción steps:
steps:
Compilar nuestra aplicación
A continuación de steps empezaremos a indicar tareas con la instrucción task para cada acción que queramos que realice nuestro pipeline. La lista de tareas que vamos a realizar será:
- Instalar la versión 11 de Java (requerida por Flutter en estos momentos).
- Instalar la última versión estable de Flutter.
- Compilar el proyecto Flutter.
- Firmar el apk de Android.
- Instalar el certificado de Apple en un keychain temporal.
- Instalar el profile de aprovisionamiento de Apple.
- Archivar con xCode la aplicación de iOS.
- Copiar el IPA y APK al directorio de artefactos.
Para instalar la versión 11 de Java usaremos la tarea JavaToolInstaller@0 indicando la arquitectura, la versión y la fuente del JDK:
- task: JavaToolInstaller@0 inputs: versionSpec: '11' jdkArchitectureOption: x64 jdkSourceOption: PreInstalled
La tarea de instalar Flutter solo nos pide definir la versión, el canal y el modo:
- task: FlutterInstall@0 inputs: channel: stable version: latest mode: auto
Podemos indicar una versión concreta si no queremos siempre estar en la última o incluso cambiar el canal a dev u otro si lo necesitamos. En este ejemplo usamos siempre la última versión en el canal estable.
Ahora que ya tenemos todo lo necesario instalado, podemos compilar nuestra aplicación flutter usando la tarea FlutterBuild@0:
- task: FlutterBuild@0 inputs: target: mobile projectDirectory: $(projectDirectory) entryPoint: 'lib/main.dart' apkTargetPlatform: android-arm64 iosTargetPlatform: device iosCodesign: false
Aquí podremos definir el target de compilación entre varias opciones:
- Mobile: iOS y Android.
- Desktop: Windows, Linux y macOS.
- Web: Generar la versión web.
- All: Todas las plataformas: iOS, Android, Windows, Linux, macOS, web.
También podremos indicar una plataforma en concreto como ipa para iOS, apk o aab para Android. A continuación, indicamos el destino de compilación para iOS y Android con apkTargetPlatform y iosTargetPlatform donde decidir si queremos compilar para simuladores o dispositivos. Finalmente establecemos a false la opción iosCodesign para firmar manualmente el paquete más adelante.
Firmar nuestra aplicación Android
Una vez generados los archivos apk e ipa, podemos ir al paso de firmar ambos. Para Android vamos a usar la tarea de Azure DevOps AndroidSigning@3:
- task: AndroidSigning@3 inputs: apkFiles: '**/*.apk' apksignerKeystoreFile: 'release.keystore' apksignerKeystoreAlias: '$(AndroidKSAlias)' apksignerKeystorePassword: '$(AndroidKSPassword)' apksignerKeyPassword: '$(AndroidKSPassword)'
Buscamos el archivo apk que queremos firmar, indicamos el nombre del keystore que hemos guardado en la librería anteriormente y las variables de alias y password.
Instalar los certificados y perfiles de Apple
A continuación vamos a instalar el certificado y el perfil de firmado de iOS en el agente de Azure DevOps:
- task: InstallAppleCertificate@2 inputs: certSecureFile: release.p12 certPwd: $(IOSPassword) keychain: temp deleteCert: true - task: InstallAppleProvisioningProfile@1 inputs: provisioningProfileLocation: secureFiles provProfileSecureFile: release.mobileprovision removeProfile: true
Con la tarea InstallAppleCertificate@2 instalamos el certificado que hemos subido a la librería, indicando el password de las variables, que usaremos la keychain temporal y que al terminar se borre el certificado. Esta tarea establecerá internamente la variable de entorno $(APPLE_CERTIFICATE_SIGNING_IDENTITY) con el valor correcto al terminar.
A continuación ejecutamos la tarea InstallAppleProvisioningProfile@1 desde el archivo subido a la librería e indicando que se elimine el perfil al terminar. Esta tarea establecerá la variable de entorno $(APPLE_PROV_PROFILE_UUID) con el UUID del perfil instalado.
Compilar nuestra aplicación iOS
Ya estamos listos para compilar nuestro IPA usando la tarea xcode@5 de Azure DevOps:
- task: Xcode@5 inputs: actions: archive xcWorkspacePath: '**/Runner.xcworkspace' scheme: Runner sdk: $(sdk) configuration: $(configuration) xcodeVersion: default packageApp: true exportOptions: auto exportPath: 'output/release/iphoneos' signingOption: manual signingIdentity: $(APPLE_CERTIFICATE_SIGNING_IDENTITY) provisioningProfileUuid: $(APPLE_PROV_PROFILE_UUID) useXcpretty: false
En esta tarea indicamos la acción archive, para que genere un .IPA firmado. Como Workspace indicamos el Runner.xcworkscpace del proyecto de Flutter y le pasamos las variables de la librería $(sdk) y $(configuration) de forma que podemos controlar con estas variables que configuración y SDK queremos usar. Indicamos que queremos firmar de forma manual y los valores del certificado y perfil que deseamos usar.
En este paso, podemos tener un error de compilación indicando que no se puede firmar los PODs con el certificado que hemos indicado. Tenemos que denegar que se intenten firmar los PODs explicitamente, editando el archivo Podfile del directorio ios de Flutter, añadiendo al final lo siguiente:
post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" end end end
Con este pequeño cambio, indicamos para cada pod del proyecto que no requiere ni permite el firmado.
En este punto ya hemos compilado y firmado, tanto nuestra aplicación de iOS como de Android, y estamos listos para desplegarlas a AppCenter y distribuirlas a nuestros usuarios.
Desplegar nuestra aplicación a AppCenter.
Una vez compilada nuestra aplicación, el primer paso para distribuirla a AppCenter es copiar y publicar los archivos IPA y APK a un directorio desde el que luego podamos acceder con la tareas de distribución de AppCenter.
Para esto, en primer lugar, vamos a usar la tarea CopyFiles@2:
- task: CopyFiles@2 inputs: Contents: '**/*.ipa' targetFolder: '$(build.artifactStagingDirectory)' - task: CopyFiles@2 inputs: Contents: '**/*.apk' targetFolder: '$(build.artifactStagingDirectory)'
Ahora podemos usar esta ruta para acceder a los binarios en la tarea AppCenterDistibute@3. Necesitaremos una tarea para cada plataforma y el nombre que le dimos a la conexión de servicio que creeamos en el primer paso de la sección Preparando Azure DevOps:
- task: AppCenterDistribute@3 inputs: serverEndpoint: 'AppCenter Android Connection' appSlug: 'EQUIPO/NOMBREAPP' symbolsOption: Android appFile: '$(build.artifactStagingDirectory)/botapp/build/app/outputs/apk/$(configuration)/app-release.apk' releaseNotesOption: input releaseNotesInput: 'Pipeline automatic build' destinationType: groups - task: AppCenterDistribute@3 inputs: serverEndpoint: 'AppCenter iOS Connection' appSlug: 'EQUIPO/NOMBREAPP' symbolsOption: Apple appFile: '$(build.artifactStagingDirectory)/output/$(configuration)/$(sdk)/botapp.ipa' releaseNotesOption: input releaseNotesInput: 'Pipeline automatic build' destinationType: groups
En cada tarea, indicaremos la conexión a AppCenter que queremos usar, que es el nombre que pusimos en el paso inicial de configuración de Azure DevOps.
A continuación, idicamos el app slug, una combinación entre el nombre de organización en AppCenter y el nombre de la aplicación también en AppCenter, tal como sale en la url de la aplicación, por ejemplo:
https://appcenter.ms/users/yjulian/apps/Build-test-Android
yjulian sería el nombre de organización y Build-test-Android el nombre de la aplicación, por lo que el appSlug correcto sería yjulian/Build-test-Android.
La opción symbolsOption nos permite indicar si la simbolización de depuración es de Android o iOS (Apple).
releaseNotesOption nos permite indicar si queremos las notas de la versión manualmente en la build (input) usando la propiedad releaseNotesInput. También podemos indicarle la opción file y en la propiedad releaseNotesFile indicar un archivo donde tenemos las notas de la versión.
Por último, la propiedad destinationType, la establecemos a groups para indicar que deseamos distribuir la aplicación mediante AppCenter. Podemos indicar stores para distribuir la aplicación a las tiendas usando AppCenter como intermediario.
Si ejecutamos el pipeline, tras completar todas las tareas, tendremos nuestras aplicaciones desplegadas en AppCenter y listas para ser usadas en nuestros dispositivos de pruebas:
Próximos pasos.
Ahora que somos capaces de desplegar nuestra aplicación a AppCenter de forma automática, ¿cómo podemos ejecutar tests en dispositivos reales de forma integrada con nuestra build o test unitarios y hacer que el resultado de estos tests decida si desplegar o no las aplicaciones? Lo veremos en próximos artículos.