Author:
Hash:
Timestamp:
+123 -114 +/-5 browse
Kevin Schoon [me@kevinschoon.com]
706b2f060f79240c4ee7b6e5b9f202cb6737d382
Sun, 28 Jan 2024 17:17:19 +0000 (1.4 years ago)
1 | diff --git a/Cargo.lock b/Cargo.lock |
2 | index 2f132f5..03c4cdf 100644 |
3 | --- a/Cargo.lock |
4 | +++ b/Cargo.lock |
5 | @@ -574,6 +574,7 @@ dependencies = [ |
6 | "capnp", |
7 | "capnp-rpc", |
8 | "clap 4.4.8", |
9 | + "clap_complete", |
10 | "nickel-lang-core", |
11 | "petgraph", |
12 | "rand", |
13 | @@ -986,9 +987,9 @@ dependencies = [ |
14 | |
15 | [[package]] |
16 | name = "clap_complete" |
17 | - version = "4.4.5" |
18 | + version = "4.4.9" |
19 | source = "registry+https://github.com/rust-lang/crates.io-index" |
20 | - checksum = "a51919c5608a32e34ea1d6be321ad070065e17613e168c5b6977024290f2630b" |
21 | + checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106" |
22 | dependencies = [ |
23 | "clap 4.4.8", |
24 | ] |
25 | diff --git a/ayllu-build/Cargo.toml b/ayllu-build/Cargo.toml |
26 | index e91dbbe..e832950 100644 |
27 | --- a/ayllu-build/Cargo.toml |
28 | +++ b/ayllu-build/Cargo.toml |
29 | @@ -25,3 +25,4 @@ sqlx = { version = "0.7.3", features = ["sqlite", "macros"] } |
30 | tokio = { version = "1.34.0", features = ["full"] } |
31 | sysinfo = "0.29.11" |
32 | serde_json = "1.0.108" |
33 | + clap_complete = "4.4.9" |
34 | diff --git a/ayllu-build/src/config.rs b/ayllu-build/src/config.rs |
35 | index c6c8b85..7f34e57 100644 |
36 | --- a/ayllu-build/src/config.rs |
37 | +++ b/ayllu-build/src/config.rs |
38 | @@ -35,6 +35,7 @@ pub struct Builder { |
39 | |
40 | #[derive(Deserialize, Serialize, Clone, Debug)] |
41 | pub struct Config { |
42 | + pub log_level: String, |
43 | pub builder: Builder, |
44 | } |
45 | |
46 | diff --git a/ayllu-build/src/main.rs b/ayllu-build/src/main.rs |
47 | index 1bfc4e9..8d00605 100644 |
48 | --- a/ayllu-build/src/main.rs |
49 | +++ b/ayllu-build/src/main.rs |
50 | @@ -1,13 +1,14 @@ |
51 | - use std::io::stderr; |
52 | + use std::io::{stderr, stdout}; |
53 | use std::path::{Path, PathBuf}; |
54 | + use std::str::FromStr; |
55 | |
56 | - use anyhow::{Result}; |
57 | - use clap::{arg, value_parser, Command}; |
58 | - use tokio::{runtime::Builder as TokioBuilder, task::LocalSet}; |
59 | + use anyhow::Result; |
60 | + use clap::{arg, Command, CommandFactory, Parser, Subcommand}; |
61 | + use clap_complete::{generate, Generator, Shell}; |
62 | + use tokio::task::LocalSet; |
63 | use tracing::{log::info, Level}; |
64 | |
65 | - |
66 | - use ayllu_database::{migrate, Builder}; |
67 | + use ayllu_database::Builder; |
68 | mod config; |
69 | mod database_ext; |
70 | mod evaluate; |
71 | @@ -15,111 +16,113 @@ mod executor; |
72 | mod models; |
73 | mod rpc_server; |
74 | |
75 | - // TODO: migrate to derive based system |
76 | + #[derive(Parser)] |
77 | + #[command(author, version, about, long_about = None)] |
78 | + #[command(name = "ayllu-build")] |
79 | + #[command(about = "Ayllu Build System")] |
80 | + struct Cli { |
81 | + /// Path to your configuration file |
82 | + #[arg(short, long, value_name = "FILE")] |
83 | + config: Option<PathBuf>, |
84 | + |
85 | + /// Sets the logging level |
86 | + #[arg(short, long, value_name = "LEVEL")] |
87 | + level: Option<Level>, |
88 | + |
89 | + #[command(subcommand)] |
90 | + command: Commands, |
91 | + } |
92 | |
93 | - pub fn main() -> Result<()> { |
94 | - let command = Command::new("ayllu-build") |
95 | - .about("ayllu build system") |
96 | - .arg( |
97 | - arg!(-c --config <FILE> "optional path to a configuration file") |
98 | - .id("config") |
99 | - .required(false) |
100 | - .value_parser(value_parser!(String)), |
101 | - ) |
102 | - .arg( |
103 | - arg!(-l --level <LEVEL> "logging level [ERROR,WARN,INFO,DEBUG,TRACE]") |
104 | - .id("level") |
105 | - .required(false) |
106 | - .value_parser(value_parser!(Level)), |
107 | - ) |
108 | - .subcommand_required(true) |
109 | - .subcommand(Command::new("serve").about("run the build server and listen for commands")) |
110 | - .subcommand(Command::new("migrate").about("initialize the database and apply migrations")) |
111 | - .subcommand( |
112 | - Command::new("evaluate") |
113 | - .arg( |
114 | - arg!([PATH] "alternate path for the build directory") |
115 | - .default_value(".ayllu/build") |
116 | - .required(false), |
117 | - ) |
118 | - .about("evaluate a local build script"), |
119 | - ) |
120 | - .subcommand( |
121 | - Command::new("plan") |
122 | - .arg( |
123 | - arg!([PATH] "alternate path for the build directory") |
124 | - .default_value(".ayllu/build") |
125 | - .required(false), |
126 | - ) |
127 | - .about("generate an execution plan and generate DOT output"), |
128 | - ); |
129 | + #[derive(Subcommand, Debug, PartialEq)] |
130 | + enum Commands { |
131 | + /// generate autocomplete commands for common shells |
132 | + Complete { |
133 | + #[arg(long)] |
134 | + shell: Shell, |
135 | + }, |
136 | + /// run migrations against the database |
137 | + Migrate {}, |
138 | + /// run the build server and listen for commands |
139 | + Serve {}, |
140 | + /// evaluate a local build script |
141 | + Evaluate { |
142 | + /// Path to your configuration file |
143 | + #[arg(short, long, value_name = "FILE")] |
144 | + alternate_path: Option<PathBuf>, |
145 | + }, |
146 | + /// generate an execution plan and output a DOT script |
147 | + Plan { |
148 | + /// Path to your configuration file |
149 | + #[arg(short, long, value_name = "FILE")] |
150 | + alternate_path: Option<PathBuf>, |
151 | + }, |
152 | + } |
153 | + |
154 | + fn print_completions<G: Generator>(gen: G, cmd: &mut Command) { |
155 | + generate(gen, cmd, cmd.get_name().to_string(), &mut stdout()); |
156 | + } |
157 | |
158 | - let matches = command.get_matches(); |
159 | - let config_path = matches |
160 | - .get_one::<String>("config") |
161 | - .map(Path::new); |
162 | - let config = config::load(config_path)?; |
163 | - let config = config.builder; |
164 | - let log_level = matches.get_one::<Level>("level").unwrap_or(&Level::INFO); |
165 | + #[tokio::main(flavor = "current_thread")] |
166 | + async fn main() -> Result<()> { |
167 | + let cli = Cli::parse(); |
168 | + let cfg = config::load(cli.config.as_ref().map(|cfg| cfg.as_path()))?; |
169 | + let log_level = Level::from_str(&cfg.log_level)?; |
170 | tracing_subscriber::fmt() |
171 | .compact() |
172 | - .with_max_level(*log_level) |
173 | .with_line_number(true) |
174 | .with_level(true) |
175 | - .with_writer(stderr) |
176 | + .with_max_level(cli.level.unwrap_or(log_level)) |
177 | .init(); |
178 | - info!("logger initialized"); |
179 | - if let Some(_matches) = matches.subcommand_matches("serve") { |
180 | - TokioBuilder::new_current_thread() |
181 | - .enable_all() |
182 | - .build() |
183 | - .unwrap() |
184 | - .block_on(async move { |
185 | - migrate(&config.database.path, "./migrations").await?; |
186 | - LocalSet::new().run_until(rpc_server::serve(config)).await |
187 | - }) |
188 | - .unwrap(); |
189 | - } else if let Some(_matches) = matches.subcommand_matches("migrate") { |
190 | - TokioBuilder::new_current_thread() |
191 | - .enable_all() |
192 | - .build() |
193 | - .unwrap() |
194 | - .block_on(async move { |
195 | - migrate(&config.database.path, "./migrations") |
196 | - .await |
197 | - .unwrap() |
198 | - }) |
199 | - } else if let Some(matches) = matches.subcommand_matches("evaluate") { |
200 | - TokioBuilder::new_current_thread() |
201 | - .enable_all() |
202 | - .build() |
203 | - .unwrap() |
204 | - .block_on(async move { |
205 | - // TODO fix me |
206 | - // migrate(&config.database.path, "./migrations").await?; |
207 | - let db = Builder::default() |
208 | - .url(&config.database.path) |
209 | - .log_queries(*log_level == Level::DEBUG) |
210 | - .build() |
211 | - .await?; |
212 | - let build_dir = matches.get_one::<String>("PATH").unwrap(); |
213 | - let rt = evaluate::RuntimeBuilder::default() |
214 | - .source(evaluate::Source::Path(PathBuf::from(build_dir))) |
215 | - .database(db) |
216 | - .log_dir(Path::new(&config.log_path).to_path_buf()) |
217 | - .build(); |
218 | - rt.eval().await?; |
219 | - let result: Result<()> = Ok(()); |
220 | - result |
221 | - })? |
222 | - } else if let Some(matches) = matches.subcommand_matches("plan") { |
223 | - let build_dir = matches.get_one::<String>("PATH").unwrap(); |
224 | - let rt = evaluate::RuntimeBuilder::default() |
225 | - .source(evaluate::Source::Path(PathBuf::from(build_dir))) |
226 | - .build(); |
227 | - let graph = rt.plan()?; |
228 | - let dot = rt.to_dot(&graph)?; |
229 | - print!("{}", dot) |
230 | - }; |
231 | - Ok(()) |
232 | + tracing::info!("logger initialized"); |
233 | + let default_build_dir = PathBuf::from_str(".ayllu/build").unwrap(); |
234 | + match cli.command { |
235 | + Commands::Complete { shell } => { |
236 | + let mut cmd = Cli::command(); |
237 | + print_completions(shell, &mut cmd); |
238 | + Ok(()) |
239 | + } |
240 | + Commands::Migrate {} => { |
241 | + let db = Builder::default() |
242 | + .url(&cfg.builder.database.path) |
243 | + .migrations("./migrations") |
244 | + .build() |
245 | + .await?; |
246 | + db.close().await?; |
247 | + Ok(()) |
248 | + } |
249 | + Commands::Serve {} => { |
250 | + LocalSet::new() |
251 | + .run_until(rpc_server::serve(&cfg.builder)) |
252 | + .await?; |
253 | + Ok(()) |
254 | + } |
255 | + Commands::Evaluate { alternate_path } => { |
256 | + // TODO fix me |
257 | + let db = Builder::default() |
258 | + .url(&cfg.builder.database.path) |
259 | + .log_queries(log_level == Level::DEBUG) |
260 | + .build() |
261 | + .await?; |
262 | + let rt = evaluate::RuntimeBuilder::default() |
263 | + .source(evaluate::Source::Path( |
264 | + alternate_path.unwrap_or(default_build_dir), |
265 | + )) |
266 | + .database(db) |
267 | + .log_dir(Path::new(&cfg.builder.log_path).to_path_buf()) |
268 | + .build(); |
269 | + rt.eval().await?; |
270 | + Ok(()) |
271 | + } |
272 | + Commands::Plan { alternate_path } => { |
273 | + let rt = evaluate::RuntimeBuilder::default() |
274 | + .source(evaluate::Source::Path( |
275 | + alternate_path.unwrap_or(default_build_dir), |
276 | + )) |
277 | + .build(); |
278 | + let graph = rt.plan()?; |
279 | + let dot = rt.to_dot(&graph)?; |
280 | + print!("{}", dot); |
281 | + Ok(()) |
282 | + } |
283 | + } |
284 | } |
285 | diff --git a/ayllu-build/src/rpc_server.rs b/ayllu-build/src/rpc_server.rs |
286 | index da7d429..5871a98 100644 |
287 | --- a/ayllu-build/src/rpc_server.rs |
288 | +++ b/ayllu-build/src/rpc_server.rs |
289 | @@ -1,9 +1,8 @@ |
290 | use std::error::Error as StdError; |
291 | |
292 | + use anyhow::Result; |
293 | use capnp::{capability::Promise, Error as CapnpError}; |
294 | - |
295 | - use sysinfo::{SystemExt}; |
296 | - |
297 | + use sysinfo::SystemExt; |
298 | |
299 | use crate::config::Builder as Config; |
300 | // use crate::database_ext::XmppExt; |
301 | @@ -12,7 +11,7 @@ use ayllu_api::build_capnp::worker::{ |
302 | Server, SubmitParams, SubmitResults, |
303 | }; |
304 | |
305 | - use ayllu_database::Wrapper as Database; |
306 | + use ayllu_database::{Builder, Wrapper as Database}; |
307 | use ayllu_rpc::Server as RpcHelper; |
308 | |
309 | struct ServerImpl { |
310 | @@ -51,8 +50,12 @@ impl Server for ServerImpl { |
311 | } |
312 | } |
313 | |
314 | - pub async fn serve(cfg: Config) -> Result<(), Box<dyn StdError>> { |
315 | - let db = Database::new(&cfg.database.path, true, false).await?; |
316 | + pub async fn serve(cfg: &Config) -> Result<()> { |
317 | + let db = Builder::default() |
318 | + .url(&cfg.database.path) |
319 | + .migrations("./migrations") |
320 | + .build() |
321 | + .await?; |
322 | let server = ServerImpl { db }; |
323 | let runtime = RpcHelper::<Client, ServerImpl>::new(&cfg.address, server); |
324 | runtime.serve().await?; |