This file provides a brief explanation of the organization of rippled's Github configuration for actions, workflows, and templates.
The ISSUE_TEMPLATE
folder holds several files used to improve the
experience of creating an issue.
config.yml
configures external links to XRPL resources that are provided when a user starts the process of creating a new issue. Currently, that includes XRPL documentation and Ripple's bug bounty program.bug_report.md
is the template used to create bug reports.feature_request.md
is the template used to create feature requests.
Additionally, pull_request_template.md
in this folder is the template
used when opening Pull Requests.
The workflows
folder holds several files that describe Github Actions
workflows.
doxygen.yml
builds and publishes the
rippled developer documentation using doxygen
whenever a new commit or
commits are published to the develop
branch.
clang-format.yml
checks that source code is correctly formatted. It runs
all rippled source files through clang-format
, and fails if any changes
are found. If it fails, it publishes a patch file as an artifact that the
developer can apply to their branch, as well as instructions on how to use
it.
levelization.yml
checks for changes in source code levelization, and
raises an alert if any changes are found (good or bad). See the levelization
documentation for more information.
The design of Github Actions allows workflows to be re-run. However, the workflow must be run as a whole and in it's entirety. If a single job in the workflow fails, it can not be run by itself. Spurious failures should be rare, but if they happen, it would be convenient to not have to run all 45 or so jobs just to retry the one. Thus the continuous integration tasks were split into seven different workflows: five for Linux jobs, and one each for MacOS and Windows. This allows a small subset of the jobs to be re-run if necessary. One file describes one workflow.
The workflows are generally built using some or all of the following stages:
- Build and/or download common dependencies, and store them in a cache
intended to be shared by later stages. For example, boost, vcpkg, or the
external projects required by our
cmake
configuration. - Build and test "base" configurations. A base configuration is one that
builds
rippled
using default settings other than compiler, debug/release, and unity/non-unity. - Use the cache from the base configuration to build and usually test a special case configuration (e.g. Coverage or Reporting mode).
- Use the
rippled
"base" artifact to run a non-default test scenario (e.g. manual tests, or IPv6 tests).
Every build and test job, whether base or special case, will store some of
the intermediate build-results (e.g. NIH cache, ccache
folders, vcpkg
libraries) in a job-specific cache. Additionally, each build and test job
will upload two artifacts: The cmake logs, and the rippled
executable built
by the job.
linux-general.yml
stages:- Pre-downloads dependencies into "NIH" ("Not Invented Here") caches.
- Builds and tests
rippled
using all of the combinations of the following except those built in the special case jobs below.- gcc-8, gcc-9, clang-8, clang-9, and clang-10
- Debug and Release
- unity and non-unity
- There is no stage 3 in this workflow.
- There is no stage 4 in this workflow.
linux-clang8-debug.yml
stages:- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-general.yml
, it will be used. - Builds and tests a base
rippled
using- clang-8
- Debug
- unity
- Using the cache from the base stage, builds and tests the following
special configurations:
- Reporting
- Coverage
- There is no stage 4 in this workflow.
- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-clang8-release.yml
stages:- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-general.yml
, it will be used. - Builds and tests a base
rippled
using- clang-8
- Release
- unity
- Using the cache from the base stage, builds and tests the following
special configurations:
- Address sanitizer (asan)
- Undefined behavior sanitizer (ubsan)
- A thread sanitizer (tsan) job is defined, but disabled, because it currently fails to run.
- There is no stage 4 in this workflow.
- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-gcc8-debug.yml
stages:- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-general.yml
, it will be used. - Builds and tests a base
rippled
using- gcc-8
- Debug
- unity
- Using the cache from the base stage, builds and tests the following
special configurations:
- Coverage
- Non-static
- Non-static with shared libraries
- Makefile (instead of Ninja)
- Minimum cmake version
- The separate
validator-keys
application
- Uses the
rippled
artifact from the base job to run all of the manual tests defined inripple
except a few that use too much memory to run on Github's hosts.
- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-gcc8-release.yml
stages:- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
linux-general.yml
, it will be used. - Builds and tests a base
rippled
using- gcc-8
- Release
- unity
- There is no stage 3 in this workflow.
- Uses the
rippled
artifact from the base job to run all of the manual tests defined inripple
except a few that use too much memory to run on Github's hosts.
- There is no stage 1 in this workflow, but if there is an appropriate
NIH cache available from
macos.yml
stages:- Pre-downloads dependencies into an "NIH" ("Not Invented Here") cache,
and builds Boost into a separate cache if not already cached. Two
caches are used because Boost changes very rarely, and is not modified
by the
rippled
build process, so it can be used across many different jobs without conflict, and saving space. - Builds and tests
rippled
using:- Debug and Release
- There is no stage 3 in this workflow.
- Uses the Debug
rippled
artifact from the base job to run the IPv6 tests using the--unittest-ipv6
command line parameter.
- Pre-downloads dependencies into an "NIH" ("Not Invented Here") cache,
and builds Boost into a separate cache if not already cached. Two
caches are used because Boost changes very rarely, and is not modified
by the
windows.yml
stages:- Pre-downloads dependencies into an "NIH" ("Not Invented Here") cache,
and builds vcpkg library dependencies and boost into separate
caches if not already cached. Three caches are used because the
vcpkg and boost libraries change very rarely, and are not modified
by the
rippled
build process, so they can be used across many different jobs without conflict, and avoid duplication across jobs. - Builds and tests
rippled
using the following configurations. (Note that MSVC 2019 Debug builds do not run tests by default due to unresolved issues with how it handlesstatic constexpr char*
variables. The tests can be forced to run by including "ci_run_win" in the git commit message.)- Ninja generator, MSVC 2017, Debug, unity
- Ninja generator, MSVC 2017, Release, unity
- Ninja generator, MSVC 2019, Debug, unity (without tests)
- Ninja generator, MSVC 2019, Release, unity
- Visual Studio 2019 generator, MSVC 2017, Debug, non-unity
- Visual Studio 2019 generator, MSVC 2017, Release, non-unity
- Visual Studio 2019 generator, MSVC 2019, Release, non-unity
- There is no stage 3 in this workflow.
- There is no stage 4 in this workflow.
- Pre-downloads dependencies into an "NIH" ("Not Invented Here") cache,
and builds vcpkg library dependencies and boost into separate
caches if not already cached. Three caches are used because the
vcpkg and boost libraries change very rarely, and are not modified
by the
Github Actions caches are immutable once written, and use explicit key names for access. Caches can be shared across workflows, and can match partial names by prefix. They can also access caches created by base and default branches, and to the forked from (parent) repository, but not across arbitrary forks, forked to (child) repositories, and distinct branches. Finally, they have a relatively short expiration time (7 days), and a relatively small size limit (5Gb).
The caching policies used by these workflows attempt to take advantage of these properties to save as much time as possible when building, while minimizing space when feasible. There is almost certainly room for improvement.
Thus, for example, the linux-general.yml
workflow downloads the "NIH"
dependencies into a single cache (per docker image). All of the source and
git timestamp files for these dependencies are stored in a single folder,
which is reused across jobs, and not duplicated across job-specific caches.
Each "base" job stores dependency build output along with ccache
output.
This significantly speeds up subsequent builds of the same
base job.
Once the base job is done, any "special case" jobs (e.g. Coverage, address sanitizer) use their base job's cache to initialize their specific caches. This further helps reduce duplicated effort, which helps speed up those jobs.
Finally, most caches build their cache key using values from the job
configuration and some components based on the hashes of the cmake
configuration, the rippled source code, and the workflow config itself.
To pull it all together with an example, the base job in
linux-clang8-release.yml
might have a cache key that looks like (hashes
abbreviated for readability and simplicity):
Linux-ecbd-clang-8-Release-ON-base-40b1-5fec-a88b
Once that job finishes, the "asan" job's cache key would look like:Linux-ecbd-clang-8-Release-ON-asan-40b1-5fec-a88b
- It would be initialized using
Linux-ecbd-clang-8-Release-ON-base-40b1-5fec-a88b
.
- It would be initialized using
Once the whole workflow finishes, the developer makes a change to source and pushes a new commit. The new cache key might look like.
Linux-ecbd-clang-8-Release-ON-base-abcd-1234-a88b
No cache with that key is found, so it looks forLinux-ecbd-clang-8-Release-ON-base-abcd-1234
Linux-ecbd-clang-8-Release-ON-base-abcd
Linux-ecbd-clang-8-Release-ON-base
That last prefix matches the cache from the previous run (Linux-ecbd-clang-8-Release-ON-base-40b1-5fec-a88b
), and initializes with that. Chances are that most of that cache will be useful to that new build, and will cut the build time significantly.
Once the base job finishes, the "asan" job's cache key would be:
Linux-ecbd-clang-8-Release-ON-asan-abcd-1234-a88b
And would initialize from the just-finishedLinux-ecbd-clang-8-Release-ON-base-abcd-1234-a88b
The components are organized in the following order
- Operating system: Caches aren't useful to be shared across OSes
- Hashes of the
cmake
config: Any changes to thecmake
config can have significant changes on the way that code is organized, dependencies are organized, dependency folders are organized, etc., which would render the caches incompatible. So to be safe, caches with differentcmake
configs can never be reused.- Additionally, this hash includes the file
.github/workflows/cache-invalidate.txt
. This file can be manually changed to force new builds to start with fresh caches in case some unforseen change causes the build to fail with a reused cache.
- Additionally, this hash includes the file
- Compiler
- Build type (Debug/Release)
- Unity flag
- Job name
- Hash of all the header files under
src
: Because changing one header file is more likely to affect a bunch of different object files, when this changes, it invalidates more. - Hash of all the source files under
src
, including headers: Changing any source file is going to generate a new build. But with the same header hash, a build cache that is likely to be very similar can be reused. - Hash of the workflow instructions (the yml file, and the
build-action/action.yml
if appropriate). If the workflow is changed without changing any of the source, a new cache may be needed, but it can be seeded with a previous build of the same source code.
The build-action
folder holds an action.yml
that is used by all of the
Linux workflows to do the actual build without tons of duplication.
Unfortunately, not all types of commands can by used in an action, so there
is still some boilerplate required in each job: checkout, cache, artifacts,
plus any steps that get displayed in a separate section in the Github UI.