Categories:
Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?

React Native continuous integration and delivery (CI/CD) with Codemagic

Jul 15, 2020

codemagic codemagic

Automate your development process

Join over 40k developers building their Android, iOS, Flutter or React Native apps with Codemagic CI/CD
Sign up now

Codemagic is now building more than just Flutter apps, meaning that anyone with an existing React Native app can use Codemagic to run their builds. But how do you get started with something like that? Lewis Cianci checks it out.

When it comes to CI/CD, Codemagic has a lot of exciting things to offer to a developer (like a tonne of free build minutes, a really great Slack community, and remote access to the build boxes). But now, we've also got workflows on Codemagic for React Native — and this is amazing.

Read more about React Native CI/CD with Codemagic

You can use these workflows by creating a new project in Codemagic and simply selecting the React Native App from the options.

That's our cue, on the left!
That's our cue, on the left!

Clicking on it will open up the “Getting started guide” for React Native:

Hitting Next shows us our codemagic.yaml file.

We can download this and add it to our local repository. This configures our Codemagic pipelines, so Codemagic knows how to run our React Native build on its build servers.

Modifying the YAML file for our build

The next step in this process is to modify our codemagic.yaml file to get it to build our solution, so let's see what this file looks like:

# Check out https://docs.codemagic.io/getting-started/building-a-react-native-app/ for more information
# Please review and update values in curly braces

workflows:
    react-native:
        name: React Native App
        environment:
            vars:
                XCODE_WORKSPACE: "{{ ADD WORKSPACE NAME HERE }}"
                XCODE_SCHEME: "{{ ADD SCHEME NAME HERE }}"
            node: latest
        scripts:
            - npm install
            - echo "sdk.dir=$HOME/programs/android-sdk-macosx" > "$FCI_BUILD_DIR/android/local.properties"
            - |
                # build Android
                cd android
                ./gradlew assembleDebug
            - |
                # build iOS
                cd ios
                pod install
                xcodebuild build -workspace "$XCODE_WORKSPACE.xcworkspace" -scheme "$XCODE_SCHEME" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
        artifacts:
            - android/app/build/outputs/**/**/*.apk
            - $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.app

Clearly, there are some changes that we need to make to this YAML to get a successful build.

The required changes

Android

Broadly speaking, you almost never want to build a debug version of your app in your CI/CD pipeline. We need to tell the build process to build a release build instead. To do this, we just update the lines under #build Android to look more like this:

            - |
                # build Android
                cd android
                ./gradlew assembleRelease

The build process will now follow the path as defined in our build.gradle. Code signing is a topic in itself (covered in depth here), so we'll just describe the necessary steps to get the build to work.

In your App level build.gradle (that's under YourProjectDirectory\android\app), we need to add in our release signing details. But we need to use the release signing details only when our build is being run on Codemagic, and not locally.

We then configure the build process to read the signing details from the release file.

    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }

        release {
            if (System.getenv()['CI']) { // CI=true is exported by Codemagic
                storeFile file(System.getenv()['FCI_BUILD_DIR'] + '/keystore.jks')
                storePassword System.getenv()['FCI_KEYSTORE_PASSWORD']
                keyAlias System.getenv()['FCI_KEY_ALIAS']
                keyPassword System.getenv()['FCI_KEY_PASSWORD']
            }
                   else {
                def keystorePropertiesFile = rootProject.file('key.properties')
                def keystoreProperties = new Properties()
                keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
                keyAlias keystoreProperties['keyAlias']
                keyPassword keystoreProperties['keyPassword']
                storeFile file(keystoreProperties['storeFile'])
                storePassword keystoreProperties['storePassword']
                   }
        }
    }

What's actually happening here? In a nutshell, if we are running in a release build, we check if the CI environment variable is declared (Codemagic build machines declare this variable). If we are, then we fetch the location of the keystore and appropriate signing details from the environment variables. Otherwise, we use the local key.properties for these details, and fetch the details from there instead. This means that our builds continue to run not only locally but also on Codemagic, which is a nice bonus.

Our local debug key.properties might look something like this (this is for a debug keystore, so yours will be different):

storePassword=android
keyPassword=yourpassword
keyAlias=androiddebugkey
storeFile=keystore.keystore

These are just the keys for the debug keystore, but normally, these are incredibly sensitive details. We don't want just anyone to be able to read these settings, so checking these files into source control is a really bad idea. Instead, we will encrypt them in Codemagic and have the build process decrypt the required details just before the build runs.

Encrypting our build time dependencies

On our project configuration page, we can use the Encrypt environment variables button to convert sensitive details into a format that can be securely checked in.

Next, we can drag our keystore file into this window and receive an encrypted variable containing our keystore. We just click the little clipboard next to the icon to copy the encrypted keystore variable to our clipboard.

We can also use this screen to encrypt individual variables, like the keystore alias and password. We'll do this now, and paste these values into our codemagic.yaml file. When we're done, it should look a little bit like this:

We've configured our build variables, so we can now use them in our build process later on. With these four environment variables encrypted (the keystore, the keystore password, the key alias and password), we're good to go.

Build script

For our Android builds, we want to:

  1. Make the cloned directory writable (I was getting build failures without this, hence the chmod)
  2. Write the keystore file into our repository so it can be used by the build process
  3. Switch to the Android directory and run the assembleRelease gradle task, which will produce a signed APK.

In the codemagic.yaml file, it looks like this:

            - npm install
            - echo "sdk.dir=$HOME/programs/android-sdk-macosx" > "$FCI_BUILD_DIR/android/local.properties"
            - |
                chmod -R 777 $FCI_BUILD_DIR
                echo $CM_KEYSTORE | base64 --decode > $FCI_BUILD_DIR/keystore.jks
                # build Android
                cd android
                ./gradlew assembleRelease

For Android, it's really that easy! For iOS, it's a little more involved …

iOS

In order for our builds to complete successfully, we need to load both our certificate and the .mobileprovision file from App Store Connect onto the build agent. In this example I have encrypted the signing certificate as CM_CERTIFICATE, the password for the certificate as CM_CERTIFICATE_PASSWORD and the provisioning profile as CM_PROVISIONING_PROFILE.

Build script

This looks a bit more complicated than the Android equivalent, and also uses ✨ fancy Codemagic CLI functionality ✨to make it work. Here's what we're doing in this build:

  1. Running keychain initialize to prepare for the import of our signing certificate into the build agent
  2. Setting PROFILES_HOME to where our mobile provisioning certificate is about to be, and then creating that directory
  3. Setting PROFILE_PATH to a temporary location within PROFILES_HOME
  4. Writing our PROVISIONING_PROFILE to PROFILE_PATH, with a temporary name
  5. Writing our signing certificate (CM_CERTIFICATE) to /tmp/certificate.p12
  6. Adding this certificate to the build box's local certificate store, using CM_CERTIFICATE_PASSWORD to do so
  7. Running the xcode-project use-profiles command
  8. And then just basically changing into the iOS directory, running pod install, and using xcode-project build-ipa to build our app, while specifying a workspace and scheme to use
        - keychain initialize
            - |
              # set up provisioning profiles
              PROFILES_HOME="$HOME/Library/MobileDevice/Provisioning Profiles"
              mkdir -p "$PROFILES_HOME"
              PROFILE_PATH="$(mktemp "$PROFILES_HOME"/$(uuidgen).mobileprovision)"
              echo ${CM_PROVISIONING_PROFILE} | base64 --decode > $PROFILE_PATH
              echo "Saved provisioning profile $PROFILE_PATH"
            - |
             # set up signing certificate
             echo $CM_CERTIFICATE | base64 --decode > /tmp/certificate.p12
             keychain add-certificates --certificate /tmp/certificate.p12 --certificate-password $CM_CERTIFICATE_PASSWORD
            - xcode-project use-profiles
            - |
                # build iOS
                cd ios
                pod install
            - xcode-project build-ipa --workspace "ios/$XCODE_WORKSPACE.xcworkspace" --scheme "$XCODE_SCHEME"

The finished codemagic.yaml

After all that, you should have a codemagic.yaml file that looks a bit like this:


workflows:
    react-native:
        name: React Native App
        environment:
            vars:
                XCODE_WORKSPACE: "CodemagicTestProject2"
                XCODE_SCHEME: "CodemagicTestProject2"
                CM_KEYSTORE: Encrypted(...)
                FCI_KEYSTORE_PASSWORD: Encrypted(...)
                FCI_KEY_PASSWORD: Encrypted(...)
                FCI_KEY_ALIAS: Encrypted(...)
                CM_CERTIFICATE: Encrypted(...)
                CM_CERTIFICATE_PASSWORD: Encrypted(...)
                CM_PROVISIONING_PROFILE: Encrypted(...)
            node: latest
        scripts:
            - npm install
            - echo "sdk.dir=$HOME/programs/android-sdk-macosx" > "$FCI_BUILD_DIR/android/local.properties"
            - |
                chmod -R 777 $FCI_BUILD_DIR
                echo $CM_KEYSTORE | base64 --decode > $FCI_BUILD_DIR/keystore.jks
                # build Android
                cd android
                ./gradlew assembleRelease
            - keychain initialize
            - |
              # set up provisioning profiles
              PROFILES_HOME="$HOME/Library/MobileDevice/Provisioning Profiles"
              mkdir -p "$PROFILES_HOME"
              PROFILE_PATH="$(mktemp "$PROFILES_HOME"/$(uuidgen).mobileprovision)"
              echo ${CM_PROVISIONING_PROFILE} | base64 --decode > $PROFILE_PATH
              echo "Saved provisioning profile $PROFILE_PATH"
            - |
             # set up signing certificate
             echo $CM_CERTIFICATE | base64 --decode > /tmp/certificate.p12
             keychain add-certificates --certificate /tmp/certificate.p12 --certificate-password $CM_CERTIFICATE_PASSWORD
            - xcode-project use-profiles
            - |
                # build iOS
                cd ios
                pod install
            - xcode-project build-ipa --workspace "ios/$XCODE_WORKSPACE.xcworkspace" --scheme "$XCODE_SCHEME"
        artifacts:
            - android/app/build/outputs/**/**/*.apk
            - build/ios/ipa

A small note - the Encrypted(...) are placeholder values. You'll need to encrypt your own values on Codemagic that suit your build process.

And that's it! Your React Native app should build with Codemagic now. If you have any questions or comments, be sure to drop into Codemagic's Slack channel and let us know. Happy developing!


Lewis Cianci is a software developer in Brisbane, Australia. His first computer had a tape drive. He’s been developing software for at least ten years, and has used quite a few mobile development frameworks (like Ionic and Xamarin Forms) in his time. After converting to Flutter, though, he’s never going back. You can reach him at his blog, read about other non-fluttery things at Medium, or maybe catch a glimpse of him at your nearest and most fanciest coffee shop with him and his dear wife.

How did you like this article?

Oops, your feedback wasn't sent

Latest articles

Show more posts