mirror of
https://github.com/Noratrieb/does-it-build.git
synced 2026-01-14 18:35:01 +01:00
Compare commits
5 commits
b244cf05b4
...
45f784eb5c
| Author | SHA1 | Date | |
|---|---|---|---|
| 45f784eb5c | |||
| 310a24aa44 | |||
| 1d31b9c3fe | |||
| 73098095a7 | |||
| 491eb1604f |
12 changed files with 213 additions and 102 deletions
80
Cargo.lock
generated
80
Cargo.lock
generated
|
|
@ -144,7 +144,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
|
|
@ -451,13 +450,13 @@ dependencies = [
|
|||
"axum",
|
||||
"color-eyre",
|
||||
"futures",
|
||||
"jiff",
|
||||
"jsonwebtoken",
|
||||
"octocrab",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -713,6 +712,25 @@ version = "0.32.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
|
|
@ -834,6 +852,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
|
|
@ -1066,6 +1085,47 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb-platform"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||
dependencies = [
|
||||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
|
|
@ -1475,6 +1535,21 @@ version = "0.3.32"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
|
|
@ -1672,6 +1747,7 @@ dependencies = [
|
|||
"base64",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
|
|
|
|||
24
Cargo.toml
24
Cargo.toml
|
|
@ -5,25 +5,29 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
askama = "0.14.0"
|
||||
axum = { version = "0.8.6", features = ["macros"] }
|
||||
axum = { version = "0.8.6", default-features = false, features = [
|
||||
"http1",
|
||||
"matched-path",
|
||||
"query",
|
||||
"tokio",
|
||||
"tower-log",
|
||||
"tracing",
|
||||
"macros",
|
||||
] }
|
||||
color-eyre = "0.6.3"
|
||||
futures = "0.3.30"
|
||||
jsonwebtoken = { version = "9.3.1", features = [] }
|
||||
jiff = "0.2.16"
|
||||
jsonwebtoken = { version = "9.3.1" }
|
||||
octocrab = "0.47.1"
|
||||
reqwest = { version = "0.12.7", features = [
|
||||
"rustls-tls",
|
||||
"http2",
|
||||
], default-features = false }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
sqlx = { version = "0.8.2", features = [
|
||||
"macros",
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
] }
|
||||
sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
|
||||
tempfile = "3.12.0"
|
||||
time = { version = "0.3.36", features = ["formatting", "macros", "parsing"] }
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
tracing = "0.1.40"
|
||||
tracing = { version = "0.1.40", features = ["attributes"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
|||
4
migrations/20251111165945_notifications_last_update.sql
Normal file
4
migrations/20251111165945_notifications_last_update.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- Add migration script here
|
||||
|
||||
ALTER TABLE notification_issues
|
||||
ADD COLUMN "last_update_date" INTEGER NULL;
|
||||
|
|
@ -83,7 +83,7 @@ async fn background_builder_inner(db: &Db, github_client: &GitHubClient) -> Resu
|
|||
match next {
|
||||
Some((nightly, mode)) => {
|
||||
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
|
||||
.wrap_err_with(|| format!("building targets for toolchain {nightly}"));
|
||||
if let Err(err) = result {
|
||||
|
|
|
|||
20
src/db.rs
20
src/db.rs
|
|
@ -106,6 +106,7 @@ pub struct NotificationIssue {
|
|||
pub status: NotificationStatus,
|
||||
pub first_failed_nightly: String,
|
||||
pub target: String,
|
||||
pub last_update_date: Option<i64>,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
|
|
@ -314,13 +315,14 @@ impl Db {
|
|||
pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO notification_issues\
|
||||
(issue_number, status, first_failed_nightly, target)\
|
||||
VALUES (?, ?, ?, ?)",
|
||||
(issue_number, status, first_failed_nightly, target, last_update_date)\
|
||||
VALUES (?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind(notification.issue_number)
|
||||
.bind(notification.status)
|
||||
.bind(notification.first_failed_nightly)
|
||||
.bind(notification.target)
|
||||
.bind(notification.last_update_date)
|
||||
.execute(&self.conn)
|
||||
.await
|
||||
.wrap_err("inserting new notification")?;
|
||||
|
|
@ -336,4 +338,18 @@ impl Db {
|
|||
.wrap_err("marking notification as closed")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_notification_last_update(
|
||||
&self,
|
||||
issue_number: i64,
|
||||
last_update: jiff::Timestamp,
|
||||
) -> Result<()> {
|
||||
sqlx::query("UPDATE notification_issues SET last_update_date = ? WHERE issue_number = ?")
|
||||
.bind(last_update.as_millisecond())
|
||||
.bind(issue_number)
|
||||
.execute(&self.conn)
|
||||
.await
|
||||
.wrap_err("marking notification as closed")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
use color_eyre::{eyre::Context, Result};
|
||||
use octocrab::issues;
|
||||
|
||||
pub struct GitHubClient {
|
||||
pub send_pings: bool,
|
||||
owner: String,
|
||||
repo: String,
|
||||
pub client: octocrab::Octocrab,
|
||||
}
|
||||
|
||||
impl GitHubClient {
|
||||
pub async fn new(
|
||||
send_pings: bool,
|
||||
client: octocrab::Octocrab,
|
||||
owner: String,
|
||||
repo: String,
|
||||
) -> Result<Self> {
|
||||
let installation = client
|
||||
.apps()
|
||||
.get_repository_installation(&owner, &repo)
|
||||
.await
|
||||
.wrap_err_with(|| format!("getting installation for {owner}/{repo}"))?;
|
||||
|
||||
let client = client
|
||||
.installation(installation.id)
|
||||
.wrap_err("getting client for installation")?;
|
||||
|
||||
Ok(Self {
|
||||
send_pings,
|
||||
owner,
|
||||
repo,
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn issues(&self) -> issues::IssueHandler<'_> {
|
||||
self.client.issues(&self.owner, &self.repo)
|
||||
}
|
||||
}
|
||||
|
|
@ -84,8 +84,6 @@ pub async fn notify_build_failure(
|
|||
return Ok(());
|
||||
};
|
||||
|
||||
info!("Creating issue for target {target}, notifying {notify_usernames:?}");
|
||||
|
||||
let issue = db.find_existing_notification(target).await?;
|
||||
|
||||
let url = format!(
|
||||
|
|
@ -93,14 +91,25 @@ pub async fn notify_build_failure(
|
|||
);
|
||||
|
||||
if let Some(issue) = issue {
|
||||
// An existing issue, send a comment.
|
||||
// An existing issue, send a comment if it's been a month since the last update.
|
||||
|
||||
github_client
|
||||
.issues()
|
||||
.create_comment(
|
||||
issue.issue_number as u64,
|
||||
format!(
|
||||
"💥 The target {target} still fails to build on the nightly {nightly}!
|
||||
if issue.last_update_date.is_none_or(|last_update_date| {
|
||||
jiff::Timestamp::from_millisecond(last_update_date).is_ok_and(|last_update_date| {
|
||||
jiff::Timestamp::now()
|
||||
.since(last_update_date)
|
||||
.is_ok_and(|diff| diff.get_months() > 0)
|
||||
})
|
||||
}) {
|
||||
info!(
|
||||
"Sending update for {target}, since enough time has elapsed since the last update"
|
||||
);
|
||||
|
||||
github_client
|
||||
.issues()
|
||||
.create_comment(
|
||||
issue.issue_number as u64,
|
||||
format!(
|
||||
"💥 The target {target} still fails to build on the nightly {nightly}!
|
||||
|
||||
<{url}>
|
||||
|
||||
|
|
@ -111,14 +120,26 @@ pub async fn notify_build_failure(
|
|||
```
|
||||
|
||||
</details>
|
||||
|
||||
This update is sent after a month of inactivity.
|
||||
"
|
||||
),
|
||||
)
|
||||
.await
|
||||
.wrap_err("creating update comment")?;
|
||||
),
|
||||
)
|
||||
.await
|
||||
.wrap_err("creating update comment")?;
|
||||
|
||||
db.set_notification_last_update(issue.issue_number, jiff::Timestamp::now())
|
||||
.await
|
||||
.wrap_err("updating last_update_date in DB")?;
|
||||
} else {
|
||||
info!("Not sending update for {target}, since not enough time has elapsed since the last one");
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Creating issue for target {target}, notifying {notify_usernames:?}");
|
||||
|
||||
// Ensure the labels exist.
|
||||
let label = github_client.issues().get_label(target).await;
|
||||
match label {
|
||||
|
|
@ -176,6 +197,7 @@ This issue will be closed automatically when this target works again!"
|
|||
issue_number: issue.number as i64,
|
||||
status: NotificationStatus::Open,
|
||||
target: target.into(),
|
||||
last_update_date: Some(jiff::Timestamp::now().as_millisecond()),
|
||||
})
|
||||
.await
|
||||
.wrap_err("inserting issue into DB")?;
|
||||
|
|
@ -225,7 +247,7 @@ pub async fn notify_build_pass(
|
|||
.await
|
||||
.wrap_err("closing issue")?;
|
||||
|
||||
db.finish_notification(issue.issue_number as i64).await?;
|
||||
db.finish_notification(issue.issue_number).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
14
src/web.rs
14
src/web.rs
|
|
@ -101,14 +101,10 @@ async fn web_build(State(state): State<AppState>, Query(query): Query<BuildQuery
|
|||
rustflags: build.rustflags,
|
||||
version: crate::VERSION,
|
||||
status: build.status,
|
||||
build_date: build.build_date.map(|build_date| {
|
||||
time::OffsetDateTime::from_unix_timestamp_nanos(build_date as i128 * 1000000)
|
||||
.map(|build_date| {
|
||||
build_date
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap()
|
||||
build_date: build.build_date.and_then(|build_date| {
|
||||
jiff::Timestamp::from_millisecond(build_date)
|
||||
.ok()
|
||||
.map(|build_date| build_date.to_string())
|
||||
}),
|
||||
build_duration_s: build
|
||||
.build_duration_ms
|
||||
|
|
@ -290,6 +286,7 @@ async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
|
|||
nightlies: Vec<String>,
|
||||
version: &'static str,
|
||||
build_count: BuildStats,
|
||||
notification_pr_url: String,
|
||||
}
|
||||
|
||||
let targets = state.db.target_list().await?;
|
||||
|
|
@ -301,6 +298,7 @@ async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
|
|||
nightlies,
|
||||
version: crate::VERSION,
|
||||
build_count,
|
||||
notification_pr_url: notification::notification_pr_url(),
|
||||
};
|
||||
|
||||
Ok(Html(page.render().unwrap()).into_response())
|
||||
|
|
|
|||
|
|
@ -1,29 +1,54 @@
|
|||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid;
|
||||
margin: 0;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
tr:not(:last-child) {
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: lightcoral;
|
||||
background-color: light-dark(lightcoral, darkred);
|
||||
}
|
||||
|
||||
.pass {
|
||||
background-color: greenyellow;
|
||||
background-color: light-dark(greenyellow, darkgreen);
|
||||
}
|
||||
|
||||
.missing {
|
||||
background-color: lightgray;
|
||||
background-color: light-dark(lightgray, rgb(85, 85, 85));
|
||||
}
|
||||
|
||||
.target-header {
|
||||
writing-mode: sideways-lr;
|
||||
}
|
||||
|
||||
.build-indicator-big {
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.build-cell {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
|
@ -36,13 +61,20 @@ td {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.target-name-col {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #1b191c;
|
||||
color: #e6dae9;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #e5a5c2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
@ -6,18 +6,15 @@
|
|||
<title>Build {{nightly}} {{target}}</title>
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
<script type="module" defer src="index.js"></script>
|
||||
<style>
|
||||
.build-indicator-big {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Build results for nightly-{{nightly}} target {{target}} {{mode}}</h1>
|
||||
<h1>
|
||||
Build results for
|
||||
<a href="/nightly?nightly={{nightly}}">nightly-{{nightly}}</a>, target
|
||||
<a href="/target?target={{target}}">{{target}}</a> ({{mode}})
|
||||
</h1>
|
||||
<a href="/">Home</a>
|
||||
<div style="margin-top: 20px" class="{{status}} build-indicator-big">
|
||||
{{status}}
|
||||
</div>
|
||||
<div class="{{status}} build-indicator-big">{{status}}</div>
|
||||
{% if let Some(rustflags) = rustflags %}
|
||||
<p>
|
||||
Using rustflags: <b><code>{{rustflags}}</code></b>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
@ -25,6 +25,12 @@
|
|||
history.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
🔔 does-it-build supports sending notifications to target maintainers via
|
||||
GitHub issues. You can add yourself with
|
||||
<a href="{{notification_pr_url}}">a PR</a>. 🔔
|
||||
</p>
|
||||
|
||||
<h1>Nightlies</h1>
|
||||
<ul>
|
||||
{% for nightly in nightlies.iter().take(5) %}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{target}} build history</title>
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
<style>
|
||||
.build-indicator-big {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Target build history for {{target}}</h1>
|
||||
|
|
@ -33,7 +28,7 @@
|
|||
</p>
|
||||
<p>
|
||||
🔔 does-it-build supports sending notifications to target maintainers via GitHub issues.
|
||||
You can add yourself with <a href="{{notification_pr_url}}">a PR</a>.
|
||||
You can add yourself with <a href="{{notification_pr_url}}">a PR</a>. 🔔
|
||||
</p>
|
||||
{% if let Some(maintainers) = maintainers %}
|
||||
<p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue