Buttery Smooth Tech: Automate Your Builds in GitHub Actions
![cover](/_next/image?url=https%3A%2F%2Fassets.gamemaker.io%2FBST_Hero_0922e7c909.jpg&w=3840&q=75)
Hello, GameMakers! This is Shi Chen from Butterscotch Shenanigans. I work on tools and pipelines to smoothly ship our games to all the platforms we publish on. And we publish on a lot of platforms! Our most cross-platform title, Levelhead, required 7 different builds to be made at least once per day. Without a way to automate those builds, our lives would have been very difficult.
Today, I want to share how we use GitHub Actions as build automation servers to make builds for our upcoming game, Crashlands 2. Build automation servers, or more fancily named, continuous integration (abbreviated as “CI”) servers, can liberate you from the tedious button-clicking and waiting when you build your project. Plus, they can run tests and upload builds to stores seamlessly, saving you countless hours and reducing human errors.
If your project is already stored on GitHub, like ours, GitHub Actions is easy to integrate and inexpensive to start (it's free for all public repositories!). We have open-sourced the tools we use, so you can build your GameMaker projects too. If you are already familiar with GitHub Actions, check out the demo to try it out yourself. Otherwise, I will walk you through how to set it up to automatically make Windows builds in the first part of the article. Later, I will discuss how to make builds for Android and iOS, automatically test the builds, and upload them to stores. Lastly, I will discuss the cost and limitations of using GitHub Actions.
Our tools bscotch/igor-setup and bscotch/igor-build only support building for Windows, Android, and iOS exports. However, they are fully open-source, so more export support can be added via pull request or forking.
Setting Up to Make Windows Builds
Prerequisites
Before we get started, you'll need the following:
- A GitHub account.
- Basic Understanding of Github Actions.
- A GameMaker account with the appropriate license to generate access keys at https://gamemaker.io/account/access_keys.
- A GameMaker project.
If your project uses the GMEXT-Steamworks extension, you need to delete the extensions\steamworks\post_build_step.bat file and use the Extension Editor to add the Steam SDK's sdk\redistributable_bin\win64\steam_api64.dll file to the extension. This allows the project to build without having to set up the Steam SDK in the build automation server.
Setting Up the Github Actions Workflow
If an automation server is like a chef in a restaurant, then the GitHub Actions Workflow (which I will simply refer as “workflow” in the remainder of this article) is the recipe the chef follows to create a delicious dish. For our dish, the Windows build, we need to do the following to set up the workflow:
- Generate a GameMaker Access Key: Go to GameMaker’s Access Key page to generate a GameMaker Access Key, which allows us to set up Igor – the GameMaker Command Line build executable – later in the build automation server.
- Create a GitHub Repository: If you haven't already, create a new GitHub repository for your GameMaker project, and upload your GameMaker project files to this repository. Check out the official GitHub guide if you need some help!
- Set Up Secrets: In your GitHub repository, navigate to Settings > Secrets and variables > Actions. Add a new secret named ACCESS_KEY with the GameMaker Access Key you just generated.
- Add the workflow File: In your repository, create a new directory called .github/workflows. Inside this directory, create a file named ci.yml, which is the abbreviation for “Continuous Integration”. Again, check out this other official GitHub guide if you need some help.
- Edit the workflow File: In this file, we will code up a Job to achieve the following goals:
- Checkout The Repository: We will use the actions/checkout action to check out your repository to access the GameMake project.
- Find the yyp File: We will use a PowerShell script to locate the GameMaker project file (*.yyp) and save its path.
- Set Up Igor: We will use the bscotch/igor-setup action to set up the GameMaker Command Line build executable Igor.
- Build with Igor: We will use the bscotch/igor-build action to build your GameMaker project for the Windows export.
- Upload Build Artifact: We will use the actions/upload-artifact action to upload the Windows build so you can download it later.
A sample workflow that does all of that is shown below, though you'll need to make edits for the specifics of your project. I have indicated them with the 📝 emoji:
name: Continuous Integration for Windows export
on:
# push: #Uncomment this line to run the workflow on push
workflow_dispatch: #This line allows you to run the workflow manually from the GitHub Actions page
workflow_call: #This line allows you to run the workflow from another workflow
jobs:
build:
runs-on: windows-2022
steps:
# Check out the repository with the GameMaker project
- uses: actions/checkout@v4
with:
lfs: true
# This step finds the yyp file in the repository and saves the path to an output
- id: find_yyp
name: Find the yyp file
run: |
$yyp = Get-ChildItem -Path ${{ github.workspace }} -Recurse -Filter *.yyp
Write-Output "YYP file found at: $yyp"
"yyp-path=$yyp" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
# This step sets up the GameMaker build CLI tool Igor https://github.com/bscotch/igor-setup
- name: use Igor Setup
uses: bscotch/igor-setup@v1
id: igor
with:
target-yyp: ${{ steps.find_yyp.outputs.yyp-path }}
access-key: ${{ secrets.ACCESS_KEY }} # 📝 To generate your Access Key, check out the Access Key section of the GameMaker Manual's Building via Command Line page: https://manual.gamemaker.io/monthly/en/#t=Settings%2FBuilding_via_Command_Line.htm
# This step uses Igor to build the GameMaker project https://github.com/bscotch/igor-build
- name: use Igor build
uses: bscotch/igor-build@v1
id: build
with:
yyp-path: ${{ steps.find_yyp.outputs.yyp-path }}
user-dir: ${{ steps.igor.outputs.user-dir }}
# This step uploads the build to the artifacts, so you can download it from the GitHub Actions page or use it in another workflow
- name: upload-build
uses: actions/upload-artifact@v4
with:
name: igor-build-windows
path: ${{ steps.build.outputs.out-dir }}
retention-days: 1 # Longer retention days can incur additional charges. See https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts
Running the Workflow
To run the workflow manually:
- Go to the Actions tab in your GitHub repository.
- Select the Continuous Integration for Windows export workflow.
- Click the Run workflow button.
To run the workflow automatically when you push any commits to this repository, uncomment the line # push: in the ci.yml file.
Using the Build Artifacts
Once the workflow completes, you'll find the build artifacts in the Actions tab. Click on the completed workflow run, and you'll see an option to download the build artifact named igor-build-windows.
Mobile Builds, Testing, and Uploading
While the above workflow works great for Windows builds, you can also take it further to make mobile builds, test them, and upload them to stores like Steam, Google Play, and Apple App Store. These tasks can get pretty complicated, so I can't fully explain everything here. However, the following sections provide tips, tricks, references for accomplishing these tasks, and you can also find examples in our GameMaker runtime testing repo Ganary.
Building for iOS
When you make iOS builds in the GameMaker IDE, you must have a MacOS device and set up XCode with your Apple Developer account’s certificate and provisioning profile. Then, the GameMaker IDE essentially converts your GameMaker project into an XCode project and builds it on the MacOS device.
To make iOS builds in GitHub Actions, you need to import your certificate and provisioning profile to the build environment. There are a lot of solutions such as Fastlane, Apple-Actions/import-codesign-certs, and yukiarrr/ios-build-action. We chose Fastlane as it is already installed in the default GitHub Actions environment, streamlines the setup process, and also has other useful features such as building the XCode project and uploading to Testflight.
On a high level, our implementation contains the following steps:
- On your local machine, follow Fastlane’s guide to set up the Match action. This allows you to import the certificate and provisioning profile later in the GitHub Actions environment.
- Store the Match setup’s MATCH_PASSWORD and MATCH_GIT_BASIC_AUTHORIZATION in your GitHub repo’s secrets.
- The GitHub Actions workflow will need to:
- Use the MacOS runner.
- Mostly follow the same steps as the Windows workflow, but this one will create the XCode project and expose its location.
- Create a fastlane/Fastfile which instructs Fastlane to import the certificate and provisioning profiles and build the project.
- Run Fastlane with the secrets MATCH_PASSWORD and MATCH_GIT_BASIC_AUTHORIZATION as environment variables to build the export *.ipa file.
Here is what the workflow file should look like with the 📝 emoji indicating where you should edit:
name: Continuous Integration for iOS export
on:
# push: #Uncomment this line to run the workflow on push
workflow_dispatch: #This line allows you to run the workflow manually from the GitHub Actions page
workflow_call: #This line allows you to run the workflow from another workflow
jobs:
Build:
runs-on: macOS-latest
steps:
# Check out the repository with the GameMaker project
- uses: actions/checkout@v4
with:
lfs: true
# This step finds the yyp file in the repository and saves the path to an output
- id: find_yyp
name: Find the yyp file
run: |
$yyp = Get-ChildItem -Path ${{ github.workspace }} -Recurse -Filter *.yyp
Write-Output "YYP file found at: $yyp"
"yyp-path=$yyp" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
# This step sets up the GameMaker build CLI tool Igor https://github.com/bscotch/igor-setup
- name: use Igor Setup
uses: bscotch/igor-setup@v1
id: igor
with:
target-yyp: ${{ steps.find_yyp.outputs.yyp-path }}
access-key: ${{ secrets.ACCESS_KEY }} # 📝 To generate your Access Key, check out the Access Key section of the GameMaker Manual's Building via Command Line page: https://manual.gamemaker.io/monthly/en/#t=Settings%2FBuilding_via_Command_Line.htm
# This step uses Igor to build the GameMaker project https://github.com/bscotch/igor-build
- name: use Igor build
uses: bscotch/igor-build@v1
id: build
with:
yyp-path: ${{ steps.find_yyp.outputs.yyp-path }}
user-dir: ${{ steps.igor.outputs.user-dir }}
# This step sets up the Fastlane fast file
- name: Set up the Fastlane fast file
working-directory: ${{ steps.build.outputs.out-dir }}
run: |
# Create the fastlane/Fastfile with the following content
mkdir -p fastlane
cat << EOF > fastlane/Fastfile
lane :beta do
setup_ci
matchType = "appstore"
bundleId = "YOUR BUNDLE NAME" #📝Use your iOS Game Options’s bundle name
match(
type: matchType,
git_url:"YOUR MATCH SETUP'S GIT REPO URL", #📝Use your Match setup’s Git repo url
storage_mode: "git",
shallow_clone: "true",
app_identifier: bundleId,
readonly: "true",
)
update_code_signing_settings(
use_automatic_signing: false,
team_id: ENV["sigh_#{bundleId}_#{matchType}_team-id"],
profile_name: ENV["sigh_#{bundleId}_#{matchType}_profile-name"],
code_sign_identity: "iPhone Distribution"
)
build_app
end
EOF
# This step runs Fastlane to build the XCode project
- name: Run Fastlane to build the XCode project
working-directory: ${{ steps.build.outputs.out-dir }}
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} # 📝 This is the password for the match repo, see https://docs.fastlane.tools/actions/match/#:~:text=for%20each%20app.-,Passphrase,-Git%20Repo%20storage
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} # 📝 This is the basic authorization for the match repo, see https://docs.fastlane.tools/actions/match/#:~:text=MATCH_GIT_BASIC_AUTHORIZATION
run: |
fastlane beta
- name: upload build as artifact
uses: actions/upload-artifact@v4
with:
name: igor-build-macOS
path: ${{ steps.build.outputs.out-dir }}
retention-days: 1
After running the workflow, both the *.ipa file and the XCode project will be uploaded as the artifact for you to download.
In later parts of the article, we will discuss how to run automated test on this *.ipa file and upload it to Testflight.
Building for Android
Similar to building iOS, building for Android requires importing your code-signing secret, the Keystore File, to the GitHub Actions environment. In addition, we need to expose the Keystore File’s location, alias, and password to Igor (GameMaker's Command Line build tool).
On a high level, our implementation contains the following steps:
- On your local machine, create the Keystore File, its alias, and password as mentioned in the GameMaker guide Setting Up for Android.
- Locate the Keystore file and follow this guide to make it available in the GitHub environment as a secret.
- Locate the local_settings.json file, which stores your IDE preferences and the Android SDK configuration. Copy the entries "machine.Platform Settings.Android.Keystore.keystore_password" and "machine.Platform Settings.Android.Keystore.alias"'s values and store them as GitHub secrets.
- The GitHub Actions workflow will need to:
- Use the Ubuntu runner.
- Add a step to recreate the Keystore file and expose its location.
- Add a step to create a local_settings.json file that includes the path to the recreated keystore file, its alias, and password by populating the entries:
- "machine.Platform Settings.Android.Keystore.filename": the path to the recreated keystore file
- "machine.Platform Settings.Android.Keystore.keystore_password": the value you previously stored in secret
- "machine.Platform Settings.Android.Keystore.keystore_alias_password": same value as above
- "machine.Platform Settings.Android.Keystore.alias": the value you previously stored in secret
- In the step that uses bscotch/igor-setup@v1, add the local-settings-override-file option to point to the local_settings.json file.
- Set up ffmpeg and Android SDK's platform tools, as these two are not included in the default GitHub Actions environment or GameMaker runtime.
Here is what the workflow file should look like with the 📝 emoji indicating where you should edit:
name: Continuous Integration for Android export
on:
# push: #Uncomment this line to run the workflow on push
workflow_dispatch: #This line allows you to run the workflow manually from the GitHub Actions page
workflow_call: #This line allows you to run the workflow from another workflow
jobs:
Build:
runs-on: ubuntu-latest
steps:
# Check out the repository with the GameMaker project
- uses: actions/checkout@v4
with:
lfs: true
# This step finds the yyp file in the repository and saves the path to an output
- id: find_yyp
name: Find the yyp file
run: |
yyp=$(find ${{ github.workspace }} -name "*.yyp")
echo "YYP file found at: $yyp"
echo "yyp-path=$yyp" >> $GITHUB_OUTPUT
#region Android setup
- name: Create the keystore file from secrets
id: write_file
uses: timheuer/[email protected]
with:
fileName: 'myTemporaryFile.keystore'
encodedString: ${{ secrets.KEYSTORE_BASE64 }} # 📝 Your keystore file encoded
- name: Create the local-settings-override-file for igor-setup
run: |
echo '{
"machine.Platform Settings.Android.Keystore.filename": "${{ steps.write_file.outputs.filePath }}",
"machine.Platform Settings.Android.Keystore.keystore_password": "${{ secrets.KEYSTORE_PASSWORD }}", # 📝 Your keystore password
"machine.Platform Settings.Android.Keystore.keystore_alias_password": "${{ secrets.KEYSTORE_PASSWORD }}", # 📝 Your keystore password
"machine.Platform Settings.Android.Keystore.alias": "${{ secrets.KEYSTORE_USERNAME }}" # 📝 Your keystore alias
}' \
> local_settings.json
- name: Set up ffmpeg # This step may be removed when https://github.com/YoYoGames/GameMaker-Bugs/issues/4977 is fixed
uses: FedericoCarboni/setup-ffmpeg@v3
with:
ffmpeg-version: '6.1.0'
- name: Set Up Android SDK's platform-tools # The default Android SDK does not include platform-tools and its component `adb`, which is required by Igor for the Android build
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager \
--sdk_root=$ANDROID_SDK_ROOT \
"platform-tools"
#endregion
# This step sets up the GameMaker build CLI tool Igor https://github.com/bscotch/igor-setup
- name: use Igor Setup
uses: bscotch/igor-setup@v1
id: igor
with:
local-settings-override-file: ${{ github.workspace }}/local_settings.json
target-yyp: ${{ steps.find_yyp.outputs.yyp-path }}
access-key: ${{ secrets.ACCESS_KEY }} # 📝 To generate your Access Key, check out the Access Key section of the GameMaker Manual's Building via Command Line page: https://manual.gamemaker.io/monthly/en/#t=Settings%2FBuilding_via_Command_Line.htm
# This step uses Igor to build the GameMaker project https://github.com/bscotch/igor-build
- name: use Igor build
uses: bscotch/igor-build@v1
id: build
with:
yyp-path: ${{ steps.find_yyp.outputs.yyp-path }}
user-dir: ${{ steps.igor.outputs.user-dir }}
- name: upload build as artifact
uses: actions/upload-artifact@v4
with:
name: igor-build-ubuntu
path: ${{ steps.build.outputs.out-dir }}
retention-days: 1
After running the workflow, the *.aab file will be uploaded as the artifact for you to download.
Automatically Test the Builds
Another important advantage of using build automation servers is that you can set up automated tests for your builds before you even ship them to your team members or players. The automated tests can be as simple or as complicated as you want, but they should launch without requiring any keyboard/mouse/touchscreen input, and the test results should be accessible for your workflow to decide whether to fail the build or ship it. In our GameMaker runtime testing workflow, we run automated tests on the Windows, Android, and iOS builds using Olympus, our GameMaker test framework:
- For Windows: Once the build is made, we launch the *.exe file with Command Line Parameters, which Olympus parses to launch the game into the testing loop. Once the loop completes, the result is output to a file for the workflow to evaluate.
- For iOS and Android: We deploy the builds to Firebase Test Lab. Similar to parsing the Command Line Parameters on Windows, Olympus uses an extension to detect whether the game is launched in Firebase Test Lab and should go to the testing loop. Once the loop completes, the game will either exit normally to flag the test result as passed, or it will throw an error to fail the test. The test result is then evaluated by the workflow.
It is possible to deploy and test mobile builds on simulators, which the GitHub hosted runners already include. We chose Firebase Test Lab as it has a free tier, offers both simulator and physical devices for running tests, and the android test can also be used for Google Play’s pre-launch report.
Uploading Builds to Steam, Google Play, and iOS TestFlight
Leveraging the vibrant GitHub Marketplace and the broader open-source community, we can use off-the-shelf solutions to upload builds to distribution platforms like Steam, Google Play, and iOS TestFlight:
- For Steam, check out game-ci/steam-deploy
- For Google Play, check out r0adkll/upload-google-play
- For iOS TestFlight, check out Fastlane upload_to_testflight or Apple-Actions/upload-testflight-build
Cost and Caveats
GitHub Actions is a powerful tool for automating builds, testing, and deployment, but there are still costs and limitations to consider.
Cost
GitHub Actions offers a good amount of free minutes for every user, so you won't have to worry about the cost when you are just getting started. However, if you have a sizable project that builds frequently, you will want to be more conscious about your workflows.
As an example, our current project, Crashlands 2, is a 5GB (and growing!) project. Our workflow makes two Windows builds (one for the full game version and another for the demo version), two Android builds (one for QA and one for publishing partner), and one iOS build. These builds are then automatically tested, uploaded to various distribution platforms, and notify team members for further testing. For such a workflow, here is the rough cost breakdown:
Billable Minutes | Per-minute rate (USD) | Total | |
Windows full | 31 | $0.016 | $0.50 |
Windows demo | 28 | $0.016 | $0.45 |
Android QA | 19 | $0.008 | $0.15 |
Android publisher | 20 | $0.008 | $0.16 |
iOS | 11 | $0.08 | $0.88 |
Sum | $2.14 |
Compared to buying and managing your own hardware, or the downtime cost of waiting for your game to compile, spending $2.14 to get all of that (5 builds, automated testing, automated deployment) is a good deal!
But the costs can definitely add up if you're making a lot of builds. Fortunately, there are plenty of ways to optimize the workflow to cut costs, and you can self-host the runners or make your project public, as either of these two options exempts you from GitHub Actions billing.
Caveats
- Using GitHub Actions involves a lot of secrets and running third party actions, so you should familiarize yourself with good safety practices to secure your workflows.
- Over time, third-party actions that you depend on can change and break your workflow. Lock in a version by targeting the action's SHA value. You can also fork the action's repository, modify it to fit your project, and use your forked version instead.
- When you are actively developing a workflow for a private repository, consider setting up a local development environment so you can iterate faster and cheaper. nektos/act is a wonderful tool to run your workflow locally instead of having to upload it to your private repository, which will incur costs.
- GitHub hosted runners have their own physical limitations in terms of RAM and Storage. If your workflow takes a long time to run and fails, it might be that the project requires more resources than the runners provide. You will then need to use larger runners or self-hosted runners.
- Lastly, GitHub Actions can be unavailable due to service outage from time to time. Since we started using GitHub Actions in 2021, we have only encountered a handful of times where service outages happened during our working hours, but they were quick to resolve. You can subscribe to the service status update at https://www.githubstatus.com/.
Conclusion
Whether you're new to continuous integration or already automating your builds, I hope you found something useful in this article. I've cross-posted this article on GitHub Gist, where you can leave comments and feedback. I'd love to hear if the countless hours I spent figuring all of this out saved you some time! Any other comments/questions are also welcome. And if you have your own solutions to automating GameMaker builds, I'd love to hear about those as well!
Thanks for reading. You can check out our 100% GitHub-Actions-grown Crashlands 2 demo builds on Steam. Wishlisting it is the best way to support us, and we hope you enjoy the game just as much as the tech behind it!
![avatar](/_next/image?url=https%3A%2F%2Fassets.gamemaker.io%2Frossm_60aa4c1523.jpg&w=640&q=75)