After countless days and nights spent building applications by hand, we finally decided to streamline part of our workflow through automation.
But… why?
Our primary motivation for implementing a Continuous Integration system was to reduce the time spent on application builds. In our previous project, a single build sometimes took over half an hour, so we set out to simplify our lives. Here are the key features we aimed to achieve:
- Online access to downloadable builds
- Support for multiple Unity versions
- Building for iOS, Windows, and Android on a single Windows machine
- Storage of builds on our local server
(Our Continuous Integration workflow in a few simple steps)
About GitLab CI
We chose GitLab CI because our repositories were already hosted there. The details of configuring GitLab and the Runner are covered in a separate article.
When setting up our GitLab Runner, we configured the CI pipeline via .gitlab-ci.yml.
Our system operates in two stages: build and deploy. The “build” job serves as a template for all platform-specific jobs. It runs a PowerShell script that launches Unity in batch mode and calls our custom C# build script.
To execute this, we use the following Unity command-line options:
C:\’Program Files’\Unity\Editor\Unity.exe | Launches the Unity executable |
-batchmode | Runs Unity without opening the editor |
-nographics | Ensures no GUI is initiated on Windows |
-executeMethod BuildScript.Build | Invokes the “Build” method in our BuildScript |
-projectPath %CI_PROJECT_DIR% | Points Unity to the project directory |
-quit | Closes Unity after the build completes |
Plus any additional variables, for example -customProjectName %CI_PROJECT_NAME%
.
GitLab provides variables such as project name, pipeline ID, project directory, and build target. We pass these into our Unity script to determine which platform to build.
.build: &build stage: build variables: tags: - Unity script: - C:\"Program Files"\Unity\Hub\Editor\%UNITY_VERSION%\Editor\Unity.exe -projectPath %CI_PROJECT_DIR% -logfile D:\Logs\%BUILD_TARGET%.log -customBuildPath %BUILD_PATH% -customProjectName %CI_PROJECT_NAME% -customBuildTarget %BUILD_TARGET% -pipelineId %CI_PIPELINE_ID% -batchmode -nographics -executeMethod BuildScript.Build -quit artifacts: name: "%CI_PROJECT_NAME% %BUILD_TARGET% %CI_PIPELINE_ID%" paths: - ./Builds/%CI_PROJECT_NAME%/%CI_PIPELINE_ID%/%BUILD_TARGET%
Builds on Demand
We offer four build options: Windows, Android, iOS, or All. You select the target in the “Run pipeline” dialog. Currently, GitLab CI doesn’t support lists of variables, so we enter them manually. If no target is specified, a build runs for all platforms. That’s why our “build” job is a template—each platform has its own job and only runs when the correct variable is provided.
Windows: >>: *build variables: BUILD_TARGET: StandaloneWindows64 BUILD_PATH : ./Builds only: variables: - $Target == "Windows" - $Target == "windows" - $Target == "All" - $Target == null
Scheduling Builds
In addition to manual triggers, we schedule builds after every commit to the master branch and daily at 2 AM using GitLab CI’s Schedules feature.
We also define the Unity version as a top-level variable for easy updates when the project starts.
Where to Find Our Builds?
We initially planned to write builds directly to our local server, but directory permissions forced us to copy them from a temporary folder instead. To avoid disk bloat, we clean up the temp folder after each deploy. Builds remain accessible as artifacts in GitLab.
Speed Optimization
To accelerate builds, we implemented Unity Cache Server. This allows Unity to download preprocessed assets from the cache instead of converting them every time we switch platforms.
Building iOS Apps on Windows
We aimed to handle all builds on a single Windows machine without involving our Mac. Building an .ipa seemed impossible on Windows until we discovered a Unity plugin that makes it possible. It required transferring certificates from our Mac, but it was well worth the effort. The plugin supports command-line usage, making it ideal for our CI setup.
Try It Yourself
That’s our story of building a Continuous Integration system with GitLab CI and Unity. It was more challenging than it sounds, but we continue to refine it. Our workflow is now simpler and faster—give it a try and set up your own!