about summary refs log tree commit diff
path: root/src/ci
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-01-08 09:23:40 +0100
committerRalf Jung <post@ralfj.de>2025-01-08 09:23:40 +0100
commit2d180714e14b34e36bf883bdf706ebaf5fa96754 (patch)
tree36b4829675d832d323283fc37457f6509f8a519c /src/ci
parentfc4a52f598f6259a98964aa3290e9e74e396c1e3 (diff)
parent67f49010adf4ec3238564ec072b3652179813dd1 (diff)
downloadrust-2d180714e14b34e36bf883bdf706ebaf5fa96754.tar.gz
rust-2d180714e14b34e36bf883bdf706ebaf5fa96754.zip
Merge from rustc
Diffstat (limited to 'src/ci')
-rw-r--r--src/ci/docker/README.md27
-rw-r--r--src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile100
-rw-r--r--src/ci/docker/host-x86_64/dist-aarch64-linux/Dockerfile32
-rw-r--r--src/ci/docker/host-x86_64/dist-aarch64-linux/aarch64-linux-gnu.defconfig10
-rw-r--r--src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile9
-rw-r--r--src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile2
-rw-r--r--src/ci/docker/host-x86_64/dist-powerpc64le-linux/shared.sh16
-rw-r--r--src/ci/docker/host-x86_64/dist-riscv64-linux/patches/gcc/8.5.0/0002-hidden-jump-target.patch2
-rw-r--r--src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile10
-rw-r--r--src/ci/docker/host-x86_64/dist-x86_64-linux/shared.sh16
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-llvm-18/Dockerfile4
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-llvm-19/Dockerfile4
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile4
-rwxr-xr-xsrc/ci/docker/scripts/build-clang.sh (renamed from src/ci/docker/host-x86_64/dist-x86_64-linux/build-clang.sh)10
-rwxr-xr-xsrc/ci/docker/scripts/build-gcc.sh (renamed from src/ci/docker/host-x86_64/dist-x86_64-linux/build-gcc.sh)10
-rwxr-xr-xsrc/ci/docker/scripts/build-gccjit.sh (renamed from src/ci/docker/host-x86_64/dist-x86_64-linux/build-gccjit.sh)0
-rwxr-xr-xsrc/ci/docker/scripts/build-zstd.sh (renamed from src/ci/docker/host-x86_64/dist-x86_64-linux/build-zstd.sh)0
-rwxr-xr-xsrc/ci/github-actions/ci.py (renamed from src/ci/github-actions/calculate-job-matrix.py)146
-rw-r--r--src/ci/github-actions/jobs.yml148
19 files changed, 349 insertions, 201 deletions
diff --git a/src/ci/docker/README.md b/src/ci/docker/README.md
index 508b7b40c01..2f52ff5a99a 100644
--- a/src/ci/docker/README.md
+++ b/src/ci/docker/README.md
@@ -1,29 +1,30 @@
 # Docker images for CI
 
 This folder contains a bunch of docker images used by the continuous integration
-(CI) of Rust. An script is accompanied (`run.sh`) with these images to actually
-execute them. To test out an image execute:
+(CI) of Rust. A script is accompanied (`run.sh`) with these images to actually
+execute them.
 
-```
-./src/ci/docker/run.sh $image_name
-```
+Note that a single Docker image can be used by multiple CI jobs, so the job name
+is the important thing that you should know. You can examine the existing CI jobs in
+the [`jobs.yml`](../github-actions/jobs.yml) file.
 
-for example:
+To run a specific CI job locally, you can use the following script:
 
 ```
-./src/ci/docker/run.sh x86_64-gnu
+python3 ./src/ci/github-actions/ci.py run-local <job-name>
 ```
 
-Images will output artifacts in an `obj/$image_name` dir at the root of a repository. Note
-that the script will overwrite the contents of this directory.
-
-To match conditions in rusts CI, also set the environment variable `DEPLOY=1`, e.g.:
+For example, to run the `x86_64-gnu-llvm-18-1` job:
 ```
-DEPLOY=1 ./src/ci/docker/run.sh x86_64-gnu
+python3 ./src/ci/github-actions/ci.py run-local x86_64-gnu-llvm-18-1
 ```
 
+The job will output artifacts in an `obj/<image-name>` dir at the root of a repository. Note
+that the script will overwrite the contents of this directory. `<image-name>` is set based on the
+Docker image executed in the given CI job.
+
 **NOTE**: In CI, the script outputs the artifacts to the `obj` directory,
-while locally, to the `obj/$image_name` directory. This is primarily to prevent
+while locally, to the `obj/<image-name>` directory. This is primarily to prevent
 strange linker errors when using multiple Docker images.
 
 For some Linux workflows (for example `x86_64-gnu-llvm-18-N`), the process is more involved. You will need to see which script is executed for the given workflow inside the [`jobs.yml`](../github-actions/jobs.yml) file and pass it through the `DOCKER_SCRIPT` environment variable. For example, to reproduce the `x86_64-gnu-llvm-18-3` workflow, you can run the following script:
diff --git a/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile
new file mode 100644
index 00000000000..8b0fb0710d4
--- /dev/null
+++ b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile
@@ -0,0 +1,100 @@
+# We document platform support for minimum glibc 2.17 and kernel 3.2.
+# CentOS 7 has headers for kernel 3.10, but that's fine as long as we don't
+# actually use newer APIs in rustc or std without a fallback. It's more
+# important that we match glibc for ELF symbol versioning.
+FROM centos:7
+
+WORKDIR /build
+
+# CentOS 7 EOL is June 30, 2024, but the repos remain in the vault.
+RUN sed -i /etc/yum.repos.d/*.repo -e 's!^mirrorlist!#mirrorlist!' \
+  -e 's!^#baseurl=http://mirror.centos.org/!baseurl=https://vault.centos.org/!'
+RUN sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf
+
+RUN yum upgrade -y && \
+    yum install -y \
+      automake \
+      bzip2 \
+      file \
+      gcc \
+      gcc-c++ \
+      git \
+      glibc-devel \
+      libedit-devel \
+      libstdc++-devel \
+      make \
+      ncurses-devel \
+      openssl-devel \
+      patch \
+      perl \
+      perl-core \
+      pkgconfig \
+      python3 \
+      unzip \
+      wget \
+      xz \
+      zlib-devel \
+      && yum clean all
+
+RUN mkdir -p /rustroot/bin
+
+ENV PATH=/rustroot/bin:$PATH
+ENV LD_LIBRARY_PATH=/rustroot/lib64:/rustroot/lib32:/rustroot/lib
+ENV PKG_CONFIG_PATH=/rustroot/lib/pkgconfig
+WORKDIR /tmp
+RUN mkdir /home/user
+COPY scripts/shared.sh /tmp/
+
+# Need at least GCC 5.1 to compile LLVM
+COPY scripts/build-gcc.sh /tmp/
+ENV GCC_VERSION=9.5.0
+ENV GCC_BUILD_TARGET=aarch64-unknown-linux-gnu
+RUN ./build-gcc.sh && yum remove -y gcc gcc-c++
+
+ENV CC=gcc CXX=g++
+
+# LLVM 17 needs cmake 3.20 or higher.
+COPY scripts/cmake.sh /tmp/
+RUN ./cmake.sh
+
+# Build LLVM+Clang
+COPY scripts/build-clang.sh /tmp/
+ENV LLVM_BUILD_TARGETS=AArch64
+RUN ./build-clang.sh
+ENV CC=clang CXX=clang++
+
+# Build zstd to enable `llvm.libzstd`.
+COPY scripts/build-zstd.sh /tmp/
+RUN ./build-zstd.sh
+
+COPY scripts/sccache.sh /scripts/
+RUN sh /scripts/sccache.sh
+
+ENV PGO_HOST=aarch64-unknown-linux-gnu
+ENV HOSTS=aarch64-unknown-linux-gnu
+
+ENV CPATH=/usr/include/aarch64-linux-gnu/:$CPATH
+
+ENV RUST_CONFIGURE_ARGS \
+      --build=aarch64-unknown-linux-gnu \
+      --enable-full-tools \
+      --enable-profiler \
+      --enable-sanitizers \
+      --enable-compiler-docs \
+      --set target.aarch64-unknown-linux-gnu.linker=clang \
+      --set target.aarch64-unknown-linux-gnu.ar=/rustroot/bin/llvm-ar \
+      --set target.aarch64-unknown-linux-gnu.ranlib=/rustroot/bin/llvm-ranlib \
+      --set llvm.link-shared=true \
+      --set llvm.thin-lto=true \
+      --set llvm.libzstd=true \
+      --set llvm.ninja=false \
+      --set rust.debug-assertions=false \
+      --set rust.jemalloc \
+      --set rust.use-lld=true \
+      --set rust.codegen-units=1
+
+ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS
+
+ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=clang
+ENV LIBCURL_NO_PKG_CONFIG 1
+ENV DIST_REQUIRE_ALL_TOOLS 1
diff --git a/src/ci/docker/host-x86_64/dist-aarch64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-aarch64-linux/Dockerfile
deleted file mode 100644
index 18972387e34..00000000000
--- a/src/ci/docker/host-x86_64/dist-aarch64-linux/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-FROM ubuntu:22.04
-
-COPY scripts/cross-apt-packages.sh /scripts/
-RUN sh /scripts/cross-apt-packages.sh
-
-COPY scripts/crosstool-ng.sh /scripts/
-RUN sh /scripts/crosstool-ng.sh
-
-COPY scripts/rustbuild-setup.sh /scripts/
-RUN sh /scripts/rustbuild-setup.sh
-WORKDIR /tmp
-
-COPY scripts/crosstool-ng-build.sh /scripts/
-COPY host-x86_64/dist-aarch64-linux/aarch64-linux-gnu.defconfig /tmp/crosstool.defconfig
-RUN /scripts/crosstool-ng-build.sh
-
-COPY scripts/sccache.sh /scripts/
-RUN sh /scripts/sccache.sh
-
-ENV PATH=$PATH:/x-tools/aarch64-unknown-linux-gnu/bin
-
-ENV CC_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-gcc \
-    AR_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-ar \
-    CXX_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-g++
-
-ENV HOSTS=aarch64-unknown-linux-gnu
-
-ENV RUST_CONFIGURE_ARGS \
-      --enable-full-tools \
-      --enable-profiler \
-      --enable-sanitizers
-ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS
diff --git a/src/ci/docker/host-x86_64/dist-aarch64-linux/aarch64-linux-gnu.defconfig b/src/ci/docker/host-x86_64/dist-aarch64-linux/aarch64-linux-gnu.defconfig
deleted file mode 100644
index 520b1667c8b..00000000000
--- a/src/ci/docker/host-x86_64/dist-aarch64-linux/aarch64-linux-gnu.defconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-CT_CONFIG_VERSION="4"
-CT_PREFIX_DIR="/x-tools/${CT_TARGET}"
-CT_USE_MIRROR=y
-CT_MIRROR_BASE_URL="https://ci-mirrors.rust-lang.org/rustc"
-CT_ARCH_ARM=y
-CT_ARCH_64=y
-CT_KERNEL_LINUX=y
-CT_LINUX_V_4_1=y
-CT_GLIBC_V_2_17=y
-CT_CC_LANG_CXX=y
diff --git a/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile
index 7e946df6163..ea5b208a7c1 100644
--- a/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile
@@ -19,6 +19,7 @@ RUN yum upgrade -y && \
       gcc \
       gcc-c++ \
       git \
+      binutils.i686 \
       glibc-devel.i686 \
       glibc-devel.x86_64 \
       libedit-devel \
@@ -46,11 +47,12 @@ ENV LD_LIBRARY_PATH=/rustroot/lib64:/rustroot/lib32:/rustroot/lib
 ENV PKG_CONFIG_PATH=/rustroot/lib/pkgconfig
 WORKDIR /tmp
 RUN mkdir /home/user
-COPY host-x86_64/dist-x86_64-linux/shared.sh /tmp/
+COPY scripts/shared.sh /tmp/
 
 # Need at least GCC 5.1 to compile LLVM nowadays
-COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/
+COPY scripts/build-gcc.sh /tmp/
 ENV GCC_VERSION=9.5.0
+ENV GCC_BUILD_TARGET=i686-pc-linux-gnu
 RUN ./build-gcc.sh && yum remove -y gcc gcc-c++
 
 COPY scripts/cmake.sh /tmp/
@@ -58,7 +60,8 @@ RUN ./cmake.sh
 
 # Now build LLVM+Clang, afterwards configuring further compilations to use the
 # clang/clang++ compilers.
-COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/
+COPY scripts/build-clang.sh /tmp/
+ENV LLVM_BUILD_TARGETS=X86
 RUN ./build-clang.sh
 ENV CC=clang CXX=clang++
 
diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile
index 9ef39189249..9d3be51d037 100644
--- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile
@@ -18,7 +18,7 @@ RUN /scripts/crosstool-ng-build.sh
 WORKDIR /build
 
 RUN apt-get install -y --no-install-recommends rpm2cpio cpio
-COPY host-x86_64/dist-powerpc64le-linux/shared.sh host-x86_64/dist-powerpc64le-linux/build-powerpc64le-toolchain.sh /build/
+COPY scripts/shared.sh host-x86_64/dist-powerpc64le-linux/build-powerpc64le-toolchain.sh /build/
 RUN ./build-powerpc64le-toolchain.sh
 
 COPY scripts/sccache.sh /scripts/
diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/shared.sh b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/shared.sh
deleted file mode 100644
index dc86dddd464..00000000000
--- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/shared.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-hide_output() {
-  set +x
-  on_err="
-echo ERROR: An error was encountered with the build.
-cat /tmp/build.log
-exit 1
-"
-  trap "$on_err" ERR
-  bash -c "while true; do sleep 30; echo \$(date) - building ...; done" &
-  PING_LOOP_PID=$!
-  "$@" &> /tmp/build.log
-  trap - ERR
-  kill $PING_LOOP_PID
-  set -x
-}
diff --git a/src/ci/docker/host-x86_64/dist-riscv64-linux/patches/gcc/8.5.0/0002-hidden-jump-target.patch b/src/ci/docker/host-x86_64/dist-riscv64-linux/patches/gcc/8.5.0/0002-hidden-jump-target.patch
index 7ae4469428b..1ae0ecf6cb5 100644
--- a/src/ci/docker/host-x86_64/dist-riscv64-linux/patches/gcc/8.5.0/0002-hidden-jump-target.patch
+++ b/src/ci/docker/host-x86_64/dist-riscv64-linux/patches/gcc/8.5.0/0002-hidden-jump-target.patch
@@ -10,7 +10,7 @@ https://sourceware.org/bugzilla/show_bug.cgi?id=28509
 And this is the first version of the proposed binutils patch,
 https://sourceware.org/pipermail/binutils/2021-November/118398.html
 
-After applying the binutils patch, I get the the unexpected error when
+After applying the binutils patch, I get the unexpected error when
 building libgcc,
 
 /scratch/nelsonc/riscv-gnu-toolchain/riscv-gcc/libgcc/config/riscv/div.S:42:
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile
index bbb4fe216a5..dde6fe7f6d0 100644
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile
@@ -47,11 +47,12 @@ ENV PKG_CONFIG_PATH=/rustroot/lib/pkgconfig
 # Clang needs to access GCC headers to enable linker plugin LTO
 WORKDIR /tmp
 RUN mkdir /home/user
-COPY host-x86_64/dist-x86_64-linux/shared.sh /tmp/
+COPY scripts/shared.sh /tmp/
 
 # Need at least GCC 5.1 to compile LLVM nowadays
-COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/
+COPY scripts/build-gcc.sh /tmp/
 ENV GCC_VERSION=9.5.0
+ENV GCC_BUILD_TARGET=x86_64-pc-linux-gnu
 RUN ./build-gcc.sh && yum remove -y gcc gcc-c++
 
 # LLVM 17 needs cmake 3.20 or higher.
@@ -60,12 +61,13 @@ RUN ./cmake.sh
 
 # Now build LLVM+Clang, afterwards configuring further compilations to use the
 # clang/clang++ compilers.
-COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/
+COPY scripts/build-clang.sh /tmp/
+ENV LLVM_BUILD_TARGETS=X86
 RUN ./build-clang.sh
 ENV CC=clang CXX=clang++
 
 # Build zstd to enable `llvm.libzstd`.
-COPY host-x86_64/dist-x86_64-linux/build-zstd.sh /tmp/
+COPY scripts/build-zstd.sh /tmp/
 RUN ./build-zstd.sh
 
 COPY scripts/sccache.sh /scripts/
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/shared.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/shared.sh
deleted file mode 100644
index dc86dddd464..00000000000
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/shared.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-hide_output() {
-  set +x
-  on_err="
-echo ERROR: An error was encountered with the build.
-cat /tmp/build.log
-exit 1
-"
-  trap "$on_err" ERR
-  bash -c "while true; do sleep 30; echo \$(date) - building ...; done" &
-  PING_LOOP_PID=$!
-  "$@" &> /tmp/build.log
-  trap - ERR
-  kill $PING_LOOP_PID
-  set -x
-}
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-18/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-18/Dockerfile
index e157debfd09..0a58f337d9d 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-18/Dockerfile
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-18/Dockerfile
@@ -54,8 +54,8 @@ ENV RUST_CONFIGURE_ARGS \
       --set rust.randomize-layout=true \
       --set rust.thin-lto-import-instr-limit=10
 
-COPY host-x86_64/dist-x86_64-linux/shared.sh /scripts/
-COPY host-x86_64/dist-x86_64-linux/build-gccjit.sh /scripts/
+COPY scripts/shared.sh /scripts/
+COPY scripts/build-gccjit.sh /scripts/
 
 RUN /scripts/build-gccjit.sh /scripts
 
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-19/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-19/Dockerfile
index e7016e7d3c0..092847cdfe0 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-19/Dockerfile
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-19/Dockerfile
@@ -54,8 +54,8 @@ ENV RUST_CONFIGURE_ARGS \
       --set rust.randomize-layout=true \
       --set rust.thin-lto-import-instr-limit=10
 
-COPY host-x86_64/dist-x86_64-linux/shared.sh /scripts/
-COPY host-x86_64/dist-x86_64-linux/build-gccjit.sh /scripts/
+COPY scripts/shared.sh /scripts/
+COPY scripts/build-gccjit.sh /scripts/
 
 RUN /scripts/build-gccjit.sh /scripts
 
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile
index 2a09cd54b13..ab749b3fdd5 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile
@@ -89,8 +89,8 @@ ENV HOST_TARGET x86_64-unknown-linux-gnu
 # assertions enabled! Therefore, we cannot force download CI rustc.
 #ENV FORCE_CI_RUSTC 1
 
-COPY host-x86_64/dist-x86_64-linux/shared.sh /scripts/
-COPY host-x86_64/dist-x86_64-linux/build-gccjit.sh /scripts/
+COPY scripts/shared.sh /scripts/
+COPY scripts/build-gccjit.sh /scripts/
 
 RUN /scripts/build-gccjit.sh /scripts
 
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-clang.sh b/src/ci/docker/scripts/build-clang.sh
index 3c8123d90de..841a0adb2ab 100755
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-clang.sh
+++ b/src/ci/docker/scripts/build-clang.sh
@@ -21,6 +21,12 @@ cd clang-build
 # include path, /rustroot/include, to clang's default include path.
 INC="/rustroot/include:/usr/include"
 
+GCC_PLUGIN_TARGET=$GCC_BUILD_TARGET
+# We build gcc for the i686 job on x86_64 so the plugin will end up under an x86_64 path
+if [[ $GCC_PLUGIN_TARGET == "i686-pc-linux-gnu" ]]; then
+  GCC_PLUGIN_TARGET=x86_64-pc-linux-gnu
+fi
+
 # We need compiler-rt for the profile runtime (used later to PGO the LLVM build)
 # but sanitizers aren't currently building. Since we don't need those, just
 # disable them. BOLT is used for optimizing LLVM.
@@ -34,12 +40,12 @@ hide_output \
       -DCOMPILER_RT_BUILD_XRAY=OFF \
       -DCOMPILER_RT_BUILD_MEMPROF=OFF \
       -DCOMPILER_RT_BUILD_CTX_PROFILE=OFF \
-      -DLLVM_TARGETS_TO_BUILD=X86 \
+      -DLLVM_TARGETS_TO_BUILD=$LLVM_BUILD_TARGETS \
       -DLLVM_INCLUDE_BENCHMARKS=OFF \
       -DLLVM_INCLUDE_TESTS=OFF \
       -DLLVM_INCLUDE_EXAMPLES=OFF \
       -DLLVM_ENABLE_PROJECTS="clang;lld;compiler-rt;bolt" \
-      -DLLVM_BINUTILS_INCDIR="/rustroot/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/plugin/include/" \
+      -DLLVM_BINUTILS_INCDIR="/rustroot/lib/gcc/$GCC_PLUGIN_TARGET/$GCC_VERSION/plugin/include/" \
       -DC_INCLUDE_DIRS="$INC"
 
 hide_output make -j$(nproc)
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-gcc.sh b/src/ci/docker/scripts/build-gcc.sh
index 57d4d338a50..11db5aa811c 100755
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-gcc.sh
+++ b/src/ci/docker/scripts/build-gcc.sh
@@ -51,7 +51,9 @@ cd ..
 rm -rf gcc-build
 rm -rf gcc-$GCC
 
-# FIXME: clang doesn't find 32-bit libraries in /rustroot/lib,
-# but it does look all the way under /rustroot/lib/[...]/32,
-# so we can link stuff there to help it out.
-ln /rustroot/lib/*.{a,so} -rst /rustroot/lib/gcc/x86_64-pc-linux-gnu/$GCC/32/
+if [[ $GCC_BUILD_TARGET == "i686-pc-linux-gnu" ]]; then
+    # FIXME: clang doesn't find 32-bit libraries in /rustroot/lib,
+    # but it does look all the way under /rustroot/lib/[...]/32,
+    # so we can link stuff there to help it out.
+    ln /rustroot/lib/*.{a,so} -rst /rustroot/lib/gcc/x86_64-pc-linux-gnu/$GCC/32/
+fi
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-gccjit.sh b/src/ci/docker/scripts/build-gccjit.sh
index c565922dcd1..c565922dcd1 100755
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-gccjit.sh
+++ b/src/ci/docker/scripts/build-gccjit.sh
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-zstd.sh b/src/ci/docker/scripts/build-zstd.sh
index a3d37ccc311..a3d37ccc311 100755
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/build-zstd.sh
+++ b/src/ci/docker/scripts/build-zstd.sh
diff --git a/src/ci/github-actions/calculate-job-matrix.py b/src/ci/github-actions/ci.py
index 1f994f0ffd2..b7dac412dbe 100755
--- a/src/ci/github-actions/calculate-job-matrix.py
+++ b/src/ci/github-actions/ci.py
@@ -1,18 +1,20 @@
 #!/usr/bin/env python3
 
 """
-This script serves for generating a matrix of jobs that should
-be executed on CI.
+This script contains CI functionality.
+It can be used to generate a matrix of jobs that should
+be executed on CI, or run a specific CI job locally.
 
-It reads job definitions from `src/ci/github-actions/jobs.yml`
-and filters them based on the event that happened on CI.
+It reads job definitions from `src/ci/github-actions/jobs.yml`.
 """
 
+import argparse
 import dataclasses
 import json
 import logging
 import os
 import re
+import subprocess
 import typing
 from pathlib import Path
 from typing import List, Dict, Any, Optional
@@ -25,13 +27,19 @@ JOBS_YAML_PATH = Path(__file__).absolute().parent / "jobs.yml"
 Job = Dict[str, Any]
 
 
-def name_jobs(jobs: List[Dict], prefix: str) -> List[Job]:
+def add_job_properties(jobs: List[Dict], prefix: str) -> List[Job]:
     """
-    Add a `name` attribute to each job, based on its image and the given `prefix`.
+    Modify the `name` attribute of each job, based on its base name and the given `prefix`.
+    Add an `image` attribute to each job, based on its image.
     """
+    modified_jobs = []
     for job in jobs:
-        job["name"] = f"{prefix} - {job['image']}"
-    return jobs
+        # Create a copy of the `job` dictionary to avoid modifying `jobs`
+        new_job = dict(job)
+        new_job["image"] = get_job_image(new_job)
+        new_job["full_name"] = f"{prefix} - {new_job['name']}"
+        modified_jobs.append(new_job)
+    return modified_jobs
 
 
 def add_base_env(jobs: List[Job], environment: Dict[str, str]) -> List[Job]:
@@ -39,11 +47,15 @@ def add_base_env(jobs: List[Job], environment: Dict[str, str]) -> List[Job]:
     Prepends `environment` to the `env` attribute of each job.
     The `env` of each job has higher precedence than `environment`.
     """
+    modified_jobs = []
     for job in jobs:
         env = environment.copy()
         env.update(job.get("env", {}))
-        job["env"] = env
-    return jobs
+
+        new_job = dict(job)
+        new_job["env"] = env
+        modified_jobs.append(new_job)
+    return modified_jobs
 
 
 @dataclasses.dataclass
@@ -116,7 +128,9 @@ def find_run_type(ctx: GitHubCtx) -> Optional[WorkflowRunType]:
 
 def calculate_jobs(run_type: WorkflowRunType, job_data: Dict[str, Any]) -> List[Job]:
     if isinstance(run_type, PRRunType):
-        return add_base_env(name_jobs(job_data["pr"], "PR"), job_data["envs"]["pr"])
+        return add_base_env(
+            add_job_properties(job_data["pr"], "PR"), job_data["envs"]["pr"]
+        )
     elif isinstance(run_type, TryRunType):
         jobs = job_data["try"]
         custom_jobs = run_type.custom_jobs
@@ -130,7 +144,7 @@ def calculate_jobs(run_type: WorkflowRunType, job_data: Dict[str, Any]) -> List[
             jobs = []
             unknown_jobs = []
             for custom_job in custom_jobs:
-                job = [j for j in job_data["auto"] if j["image"] == custom_job]
+                job = [j for j in job_data["auto"] if j["name"] == custom_job]
                 if not job:
                     unknown_jobs.append(custom_job)
                     continue
@@ -140,10 +154,10 @@ def calculate_jobs(run_type: WorkflowRunType, job_data: Dict[str, Any]) -> List[
                     f"Custom job(s) `{unknown_jobs}` not found in auto jobs"
                 )
 
-        return add_base_env(name_jobs(jobs, "try"), job_data["envs"]["try"])
+        return add_base_env(add_job_properties(jobs, "try"), job_data["envs"]["try"])
     elif isinstance(run_type, AutoRunType):
         return add_base_env(
-            name_jobs(job_data["auto"], "auto"), job_data["envs"]["auto"]
+            add_job_properties(job_data["auto"], "auto"), job_data["envs"]["auto"]
         )
 
     return []
@@ -181,12 +195,64 @@ def format_run_type(run_type: WorkflowRunType) -> str:
         raise AssertionError()
 
 
-if __name__ == "__main__":
-    logging.basicConfig(level=logging.INFO)
+def get_job_image(job: Job) -> str:
+    """
+    By default, the Docker image of a job is based on its name.
+    However, it can be overridden by its IMAGE environment variable.
+    """
+    env = job.get("env", {})
+    # Return the IMAGE environment variable if it exists, otherwise return the job name
+    return env.get("IMAGE", job["name"])
 
-    with open(JOBS_YAML_PATH) as f:
-        data = yaml.safe_load(f)
 
+def is_linux_job(job: Job) -> bool:
+    return "ubuntu" in job["os"]
+
+
+def find_linux_job(job_data: Dict[str, Any], job_name: str, pr_jobs: bool) -> Job:
+    candidates = job_data["pr"] if pr_jobs else job_data["auto"]
+    jobs = [job for job in candidates if job.get("name") == job_name]
+    if len(jobs) == 0:
+        available_jobs = "\n".join(
+            sorted(job["name"] for job in candidates if is_linux_job(job))
+        )
+        raise Exception(f"""Job `{job_name}` not found in {'pr' if pr_jobs else 'auto'} jobs.
+The following jobs are available:
+{available_jobs}""")
+    assert len(jobs) == 1
+
+    job = jobs[0]
+    if not is_linux_job(job):
+        raise Exception("Only Linux jobs can be executed locally")
+    return job
+
+
+def run_workflow_locally(job_data: Dict[str, Any], job_name: str, pr_jobs: bool):
+    DOCKER_DIR = Path(__file__).absolute().parent.parent / "docker"
+
+    job = find_linux_job(job_data, job_name=job_name, pr_jobs=pr_jobs)
+
+    custom_env = {}
+    # Replicate src/ci/scripts/setup-environment.sh
+    # Adds custom environment variables to the job
+    if job_name.startswith("dist-"):
+        if job_name.endswith("-alt"):
+            custom_env["DEPLOY_ALT"] = "1"
+        else:
+            custom_env["DEPLOY"] = "1"
+    custom_env.update({k: str(v) for (k, v) in job.get("env", {}).items()})
+
+    args = [str(DOCKER_DIR / "run.sh"), get_job_image(job)]
+    env_formatted = [f"{k}={v}" for (k, v) in sorted(custom_env.items())]
+    print(f"Executing `{' '.join(env_formatted)} {' '.join(args)}`")
+
+    env = os.environ.copy()
+    env.update(custom_env)
+
+    subprocess.run(args, env=env)
+
+
+def calculate_job_matrix(job_data: Dict[str, Any]):
     github_ctx = get_github_ctx()
 
     run_type = find_run_type(github_ctx)
@@ -197,7 +263,7 @@ if __name__ == "__main__":
 
     jobs = []
     if run_type is not None:
-        jobs = calculate_jobs(run_type, data)
+        jobs = calculate_jobs(run_type, job_data)
     jobs = skip_jobs(jobs, channel)
 
     if not jobs:
@@ -208,3 +274,45 @@ if __name__ == "__main__":
     logging.info(f"Output:\n{yaml.dump(dict(jobs=jobs, run_type=run_type), indent=4)}")
     print(f"jobs={json.dumps(jobs)}")
     print(f"run_type={run_type}")
+
+
+def create_cli_parser():
+    parser = argparse.ArgumentParser(
+        prog="ci.py", description="Generate or run CI workflows"
+    )
+    subparsers = parser.add_subparsers(
+        help="Command to execute", dest="command", required=True
+    )
+    subparsers.add_parser(
+        "calculate-job-matrix",
+        help="Generate a matrix of jobs that should be executed in CI",
+    )
+    run_parser = subparsers.add_parser(
+        "run-local", help="Run a CI jobs locally (on Linux)"
+    )
+    run_parser.add_argument(
+        "job_name",
+        help="CI job that should be executed. By default, a merge (auto) "
+        "job with the given name will be executed",
+    )
+    run_parser.add_argument(
+        "--pr", action="store_true", help="Run a PR job instead of an auto job"
+    )
+    return parser
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.INFO)
+
+    with open(JOBS_YAML_PATH) as f:
+        data = yaml.safe_load(f)
+
+    parser = create_cli_parser()
+    args = parser.parse_args()
+
+    if args.command == "calculate-job-matrix":
+        calculate_job_matrix(data)
+    elif args.command == "run-local":
+        run_workflow_locally(data, args.job_name, args.pr)
+    else:
+        raise Exception(f"Unknown command {args.command}")
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 876a7793592..6bf4a75d080 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -91,26 +91,26 @@ envs:
 # These jobs automatically inherit envs.pr, to avoid repeating
 # it in each job definition.
 pr:
-  - image: mingw-check
+  - name: mingw-check
     <<: *job-linux-4c
-  - image: mingw-check-tidy
+  - name: mingw-check-tidy
     continue_on_error: true
     <<: *job-linux-4c
-  - image: x86_64-gnu-llvm-18
+  - name: x86_64-gnu-llvm-18
     env:
       ENABLE_GCC_CODEGEN: "1"
       # We are adding (temporarily) a dummy commit on the compiler
       READ_ONLY_SRC: "0"
       DOCKER_SCRIPT: x86_64-gnu-llvm.sh
     <<: *job-linux-16c
-  - image: x86_64-gnu-tools
+  - name: x86_64-gnu-tools
     <<: *job-linux-16c
 
 # Jobs that run when you perform a try build (@bors try)
 # These jobs automatically inherit envs.try, to avoid repeating
 # it in each job definition.
 try:
-  - image: dist-x86_64-linux
+  - name: dist-x86_64-linux
     env:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-linux-16c
@@ -123,106 +123,106 @@ auto:
   #   Linux/Docker builders   #
   #############################
 
-  - image: aarch64-gnu
+  - name: aarch64-gnu
     <<: *job-aarch64-linux
 
-  - image: aarch64-gnu-debug
+  - name: aarch64-gnu-debug
     <<: *job-aarch64-linux
 
-  - image: arm-android
+  - name: arm-android
     <<: *job-linux-4c
 
-  - image: armhf-gnu
+  - name: armhf-gnu
     <<: *job-linux-4c
 
-  - image: dist-aarch64-linux
+  - name: dist-aarch64-linux
     env:
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-linux-4c
+    <<: *job-aarch64-linux
 
-  - image: dist-android
+  - name: dist-android
     <<: *job-linux-4c
 
-  - image: dist-arm-linux
+  - name: dist-arm-linux
     <<: *job-linux-8c
 
-  - image: dist-armhf-linux
+  - name: dist-armhf-linux
     <<: *job-linux-4c
 
-  - image: dist-armv7-linux
+  - name: dist-armv7-linux
     <<: *job-linux-4c
 
-  - image: dist-i586-gnu-i586-i686-musl
+  - name: dist-i586-gnu-i586-i686-musl
     <<: *job-linux-4c
 
-  - image: dist-i686-linux
+  - name: dist-i686-linux
     <<: *job-linux-4c
 
-  - image: dist-loongarch64-linux
+  - name: dist-loongarch64-linux
     <<: *job-linux-4c
 
-  - image: dist-loongarch64-musl
+  - name: dist-loongarch64-musl
     <<: *job-linux-4c
 
-  - image: dist-ohos
+  - name: dist-ohos
     <<: *job-linux-4c
 
-  - image: dist-powerpc-linux
+  - name: dist-powerpc-linux
     <<: *job-linux-4c
 
-  - image: dist-powerpc64-linux
+  - name: dist-powerpc64-linux
     <<: *job-linux-4c
 
-  - image: dist-powerpc64le-linux
+  - name: dist-powerpc64le-linux
     <<: *job-linux-4c-largedisk
 
-  - image: dist-riscv64-linux
+  - name: dist-riscv64-linux
     <<: *job-linux-4c
 
-  - image: dist-s390x-linux
+  - name: dist-s390x-linux
     <<: *job-linux-4c
 
-  - image: dist-various-1
+  - name: dist-various-1
     <<: *job-linux-4c
 
-  - image: dist-various-2
+  - name: dist-various-2
     <<: *job-linux-4c
 
-  - image: dist-x86_64-freebsd
+  - name: dist-x86_64-freebsd
     <<: *job-linux-4c
 
-  - image: dist-x86_64-illumos
+  - name: dist-x86_64-illumos
     <<: *job-linux-4c
 
-  - image: dist-x86_64-linux
+  - name: dist-x86_64-linux
     env:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-linux-16c
 
-  - image: dist-x86_64-linux-alt
+  - name: dist-x86_64-linux-alt
     env:
       IMAGE: dist-x86_64-linux
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-linux-16c
 
-  - image: dist-x86_64-musl
+  - name: dist-x86_64-musl
     env:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-linux-4c
 
-  - image: dist-x86_64-netbsd
+  - name: dist-x86_64-netbsd
     <<: *job-linux-4c
 
   # The i686-gnu job is split into multiple jobs to run tests in parallel.
   # i686-gnu-1 skips tests that run in i686-gnu-2.
-  - image: i686-gnu-1
+  - name: i686-gnu-1
     env:
       IMAGE: i686-gnu
       DOCKER_SCRIPT: stage_2_test_set1.sh
     <<: *job-linux-4c
 
   # Skip tests that run in i686-gnu-1
-  - image: i686-gnu-2
+  - name: i686-gnu-2
     env:
       IMAGE: i686-gnu
       DOCKER_SCRIPT: stage_2_test_set2.sh
@@ -230,14 +230,14 @@ auto:
 
   # The i686-gnu-nopt job is split into multiple jobs to run tests in parallel.
   # i686-gnu-nopt-1 skips tests that run in i686-gnu-nopt-2
-  - image: i686-gnu-nopt-1
+  - name: i686-gnu-nopt-1
     env:
       IMAGE: i686-gnu-nopt
       DOCKER_SCRIPT: /scripts/stage_2_test_set1.sh
     <<: *job-linux-4c
 
   # Skip tests that run in i686-gnu-nopt-1
-  - image: i686-gnu-nopt-2
+  - name: i686-gnu-nopt-2
     env:
       IMAGE: i686-gnu-nopt
       DOCKER_SCRIPT: >-
@@ -245,13 +245,13 @@ auto:
         /scripts/stage_2_test_set2.sh
     <<: *job-linux-4c
 
-  - image: mingw-check
+  - name: mingw-check
     <<: *job-linux-4c
 
-  - image: test-various
+  - name: test-various
     <<: *job-linux-4c
 
-  - image: x86_64-fuchsia
+  - name: x86_64-fuchsia
     # Only run this job on the nightly channel. Fuchsia requires
     # nightly features to compile, and this job would fail if
     # executed on beta and stable.
@@ -260,10 +260,10 @@ auto:
 
   # Tests integration with Rust for Linux.
   # Builds stage 1 compiler and tries to compile a few RfL examples with it.
-  - image: x86_64-rust-for-linux
+  - name: x86_64-rust-for-linux
     <<: *job-linux-4c
 
-  - image: x86_64-gnu
+  - name: x86_64-gnu
     <<: *job-linux-4c
 
   # This job ensures commits landing on nightly still pass the full
@@ -271,7 +271,7 @@ auto:
   # depend on the channel being built (for example if they include the
   # channel name on the output), and this builder prevents landing
   # changes that would result in broken builds after a promotion.
-  - image: x86_64-gnu-stable
+  - name: x86_64-gnu-stable
     # Only run this job on the nightly channel. Running this on beta
     # could cause failures when `dev: 1` in `stage0.txt`, and running
     # this on stable is useless.
@@ -281,20 +281,20 @@ auto:
       RUST_CI_OVERRIDE_RELEASE_CHANNEL: stable
     <<: *job-linux-4c
 
-  - image: x86_64-gnu-aux
+  - name: x86_64-gnu-aux
     <<: *job-linux-4c
 
-  - image: x86_64-gnu-debug
+  - name: x86_64-gnu-debug
     # This seems to be needed because a full stage 2 build + run-make tests
     # overwhelms the storage capacity of the standard 4c runner.
     <<: *job-linux-4c-largedisk
 
-  - image: x86_64-gnu-distcheck
+  - name: x86_64-gnu-distcheck
     <<: *job-linux-8c
 
   # The x86_64-gnu-llvm-19 job is split into multiple jobs to run tests in parallel.
   # x86_64-gnu-llvm-19-1 skips tests that run in x86_64-gnu-llvm-19-{2,3}.
-  - image: x86_64-gnu-llvm-19-1
+  - name: x86_64-gnu-llvm-19-1
     env:
       RUST_BACKTRACE: 1
       IMAGE: x86_64-gnu-llvm-19
@@ -302,7 +302,7 @@ auto:
     <<: *job-linux-4c
 
   # Skip tests that run in x86_64-gnu-llvm-19-{1,3}
-  - image: x86_64-gnu-llvm-19-2
+  - name: x86_64-gnu-llvm-19-2
     env:
       RUST_BACKTRACE: 1
       IMAGE: x86_64-gnu-llvm-19
@@ -310,7 +310,7 @@ auto:
     <<: *job-linux-4c
 
   # Skip tests that run in x86_64-gnu-llvm-19-{1,2}
-  - image: x86_64-gnu-llvm-19-3
+  - name: x86_64-gnu-llvm-19-3
     env:
       RUST_BACKTRACE: 1
       IMAGE: x86_64-gnu-llvm-19
@@ -319,7 +319,7 @@ auto:
 
   # The x86_64-gnu-llvm-18 job is split into multiple jobs to run tests in parallel.
   # x86_64-gnu-llvm-18-1 skips tests that run in x86_64-gnu-llvm-18-{2,3}.
-  - image: x86_64-gnu-llvm-18-1
+  - name: x86_64-gnu-llvm-18-1
     env:
       RUST_BACKTRACE: 1
       READ_ONLY_SRC: "0"
@@ -328,7 +328,7 @@ auto:
     <<: *job-linux-4c
 
   # Skip tests that run in x86_64-gnu-llvm-18-{1,3}
-  - image: x86_64-gnu-llvm-18-2
+  - name: x86_64-gnu-llvm-18-2
     env:
       RUST_BACKTRACE: 1
       READ_ONLY_SRC: "0"
@@ -337,7 +337,7 @@ auto:
     <<: *job-linux-4c
 
   # Skip tests that run in x86_64-gnu-llvm-18-{1,2}
-  - image: x86_64-gnu-llvm-18-3
+  - name: x86_64-gnu-llvm-18-3
     env:
       RUST_BACKTRACE: 1
       READ_ONLY_SRC: "0"
@@ -345,10 +345,10 @@ auto:
       DOCKER_SCRIPT: x86_64-gnu-llvm3.sh
     <<: *job-linux-4c
 
-  - image: x86_64-gnu-nopt
+  - name: x86_64-gnu-nopt
     <<: *job-linux-4c
 
-  - image: x86_64-gnu-tools
+  - name: x86_64-gnu-tools
     env:
       DEPLOY_TOOLSTATES_JSON: toolstates-linux.json
     <<: *job-linux-4c
@@ -357,7 +357,7 @@ auto:
   #  macOS Builders  #
   ####################
 
-  - image: dist-x86_64-apple
+  - name: dist-x86_64-apple
     env:
       SCRIPT: ./x.py dist bootstrap --include-default-paths --host=x86_64-apple-darwin --target=x86_64-apple-darwin
       RUST_CONFIGURE_ARGS: --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set rust.lto=thin --set rust.codegen-units=1
@@ -371,7 +371,7 @@ auto:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-macos-xl
 
-  - image: dist-apple-various
+  - name: dist-apple-various
     env:
       SCRIPT: ./x.py dist bootstrap --include-default-paths --host='' --target=aarch64-apple-ios,x86_64-apple-ios,aarch64-apple-ios-sim,aarch64-apple-ios-macabi,x86_64-apple-ios-macabi
       # Mac Catalyst cannot currently compile the sanitizer:
@@ -385,19 +385,19 @@ auto:
       NO_OVERFLOW_CHECKS: 1
     <<: *job-macos-xl
 
-  - image: x86_64-apple-1
+  - name: x86_64-apple-1
     env:
       <<: *env-x86_64-apple-tests
     <<: *job-macos-xl
 
-  - image: x86_64-apple-2
+  - name: x86_64-apple-2
     env:
       SCRIPT: ./x.py --stage 2 test tests/ui tests/rustdoc
       <<: *env-x86_64-apple-tests
     <<: *job-macos-xl
 
   # This target only needs to support 11.0 and up as nothing else supports the hardware
-  - image: dist-aarch64-apple
+  - name: dist-aarch64-apple
     env:
       SCRIPT: ./x.py dist bootstrap --include-default-paths --host=aarch64-apple-darwin --target=aarch64-apple-darwin
       RUST_CONFIGURE_ARGS: >-
@@ -421,7 +421,7 @@ auto:
     <<: *job-macos-m1
 
   # This target only needs to support 11.0 and up as nothing else supports the hardware
-  - image: aarch64-apple
+  - name: aarch64-apple
     env:
       SCRIPT: ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin
       RUST_CONFIGURE_ARGS: >-
@@ -442,20 +442,20 @@ auto:
   #  Windows Builders  #
   ######################
 
-  - image: x86_64-msvc
+  - name: x86_64-msvc
     env:
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-profiler
       SCRIPT: make ci-msvc
     <<: *job-windows-8c
 
-  - image: i686-msvc
+  - name: i686-msvc
     env:
       RUST_CONFIGURE_ARGS: --build=i686-pc-windows-msvc
       SCRIPT: make ci-msvc
     <<: *job-windows-8c
 
   # x86_64-msvc-ext is split into multiple jobs to run tests in parallel.
-  - image: x86_64-msvc-ext1
+  - name: x86_64-msvc-ext1
     env:
       SCRIPT: python x.py --stage 2 test src/tools/cargotest src/tools/cargo
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-lld
@@ -464,7 +464,7 @@ auto:
   # Temporary builder to workaround CI issues
   # See <https://github.com/rust-lang/rust/issues/127883>
   #FIXME: Remove this, and re-enable the same tests in `checktools.sh`, once CI issues are fixed.
-  - image: x86_64-msvc-ext2
+  - name: x86_64-msvc-ext2
     env:
       SCRIPT: >
         python x.py test --stage 2 src/tools/miri --target aarch64-apple-darwin --test-args pass &&
@@ -476,7 +476,7 @@ auto:
     <<: *job-windows
 
   # Run `checktools.sh` and upload the toolstate file.
-  - image: x86_64-msvc-ext3
+  - name: x86_64-msvc-ext3
     env:
       SCRIPT: src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh x.py /tmp/toolstate/toolstates.json windows
       HOST_TARGET: x86_64-pc-windows-msvc
@@ -500,7 +500,7 @@ auto:
   # came from the mingw-w64 SourceForge download site. Unfortunately
   # SourceForge is notoriously flaky, so we mirror it on our own infrastructure.
 
-  - image: i686-mingw
+  - name: i686-mingw
     env:
       RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
       SCRIPT: make ci-mingw
@@ -510,7 +510,7 @@ auto:
     <<: *job-windows-8c
 
   # x86_64-mingw is split into two jobs to run tests in parallel.
-  - image: x86_64-mingw-1
+  - name: x86_64-mingw-1
     env:
       SCRIPT: make ci-mingw-x
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-gnu
@@ -519,7 +519,7 @@ auto:
       NO_DOWNLOAD_CI_LLVM: 1
     <<: *job-windows
 
-  - image: x86_64-mingw-2
+  - name: x86_64-mingw-2
     env:
       SCRIPT: make ci-mingw-bootstrap
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-gnu
@@ -528,7 +528,7 @@ auto:
       NO_DOWNLOAD_CI_LLVM: 1
     <<: *job-windows
 
-  - image: dist-x86_64-msvc
+  - name: dist-x86_64-msvc
     env:
       RUST_CONFIGURE_ARGS: >-
         --build=x86_64-pc-windows-msvc
@@ -542,7 +542,7 @@ auto:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-windows-8c
 
-  - image: dist-i686-msvc
+  - name: dist-i686-msvc
     env:
       RUST_CONFIGURE_ARGS: >-
         --build=i686-pc-windows-msvc
@@ -555,7 +555,7 @@ auto:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-windows
 
-  - image: dist-aarch64-msvc
+  - name: dist-aarch64-msvc
     env:
       RUST_CONFIGURE_ARGS: >-
         --build=x86_64-pc-windows-msvc
@@ -567,7 +567,7 @@ auto:
       DIST_REQUIRE_ALL_TOOLS: 1
     <<: *job-windows
 
-  - image: dist-i686-mingw
+  - name: dist-i686-mingw
     env:
       RUST_CONFIGURE_ARGS: >-
         --build=i686-pc-windows-gnu
@@ -580,7 +580,7 @@ auto:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-windows
 
-  - image: dist-x86_64-mingw
+  - name: dist-x86_64-mingw
     env:
       SCRIPT: python x.py dist bootstrap --include-default-paths
       RUST_CONFIGURE_ARGS: >-
@@ -593,7 +593,7 @@ auto:
       CODEGEN_BACKENDS: llvm,cranelift
     <<: *job-windows
 
-  - image: dist-x86_64-msvc-alt
+  - name: dist-x86_64-msvc-alt
     env:
       RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-extended --enable-profiler
       SCRIPT: python x.py dist bootstrap --include-default-paths