🚧 WIP: Under Construction 🚧
An opam plugin and library for building Github Action workflows in OCaml. For the plugin to work your project must use opam and dune.
We're still working out the kinks so you need to pin it!
$ opam pin add --yes git+https://github.com/patricoferris/opam-github-workflow
$ cd <my-cool-project>
$ opam github-workflow test
OCaml-CI is an OCurrent-powered CI pipeline for OCaml projects. If you can add yourself to that CI I would highly recommend it. Otherwise, you can make Github do the work for you by using the opam github-workflow ci
command. This generates a similar workflow to what happens in OCaml-CI using docker images for the Linux distributions and the Github runners for MacOS and Windows.
The command takes a little time to run as it tries (like OCaml-CI) to be reproducible by:
- Getting the latest sha256 hash of the docker container
- Getting the latest commit hash of the opam-repository
That means running this command twice may produce different results. A nice workflow is to add a dune
file at the root of your project with the following contents:
(dirs :standard .github)
(rule
(alias ci)
(action
(progn
(with-stdout-to
data.out
(run opam github-workflow ci))
(diff? ./.github/workflows/test.yml data.out))))
This allows you to run dune build @ci
which will run the command and suggest changes to your workflow file for you. If you like the changes then you run dune promote
. By default dune ignore files and directories that start with .
and _
hence the (dirs :standard .github)
.
$ opam pin add --yes git+https://github.com/patricoferris/opam-github-workflow
$ opam install github-workflow
The library follows the workflow syntax for Github Actions. Each key for the Yaml has a type such as job
or on
with each having a default that you can build on top of to produce new values.
To do this nicely, each type comes with some functions prefixed with with_
allowing you to write them in a combinator fashion. For example:
# #require "github-workflow"
# open Workflow
# let steps = [
step
|> with_step_name "Hello \o/"
|> with_step_run "echo 'Hello World'"]
File "_none_", line 3, characters 27-29:
Warning 14: illegal backslash escape in string.
val steps : Types.step list =
[{Workflow__.Types.step_name = Some "Hello \\o/"; uses = None;
step_run = Some "echo 'Hello World'"; step_env = None;
step_workdir = None; step_shell = None; with_ = None; step_if = None;
continue_on_err = None}]
Then you can create a new job giving it any name you want by creating a new record type. Note you can generate the to_yaml
function using ppx_deriving_yaml or by hand.
# type test = { test : Types.job }
type test = { test : Types.job; }
# let test_to_yaml t : Yaml.value = `O [ ("test", Types.job_to_yaml t.test) ]
val test_to_yaml : test -> Yaml.value = <fun>
You are now ready to combine everything into a new workflow.
# let () =
let name = "A `Hello World' Workflow" in
let test_job =
{
test =
job "ubuntu-latest"
|> with_steps steps;
}
in
let on = simple_event [ "push"; "pull_request" ] in
let w : test Types.t = t test_job |> with_name name |> with_on on in
Pp.workflow ~drop_null:true test_to_yaml Format.std_formatter w;
name: A `Hello World' Workflow
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Hello \o/
run: echo 'Hello World'
See tests/bin/run.t
for the help page of the plugin tool.