mirror of
https://github.com/Noratrieb/does-it-build.git
synced 2026-01-14 18:35:01 +01:00
migrate to rootcause
This commit is contained in:
parent
25179d488d
commit
f539a3c45c
8 changed files with 184 additions and 101 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
|
@ -454,6 +454,7 @@ dependencies = [
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"octocrab",
|
"octocrab",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rootcause",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -545,7 +546,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"spin",
|
"spin 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1157,7 +1158,7 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"spin",
|
"spin 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1791,6 +1792,30 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rootcause"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12e6b4d9966d17c1e58cd682656e9e1551f19f421b07d7618723009445f06099"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.16.0",
|
||||||
|
"indexmap",
|
||||||
|
"rootcause-internals",
|
||||||
|
"rustc-hash",
|
||||||
|
"spin 0.10.0",
|
||||||
|
"triomphe",
|
||||||
|
"unsize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rootcause-internals"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac64da7400123d25e3603dfd91a0ce7a78a3ad6e2d9400e6cdbb1aef0574c0d7"
|
||||||
|
dependencies = [
|
||||||
|
"triomphe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
|
@ -2132,6 +2157,12 @@ dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
|
@ -2664,6 +2695,15 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "triomphe"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
|
||||||
|
dependencies = [
|
||||||
|
"unsize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
|
@ -2703,6 +2743,15 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsize"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ axum = { version = "0.8.6", default-features = false, features = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
color-eyre = "0.6.3"
|
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
jiff = "0.2.16"
|
jiff = "0.2.16"
|
||||||
jsonwebtoken = { version = "9.3.1" }
|
jsonwebtoken = { version = "9.3.1" }
|
||||||
|
|
@ -23,6 +22,7 @@ reqwest = { version = "0.12.7", features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"http2",
|
"http2",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
|
rootcause = "0.10.0"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
|
sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
|
||||||
tempfile = "3.12.0"
|
tempfile = "3.12.0"
|
||||||
|
|
|
||||||
68
src/build.rs
68
src/build.rs
|
|
@ -6,11 +6,8 @@ use std::{
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use color_eyre::{
|
|
||||||
eyre::{bail, Context},
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use rootcause::{prelude::ResultExt, report};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
|
@ -18,6 +15,7 @@ use crate::{
|
||||||
db::{BuildMode, Db, FullBuildInfo, Status},
|
db::{BuildMode, Db, FullBuildInfo, Status},
|
||||||
nightlies::Nightlies,
|
nightlies::Nightlies,
|
||||||
notification::GitHubClient,
|
notification::GitHubClient,
|
||||||
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CustomBuildFlags {
|
struct CustomBuildFlags {
|
||||||
|
|
@ -73,11 +71,11 @@ pub async fn background_builder(db: Db, github_client: GitHubClient) -> Result<(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn background_builder_inner(db: &Db, github_client: &GitHubClient) -> Result<()> {
|
async fn background_builder_inner(db: &Db, github_client: &GitHubClient) -> Result<()> {
|
||||||
let nightlies = Nightlies::fetch().await.wrap_err("fetching nightlies")?;
|
let nightlies = Nightlies::fetch().await.context("fetching nightlies")?;
|
||||||
let already_finished = db
|
let already_finished = db
|
||||||
.finished_nightlies()
|
.finished_nightlies()
|
||||||
.await
|
.await
|
||||||
.wrap_err("fetching finished nightlies")?;
|
.context("fetching finished nightlies")?;
|
||||||
|
|
||||||
let next = nightlies.select_latest_to_build(&already_finished);
|
let next = nightlies.select_latest_to_build(&already_finished);
|
||||||
match next {
|
match next {
|
||||||
|
|
@ -85,12 +83,12 @@ async fn background_builder_inner(db: &Db, github_client: &GitHubClient) -> Resu
|
||||||
info!(%nightly, %mode, "Building next nightly");
|
info!(%nightly, %mode, "Building next nightly");
|
||||||
let result = build_every_target_for_toolchain(db, &nightly, mode, github_client)
|
let result = build_every_target_for_toolchain(db, &nightly, mode, github_client)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("building targets for toolchain {nightly}"));
|
.context_with(|| format!("building targets for toolchain {nightly}"));
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
error!(%nightly, %mode, ?err, "Failed to build nightly");
|
error!(%nightly, %mode, ?err, "Failed to build nightly");
|
||||||
db.finish_nightly_as_broken(&nightly, mode, &format!("{err:?}"))
|
db.finish_nightly_as_broken(&nightly, mode, &format!("{err:?}"))
|
||||||
.await
|
.await
|
||||||
.wrap_err("marking nightly as broken")?;
|
.context("marking nightly as broken")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -108,16 +106,16 @@ async fn targets_for_toolchain(toolchain: &Toolchain) -> Result<Vec<String>> {
|
||||||
.arg("target-list")
|
.arg("target-list")
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to spawn rustc")?;
|
.context("failed to spawn rustc")?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
bail!(
|
return Err(report!(
|
||||||
"failed to get target-list from rustc: {:?}",
|
"failed to get target-list from rustc: {:?}",
|
||||||
String::from_utf8(output.stderr)
|
String::from_utf8(output.stderr)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8(output.stdout)
|
Ok(String::from_utf8(output.stdout)
|
||||||
.wrap_err("rustc target-list is invalid UTF-8")?
|
.context("rustc target-list is invalid UTF-8")?
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.collect())
|
.collect())
|
||||||
|
|
@ -135,9 +133,12 @@ async fn install_toolchain(toolchain: &Toolchain, mode: BuildMode) -> Result<()>
|
||||||
.arg("minimal")
|
.arg("minimal")
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to spawn rustup")?;
|
.context("failed to spawn rustup")?;
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
bail!("rustup failed: {:?}", String::from_utf8(result.stderr));
|
return Err(report!(
|
||||||
|
"rustup failed: {:?}",
|
||||||
|
String::from_utf8(result.stderr)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let result = Command::new("rustup")
|
let result = Command::new("rustup")
|
||||||
.arg("component")
|
.arg("component")
|
||||||
|
|
@ -147,9 +148,12 @@ async fn install_toolchain(toolchain: &Toolchain, mode: BuildMode) -> Result<()>
|
||||||
.arg(&toolchain.0)
|
.arg(&toolchain.0)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to spawn rustup")?;
|
.context("failed to spawn rustup")?;
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
bail!("rustup failed: {:?}", String::from_utf8(result.stderr));
|
return Err(report!(
|
||||||
|
"rustup failed: {:?}",
|
||||||
|
String::from_utf8(result.stderr)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -165,12 +169,12 @@ async fn uninstall_toolchain(toolchain: &Toolchain) -> Result<()> {
|
||||||
.arg(&toolchain.0)
|
.arg(&toolchain.0)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to spawn rustup")?;
|
.context("failed to spawn rustup")?;
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
bail!(
|
return Err(report!(
|
||||||
"rustup toolchain remove failed: {:?}",
|
"rustup toolchain remove failed: {:?}",
|
||||||
String::from_utf8(result.stderr)
|
String::from_utf8(result.stderr)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +195,7 @@ pub async fn build_every_target_for_toolchain(
|
||||||
|
|
||||||
let targets = targets_for_toolchain(&toolchain)
|
let targets = targets_for_toolchain(&toolchain)
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to get targets")?;
|
.context("failed to get targets")?;
|
||||||
|
|
||||||
let results = futures::stream::iter(
|
let results = futures::stream::iter(
|
||||||
targets
|
targets
|
||||||
|
|
@ -208,7 +212,7 @@ pub async fn build_every_target_for_toolchain(
|
||||||
for target in targets {
|
for target in targets {
|
||||||
build_single_target(db, nightly, &target, mode, github_client)
|
build_single_target(db, nightly, &target, mode, github_client)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("building target {target} for toolchain {toolchain}"))?;
|
.context_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.
|
||||||
|
|
@ -230,7 +234,7 @@ async fn build_single_target(
|
||||||
let existing = db
|
let existing = db
|
||||||
.build_status_full(nightly, target, mode)
|
.build_status_full(nightly, target, mode)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting existing build")?;
|
.context("getting existing build")?;
|
||||||
if existing.is_some() {
|
if existing.is_some() {
|
||||||
debug!("Build already exists");
|
debug!("Build already exists");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -238,13 +242,13 @@ async fn build_single_target(
|
||||||
|
|
||||||
info!("Building target");
|
info!("Building target");
|
||||||
|
|
||||||
let tmpdir = tempfile::tempdir().wrap_err("creating temporary directory")?;
|
let tmpdir = tempfile::tempdir().context("creating temporary directory")?;
|
||||||
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let result = build_target(tmpdir.path(), &Toolchain::from_nightly(nightly), target)
|
let result = build_target(tmpdir.path(), &Toolchain::from_nightly(nightly), target)
|
||||||
.await
|
.await
|
||||||
.wrap_err("running build")?;
|
.context("running build")?;
|
||||||
|
|
||||||
let full_build_info = FullBuildInfo {
|
let full_build_info = FullBuildInfo {
|
||||||
nightly: nightly.into(),
|
nightly: nightly.into(),
|
||||||
|
|
@ -298,14 +302,14 @@ async fn build_target(tmpdir: &Path, toolchain: &Toolchain, target: &str) -> Res
|
||||||
.current_dir(tmpdir)
|
.current_dir(tmpdir)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("spawning cargo init")?;
|
.context("spawning cargo init")?;
|
||||||
if !init.status.success() {
|
if !init.status.success() {
|
||||||
bail!("init failed: {}", String::from_utf8(init.stderr)?);
|
return Err(report!("init failed: {}", String::from_utf8(init.stderr)?));
|
||||||
}
|
}
|
||||||
|
|
||||||
let librs = tmpdir.join("src").join("lib.rs");
|
let librs = tmpdir.join("src").join("lib.rs");
|
||||||
std::fs::write(&librs, "#![no_std]\n")
|
std::fs::write(&librs, "#![no_std]\n")
|
||||||
.wrap_err_with(|| format!("writing to {}", librs.display()))?;
|
.context_with(|| format!("writing to {}", librs.display()))?;
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
toolchain: &Toolchain,
|
toolchain: &Toolchain,
|
||||||
|
|
@ -330,14 +334,16 @@ async fn build_target(tmpdir: &Path, toolchain: &Toolchain, target: &str) -> Res
|
||||||
*rustflags = Some(flags);
|
*rustflags = Some(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.current_dir(tmpdir)
|
let output = cmd
|
||||||
|
.current_dir(tmpdir)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.wrap_err("spawning cargo build")
|
.context("spawning cargo build")?;
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = run(toolchain, target, &mut rustflags, tmpdir, "-Zbuild-std").await?;
|
let mut output = run(toolchain, target, &mut rustflags, tmpdir, "-Zbuild-std").await?;
|
||||||
let mut stderr = String::from_utf8(output.stderr).wrap_err("cargo stderr utf8")?;
|
let mut stderr = String::from_utf8(output.stderr).context("cargo stderr utf8")?;
|
||||||
|
|
||||||
let status = if output.status.success() {
|
let status = if output.status.success() {
|
||||||
Status::Pass
|
Status::Pass
|
||||||
|
|
@ -352,7 +358,7 @@ async fn build_target(tmpdir: &Path, toolchain: &Toolchain, target: &str) -> Res
|
||||||
"-Zbuild-std=core",
|
"-Zbuild-std=core",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
stderr = String::from_utf8(output.stderr).wrap_err("cargo stderr utf8")?;
|
stderr = String::from_utf8(output.stderr).context("cargo stderr utf8")?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Status::Pass
|
Status::Pass
|
||||||
|
|
|
||||||
78
src/db.rs
78
src/db.rs
|
|
@ -1,12 +1,11 @@
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use color_eyre::{
|
use rootcause::{prelude::ResultExt, report};
|
||||||
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};
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Db {
|
pub struct Db {
|
||||||
pub conn: Pool<Sqlite>,
|
pub conn: Pool<Sqlite>,
|
||||||
|
|
@ -112,12 +111,13 @@ pub struct NotificationIssue {
|
||||||
impl Db {
|
impl Db {
|
||||||
pub async fn open(path: &str) -> Result<Self> {
|
pub async fn open(path: &str) -> Result<Self> {
|
||||||
let db_opts = SqliteConnectOptions::from_str(path)
|
let db_opts = SqliteConnectOptions::from_str(path)
|
||||||
.wrap_err("parsing database URL")?
|
.context("parsing database URL")
|
||||||
|
.attach(format!("url: {path}"))?
|
||||||
.create_if_missing(true);
|
.create_if_missing(true);
|
||||||
|
|
||||||
let conn = Pool::connect_with(db_opts)
|
let conn = Pool::connect_with(db_opts)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("opening db from `{path}`"))?;
|
.context_with(|| format!("opening db from `{path}`"))?;
|
||||||
Ok(Self { conn })
|
Ok(Self { conn })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,38 +137,44 @@ impl Db {
|
||||||
.bind(info.build_duration_ms)
|
.bind(info.build_duration_ms)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("inserting build info into database")?;
|
.context("inserting build info into database")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn history_for_target(&self, target: &str) -> Result<Vec<BuildInfo>> {
|
pub async fn history_for_target(&self, target: &str) -> Result<Vec<BuildInfo>> {
|
||||||
sqlx::query_as::<_, BuildInfo>(
|
let history = sqlx::query_as::<_, BuildInfo>(
|
||||||
"SELECT nightly, target, status, mode FROM build_info WHERE target = ?",
|
"SELECT nightly, target, status, mode FROM build_info WHERE target = ?",
|
||||||
)
|
)
|
||||||
.bind(target)
|
.bind(target)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting history for single target")
|
.context("getting history for single target")
|
||||||
|
.attach(format!("target: {target}"))?;
|
||||||
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn history_for_nightly(&self, nightly: &str) -> Result<Vec<BuildInfo>> {
|
pub async fn history_for_nightly(&self, nightly: &str) -> Result<Vec<BuildInfo>> {
|
||||||
sqlx::query_as::<_, BuildInfo>(
|
let history = sqlx::query_as::<_, BuildInfo>(
|
||||||
"SELECT nightly, target, status, mode FROM build_info WHERE nightly = ?",
|
"SELECT nightly, target, status, mode FROM build_info WHERE nightly = ?",
|
||||||
)
|
)
|
||||||
.bind(nightly)
|
.bind(nightly)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting history for single nightly")
|
.context("getting history for single nightly")
|
||||||
|
.attach(format!("nightly: {nightly}"))?;
|
||||||
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn nightly_info(&self, nightly: &str) -> Result<Vec<FinishedNightlyWithBroken>> {
|
pub async fn nightly_info(&self, nightly: &str) -> Result<Vec<FinishedNightlyWithBroken>> {
|
||||||
sqlx::query_as::<_, FinishedNightlyWithBroken>(
|
let info = sqlx::query_as::<_, FinishedNightlyWithBroken>(
|
||||||
"SELECT nightly, mode, is_broken, broken_error FROM finished_nightly WHERE nightly = ?",
|
"SELECT nightly, mode, is_broken, broken_error FROM finished_nightly WHERE nightly = ?",
|
||||||
)
|
)
|
||||||
.bind(nightly)
|
.bind(nightly)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting finished_nightly for single nightly")
|
.context("getting finished_nightly for single nightly")
|
||||||
|
.attach(format!("nightly: {nightly}"))?;
|
||||||
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn target_list(&self) -> Result<Vec<String>> {
|
pub async fn target_list(&self) -> Result<Vec<String>> {
|
||||||
|
|
@ -177,11 +183,14 @@ impl Db {
|
||||||
target: String,
|
target: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlx::query_as::<_, TargetName>("SELECT DISTINCT target FROM build_info ORDER BY target")
|
let list = sqlx::query_as::<_, TargetName>(
|
||||||
.fetch_all(&self.conn)
|
"SELECT DISTINCT target FROM build_info ORDER BY target",
|
||||||
.await
|
)
|
||||||
.wrap_err("getting list of all targets")
|
.fetch_all(&self.conn)
|
||||||
.map(|elems| elems.into_iter().map(|elem| elem.target).collect())
|
.await
|
||||||
|
.context("getting list of all targets")?;
|
||||||
|
|
||||||
|
Ok(list.into_iter().map(|elem| elem.target).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn nightly_list(&self) -> Result<Vec<String>> {
|
pub async fn nightly_list(&self) -> Result<Vec<String>> {
|
||||||
|
|
@ -190,13 +199,14 @@ impl Db {
|
||||||
nightly: String,
|
nightly: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlx::query_as::<_, NightlyName>(
|
let list = sqlx::query_as::<_, NightlyName>(
|
||||||
"SELECT DISTINCT nightly FROM build_info ORDER BY nightly DESC",
|
"SELECT DISTINCT nightly FROM build_info ORDER BY nightly DESC",
|
||||||
)
|
)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting list of all targets")
|
.context("getting list of all nightlies")?;
|
||||||
.map(|elems| elems.into_iter().map(|elem| elem.nightly).collect())
|
|
||||||
|
Ok(list.into_iter().map(|elem| elem.nightly).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_count(&self) -> Result<BuildStats> {
|
pub async fn build_count(&self) -> Result<BuildStats> {
|
||||||
|
|
@ -211,7 +221,7 @@ impl Db {
|
||||||
)
|
)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting list of all targets")?;
|
.context("getting total count of builds")?;
|
||||||
|
|
||||||
let count = |status| {
|
let count = |status| {
|
||||||
results
|
results
|
||||||
|
|
@ -242,7 +252,8 @@ impl Db {
|
||||||
.bind(mode)
|
.bind(mode)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("getting build status from DB")?;
|
.context("getting build status from DB")
|
||||||
|
.attach(format!("nightly: {nightly}, target: {target}"))?;
|
||||||
Ok(result.first().cloned())
|
Ok(result.first().cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,7 +262,7 @@ impl Db {
|
||||||
sqlx::query_as::<_, FinishedNightly>("SELECT nightly, mode from finished_nightly")
|
sqlx::query_as::<_, FinishedNightly>("SELECT nightly, mode from finished_nightly")
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("fetching finished nightlies")?;
|
.context("getting finished nightlies")?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
@ -264,10 +275,10 @@ impl Db {
|
||||||
.bind(mode)
|
.bind(mode)
|
||||||
.fetch_all(&self.conn)
|
.fetch_all(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("checking whether a nightly is finished")?;
|
.context("checking whether a nightly is finished")?;
|
||||||
|
|
||||||
if result.len() > 1 {
|
if result.len() > 1 {
|
||||||
bail!("found more than one result for {nightly} {mode}");
|
return Err(report!("found more than one result for {nightly} {mode}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result.len() == 1)
|
Ok(result.len() == 1)
|
||||||
|
|
@ -279,7 +290,7 @@ impl Db {
|
||||||
.bind(mode)
|
.bind(mode)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("inserting finished nightly")?;
|
.context("inserting finished nightly")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,7 +306,7 @@ impl Db {
|
||||||
.bind(error)
|
.bind(error)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("inserting finished broken nightly")?;
|
.context("inserting finished broken nightly")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,13 +314,14 @@ impl Db {
|
||||||
&self,
|
&self,
|
||||||
target: &str,
|
target: &str,
|
||||||
) -> Result<Option<NotificationIssue>> {
|
) -> Result<Option<NotificationIssue>> {
|
||||||
sqlx::query_as::<_, NotificationIssue>(
|
let notification = sqlx::query_as::<_, NotificationIssue>(
|
||||||
"SELECT * FROM notification_issues WHERE status = 'open' AND target = ?",
|
"SELECT * FROM notification_issues WHERE status = 'open' AND target = ?",
|
||||||
)
|
)
|
||||||
.bind(target)
|
.bind(target)
|
||||||
.fetch_optional(&self.conn)
|
.fetch_optional(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("finding existing notification")
|
.context("finding existing notification")?;
|
||||||
|
Ok(notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> {
|
pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> {
|
||||||
|
|
@ -325,7 +337,7 @@ impl Db {
|
||||||
.bind(notification.last_update_date)
|
.bind(notification.last_update_date)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("inserting new notification")?;
|
.context("inserting new notification")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +347,7 @@ impl Db {
|
||||||
.bind(issue_number)
|
.bind(issue_number)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("marking notification as closed")?;
|
.context("marking notification as closed")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +361,7 @@ impl Db {
|
||||||
.bind(issue_number)
|
.bind(issue_number)
|
||||||
.execute(&self.conn)
|
.execute(&self.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("marking notification as closed")?;
|
.context("marking notification as closed")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/main.rs
18
src/main.rs
|
|
@ -4,13 +4,15 @@ mod nightlies;
|
||||||
mod notification;
|
mod notification;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
use color_eyre::{eyre::WrapErr, Result};
|
|
||||||
use db::Db;
|
use db::Db;
|
||||||
|
use rootcause::{prelude::ResultExt, Report};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
const VERSION: &str = env!("GIT_COMMIT");
|
const VERSION: &str = env!("GIT_COMMIT");
|
||||||
const VERSION_SHORT: &str = env!("GIT_COMMIT_SHORT");
|
const VERSION_SHORT: &str = env!("GIT_COMMIT_SHORT");
|
||||||
|
|
||||||
|
type Result<T, E = Report> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
main_inner().await
|
main_inner().await
|
||||||
|
|
@ -25,25 +27,25 @@ async fn main_inner() -> Result<()> {
|
||||||
db::MIGRATOR
|
db::MIGRATOR
|
||||||
.run(&db.conn)
|
.run(&db.conn)
|
||||||
.await
|
.await
|
||||||
.wrap_err("running migrations")?;
|
.context("running migrations")?;
|
||||||
|
|
||||||
let send_pings = std::env::var("GITHUB_SEND_PINGS")
|
let send_pings = std::env::var("GITHUB_SEND_PINGS")
|
||||||
.map(|_| true)
|
.map(|_| true)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let github_owner = std::env::var("GITHUB_OWNER").wrap_err("missing GITHUB_OWNER env var")?;
|
let github_owner = std::env::var("GITHUB_OWNER").context("missing GITHUB_OWNER env var")?;
|
||||||
let github_repo = std::env::var("GITHUB_REPO").wrap_err("missing GITHUB_REPO env var")?;
|
let github_repo = std::env::var("GITHUB_REPO").context("missing GITHUB_REPO env var")?;
|
||||||
let app_id = std::env::var("GITHUB_APP_ID")
|
let app_id = std::env::var("GITHUB_APP_ID")
|
||||||
.wrap_err("missing GITHUB_APP_ID env var")?
|
.context("missing GITHUB_APP_ID env var")?
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.wrap_err("invalid GITHUB_APP_ID")?;
|
.context("invalid GITHUB_APP_ID")?;
|
||||||
let key = std::env::var("GITHUB_APP_PRIVATE_KEY")
|
let key = std::env::var("GITHUB_APP_PRIVATE_KEY")
|
||||||
.wrap_err("missing GITHUB_APP_PRIVATE_KEY env var")?;
|
.context("missing GITHUB_APP_PRIVATE_KEY env var")?;
|
||||||
let key = jsonwebtoken::EncodingKey::from_rsa_pem(key.as_bytes()).unwrap();
|
let key = jsonwebtoken::EncodingKey::from_rsa_pem(key.as_bytes()).unwrap();
|
||||||
|
|
||||||
let github_client = octocrab::Octocrab::builder()
|
let github_client = octocrab::Octocrab::builder()
|
||||||
.app(app_id.into(), key)
|
.app(app_id.into(), key)
|
||||||
.build()
|
.build()
|
||||||
.wrap_err("failed to create client")?;
|
.context("failed to create client")?;
|
||||||
|
|
||||||
let github_client = notification::GitHubClient::new(
|
let github_client = notification::GitHubClient::new(
|
||||||
send_pings,
|
send_pings,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::RandomState;
|
use std::hash::RandomState;
|
||||||
|
|
||||||
use color_eyre::eyre::Context;
|
use rootcause::prelude::ResultExt;
|
||||||
use color_eyre::Result;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::db::{BuildMode, FinishedNightly};
|
use crate::db::{BuildMode, FinishedNightly};
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
const EARLIEST_CUTOFF_DATE: &str = "2023-01-01";
|
const EARLIEST_CUTOFF_DATE: &str = "2023-01-01";
|
||||||
|
|
||||||
|
|
@ -15,13 +15,21 @@ pub struct Nightlies {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nightlies {
|
impl Nightlies {
|
||||||
pub async fn fetch() -> Result<Nightlies> {
|
async fn get_body(url: &str) -> Result<String, rootcause::Report> {
|
||||||
let manifests = reqwest::get("https://static.rust-lang.org/manifests.txt")
|
Ok(reqwest::get(url)
|
||||||
.await
|
.await
|
||||||
.wrap_err("fetching https://static.rust-lang.org/manifests.txt")?
|
.context("executing GET request")?
|
||||||
.text()
|
.text()
|
||||||
.await
|
.await
|
||||||
.wrap_err("fetching body of https://static.rust-lang.org/manifests.txt")?;
|
.context("fetching body")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch() -> Result<Nightlies> {
|
||||||
|
let url = "https://static.rust-lang.org/manifests.txt";
|
||||||
|
let manifests = Nightlies::get_body(url)
|
||||||
|
.await
|
||||||
|
.context(format!("Fetching manifests.txt"))
|
||||||
|
.attach(format!("url: {url}"))?;
|
||||||
let mut all = nightlies_from_manifest(&manifests)
|
let mut all = nightlies_from_manifest(&manifests)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|date| date.as_str() > EARLIEST_CUTOFF_DATE)
|
.filter(|date| date.as_str() > EARLIEST_CUTOFF_DATE)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use color_eyre::eyre::{Context, Result};
|
|
||||||
use octocrab::models::issues::IssueStateReason;
|
use octocrab::models::issues::IssueStateReason;
|
||||||
use octocrab::models::IssueState;
|
use octocrab::models::IssueState;
|
||||||
|
use rootcause::prelude::ResultExt;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::db::{Db, FullBuildInfo, NotificationIssue, NotificationStatus, Status};
|
use crate::db::{Db, FullBuildInfo, NotificationIssue, NotificationStatus, Status};
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
pub const TABLE_FILE: &str = file!();
|
pub const TABLE_FILE: &str = file!();
|
||||||
pub const TABLE_LINE: u32 = line!() + 1;
|
pub const TABLE_LINE: u32 = line!() + 1;
|
||||||
|
|
@ -55,11 +56,11 @@ impl GitHubClient {
|
||||||
.apps()
|
.apps()
|
||||||
.get_repository_installation(&owner, &repo)
|
.get_repository_installation(&owner, &repo)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("getting installation for {owner}/{repo}"))?;
|
.context_with(|| format!("getting installation for {owner}/{repo}"))?;
|
||||||
|
|
||||||
let client = client
|
let client = client
|
||||||
.installation(installation.id)
|
.installation(installation.id)
|
||||||
.wrap_err("getting client for installation")?;
|
.context("getting client for installation")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
send_pings,
|
send_pings,
|
||||||
|
|
@ -143,11 +144,11 @@ This update is sent after a month of inactivity.
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.wrap_err("creating update comment")?;
|
.context("creating update comment")?;
|
||||||
|
|
||||||
db.set_notification_last_update(issue.issue_number, jiff::Timestamp::now())
|
db.set_notification_last_update(issue.issue_number, jiff::Timestamp::now())
|
||||||
.await
|
.await
|
||||||
.wrap_err("updating last_update_date in DB")?;
|
.context("updating last_update_date in DB")?;
|
||||||
} else {
|
} else {
|
||||||
info!("Not sending update for {target}, since not enough time has elapsed since the last one");
|
info!("Not sending update for {target}, since not enough time has elapsed since the last one");
|
||||||
}
|
}
|
||||||
|
|
@ -166,9 +167,13 @@ This update is sent after a month of inactivity.
|
||||||
.issues()
|
.issues()
|
||||||
.create_label(target, "d73a4a", format!("Target: {target}"))
|
.create_label(target, "d73a4a", format!("Target: {target}"))
|
||||||
.await
|
.await
|
||||||
.wrap_err("creating label")?;
|
.context("creating label")?;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err)
|
||||||
|
.context("failed to fetch label label")
|
||||||
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err).wrap_err("failed to fetch label label"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pings = notify_usernames
|
let pings = notify_usernames
|
||||||
|
|
@ -207,7 +212,7 @@ This issue will be closed automatically when this target works again!"
|
||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.wrap_err("failed to create issue")?;
|
.context("failed to create issue")?;
|
||||||
|
|
||||||
db.insert_notification(NotificationIssue {
|
db.insert_notification(NotificationIssue {
|
||||||
first_failed_nightly: nightly.into(),
|
first_failed_nightly: nightly.into(),
|
||||||
|
|
@ -217,7 +222,7 @@ This issue will be closed automatically when this target works again!"
|
||||||
last_update_date: Some(jiff::Timestamp::now().as_millisecond()),
|
last_update_date: Some(jiff::Timestamp::now().as_millisecond()),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.wrap_err("inserting issue into DB")?;
|
.context("inserting issue into DB")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +258,7 @@ pub async fn notify_build_pass(
|
||||||
thanks for playing this round of Tier 3 rustc target breakage fixing! See y'all next time :3!\n\n<{url}>"),
|
thanks for playing this round of Tier 3 rustc target breakage fixing! See y'all next time :3!\n\n<{url}>"),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.wrap_err("creating update comment")?;
|
.context("creating update comment")?;
|
||||||
|
|
||||||
github_client
|
github_client
|
||||||
.issues()
|
.issues()
|
||||||
|
|
@ -262,7 +267,7 @@ pub async fn notify_build_pass(
|
||||||
.state_reason(IssueStateReason::Completed)
|
.state_reason(IssueStateReason::Completed)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.wrap_err("closing issue")?;
|
.context("closing issue")?;
|
||||||
|
|
||||||
db.finish_notification(issue.issue_number).await?;
|
db.finish_notification(issue.issue_number).await?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/web.rs
19
src/web.rs
|
|
@ -7,13 +7,13 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use color_eyre::{eyre::Context, Result};
|
use rootcause::{prelude::ResultExt, Report};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{BuildInfo, BuildMode, BuildStats, Db, Status},
|
db::{BuildInfo, BuildMode, BuildStats, Db, Status},
|
||||||
notification,
|
notification, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -38,7 +38,10 @@ pub async fn webserver(db: Db, notification_repo: String) -> Result<()> {
|
||||||
info!("Serving website on port 3000 (commit {})", crate::VERSION);
|
info!("Serving website on port 3000 (commit {})", crate::VERSION);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
axum::serve(listener, app).await.wrap_err("failed to serve")
|
axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.context("failed to serve")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -325,12 +328,10 @@ async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
Ok(Html(page.render().unwrap()).into_response())
|
Ok(Html(page.render().unwrap()).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
render(state)
|
render(state).await.unwrap_or_else(|err: Report| {
|
||||||
.await
|
error!(?err, "Error loading data for root page");
|
||||||
.unwrap_or_else(|err: color_eyre::eyre::Error| {
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
error!(?err, "Error loading data for root page");
|
})
|
||||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index_css() -> impl IntoResponse {
|
async fn index_css() -> impl IntoResponse {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue