Logo

Roger Clotet Solé

Hello, World!

I'm a dad and a software craftsman based in Girona
I build stuff for the web and distributed systems
I love learning, photography, videogames, and driving

Latest posts

A GitLab CI workflow for an Android game made with Godot

Did I need to automate everything related to building and releasing my game Drift Survivors? Of course not. I'm the only developer and I could simply do builds manually and upload them to the Google Play Console as I did with other small games I created.

The thing is, the more you automate, the easier it is to develop and, more importantly, to come back after a few months to add a new feature or fix some bug. Otherwise, it's likely you don't remember how the build worked, what keystore or password you need, or where exactly in the Google Play Console you uploaded versions. To me, not needing to remember a thing is saving effort you can be spending doing something more productive.

Overview

My current continuous integration pipeline is something like this:

Continuous integration worflow by stage

Or if we look at it by dependencies:

Continuous integration workflow by dependencies

I'll explain and copy the configuration for each state. For now, every job is only run when a tag is created (only: - tags), since it's when I want to create a new release, enabling me to just push commits to the main branch when I want. Since I'm usually the only developer in the project, it makes sense to skip creating new branches for most small changes.

Build stage

.godot-android:
  image: rogerclotet/godot-ci:4.0.2
  cache:
    key: import-assets
    paths:
      - .godot/imported/
  before_script:
    - echo $SECRET_RELEASE_KEYSTORE_BASE64 | base64 --decode > /root/release.keystore
    - echo "textures/vram_compression/import_etc2_astc=true" >> project.godot
    - echo "Opening editor to import assets..."
    - godot -v -e --quit --headless 2>&1 | grep -v "StringName"
    - sed 's@keystore/release=".*"@keystore/release="'/root/release.keystore'"@g' -i export_presets.cfg
    - sed 's@keystore/release_user=".*"@keystore/release_user="'$SECRET_RELEASE_KEYSTORE_USER'"@g' -i export_presets.cfg
    - sed 's@keystore/release_password=".*"@keystore/release_password="'$SECRET_RELEASE_KEYSTORE_PASSWORD'"@g' -i export_presets.cfg
    - sed 's@version/code=.*@version/code='$(git show -s --format=%ct $CI_COMMIT_TAG)'@g' -i export_presets.cfg
    - sed 's@version/name=".*"@version/name="'$CI_COMMIT_TAG'"@g' -i export_presets.cfg

android_aab:
  stage: build
  extends: .godot-android
  script:
    - godot -q --headless --export-release "Android AAB" $AAB_FILE 2>&1 | grep -v "StringName"
    - if ! [[ -f "$AAB_FILE" ]]; then exit 1; fi
  only:
    - tags
  artifacts:
    paths:
      - $AAB_FILE

android_apk:
  stage: build
  extends: .godot-android
  script:
    - godot -q --headless --export-release "Android APK" $APK_FILE 2>&1 | grep -v "StringName"
    - if ! [[ -f "$APK_FILE" ]]; then exit 1; fi
  only:
    - tags
  artifacts:
    paths:
      - $APK_FILE
  • .godot-android is the shared configuration for the 2 jobs in this stage. It defines the image used (a fork of https://github.com/aBARICHELLO/godot-ci/ to be able to use Godot 4 until the new fixes are merged), the cache directories for imported assets, and some configuration steps for both the Godot editor and the project settings, to prepare for the build itself. It also runs the Godot editor in headless mode and quitting right after that, just to make sure assets are imported and the project is properly initialized.
  • android_aab extends this, builds the AAB export configuration and makes sure the file has been generated correctly, and marks it as an artifact to be exported. An AAB file is what we need to upload the game to the Play Store.
  • android_apk is exactly like android_aab but it creates an APK file, which can be installed directly to any Android device. I used two separate jobs for this to export the two files separately, to make it easier to make them downloadable, as we'll see below.

Publish Stage

publish_packages:
  stage: publish
  needs: [android_aab, android_apk]
  image: curlimages/curl:latest
  script:
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $AAB_FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${AAB_FILE}"'
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $APK_FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${APK_FILE}"'
  only:
    - tags

release:
  stage: publish
  needs: [publish_packages]
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  only:
    - tags
  before_script:
    - apk add git
  script:
    - echo "Creating release $CI_COMMIT_TAG..."
  release:
    tag_name: $CI_COMMIT_TAG
    description: |
      Changes:
      $(git log $(git describe --abbrev=0 --tags --exclude=$CI_COMMIT_TAG)..$CI_COMMIT_TAG --oneline --no-decorate --reverse | sed "s/^[^ ]* /- /g")
    assets:
      links:
        - name: AAB
          url: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${AAB_FILE}
          link_type: package
        - name: APK
          url: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${APK_FILE}
          link_type: package

.fastlane:
  image: cijumbo/fastlane
  before_script:
    - bundle update fastlane
    - curl -s https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer | bash
  dependencies:
    - android_aab

deploy_to_internal:
  stage: publish
  needs: [android_aab]
  extends: .fastlane
  script: bundle exec fastlane supply --track internal --aab $EXPORT_NAME.$CI_COMMIT_TAG.aab
  only:
    - tags
  • publish_packages publishes the two exported files (AAB and APK) as generic packages to GitLab. This allows us to have a permanent link to the files in the release, since linking to build artifacts is not recommended.
  • release creates the GitLab release, automatically gathering changes descriptions from the commits since the last release, and linking the packages from the previous job.
  • .fastlane is a shared job configuration used by deploy_to_internal and promote_to_production, which we'll see later. It uses an image that includes fastlane, a tool to automate releases and changes to apps in the Play Store and the App Store. It also downloads the needed secure files from GitLab, namely the keystore to sign the app we want to deploy.
  • deploy_to_internal uses fastlane to upload the AAB package to the internal track in the Play Store, used for internal testing with limited approved users. After I test everything looks okay when I update the game on my phone, we can go to the final stage:

Production stage

promote_to_production:
  stage: production
  needs: [deploy_to_internal]
  extends: .fastlane
  when: manual
  script: bundle exec fastlane supply --track internal --track_promote_to production --version_code $(git show -s --format=%ct $CI_COMMIT_TAG)
  dependencies: []
  only:
    - tags
  • promote_to_production promotes the version we tested in the internal track to the production1 track, which will be reviewed by Google and made available to all users after that. This step must be triggered manually, in case the internal version was not suitable for production.

We could add some intermediate steps, like going th the alpha or beta tracks before that, to allow some users to start testing the new update before going to production, but for now that's more than enough.

Conclusion

Automating your releases isn't free, it took a lot of trial and error, but I think it has many benefits. It also generates some nice releases in the repository, where your users can download the exported packages even if they don't use the Play Store, or just want to test an old version of the game. The releases look like this:

Release example

This set up was created for my game Drift Survivors, which you can already get for free in the Play Store, and you can see a more updated version of the configuration here.

See all posts in the blog

Work Experience

Resume

TravelPerk Logo
TravelPerk

Senior Software Engineer

June 2019 - March 2020, November 2021 - April 2024

I worked in TravelPerk as a senior full-stack software engineer, using React for frontend and Python with Django and Tornado for backend.

One of my significant contributions was a large effort transitioning the frontend codebase from JavaScript with Flow typing to TypeScript, enhancing maintainability and uncovering latent bugs.

I also contributed to shared backend projects, significantly improving developer experience and code quality. I actively participated in backend and frontend guilds to promote best practices among developers.

I was also involved in hiring and onboarding new people to the team, improving and refining processes and interviews.

I left to improve my work-life balance and rejoined when remote work became an option.

Typeform Logo
Typeform

Senior Software Engineer

March 2020 - November 2021

I was the sole backend engineer in a team that was responsible for the form building aspect of the company.

My responsibilities included updating and maintaining several backend services, while also adding new features to enhance our offerings. Despite being primarily focused on backend development, I also contributed to parts of the frontend, ensuring a seamless user experience.

My efforts played a significant role in improving the developer experience and elevating the overall code quality.

Social Point Logo
Social Point

Backend Engineer / Senior Backend Engineer

June 2012 - May 2019

At Social Point, I developed the server side of highly successful mobile games, catering to millions of daily active users. I worked in a 5-person team for 5 years and later in a 2-person team for a year.

One of my significant contributions was leading a project that introduced the first real-time backend part of our system developed in Go, and improving the continuous deployment strategy for real-time servers.

I encountered numerous challenges, primarily related to concurrency and high availability. I primarily used PHP (with Symfony) and Go in AWS.

Personal projects

These are some of my personal projects. Most things I develop as side projects don't end up anywhere and only serve as learning experiences, but these are the ones I think are worth sharing the most:

Drift Survivors - 2023

A mobile game inspired in Vampire Survivors, where you drive a car drifting around a desert, running over enemies, and shooting them with several weapons, in order to survive as long as possible while waves of stronger and stronger enemies come to you.

This game was developed using Godot and using custom pixel art. The architecture of the project is built with expandability in mind, and it's easy to add new weapons and upgrades to the game.

You can download and play the game on Android from the Play Store.

Source code

Drift Survivors

Project Minesweeper - 2021

This is a multiplayer minesweeper game I built to learn about Socket.IO and NodeJS, inspired by Minesweeper Flags.

It uses real-time communication with server state persistence. It has a chat, editable user profiles, configurable web and desktop notifications, and a game list with board preview to manage multiple concurrent games.

On the technical side, I used ReactJS, the previously mentioned Socket.IO, MaterialUI, tailwindcss, and MongoDB.

You can read more about it in the blog: Project Minesweeper Part 1: Introduction and Tech

Source code

Project Minesweeper

Blocs! - 2020

A small puzzle game for mobile. The first game I published on the Google Play Store.

The gameplay is inspired by other puzzle games I tried before, but were full of ads and in-app purchases. I started learning game development some time before, took part in a game jam, and wanted to make a simple game to "test my skills", and this seemed like a good way of doing it.

It was made with Unity, art was created with Aseprite, and has local saves for high scores, last games and current game state, localization in Catalan, English, and Spanish, and different color schemes to choose from.

You can install and play the game for free on Android, or play now from mobile or desktop on https://blocs.clotet.dev.

Source code

Blocs!

Amic Invisible webapp - 2018

A webapp to manage Secret Santa groups with friends and family, built as a prototype with plans to rewrite it in a better way in the future.

It allows you to create groups, invite people using a link or using the share dialog in your phone, decide randomly who is each other's secret Santa, and share a "wish list" to help her or him get ideas for your gift.

Amic Invisible webapp

Ja Arribarem Club website - 2006

A website for a popular walk event I've built, iterated upon, and maintained since 2006.

It started as a static site to provide basic information of the current year's event, and since then I've rewritten it a couple of times to what it is today: s Symfony application with an administration zone to edit and add new contents every year and require minimal maintenance from my side.

Source code

Ja Arribarem Club website

That's it!

I'm always working in side projects and willing to collaborate.

Feel free to get in touch!