blob: 0170e232caba7eeb5bd296af3d417db8e258155a [file] [log] [blame] [edit]
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Testing with CI - Rust Compiler Development Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="A guide to developing the Rust compiler (rustc)">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="../pagetoc.css">
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust Compiler Development Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide/edit/main/src/tests/ci.md" title="Suggest an edit" aria-label="Suggest an edit" rel="edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="testing-with-ci"><a class="header" href="#testing-with-ci">Testing with CI</a></h1>
<p>The primary goal of our CI system is to ensure that the <code>main</code> branch of
<code>rust-lang/rust</code> is always in a valid state by passing our test suite.</p>
<p>From a high-level point of view, when you open a pull request at
<code>rust-lang/rust</code>, the following will happen:</p>
<ul>
<li>A small <a href="#pull-request-builds">subset</a> of tests and checks are run after each
push to the PR.
This should help catch common errors.</li>
<li>When the PR is approved, the <a href="https://github.com/bors">bors</a> bot enqueues the PR into a <a href="https://bors.rust-lang.org/queue/rust">merge queue</a>.</li>
<li>Once the PR gets to the front of the queue, bors will create a merge commit
and run the <a href="#auto-builds">full test suite</a> on it.
The merge commit either contains only one specific PR or it can be a <a href="#rollups">"rollup"</a> which
combines multiple PRs together, to reduce CI costs and merge delays.</li>
<li>Once the whole test suite finishes, two things can happen. Either CI fails
with an error that needs to be addressed by the developer, or CI succeeds and
the merge commit is then pushed to the <code>main</code> branch.</li>
</ul>
<p>If you want to modify what gets executed on CI, see <a href="#modifying-ci-jobs">Modifying CI jobs</a>.</p>
<h2 id="ci-workflow"><a class="header" href="#ci-workflow">CI workflow</a></h2>
<!-- date-check: Oct 2024 -->
<p>Our CI is primarily executed on <a href="https://github.com/rust-lang/rust/actions">GitHub Actions</a>, with a single workflow defined
in <a href="https://github.com/rust-lang/rust/blob/HEAD/.github/workflows/ci.yml"><code>.github/workflows/ci.yml</code></a>, which contains a bunch of steps that are
unified for all CI jobs that we execute.
When a commit is pushed to a corresponding branch or a PR, the workflow executes the
<a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/citool"><code>src/ci/citool</code></a> crate, which dynamically generates the specific CI jobs that should be executed.
This script uses the <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a> file as an
input, which contains a declarative configuration of all our CI jobs.</p>
<blockquote>
<p>Almost all build steps shell out to separate scripts. This keeps the CI fairly
platform independent (i.e., we are not overly reliant on GitHub Actions).
GitHub Actions is only relied on for bootstrapping the CI process and for
orchestrating the scripts that drive the process.</p>
</blockquote>
<p>In essence, all CI jobs run <code>./x test</code>, <code>./x dist</code> or some other command with
different configurations, across various operating systems, targets, and platforms.
There are two broad categories of jobs that are executed, <code>dist</code> and non-<code>dist</code> jobs.</p>
<ul>
<li>Dist jobs build a full release of the compiler for a specific platform,
including all the tools we ship through rustup.
Those builds are then uploaded
to the <code>rust-lang-ci2</code> S3 bucket and are available to be locally installed
with the <a href="https://github.com/kennytm/rustup-toolchain-install-master">rustup-toolchain-install-master</a> tool.
The same builds are also used
for actual releases: our release process basically consists of copying those
artifacts from <code>rust-lang-ci2</code> to the production endpoint and signing them.</li>
<li>Non-dist jobs run our full test suite on the platform, and the test suite of
all the tools we ship through rustup;
The amount of stuff we test depends on
the platform (for example some tests are run only on Tier 1 platforms), and
some quicker platforms are grouped together on the same builder to avoid wasting CI resources.</li>
</ul>
<p>Based on an input event (usually a push to a branch), we execute one of three
kinds of builds (sets of jobs).</p>
<ol>
<li>PR builds</li>
<li>Auto builds</li>
<li>Try builds</li>
</ol>
<h3 id="pull-request-builds"><a class="header" href="#pull-request-builds">Pull Request builds</a></h3>
<p>After each push to a pull request, a set of <code>pr</code> jobs are executed.
Currently, these execute the <code>x86_64-gnu-llvm-X</code>, <code>x86_64-gnu-tools</code>, <code>pr-check-1</code>, <code>pr-check-2</code>
and <code>tidy</code> jobs, all running on Linux.
These execute a relatively short
(~40 minutes) and lightweight test suite that should catch common issues.
More specifically, they run a set of lints, they try to perform a cross-compile check
build to Windows mingw (without producing any artifacts), and they test the
compiler using a <em>system</em> version of LLVM.
Unfortunately, it would take too many
resources to run the full test suite for each commit on every PR.</p>
<blockquote>
<p><strong>Note on doc comments</strong></p>
<p>Note that PR CI as of Oct 2024 <!-- datecheck --> by default does not try to
run <code>./x doc xxx</code>. This means that if you have any broken intradoc links that
would lead to <code>./x doc xxx</code> failing, it will happen very late into the full
merge queue CI pipeline.</p>
<p>Thus, it is a good idea to run <code>./x doc xxx</code> locally for any doc comment
changes to help catch these early.</p>
</blockquote>
<p>PR jobs are defined in the <code>pr</code> section of <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a>.
Their results can be observed
directly on the PR, in the "CI checks" section at the bottom of the PR page.</p>
<h3 id="auto-builds"><a class="header" href="#auto-builds">Auto builds</a></h3>
<p>Before a commit can be merged into the <code>main</code> branch, it needs to pass our complete test suite.
We call this an <code>auto</code> build.
This build runs tens of CI jobs that exercise various tests across operating systems and targets.
The full test suite is quite slow;
it can take several hours until all the <code>auto</code> CI jobs finish.</p>
<p>Most platforms only run the build steps, some run a restricted set of tests;
only a subset run the full suite of tests (see Rust's <a href="https://forge.rust-lang.org/release/platform-support.html#rust-platform-support">platform tiers</a>).</p>
<p>Auto jobs are defined in the <code>auto</code> section of <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a>.
They are executed on the <code>auto</code> branch under the <code>rust-lang/rust</code> repository,
and the final result will be reported via a comment made by bors on the corresponding PR.
The live results can be seen on <a href="https://github.com/rust-lang/rust/actions">the GitHub Actions workflows page</a>.</p>
<p>At any given time, at most a single <code>auto</code> build is being executed.
Find out more in <a href="#merging-prs-serially-with-bors">Merging PRs serially with bors</a>.</p>
<h3 id="try-builds"><a class="header" href="#try-builds">Try builds</a></h3>
<p>Sometimes we want to run a subset of the test suite on CI for a given PR, or
build a set of compiler artifacts from that PR, without attempting to merge it.
We call this a "try build".
A try build is started after a user with the proper
permissions posts a PR comment with the <code>@bors try</code> command.</p>
<p>There are several use-cases for try builds:</p>
<ul>
<li>Run a set of performance benchmarks using our <a href="https://github.com/rust-lang/rustc-perf">rustc-perf</a> benchmark suite.
For this, a working compiler build is needed, which can be generated with a
try build that runs the <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile">dist-x86_64-linux</a> CI job, which builds an optimized
version of the compiler on Linux (this job is currently executed by default
when you start a try build).
To create a try build and schedule it for a
performance benchmark, you can use the <code>@bors try @rust-timer queue</code> command combination.</li>
<li>Check the impact of the PR across the Rust ecosystem, using a <a href="crater.html">Crater</a> run.
Again, a working compiler build is needed for this, which can be produced by
the <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile">dist-x86_64-linux</a> CI job.</li>
<li>Run a specific CI job (e.g. Windows tests) on a PR, to quickly test if it
passes the test suite executed by that job.</li>
</ul>
<p>By default, if you send a comment with <code>@bors try</code>, the jobs defined in the <code>try</code> section of
<a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a> will be executed.
We call this mode a "fast try build".
Such a try build will not execute any tests, and it will allow compilation warnings.
It is useful when you want to
get an optimized toolchain as fast as possible, for a Crater run or performance benchmarks,
even if it might not be working fully correctly.
If you want to do a full build for the default try job,
specify its job name in a job pattern (explained below).</p>
<p>If you want to run custom CI jobs in a try build and make sure that they pass all tests and do
not produce any compilation warnings, you can select CI jobs to be executed by specifying a <em>job pattern</em>,
which can be used in one of two ways:</p>
<ul>
<li>You can add a set of <code>try-job: &lt;job pattern&gt;</code> directives to the PR description (described below) and then
simply run <code>@bors try</code>.
CI will read these directives and run the jobs that you have specified.
This is
useful if you want to rerun the same set of try jobs multiple times, after incrementally modifying a PR.</li>
<li>You can specify the job pattern using the <code>jobs</code> parameter of the try command: <code>@bors try jobs=&lt;job pattern&gt;</code>.
This is useful for one-off try builds with specific jobs.
Note that the <code>jobs</code> parameter has a higher priority than the PR description directives.
<ul>
<li>There can also be multiple patterns specified, e.g. <code>@bors try jobs=job1,job2,job3</code>.</li>
</ul>
</li>
</ul>
<p>Each job pattern can either be an exact name of a job or a glob pattern that matches multiple jobs,
for example <code>*msvc*</code> or <code>*-alt</code>.
You can start at most 20 jobs in a single try build.
When using
glob patterns in the PR description, you can optionally wrap them in backticks (<code>`</code>) to avoid GitHub rendering
the pattern as Markdown if it contains e.g. an asterisk. Note that this escaping will not work when using
the <code>@bors jobs=</code> parameter.</p>
<p>The job pattern needs to match one or more jobs defined in the <code>auto</code> or <code>optional</code> sections
of <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a>:</p>
<ul>
<li><code>auto</code> jobs are executed before a commit is merged into the <code>main</code> branch.</li>
<li><code>optional</code> jobs are executed only when explicitly requested via a try build.
They are typically used for tier 2 and tier 3 targets.</li>
</ul>
<p>One reason to do a try build is to do a perf run, as described above, with <code>@rust-timer queue</code>.
This perf build then compares against some commit on main.
With <code>@bors try parent=&lt;sha&gt;</code> you can base your try build and subsequent perf run on a specific commit on <code>main</code>,
to help make the perf comparison as fair as possible.</p>
<blockquote>
<p><strong>Using <code>try-job</code> PR description directives</strong></p>
<ol>
<li>
<p>Identify which set of try-jobs you would like to exercise. You can
find the name of the CI jobs in <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a>.</p>
</li>
<li>
<p>Amend PR description to include a set of patterns (usually at the end
of the PR description), for example:</p>
<pre><code class="language-text">This PR fixes #123456.
try-job: x86_64-msvc
try-job: test-various
try-job: `*-alt`
</code></pre>
<p>Each <code>try-job</code> pattern must be on its own line.</p>
</li>
<li>
<p>Run the prescribed try jobs with <code>@bors try</code>. As aforementioned, this
requires the user to either (1) have <code>try</code> permissions or (2) be delegated
with <code>try</code> permissions by <code>@bors delegate</code> by someone who has <code>try</code>
permissions.</p>
</li>
</ol>
<p>Note that this is usually easier to do than manually edit <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a>.
However, it can be less flexible because you cannot adjust the set of tests
that are exercised this way.</p>
</blockquote>
<p>Try builds are executed on the <code>try</code> branch under the <code>rust-lang/rust</code> repository and
their results can be seen on <a href="https://github.com/rust-lang/rust/actions">the GitHub Actions workflows page</a>,
although usually you will be notified of the result by a comment made by bors on
the corresponding PR.</p>
<p>Multiple try builds can execute concurrently across different PRs, but there can be at most
a single try build running on a single PR at any given time.</p>
<p>Note that try builds are handled using the <a href="https://github.com/rust-lang/bors">new bors</a> implementation.</p>
<h3 id="modifying-ci-jobs"><a class="header" href="#modifying-ci-jobs">Modifying CI jobs</a></h3>
<p>If you want to modify what gets executed on our CI, you can simply modify the
<code>pr</code>, <code>auto</code> or <code>try</code> sections of the <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/github-actions/jobs.yml"><code>jobs.yml</code></a> file.</p>
<p>You can also modify what gets executed temporarily, for example to test a
particular platform or configuration that is challenging to test locally (for
example, if a Windows build fails, but you don't have access to a Windows machine).
Don't hesitate to use CI resources in such situations.</p>
<p>You can perform an arbitrary CI job in two ways:</p>
<ul>
<li>Use the <a href="#try-builds">try build</a> functionality, and specify the CI jobs that
you want to be executed in try builds in your PR description.</li>
<li>Modify the <a href="#pull-request-builds"><code>pr</code></a> section of <code>jobs.yml</code> to specify which
CI jobs should be executed after each push to your PR.
This might be faster than repeatedly starting try builds.</li>
</ul>
<p>To modify the jobs executed after each push to a PR, you can simply copy one of
the job definitions from the <code>auto</code> section to the <code>pr</code> section.
For example, the <code>x86_64-msvc</code> job is responsible for running the 64-bit MSVC tests.
You can copy it to the <code>pr</code> section to cause it to be executed after a commit is pushed
to your PR, like this:</p>
<pre><code class="language-yaml">pr:
...
- image: x86_64-gnu-tools
&lt;&lt;: *job-linux-16c
# this item was copied from the `auto` section
# vvvvvvvvvvvvvvvvvv
- image: x86_64-msvc
env:
RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-profiler
SCRIPT: make ci-msvc
&lt;&lt;: *job-windows-8c
</code></pre>
<p>Then you can commit the file and push it to your PR branch on GitHub.
GitHub Actions should then execute this CI job after each push to your PR.</p>
<div class="warning">
<p><strong>After you have finished your experiments, don't forget to remove any changes
you have made to <code>jobs.yml</code>, if they were supposed to be temporary!</strong></p>
<p>A good practice is to prefix <code>[WIP]</code> in PR title while still running try jobs
and <code>[DO NOT MERGE]</code> in the commit that modifies the CI jobs for testing purposes.</p>
</div>
<p>Although you are welcome to use CI, just be conscious that this is a shared
resource with limited concurrency.
Try not to enable too many jobs at once;
one or two should be sufficient in most cases.</p>
<h2 id="merging-prs-serially-with-bors"><a class="header" href="#merging-prs-serially-with-bors">Merging PRs serially with bors</a></h2>
<p>CI services usually test the last commit of a branch merged with the last commit
in <code>main</code>, and while that’s great to check if the feature works in isolation,
it doesn’t provide any guarantee the code is going to work once it’s merged.
Breakages like these usually happen when another, incompatible PR is merged
after the build happened.</p>
<p>To ensure a <code>main</code> branch that works all the time, we forbid manual merges.
Instead, all PRs have to be approved through our bot, <a href="https://github.com/bors">bors</a> (the software
behind it is called <a href="https://github.com/rust-lang/homu">homu</a>).
All the approved PRs are put in a <a href="https://bors.rust-lang.org/queue/rust">merge queue</a>
(sorted by priority and creation date) and are automatically tested one at the time.
If all the builders are green, the PR is merged, otherwise the failure is
recorded and the PR will have to be re-approved again.</p>
<p>Bors doesn’t interact with CI services directly, but it works by pushing the
merge commit it wants to test to specific branches (like <code>auto</code> or <code>try</code>), which
are configured to execute CI checks.
Bors then detects the outcome of the build by listening for either Commit Statuses or Check Runs.
Since the merge commit is
based on the latest <code>main</code> and only one can be tested at the same time, when
the results are green, <code>main</code> is fast-forwarded to that merge commit.</p>
<p>Unfortunately, testing a single PR at a time, combined with our long CI (~2
hours for a full run), means we can’t merge a lot of PRs in a single day, and a
single failure greatly impacts our throughput.
The maximum number of PRs we can merge in a day is around ~10.</p>
<p>The long CI run times, and requirement for a large builder pool, is largely due
to the fact that full release artifacts are built in the <code>dist-</code> builders.
This is worth it because these release artifacts:</p>
<ul>
<li>Allow perf testing even at a later date.</li>
<li>Allow bisection when bugs are discovered later.</li>
<li>Ensure release quality since if we're always releasing, we can catch problems
early.</li>
</ul>
<h3 id="rollups"><a class="header" href="#rollups">Rollups</a></h3>
<p>Some PRs don’t need the full test suite to be executed: trivial changes like
typo fixes or README improvements <em>shouldn’t</em> break the build, and testing every
single one of them for 2+ hours would be wasteful.
To solve this, we regularly create a "rollup", a PR where we merge several pending trivial PRs so
they can be tested together.
Rollups are created manually by a team member using
the "create a rollup" button on the <a href="https://bors.rust-lang.org/queue/rust">merge queue</a>.
The team member uses their judgment to decide if a PR is risky or not.</p>
<h2 id="docker"><a class="header" href="#docker">Docker</a></h2>
<p>All CI jobs, except those on macOS and Windows, are executed inside that
platform’s custom <a href="https://github.com/rust-lang/rust/tree/HEAD/src/ci/docker">Docker container</a>.
This has a lot of advantages for us:</p>
<ul>
<li>
<p>The build environment is consistent regardless of the changes of the
underlying image (switching from the trusty image to xenial was painless for us).</p>
</li>
<li>
<p>We can use ancient build environments to ensure maximum binary compatibility,
for example <a href="https://github.com/rust-lang/rust/blob/HEAD/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile">using older CentOS releases</a> on our Linux builders.</p>
</li>
<li>
<p>We can avoid reinstalling tools (like QEMU or the Android emulator) every time,
thanks to Docker image caching.</p>
</li>
<li>
<p>Users can run the same tests in the same environment locally by just running this command:</p>
<pre><code>cargo run --manifest-path src/ci/citool/Cargo.toml run-local &lt;job-name&gt;
</code></pre>
<p>This is helpful for debugging failures.
Note that there are only Linux Docker images available locally due to licensing and
other restrictions.</p>
</li>
</ul>
<p>The Docker images prefixed with <code>dist-</code> are used for building artifacts while
those without that prefix run tests and checks.</p>
<p>We also run tests for less common architectures (mainly Tier 2 and Tier 3 platforms) in CI.
Since those platforms are not x86, we either run everything
inside QEMU, or we just cross-compile if we don’t want to run the tests for that platform.</p>
<p>These builders are running on a special pool of builders set up and maintained for us by GitHub.</p>
<h2 id="caching"><a class="header" href="#caching">Caching</a></h2>
<p>Our CI workflow uses various caching mechanisms, mainly for two things:</p>
<h3 id="docker-images-caching"><a class="header" href="#docker-images-caching">Docker images caching</a></h3>
<p>The Docker images we use to run most of the Linux-based builders take a <em>long</em> time to fully build.
To speed up the build, we cache them using <a href="https://docs.docker.com/build/cache/backends/registry/">Docker registry
caching</a>, with the intermediate artifacts being stored on <a href="https://github.com/rust-lang/rust/pkgs/container/rust-ci">ghcr.io</a>.
We also push the built Docker images to ghcr, so that they can be reused by other tools
(rustup) or by developers running the Docker build locally (to speed up their build).</p>
<p>Since we test multiple, diverged branches (<code>main</code>, <code>beta</code> and <code>stable</code>), we
can’t rely on a single cache for the images, otherwise builds on a branch would
override the cache for the others.
Instead, we store the images under different
tags, identifying them with a custom hash made from the contents of all the
Dockerfiles and related scripts.</p>
<p>The CI calculates a hash key, so that the cache of a Docker image is
invalidated if one of the following changes:</p>
<ul>
<li>Dockerfile</li>
<li>Files copied into the Docker image in the Dockerfile</li>
<li>The architecture of the GitHub runner (x86 or ARM)</li>
</ul>
<h3 id="llvm-caching-with-sccache"><a class="header" href="#llvm-caching-with-sccache">LLVM caching with Sccache</a></h3>
<p>We build some C/C++ stuff in various CI jobs, and we rely on <a href="https://github.com/mozilla/sccache">Sccache</a> to cache
the intermediate LLVM artifacts.
Sccache is a distributed ccache developed by
Mozilla, which can use an object storage bucket as the storage backend.</p>
<p>With Sccache there's no need to calculate the hash key ourselves.
Sccache invalidates the cache automatically when it detects changes to relevant inputs,
such as the source code, the version of the compiler, and important environment variables.
So we just pass the Sccache wrapper on top of Cargo and Sccache does the rest.</p>
<p>We store the persistent artifacts on the S3 bucket, <code>rust-lang-ci-sccache2</code>.
So when the CI runs, if Sccache sees that LLVM is being compiled with the same C/C++
compiler and the LLVM source code is the same, Sccache retrieves the individual
compiled translation units from S3.</p>
<h2 id="custom-tooling-around-ci"><a class="header" href="#custom-tooling-around-ci">Custom tooling around CI</a></h2>
<p>During the years, we developed some custom tooling to improve our CI experience.</p>
<h3 id="rust-log-analyzer-to-show-the-error-message-in-prs"><a class="header" href="#rust-log-analyzer-to-show-the-error-message-in-prs">Rust Log Analyzer to show the error message in PRs</a></h3>
<p>The build logs for <code>rust-lang/rust</code> are huge, and it’s not practical to find
what caused the build to fail by looking at the logs.
We therefore developed a bot called <a href="https://github.com/rust-lang/rust-log-analyzer">Rust Log Analyzer</a> (RLA) that
receives the build logs on failure, and extracts the error message automatically,
posting it on the PR thread.</p>
<p>The bot is not hardcoded to look for error strings, but was trained with a bunch
of build failures to recognize which lines are common between builds and which are not.
While the generated snippets can be weird sometimes, the bot is pretty
good at identifying the relevant lines, even if it’s an error we've never seen before.</p>
<h3 id="toolstate-to-support-allowed-failures"><a class="header" href="#toolstate-to-support-allowed-failures">Toolstate to support allowed failures</a></h3>
<p>The <code>rust-lang/rust</code> repo doesn’t only test the compiler on its CI, but also a
variety of tools and documentation.
Some documentation is pulled in via git submodules.
If we blocked merging rustc PRs on the documentation being fixed, we
would be stuck in a chicken-and-egg problem, because the documentation's CI
would not pass since updating it would need the not-yet-merged version of rustc
to test against (and we usually require CI to be passing).</p>
<p>To avoid the problem, submodules are allowed to fail, and their status is
recorded in <a href="https://rust-lang-nursery.github.io/rust-toolstate">rust-toolstate</a>.
When a submodule breaks, a bot automatically pings
the maintainers so they know about the breakage, and it records the failure on
the toolstate repository.
The release process will then ignore broken tools on
nightly, removing them from the shipped nightlies.</p>
<p>While tool failures are allowed most of the time, they’re automatically
forbidden a week before a release: we don’t care if tools are broken on nightly
but they must work on beta and stable, so they also need to work on nightly a
few days before we promote nightly to beta.</p>
<p>More information is available in the <a href="https://forge.rust-lang.org/infra/toolstate.html">toolstate documentation</a>.</p>
<h2 id="public-ci-dashboard"><a class="header" href="#public-ci-dashboard">Public CI dashboard</a></h2>
<p>To monitor the Rust CI, you can have a look at the <a href="https://p.datadoghq.com/sb/3a172e20-e9e1-11ed-80e3-da7ad0900002-b5f7bb7e08b664a06b08527da85f7e30">public dashboard</a> maintained by the infra team.</p>
<p>These are some useful panels from the dashboard:</p>
<ul>
<li>Pipeline duration: check how long the auto builds take to run.</li>
<li>Top slowest jobs: check which jobs are taking the longest to run.</li>
<li>Change in median job duration: check what jobs are slowest than before. Useful
to detect regressions.</li>
<li>Top failed jobs: check which jobs are failing the most.</li>
</ul>
<p>To learn more about the dashboard, see the <a href="https://docs.datadoghq.com/continuous_integration/">Datadog CI docs</a>.</p>
<h2 id="determining-the-ci-configuration"><a class="header" href="#determining-the-ci-configuration">Determining the CI configuration</a></h2>
<p>If you want to determine which <code>bootstrap.toml</code> settings are used in CI for a
particular job, it is probably easiest to just look at the build log.
To do this:</p>
<ol>
<li>Go to
<a href="https://github.com/rust-lang/rust/actions?query=branch%3Aauto+is%3Asuccess">https://github.com/rust-lang/rust/actions?query=branch%3Aauto+is%3Asuccess</a>
to find the most recently successful build, and click on it.</li>
<li>Choose the job you are interested in on the left-hand side.</li>
<li>Click on the gear icon and choose "View raw logs"</li>
<li>Search for the string "Configure the build"</li>
<li>All of the build settings are listed on the line with the text, <code>build.configure-args</code></li>
</ol>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tests/docker.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tests/adding.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../tests/docker.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tests/adding.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
<script src="../mermaid.min.js"></script>
<script src="../mermaid-init.js"></script>
<script src="../pagetoc.js"></script>
</div>
</body>
</html>