about summary refs log tree commit diff
path: root/src/ci
diff options
context:
space:
mode:
authorThe rustc-dev-guide Cronjob Bot <github-actions@github.com>2025-04-19 13:53:12 +0000
committerThe rustc-dev-guide Cronjob Bot <github-actions@github.com>2025-04-19 13:53:12 +0000
commit3b2302ec0d17cbc94ef68b15f0bb6524f8b67f3f (patch)
tree05635b943c977eba01cebf3ef78d8a479e555de5 /src/ci
parenta0c64dce0d70d1be54a6fd7b7aa9ed37665ad998 (diff)
parenta7c39b68616668a45f0afd62849a1da7c8ad2516 (diff)
downloadrust-3b2302ec0d17cbc94ef68b15f0bb6524f8b67f3f.tar.gz
rust-3b2302ec0d17cbc94ef68b15f0bb6524f8b67f3f.zip
Merge from rustc
Diffstat (limited to 'src/ci')
-rw-r--r--src/ci/citool/Cargo.lock4
-rw-r--r--src/ci/citool/src/analysis.rs62
-rw-r--r--src/ci/citool/src/github.rs109
-rw-r--r--src/ci/citool/src/main.rs11
-rw-r--r--src/ci/citool/tests/test-jobs.yml2
-rw-r--r--src/ci/docker/host-x86_64/mingw-check/Dockerfile1
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-llvm-20/Dockerfile69
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile1
-rw-r--r--src/ci/github-actions/jobs.yml39
9 files changed, 270 insertions, 28 deletions
diff --git a/src/ci/citool/Cargo.lock b/src/ci/citool/Cargo.lock
index 800eaae0766..2fe219f368b 100644
--- a/src/ci/citool/Cargo.lock
+++ b/src/ci/citool/Cargo.lock
@@ -563,9 +563,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.5"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
 dependencies = [
  "adler2",
 ]
diff --git a/src/ci/citool/src/analysis.rs b/src/ci/citool/src/analysis.rs
index 7fbfad467c6..9fc7c309bfb 100644
--- a/src/ci/citool/src/analysis.rs
+++ b/src/ci/citool/src/analysis.rs
@@ -7,6 +7,7 @@ use build_helper::metrics::{
     format_build_steps,
 };
 
+use crate::github::JobInfoResolver;
 use crate::metrics;
 use crate::metrics::{JobMetrics, JobName, get_test_suites};
 use crate::utils::{output_details, pluralize};
@@ -185,13 +186,19 @@ fn render_table(suites: BTreeMap<String, TestSuiteRecord>) -> String {
 }
 
 /// Outputs a report of test differences between the `parent` and `current` commits.
-pub fn output_test_diffs(job_metrics: &HashMap<JobName, JobMetrics>) {
+pub fn output_test_diffs(
+    job_metrics: &HashMap<JobName, JobMetrics>,
+    job_info_resolver: &mut JobInfoResolver,
+) {
     let aggregated_test_diffs = aggregate_test_diffs(&job_metrics);
-    report_test_diffs(aggregated_test_diffs);
+    report_test_diffs(aggregated_test_diffs, job_metrics, job_info_resolver);
 }
 
 /// Prints the ten largest differences in bootstrap durations.
-pub fn output_largest_duration_changes(job_metrics: &HashMap<JobName, JobMetrics>) {
+pub fn output_largest_duration_changes(
+    job_metrics: &HashMap<JobName, JobMetrics>,
+    job_info_resolver: &mut JobInfoResolver,
+) {
     struct Entry<'a> {
         job: &'a JobName,
         before: Duration,
@@ -225,14 +232,14 @@ pub fn output_largest_duration_changes(job_metrics: &HashMap<JobName, JobMetrics
             });
         }
     }
-    changes.sort_by(|e1, e2| e1.change.partial_cmp(&e2.change).unwrap().reverse());
+    changes.sort_by(|e1, e2| e1.change.abs().partial_cmp(&e2.change.abs()).unwrap().reverse());
 
     println!("# Job duration changes");
     for (index, entry) in changes.into_iter().take(10).enumerate() {
         println!(
-            "{}. `{}`: {:.1}s -> {:.1}s ({:.1}%)",
+            "{}. {}: {:.1}s -> {:.1}s ({:.1}%)",
             index + 1,
-            entry.job,
+            format_job_link(job_info_resolver, job_metrics, entry.job),
             entry.before.as_secs_f64(),
             entry.after.as_secs_f64(),
             entry.change
@@ -400,7 +407,11 @@ fn generate_test_name(name: &str) -> String {
 }
 
 /// Prints test changes in Markdown format to stdout.
-fn report_test_diffs(diff: AggregatedTestDiffs) {
+fn report_test_diffs(
+    diff: AggregatedTestDiffs,
+    job_metrics: &HashMap<JobName, JobMetrics>,
+    job_info_resolver: &mut JobInfoResolver,
+) {
     println!("# Test differences");
     if diff.diffs.is_empty() {
         println!("No test diffs found");
@@ -509,21 +520,42 @@ fn report_test_diffs(diff: AggregatedTestDiffs) {
             }
 
             if doctest_count > 0 {
+                let prefix =
+                    if doctest_count < original_diff_count { "Additionally, " } else { "" };
                 println!(
-                    "\nAdditionally, {doctest_count} doctest {} were found. These are ignored, as they are noisy.",
+                    "\n{prefix}{doctest_count} doctest {} were found. These are ignored, as they are noisy.",
                     pluralize("diff", doctest_count)
                 );
             }
 
             // Now print the job group index
-            println!("\n**Job group index**\n");
-            for (group, jobs) in job_index.into_iter().enumerate() {
-                println!(
-                    "- {}: {}",
-                    format_job_group(group as u64),
-                    jobs.iter().map(|j| format!("`{j}`")).collect::<Vec<_>>().join(", ")
-                );
+            if !job_index.is_empty() {
+                println!("\n**Job group index**\n");
+                for (group, jobs) in job_index.into_iter().enumerate() {
+                    println!(
+                        "- {}: {}",
+                        format_job_group(group as u64),
+                        jobs.iter()
+                            .map(|j| format_job_link(job_info_resolver, job_metrics, j))
+                            .collect::<Vec<_>>()
+                            .join(", ")
+                    );
+                }
             }
         },
     );
 }
+
+/// Tries to get a GitHub Actions job summary URL from the resolver.
+/// If it is not available, just wraps the job name in backticks.
+fn format_job_link(
+    job_info_resolver: &mut JobInfoResolver,
+    job_metrics: &HashMap<JobName, JobMetrics>,
+    job_name: &str,
+) -> String {
+    job_metrics
+        .get(job_name)
+        .and_then(|metrics| job_info_resolver.get_job_summary_link(job_name, &metrics.current))
+        .map(|summary_url| format!("[{job_name}]({summary_url})"))
+        .unwrap_or_else(|| format!("`{job_name}`"))
+}
diff --git a/src/ci/citool/src/github.rs b/src/ci/citool/src/github.rs
new file mode 100644
index 00000000000..35e4c3f9599
--- /dev/null
+++ b/src/ci/citool/src/github.rs
@@ -0,0 +1,109 @@
+use std::collections::HashMap;
+
+use anyhow::Context;
+use build_helper::metrics::{CiMetadata, JsonRoot};
+
+pub struct GitHubClient;
+
+impl GitHubClient {
+    fn get_workflow_run_jobs(
+        &self,
+        repo: &str,
+        workflow_run_id: u64,
+    ) -> anyhow::Result<Vec<GitHubJob>> {
+        let req = ureq::get(format!(
+            "https://api.github.com/repos/{repo}/actions/runs/{workflow_run_id}/jobs?per_page=100"
+        ))
+        .header("User-Agent", "rust-lang/rust/citool")
+        .header("Accept", "application/vnd.github+json")
+        .header("X-GitHub-Api-Version", "2022-11-28")
+        .call()
+        .context("cannot get workflow job list")?;
+
+        let status = req.status();
+        let mut body = req.into_body();
+        if status.is_success() {
+            // This API response is actually paged, but we assume for now that there are at
+            // most 100 jobs per workflow.
+            let response = body
+                .read_json::<WorkflowRunJobsResponse>()
+                .context("cannot deserialize workflow run jobs response")?;
+            // The CI job names have a prefix, e.g. `auto - foo`. We remove the prefix here to
+            // normalize the job name.
+            Ok(response
+                .jobs
+                .into_iter()
+                .map(|mut job| {
+                    job.name = job
+                        .name
+                        .split_once(" - ")
+                        .map(|res| res.1.to_string())
+                        .unwrap_or_else(|| job.name);
+                    job
+                })
+                .collect())
+        } else {
+            Err(anyhow::anyhow!(
+                "Cannot get jobs of workflow run {workflow_run_id}: {status}\n{}",
+                body.read_to_string()?
+            ))
+        }
+    }
+}
+
+#[derive(serde::Deserialize)]
+struct WorkflowRunJobsResponse {
+    jobs: Vec<GitHubJob>,
+}
+
+#[derive(serde::Deserialize)]
+struct GitHubJob {
+    name: String,
+    id: u64,
+}
+
+/// Can be used to resolve information about GitHub Actions jobs.
+/// Caches results internally to avoid too unnecessary GitHub API calls.
+pub struct JobInfoResolver {
+    client: GitHubClient,
+    // Workflow run ID -> jobs
+    workflow_job_cache: HashMap<u64, Vec<GitHubJob>>,
+}
+
+impl JobInfoResolver {
+    pub fn new() -> Self {
+        Self { client: GitHubClient, workflow_job_cache: Default::default() }
+    }
+
+    /// Get a link to a job summary for the given job name and bootstrap execution.
+    pub fn get_job_summary_link(&mut self, job_name: &str, metrics: &JsonRoot) -> Option<String> {
+        metrics.ci_metadata.as_ref().and_then(|metadata| {
+            self.get_job_id(metadata, job_name).map(|job_id| {
+                format!(
+                    "https://github.com/{}/actions/runs/{}#summary-{job_id}",
+                    metadata.repository, metadata.workflow_run_id
+                )
+            })
+        })
+    }
+
+    fn get_job_id(&mut self, ci_metadata: &CiMetadata, job_name: &str) -> Option<u64> {
+        if let Some(job) = self
+            .workflow_job_cache
+            .get(&ci_metadata.workflow_run_id)
+            .and_then(|jobs| jobs.iter().find(|j| j.name == job_name))
+        {
+            return Some(job.id);
+        }
+
+        let jobs = self
+            .client
+            .get_workflow_run_jobs(&ci_metadata.repository, ci_metadata.workflow_run_id)
+            .inspect_err(|e| eprintln!("Cannot download workflow jobs: {e:?}"))
+            .ok()?;
+        let job_id = jobs.iter().find(|j| j.name == job_name).map(|j| j.id);
+        // Save the cache even if the job name was not found, it could be useful for further lookups
+        self.workflow_job_cache.insert(ci_metadata.workflow_run_id, jobs);
+        job_id
+    }
+}
diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs
index 6db5eab458c..a1956da352f 100644
--- a/src/ci/citool/src/main.rs
+++ b/src/ci/citool/src/main.rs
@@ -1,6 +1,7 @@
 mod analysis;
 mod cpu_usage;
 mod datadog;
+mod github;
 mod jobs;
 mod metrics;
 mod utils;
@@ -18,6 +19,7 @@ use serde_yaml::Value;
 use crate::analysis::{output_largest_duration_changes, output_test_diffs};
 use crate::cpu_usage::load_cpu_usage;
 use crate::datadog::upload_datadog_metric;
+use crate::github::JobInfoResolver;
 use crate::jobs::RunType;
 use crate::metrics::{JobMetrics, download_auto_job_metrics, download_job_metrics, load_metrics};
 use crate::utils::load_env_var;
@@ -145,6 +147,7 @@ fn postprocess_metrics(
 ) -> anyhow::Result<()> {
     let metrics = load_metrics(&metrics_path)?;
 
+    let mut job_info_resolver = JobInfoResolver::new();
     if let (Some(parent), Some(job_name)) = (parent, job_name) {
         // This command is executed also on PR builds, which might not have parent metrics
         // available, because some PR jobs don't run on auto builds, and PR jobs do not upload metrics
@@ -160,7 +163,7 @@ fn postprocess_metrics(
                     job_name,
                     JobMetrics { parent: Some(parent_metrics), current: metrics },
                 )]);
-                output_test_diffs(&job_metrics);
+                output_test_diffs(&job_metrics, &mut job_info_resolver);
                 return Ok(());
             }
             Err(error) => {
@@ -180,8 +183,10 @@ fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow
     let metrics = download_auto_job_metrics(&db, &parent, &current)?;
 
     println!("\nComparing {parent} (parent) -> {current} (this PR)\n");
-    output_test_diffs(&metrics);
-    output_largest_duration_changes(&metrics);
+
+    let mut job_info_resolver = JobInfoResolver::new();
+    output_test_diffs(&metrics, &mut job_info_resolver);
+    output_largest_duration_changes(&metrics, &mut job_info_resolver);
 
     Ok(())
 }
diff --git a/src/ci/citool/tests/test-jobs.yml b/src/ci/citool/tests/test-jobs.yml
index 3593b3f7df6..d81be88b708 100644
--- a/src/ci/citool/tests/test-jobs.yml
+++ b/src/ci/citool/tests/test-jobs.yml
@@ -27,7 +27,7 @@ runners:
     <<: *base-job
 envs:
   env-x86_64-apple-tests: &env-x86_64-apple-tests
-    SCRIPT: ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact
+    SCRIPT: ./x.py check compiletest --set build.compiletest-use-stage0-libtest=true && ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact
     RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc
     RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
     # Ensure that host tooling is tested on our minimum supported macOS version.
diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
index 8bb7116c3ec..418408e9242 100644
--- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile
+++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
@@ -47,6 +47,7 @@ COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/
 ENV SCRIPT \
            python3 ../x.py check --stage 0 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \
            /scripts/check-default-config-profiles.sh && \
+           python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \
            python3 ../x.py check --target=x86_64-pc-windows-gnu --host=x86_64-pc-windows-gnu && \
            python3 ../x.py clippy ci && \
            python3 ../x.py build --stage 0 src/tools/build-manifest && \
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-20/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-20/Dockerfile
new file mode 100644
index 00000000000..408b87125e0
--- /dev/null
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-20/Dockerfile
@@ -0,0 +1,69 @@
+FROM ubuntu:25.04
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+  bzip2 \
+  g++ \
+  gcc-multilib \
+  make \
+  ninja-build \
+  file \
+  curl \
+  ca-certificates \
+  python3 \
+  git \
+  cmake \
+  sudo \
+  gdb \
+  llvm-20-tools \
+  llvm-20-dev \
+  libedit-dev \
+  libssl-dev \
+  pkg-config \
+  zlib1g-dev \
+  xz-utils \
+  nodejs \
+  mingw-w64 \
+  # libgccjit dependencies
+  flex \
+  libmpfr-dev \
+  libgmp-dev \
+  libmpc3 \
+  libmpc-dev \
+  && rm -rf /var/lib/apt/lists/*
+
+# Install powershell (universal package) so we can test x.ps1 on Linux
+# FIXME: need a "universal" version that supports libicu74, but for now it still works to ignore that dep.
+RUN curl -sL "https://github.com/PowerShell/PowerShell/releases/download/v7.3.1/powershell_7.3.1-1.deb_amd64.deb" > powershell.deb && \
+    dpkg --ignore-depends=libicu72 -i powershell.deb && \
+    rm -f powershell.deb
+
+COPY scripts/sccache.sh /scripts/
+RUN sh /scripts/sccache.sh
+
+# We are disabling CI LLVM since this builder is intentionally using a host
+# LLVM, rather than the typical src/llvm-project LLVM.
+ENV NO_DOWNLOAD_CI_LLVM 1
+ENV EXTERNAL_LLVM 1
+
+# Using llvm-link-shared due to libffi issues -- see #34486
+ENV RUST_CONFIGURE_ARGS \
+      --build=x86_64-unknown-linux-gnu \
+      --llvm-root=/usr/lib/llvm-20 \
+      --enable-llvm-link-shared \
+      --set rust.randomize-layout=true \
+      --set rust.thin-lto-import-instr-limit=10
+
+COPY scripts/shared.sh /scripts/
+
+ARG SCRIPT_ARG
+
+COPY scripts/add_dummy_commit.sh /tmp/
+COPY scripts/x86_64-gnu-llvm.sh /tmp/
+COPY scripts/x86_64-gnu-llvm2.sh /tmp/
+COPY scripts/x86_64-gnu-llvm3.sh /tmp/
+COPY scripts/stage_2_test_set1.sh /tmp/
+COPY scripts/stage_2_test_set2.sh /tmp/
+
+ENV SCRIPT "/tmp/add_dummy_commit.sh && /tmp/${SCRIPT_ARG}"
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 89806634c6c..05c90af7807 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
@@ -101,4 +101,5 @@ COPY scripts/shared.sh /scripts/
 # the local version of the package is different than the one used by the CI.
 ENV SCRIPT /tmp/checktools.sh ../x.py && \
   npm install browser-ui-test@$(head -n 1 /tmp/browser-ui-test.version) --unsafe-perm=true && \
+  python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \
   python3 ../x.py test tests/rustdoc-gui --stage 2 --test-args "'--jobs 1'"
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 68e680a1b1b..cb2bec5a9df 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -23,8 +23,8 @@ runners:
     os: ubuntu-24.04-16core-64gb
     <<: *base-job
 
-  - &job-macos-xl
-    os: macos-13 # We use the standard runner for now
+  - &job-macos
+    os: macos-13
     <<: *base-job
 
   - &job-macos-m1
@@ -58,7 +58,7 @@ runners:
     <<: *base-job
 envs:
   env-x86_64-apple-tests: &env-x86_64-apple-tests
-    SCRIPT: ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact
+    SCRIPT: ./x.py check compiletest --set build.compiletest-use-stage0-libtest=true && ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact
     RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc
     RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
     # Ensure that host tooling is tested on our minimum supported macOS version.
@@ -304,6 +304,31 @@ auto:
   - name: x86_64-gnu-distcheck
     <<: *job-linux-8c
 
+  # The x86_64-gnu-llvm-20 job is split into multiple jobs to run tests in parallel.
+  # x86_64-gnu-llvm-20-1 skips tests that run in x86_64-gnu-llvm-20-{2,3}.
+  - name: x86_64-gnu-llvm-20-1
+    env:
+      RUST_BACKTRACE: 1
+      IMAGE: x86_64-gnu-llvm-20
+      DOCKER_SCRIPT: stage_2_test_set1.sh
+    <<: *job-linux-4c
+
+  # Skip tests that run in x86_64-gnu-llvm-20-{1,3}
+  - name: x86_64-gnu-llvm-20-2
+    env:
+      RUST_BACKTRACE: 1
+      IMAGE: x86_64-gnu-llvm-20
+      DOCKER_SCRIPT: x86_64-gnu-llvm2.sh
+    <<: *job-linux-4c
+
+  # Skip tests that run in x86_64-gnu-llvm-20-{1,2}
+  - name: x86_64-gnu-llvm-20-3
+    env:
+      RUST_BACKTRACE: 1
+      IMAGE: x86_64-gnu-llvm-20
+      DOCKER_SCRIPT: x86_64-gnu-llvm3.sh
+    <<: *job-linux-4c
+
   # 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}.
   - name: x86_64-gnu-llvm-19-1
@@ -355,7 +380,7 @@ auto:
       NO_OVERFLOW_CHECKS: 1
       DIST_REQUIRE_ALL_TOOLS: 1
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-macos-xl
+    <<: *job-macos
 
   - name: dist-apple-various
     env:
@@ -372,18 +397,18 @@ auto:
       NO_LLVM_ASSERTIONS: 1
       NO_DEBUG_ASSERTIONS: 1
       NO_OVERFLOW_CHECKS: 1
-    <<: *job-macos-xl
+    <<: *job-macos
 
   - name: x86_64-apple-1
     env:
       <<: *env-x86_64-apple-tests
-    <<: *job-macos-xl
+    <<: *job-macos
 
   - name: x86_64-apple-2
     env:
       SCRIPT: ./x.py --stage 2 test tests/ui tests/rustdoc
       <<: *env-x86_64-apple-tests
-    <<: *job-macos-xl
+    <<: *job-macos
 
   - name: dist-aarch64-apple
     env: