Continuous Integration – Setting up a Jenkins build server

There are some things in a project that you should do as early as possible as they will save you a lot of time in the long run. The most important time saver might be setting up an automatic build server.

Even in small and smallest teams you’ll have the need of creating a build of your game every now and then. Here are some occurrences:

  • Your non-programming colleagues can test the current version of your game
  • You need a version to give to the press
  • Your publisher wants a milestone version
  • You just want to make sure the game still works on the devices
  • You need a final release build
  • The final release build wasn’t that final and you need a version with several bug fixes

The more you get into a project, the higher the request for current and special builds of your game. In the final phase of your game you definitively want a version at least every day, so your QA can find the last bugs and can make sure no new bugs are introduced.

But you definitively don’t want to sit down every day building this version, do you? Eventually programmers are lazy and this is where our good friend Jenkins steps in.

At your command, Sir!

The Jenkins project was originally developed as a project called Hudson. It provides a so called continuous integration service which takes over the work of automatically building your software once it is properly configured. Here are some use cases when a new version of your game might be build:

  • Each day at a specific time (Daily Build)
  • Each time something changed, i.e. somebody committed changes to the project repositories (Continuous Build)
  • Manually triggered for a specific branch (Release Build, Press Build,…)

Configuring the build server

After you installed your build server, it’s time to configure it correctly. There are several plugins which makes it easy to let Jenkins get the source code of your project from a repository (Git, SVN, Mercurial,…). After this step you have to decide what Jenkins should do with the sources to give you a proper build.

Jenkins is structured by jobs. Each job consists of an arbitrary number of build steps. These build steps are among others:

  • Executing a shell/command line/Nant/Ant/… script
  • Triggering a Unity3D build
  • Sending files to a FTP server

When setting up the build server for our current project, I found great use of the Parameterized Trigger Plugin. As the name suggests, it allows to trigger other jobs with a bunch of parameters as a build step for another job.

As you saw there are a lot of different configured versions that you have to build during the development of your game. The configuration of what to build usually contains:

  • Branch of repository to use
  • Platform
  • Release or debug configuration

Additionally there may be project-specific settings that change between different builds. You don’t want to set up a completely new job for every configuration you need, as you would have to change multiple jobs if something changes in your build pipeline.

Instead you should add a parameterized job that contains all the configuration values that are available. After that we will use that job for all the specific jobs. The job structure will look like this:

A single main Jenkins build job is used by the jobs for specific versions of the game
A single main Jenkins build job is used by the jobs for specific versions of the game

Sharing the workspace

By default, each job uses its own workspace (i.e. an own folder) to work in. This is the place the sources are checked out and the scripts are executed in.

As the parameterized job is used for several other jobs, we don’t want it to work in a separate folder. Instead we tell the parameterized job which workspace to use by sending the workspace of the calling job as a parameter and setting the workspace with the “Custom Workspace” option in the parameterized job.

This way the main job works in the workspace of the specific jobs rather than in its own. It gives you the opportunity to do the workspace handling in the specific jobs, e.g. to clean it up before each build (for daily, release builds) or to copy the results of a successful build somewhere they can be accessed (FTP server, website,…).

Creating specific jobs

Now that you’ve got your main build pipeline with the parameterized job, it’s time to set up the specific jobs we use to build the different versions of our game with.

Here are the jobs I set up for our current project and the things which are specific to them:

Daily build

  • Runs automatically each night between 0 – 7 a.m.
  • Configuration
    • Project branch: develop
    • Framework branch: develop
    • Platform: Android
    • Build Target: debug
    • Configuration: Debug Android
  • Cleans the workspace before running
  • Copies the built Android APK to the FTP server

Master build

  • Has to be triggered manually
  • Configuration
    • Project branch: master
    • Framework branch: master
    • Platform: Android
    • Build Target: release
    • Configuration: Release Android
  • Cleans the workspace before running
  • Copies the built Android APK to the FTP server

Manual build

  • Has to be triggered manually
  • Configuration
    • Project branch: develop
    • Framework branch: develop
    • Platform: Android
    • Build Target: debug
    • Configuration: Debug Android
  • Does not clean the workspace before running
  • Copies the built Android APK to the FTP server

Continuous job

  • Used to make sure that the project still compiles correctly.
  • Triggers after each repository change (framework and project)
  • Configuration
    • Project branch: develop
    • Framework branch: develop
    • Platform: Android
    • Build Target: debug
    • Configuration: Debug Android
  • Does not clean the workspace before running
  • Sends out mails to the contributors of the last commit(s) if the job is unstable or fails

The additional build steps for each job depend on your needs. The important thing here is that the main build pipeline is shared between all jobs, so changing it is easy and adding a job for a new version is quickly done, too.

Conclusion

Setting up a build server takes some time and may seem to be a big overhead for the few versions you build at the start of your project. But it’s worth taking the time and the earlier you do it, the earlier the time will be made up. With a properly set up build server you only need a single click to create a new version of your game. Or no click at all if you use a time trigger.

Furthermore a build server is a great tool to guarantee that your project compiles which is especially useful on a team. The continuous job even sends out mails to remind the people who committed bad stuff to fix it. And of course you can run the unit tests of your project before each build to make sure the project not only compiles, but also still succeeds the tests.

Once set up there are many, many ways to further automatize things using your build server. If you can think of something that may be automatized in your project, chances are good that there is already a Jenkins plugin out there for the job. Here are some more plugins that I found useful:

  • MSBuild: Use MSBuild to build .NET projects
  • NUnit: Run NUnit tests as a build step
  • Publish over FTP: Send files (e.g. the built game) to an FTP server
  • Version Number: Automatically generate a version number and use it during the job
  • Unity3D: A bit nicer way to start a Unity3D build than using the command line directly

If you made your own experiences with Jenkins, please share your knowledge and leave a comment!

  • Pingback: Unit Testing - Coding with Style in Unity3D()

  • Muhammad Umair Mirza

    You should post how you have setup all the things, Its not that much explained.

    • You’re right, it’s just an overview about the rough structure. Right now I haven’t setup a build server for a project (they are all quite small at the moment, so it would be a bit too much work for them), but as soon as I am working on a bigger one again and have setup a build server, I might add another blog post that goes into the details 🙂

      • Muhammad Umair Mirza

        Thank you for your reply. I was searching for some tutorial as I haven’t used Jenkins, And I am currently setting up some server for automating the builds, I am using windows server and facing a lot of challenges.

        • I remember facing some difficulties in the beginning as well, so don’t give up.

          What was quite useful in the end were “Parameterized jobs”. They are a bit like functions in a programming language. The first parameter is most of the time the workspace to run the job in. With those jobs you can easily split the work into smaller chunks and reuse jobs between multiple builds (e.g. daily, continuous, manual,…)

  • Jim Laflin

    Been trying to setup something similar myself, however can’t find how to configure where the final build of the APK ends up? Don’t suppose you could give me any pointers?

    • Hey Jim!

      If you use a custom script to build you can use one of the BuildPlayerOptions (https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildPlayer.html)

      If you use the command line you can specify it after you choose the platform (e.g. -buildWindowsPlayer https://docs.unity3d.com/Manual/CommandLineArguments.html) For Android I can’t find a command line argument where you can specify the output path though.

      Hope this helps!

      • Jim Laflin

        Thanks for that, I’ll give it a go at the weekend 🙂

      • Jim Laflin

        I tried this and got it working, thanks! I had to use the custom script, it was pretty simple from there. I’ve had to abandon Jenkins for now though as whilst our Android builds from the Editor work great, the ones from Jenkins have loads of images missing making the build impossible to use. We can’t see any differences between the images that do work and those that don’t, and haven’t got time to figure it out unfortunately as a deadline looms. Thanks again for your help

        • Yeah, I heard about that problem already, but haven’t got the time to investigate why it occurs. If you stumble upon a solution, feel free to post it 🙂