Cordova CI\CD with Bitbucket (Android)

Elihai David Vanunu
6 min readApr 3, 2023

Imagine you have a beautiful website and you want to wrap it with a mobile app using Cordova since you already know JavaScript and NPM. But, when you try to execute it, you encounter many issues like installing the correct version of JAVA, Android SDK, Emulator, and Gradle. Building and deploying Cordova APK/AAB is a complex process that combines many layers of programs and different languages, which can lead to many problems.

To automate the build and deployment process of your app, you can use Bitbucket pipelines, which will allow your web developers to build the website and push the changes to git, while the app is deployed automatically.

Build an AAB file

Create a file called bitbucket-pipelines.yml with an empty pipeline on the master branch.

pipelines:
branches:
master:
- step:
name: build

You can use a Docker image that already has the required dependencies:

and the first step is to install Git:

pipelines:
branches:
master:
- step:
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
-

Then run npm install to install our node packages, and add the android platform. If you pushed it to Git, there is no need to add it again. To build, we need to build APK and then the bundle.

pipelines:
branches:
master:
- step:
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
- npm install
- cordova platform add android --confirm --no-interactive --noresources
- cordova build android --release --no-interactive --prod --device
- cd platforms/android && ./gradlew bundle

To extract the bundle after the build, you need to use the artifacts clause:

pipelines:
branches:
master:
- step:
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
- npm install
- cordova platform add android --confirm --no-interactive --noresources
- cd platforms/android && ./gradlew bundle
artifacts:
download: true
paths:
- platforms/android/app/build/outputs/bundle/release/app-release.aab

2. Upload file to Downloads section

If the build was successful, the AAB file is generated, but it needs to be downloaded. Use a ready pipeline called atlassian/bitbucket-upload-file, and declare the environment variables: BITBUCKET_USERNAME as your git username and BITBUCKET_APP_PASSWORD as the app password you created. You can declare the variables in repository-variables under Repository settings

 - step:
name: 'Deploy the output into the download section'
script:
- pipe: atlassian/bitbucket-upload-file:0.3.2
variables:
BITBUCKET_USERNAME: $BITBUCKET_USERNAME
BITBUCKET_APP_PASSWORD: $BITBUCKET_APP_PASSWORD
FILENAME: "platforms/android/app/build/outputs/bundle/release/app-release.aab"

After a successful run, you can enter the Downloads section in Git and see the file ready to download.

3. Sign the AAB file

To upload the file to the play store, it must be signed. Create a JKS file and a file called build.json in the root directory that looks like this:

{
"android": {
"debug": {
"keystore": "",
"storePassword": "",
"alias": "",
"password": "",
"keystoreType": ""
},
"release": {
"keystore": "key.jks",
"storePassword": "~PASSWORD~",
"alias": "key0",
"password": "~PASSWORD~",
"keystoreType": ""
}
}
}

Replace key.jks with the path of the key in the project. You can encode it via base64 in the environment variables, as shown below. Save the password in the environment variables under the KEY_PASSWORD key. Now you can replace the ~PASSWORD~ placeholder with the real password using the sed command:


- step:
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
- npm install
- sed -i "s/~PASSWORD~/$KEY_PASSWORD/g" build.json
- cordova platform add android --confirm --no-interactive --noresources
- cd platforms/android && ./gradlew bundle

Now Cordova will take the JKS and sign the file.

4. Update the version number

To upload a new version to the play store, you need to update the version number in each upload. Use the sed command to update the number in the config.xml file. Suppose we have a config.xml file like this: android-versionCode="10000". This script will update the last two numbers:

- step:
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
- npm install
- sed -i "s/~PASSWORD~/$KEY_PASSWORD/g" build.json
- STORE_BUILD_NUMBER=$((20 + $BITBUCKET_BUILD_NUMBER))
- sed -i -E "s/android-versionCode=\"10000/android-versionCode=\"100$STORE_BUILD_NUMBER/g" config.xml
- cordova platform add android --confirm --no-interactive --noresources
- cd platforms/android && ./gradlew bundle

The BITBUCKET_BUILD_NUMBER is pre-declared by Bitbucket and increases monotonically. You can add any number you want to it.

Publish Your Package to Google Play Store

To publish new versions automatically, you need to have owner permissions and enable API uploads via console. Additionally, you must create a GCP project, a service account, and give it permissions to upload new versions. You can find a helpful guide here: https://docs.codemagic.io/yaml-publishing/google-play/. In our case, you need to download the P12 file instead of the JSON format in step 7.

After downloading the P12 file, it’s not a good idea to push it to Git. Instead, you can save it in environment variables. To do this, encode it to base64 with a shell or an online converter.

cat pc-api-xxxxxxxxxx-xxx-xxxxxxx.p12 | base64 -w 0

Then, copy the base64 string and save it in environment variables with the GCP_KEY key.

Next, we’ll use this Python script to upload: https://gist.github.com/elihaidv/b4a38e22f8d7c78d56ac8de22a9f843a

Here are the steps for using the script:

  1. Download the Python script.
  2. Encode the P12 key and save it to a local file.
  3. Install the required Python packages.
  4. Execute the script and publish the app.
 - step:
image: python:3.7-slim
name: 'Deploy the output to Google Play'
script:
- wget https://gist.githubusercontent.com/elihaidv/b4a38e22f8d7c78d56ac8de22a9f843a/raw/basic_upload_apks_service_account.py
- echo $GCP_KEY | base64 --decode > key.p12
- pip install --no-cache-dir google-api-python-client oauth2client pyOpenSSL
- python production basic_upload_apks_service_account.py <your.package.name> platforms/android/app/build/outputs/bundle/release/app-release.aab <yourserviceaccount@pc-api-xxx-xx.iam.gserviceaccount.com>

In the last line of the script, replace <your.package.name> with your package name, and <yourserviceaccount@pc-api-xxx-xx.iam.gserviceaccount.com> with the name of the service account you created.

You can also change the track from production to alpha, beta, rollout, or another track depending on the branch. For example, use master for production and dev for alpha. Take a look in the complete file at the end of the guide.

6. Update Java and Build Tools

You may encounter problems with incompatible versions, so you can download a newer Java or another build tool. For example:

- yes | sdkmanager --sdk_root=/opt/android "build-tools;32.0.0"
- apt-get update && apt-get install -y openjdk-11-jdk
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

7. Summary

Building and releasing a Cordova app can be a painful process. However, now you can simply upload new versions without building them locally. Just change your web files, and a new version will be available to your users.

We covered only the Android process, but you can also automate the iOS process. Consider using Codemagic. Maybe I will write another guide in the future.

Here is the complete YML file, with multiple branches:

definitions:
steps:
- step: &build
name: build
image: beevelop/cordova:latest
script:
- apt-get update && apt-get install -y unzip git git-core libc-client-dev libkrb5-dev
- npm install
- yes | sdkmanager --sdk_root=/opt/android "build-tools;32.0.0"
- apt-get update && apt-get install -y openjdk-11-jdk
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- sed -i "s/~PASSWORD~/$KEY_PASSWORD/g" build.json
- STORE_BUILD_NUMBER=$((20 + $BITBUCKET_BUILD_NUMBER))
- sed -i -E "s/android-versionCode=\"10000/android-versionCode=\"100$STORE_BUILD_NUMBER/g" config.xml
- cordova platform add android --confirm --no-interactive --noresources
- cd platforms/android && ./gradlew bundle
artifacts:
download: true
paths:
- platforms/android/app/build/outputs/bundle/release/app-release.aab

- step: &deployDownloads
name: 'Deploy the output to the download section'
script:
- pipe: atlassian/bitbucket-upload-file:0.3.2
variables:
BITBUCKET_USERNAME: $BITBUCKET_USERNAME
BITBUCKET_APP_PASSWORD: $BITBUCKET_APP_PASSWORD
FILENAME: "platforms/android/app/build/outputs/bundle/release/app-release.aab"

- step: &deployGooglePlay
image: python:3.7-slim
name: 'Deploy the output to Google Play'
script:
- source set_env.sh
- wget https://gist.githubusercontent.com/elihaidv/b4a38e22f8d7c78d56ac8de22a9f843a/raw/basic_upload_apks_service_account.py
- echo $GCP_KEY | base64 --decode > key.p12
- pip install --no-cache-dir google-api-python-client oauth2client pyOpenSSL
- python basic_upload_apks_service_account.py $TRACK <your.package.name> platforms/android/app/build/outputs/bundle/release/app-release.aab <yourserviceaccount@pc-api-xxx-xx.iam.gserviceaccount.com>

pipelines:
branches:
master:
- step:
name: 'Production build'
script:
- echo "export TRACK=production" >> set_env.sh
artifacts:
- set_env.sh
- step: *build
- step: *deployDownloads
- step: *deployGooglePlay
beta:
- step:
name: 'Beta build'
script:
- echo "export TRACK=beta" >> set_env.sh
artifacts:
- set_env.sh
- step: *build
- step: *deployDownloads
- step: *deployGooglePlay

--

--