diff --git a/build.rs b/build.rs index ba68507..945d25a 100644 --- a/build.rs +++ b/build.rs @@ -12,6 +12,12 @@ fn main() { }; println!("cargo:rustc-env=GIT_COMMIT={version}"); + let version_short = if version.len() > 16 { + &version[..16] + } else { + &version + }; + println!("cargo:rustc-env=GIT_COMMIT_SHORT={version_short}"); } fn try_get_commit() -> color_eyre::Result { diff --git a/migrations/20250704201048_more-build-metadata.sql b/migrations/20250704201048_more-build-metadata.sql new file mode 100644 index 0000000..d690276 --- /dev/null +++ b/migrations/20250704201048_more-build-metadata.sql @@ -0,0 +1,10 @@ +-- Add migration script here + +ALTER TABLE "build_info" + ADD COLUMN "build_date" INTEGER NULL; + +ALTER TABLE "build_info" + ADD COLUMN "build_duration_ms" INTEGER NULL; + +ALTER TABLE "build_info" + ADD COLUMN "does_it_build_version" VARCHAR NULL; diff --git a/migrations/20250704205252_build-count-index.sql b/migrations/20250704205252_build-count-index.sql new file mode 100644 index 0000000..efaa7b9 --- /dev/null +++ b/migrations/20250704205252_build-count-index.sql @@ -0,0 +1,3 @@ +-- Add migration script here + +CREATE INDEX IF NOT EXISTS build_info_status ON build_info (status); diff --git a/src/build.rs b/src/build.rs index e2d35f2..fa626c7 100644 --- a/src/build.rs +++ b/src/build.rs @@ -2,7 +2,7 @@ use std::{ fmt::{Debug, Display}, num::NonZeroUsize, path::Path, - time::Duration, + time::{Duration, Instant, SystemTime}, }; use color_eyre::{ @@ -52,6 +52,13 @@ impl Display for Toolchain { } pub async fn background_builder(db: Db) -> Result<()> { + if concurrent_jobs() == 0 { + info!("Suspending background thread since DOES_IT_BUILD_PARALLEL_JOBS=0"); + loop { + tokio::time::sleep(Duration::from_secs(3600)).await; + } + } + loop { if let Err(err) = background_builder_inner(&db).await { error!("error in background builder: {err}"); @@ -192,21 +199,12 @@ pub async fn build_every_target_for_toolchain( .await .wrap_err("failed to get targets")?; - let concurrent = std::env::var("DOES_IT_BUILD_PARALLEL_JOBS") - .map(|jobs| jobs.parse().unwrap()) - .unwrap_or_else(|_| { - std::thread::available_parallelism() - .unwrap_or(NonZeroUsize::new(2).unwrap()) - .get() - / 2 - }); - let results = futures::stream::iter( targets .iter() .map(|target| build_single_target(db, nightly, target, mode)), ) - .buffer_unordered(concurrent) + .buffer_unordered(concurrent_jobs()) .collect::>>() .await; for result in results { @@ -242,6 +240,8 @@ async fn build_single_target(db: &Db, nightly: &str, target: &str, mode: BuildMo let tmpdir = tempfile::tempdir().wrap_err("creating temporary directory")?; + let start_time = Instant::now(); + let result = build_target( tmpdir.path(), &Toolchain::from_nightly(nightly), @@ -258,6 +258,16 @@ async fn build_single_target(db: &Db, nightly: &str, target: &str, mode: BuildMo stderr: result.stderr, mode, rustflags: result.rustflags, + build_date: Some( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() + .try_into() + .unwrap(), + ), + does_it_build_version: Some(crate::VERSION_SHORT.into()), + build_duration_ms: Some(start_time.elapsed().as_millis().try_into().unwrap()), }) .await?; @@ -342,3 +352,14 @@ async fn build_target( rustflags, }) } + +fn concurrent_jobs() -> usize { + std::env::var("DOES_IT_BUILD_PARALLEL_JOBS") + .map(|jobs| jobs.parse().unwrap()) + .unwrap_or_else(|_| { + std::thread::available_parallelism() + .unwrap_or(NonZeroUsize::new(2).unwrap()) + .get() + / 2 + }) +} diff --git a/src/db.rs b/src/db.rs index 73aa95f..4bc3b58 100644 --- a/src/db.rs +++ b/src/db.rs @@ -49,6 +49,9 @@ pub struct FullBuildInfo { pub stderr: String, pub mode: BuildMode, pub rustflags: Option, + pub build_date: Option, + pub does_it_build_version: Option, + pub build_duration_ms: Option, } #[derive(Debug, PartialEq, Clone, Copy, sqlx::Type, Serialize, Deserialize)] @@ -82,6 +85,17 @@ pub struct FinishedNightlyWithBroken { pub broken_error: Option, } +pub struct BuildStats { + pub pass_count: u32, + pub error_count: u32, +} + +impl BuildStats { + pub fn total(&self) -> u32 { + self.pass_count + self.error_count + } +} + impl Db { pub async fn open(path: &str) -> Result { let db_opts = SqliteConnectOptions::from_str(path) @@ -96,7 +110,8 @@ impl Db { pub async fn insert(&self, info: FullBuildInfo) -> Result<()> { sqlx::query( - "INSERT INTO build_info (nightly, target, status, stderr, mode, rustflags) VALUES (?, ?, ?, ?, ?, ?);", + "INSERT INTO build_info (nightly, target, status, stderr, mode, rustflags, build_date, does_it_build_version, build_duration_ms) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);", ) .bind(info.nightly) .bind(info.target) @@ -104,6 +119,9 @@ impl Db { .bind(info.stderr) .bind(info.mode) .bind(info.rustflags) + .bind(info.build_date) + .bind(info.does_it_build_version) + .bind(info.build_duration_ms) .execute(&self.conn) .await .wrap_err("inserting build info into database")?; @@ -168,6 +186,34 @@ impl Db { .map(|elems| elems.into_iter().map(|elem| elem.nightly).collect()) } + pub async fn build_count(&self) -> Result { + #[derive(sqlx::FromRow)] + struct BuildStat { + build_count: u32, + status: Status, + } + + let results = sqlx::query_as::<_, BuildStat>( + "SELECT COUNT(status) as build_count, status FROM build_info GROUP BY status", + ) + .fetch_all(&self.conn) + .await + .wrap_err("getting list of all targets")?; + + let count = |status| { + results + .iter() + .find(|row| row.status == status) + .map(|row| row.build_count) + .unwrap_or(0) + }; + + Ok(BuildStats { + pass_count: count(Status::Pass), + error_count: count(Status::Error), + }) + } + pub async fn build_status_full( &self, nightly: &str, @@ -175,7 +221,7 @@ impl Db { mode: BuildMode, ) -> Result> { let result = sqlx::query_as::<_, FullBuildInfo>( - "SELECT nightly, target, status, stderr, mode, rustflags FROM build_info + "SELECT nightly, target, status, stderr, mode, rustflags, build_date, does_it_build_version, build_duration_ms FROM build_info WHERE nightly = ? AND target = ? AND mode = ?", ) .bind(nightly) diff --git a/src/main.rs b/src/main.rs index 5a46bc3..0619c77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use db::Db; use tracing_subscriber::EnvFilter; const VERSION: &str = env!("GIT_COMMIT"); +const VERSION_SHORT: &str = env!("GIT_COMMIT_SHORT"); #[tokio::main] async fn main() -> Result<()> { diff --git a/src/web.rs b/src/web.rs index 0866c46..f52fdeb 100644 --- a/src/web.rs +++ b/src/web.rs @@ -11,7 +11,7 @@ use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; use tracing::{error, info}; -use crate::db::{BuildInfo, BuildMode, Db, Status}; +use crate::db::{BuildInfo, BuildMode, BuildStats, Db, Status}; #[derive(Clone)] pub struct AppState { @@ -25,6 +25,7 @@ pub async fn webserver(db: Db) -> Result<()> { .route("/target", get(web_target)) .route("/nightly", get(web_nightly)) .route("/index.css", get(index_css)) + .route("/index.js", get(index_js)) .with_state(AppState { db }); info!("Serving website on port 3000 (commit {})", crate::VERSION); @@ -52,6 +53,9 @@ async fn web_build(State(state): State, Query(query): Query, version: &'static str, status: Status, + build_date: Option, + build_duration_s: Option, + does_it_build_version: Option, } match state @@ -72,6 +76,19 @@ async fn web_build(State(state): State, Query(query): Query, Query(query): Query) -> 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, - }; - - 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() + async fn render(state: AppState) -> Result { + #[derive(askama::Template)] + #[template(path = "index.html")] + struct RootPage { + targets: Vec, + nightlies: Vec, + version: &'static str, + build_count: BuildStats, } + + let targets = state.db.target_list().await?; + let nightlies = state.db.nightly_list().await?; + let build_count = state.db.build_count().await?; + + let page = RootPage { + targets, + nightlies, + version: crate::VERSION, + build_count, + }; + + Ok(Html(page.render().unwrap()).into_response()) } + + render(state) + .await + .unwrap_or_else(|err: color_eyre::eyre::Error| { + error!(?err, "Error loading data for root page"); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + }) } async fn index_css() -> impl IntoResponse { @@ -298,6 +317,16 @@ async fn index_css() -> impl IntoResponse { ) } +async fn index_js() -> impl IntoResponse { + ( + [( + axum::http::header::CONTENT_TYPE, + axum::http::HeaderValue::from_static("application/javascript; charset=utf-8"), + )], + include_str!("../static/index.js"), + ) +} + #[derive(Serialize, Deserialize)] struct TriggerBuildBody { nightly: String, diff --git a/static/index.js b/static/index.js new file mode 100644 index 0000000..a1e30c1 --- /dev/null +++ b/static/index.js @@ -0,0 +1,13 @@ +// Do some fancy number formatting in your locale, so you can't blame me if the numbers look ass. +document.querySelectorAll(".number").forEach((elem) => { + elem.textContent = new Intl.NumberFormat().format(Number(elem.textContent)); +}); + +// Do some fancy date formatting in your locale, so you can't blame me if the numbers look ass. +document.querySelectorAll(".date").forEach((elem) => { + console.log("run") + elem.textContent = new Intl.DateTimeFormat(undefined, { + dateStyle: "short", + timeStyle: "short", + }).format(new Date(elem.textContent)); +}); diff --git a/templates/build.html b/templates/build.html index 4e28ced..fb90738 100644 --- a/templates/build.html +++ b/templates/build.html @@ -5,6 +5,7 @@ Build {{nightly}} {{target}} + +

Does it build?

- This website builds every rustc target on many nightlies to check which - ones work and which ones do not. + This website builds the standard libary for every rustc target on every + nightly to check whether it builds or not. +

+

+ There are currently + {{build_count.total()}} build results stored, + with {{build_count.pass_count}} passing and + {{build_count.error_count}} erroring.

-

You can select a target from the list below and check its history.

- If that is not what you want, you can visit the - full table to see - all information at once (which will really stress test - your browser). + You can select a nightly or target from the list below and check its + history.

Nightlies

@@ -58,20 +61,8 @@ diff --git a/templates/nightly.html b/templates/nightly.html index 6176b5a..16bb06f 100644 --- a/templates/nightly.html +++ b/templates/nightly.html @@ -86,20 +86,8 @@ {% endfor %} diff --git a/templates/target.html b/templates/target.html index 37aafb5..b01ccee 100644 --- a/templates/target.html +++ b/templates/target.html @@ -67,20 +67,8 @@ {% endfor %}