Creating GitHub Build Status Badges for Xcode Cloud Builds

Xcode Cloud doesn’t have status badges at the moment. Can we build one?

Joe Diragi
Better Programming

--

We all love a good README.md. It’s the first thing that people who are coming to our repo will see. It very much could be the one thing that makes a potential contributor turn away from your project.

A good README.md gives a detailed description of the project, how to contribute code, maybe an installation/build guide and how to create issues. An excellent README.md will highlight the status of the project, display inviting and exciting screenshots, and just generally get readers excited about the project.

This is where status badges come in. While status badges don’t make your project great on their own, they can very much contribute to that overall feeling of polish. Displaying your project’s CI build/test status shows confidence in your work. If you write crummy test cases and your builds fail unless you stand on one foot and spin around three times before pressing ‘Build’, you wouldn’t want to advertise that to the world!

The Problem

There are plenty of CI/CD services that provide these build badges with their service. And the ones that don’t often have some third-party option available (see: shields.io). Since Xcode Cloud is so new and, well, Apple©, there doesn’t yet exist a service that provides a build status badge. How can I flex my programming prowess in these conditions?

The Solution

We’re treading into uncharted waters here, so StackOverflow can’t help us this time.

I set out to find a way to communicate with the outside world from the build machine after a build completes. I could certainly do this using a custom build script that would send a message to a server with a report containing the build status and how many of my tests passed.

That may be a bit overkill, though. After some more digging in the docs, I discovered webhooks. Basically, when a workflow finishes running, Xcode Cloud will send a POST request to a server of my choosing containing a detailed report of how the build went.

I won’t post it here, but if you take a look at the webhooks documentation, Apple graciously provides an example request body. “Perfect”, I exclaim, alone in my living room </3.

Planning

The implementation will contain one (1) part. An HTTP webserver. Obviously there are a lot of options when it comes to building a webserver. I chose to use Deno for a multitude of reasons, the most important being Deno Deploy. I’m a broke college student with a baby on the way so paying for any type of cloud hosting is not exactly an option right now. With Heroku’s free tier going away I had to find an alternative. Deno has an awesome free tier that will be more than enough for what I’m using it for.

Aside from Deno Deploy, Deno is an amazing Javascript/Typescript framework that comes with all of the tools I need. It’s very similar to node.js (in fact, it’s made by the same guy who built node), only it’s a lot more modern and secure. Plus it comes with first-class TypeScript support.

Enough Deno hype, how am I going to build this thing?

The server needs to receive the POST request, parse the body and respond in some way. After that, it needs to have an endpoint that we can pass to our README.md that generates a status badge based on the build status we received from the webhook.

The Implementation

The first part is simple enough in Deno:

I like using types in TypeScript, so I went ahead and modeled the response using TypeScript interfaces. You can check that out here.

The code simply listens for requests on the /builder endpoint and parses the data accordingly. I set up some global properties that will change based on the request data. Inside of the parseWorkflow method, I loop through theBuildActions (one of: ANALYZE, BUILD or TEST) and update the global variables based on the values we got in the request.

This approach should work just fine using Deno Deploy, but if you’re deploying somewhere that works like Heroku’s free tier did, you’ll want to write these values to a persistent store somewhere. In fact, I will probably wind up doing that myself eventually. You can keep up with my progress at the GitHub repo.

Next, we need to setup the badge endpoints. For simplicity’s sake, I’ll use shields.io to generate them. This is done by passing some values to a shields.io link like so:

https://shields.io/badge/<label>-<message>-<color>

A URL for a green tests badge that displays the number of passing tests might look like this:

https://shields.io/badge/Tests-4%20Passing-green

We can create the endpoint by adding this code to the requestHandler:

And just like that, we can deploy our server to a hosting service and set the /builder endpoint as a webhook on App Store Connect.

Once everything is deployed and the webhook is set up, we can include the badge in our README.md like so:

![Build status badge](https://ourserver.com/badges/build-status)

Conclusion

And just like that, we did it baby.

It’s not perfect, though. I really want to have the values stored in a postgres database to make sure if our runner is ever restarted or interrupted at all the status will not be changed inadvertently. There also seems to be some craziness going on with the way GitHub handles caching assets in READMEs. Overall, this can be a great way to implement status badges for Xcode Cloud. Hopefully one day I can create a service that will allow users to sign up and create their own badges. It’s definitely something I could do, I just can’t afford hosting at the moment. Users would generate their own endpoints that could receive messages from Xcode Cloud webhooks and they wouldn’t have to deploy their own server to accomplish this. Build status would be saved in a database and when Xcode pings the server, it would find the user in the database, update their build settings and the badges endpoint would render badges appropriately for each user.

Don’t forget to check out and star the GitHub repo for the complete implementation and keep up with future updates!

Update 09/08/22

Over the last day or so, I’ve been going crazy trying to get the badges working with GitHub. This discussion over on GitHub highlights the problem I’m facing with caching. You can see I’ve pushed several commits trying to get my deployed server working properly. I’ve tried setting Cache-Control, ETag, and Expires in the response header all to no avail. It seems as though GitHub’s camo media caching just doesn’t respect the header values that are passed to it. Their documentation says simply editing the Cache-Control header should take care of it. It doesn’t. I’ve set and verified response headers, tried every number of combinations and just nothing seems to work at all. It doesn’t make any sense.

However, GitLab’s README renderer works *perfectly*. Every time the badge is updated on the server it’s also updated on the README on GitLab. Obviously, I’d much prefer these badges work on GitHub. It just looks like there’s no way to make GitHub respect my Cache-Control.

--

--