Stream: ideas

Topic: A Roc-based CI system


view this post on Zulip Jasper Woudenberg (Mar 10 2024 at 17:10):

I've long found it frustrating setting up CI for projects. For a while I've been thinking about what my ideal CI system would look like. Lately I've started wondering if this would work well as a Roc platform.

Below are a couple of problems I've encountered with CI systems I'm familiar with:

Based on the above, my ideal CI system would include the following:

I'm curious if the above resonates with anyone. Some questions (but other questions and thoughts are welcome too):

view this post on Zulip Isaac Van Doren (Mar 10 2024 at 17:54):

I definitely resonate with the issues you mention and think Roc would work very well as part of a solution! Similar sentiments have come up before on Zulip. I was about to link to the CI issue Greg created on the book repo but I see that you already found it :smiley:. I think basic-cli would work well as a starting point at least

view this post on Zulip Brendan Hansknecht (Mar 10 2024 at 19:45):

Reminds me of some work on rbt (Roc Build Tool) which was going to be a build system with roc as the configuration language

view this post on Zulip Luke Boswell (Mar 10 2024 at 20:00):

https://github.com/roc-lang/rbt

view this post on Zulip Luke Boswell (Mar 10 2024 at 20:01):

I dont think it's been worked on for quite a while, but just sharing in case this helps in any way.

view this post on Zulip Eli Dowling (Mar 10 2024 at 22:43):

I totally agree (I made a similar thread here before) for related research I'd suggest checking out dagger.io and occurrent which Ocaml-ci is built on https://github.com/ocurrent/ocurrent

The Ocurrent authors have some great blog posts about how it works internally, very interesting stuff

view this post on Zulip Jasper Woudenberg (Mar 20 2024 at 17:18):

Some additional thoughts on design of a roc-ci platform.

I think compatibility with platforms like Github Actions is important.
A lot of small projects use free CI platforms because they're free,
having to set up CI servers (and pay for them) would be a deal breaker for these projects.

Another thing important to me is making it possible to run Roc CI locally.

Both goals could be achieved by having separate concepts of job specifications and runners.
The CI job specification is the Roc code describing the steps CI should execute.
The roc-ci platform comes with runners for executing that job specification in different places.

Examples of runners:

Another type of runner to consider is one modeled after Jenkins CI.
Jenkins is self-hosted CI software where you bring your own CI machines that Jenkins joins in a cluster.
It's appealing to have a self-hosted option that does not require running a cluster, a considerable cost.

However, supporting a Jenkins-like CI model adds a lot of requirements the other runner types don't have:

I think the effort it would take to do that correctly might be better directed at creating a Kubernetes alternative.
I'd love a simpler alternative to Kubernetes and even Nomad, and roc-ci could run on top of it.
Scrapping the Jenkins model as a design goal for roc-ci would reduce scope for the project a lot.

view this post on Zulip Anton (Mar 20 2024 at 17:43):

Scrapping it as a design goal for roc-ci itself though

What does "it" refer to exactly?

view this post on Zulip Jasper Woudenberg (Mar 20 2024 at 18:48):

Anton said:

Scrapping it as a design goal for roc-ci itself though

What does "it" refer to exactly?

I meant designing for a Jenkins-like CI setup, where you bring your own servers and the CI software turns them into a CI cluster.

I'll update the message above to clarify. Thanks!

view this post on Zulip Anton (Mar 20 2024 at 18:53):

Yeah, not designing for the Jenkins-like setup sounds wise

view this post on Zulip Jasper Woudenberg (Mar 21 2024 at 22:46):

I've been doing some design experiments on the train today. Here's an example of what a pipeline definition might look like:
https://github.com/jwoudenberg/roc-ci/blob/main/package/Example.roc
(some but not a lot of implementation/validation in other files in that repo)

My goal here was to let steps be plain Task-returning roc functions. The pipeline composes these together into a DAG, but without allowing access to values that will later be passed between steps. Individual steps might run in different machines/containers, so having logic run in the space between them would be tricky implementation-wise (where would that logic run?).

If this is going to become a platform, some of this Roc code might have to be rewritten in a different language eventually. Not doing that now because (1) I like prototyping in Roc, and (2) this is also for the Roc book.

Feedback very welcome!

view this post on Zulip Luke Boswell (Mar 21 2024 at 22:47):

I wonder if you could use RecordBuilder to help here?

view this post on Zulip Luke Boswell (Mar 21 2024 at 22:48):

I'm not an expert with applicative functors, but I feel like there is a design somewhere here which might be nice

view this post on Zulip Richard Feldman (Mar 21 2024 at 22:53):

yeah I think record builder would be a good fit for a build system because then you can generate a graph of it without having to run it! :smiley:

view this post on Zulip Eli Dowling (Mar 21 2024 at 23:54):

That's exactly what I came here to say. Having a representation of the pipeline that can then be used to generate a gui and visualisations of what steps failed and such is super important

view this post on Zulip Luke Boswell (Mar 22 2024 at 00:08):

Also, it would be super to have a more real-world example of record builders, so if this works out for CI then that will be a nice use case to show people I think.

view this post on Zulip Jasper Woudenberg (Mar 22 2024 at 08:30):

Thanks! I definitely want the applicative property of being able to get a representation of the pipeline without needing to run it. One other place where that will come in useful: for generating .github/workflow output files.

I think what I currently have has this applicative property, but it's hard to recognize the record builder pattern in it because the state used during construction needs to account for types between steps, i.e. if you want to run step2 after step1, the return type of step1 needs to be the same as the type of an argument of step2. I'm going to ponder a bit if there's ways to make the record builder pattern more appearant in the code and/or prose for the chapter.

An observation: A common thread in your comments is the leap from "want to get a dependency graph without running any steps" to "applicatives/record builders". I'm wondering if it's a good idea to teach that thought process in the chapter, or whether it'd become too abstract.

view this post on Zulip Luke Boswell (Mar 22 2024 at 09:56):

It took some helpful guidance from @Agus Zubiaga where he walked me through the RecordBuilder before I understood what was going on.

If you can find a concrete use case that fits well with it, then I think it could be a great way to talk about the concept.

It is more advanced than some topics and involves understanding types. Maybe that would make it a good candidate for a later chapter?

it's hard to recognize the record builder pattern in it because the state used during construction needs to account for types between steps, i.e. if you want to run step2 after step1, the return type of step1 needs to be the same as the type of an argument of step2

I would say this is a good reason why you would reach for the record builder. You use it to make a type safe API by building up the state in steps and each step can have a different type.

I'm definitely not an expert here, as I only recently learnt about these myself and wrote that guide to remember how they work.


Last updated: Jun 16 2026 at 16:19 UTC