diff --git a/Cargo.lock b/Cargo.lock index 1920297..e53504c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,7 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" name = "aocsync" version = "0.1.0" dependencies = [ + "anyhow", "glog", "log 0.4.17", "rocket 0.5.0-rc.2", diff --git a/Cargo.toml b/Cargo.toml index 7238083..6bb4f15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ serde = { version = "1.0.148", features = ["serde_derive"] } rocket_anyhow = "0.1.1" thiserror = "1.0.37" rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] } +anyhow = "1.0.66" diff --git a/Rocket.toml b/Rocket.toml index 2f8f6a5..829a0c6 100644 --- a/Rocket.toml +++ b/Rocket.toml @@ -8,8 +8,7 @@ port = 9346 # Uncomment to make it production like. #log_level = "critical" -source_root = "/net/nasx/mnt/storage/aoc/github/" build_root = "/tmp/aocsync/" www_root = "/tmp/aocsync-serve/" -repos = { akramer = { name = "aoc2022", branch = "main" }, ggriffiniii = { name = "aoc2022", branch = "main" }, wathiede = { name = "advent/2022", branch = "master", first_commit = "bc3feabb4669b7ad8167398a4d37eefe68ea0565" } } +repos = { akramer = { url = "https://github.com/akramer/aoc2022", name = "aoc2022", branch = "main" }, ggriffiniii = { url = "https://github.com/ggriffiniii/aoc2022", name = "aoc2022", branch = "main" }, wathiede = { url = "https://github.com/wathiede/advent", name = "advent/2022", branch = "master", first_commit = "bc3feabb4669b7ad8167398a4d37eefe68ea0565", subdir = "2022" } } diff --git a/src/bin/aocpoller.rs b/src/bin/aocpoller.rs new file mode 100644 index 0000000..7deeb0b --- /dev/null +++ b/src/bin/aocpoller.rs @@ -0,0 +1,34 @@ +use std::process::Command; + +use aocsync::{logging_run, Config}; +use glog::Flags; +use log::info; +fn main() -> anyhow::Result<()> { + glog::new() + .init(Flags { + colorlogtostderr: true, + //alsologtostderr: true, // use logtostderr to only write to stderr and not to files + logtostderr: true, + ..Default::default() + }) + .unwrap(); + let config = rocket::Config::figment() + .extract::() + .expect("Couldn't parse config"); + info!("{:#?}", config); + for (name, repo) in config.repos.iter() { + let ts = logging_run( + Command::new("git") + .arg("ls-remote") + .arg(&repo.url) + .arg("-q") + .arg(&repo.name) + .arg("-h") + .arg(&repo.branch), + )?; + let out = String::from_utf8_lossy(&ts.output.stdout); + let head = &out[..out.find('\t').expect("No tab in output")]; + info!("HEAD: '{}'", head); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0994a37 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,93 @@ +use std::{ + collections::HashMap, + io, + path::{Path, PathBuf}, + process::{Command, Output}, + time::{Duration, Instant}, +}; + +use log::info; +use rocket::response::Responder; +use serde::Deserialize; +use thiserror::Error; + +#[derive(Error, Debug, Responder)] +pub enum SyncError { + #[error("IO error")] + IoError(#[from] io::Error), +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub build_root: PathBuf, + pub www_root: PathBuf, + pub repos: HashMap, +} + +#[derive(Debug, Deserialize)] +pub struct Repo { + pub name: String, + pub url: String, + pub branch: String, + pub first_commit: Option, + pub subdir: Option, +} + +pub fn indent_output(bytes: &[u8]) -> String { + let s = String::from_utf8_lossy(bytes); + let s = if s.is_empty() { "" } else { &s }; + s.split('\n') + .map(|l| format!("\t{l}")) + .collect::>() + .join("\n") +} + +pub fn format_task_status(ts: &TaskStatus) -> String { + format!( + "Status {} ({}) {}s:\nStdout:\n{}\nStderr:\n{}", + ts.command, + ts.output.status, + ts.duration.as_secs_f32(), + indent_output(&ts.output.stdout), + indent_output(&ts.output.stderr) + ) +} + +pub struct TaskStatus { + pub command: String, + pub output: Output, + pub duration: Duration, +} + +pub fn logging_run(cmd: &mut Command) -> Result { + info!( + "{}$ {} {}", + cmd.get_current_dir() + .unwrap_or(Path::new("")) + .display(), + cmd.get_program().to_string_lossy(), + cmd.get_args() + .map(|a| a.to_string_lossy()) + .collect::>() + .join(" "), + ); + let start = Instant::now(); + let o = cmd.output()?; + let ts = TaskStatus { + output: o, + command: format!("{:?}", cmd), + duration: start.elapsed(), + }; + info!("{}", format_task_status(&ts)); + Ok(ts) +} + +pub fn git_checkout(build_path: &Path, rev: &str) -> Result { + logging_run( + Command::new("git") + .current_dir(&build_path) + .arg("checkout") + .arg("-q") + .arg(rev), + ) +} diff --git a/src/main.rs b/src/main.rs index d981bd5..c579e2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,90 +7,32 @@ use std::{ io, path::{Path, PathBuf}, process::{Command, Output}, - time::{Duration, Instant}, }; +use aocsync::{format_task_status, git_checkout, logging_run, Config, SyncError, TaskStatus}; use glog::Flags; use log::{error, info}; use rocket::{fairing::AdHoc, fs::FileServer, response::Responder, State}; use rocket_dyn_templates::{context, Template}; use serde::Deserialize; -use thiserror::Error; - -#[derive(Error, Debug, Responder)] -pub enum SyncError { - #[error("IO error")] - IoError(#[from] io::Error), -} // For testing #[get("/-/reload/")] -fn get_reload(config: &State, repo: &str) -> Result { - reload(config, repo) +fn get_reload( + lock: &State, + config: &State, + repo: &str, +) -> Result { + reload(lock, config, repo) } #[post("/-/reload/")] -fn post_reload(config: &State, repo: &str) -> Result { - reload(config, repo) -} - -fn indent_output(bytes: &[u8]) -> String { - let s = String::from_utf8_lossy(bytes); - let s = if s.is_empty() { "" } else { &s }; - s.split('\n') - .map(|l| format!("\t{l}")) - .collect::>() - .join("\n") -} - -fn format_task_status(ts: &TaskStatus) -> String { - format!( - "Status {} ({}) {}s:\nStdout:\n{}\nStderr:\n{}", - ts.command, - ts.output.status, - ts.duration.as_secs_f32(), - indent_output(&ts.output.stdout), - indent_output(&ts.output.stderr) - ) -} - -struct TaskStatus { - command: String, - output: Output, - duration: Duration, -} - -fn logging_run(cmd: &mut Command) -> Result { - info!( - "{}$ {} {}", - cmd.get_current_dir() - .unwrap_or(Path::new("")) - .display(), - cmd.get_program().to_string_lossy(), - cmd.get_args() - .map(|a| a.to_string_lossy()) - .collect::>() - .join(" "), - ); - let start = Instant::now(); - let o = cmd.output()?; - let ts = TaskStatus { - output: o, - command: format!("{:?}", cmd), - duration: start.elapsed(), - }; - info!("{}", format_task_status(&ts)); - Ok(ts) -} - -fn git_checkout(build_path: &Path, rev: &str) -> Result { - logging_run( - Command::new("git") - .current_dir(&build_path) - .arg("checkout") - .arg("-q") - .arg(rev), - ) +fn post_reload( + lock: &State, + config: &State, + repo: &str, +) -> Result { + reload(lock, config, repo) } fn bench_at_commit( @@ -113,7 +55,14 @@ fn bench_at_commit( Ok(output) } -fn reload(config: &State, name: &str) -> Result { +use std::sync::{Arc, Mutex}; + +fn reload( + lock: &State, + config: &State, + name: &str, +) -> Result { + let _locked = lock.0.lock().unwrap(); let repo = &config.repos[name]; info!("Need to reload '{}': {:?}\n{:#?}", name, config, repo); @@ -122,12 +71,15 @@ fn reload(config: &State, name: &str) -> Result { let www_root = config.www_root.join(name); let target_root = config.build_root.join("target"); - let source_path = config.source_root.join(name).join(&repo.name); - let build_path = git_root.join(&repo.name); + let checkout_path = git_root.join(&repo.name); + let build_path = if let Some(subdir) = &repo.subdir { + git_root.join(&repo.name).join(subdir) + } else { + git_root.join(&repo.name) + }; let target_path = target_root.join(name); dbg!( - &source_path, &build_path, &target_path, &commits_root, @@ -152,28 +104,35 @@ fn reload(config: &State, name: &str) -> Result { fs::create_dir_all(&www_root)?; } let mut output = vec![logging_run(Command::new("git").arg("version"))?]; - let needs_clone = !build_path.exists(); + let needs_clone = !checkout_path.exists(); if needs_clone { output.push(logging_run( Command::new("git") .current_dir(&git_root) .arg("clone") - .arg(&source_path), + .arg(&repo.url) + .arg(&checkout_path), )?); } output.push(logging_run( Command::new("git") - .current_dir(&build_path) + .current_dir(&checkout_path) .arg("checkout") .arg("-f") .arg(&repo.branch), )?); - dbg!(&build_path); + output.push(logging_run( + Command::new("git") + .current_dir(&checkout_path) + .arg("checkout") + .arg("HEAD"), + )?); + dbg!(&checkout_path); // Make sure buildable clone is up to date output.push(logging_run( - Command::new("git").current_dir(&build_path).arg("fetch"), + Command::new("git").current_dir(&checkout_path).arg("pull"), )?); - let commits = logging_run(Command::new("git").current_dir(&build_path).args([ + let commits = logging_run(Command::new("git").current_dir(&checkout_path).args([ "log", "--format=%H", &repo.branch, @@ -243,20 +202,7 @@ fn http500(req: &rocket::Request) -> String { format!("{:?}", req) } -#[derive(Debug, Deserialize)] -struct Config { - source_root: PathBuf, - build_root: PathBuf, - www_root: PathBuf, - repos: HashMap, -} - -#[derive(Debug, Deserialize)] -struct Repo { - name: String, - branch: String, - first_commit: Option, -} +struct ReloadLock(Arc>); #[launch] fn rocket() -> _ { @@ -276,6 +222,7 @@ fn rocket() -> _ { rocket::build() .mount("/", routes![index, get_reload, post_reload]) .mount("/results/", FileServer::from(config.www_root)) + .manage(ReloadLock(Arc::new(Mutex::new(())))) //.register("/", catchers![http500]) .attach(AdHoc::config::()) .attach(Template::fairing())