Add sqlite for tracking build history.
This commit is contained in:
@@ -1,18 +1,32 @@
|
||||
use crate::Result;
|
||||
use crate::cli::parameters::BuildArgs;
|
||||
use crate::config::Config;
|
||||
use crate::config::TargetConfig;
|
||||
use crate::error::CustomError;
|
||||
use crate::database::db_handle::DbHandle;
|
||||
use crate::fs_util::assert_directory;
|
||||
use crate::fs_util::is_git_repo;
|
||||
use crate::git_util::git_force_into_state;
|
||||
use crate::git_util::git_init_at_rev;
|
||||
use crate::nix_util::nixos_build_target;
|
||||
|
||||
pub(crate) async fn run_build(args: BuildArgs) -> Result<(), CustomError> {
|
||||
pub(crate) async fn run_build(args: BuildArgs) -> Result<()> {
|
||||
println!("{:?}", args);
|
||||
let config = Config::load_from_file(args.config).await?;
|
||||
println!("{:?}", config);
|
||||
|
||||
let database_path = config.get_database_path()?;
|
||||
let database_parent = database_path
|
||||
.parent()
|
||||
.expect("Database should exist in a folder.");
|
||||
let database_path = database_path.to_string_lossy();
|
||||
assert_directory!(
|
||||
database_parent,
|
||||
"Creating database directory {}",
|
||||
database_parent.to_string_lossy()
|
||||
);
|
||||
|
||||
let db_handle = DbHandle::new(Some(database_path)).await?;
|
||||
|
||||
for target_name in args.target {
|
||||
let target_config = {
|
||||
let target_config = config.get_target_config(&target_name)?;
|
||||
@@ -30,15 +44,12 @@ pub(crate) async fn run_build(args: BuildArgs) -> Result<(), CustomError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prepare_flake_repo(
|
||||
config_root: &Config,
|
||||
target_config: &TargetConfig,
|
||||
) -> Result<(), CustomError> {
|
||||
async fn prepare_flake_repo(config_root: &Config, target_config: &TargetConfig) -> Result<()> {
|
||||
let repo_directory = target_config.get_repo_directory(config_root)?;
|
||||
assert_directory!(
|
||||
&repo_directory,
|
||||
"Creating repo directory {}",
|
||||
(&repo_directory).to_string_lossy()
|
||||
repo_directory.to_string_lossy()
|
||||
);
|
||||
|
||||
if is_git_repo(&repo_directory).await? {
|
||||
@@ -63,16 +74,13 @@ async fn prepare_flake_repo(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn build_target(
|
||||
config_root: &Config,
|
||||
target_config: &TargetConfig,
|
||||
) -> Result<(), CustomError> {
|
||||
async fn build_target(config_root: &Config, target_config: &TargetConfig) -> Result<()> {
|
||||
let flake_directory = target_config.get_flake_directory(config_root)?;
|
||||
let build_directory = target_config.get_build_directory(config_root)?;
|
||||
assert_directory!(
|
||||
&build_directory,
|
||||
"Creating build directory {}",
|
||||
(&build_directory).to_string_lossy()
|
||||
build_directory.to_string_lossy()
|
||||
);
|
||||
|
||||
nixos_build_target(build_directory, flake_directory, target_config.get_attr()?).await?;
|
||||
|
||||
@@ -73,4 +73,11 @@ impl Config {
|
||||
let work_dir = current_dir.join("work");
|
||||
Ok(Cow::Owned(work_dir))
|
||||
}
|
||||
|
||||
/// The path to the sqlite database where run history is stored.
|
||||
pub(crate) fn get_database_path(&self) -> Result<Cow<'_, Path>, CustomError> {
|
||||
let output_directory = self.get_output_directory()?;
|
||||
let database_path = output_directory.join("nix_builder.sqlite");
|
||||
Ok(Cow::Owned(database_path))
|
||||
}
|
||||
}
|
||||
|
||||
80
src/database/db_handle.rs
Normal file
80
src/database/db_handle.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use sqlx::Executor;
|
||||
use sqlx::Pool;
|
||||
use sqlx::Sqlite;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
use super::migration::run_migrations;
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) struct DbHandle {
|
||||
pub(crate) conn: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl DbHandle {
|
||||
pub(crate) async fn new<P: AsRef<str>>(db_path: Option<P>) -> Result<DbHandle> {
|
||||
let db_path = db_path.as_ref().map(|p| p.as_ref());
|
||||
let options = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.test_before_acquire(true)
|
||||
.after_connect(|conn, _meta| {
|
||||
Box::pin(async move {
|
||||
// Enforce foreign keys.
|
||||
conn.execute("PRAGMA foreign_keys = ON;").await?;
|
||||
// Allows writes at the same time as reads.
|
||||
conn.execute("PRAGMA journal_mode = WAL;").await?;
|
||||
// Do not sync to disk after *every* write.
|
||||
conn.execute("PRAGMA synchronous = NORMAL;").await?;
|
||||
// Keep 10k database pages in memory (~40MiB).
|
||||
conn.execute("PRAGMA cache_size = 10000;").await?;
|
||||
// Stores temporary tables, indexes, and sorting operations in memory.
|
||||
conn.execute("PRAGMA temp_store = MEMORY;").await?;
|
||||
// Use mmap to access database.
|
||||
conn.execute("PRAGMA mmap_size = 268435456;").await?;
|
||||
// Clear space of deleted rows at transaction end.
|
||||
conn.execute("PRAGMA auto_vacuum = FULL;").await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.after_release(|conn, _meta| {
|
||||
Box::pin(async move {
|
||||
// Attempt to optimize the database. (Currently just runs ANALYZE)
|
||||
conn.execute("PRAGMA optimize;").await?;
|
||||
// Rebuild the DB file which defragments and clears out deleted pages.
|
||||
conn.execute("VACUUM;").await?;
|
||||
Ok(true)
|
||||
})
|
||||
});
|
||||
let conn = match db_path {
|
||||
Some(path) => {
|
||||
info!("Connecting to sqlite database at {path}");
|
||||
if !Sqlite::database_exists(path).await.unwrap_or(false) {
|
||||
info!("Creating a new sqlite database at {path}");
|
||||
Sqlite::create_database(path).await.unwrap();
|
||||
} else {
|
||||
info!("Connecting to existing sqlite database at {path}");
|
||||
}
|
||||
let full_url = format!("sqlite:{path}");
|
||||
options.connect(&full_url).await?
|
||||
}
|
||||
None => {
|
||||
warn!("No sqlite_path set in config. Using an in-memory database.");
|
||||
// We force it to a single connection that never dies or else the data and schema in the in-memory DB is lost.
|
||||
options
|
||||
.min_connections(1)
|
||||
.max_connections(1)
|
||||
.idle_timeout(None)
|
||||
.max_lifetime(None)
|
||||
.connect("sqlite::memory:")
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
run_migrations(&conn).await?;
|
||||
|
||||
Ok(DbHandle { conn })
|
||||
}
|
||||
}
|
||||
15
src/database/migration.rs
Normal file
15
src/database/migration.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use sqlx::Acquire;
|
||||
use sqlx::migrate::Migrate;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) async fn run_migrations<'a, A>(db: A) -> Result<()>
|
||||
where
|
||||
A: Acquire<'a>,
|
||||
<A::Connection as Deref>::Target: Migrate,
|
||||
{
|
||||
sqlx::migrate!("./migrations").run(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
2
src/database/mod.rs
Normal file
2
src/database/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod db_handle;
|
||||
pub(crate) mod migration;
|
||||
@@ -14,6 +14,8 @@ pub(crate) enum CustomError {
|
||||
FromUtf8(#[allow(dead_code)] FromUtf8Error),
|
||||
PathStripPrefix(#[allow(dead_code)] std::path::StripPrefixError),
|
||||
UrlParseError(#[allow(dead_code)] url::ParseError),
|
||||
Migrate(#[allow(dead_code)] sqlx::migrate::MigrateError),
|
||||
Sql(#[allow(dead_code)] sqlx::Error),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for CustomError {
|
||||
@@ -81,3 +83,15 @@ impl From<url::ParseError> for CustomError {
|
||||
CustomError::UrlParseError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::migrate::MigrateError> for CustomError {
|
||||
fn from(value: sqlx::migrate::MigrateError) -> Self {
|
||||
CustomError::Migrate(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for CustomError {
|
||||
fn from(value: sqlx::Error) -> Self {
|
||||
CustomError::Sql(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@ use crate::error::CustomError;
|
||||
|
||||
pub(crate) async fn is_directory<D: AsRef<Path>>(dir: D) -> Result<bool, CustomError> {
|
||||
let metadata = tokio::fs::metadata(dir).await;
|
||||
let result = match metadata {
|
||||
Ok(metadata) if metadata.is_dir() => true,
|
||||
_ => false,
|
||||
};
|
||||
let result = matches!(metadata, Ok(metadata) if metadata.is_dir());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ where
|
||||
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["init"]);
|
||||
command.args(["init"]);
|
||||
command.arg(format!("--initial-branch={}", branch.as_ref()));
|
||||
command.kill_on_drop(true);
|
||||
let output = command.output().await?;
|
||||
@@ -43,7 +43,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["remote", "add"]);
|
||||
command.args(["remote", "add"]);
|
||||
command.arg(name);
|
||||
command.arg(url);
|
||||
command.kill_on_drop(true);
|
||||
@@ -78,7 +78,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["fetch"]);
|
||||
command.args(["fetch"]);
|
||||
if let Some(d) = depth {
|
||||
command.arg(format!("--depth={}", d));
|
||||
}
|
||||
@@ -118,7 +118,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["checkout"]);
|
||||
command.args(["checkout"]);
|
||||
if force {
|
||||
command.arg("--force");
|
||||
}
|
||||
@@ -149,7 +149,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["remote", "get-url"]);
|
||||
command.args(["remote", "get-url"]);
|
||||
command.arg(remote.as_ref());
|
||||
command.kill_on_drop(true);
|
||||
let output = command.output().await?;
|
||||
@@ -181,7 +181,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["reset"]);
|
||||
command.args(["reset"]);
|
||||
if hard {
|
||||
command.arg("--hard");
|
||||
}
|
||||
@@ -213,7 +213,7 @@ where
|
||||
let mut command = Command::new("git");
|
||||
command.arg("-C");
|
||||
command.arg(dest);
|
||||
command.args(&["clean"]);
|
||||
command.args(["clean"]);
|
||||
if recurse_into_untracked_directories {
|
||||
command.arg("-d");
|
||||
}
|
||||
|
||||
@@ -12,18 +12,21 @@ use self::init_tracing::shutdown_telemetry;
|
||||
mod cli;
|
||||
mod command;
|
||||
mod config;
|
||||
mod database;
|
||||
mod error;
|
||||
mod fs_util;
|
||||
mod git_util;
|
||||
mod init_tracing;
|
||||
mod nix_util;
|
||||
|
||||
fn main() -> Result<ExitCode, CustomError> {
|
||||
pub(crate) type Result<T> = std::result::Result<T, CustomError>;
|
||||
|
||||
fn main() -> Result<ExitCode> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async { main_body().await })
|
||||
}
|
||||
|
||||
async fn main_body() -> Result<ExitCode, CustomError> {
|
||||
async fn main_body() -> Result<ExitCode> {
|
||||
init_telemetry().expect("Telemetry should initialize successfully.");
|
||||
let args = Cli::parse();
|
||||
match args.command {
|
||||
|
||||
@@ -29,7 +29,7 @@ where
|
||||
// nixos-rebuild build --show-trace --sudo --max-jobs "$JOBS" --flake "$DIR/../../#odo" --log-format internal-json -v "${@}"
|
||||
let mut command = Command::new("nixos-rebuild");
|
||||
command.current_dir(build_path);
|
||||
command.args(&[
|
||||
command.args([
|
||||
"build",
|
||||
"--show-trace",
|
||||
"--sudo",
|
||||
|
||||
Reference in New Issue
Block a user