This repository documents and implements our centralized FM CI setup, based on
a standard bhv checkout.
The CI configuration for every FM repositories, including fm-ci (the current
repository), is almost always exactly the following.
trigger:
rules:
- if: $CI_PIPELINE_SOURCE =~ /^(merge_request_event|schedule|wep|api)$/
variables:
FM_CI_TRIGGER_KIND: mr
- if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
FM_CI_TRIGGER_KIND: default
variables:
ORIGIN_CI_COMMIT_SHA: $CI_COMMIT_SHA
ORIGIN_CI_COMMIT_BRANCH: $CI_COMMIT_BRANCH
ORIGIN_CI_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID
ORIGIN_CI_MERGE_REQUEST_LABELS: $CI_MERGE_REQUEST_LABELS
ORIGIN_CI_MERGE_REQUEST_PROJECT_ID: $CI_MERGE_REQUEST_PROJECT_ID
ORIGIN_CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
ORIGIN_CI_MERGE_REQUEST_TARGET_BRANCH_NAME: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
ORIGIN_CI_PIPELINE_SOURCE: $CI_PIPELINE_SOURCE
ORIGIN_CI_PIPELINE_URL: $CI_PIPELINE_URL
ORIGIN_CI_PROJECT_PATH: $CI_PROJECT_PATH
ORIGIN_CI_PROJECT_TITLE: $CI_PROJECT_TITLE
FM_CI_TRIGGER_KIND: $FM_CI_TRIGGER_KIND
trigger:
project: bedrocksystems/formal-methods/fm-ci
branch: trigger-$FM_CI_TRIGGER_KIND
strategy: dependNote: due to the impossibility to trigger a pipeline on a different branch
of the same repository, the configuration for fm-ci (the current repository)
has an extra level of trigger. It first triggers a job on the main branch of
ci-backfire,
which then "backfires" and performs the appropriate trigger on fm-ci.
With the above configuration, CI in FM repository simply amounts to triggering
a job on fm-ci (the current repository). Note that such triggers only target
special "trigger branches", whose name is always prefixed by trigger-.
Important notes:
- Trigger branches should all be kept in sync, and point to the same commit.
- The CI configuration in trigger branches should be as simple as possible.
- Changes to trigger branches are harder to test, and should be avoided.
There are currently two trigger branches:
trigger-mrwhich is target branch of merge request pipelines,trigger-defaultwhich is the target branch of main branch push pipelines.
The separation of target branches is needed because pushing to the main branch
of any FM repository eventually produces "golden artifacts". Such artifact are
thus retrievable with an API request for the latest successful pipeline on the
trigger-default branch. Currently, only NOVA relies on this mechanism, using
the job artifact API.
Triggers set environment variables identifying the originating repository, and associated parameters like merge request labels. All variables should follow the following naming convention:
- Any variable forwarded from the triggering job should have its standard name
prefixed with
ORIGIN_. - All other variables should have a name prefixed with
FM_CI_.
The CI configuration in trigger branches is mostly responsible for dynamically producing a CI configuration, based on the environment variables it receives.
The CI configuration is generated by checking out the relevant fm-ci commit,
and then running the following command.
dune exec -- ./gen/gen.exe ${CI_JOB_TOKEN} config.toml gen-config.ymlThis program takes as input a GitLab token, configuration file config.toml,
and it outputs a YAML file gen-config.yml that is used in a child pipeline.
The config.toml file configures what FM repositories are available, as well
as their relative path in a bhv checkout, the name of their main branch, the
list of their immediate dependencies, and more. Available configurations are
documented in comments withing config.toml.
The generation process is documented in the various OCaml files, the main file
being gen/gen.ml.
The CI::same-branch label can be set in MRs of any FM repository, and it has
the effect of selecting branches of the same name as the MR branch for all the
FM repositories in the bhv checkout, if such a branch exists.
Note that when fm-ci (the current repository) has a branch of the same name,
the CI configuration in the trigger branches checks out that branch before the
generation script is run.
The FM-CI-Compare label can be set in MRs of any FM repository, so as to run
a performance comparison. The label only affects the main building job, called
full-build-compare when the label is set, and full-build otherwise.
When the label is set, the CI configuration generation script must determine a
reference branch for all involved repositories (either just the one triggering
the pipeline in the originating MR, or potentially several repositories if the
CI::same-branch label is also set). For other repositories, the reference is
simply their main branch (as specified in the configuration file). For all the
involved repositories, the reference commit is defined as the git merge-base
of the main branch and the MR branch.
The full-build-compare job proceeds as follows:
- Clone
bhvand checkout the branch for the main build. - Run
make initand checkout the main build commits for all sub-repos. - Run the build, and copy the resulting
_buildfolder. - Run
make gitcleanand checkout the reference build commits for all repos. - Run the build, and copy the resulting
_buildfolder as well. - Run
make gitcleanagain, and checkout the main build commits again. - Generate the performance data based on the two copied
_buildfolders.
Note that step 3 is allowed to fail, but we remember its status so that we can make the full job fail accordingly when the end of the job is reached. This is useful to get partial performance data even if not all files build. Unlike the main build, the reference build is not allowed to fail.
When an MR targets a branch that is different from the configured repository's
main branch, the reference commit used for performance comparison is chosen to
be that of the target branch (or a merge base of that and the MR branch). When
CI::same-branch is set, the reference commit for repos that did not initiate
the MR is taken from a branch with the same name, if it exists. Otherwise, the
reference is taken to be the main branch.
Steps for adding new FM repositories:
- Create a clone of the repository on Gitlab (probably under the
formal-methodsgroup). - Create a protected
br-mainbranch. - Add the repository to
bhv/boards/common/fm.mk. - Add the repository's path to
bhv/.gitignore. - Run
make cloneto check that everything works. - Add the repository to
fm-ci/config.toml(do not forget to update the dependencies). - Add the repository to
fm-workspace's insetup-fmdeps.shFM_REPOS(for external users). TODO: CI tests for this.
In case of permission errors, go to Settings -> CI/CD -> Job token permissions -> CI/CD job token allowlist and add
bedrocksystems with Default permissions.
Note that it is also necessary to update the fm-workspace repository to reflect the changes to repositories.
The dune cache is trimmed by a weekly pipeline.
It simply sets FM_CI_TRIM_DUNE_CACHE=true.
Another weekly pipeline uses FM_CI_FULL_OPAM=true to:
- disable standard CI jobs
- test installing and uninstalling each package in isolation.
Another pipeline weekly builds the new public release docker image.
This sets FM_CI_DOCKER_OPAM=true to:
- disable standard CI jobs
- build a Docker release image using
opam install.
Infrastructure for building the release image is found in the docker folder.
Variable FM_CI_ONLY_FULL_BUILD=true disables everything except the full-build job.
This is only relevant when there is an fm-ci branch. In that case, we should
inspect the repos configuration of both main, and the branch, since they may
not have the same set of repositories.
The logic then needs to be changed in the full-build-compare job, so that we
run make init for bhv also for the reference build.