diff --git a/Cargo.lock b/Cargo.lock index 5fcd03d..866d288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -44,6 +44,50 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "async-trait" version = "0.1.82" @@ -165,6 +209,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -343,6 +396,7 @@ dependencies = [ name = "does-it-build" version = "0.1.0" dependencies = [ + "askama", "axum", "color-eyre", "futures", @@ -694,6 +748,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "1.4.1" @@ -890,6 +953,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2107,6 +2180,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 4a25339..7d05212 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +askama = "0.12.1" axum = { version = "0.7.5", features = ["macros"] } color-eyre = "0.6.3" futures = "0.3.30" diff --git a/README.md b/README.md index 743ce38..9752465 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ It does this in parallel, using half of the available threads (or `DOES_IT_BUILD - `DB_PATH`: Path to SQlite DB to store the results - `DOES_IT_BUILD_PARALLEL_JOBS`: Parallel build jobs, defaults to cores/2. +Build configuration: `DOES_IT_BUILD_OVERRIDE_VERSION` to override the git commit. + ## Deployment deployed at diff --git a/build.rs b/build.rs index 7fa10c1..232ad4d 100644 --- a/build.rs +++ b/build.rs @@ -15,6 +15,10 @@ fn main() { } fn try_get_commit() -> color_eyre::Result { + if let Ok(overridden) = std::env::var("DOES_IT_BUILD_OVERRIDE_VERSION") { + return Ok(overridden); + } + let stdout = std::process::Command::new("git") .arg("rev-parse") .arg("HEAD") diff --git a/src/db.rs b/src/db.rs index e2ba9d0..75a87a4 100644 --- a/src/db.rs +++ b/src/db.rs @@ -100,13 +100,36 @@ impl Db { Ok(()) } - pub async fn build_status(&self) -> Result> { + 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 = ?", + ) + .bind(target) + .fetch_all(&self.conn) + .await + .wrap_err("getting history for single target") + } + + pub async fn target_list(&self) -> Result> { + #[derive(sqlx::FromRow)] + struct TargetName { + target: String, + } + + sqlx::query_as::<_, TargetName>("SELECT DISTINCT target FROM build_info ORDER BY target") + .fetch_all(&self.conn) + .await + .wrap_err("getting list of all targets") + .map(|elems| elems.into_iter().map(|elem| elem.target).collect()) + } + pub async fn build_status_full( &self, nightly: &str, diff --git a/src/nightlies.rs b/src/nightlies.rs index cd26b03..df384ac 100644 --- a/src/nightlies.rs +++ b/src/nightlies.rs @@ -62,6 +62,7 @@ impl Nightlies { &self, already_finished: &[FinishedNightly], ) -> Option<(String, BuildMode)> { + dbg!(&self.all[..20]); let already_finished = HashSet::<_, RandomState>::from_iter(already_finished.iter()); self.all diff --git a/src/web.rs b/src/web.rs index 8ad3180..c287ce4 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,3 +1,5 @@ +use std::{cmp::Reverse, collections::HashMap}; + use axum::{ extract::{Query, State}, http::StatusCode, @@ -9,7 +11,7 @@ use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; use tracing::{error, info}; -use crate::db::{BuildMode, Db}; +use crate::db::{BuildInfo, BuildMode, Db, Status}; #[derive(Clone)] pub struct AppState { @@ -18,11 +20,13 @@ pub struct AppState { pub async fn webserver(db: Db) -> Result<()> { let app = Router::new() - .route("/", get(root)) - .route("/build", get(build)) + .route("/", get(web_root)) + .route("/build", get(web_build)) + .route("/target", get(web_target)) + .route("/full-table", get(web_full_table)) .route("/index.css", get(index_css)) .route("/index.js", get(index_js)) - .route("/target-state", get(target_state)) + .route("/full-mega-monster", get(full_mega_monster)) .route("/trigger-build", post(trigger_build)) .with_state(AppState { db }); @@ -39,7 +43,7 @@ struct BuildQuery { mode: Option, } -async fn build(State(state): State, Query(query): Query) -> Response { +async fn web_build(State(state): State, Query(query): Query) -> Response { match state .db .build_status_full( @@ -68,8 +72,105 @@ async fn build(State(state): State, Query(query): Query) - } } -async fn root() -> impl IntoResponse { - Html(include_str!("../static/index.html").replace("{{version}}", crate::VERSION)) +#[derive(Deserialize)] +struct TargetQuery { + target: String, +} + +async fn web_target(State(state): State, Query(query): Query) -> Response { + use askama::Template; + #[derive(askama::Template)] + #[template(path = "target.html")] + struct TargetPage { + target: String, + status: String, + version: &'static str, + builds: Vec<(String, Option, Option)>, + } + + match state.db.history_for_target(&query.target).await { + Ok(builds) => { + let latest_core = builds + .iter() + .filter(|build| build.mode == BuildMode::Core) + .max_by_key(|elem| elem.nightly.clone()); + let latest_miri = builds + .iter() + .filter(|build| build.mode == BuildMode::Core) + .max_by_key(|elem| elem.nightly.clone()); + + let status = match (latest_core, latest_miri) { + (Some(core), Some(miri)) => { + if core.status == Status::Error || miri.status == Status::Error { + Status::Error + } else { + Status::Pass + } + .to_string() + } + (Some(one), None) | (None, Some(one)) => one.status.to_string(), + (None, None) => "missing".to_owned(), + }; + + let mut builds_grouped = + HashMap::, Option)>::new(); + for build in builds { + let v = builds_grouped.entry(build.nightly.clone()).or_default(); + match build.mode { + BuildMode::Core => v.0 = Some(build), + BuildMode::MiriStd => v.1 = Some(build), + } + } + + let mut builds = builds_grouped + .into_iter() + .map(|(k, (v1, v2))| (k, v1, v2)) + .collect::>(); + builds.sort_by_cached_key(|build| Reverse(build.0.clone())); + + let page = TargetPage { + status, + target: query.target, + version: crate::VERSION, + builds, + }; + + 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, + version: &'static str, + } + + match state.db.target_list().await { + Ok(targets) => { + let page = RootPage { + targets, + version: crate::VERSION, + }; + + Html(page.render().unwrap()).into_response() + } + Err(err) => { + error!(?err, "Error loading target state"); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } +} + +async fn web_full_table() -> impl IntoResponse { + Html(include_str!("../static/full-table.html").replace("{{version}}", crate::VERSION)) } async fn index_css() -> impl IntoResponse { ( @@ -90,8 +191,8 @@ async fn index_js() -> impl IntoResponse { ) } -async fn target_state(State(state): State) -> impl IntoResponse { - state.db.build_status().await.map(Json).map_err(|err| { +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 }) @@ -117,3 +218,21 @@ async fn trigger_build( // // StatusCode::ACCEPTED } + +impl Status { + fn to_emoji(&self) -> &'static str { + match self { + Status::Pass => "✅", + Status::Error => "❌", + } + } +} + +impl BuildInfo { + fn link(&self) -> String { + format!( + "build?nightly={}&target={}&mode={}", + self.nightly, self.target, self.mode + ) + } +} diff --git a/static/build.html b/static/build.html index cfdfa64..31af71d 100644 --- a/static/build.html +++ b/static/build.html @@ -12,8 +12,7 @@ -

Build results for nightly-{{nightly}} target-{{target}} {{mode}}

- Back +

Build results for nightly-{{nightly}} target {{target}} {{mode}}

{{status}}
diff --git a/static/index.html b/static/full-table.html similarity index 100% rename from static/index.html rename to static/full-table.html diff --git a/static/index.css b/static/index.css index a4201f3..876e7ed 100644 --- a/static/index.css +++ b/static/index.css @@ -24,14 +24,16 @@ td { writing-mode: sideways-lr; } +.build-cell { + font-size: 2rem; +} + .build-info-a { color: black; text-decoration: none; -} - -.build-cell { - padding-left: 5px; - padding-right: 5px; + height: 100%; + width: 100%; + padding: 5px; } .target-name-col { diff --git a/static/index.js b/static/index.js index 140551c..0828dfc 100644 --- a/static/index.js +++ b/static/index.js @@ -143,7 +143,7 @@ const miriTable = new Table( ); function fetchTargets() { - fetch("target-state") + fetch("full-mega-monster") .then((body) => body.json()) .then((body) => { const core = body.filter((info) => info.mode === "core"); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..4177386 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,52 @@ + + + + + + Does it build? + + + + +

Does it build?

+

+ This website builds every rustc target on many nightlies to check which + ones work and which ones do not. +

+ +

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). +

+ +
    + {% for target in targets %} +
  • + {{ target }} +
  • + {% endfor %} +
+ + + + + diff --git a/templates/target.html b/templates/target.html new file mode 100644 index 0000000..5c2135e --- /dev/null +++ b/templates/target.html @@ -0,0 +1,78 @@ + + + + + + {{target}} build history + + + + +

Target build history for {{target}}

+ Back +
+ {{status}} +
+

+ This contains the history of this target. Core is built with + cargo build --release -Zbuild-std=core. This checks that + codegen/linking of core works, but does not check whether std builds. +

+

+ std is being built with cargo miri setup. If a target does + not support std, the std column represents core/alloc. This checks that + std builds (on targets that have it) but does not check whether + codegen/linking works. +

+ + + + + + + {% for build in builds %} + + + {% match build.1 %} {% when Some with (build) %} + + {% when None %} + + {% endmatch %} {% match build.2 %} {% when Some with (build) %} + + {% when None %} + + {% endmatch %} + + {% endfor %} +
nightlycorestd
{{ build.0 }} + + {{ build.status.to_emoji() }} + + + + {{ build.status.to_emoji() }} + +
+ + +