From 61d78680e0a51f097d051a995d0b932c54e32934 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:26:02 +0200 Subject: [PATCH] Add nightly overview pages --- src/build.rs | 12 ++--- src/db.rs | 29 +++++++++++- src/nightlies.rs | 7 ++- src/web.rs | 105 +++++++++++++++++++++++++++++++---------- static/build.html | 8 +++- static/index.js | 4 +- templates/index.html | 25 +++++++++- templates/nightly.html | 78 ++++++++++++++++++++++++++++++ templates/target.html | 2 +- 9 files changed, 229 insertions(+), 41 deletions(-) create mode 100644 templates/nightly.html diff --git a/src/build.rs b/src/build.rs index 9fcb5b3..1e4aa13 100644 --- a/src/build.rs +++ b/src/build.rs @@ -57,7 +57,7 @@ async fn background_builder_inner(db: &Db, nightly_cache: &mut NightlyCache) -> match next { Some((nightly, mode)) => { info!(%nightly, %mode, "Building next nightly"); - let result = build_every_target_for_toolchain(&db, &nightly, mode) + let result = build_every_target_for_toolchain(db, &nightly, mode) .await .wrap_err_with(|| format!("building targets for toolchain {nightly}")); if let Err(err) = result { @@ -69,7 +69,7 @@ async fn background_builder_inner(db: &Db, nightly_cache: &mut NightlyCache) -> } None => { info!("No new nightly, waiting for an hour to try again"); - tokio::time::sleep(Duration::from_secs(1 * 60 * 60)).await; + tokio::time::sleep(Duration::from_secs(60 * 60)).await; } } Ok(()) @@ -191,7 +191,7 @@ pub async fn build_every_target_for_toolchain( let results = futures::stream::iter( targets .iter() - .map(|target| build_single_target(&db, nightly, target, mode)), + .map(|target| build_single_target(db, nightly, target, mode)), ) .buffer_unordered(concurrent) .collect::>>() @@ -266,7 +266,7 @@ async fn build_target( BuildMode::Core => { let init = Command::new("cargo") .args(["init", "--lib", "--name", "target-test"]) - .current_dir(&tmpdir) + .current_dir(tmpdir) .output() .await .wrap_err("spawning cargo init")?; @@ -282,7 +282,7 @@ async fn build_target( .arg(format!("+{toolchain}")) .args(["build", "-Zbuild-std=core", "--release"]) .args(["--target", target]) - .current_dir(&tmpdir) + .current_dir(tmpdir) .output() .await .wrap_err("spawning cargo build")? @@ -291,7 +291,7 @@ async fn build_target( .arg(format!("+{toolchain}")) .args(["miri", "setup"]) .args(["--target", target]) - .current_dir(&tmpdir) + .current_dir(tmpdir) .env("MIRI_SYSROOT", tmpdir) .output() .await diff --git a/src/db.rs b/src/db.rs index 75a87a4..3bc06f5 100644 --- a/src/db.rs +++ b/src/db.rs @@ -33,7 +33,7 @@ impl Display for BuildMode { } } -#[derive(sqlx::FromRow, Serialize, Deserialize)] +#[derive(sqlx::FromRow, Serialize, Deserialize, Clone)] pub struct BuildInfo { pub nightly: String, pub target: String, @@ -81,7 +81,7 @@ impl Db { let conn = Pool::connect_with(db_opts) .await - .wrap_err_with(|| format!("opening db from `{}`", path))?; + .wrap_err_with(|| format!("opening db from `{path}`"))?; Ok(Self { conn }) } @@ -117,6 +117,16 @@ impl Db { .wrap_err("getting history for single target") } + pub async fn history_for_nightly(&self, nightly: &str) -> Result> { + sqlx::query_as::<_, BuildInfo>( + "SELECT nightly, target, status, mode FROM build_info WHERE nightly = ?", + ) + .bind(nightly) + .fetch_all(&self.conn) + .await + .wrap_err("getting history for single nightly") + } + pub async fn target_list(&self) -> Result> { #[derive(sqlx::FromRow)] struct TargetName { @@ -130,6 +140,21 @@ impl Db { .map(|elems| elems.into_iter().map(|elem| elem.target).collect()) } + pub async fn nightly_list(&self) -> Result> { + #[derive(sqlx::FromRow)] + struct NightlyName { + nightly: String, + } + + sqlx::query_as::<_, NightlyName>( + "SELECT DISTINCT nightly FROM build_info ORDER BY nightly DESC", + ) + .fetch_all(&self.conn) + .await + .wrap_err("getting list of all targets") + .map(|elems| elems.into_iter().map(|elem| elem.nightly).collect()) + } + pub async fn build_status_full( &self, nightly: &str, diff --git a/src/nightlies.rs b/src/nightlies.rs index 7493870..28498d8 100644 --- a/src/nightlies.rs +++ b/src/nightlies.rs @@ -43,7 +43,7 @@ impl Nightlies { .last() .ok_or_eyre("did not find any nightlies in manifests.txt")?; - for nightly in guess_more_recent_nightlies(&latest)? { + for nightly in guess_more_recent_nightlies(latest)? { if nightly_exists(&nightly, cache) .await .wrap_err_with(|| format!("checking whether {nightly} exists"))? @@ -54,7 +54,10 @@ impl Nightlies { all.reverse(); - debug!("Loaded {} nightlies from the manifest and manual additions", all.len()); + debug!( + "Loaded {} nightlies from the manifest and manual additions", + all.len() + ); Ok(Self { all }) } diff --git a/src/web.rs b/src/web.rs index c287ce4..ff8db9b 100644 --- a/src/web.rs +++ b/src/web.rs @@ -4,7 +4,7 @@ use axum::{ extract::{Query, State}, http::StatusCode, response::{Html, IntoResponse, Response}, - routing::{get, post}, + routing::get, Json, Router, }; use color_eyre::{eyre::Context, Result}; @@ -23,11 +23,11 @@ pub async fn webserver(db: Db) -> Result<()> { .route("/", get(web_root)) .route("/build", get(web_build)) .route("/target", get(web_target)) + .route("/nightly", get(web_nightly)) .route("/full-table", get(web_full_table)) .route("/index.css", get(index_css)) .route("/index.js", get(index_js)) .route("/full-mega-monster", get(full_mega_monster)) - .route("/trigger-build", post(trigger_build)) .with_state(AppState { db }); info!("Serving website on port 3000 (commit {})", crate::VERSION); @@ -144,24 +144,95 @@ async fn web_target(State(state): State, Query(query): Query, Query(query): Query) -> Response { + use askama::Template; + #[derive(askama::Template)] + #[template(path = "nightly.html")] + struct NightlyPage { + nightly: String, + version: &'static str, + builds: Vec<(String, Option, Option)>, + core_failures: usize, + std_failures: usize, + } + + match state.db.history_for_nightly(&query.nightly).await { + Ok(builds) => { + let mut builds_grouped = + HashMap::, Option)>::new(); + for build in &builds { + let v = builds_grouped.entry(build.target.clone()).or_default(); + match build.mode { + BuildMode::Core => v.0 = Some(build.clone()), + BuildMode::MiriStd => v.1 = Some(build.clone()), + } + } + + let mut std_failures = 0; + let mut core_failures = 0; + for build in builds { + if build.status == Status::Error { + match build.mode { + BuildMode::Core => core_failures += 1, + BuildMode::MiriStd => std_failures += 1, + } + } + } + + let mut builds = builds_grouped + .into_iter() + .map(|(k, (v1, v2))| (k, v1, v2)) + .collect::>(); + builds.sort_by_cached_key(|build| build.0.clone()); + + let page = NightlyPage { + nightly: query.nightly, + version: crate::VERSION, + builds, + std_failures, + core_failures, + }; + + Html(page.render().unwrap()).into_response() + } + Err(err) => { + error!(?err, "Error loading target state"); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } +} + async fn web_root(State(state): State) -> impl IntoResponse { use askama::Template; #[derive(askama::Template)] #[template(path = "index.html")] struct RootPage { targets: Vec, + nightlies: Vec, version: &'static str, } match state.db.target_list().await { - Ok(targets) => { - let page = RootPage { - targets, - version: crate::VERSION, - }; + Ok(targets) => match state.db.nightly_list().await { + Ok(nightlies) => { + let page = RootPage { + targets, + nightlies, + version: crate::VERSION, + }; - Html(page.render().unwrap()).into_response() - } + Html(page.render().unwrap()).into_response() + } + Err(err) => { + error!(?err, "Error loading nightly state"); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + }, Err(err) => { error!(?err, "Error loading target state"); StatusCode::INTERNAL_SERVER_ERROR.into_response() @@ -203,22 +274,6 @@ struct TriggerBuildBody { nightly: String, } -#[axum::debug_handler] -async fn trigger_build( - State(_state): State, - _body: Json, -) -> StatusCode { - return StatusCode::BAD_REQUEST; - // tokio::spawn(async move { - // let result = build::build_every_target_for_toolchain(&state.db, &body.nightly).await; - // if let Err(err) = result { - // error!(?err, "Error while building"); - // } - // }); - // - // StatusCode::ACCEPTED -} - impl Status { fn to_emoji(&self) -> &'static str { match self { diff --git a/static/build.html b/static/build.html index 31af71d..db0ada4 100644 --- a/static/build.html +++ b/static/build.html @@ -1,4 +1,4 @@ - + @@ -19,6 +19,12 @@
 {{stderr}}
     
+

+ Build history for target {{target}} +

+

+ Build state for nightly {{nightly}} +