Skip to content

hydra-evaluator: Rewrite in Rust#1608

Open
Ericson2314 wants to merge 1 commit into
masterfrom
no-c-plus-plus
Open

hydra-evaluator: Rewrite in Rust#1608
Ericson2314 wants to merge 1 commit into
masterfrom
no-c-plus-plus

Conversation

@Ericson2314

Copy link
Copy Markdown
Member

Port hydra-evaluator from C++ to Rust, removing the last C++ executable from the project. The new implementation uses sqlx and tokio, matching the async patterns already established by hydra-queue-runner and hydra-builder.

Build/packaging changes:

  • Remove all C++ dependencies from subprojects/hydra/meson.build
  • Move Rust packaging from hydra-queue-runner/package.nix to subprojects/rust-package.nix with named outputs (queue_runner, builder, evaluator)
  • Add evaluatorExecutable option to the NixOS web-app module, matching how the queue runner and builder are discovered via their own modules
  • Clean up stale references in hydra-tests/meson.build and dev-shell.nix

Eveeifyeve

This comment was marked as low quality.

@Ericson2314 Ericson2314 force-pushed the no-c-plus-plus branch 6 times, most recently from 4bce9fd to 2a1c3e0 Compare May 4, 2026 21:42
Port hydra-evaluator from C++ to Rust, removing the last C++ executable
from the project. The new implementation uses `sqlx` and `tokio`, matching
the async patterns already established by hydra-queue-runner and
hydra-builder.

Build/packaging changes:
- Remove all C++ dependencies from `subprojects/hydra/meson.build`
- Add `subprojects/hydra-evaluator/package.nix` following the same
  per-binary pattern as `hydra-queue-runner` and `hydra-builder`: each
  Rust binary is its own derivation that builds against a fileset
  containing only its own crate, and `postPatch` strips the other Rust
  binary crates from the workspace `members` list so cargo doesn't try
  to load their (absent) manifests. Properly sharing the dep crates
  between these builds is left for later.
- Add `evaluatorExecutable` option to the NixOS web-app module, matching
  how the queue runner and builder are discovered via their own modules
- Clean up stale references in `hydra-tests/meson.build` and
  `dev-shell.nix`

`JobsetRow` uses `i32` for `lastCheckedTime` and `triggerTime` to match
the `INT4` Postgres schema (`sqlx` refuses to decode `INT4` into `i64`).
Post-eval DB updates are wrapped in a transaction, as in the C++
original. The DBI parser supports `sslmode`/`sslrootcert`/`sslcert`/
`sslkey` that `libpq` understood natively in the C++ version; unknown
parameters are warned about rather than fatal.

Satisfy the crate's own `#![deny(clippy::pedantic)]` gate:
- use `fs_err` per workspace `disallowed_methods` policy
- replace `as`-casts with `try_from` (schema is `INT4`, saturate at `i32::MAX`)
- collapse nested `if let` chains

Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Co-authored-by: Claude <noreply@anthropic.com>
Comment on lines +64 to +137
pub(crate) fn parse_hydra_dbi() -> anyhow::Result<PgConnectOptions> {
let dbi = std::env::var("HYDRA_DBI").unwrap_or_else(|_| "dbi:Pg:dbname=hydra;".to_owned());
parse_dbi(&dbi)
}

fn parse_dbi(dbi: &str) -> anyhow::Result<PgConnectOptions> {
let params = dbi
.strip_prefix("dbi:Pg:")
.or_else(|| dbi.strip_prefix("DBI:Pg:"))
.context("$HYDRA_DBI does not denote a PostgreSQL database")?;

let mut opts = PgConnectOptions::new();

for pair in params.split(';').filter(|s| !s.is_empty()) {
let (key, value) = pair
.split_once('=')
.with_context(|| format!("invalid DBI parameter: {pair}"))?;
match key.trim() {
"dbname" => opts = opts.database(value.trim()),
"host" => opts = opts.host(value.trim()),
"port" => {
opts = opts.port(
value
.trim()
.parse()
.with_context(|| format!("invalid port: {value}"))?,
);
}
"user" => opts = opts.username(value.trim()),
"password" => opts = opts.password(value.trim()),
"application_name" => opts = opts.application_name(value.trim()),
// The C++ evaluator used libpq which understood these
// natively; without explicit handling TLS-required
// deployments would fail.
"sslmode" => {
let mode = match value.trim() {
"disable" => PgSslMode::Disable,
"allow" => PgSslMode::Allow,
"prefer" => PgSslMode::Prefer,
"require" => PgSslMode::Require,
"verify-ca" => PgSslMode::VerifyCa,
"verify-full" => PgSslMode::VerifyFull,
v => anyhow::bail!("invalid sslmode: {v}"),
};
opts = opts.ssl_mode(mode);
}
"sslrootcert" => opts = opts.ssl_root_cert(value.trim()),
"sslcert" => opts = opts.ssl_client_cert(value.trim()),
"sslkey" => opts = opts.ssl_client_key(value.trim()),
// Warn rather than bail on unknown parameters, to avoid
// breaking on libpq keywords we haven't mapped yet.
other => {
tracing::warn!("ignoring unsupported DBI parameter: {other}");
}
}
}

Ok(opts)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_simple_dbi() {
parse_dbi("dbi:Pg:dbname=hydra;host=localhost;port=5432").unwrap();
}

#[test]
fn parse_dbi_uppercase() {
parse_dbi("DBI:Pg:dbname=testdb;").unwrap();
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in the db crate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants