Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!

While the last post of 2021 was a rant about Misery of Manual Builds, I didn’t post a concrete solution of how I solved it.

So, this post is how I went about automating the build process of uploading a new app on the App Store.

Some background:

I created an app Quoting that experimented with the new async/await syntax.

However, I was too lazy to manually archive the build, create screenshots, and update the details on App Store Connect (ASC). So, I never went around getting the app to see the sunlight.

This was back in September last year.

In December, Codemagic released a new feature on directly publishing for App Store review. Dog feeding the feature on my app was the best way to write about it as the Developer Relations Engineer.

So, let’s automate the process!

Workflow Configuration

Everything that you want to automate goes into the workflow configuration file. Think of it as giving specific instructions to the service, like:

  • build the app,
  • test the app,
  • archive the app, and
  • publish the app.

It may require additional parameters, like the ASC API key, certificates, etc.

Many CI/CD services use a .yaml file for workflow configuration, and I’m using codemagic.yaml.

The below configuration creates a quoting-release-workflow with the given project name, scheme, and App Store ID. I prefer to describe them under variables to reuse them later in different scripts.

workflows:
  quoting-release-workflow:
    environment:
      groups:
        - appstore_credentials
      vars:
        XCODE_PROJECT: "Quoting.xcodeproj"
        XCODE_SCHEME: "Quoting"
        APP_STORE_APP_ID: 1576937390
    triggering:
      events:
        - tag

You can run scripts for different commands like code signing, incrementing build numbers, and to build the app for distribution. You give them a name and the script to run:

- name: Example command 
   script: |
     echo "Example command"

Code Signing

The groups contain a list of values that I’ve stored on Codemagic’s UI. In this case, these are the values required for code signing the app under the group appstore_credentials.

While code signing is a nightmare, it is relatively easier when you’re the only one working on the app.

From the documentation:

Code signing your app assures users that it’s from a known source and hasn’t been modified since it was last signed. Before your app can integrate app services, be installed on a device, or be submitted to the App Store, it must be signed with a certificate issued by Apple.

As they’re private sensitive data, you encrypt the following values in Codemagic’s UI. You can get them from ASC > Users and Access > Keys:

  • APP_STORE_CONNECT_KEY_IDENTIFIER: Key ID when you create a new key.
  • APP_STORE_CONNECT_ISSUER_ID: Issuer ID mentioned at the top.
  • APP_STORE_CONNECT_PRIVATE_KEY: Private API key for the given Key ID.
  • CERTIFICATE_PRIVATE_KEY: Private key to be included in the signing certificate.

The process for code signing is:

  • Initializes the keychain to be used for code signing,
  • Fetches the signing certificate from ASC,
  • Adds the new signing certificate to the keychain, and
  • Uses the signing certificate in the Xcode project

Updating the workflow configuration accordingly:

scripts:
  - name: Set up code signing
    script: |
      keychain initialize
      app-store-connect fetch-signing-files "$(xcode-project detect-bundle-id)" --type IOS_APP_STORE --create
      keychain add-certificates
      xcode-project use-profiles

The next script does what I’ve been doing manually ever since: incrementing the build number.

- name: Increment build number
  script: |
    #!/bin/sh
    set -e
    set -x
    agvtool new-version -all $(($BUILD_NUMBER + 1))

In the latest Xcode, you can increment the build number while archiving, but I remember doing this for four targets last to last year manually for every TestFlight build. (Yeah, I should have just added a script in Xcode)

Building for Distribution

The next step is to build the app for distribution:

- name: Build ipa for distribution
  script: |
    xcode-project build-ipa --project "$XCODE_PROJECT" --scheme "$XCODE_SCHEME"   
  artifacts:
    - build/ios/ipa/*.ipa
    - $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM

And finally, the last part is to directly publish for App Store review:

publishing:
  app_store_connect:
    key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
    issuer_id: $APP_STORE_CONNECT_ISSUER_ID
    api_key: $APP_STORE_CONNECT_PRIVATE_KEY

    submit_to_app_store: true
    
    release_type: MANUAL

It uses the value from the environment variables in the appstore_credentials created earlier. I prefer to keep the release type as MANUAL so that you can take my time to release the app when the new version is approved.

You can also notify yourself when a build succeeds or fails on Codemagic:

email:
  recipients:
    - rudrankriyam@mymail.com
  notify:
    success: true
    failure: true

The whole configuration file :

workflows:
  quoting-release-workflow:
    environment:
      groups:
        - appstore_credentials
      vars:
        XCODE_PROJECT: "Quoting.xcodeproj"
        XCODE_SCHEME: "Quoting"
        APP_STORE_APP_ID: 1576937390
    triggering:
      events:
        - tag
    scripts:
      - name: Set up code signing
        script: |
          keychain initialize
          app-store-connect fetch-signing-files "$(xcode-project detect-bundle-id)" --type IOS_APP_STORE --create
          keychain add-certificates
          xcode-project use-profiles
      - name: Increment build number
        script: |
          #!/bin/sh
          set -e
          set -x
          agvtool new-version -all $(($BUILD_NUMBER + 1))
      - name: Build ipa for distribution
        script: |
          xcode-project build-ipa --project "$XCODE_PROJECT" --scheme "$XCODE_SCHEME"
    artifacts:
      - build/ios/ipa/*.ipa
      - $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM
    publishing:
      app_store_connect:
        key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
        issuer_id: $APP_STORE_CONNECT_ISSUER_ID
        api_key: $APP_STORE_CONNECT_PRIVATE_KEY

        submit_to_app_store: true

        release_type: MANUAL
      email:
        recipients:
          - rudrankriyam@mymail.com
        notify:
          success: true
          failure: true

Waiting for Review

And, that’s it! All I did for the release was push the final code, add the release tag in GitHub and a webhook, and go out to have a coffee.

The build started on Codemagic and after 15 minutes, I had Waiting for Review on App Store Connect!

I recently loved this line from Mustafa Yusuf in this session. It’s unrelated to CI/CD but something we can relate to in general:

Once you know all this, it’s all complicated and sounds complex, but it’s just so simple when you start coding it.

Conclusion

It’s pure bliss when the automation works flawlessly but not so much fun if it doesn’t. Now that I’ve set it up for one app, it’ll be much easier for every other app.

I loathed the process of sending the app to the Store manually, and now I don’t have to worry about it all.

I haven’t mentioned automatic screenshots, something that I plan to cover in a post soon!

I hope if it was the first time you heard about automating your build and want to automate your release process, please tag @rudrankriyam on Twitter! I would love to know about your experience!

Thanks for reading, and I hope you’re enjoying it!

Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!