Currently there is a high market demand for the creation of mobile solutions to meet customer needs, and for that frameworks and tools like Ionic, React Native and Flutter provide good solutions and facilities to build and deploy our Apps adding store facilities like Play Store and App Store (not so easy) which offers us good platforms and APIs to make the job of deploying, testing and monitoring easier. However if you are working or managing large teams and your App does a lot of builds and deploys, using only these approaches may not be efficient and quite problematic. With this in mind my goal in this article is to demonstrate the complete creation of CI/CD pipelines to test, build and deploy an Ionic hybrid application using Azure Pipelines, Github and Visual Studio App Center that can distribute the apk to Play Store.
Preparation and required setup
For the steps that will be shown here, you need the following prerequisite:
Note: I'm using a docker image created by beevelop and you can be found here, this will simplify our setup because the image already contains the essencial tools like nodejs, android build tools, cordova, gradle etc..., but isn't necessary in this case if you don't want to.
Tools
- The latest version of node
- Ionic CLI, you can install running
# npm install -g @ionic/cli
- Cordova to build the application for the android platform (generating the apk), you can install with this command
# npm install -g cordova
- Android Studio, which can be downloaded here
If you don't want to install the full android studio, you can just download the Command line tools which can be found on the same Android Studio Download page, and configure in the PATH, see the example.
- Gradle, which can be installed following these steps.
This tool need jdk or jre installed as well, all the necessary information is present in the official documentation, i really recommend that you use a docker image that can be found here. On the other hand you need configure in PATH, see the example.
- An text editor i used Visual Studio Code , which can be downloaded here
Note: These tools like Cordova, Android Studio, Android Sdk Tools and gradle are optional to making local tests, basically all of these tools are available on Hosted Microsoft agents that we are going to use.
Accounts
- Github account to maintain our repo, you can create here
- An Azure DevOps account and an organization created, more information can be found here > Note: it is also possible use github account to make login on Azure DevOps.
- Visual Studio App Center account to deploy the App and enable distribution to Play Store, you can create here
Creating an ionic project
First let's create the App using ionic cli
to start a simple app with Tabs, so execute ionic start SampleApp tabs --type=ionic-angular --cordova
, this makes it explicit that we want to use Angular and cordova, after that you can navigate to the app folder using cd SampleApp
, the structure should look like this:
At this moment the App was successfully created, now the next step is to test the App locally in the browser to see what the initial structure looks like to do this run ionic serve
this command automatically open the App (simulated) in the browser, it is also possible to choose the port, browser and other options see more running ionic serve --help
. After these steps you can change the App as you wish to differentiate from the template created automatically by ionic.
Note: It is also possible to use a docker container to create the initial structure of the App (e.g.
docker run -it --rm -v $($PWD):/usr/app beevelop/ionic:latest ionic start SampleApp tabs --type=ionic-angular --cordova
) note that in this case the variable $PWD is used, this variable contains the name of current working directory.
Finally you can modify the App as you wish, to do this use Visual Studio Code or any text editor of your choice.
Creating a repository on github and pushing the App code
To create a repo to the App on Github, first log into your account and select New repository , fill in the fields then choose the one of these two options:
- Public (Anyone can see this repository. You choose who can commit)
- Private (You choose who can see and commit to this repository)
Keep the Initialize this repository with a README option unchecked because we're importing an existing repository, if you filled everything in correctly, the fields should look like these:
Go back to the terminal and navigate to the App code and run these commands:
git remote add origin https://github.com/<github_user>/<project_name>.git
git push -u origin master
Now you have a repo created and the code inside it, in the next steps we will talk a little bit about Azure DevOps and how to create and configure pipelines.
Creating a project in Azure DevOps
Azure DevOps is a powerful Service (or on-premise) that offers support to teams plan work, collaborate with code development, build, deploy, tests and create and manage many different kinds of integrations to Azure or third-party services by default, such as Jenkins, Chef, Nuget and others. To learn more access the official microsoft documentation to see all possible capabilities.
To create a project on Azure DevOps, make log in with your microsoft account (or github account) and follow these steps:
- Select New project
Just fill in the Project name field and click on New project , you can check the project created to this article here.
Creating build and deploy pipelines
To manage pipelines Azure DevOps provides the Azure Pipelines that can be used to automatically build, tests and deploys supporting any language or project types, in our case we will use to create three pipelines to build, test and deploy our App.
Note: Don't necessarily have to be three pipelines, this separation is only logical
Azure DevOps has a division between Build and Release Pipeline, the first one is generally used to generate one or more artifacts using the source code of the project and the second one is more common used to deploy this artifacts generated. The two next topics explain how to create two Build Pipelines and one Release Pipeline to deploy the App.
Build pipelines
Then to create the first pipeline that is responsible for building (Angular part) and testing the App, follow these steps:
- Click on Pipelines
- Select New pipeline or Create Pipeline
- Select Github as the source code option
You can choose the User the classic editor option if you want a GUID to help while creating the pipeline
- Select the repository created for your App
At this point Azure DevOps will redirect you to the github page for you accept and install Azure Pipelines app.
- Select approve and install
If everything is ok, some templates will appear to choose one, let's start from scratch.
- Select Starter Pipeline.
Finally you can see the minimum pipeline structure, delete all lines and put the following:
# Disables CI builds entirely, then commits don't trigger a build
trigger: none
# Activates pull request trigger, so any pull request to the master trigger a build
pr:
- master
# Specifies which pool (Hosted or Self-hosted) to use for this pipeline.
# In this case it is in the scope of the pipeline, but you can use it at the stage or job level
pool:
vmImage: 'ubuntu-latest'
# Specific variables to use, in our case it is just one and at the pipeline level
variables:
chromeDriverVersion: '80.0.3987.106'
# Specifies a linear sequence of operations that make up a job
# We need only one job, so we can use the simplified structure
steps:
# Tasks are the building blocks of a pipeline
# We can choose from a catalog of tasks available by default or download from the Azure DevOps marketplace
# In this case I chose to use 6 tasks (not mandatory) to install dependencies and build the App.
- task: Npm@1 # Run npm install
displayName: 'npm install'
inputs:
command: install
workingDir: '$(System.DefaultWorkingDirectory)'
- task: Npm@1 # Run npm run build
displayName: 'npm run build'
inputs:
command: custom
customCommand: run build
- script: | # Run unit tests
npx --no-install ng test --watch=false --reporters=progress,junit
displayName: 'ng test'
# This task download the chromedriver binary, required for e2e tests
# In this case I installed a specific version of chromedriver to avoid errors with the version of Chrome in the hosted agent microsoft, see the troubleshooting topic.
- script: |
node node_modules/.bin/webdriver-manager update --versions.chrome=$(chromeDriverVersion) --gecko false --standalone false
displayName: 'changing version of chromedriver to $(chromeDriverVersion)'
- script: | # Run e2e tests without update webdriver
npx --no-install ng e2e --webdriverUpdate=false
displayName: 'ng e2e tests'
- task: PublishTestResults@2 # Publish tests results
displayName: 'Publishing test results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/TESTS-*.xml'
- publish: 'www/' # Publish App artifact
artifact: 'www'
displayName: 'Publishing Artifact'
- Click on Save and Run to test the pipeline
You can now rename this pipeline to App Build CI by clicking Edit → Three dots → Triggers → YAML and editing the Name field.
This build specification is responsible to install, build and test the App, this pipeline generates the www
artifact whenever it is successfully executed, you can see a example of the artifact generated here, this artifact includes assets, styles and scripts that have been compiled and will be used to build the .apk file in the next step.
Finally, to generate the .apk file, it is necessary to create another pipeline with the name Android App CI, and put the following content:
# Enable CI builds, trigger a build whenever the master branch receive new modifications
trigger: ['master']
pr: none # Disable PR builds entirely
pool:
vmImage: 'ubuntu-latest'
steps:
# This task trigger a new build to run, this build is the same as the step above that generates the bundle
- task: TriggerBuild@3
displayName: 'trigger App Build CI'
inputs:
definitionIsInCurrentTeamProject: true
buildDefinition: 'App Build CI'
queueBuildForUserThatTriggeredBuild: true
ignoreSslCertificateErrors: true
useSameSourceVersion: false
useCustomSourceVersion: false
useSameBranch: true
waitForQueuedBuildsToFinish: true
waitForQueuedBuildsToFinishRefreshTime: '10'
failTaskIfBuildsNotSuccessful: true
cancelBuildsIfAnyFails: false
treatPartiallySucceededBuildAsSuccessful: false
downloadBuildArtifacts: false
storeInEnvironmentVariable: true
authenticationMethod: 'OAuth Token'
password: '$(System.AccessToken)' # Necessary allow permission of Queue builds to <Project> Build Service, See the troubleshooting topic
enableBuildInQueueCondition: false
dependentOnSuccessfulBuildCondition: false
dependentOnFailedBuildCondition: false
checkbuildsoncurrentbranch: false
failTaskIfConditionsAreNotFulfilled: false
# Download the artifact of the build triggered above
- task: DownloadPipelineArtifact@2
displayName: 'download artifact App Build CI'
inputs:
buildType: 'specific'
project: 'Ionic App'
definition: 'App Build CI'
specificBuildWithTriggering: true
buildVersionToDownload: 'specific'
runId: '$(TriggeredBuildIds)'
targetPath: '$(Build.BinariesDirectory)'
# Moves the www folder containing bundle to the Default Working Directory path (e.g. /agent/_work/1/s)
- bash: |
mv $(Build.BinariesDirectory)/www $(System.DefaultWorkingDirectory)
displayName: 'mv $(Build.BinariesDirectory)/www $(System.DefaultWorkingDirectory)'
- bash: | # Installing cordova
sudo npm i -g cordova
displayName: 'npm i -g cordova@latest'
- bash: | # Build android App using cordova
npx ionic cordova build android --no-build --release
displayName: 'ionic cordova build android --no-build --release'
# Needed to solve missing dependency problem, see the troubleshooting topic
- bash: |
sudo apt-get install lib32z1
displayName: 'sudo apt-get install lib32z1'
# Signin the .apk file with the specified keystore
- task: AndroidSigning@3
displayName: 'android signing'
inputs:
apkFiles: '**/outputs/apk/release/app*.apk'
apksignerKeystoreFile: 'sampleapp.keystore'
# This password is placed in Variables inside the pipeline and the option to keep the value secret is checked.
apksignerKeystorePassword: '$(password_keystore)'
apksignerKeystoreAlias: 'sampleapp'
- task: CopyFiles@2 # Copies all .apk files to publish
displayName: 'copy **/outputs/apk/release/app*.apk to $(Build.BinariesDirectory)'
inputs:
SourceFolder: '$(System.DefaultWorkingDirectory)'
Contents: '**/outputs/apk/release/app*.apk'
CleanTargetFolder: true
TargetFolder: '$(Build.BinariesDirectory)'
flattenFolders: true
- publish: '$(Build.BinariesDirectory)' # Publish artifact
artifact: 'android-app'
displayName: 'publish artifact'
Before running this pipeline it is necessary to generate the keystore file on your local machine using the following command that comes with the JDK:
$ keytool -genkey -v -keystore sampleapp.keystore -alias sampleapp -keyalg RSA -keysize 2048 -validity 10000
Then put in Library → Secure File which can be found in the Pipelines menu.
Now you can run and see the result, whenever you run the Android App CI pipeline, another App Build CI will triggered and the generated artifact will be downloaded to generate the .apk
file. You can find more information about yaml structure in the official Microsoft YAML reference..
Release pipeline
The last pipeline is responsible for deploying the App to the Visual Studio App Center, So to do that you need to first create an app in the App Center and then a new release pipeline in Azure DevOps.
Creating an App in Visual Studio App Center
Access your account at the App Center and click on Add new → Add new app.
- Make sure you are checking the following options:
- Release Type Alpha
- OS Android
- Platform Java/Kotlin
- After that you need your App Center API key. To get it navigate to your Account Settings
- Edit User API Tokens
- Select New API Token
- Make sure you are selecting the Full Access option
- Copy and save the generated API token
You'll need to use this API token to configure the automatic deployment of the App.
Creating a Release pipeline
Log in to Azure DevOps and navigate to Pipelines → Releases
- Select New
- Select New release pipeline
The pipeline configuration should appear to add stages and artifacts.
First you need to add an artifact to the pipeline, click Add an artifact and select Android App CI as Source (build pipeline)
Add a new stage by clicking on Add a stage box and select Empty Job because it is not necessary to use any templates, next change the stage name to Deploy Alpha for example, now it is necessary to add the deploy task for this click on Tasks
- Click the plus icon to add a new task
- Search for Visual Studio App Center in the task catalog
- Add the App Center distribute task to the job
After that for everything to work well you must fill in the fields of the task, below i'll explain how to fill each of them.
- App Center service connection
Select the service connection to Visual Studio App Center, in this case you need to create one first by following these steps: Enter Manage, select New service connection → Visual Studio App Center → Next and fill in the API Token with the token generated in Visual Studio App Center, Lastly give a name to connection and go back to the task and select the created connection.
- App slug
To find the slug app go to Visual Studio App Center and click on the App and extract from the URL (e.g. https://appcenter.ms/users/carlosggflor-gmail.com/apps/SampleApp
→ carlosggflor-gmail.com/SampleApp
)
- Binary file path
Relative path from the repo root to the APK/AAB or IPA file you want to publish, in this case you need to select the artifact (.apk file) path generated by running Android App CI build (e.g. $(System.DefaultWorkingDirectory)/_Android App CI/android-app/app-release-unsigned.apk
).
- Release notes
Release notes will be attached to the release and shown to testers on the installation page.
- Release destination
This field is very important because it is where you decide the destination that the release will be distributed, in the case if Groups is selected you can deploy it to one or more groups using commas or semicolons to separate multiple IDs, you can create these groups in the Visual Studio App Center navigating to your App → Distribute → Groups
To get the ID, just select the group created and click on the settings icon
In case of you leave the Destination ID field empty, then the default group (Collaborators) is chosen.
Otherwise, if you select Store you need to enter the ID of distribution store to deploy, in our case we didn't create the store connection (e.g. Google play or Intune Company Portal), but if you want to create enter Visual Studio App Center navigating to your App → Distribute → Stores → Connect to store and follow the steps.
Finally you can rename your pipeline and run clicking on Create release → Create.
After the deployment as finished you can see the release in the Visual Studio App Center → Distribute → Releases, the image below mark the main information about this page.
1 - The App version, you can change that version by editing the config.xml
file in the source code.
2 - The destination chosen in the deployment task for the App Center.
From here you can distribute the same release to other groups and stores (Google Play or Intune), set up testers and generate links to download the App.
Lastly, you need to configure Continuous deployment in the pipeline for create a release whenever build Android App CI is finished, to do this click on lightning icon and enable the Continuous deployment trigger option.
Conclusion
We created an hybrid application using ionic with cordova and Azure DevOps to integrated with Github to keep the code and created CI/CD pipelines to test, build and deploy our App in the Visual Studio App Center. We can change any part related to Azure DevOps like the CI/CD tool and still use it as an intermediary. We went through a detailed explanation of each step of the build and the deployment stage.
Source code and pipelines
SampleApp on Github. https://github.com/carlosgit2016/SampleApp
Ionic App on Azure DevOps. https://dev.azure.com/gabrielggff25/Ionic%20App
Extras
Configuring Android SDK tools in PATH
I didn't find a simple example of this, so if you don't want to install Android Studio or are using Linux and need to configure the tools in PATH, see below
Linux
# i downloaded the command line tools in /tmp/ and unzipped it in /opt/android/tools
# use any editor to edit the /etc/profile
sudo vi /etc/profile
cat /etc/profile
# put the PATH variable at the end of the file
# should look like this
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
if [ "${PS1-}" ]; then
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "`id -u`" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
PATH=$PATH:/opt/android/tools/bin
Windows
- Open control panel
- Look for Edit the system environment variables
- Select Environment Variables
- Two click on Path in System Variables
- Click New and put the path where you unzip the file (e.g.
C:\Temp\tools\bin
)
Troubleshooting
Without permission to trigger another build in the Android App CI pipeline.
Error:
Error message: Error: TF215106: Access denied. Ionic App Build Service (gabrielggff25) needs Queue builds permissions for build pipeline 20:App Build CI in team project Ionic App to perform the action. For more information, contact the Azure DevOps administrator.
Solution:
Allow Queue builds permission to user Project Build Service, see the image below.
Problem with dependency missing during the execution of pipeline Android App CI build
The problem occurs during the build of the App for android, see Error while loading shared libraries
Problem with chromedriver version on agent
Maybe the current version of chromedriver is new to Google Chrome installed on the agent, see installed softwares on ubuntu machines
References
Deploy Azure DevOps Builds with App Center
Ionic Framework Getting Started
Top comments (7)
Ok, another trap for young players - if you ONLY have 1 DevOps Build Pool/agent then your Trigger Build Task will queue until it hits 1 hour (for Azure Hosted one) and time out.
I created a 2nd build machine so that 2 tasks could run in parallel.
Thanks Rodney.
That is a good point in case of using Self Hosted agents instead of Microsoft hosted agents
Hi Carlos, this is a brilliant, useful article - FYI you have to install the TriggerBuild task from DevOps marketplace - marketplace.visualstudio.com/items...
Nice catch. I forgot to mention that
Great article, thank you! How does this process look when building iOS? Are there any particular resources to check out and/or pitfalls to watch out for?
Hey Stefan. Thank you.
For iOS some things will need to change. But in fact you just need to touch in the second pipeline "Android App CI", the signing method need to be changed and the command used for build to android too.
For publishing you can't use the Visual Studio App Center I think. You'll need configure a agent with the XCode.
Hi Caros, what you mean signing method need to be changed?
Furthermore, build for android also, does the workflow or tooling chance? Or you just tell Stefan to look up all newbie issue?