The past few days I was attempting to auto deploy a react native app to App Center using Github actions. This process is the exact same for a fully native app, since we are running the normal xcode / gradle commands. I'll walk you through all the annoying things Apple put me through, so you don't have to. If you only want to build for Android, you can skip to that and be done in a few minutes.. iOS setup is much more involved
iOS
GitHub is where people build software. More than 50 million people use GitHub to discover, fork, and contribute to over 100 million projects. GitHub Desktop Focus on what matters instead of fighting with Git. Whether you're new to Git or a seasoned user, GitHub Desktop simplifies your development workflow. Download for macOS Download for Windows (64bit) Download for macOS or Windows (msi) Download for Windows. By downloading, you agree to the Open Source Applications Terms. See full list on digitaltrends.com.
There's not much out there when you search for help with iOS builds, and the only ones you find tell you to use fastlane for cert management. I knew there had to be a way though, even with my naive knowledge of the profiles and certificate logic that iOS app builds have, I know I only needed to download two files in order to build an app on my local machine, so it couldn't be too hard, right?
Before I start, let me quickly rant about Apple. I was able to get it mostly working, then ran into the code signing process prompting for a password and hanging indefinitely inside the build container.. The fix for this is a command that makes no sense, and someone had to reverse engineer Apple's tooling just to find it. It's been almost 4 years and Apple has yet to improve on any of these command line utilities. It's honestly a miracle we are able to make this work at all.
Getting Started
Instead of stepping through each little piece of the config, I'm going to share the final piece of the main config, then explain the parts around it. If you are not using react native, you will need to remove the
ios
Allow unverified apps mac. folder prefix from my commands. Place this inside .github/workflows/ios-build.yaml
This tutorial is assuming that you have a signing certificate and provisioning profile for the app on you machine already. Also, I'm doing an ad-hoc build.
Install GPG
I followed the official guide from github on how to create secrets that are too large to be stored as plain text. Unfortunately, they fail to mention that gpg isn't installed on the Mac OS images, but it is on all the others. There's an open issue about getting this added to the Mac OS image itself. This would be great because it takes about 2.5 minutes just to install gpg. We will be using it to encrypt our provisioning profile and signing certificate.
Add Cert / Profile to the repo
Here's what I did first. Open up your app in xcode, go to Build Settings under your app target, and find
Signing
Hover over the release profile, and to the right of it you should see
Profile Name (uuid)
Run google play apps on mac. Copy this uuid. We need it to get the profile and it's also needed in a later step.
Open up a terminal window inside of your app's directory. Make a folder called
.github
inside it create workflows
and secrets
Next, run this:
I know, so weird that I didn't use
cp
. I was thinking the same thing. Anyways, be sure to paste the UUID from xcode so that you get the right one.Next, we need to encrypt it, this way other people can't access the profile. But first, we need to grab the signing certificate as well.
Inside xcode, go to preferences -> accounts -> click the account you're using -> manage certificates -> right click on the cert you are using, and click export.
Don't give it a password. Put it inside the app folder as well, it should be named
Certificates.p12
Encrypt Certificate and Profile
Encrypting them is easy, make sure you have
gpg
installed. On Mac, you can run brew install gpg
, linux machines should have it already installed. From a terminal window inside your app:For both commands, I used a randomly generated password. I recommend using the same password for both files.
Immediately after you finish running the command for both files, open up the settings page for your repo on github, go to secrets, and create a new one called
IOS_PROFILE_KEY
and the value is the password you just used for gpg. Next, delete Certificates.p12
and profile.mobileprovision
. We don't want to accidentally commit these since they are unencrypted. Lastly, move
Certificates.p12.gpg
and profile.mobileprovision.gpg
to the .github/secrets
folder.Decrypt and Install Certs on the server
We're almost there with this gpg stuff, I promise. Create
.github/secrets/decrypt_secrets.sh
and put the following inside it:First off, we decrypt both files using the secret key we just added to github. Next, we copy the mobile provision into the same location that it existed on our local machine. Funny enough.. I never saw this anywhere because I could only find people using fastlane. It was just a lucky guess that this is the correct procedure.
After that is the weird stuff.. We create a new keychain on the build server. This might not be necessary, but through my trials and errors I ended up doing it. The problem I initially ran into was xcode building correctly, but hanging forever because the codesign tool would prompt asking for a password, clearly we can't go into the UI and hit the enter key (the password is blank).
The final line, set-key-partition-list, is the most important part that fixes the issue I described above. Even though nobody seems to really understand the meaning of this command. You can read more about it on this SO post I found. The commenter seemed to give more info than Apple does about this whole process, so I followed them fully, by including the new keychain part. Do as you wish, but the config I have works :)
Make sure you run
chmod +x .github/secrets/decrypt_secrets.sh
before you commit any of this.Back to the actions
With most of the setup out of the way, let me continue explaining the actions file I posted above. Please have it open while reading along below.
Selecting xcode version
After installing gpg, we can select an xcode version. This may be really outdated by the time you're reading it. Github has a nifty docs page that is about a mile long. Here's the section on xcode. This way you can adjust the config if you need to use a different version of xcode or the iOS sdk.
npm / yarn
Next step is caching the npm dependencies then running yarn install. You can remove it if you're doing a fully native app. Best app to organize photos on mac desktop. If you are using React Native, this will cache the
node_modules
folder until the yarn.lock
changes. If you use npm, simply change yarn.lock
to package.json
You'll notice my yarn install step is setting an NPM_TOKEN value. My app uses private npm packages so this piece of code is needed for it to download them.
Pods
Very similar to npm / yarn step, this will cache all the pods until the lock file changes. Very self explanatory.
Build app
Okay, our last couple things to configure..
Replace
appname
with the actual app name you see in xcode. Replace UUID
with the mobile provision uuid that we copied over in the gpg encryption step. There may be an easier way, but here's how I got the CODE_SIGN_IDENTITY
value.. Inside xcode, with the project open, go to General
and click the little i
icon next to the Signing (Release)
section. It should be spelled out under the Certificates included
section. I hope this helps. If you still can't find it, the next step will have you do a build inside xcode, and the outputted plist file should have it inside.Exporting
Replace appname with the app's name one more time, this command actually spits out the
.ipa
file. To get the exportOptionsPlist
, inside xcode, do a product -> archive of your app. Choose distribute, choose ad-hoc. And then export it to your desktop. Now copy the file inside it, ExportOptions.plist
to appname/ios/ci.plist
. It's almost all set for you, but we have to add the following BEFORE ApplicationPropertiesNow it should be good to go and successfully build..
Deploy it!
The ipa is here:
ios/build/cnnrnv2.ipa
Which means it's up to you what to do with it. The Mac OS environment doesn't support docker actions on github, most actions won't work. If there's not an existing action for the platform you're using, you will have to make a node module and deploy it to npm like appcenter does.
You'll see that I am deploying to app center. They offer a node cli, so I install it, and then run the distribute command with the path to the ipa file. Take note of this part:
--release-notes '$(git log -1 --pretty=format:%s)'
this attaches the commit to the release notes. Might be useful to pass this git log command for any service you may be using. This is much quicker than iOS. Here's the config. Place it in
.github/workflows/android-deploy.yaml
Thats.. seriously it. So much easier than iOS. Not much setup is required here, but I'll go over the file.
Setup
The first three actions are setup. Checking out the code, adding in node and java. If you are not using react native you may remove the node action and yarn install step.
Caching npm dependencies
As explained in the iOS tutorial, this will hash the yarn.lock file (change to package-lock.json if using npm) and restore the
node_modules
each time it is ran, if the lock hasn't changed.Install yarn dependencies
This step adds my NPM_TOKEN secret to let yarn access private npm packages, otherwise its a basic yarn install command.
Build for Android
Builds and spits out the apk for us! Standard gradle.
Deploy
Please reference the deploy section for iOS. The only difference is the nicely built action that we are able to use, because linux supports all docker based actions.
Conclusion
I hope I didn't miss anything. I tried my best to reference issues and articles I found along the way. If you get stuck during any of this, or have other solutions, please reach out on twitter @zachcodes so I can update the article. I really wish Apple would invest in making their command line tooling better. I don't mind having to encrypt the cert and profile, but let me pass them in to the build command as arguments.. Also, why do we need to create this extra plist file just to export? Give me an ad-hoc build command and remove the duplication of setting the profile uuid in every command.. Oh well..
Parcel
At least we have a way to make it work! Overall I'm happy with the outcome, just shocked at how Apple doesn't bother making any of this an easy process. Github has definitely taken away much of the pain.