diff --git a/migrations/20250703173258_index.sql b/migrations/20250703173258_index.sql deleted file mode 100644 index ee4693a..0000000 --- a/migrations/20250703173258_index.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Add migration script here - -CREATE INDEX IF NOT EXISTS build_info_nightly ON build_info (nightly); - -CREATE INDEX IF NOT EXISTS build_info_target ON build_info (target); diff --git a/migrations/20250703200547_broken-error.sql b/migrations/20250703200547_broken-error.sql deleted file mode 100644 index 8f6dfde..0000000 --- a/migrations/20250703200547_broken-error.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Add migration script here - -ALTER TABLE finished_nightly - ADD COLUMN broken_error BOOLEAN DEFAULT NULL; diff --git a/src/build.rs b/src/build.rs index 32fa1e1..9fcb5b3 100644 --- a/src/build.rs +++ b/src/build.rs @@ -57,19 +57,19 @@ 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 { error!(%nightly, %mode, ?err, "Failed to build nightly"); - db.finish_nightly_as_broken(&nightly, mode, &format!("{err:?}")) + db.finish_nightly_as_broken(&nightly, mode) .await .wrap_err("marking nightly as broken")?; } } None => { info!("No new nightly, waiting for an hour to try again"); - tokio::time::sleep(Duration::from_secs(60 * 60)).await; + tokio::time::sleep(Duration::from_secs(1 * 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 42c1811..75a87a4 100644 --- a/src/db.rs +++ b/src/db.rs @@ -33,7 +33,7 @@ impl Display for BuildMode { } } -#[derive(sqlx::FromRow, Serialize, Deserialize, Clone)] +#[derive(sqlx::FromRow, Serialize, Deserialize)] pub struct BuildInfo { pub nightly: String, pub target: String, @@ -73,14 +73,6 @@ pub struct FinishedNightly { pub mode: BuildMode, } -#[derive(sqlx::FromRow, Debug, PartialEq, Eq, Hash)] -pub struct FinishedNightlyWithBroken { - pub nightly: String, - pub mode: BuildMode, - pub is_broken: bool, - pub broken_error: Option, -} - impl Db { pub async fn open(path: &str) -> Result { let db_opts = SqliteConnectOptions::from_str(path) @@ -89,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 }) } @@ -108,6 +100,13 @@ impl Db { Ok(()) } + pub async fn full_mega_monster(&self) -> Result> { + sqlx::query_as::<_, BuildInfo>("SELECT nightly, target, status, mode FROM build_info") + .fetch_all(&self.conn) + .await + .wrap_err("getting build status from DB") + } + pub async fn history_for_target(&self, target: &str) -> Result> { sqlx::query_as::<_, BuildInfo>( "SELECT nightly, target, status, mode FROM build_info WHERE target = ?", @@ -118,26 +117,6 @@ 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 nightly_info(&self, nightly: &str) -> Result> { - sqlx::query_as::<_, FinishedNightlyWithBroken>( - "SELECT nightly, mode, is_broken, broken_error FROM finished_nightly WHERE nightly = ?", - ) - .bind(nightly) - .fetch_all(&self.conn) - .await - .wrap_err("getting finished_nightly for single nightly") - } - pub async fn target_list(&self) -> Result> { #[derive(sqlx::FromRow)] struct TargetName { @@ -151,21 +130,6 @@ 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, @@ -222,16 +186,10 @@ impl Db { Ok(()) } - pub async fn finish_nightly_as_broken( - &self, - nightly: &str, - mode: BuildMode, - error: &str, - ) -> Result<()> { - sqlx::query("INSERT INTO finished_nightly (nightly, mode, is_broken, broken_error) VALUES (?, ?, TRUE, ?)") + pub async fn finish_nightly_as_broken(&self, nightly: &str, mode: BuildMode) -> Result<()> { + sqlx::query("INSERT INTO finished_nightly (nightly, mode, is_broken) VALUES (?, ?, TRUE)") .bind(nightly) .bind(mode) - .bind(error) .execute(&self.conn) .await .wrap_err("inserting finished broken nightly")?; diff --git a/src/nightlies.rs b/src/nightlies.rs index f6675dd..7493870 100644 --- a/src/nightlies.rs +++ b/src/nightlies.rs @@ -9,7 +9,7 @@ use tracing::debug; use crate::db::{BuildMode, FinishedNightly}; -const EARLIEST_CUTOFF_DATE: &str = "2022-01-01"; +const EARLIEST_CUTOFF_DATE: &str = "2023-01-01"; #[derive(Default)] pub struct NightlyCache { @@ -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,10 +54,7 @@ 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 4bb4221..c287ce4 100644 --- a/src/web.rs +++ b/src/web.rs @@ -4,8 +4,8 @@ use axum::{ extract::{Query, State}, http::StatusCode, response::{Html, IntoResponse, Response}, - routing::get, - Router, + routing::{get, post}, + Json, Router, }; use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; @@ -23,8 +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); @@ -141,114 +144,24 @@ 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, - core_broken: Option, - std_broken: Option, - } - - match state.db.history_for_nightly(&query.nightly).await { - Ok(builds) => match state.db.nightly_info(&query.nightly).await { - Ok(info) => { - 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 core_broken = info - .iter() - .find(|info| info.mode == BuildMode::Core && info.is_broken) - .and_then(|info| info.broken_error.clone()); - let std_broken = info - .iter() - .find(|info| info.mode == BuildMode::MiriStd && info.is_broken) - .and_then(|info| info.broken_error.clone()); - - let page = NightlyPage { - nightly: query.nightly, - version: crate::VERSION, - builds, - std_failures, - core_failures, - core_broken, - std_broken, - }; - - Html(page.render().unwrap()).into_response() - } - Err(err) => { - error!(?err, "Error loading target state"); - StatusCode::INTERNAL_SERVER_ERROR.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) => match state.db.nightly_list().await { - Ok(nightlies) => { - let page = RootPage { - targets, - nightlies, - version: crate::VERSION, - }; + Ok(targets) => { + let page = RootPage { + targets, + version: crate::VERSION, + }; - Html(page.render().unwrap()).into_response() - } - Err(err) => { - error!(?err, "Error loading nightly state"); - StatusCode::INTERNAL_SERVER_ERROR.into_response() - } - }, + Html(page.render().unwrap()).into_response() + } Err(err) => { error!(?err, "Error loading target state"); StatusCode::INTERNAL_SERVER_ERROR.into_response() @@ -256,6 +169,9 @@ async fn web_root(State(state): State) -> impl IntoResponse { } } +async fn web_full_table() -> impl IntoResponse { + Html(include_str!("../static/full-table.html").replace("{{version}}", crate::VERSION)) +} async fn index_css() -> impl IntoResponse { ( [( @@ -265,12 +181,44 @@ async fn index_css() -> impl IntoResponse { include_str!("../static/index.css"), ) } +async fn index_js() -> impl IntoResponse { + ( + [( + axum::http::header::CONTENT_TYPE, + axum::http::HeaderValue::from_static("text/javascript"), + )], + include_str!("../static/index.js"), + ) +} + +async fn full_mega_monster(State(state): State) -> impl IntoResponse { + state.db.full_mega_monster().await.map(Json).map_err(|err| { + error!(?err, "Error loading target state"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} #[derive(Serialize, Deserialize)] 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 db0ada4..31af71d 100644 --- a/static/build.html +++ b/static/build.html @@ -1,4 +1,4 @@ - + @@ -19,12 +19,6 @@
 {{stderr}}
     
-

- Build history for target {{target}} -

-

- Build state for nightly {{nightly}} -