CI/CD for mobile app development has always been challenging due to the different machine configurations, hardware limitation and requirement, and OS environment for building the packages. Typically, for iOS app to be deployed for testing, developers have to spend about half an hour to manually prepare the “archive” IPA and upload them into TestFlight for user testing, and there is no automated ways to test their app in various iPhone and iOS versions. Similar to Android, the setup for CI/CD and builds for different app packages is not consistent & it's a constant painful process to maintain due to the platform updates and new features. With an increasing emphasis on mobile, the pressure to routinely update mobile apps & push new features means there is a need to embrace CI/CD methodologies in mobile development.
For our mobile application to compile into native APK(Android) and IPA (IOS), we need to detach it from Expo. Alternatively, Expo also provide IPA/APK building services using their exp publish
command, but we are not using that for our lab.
So our workflow is as follows:
- Detach application from Expo
- Build a Docker container image that allows us to build APK from the React Native code.
- Build out a CI/CD pipeline using AWS CodePipeline, AWS CodeCommit, AWS CodeBuild and testing on AWS Device Farm. AWS Device Farm let you run your newly compiled apk on real Android devices.
Note: We will not be building IPA for IOS in this lab.
In your docker container, quite React-Native packager (CRTL-C), but still stay within the Docker environment, run this command to detach your application from Expo:
exp detach
You need to login to your Expo account to detach.
An Expo user account is required to proceed.
? How would you like to authenticate?
Make a new Expo account
❯ Log in with an existing Expo account
Cancel
Since we are on AWS Cloud9/docker, we will skip IOS process. To proceed with Android build, we need to provide an Android package name, for example com.apk.mycoolmobileapp
. This process will update your app.json
file in the current directory.
Success. You are now logged in as asean.
[07:20:36] Making sure project is set up correctly...
\[07:20:39] Warning: Not using the Expo fork of react-native. See https://docs.expo.io/.
[07:20:47] Your project looks good!
Validating project manifest...
You have not specified a custom scheme for deep linking. A default value of expa9173199816248ed91b705996074b191 will be used. You can change this later by following the instructions in this guide: https://docs.expo.io/versions/latest/workflow/linking
Skipping iOS because you are not running macOS.
You'll need to specify an Android package name. See: https://docs.expo.io/versions/latest/guides/configuration.html#package
? What would you like your Android package name to be? com.apk.mycoolmobileapp
Moving Android project files...
Downloading Android code...
Updating Android app...
{ buildPhase: 'running shell app modifications' } 'Warning: No config file specified.'
Cleaning up Android...
Android detach is complete!
Writing ExpoKit configuration...
Installing the Expo fork of react-native...
Installing react-native@https://github.com/expo/react-native/archive/sdk-27.0.0.tar.gz using yarn...
Finished detaching your project! Look in the `android` and `ios` directories for the respective native projects. Follow the ExpoKit guide at https://docs.expo.io/versions/latest/guides/expokit.html to get your project running.
This will detach your React Native project from Expo. You will see a new android
subdirectory created for you.
- Notice that
app.json
now has a new line item:
publishManifestPath": "android/app/src/main/assets/shell-app-manifest.json"
We need to create this file under android/app/src/main/assets
directory.
# copy kernel-manifest.json as shell-app-manifest.json
cd android/app/src/main/assets
cp kernel-manifest.json shell-app-manifest.json
Before we commit our code to AWS CodeCommit, let's ommit some directories from git push
using .gitignore
.
Add .gitignore
to your ~/environment/rn
directory:
# expo
.expo/
# dependencies
/node_modules
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Your code directory should look like so now:
ls -a
. .. android App.js app.json App.test.js aws-exports.js buildspec.yml .expo .expo-source .git .gitignore native-base-theme node_modules package.json src yarn.lock
Let's create a new AWS CodeCommit Repository for our application. Run this in a AWS Cloud9 Terminal (not your docker environment):
aws codecommit create-repository --repository-name jiojiome-mobile-app
Output of the command:
{
"repositoryMetadata": {
"repositoryName": "jiojiome-mobile-app",
"cloneUrlSsh": "ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/jiojiome-mobile-app",
"lastModifiedDate": 1534061690.066,
"repositoryId": "87b338a8-0726-45e3-9ba4-6a73e4f406f1",
"cloneUrlHttp": "https://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/jiojiome-mobile-app",
"creationDate": 1534061690.066,
"Arn": "arn:aws:codecommit:ap-southeast-1:XXXXXXXXXXXX:jiojiome-mobile-app",
"accountId": "XXXXXXXXXXX"
}
}
Setup AWS Cloud9 so that you can use ssh to git push to the repository.
create ssh in iam
- IAM -> User -> Security credential -> SSH keys for AWS CodeCommit -> Upload SSH public key
In AWS Cloud9
- ssh-keygen (this creates a new ssh keypair)
- edit ssh/config
Host git-codecommit.*.amazonaws.com
User XXXXXXXXXX(from IAM)
IdentityFile ~/.ssh/id_rsa (key pair created just now)
- chmod 600 ssh/config
Execute these commands at the top level of your code directory (~/environment/rn
):
This initialize our code as a git repository and set the remote repository to the newly created CodeCommit Repository.
Run this in a AWS Cloud9 Terminal (not your docker environment) to push the code to AWS CodeCommit:
git init
git commit -m "first commit"
git remote add origin ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/jiojiome-mobile-app
git push -u origin master
Verify that your code is uploaded to AWS CodeCommit using the CodeCommit console
Let's add buildspec.yml
to the top level directory of our application (~/environment/rn
). A Build Spec is a collection of build commands and related settings, in YAML format, that AWS CodeBuild uses to run a build. You can include a build spec as part of the source code. This buildspec.yml
basically says that we will run gradlew
to compile our Android apk file. Once compiled, this apk can be sent to AWS Device Farm for device testing, or installed on your own Android device. If all the test passed, you can proceed to submit this apk to Google Play Store.
version: 0.1
phases:
build:
commands:
- cd android && ./gradlew assembleRelease
artifacts:
files:
- android/app/build/outputs/apk/app-prod-release-unsigned.apk
Let's commit this new file to CodeCommit. Execute these commands at the top level of your code directory (~/environment/rn
):
git add buildspec.yml
git commit -am "added buildspec.yml"
git push
To build our apk, we need a build environment which can compile an APK using Java8. We'll create such an environment using Docker inside AWS Cloud9 (as java8-android-localbuilder
) and upload this Docker Image to Amazon ECR. AWS CodeBuild will pull this image from ECR to build our APK.
Take this Dockerfile and build out the java8-android-localbuilder container image. Name the image as java8-android-localbuilder
.
docker build -t java8-android-localbuilder .
Execute these commands at the top level of your code directory (~/environment/rn
):
# Login to Amazon ECR
`aws ecr get-login --no-include-email`
#Create a new ECR Repository
aws ecr create-repository --repository-name java8-android-builder
You'll see:
{
"repository": {
"registryId": "XXXXXXXXXXX",
"repositoryName": "java8-android-builder",
"repositoryArn": "arn:aws:ecr:ap-southeast-1:XXXXXXXXXXX:repository/java8-android-builder",
"createdAt": 1534061289.0,
"repositoryUri": "XXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/java8-android-builder"
}
}
Replace XXXXXXXXXXX
with your own 12-digit AWS Account Number.
docker tag java8-android-localbuilder XXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/java8-android-builder
docker push XXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/java8-android-builder
AWS Codebuild Service need the permission to pull this specific image from Amaozn ECR. This is a bonus lab!
CodeBuild->java8-android-builder->Permissions->
- Goto AWS CodePipeline Console
- Click "Create pipeline"
- Name the new pipeline "my mobile pipeline" and click "Next Step"
- Select Source Provider as
AWS CodeCommit
, Select the CodeCommit Repository and set the Branch name tomaster
. Click "Next Step" - Select "AWS Codebuild" as the Build provider, click on "Create a new build project"
- Enter Project Name as "Mobile Codebuild for apk"
- Select "Specify a Docker image", Environment Type =
Linux
, Custom image type =ECR
, ECR Repository =java8-android-builder
, image =latest
. 8 Set build specification asUse the buildspec.yml in the source code root directory
9 Under Advanced section, selectCompute type
as15GB memory, 8vCPU
and click "Save build project". - Select "No Deployment" and click "Next Step"
- In
AWS Service Role
page, click "Create roleand then "Next Step
- Click "Create pipeline"
You can now trigger a pipeline build by commit to the source code. eg: change a line of code and git commit to AWS CodeCommit.
You can now integrate Device Farm as a test target inside AWS CodePipeline. Reference Blog post here