How to use Bitrise CI for React Native apps

Issue #277

Original post https://codeburst.io/using-bitrise-ci-for-react-native-apps-b9e7b2722fe5


After trying Travis, CircleCI and BuddyBuild, I now choose Bitrise for my mobile applications. The many cool steps and workflows make Bitrise an ideal CI to try. Like any other CIs, the learning steps and configurations can be intimidating at first. Things that work locally can fail on CI, and how to send things that are marked as git ignore to be used in CI builds are popular issues.

In this post I will show how to deploy React Native apps for both iOS and Android. The key thing to remember is to have the same setup on CI as you have locally. They are to ensure exact same versions for packages and tools, to be aware of file patterns in gitignore as those changes won’t be there on CI, and how to property use environment variables and secured files.

This post tried to be intuitive with lots of screenshots. Hope you learn something and avoid the wasted hours that I have been too.

Support for React Native

In its simplest sense, React Native is just Javascript code with native iOS and Android projects, and Bitrise has a very good support for React Native. It scans ios and android folder, and give suggestion about scheme and build variants to build.

Another good thing about Bitrise is its various useful steps and workflows. Unlike other CIs where we have to spend days on how to property edit the configuration file, Bitrise has a pretty good UI to add and edit steps. Steps are just custom scripts, and we can write pretty much what we like. Most of the predefined steps are open source. Here are a few

Building for iOS

Unless you use the step Build with Simulator , you will need provisioning profile and certificates with private keys in order to build for devices.

Install React Native & npm install

React Native moves fast and break things. If you don’t have same version of React Native, you will have hard time figuring out why the builds constantly fail on CI.

Currently I use React Native 0.57.0, so I enter install react-native@0.57.0 to let Bitrise install the same version. But normally you just need to npm install as versions should be explicitly defined in package.json file.

Also, we need to make sure we have the same version of react-native-cli with Install React Native step

Install CocoaPods

It’s safe to have the same CocoaPods version. For Xcode 10, we need at least CocoaPods 1.6.0.beta-1 to avoid the bug Skipping code signing because the target does not have an Info.plist file. Note that our Xcode project is inside ios folder, so we specify ./ios/Podfile for Podfile path

Code signing

Under Workflow -> Code Signing we can upload provisioning profiles and certificates.

Then we need to use Certificate and profile install step to make use of the profiles and certificates we uploaded.

iOS Auto Provision

Another option is to use the iOS Auto Provision step, but it requires that we need to have an account in team that has connected Apple Developer Account. This way Bitrise can autogenerate profiles for us.

Sometimes you need to set Should the step try to generate Provisioning Profiles even if Xcode managed signing is enabled in the Xcode project? to yes

Under Xcode Archive & Export for iOS -> Debug -> Additional options for xcodebuild call We need to pass -allowProvisioningUpdates to make auto provisioning profile update happen.

Recreate user scheme

Bitrise can autogenerate schemes with the Recreate user scheme step, but it’s good to mark our scheme as Shared in Xcode. This ensures consistence between local and CI builds.

Archive

To archive project, we need to add Xcode Archive & Export . For React Native, iOS project is inside ios folder, so we need to specify ./ios/MyApp.xcworkspace for Project path. Note that I use xcworkspace because I have CocoaPods

Right now, as of React Native 0.57.0, it has problem running with the new build system in Xcode 10, so the quick fix is to use legacy build system. Under Xcode Archive & Export step there are fields to add additional options for xcodebuild call. Enter -UseModernBuildSystem=NO

You can read more Build System Release Notes for Xcode 10

Xcode 10 uses a new build system. The new build system provides improved reliability and build performance, and it catches project configuration problems that the legacy build system does not.

Building for Android

For Android, the most troublesome task is to give keystore files to Bitrise as it is something we would keep privately from GitHub. Bitrise allows us to upload keystore file, but we also need to specify key alias and key password. One solution is to use Secrets and Environment variables

But as we often use gradle.properties to specify custom variables for Gradle, it’s convenient to upload this file. Under Workflow -> Code Signing is where we can upload keystore, as well as secured files

The generated URL variables are path to our uploaded files. We can use curl to download them. Bitrise has also Generic File Storage step to download all secured files.

The downloaded files is located at GENERIC_FILE_STORAGE , so we add another Custom Script step to copy those files into ./android folder

#!/usr/bin/env bash

cp $GENERIC_FILE_STORAGE/gradle.properties ./android/gradle.properties
cp $GENERIC_FILE_STORAGE/my_app_android.keystore ./android/my_app_android.keystore

Note that the name of the files are the same of when we uploaded, and we use cp command to copy to folders.

Android Build

By default, Bitrise has 2 workflows: primary for quick start, and deploy for archiving and deploying. In the deploy workflow there is Android Build step. We can overwrite module and build variant here.

If all go well

In Bitrise we can mark a step to continue regardless of the previous step. I use this to make sure iOS build independently from Android

If both iOS and Android build are successful, we should see the below summary

Although Bitrise has a good UI to configure workflows and steps, we have fine grained control every parameters in the bitrise.yml file. It’s good to reason here as we get familiar with all the steps.

Where to go from here

I hope you learn something and have a happy deploy. Since you are here, below articles may be of your interest

Using CircleCI 2.0

Issue #158

We ‘ve been using CircleCI for many of our open source projects. Since the end of last year 2017, version 2.0 began to come out, and we think it’s good time to try it now together with Swift 4.1 and Xcode 9.3

The problem with version 2.0 is it’s so powerful and has lots of cool new features like jobs and workflows, but that requires going to documentation for how to migrate configuration file, especially Search and Replace Deprecated 2.0 Keys

Creating config.yml

The first thing is to create a new config.yml inside folder .circleci

Copy your existing circle.yml file into a new directory called .circleci at the root of your project repository.

Next is to declare version and jobs

Add version: 2 to the top of the .circleci/config.yml file.

Checking xcodebuild

For simple cases, we just use xcodebuild to build and test the project, so it’s good to try it locally to avoid lots of trial commits to trigger CircleCI. You can take a look at this PR https://github.com/hyperoslo/Cheers/pull/20

Before our configuration file for version 1.0 looks like this

1
- set -o pipefail && xcodebuild -project Cheers.xcodeproj -scheme "Cheers-iOS" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.0' -enableCodeCoverage YES test

Now we should put pipefail inside shell, follow https://github.com/CircleCI-Public/circleci-demo-ios/blob/master/.circleci/config.yml

shell: /bin/bash –login -o pipefail

Now is the actual trying xcodebuild, after many failures due to destination param

1
2
3
4
5
6
xcodebuild: error: Unable to find a destination matching the provided destination specifier:
{ platform:iOS Simulator, OS:11.3 }

Missing required device specifier option.
The device type “iOS Simulator” requires that either “name” or “id” be specified.
Please supply either “name” or “id”.
1
xcodebuild: error: option 'Destination' requires at least one parameter of the form 'key=value'

I found this to work, run this in the same folder as your xcodeproj

1
xcodebuild -project Cheers.xcodeproj -scheme "Cheers-iOS" -sdk iphonesimulator -destination "platform=iOS Simulator,OS=11.3,name=iPhone X" -enableCodeCoverage YES test

Adding workflow

Version 2.0 introduces workflow which helps organising jobs

A workflow is a set of rules for defining a collection of jobs and their run order. Workflows support complex job orchestration using a simple set of configuration keys to help you resolve failures sooner.

For our simple use cases, we add this workflow

1
2
3
4
5
workflows:
version: 2
build-and-test:
jobs:
- build-and-test

Collecting Test Metadata

CircleCI collects test metadata from XML files and uses it to provide insights into your job

Final

Use below as template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: 2.1
jobs:
build_test:
macos:
xcode: "11.0"
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
command: |
curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
pod install
- run:
command: xcodebuild -workspace MyApp.xcworkspace -scheme "MyApp" -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone X,OS=12.2" -enableCodeCoverage YES test
- store_test_results:
path: test-results

workflows:
version: 2.1
primary:
jobs:
- build_test

Read more

Dealing with updated pod in BuddyBuild

Issue #149

We’re using BuddyBuild as our CI. Today one of our dependencies gets a sweat update https://github.com/hyperoslo/BarcodeScanner/releases/tag/4.1.1. So we pod update BarcodeScanner in one of our projects that depends on it. All is fine when running locally. So I make a Pull Request and wait for the build to kick off in BuddyBuild.

For some reason, BuddyBuild can’t pick up the right version of Cocoapods, hence can’t update the new pods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[!] The version of CocoaPods used to generate the lockfile (1.3.1) is higher than the version of the current executable (1.1.1). Incompatibility issues may arise.

=== CocoaPods ===
104
Switching CocoaPods version to 1.1.1
105
Using Command Line: gem cleanup "cocoapods"
106
Using Command Line: gem uninstall "cocoapods" --all --executables --force
107
Using Command Line: gem install "cocoapods" --no-rdoc --no-ri --version "1.1.1"
108
Unpacking caches - cocoapods pod specs
109
Using Command Line: pod install --no-repo-update
110
Analyzing dependencies
111
Pre-downloading: `Tailor` from `https://github.com/zenangst/Tailor`, tag `3.0.0`
112
[!] Unable to satisfy the following requirements:
113
- `BarcodeScanner (~> 4.0)` required by `Podfile`
114
- `BarcodeScanner (~> 4.0)` required by `Podfile`
115
- `BarcodeScanner (~> 4.0)` required by `Podfile`
116
- `BarcodeScanner (~> 4.0)` required by `Podfile`
117
- `BarcodeScanner (~> 4.0)` required by `Podfile`
118
- `BarcodeScanner (~> 4.0)` required by `Podfile`
119
- `BarcodeScanner (= 4.1.1)` required by `Podfile.lock`
120
None of your spec sources contain a spec satisfying the dependencies: `BarcodeScanner (~> 4.0), BarcodeScanner (= 4.1.1)`.
121
You have either:
122
* out-of-date source repos which you can update with `pod repo update`.
123
* mistyped the name or version.
124
* not added the source repo that hosts the Podspec to your Podfile.
125
Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.

Trying script

So I thought I could run some custom script to force BuddyBuild to update the pods. Start with https://docs.buddybuild.com/builds/custom_build_steps.html

I commit the file buddybuild_postclone.sh with

1
2
3
4
#!/usr/bin/env bash

pod repo update
pod update BarcodeScanner

Didn’t work. I then update the script with

1
2
3
4
5
#!/usr/bin/env bash

pod repo remove master
pod setup
pod install

Didn’t work. Taking a closer look at the log. I see

1
Switching CocoaPods version to 1.1.1

It seems BuddyBuild is using cocoapods 1.1.1. Mine is 1.4.0.

Specifying cocoapods version

So I need to specify the correct cocoapods version to make sure I and BuddyBuild are on the same page

1
2
gem install bundler
bundler init

And in my Gemfile

1
2
3
4
5
6
7
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "cocoapods", '~> 1.0'

Then run

1
bundler install

And check Gemfile.lock to make sure everything is OK

Finally

Commit the changes, and now BuddyBuild is picking up the right cocoapods version, hence using the new pods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


=== CocoaPods ===
45
Switching CocoaPods version to 1.4.0
46
Using Command Line: gem cleanup "cocoapods"
47
Using Command Line: gem uninstall "cocoapods" --all --executables --force
48
Using Command Line: gem install "cocoapods" --no-rdoc --no-ri --version "1.4.0"
49
Unpacking caches - cocoapods pod specs
50
Using Command Line: pod install --no-repo-update