add miri std support

This commit is contained in:
nora 2024-09-08 11:44:16 +02:00
parent ccb57e400c
commit 262e8acd9f
8 changed files with 394 additions and 225 deletions

View file

@ -0,0 +1,43 @@
PRAGMA foreign_keys=OFF;
-- Migrate build_info
CREATE TABLE new_build_info (
"nightly" VARCHAR NOT NULL,
"target" VARCHAR NOT NULL,
"status" VARCHAR NOT NULL,
"stderr" VARCHAR NOT NULL,
"mode" VARCHAR NOT NULL,
PRIMARY KEY ("nightly", "target", "mode")
);
INSERT INTO new_build_info (nightly, target, status, stderr, mode)
SELECT nightly, target, status, stderr, 'core' FROM build_info;
DROP TABLE build_info;
ALTER TABLE new_build_info RENAME TO build_info;
-- Migrate finished_nightly
CREATE TABLE new_finished_nightly (
"nightly" VARCHAR NOT NULL,
"mode" VARCHAR NOT NULL,
PRIMARY KEY ("nightly", "mode")
);
INSERT INTO new_finished_nightly (nightly, mode)
SELECT nightly, 'core' FROM finished_nightly;
DROP TABLE finished_nightly;
ALTER TABLE new_finished_nightly RENAME TO finished_nightly;
-- Finish
PRAGMA foreign_keys=ON;

View file

@ -14,7 +14,7 @@ use tokio::process::Command;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
db::{Db, FullBuildInfo, Status}, db::{BuildMode, Db, FullBuildInfo, Status},
nightlies::Nightlies, nightlies::Nightlies,
}; };
@ -45,9 +45,9 @@ pub async fn background_builder(db: Db) -> Result<()> {
let next = nightlies.select_latest_to_build(&already_finished); let next = nightlies.select_latest_to_build(&already_finished);
match next { match next {
Some(nightly) => { Some((nightly, mode)) => {
info!(%nightly, "Building next nightly"); info!(%nightly, %mode, "Building next nightly");
build_every_target_for_toolchain(&db, &nightly) build_every_target_for_toolchain(&db, &nightly, mode)
.await .await
.wrap_err_with(|| format!("building targets for toolchain {nightly}"))?; .wrap_err_with(|| format!("building targets for toolchain {nightly}"))?;
} }
@ -82,7 +82,7 @@ async fn targets_for_toolchain(toolchain: &Toolchain) -> Result<Vec<String>> {
} }
#[tracing::instrument] #[tracing::instrument]
async fn install_toolchain(toolchain: &Toolchain) -> Result<()> { async fn install_toolchain(toolchain: &Toolchain, mode: BuildMode) -> Result<()> {
info!(%toolchain, "Installing toolchain"); info!(%toolchain, "Installing toolchain");
let result = Command::new("rustup") let result = Command::new("rustup")
@ -109,6 +109,20 @@ async fn install_toolchain(toolchain: &Toolchain) -> Result<()> {
if !result.status.success() { if !result.status.success() {
bail!("rustup failed: {:?}", String::from_utf8(result.stderr)); bail!("rustup failed: {:?}", String::from_utf8(result.stderr));
} }
if mode == BuildMode::MiriStd {
let result = Command::new("rustup")
.arg("component")
.arg("add")
.arg("miri")
.arg("--toolchain")
.arg(&toolchain.0)
.output()
.await
.wrap_err("failed to spawn rustup")?;
if !result.status.success() {
bail!("rustup failed: {:?}", String::from_utf8(result.stderr));
}
}
Ok(()) Ok(())
} }
@ -132,14 +146,18 @@ async fn uninstall_toolchain(toolchain: &Toolchain) -> Result<()> {
Ok(()) Ok(())
} }
pub async fn build_every_target_for_toolchain(db: &Db, nightly: &str) -> Result<()> { pub async fn build_every_target_for_toolchain(
if db.is_nightly_finished(nightly).await? { db: &Db,
nightly: &str,
mode: BuildMode,
) -> Result<()> {
if db.is_nightly_finished(nightly, mode).await? {
debug!("Nightly is already finished, not trying again"); debug!("Nightly is already finished, not trying again");
return Ok(()); return Ok(());
} }
let toolchain = Toolchain::from_nightly(nightly); let toolchain = Toolchain::from_nightly(nightly);
install_toolchain(&toolchain).await?; install_toolchain(&toolchain, mode).await?;
let targets = targets_for_toolchain(&toolchain) let targets = targets_for_toolchain(&toolchain)
.await .await
@ -153,7 +171,7 @@ pub async fn build_every_target_for_toolchain(db: &Db, nightly: &str) -> Result<
let results = futures::stream::iter( let results = futures::stream::iter(
targets targets
.iter() .iter()
.map(|target| build_single_target(&db, nightly, target)), .map(|target| build_single_target(&db, nightly, target, mode)),
) )
.buffer_unordered(concurrent) .buffer_unordered(concurrent)
.collect::<Vec<Result<()>>>() .collect::<Vec<Result<()>>>()
@ -163,13 +181,13 @@ pub async fn build_every_target_for_toolchain(db: &Db, nightly: &str) -> Result<
} }
for target in targets { for target in targets {
build_single_target(db, nightly, &target) build_single_target(db, nightly, &target, mode)
.await .await
.wrap_err_with(|| format!("building target {target} for toolchain {toolchain}"))?; .wrap_err_with(|| format!("building target {target} for toolchain {toolchain}"))?;
} }
// Mark it as finished, so we never have to build it again. // Mark it as finished, so we never have to build it again.
db.finish_nightly(nightly).await?; db.finish_nightly(nightly, mode).await?;
uninstall_toolchain(&toolchain).await?; uninstall_toolchain(&toolchain).await?;
@ -177,9 +195,9 @@ pub async fn build_every_target_for_toolchain(db: &Db, nightly: &str) -> Result<
} }
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
async fn build_single_target(db: &Db, nightly: &str, target: &str) -> Result<()> { async fn build_single_target(db: &Db, nightly: &str, target: &str, mode: BuildMode) -> Result<()> {
let existing = db let existing = db
.build_status_full(nightly, target) .build_status_full(nightly, target, mode)
.await .await
.wrap_err("getting existing build")?; .wrap_err("getting existing build")?;
if existing.is_some() { if existing.is_some() {
@ -191,15 +209,21 @@ async fn build_single_target(db: &Db, nightly: &str, target: &str) -> Result<()>
let tmpdir = tempfile::tempdir().wrap_err("creating temporary directory")?; let tmpdir = tempfile::tempdir().wrap_err("creating temporary directory")?;
let result = build_target(tmpdir.path(), &Toolchain::from_nightly(nightly), target) let result = build_target(
.await tmpdir.path(),
.wrap_err("running build")?; &Toolchain::from_nightly(nightly),
target,
mode,
)
.await
.wrap_err("running build")?;
db.insert(FullBuildInfo { db.insert(FullBuildInfo {
nightly: nightly.into(), nightly: nightly.into(),
target: target.into(), target: target.into(),
status: result.status, status: result.status,
stderr: result.stderr, stderr: result.stderr,
mode,
}) })
.await?; .await?;
@ -212,31 +236,47 @@ struct BuildResult {
} }
/// Build a target core in a temporary directory and see whether it passes or not. /// Build a target core in a temporary directory and see whether it passes or not.
async fn build_target(tmpdir: &Path, toolchain: &Toolchain, target: &str) -> Result<BuildResult> { async fn build_target(
std::fs::create_dir_all(&tmpdir).wrap_err("creating target src dir")?; tmpdir: &Path,
toolchain: &Toolchain,
target: &str,
mode: BuildMode,
) -> Result<BuildResult> {
let output = match mode {
BuildMode::Core => {
let init = Command::new("cargo")
.args(["init", "--lib", "--name", "target-test"])
.current_dir(&tmpdir)
.output()
.await
.wrap_err("spawning cargo init")?;
if !init.status.success() {
bail!("init failed: {}", String::from_utf8(init.stderr)?);
}
let init = Command::new("cargo") let librs = tmpdir.join("src").join("lib.rs");
.args(["init", "--lib", "--name", "target-test"]) std::fs::write(&librs, "#![no_std]\n")
.current_dir(&tmpdir) .wrap_err_with(|| format!("writing to {}", librs.display()))?;
.output()
.await
.wrap_err("spawning cargo init")?;
if !init.status.success() {
bail!("init failed: {}", String::from_utf8(init.stderr)?);
}
let librs = tmpdir.join("src").join("lib.rs"); Command::new("cargo")
std::fs::write(&librs, "#![no_std]\n") .arg(format!("+{toolchain}"))
.wrap_err_with(|| format!("writing to {}", librs.display()))?; .args(["build", "-Zbuild-std=core", "--release"])
.args(["--target", target])
let output = Command::new("cargo") .current_dir(&tmpdir)
.arg(format!("+{toolchain}")) .output()
.args(["build", "-Zbuild-std=core", "--release"]) .await
.args(["--target", target]) .wrap_err("spawning cargo build")?
.current_dir(&tmpdir) }
.output() BuildMode::MiriStd => Command::new("cargo")
.await .arg(format!("+{toolchain}"))
.wrap_err("spawning cargo build")?; .args(["miri", "setup"])
.args(["--target", target])
.current_dir(&tmpdir)
.env("MIRI_SYSROOT", tmpdir)
.output()
.await
.wrap_err("spawning cargo build")?,
};
let stderr = String::from_utf8(output.stderr).wrap_err("cargo stderr utf8")?; let stderr = String::from_utf8(output.stderr).wrap_err("cargo stderr utf8")?;

View file

@ -1,6 +1,9 @@
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
use color_eyre::{eyre::Context, Result}; use color_eyre::{
eyre::{bail, Context},
Result,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{migrate::Migrator, sqlite::SqliteConnectOptions, Pool, Sqlite}; use sqlx::{migrate::Migrator, sqlite::SqliteConnectOptions, Pool, Sqlite};
@ -11,11 +14,31 @@ pub struct Db {
pub static MIGRATOR: Migrator = sqlx::migrate!(); pub static MIGRATOR: Migrator = sqlx::migrate!();
#[derive(Debug, Clone, Copy, sqlx::Type, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[sqlx(rename_all = "kebab-case")]
#[serde(rename_all = "kebab-case")]
pub enum BuildMode {
/// `-Zbuild-std=core`
Core,
/// `cargo miri setup`
MiriStd,
}
impl Display for BuildMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Core => f.write_str("core"),
Self::MiriStd => f.write_str("miri-std"),
}
}
}
#[derive(sqlx::FromRow, Serialize, Deserialize)] #[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct BuildInfo { pub struct BuildInfo {
pub nightly: String, pub nightly: String,
pub target: String, pub target: String,
pub status: Status, pub status: Status,
pub mode: BuildMode,
} }
#[derive(Clone, sqlx::FromRow, Serialize, Deserialize)] #[derive(Clone, sqlx::FromRow, Serialize, Deserialize)]
@ -24,6 +47,7 @@ pub struct FullBuildInfo {
pub target: String, pub target: String,
pub status: Status, pub status: Status,
pub stderr: String, pub stderr: String,
pub mode: BuildMode,
} }
#[derive(Debug, PartialEq, Clone, Copy, sqlx::Type, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Copy, sqlx::Type, Serialize, Deserialize)]
@ -43,9 +67,10 @@ impl Display for Status {
} }
} }
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow, Debug, PartialEq, Eq, Hash)]
struct FinishedNightly { pub struct FinishedNightly {
nightly: String, pub nightly: String,
pub mode: BuildMode,
} }
impl Db { impl Db {
@ -62,12 +87,13 @@ impl Db {
pub async fn insert(&self, info: FullBuildInfo) -> Result<()> { pub async fn insert(&self, info: FullBuildInfo) -> Result<()> {
sqlx::query( sqlx::query(
"INSERT INTO build_info (nightly, target, status, stderr) VALUES (?, ?, ?, ?);", "INSERT INTO build_info (nightly, target, status, stderr, mode) VALUES (?, ?, ?, ?, ?);",
) )
.bind(info.nightly) .bind(info.nightly)
.bind(info.target) .bind(info.target)
.bind(info.status) .bind(info.status)
.bind(info.stderr) .bind(info.stderr)
.bind(info.mode)
.execute(&self.conn) .execute(&self.conn)
.await .await
.wrap_err("inserting build info into database")?; .wrap_err("inserting build info into database")?;
@ -75,7 +101,7 @@ impl Db {
} }
pub async fn build_status(&self) -> Result<Vec<BuildInfo>> { pub async fn build_status(&self) -> Result<Vec<BuildInfo>> {
sqlx::query_as::<_, BuildInfo>("SELECT nightly, target, status FROM build_info") sqlx::query_as::<_, BuildInfo>("SELECT nightly, target, status, mode FROM build_info")
.fetch_all(&self.conn) .fetch_all(&self.conn)
.await .await
.wrap_err("getting build status from DB") .wrap_err("getting build status from DB")
@ -85,43 +111,52 @@ impl Db {
&self, &self,
nightly: &str, nightly: &str,
target: &str, target: &str,
mode: BuildMode,
) -> Result<Option<FullBuildInfo>> { ) -> Result<Option<FullBuildInfo>> {
let result = sqlx::query_as::<_, FullBuildInfo>( let result = sqlx::query_as::<_, FullBuildInfo>(
"SELECT nightly, target, status, stderr FROM build_info "SELECT nightly, target, status, stderr, mode FROM build_info
WHERE nightly = ? AND target = ?", WHERE nightly = ? AND target = ? AND mode = ?",
) )
.bind(nightly) .bind(nightly)
.bind(target) .bind(target)
.bind(mode)
.fetch_all(&self.conn) .fetch_all(&self.conn)
.await .await
.wrap_err("getting build status from DB")?; .wrap_err("getting build status from DB")?;
Ok(result.first().cloned()) Ok(result.first().cloned())
} }
pub async fn finished_nightlies(&self) -> Result<Vec<String>> { pub async fn finished_nightlies(&self) -> Result<Vec<FinishedNightly>> {
let result = sqlx::query_as::<_, FinishedNightly>("SELECT nightly from finished_nightly") let result =
.fetch_all(&self.conn) sqlx::query_as::<_, FinishedNightly>("SELECT nightly, mode from finished_nightly")
.await .fetch_all(&self.conn)
.wrap_err("fetching fnished nightlies")?; .await
.wrap_err("fetching finished nightlies")?;
Ok(result.into_iter().map(|nightly| nightly.nightly).collect()) Ok(result)
} }
pub async fn is_nightly_finished(&self, nightly: &str) -> Result<bool> { pub async fn is_nightly_finished(&self, nightly: &str, mode: BuildMode) -> Result<bool> {
let result = sqlx::query_as::<_, FinishedNightly>( let result = sqlx::query_as::<_, FinishedNightly>(
"SELECT nightly from finished_nightly WHERE nightly = ?", "SELECT nightly, mode from finished_nightly WHERE nightly = ? AND mode = ?",
) )
.bind(nightly) .bind(nightly)
.bind(mode)
.fetch_all(&self.conn) .fetch_all(&self.conn)
.await .await
.wrap_err("fetching fnished nightlies")?; .wrap_err("checking whether a nightly is finished")?;
if result.len() > 1 {
bail!("found more than one result for {nightly} {mode}");
}
Ok(result.len() == 1) Ok(result.len() == 1)
} }
pub async fn finish_nightly(&self, nightly: &str) -> Result<()> { pub async fn finish_nightly(&self, nightly: &str, mode: BuildMode) -> Result<()> {
sqlx::query("INSERT INTO finished_nightly (nightly) VALUES (?)") sqlx::query("INSERT INTO finished_nightly (nightly, mode) VALUES (?, ?)")
.bind(nightly) .bind(nightly)
.bind(mode)
.execute(&self.conn) .execute(&self.conn)
.await .await
.wrap_err("inserting finished nightly")?; .wrap_err("inserting finished nightly")?;

View file

@ -5,12 +5,15 @@ mod web;
use color_eyre::{eyre::WrapErr, Result}; use color_eyre::{eyre::WrapErr, Result};
use db::Db; use db::Db;
use tracing_subscriber::EnvFilter;
const VERSION: &str = env!("GIT_COMMIT"); const VERSION: &str = env!("GIT_COMMIT");
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
tracing_subscriber::fmt().init(); tracing_subscriber::fmt()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("info")))
.init();
let db = Db::open(&std::env::var("DB_PATH").unwrap_or("db.sqlite".into())).await?; let db = Db::open(&std::env::var("DB_PATH").unwrap_or("db.sqlite".into())).await?;
db::MIGRATOR db::MIGRATOR

View file

@ -5,6 +5,8 @@ use color_eyre::eyre::Context;
use color_eyre::Result; use color_eyre::Result;
use tracing::debug; use tracing::debug;
use crate::db::{BuildMode, FinishedNightly};
const EARLIEST_CUTOFF_DATE: &str = "2023-01-01"; const EARLIEST_CUTOFF_DATE: &str = "2023-01-01";
/// All nightlies that exist. /// All nightlies that exist.
@ -29,13 +31,22 @@ impl Nightlies {
Ok(Self { all }) Ok(Self { all })
} }
pub fn select_latest_to_build(&self, already_finished: &[String]) -> Option<String> { pub fn select_latest_to_build(
&self,
already_finished: &[FinishedNightly],
) -> Option<(String, BuildMode)> {
let already_finished = HashSet::<_, RandomState>::from_iter(already_finished.iter()); let already_finished = HashSet::<_, RandomState>::from_iter(already_finished.iter());
self.all self.all
.iter() .iter()
.find(|nightly| !already_finished.contains(nightly)) .flat_map(|nightly| [(nightly, BuildMode::Core), (nightly, BuildMode::MiriStd)])
.cloned() .find(|(nightly, mode)| {
!already_finished.contains(&FinishedNightly {
nightly: (*nightly).to_owned(),
mode: *mode,
})
})
.map(|(nightly, mode)| (nightly.clone(), mode))
} }
} }

View file

@ -9,7 +9,7 @@ use color_eyre::{eyre::Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{error, info}; use tracing::{error, info};
use crate::db::Db; use crate::db::{BuildMode, Db};
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
@ -21,6 +21,7 @@ pub async fn webserver(db: Db) -> Result<()> {
.route("/", get(root)) .route("/", get(root))
.route("/build", get(build)) .route("/build", get(build))
.route("/index.css", get(index_css)) .route("/index.css", get(index_css))
.route("/index.js", get(index_js))
.route("/target-state", get(target_state)) .route("/target-state", get(target_state))
.route("/trigger-build", post(trigger_build)) .route("/trigger-build", post(trigger_build))
.with_state(AppState { db }); .with_state(AppState { db });
@ -31,20 +32,21 @@ pub async fn webserver(db: Db) -> Result<()> {
axum::serve(listener, app).await.wrap_err("failed to serve") axum::serve(listener, app).await.wrap_err("failed to serve")
} }
async fn root() -> impl IntoResponse {
Html(include_str!("../static/index.html").replace("{{version}}", crate::VERSION))
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct BuildQuery { struct BuildQuery {
nightly: String, nightly: String,
target: String, target: String,
mode: Option<BuildMode>,
} }
async fn build(State(state): State<AppState>, Query(query): Query<BuildQuery>) -> Response { async fn build(State(state): State<AppState>, Query(query): Query<BuildQuery>) -> Response {
match state match state
.db .db
.build_status_full(&query.nightly, &query.target) .build_status_full(
&query.nightly,
&query.target,
query.mode.unwrap_or(BuildMode::Core),
)
.await .await
{ {
Ok(Some(build)) => { Ok(Some(build)) => {
@ -65,6 +67,9 @@ async fn build(State(state): State<AppState>, Query(query): Query<BuildQuery>) -
} }
} }
async fn root() -> impl IntoResponse {
Html(include_str!("../static/index.html").replace("{{version}}", crate::VERSION))
}
async fn index_css() -> impl IntoResponse { async fn index_css() -> impl IntoResponse {
( (
[( [(
@ -74,6 +79,15 @@ async fn index_css() -> impl IntoResponse {
include_str!("../static/index.css"), 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 target_state(State(state): State<AppState>) -> impl IntoResponse { async fn target_state(State(state): State<AppState>) -> impl IntoResponse {
state.db.build_status().await.map(Json).map_err(|err| { state.db.build_status().await.map(Json).map_err(|err| {

View file

@ -4,19 +4,15 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Does it build?</title> <title>Does it build?</title>
<link rel="stylesheet" href="/index.css" /> <link rel="stylesheet" href="index.css" />
</head> </head>
<body> <body>
<h1>Does it build?</h1> <h1>Does it build?</h1>
<!--core-->
<h2>Core Build</h2>
<p>Builds every target with: <p>Builds every target with:
<pre>cargo build --release -Zbuild-std=core</pre></p> <pre>cargo build --release -Zbuild-std=core</pre></p>
<p>Does therefore currently not check for the std build status.</p> <p>This checks that codegen/linking of core works, but does not check whether std builds.</p>
<!--<form id="nightly-form">
<h2>Force manual build</h2>
<label for="nightly-date-field">Nightly date</label>
<input id="nightly-date-field" placeholder="2024-08-05" type="text" />
<input type="submit" />
</form>-->
<label for="target-filter">Target Filter</label> <label for="target-filter">Target Filter</label>
<input id="target-filter" /> <input id="target-filter" />
<label for="target-filter-failed">Filter failed</label> <label for="target-filter-failed">Filter failed</label>
@ -27,6 +23,23 @@
<td>loading...</td> <td>loading...</td>
</tr> </tr>
</table> </table>
<!--std-->
<h2>Miri Std Build</h2>
<p>Builds every target with:
<pre>cargo miri setup</pre></p>
<p>This checks that std builds (on targets that have it) but does not check whether codegen/linking works.</p>
<label for="target-filter-miri">Target Filter</label>
<input id="target-filter-miri" />
<label for="target-filter-failed-miri">Filter failed</label>
<input type="checkbox" id="target-filter-failed-miri" />
<table id="target-state-miri" class="target-state-table">
<tr>
<td>loading...</td>
</tr>
</table>
<footer class="footer"> <footer class="footer">
<span>does-it-build {{version}}</span> <span>does-it-build {{version}}</span>
<a href="https://github.com/Noratrieb/does-it-build"> <a href="https://github.com/Noratrieb/does-it-build">
@ -44,155 +57,7 @@
</svg> </svg>
</a> </a>
</footer> </footer>
<script> <script src="index.js">
let data = [];
let filter = localStorage.getItem("filter") ?? "";
document.getElementById("target-filter").value = filter;
document.getElementById("target-filter-failed").value =
localStorage.getItem("filterFailed") ?? false;
const table = document.getElementById("target-state");
function fetchTargets() {
fetch("/target-state")
.then((body) => body.json())
.then((body) => {
data = body;
renderTable();
});
}
function renderTable() {
const allTargets = new Set();
const allNightlies = new Set();
const nightlyInfos = new Map();
// Targets that have, at some point, errored
const targetsWithErrors = new Set();
for (const info of data) {
allNightlies.add(info.nightly);
if (!info.target.includes(filter)) {
continue;
}
if (info.status === "error") {
targetsWithErrors.add(info.target);
}
allTargets.add(info.target);
if (!nightlyInfos.has(info.nightly)) {
nightlyInfos.set(info.nightly, new Map());
}
nightlyInfos.get(info.nightly).set(info.target, info);
}
const nightlies = Array.from(allNightlies);
nightlies.sort();
nightlies.reverse();
const targets = Array.from(allTargets);
targets.sort();
const header = document.createElement("tr");
const headerNightly = document.createElement("th");
headerNightly.innerText = "nightly";
header.appendChild(headerNightly);
const targetHeaders = targets.forEach((target) => {
if (
document.getElementById("target-filter-failed").checked &&
!targetsWithErrors.has(target)
) {
return;
}
const elem = document.createElement("th");
elem.innerText = target;
header.appendChild(elem);
});
const rows = nightlies.map((nightly) => {
const tr = document.createElement("tr");
const nightlyCol = document.createElement("td");
nightlyCol.innerText = nightly;
tr.appendChild(nightlyCol);
const info = nightlyInfos.get(nightly) ?? new Map();
for (const target of targets) {
if (
document.getElementById("target-filter-failed").checked &&
!targetsWithErrors.has(target)
) {
continue;
}
const td = document.createElement("td");
const targetInfo = info.get(target);
if (targetInfo) {
const a = document.createElement("a");
a.classList.add("build-info-a");
a.href = `/build?nightly=${encodeURIComponent(
nightly
)}&target=${encodeURIComponent(target)}`;
a.innerText = targetInfo.status;
td.appendChild(a);
td.classList.add(targetInfo.status);
} else {
td.innerText = "";
td.classList.add("missing");
}
tr.appendChild(td);
}
return tr;
});
table.replaceChildren(header, ...rows);
}
//function onTriggerBuild(e) {
// e.preventDefault();
//
// const date = document.getElementById("nightly-date-field").value;
// if (!date) {
// return;
// }
//
// fetch("/trigger-build", {
// method: "POST",
// body: JSON.stringify({
// nightly: date,
// }),
// headers: {
// "Content-Type": "application/json",
// },
// }).then(() =>
// alert(`triggered build for ${date}, this may take a few minutes`)
// );
//}
function onFilterChange(e) {
filter = e.target.value;
localStorage.setItem("filter", filter);
console.log(filter);
renderTable();
}
//document
// .getElementById("nightly-form")
// .addEventListener("submit", onTriggerBuild);
document
.getElementById("target-filter")
.addEventListener("input", onFilterChange);
document
.getElementById("target-filter-failed")
.addEventListener("input", renderTable);
// Initial fetch
fetchTargets();
</script> </script>
</body> </body>
</html> </html>

158
static/index.js Normal file
View file

@ -0,0 +1,158 @@
class Table {
constructor(data, tableElemId, filterElemId, filterFailedElemId) {
this.data = data;
this.elem = document.getElementById(tableElemId);
document.getElementById(filterElemId).addEventListener("input", (e) => {
this.filter.search = e.target.value;
this.render();
});
document
.getElementById(filterFailedElemId)
.addEventListener("input", (e) => {
this.filter.filterFailed = e.target.checked;
this.render();
});
this.filter = {
search: "",
filterFailed: false,
};
}
update(data) {
this.data = data;
}
render() {
const allTargets = new Set();
const allNightlies = new Set();
const nightlyInfos = new Map();
// Targets that have, at some point, errored
const targetsWithErrors = new Set();
// Whether a nightly is completely broken.
// These are still filtered out when filter failed is selected.
const isNightlyBroken = new Map();
// The first pass over the data, to find nightlies that are broken.
for (const info of this.data) {
if (!isNightlyBroken.has(info.nightly)) {
// Assume that a nightly is broken until proven otherwise.
isNightlyBroken.set(info.nightly, true);
}
if (info.status == "pass") {
// This nightly has built something, so it's clearly not broken :).
isNightlyBroken.set(info.nightly, false);
}
}
// Second pass over the data, group by nightly and prepare data for filter.
for (const info of this.data) {
allNightlies.add(info.nightly);
if (!info.target.includes(this.filter.search)) {
continue;
}
if (info.status === "error" && !isNightlyBroken.get(info.nightly)) {
targetsWithErrors.add(info.target);
}
allTargets.add(info.target);
if (!nightlyInfos.has(info.nightly)) {
nightlyInfos.set(info.nightly, new Map());
}
nightlyInfos.get(info.nightly).set(info.target, info);
}
const nightlies = Array.from(allNightlies);
nightlies.sort();
nightlies.reverse();
const targets = Array.from(allTargets);
targets.sort();
const header = document.createElement("tr");
const headerNightly = document.createElement("th");
headerNightly.innerText = "nightly";
header.appendChild(headerNightly);
targets.forEach((target) => {
if (this.filter.filterFailed && !targetsWithErrors.has(target)) {
return;
}
const elem = document.createElement("th");
elem.innerText = target;
header.appendChild(elem);
});
const rows = nightlies.map((nightly) => {
const tr = document.createElement("tr");
const nightlyCol = document.createElement("td");
nightlyCol.innerText = nightly;
tr.appendChild(nightlyCol);
const info = nightlyInfos.get(nightly) ?? new Map();
for (const target of targets) {
if (this.filter.filterFailed && !targetsWithErrors.has(target)) {
continue;
}
const td = document.createElement("td");
const targetInfo = info.get(target);
if (targetInfo) {
const a = document.createElement("a");
a.classList.add("build-info-a");
a.href = `build?nightly=${encodeURIComponent(
nightly
)}&target=${encodeURIComponent(target)}&mode=${encodeURIComponent(
targetInfo.mode
)}`;
a.innerText = targetInfo.status;
td.appendChild(a);
td.classList.add(targetInfo.status);
} else {
td.innerText = "";
td.classList.add("missing");
}
tr.appendChild(td);
}
return tr;
});
this.elem.replaceChildren(header, ...rows);
}
}
const coreTable = new Table(
[],
"target-state",
"target-filter",
"target-filter-failed"
);
const miriTable = new Table(
[],
"target-state-miri",
"target-filter-miri",
"target-filter-failed-miri"
);
function fetchTargets() {
fetch("target-state")
.then((body) => body.json())
.then((body) => {
const core = body.filter((info) => info.mode === "core");
const miri = body.filter((info) => info.mode === "miri-std");
coreTable.update(core);
miriTable.update(miri);
coreTable.render();
miriTable.render();
});
}
// Initial fetch
fetchTargets();