1use {
2 clap::{ArgAction, Args, CommandFactory, Parser, Subcommand},
3 std::path::PathBuf,
4};
5
6pub mod build;
7pub mod cfg;
8pub mod clean;
9pub mod config;
10pub mod deploy;
11pub mod dump;
12pub mod error;
13pub mod idl;
14pub mod init;
15pub mod new;
16pub mod style;
17pub mod test;
18pub mod toolchain;
19pub mod utils;
20pub use error::CliResult;
21
22#[derive(Parser, Debug)]
23#[command(
24 name = "quasar",
25 version,
26 about = "Build programs that execute at the speed of light",
27 disable_help_subcommand = true
28)]
29pub struct Cli {
30 #[command(subcommand)]
31 pub command: Command,
32}
33
34#[derive(Subcommand, Debug)]
35pub enum Command {
36 Init(InitCommand),
38 Add(AddCommand),
40 Build(BuildCommand),
42 Test(TestCommand),
44 Deploy(DeployCommand),
46 Clean(CleanCommand),
48 Config(ConfigCommand),
50 Idl(IdlCommand),
52 Profile(ProfileCommand),
54 Dump(DumpCommand),
56 Completions(CompletionsCommand),
58}
59
60#[derive(Args, Debug, Default)]
65pub struct InitCommand {
66 #[arg(value_name = "NAME")]
68 pub name: Option<String>,
69
70 #[arg(long, short, action = ArgAction::SetTrue)]
72 pub yes: bool,
73
74 #[arg(long, action = ArgAction::SetTrue)]
76 pub no_git: bool,
77
78 #[arg(long)]
81 pub framework: Option<String>,
82
83 #[arg(long)]
85 pub template: Option<String>,
86
87 #[arg(long)]
89 pub toolchain: Option<String>,
90}
91
92#[derive(Args, Debug)]
93pub struct AddCommand {
94 #[arg(short, long, value_name = "NAME")]
96 pub instruction: Option<String>,
97
98 #[arg(short, long, value_name = "NAME")]
100 pub state: Option<String>,
101
102 #[arg(short, long, value_name = "NAME")]
104 pub error: Option<String>,
105}
106
107#[derive(Args, Debug, Default)]
108pub struct BuildCommand {
109 #[arg(long, action = ArgAction::SetTrue)]
111 pub debug: bool,
112
113 #[arg(long, short, action = ArgAction::SetTrue)]
115 pub watch: bool,
116
117 #[arg(long, value_name = "FEATURES")]
119 pub features: Option<String>,
120}
121
122#[derive(Args, Debug, Default)]
123pub struct TestCommand {
124 #[arg(long, action = ArgAction::SetTrue)]
126 pub debug: bool,
127
128 #[arg(long, short, value_name = "PATTERN")]
130 pub filter: Option<String>,
131
132 #[arg(long, short, action = ArgAction::SetTrue)]
134 pub watch: bool,
135
136 #[arg(long, action = ArgAction::SetTrue)]
138 pub no_build: bool,
139
140 #[arg(long, value_name = "FEATURES")]
142 pub features: Option<String>,
143}
144
145#[derive(Args, Debug, Default)]
146pub struct DeployCommand {
147 #[arg(long, value_name = "KEYPAIR")]
149 pub program_keypair: Option<PathBuf>,
150
151 #[arg(long, value_name = "KEYPAIR")]
153 pub upgrade_authority: Option<PathBuf>,
154
155 #[arg(long, short, value_name = "KEYPAIR")]
157 pub keypair: Option<PathBuf>,
158
159 #[arg(long, short, value_name = "URL")]
161 pub url: Option<String>,
162
163 #[arg(long, action = ArgAction::SetTrue)]
165 pub skip_build: bool,
166}
167
168#[derive(Args, Debug, Default)]
169pub struct CleanCommand {
170 #[arg(long, short, action = ArgAction::SetTrue)]
172 pub all: bool,
173}
174
175#[derive(Args, Debug)]
176pub struct ConfigCommand {
177 #[command(subcommand)]
178 pub action: Option<ConfigAction>,
179}
180
181#[derive(Subcommand, Debug)]
182pub enum ConfigAction {
183 Get {
185 #[arg(value_name = "KEY")]
187 key: String,
188 },
189 Set {
191 #[arg(value_name = "KEY")]
193 key: String,
194 #[arg(value_name = "VALUE")]
196 value: String,
197 },
198 List,
200 Reset,
202}
203
204#[derive(Args, Debug)]
205pub struct IdlCommand {
206 #[arg(value_name = "PATH")]
208 pub crate_path: PathBuf,
209}
210
211#[derive(Args, Debug, Clone)]
212pub struct DumpCommand {
213 #[arg(value_name = "ELF")]
215 pub elf_path: Option<PathBuf>,
216
217 #[arg(long, short, value_name = "SYMBOL")]
219 pub function: Option<String>,
220
221 #[arg(long, short = 'S', action = ArgAction::SetTrue)]
223 pub source: bool,
224}
225
226#[derive(Args, Debug, Clone)]
227pub struct ProfileCommand {
228 #[arg(value_name = "ELF")]
230 pub elf_path: Option<PathBuf>,
231
232 #[arg(long = "diff", value_name = "PROGRAM", conflicts_with = "elf_path")]
234 pub diff_program: Option<String>,
235
236 #[arg(long, action = ArgAction::SetTrue, conflicts_with = "diff_program")]
238 pub share: bool,
239
240 #[arg(long, action = ArgAction::SetTrue)]
242 pub expand: bool,
243
244 #[arg(long, short, action = ArgAction::SetTrue)]
246 pub watch: bool,
247}
248
249#[derive(Args, Debug)]
250pub struct CompletionsCommand {
251 #[arg(value_enum)]
253 pub shell: clap_complete::Shell,
254}
255
256pub fn run(cli: Cli) -> CliResult {
261 match cli.command {
262 Command::Init(cmd) => init::run(
263 cmd.name,
264 cmd.yes,
265 cmd.no_git,
266 cmd.framework,
267 cmd.template,
268 cmd.toolchain,
269 ),
270 Command::Add(cmd) => {
271 if cmd.instruction.is_none() && cmd.state.is_none() && cmd.error.is_none() {
272 eprintln!(
273 " {}",
274 style::fail(
275 "specify at least one of -i/--instruction, -s/--state, or -e/--error"
276 )
277 );
278 std::process::exit(1);
279 }
280 if let Some(name) = cmd.instruction {
281 new::run_instruction(&name)?;
282 }
283 if let Some(name) = cmd.state {
284 new::run_state(&name)?;
285 }
286 if let Some(name) = cmd.error {
287 new::run_error(&name)?;
288 }
289 Ok(())
290 }
291 Command::Build(cmd) => build::run(cmd.debug, cmd.watch, cmd.features),
292 Command::Test(cmd) => {
293 test::run(cmd.debug, cmd.filter, cmd.watch, cmd.no_build, cmd.features)
294 }
295 Command::Deploy(cmd) => deploy::run(
296 cmd.program_keypair,
297 cmd.upgrade_authority,
298 cmd.keypair,
299 cmd.url,
300 cmd.skip_build,
301 ),
302 Command::Clean(cmd) => clean::run(cmd.all),
303 Command::Config(cmd) => cfg::run(cmd.action),
304 Command::Idl(cmd) => idl::run(cmd),
305 Command::Dump(cmd) => dump::run(cmd.elf_path, cmd.function, cmd.source),
306 Command::Completions(cmd) => {
307 clap_complete::generate(
308 cmd.shell,
309 &mut Cli::command(),
310 "quasar",
311 &mut std::io::stdout(),
312 );
313 Ok(())
314 }
315 Command::Profile(cmd) => {
316 if cmd.watch {
317 return profile_watch(cmd.expand);
318 }
319
320 let elf_path = if let Some(path) = cmd.elf_path {
321 path
322 } else if cmd.diff_program.is_none() {
323 build::profile_build()?
325 } else {
326 std::path::PathBuf::new()
328 };
329
330 quasar_profile::run(quasar_profile::ProfileCommand {
331 elf_path: if elf_path.as_os_str().is_empty() {
332 None
333 } else {
334 Some(elf_path)
335 },
336 diff_program: cmd.diff_program,
337 share: cmd.share,
338 expand: cmd.expand,
339 });
340 Ok(())
341 }
342 }
343}
344
345pub fn print_help() {
350 let v = env!("CARGO_PKG_VERSION");
351
352 println!();
353 println!(
354 " {} {}",
355 style::bold("quasar"),
356 style::dim(&format!("v{v}"))
357 );
358 println!(
359 " {}",
360 style::dim("Build programs that execute at the speed of light")
361 );
362 println!();
363 println!(" {}", style::bold("Commands:"));
364 print_cmd(
365 "init [name] [-y] [--no-git] [--template]",
366 "Scaffold a new project",
367 );
368 print_cmd(
369 "add [-i name] [-s name] [-e name]",
370 "Add instructions, state, errors",
371 );
372 print_cmd(
373 "build [--debug] [-w] [--features]",
374 "Compile the on-chain program",
375 );
376 print_cmd(
377 "test [--debug] [-f] [-w] [--features]",
378 "Run the test suite",
379 );
380 print_cmd(
381 "deploy [-u url] [-k keypair] [--skip-build]",
382 "Deploy to a cluster",
383 );
384 print_cmd("clean [-a]", "Remove build artifacts");
385 print_cmd("config [get|set|list|reset]", "Manage global settings");
386 print_cmd("idl <path>", "Generate the program IDL");
387 print_cmd(
388 "profile [elf] [--expand] [--diff] [-w]",
389 "Measure compute-unit usage",
390 );
391 print_cmd("dump [elf] [-f] [-S]", "Dump sBPF assembly");
392 println!();
393 println!(" {}", style::bold("Options:"));
394 print_cmd("-h, --help", "Print help");
395 print_cmd("-V, --version", "Print version");
396 println!();
397 println!(
398 " Run {} for details on any command.",
399 style::bold("quasar <command> --help")
400 );
401 println!();
402}
403
404fn print_cmd(cmd: &str, desc: &str) {
405 println!(" {} {}", style::color(45, &format!("{cmd:<34}")), desc);
406}
407
408fn profile_watch(expand: bool) -> CliResult {
409 fn profile_once(expand: bool) {
410 match build::profile_build() {
411 Ok(elf) => {
412 quasar_profile::run(quasar_profile::ProfileCommand {
413 elf_path: Some(elf),
414 diff_program: None,
415 share: false,
416 expand,
417 });
418 }
419 Err(e) => {
420 eprintln!(" {}", style::fail(&format!("{e}")));
421 }
422 }
423 }
424
425 profile_once(expand);
426
427 loop {
428 let baseline = build::collect_mtimes(std::path::Path::new("src"));
429 loop {
430 std::thread::sleep(std::time::Duration::from_secs(1));
431 let current = build::collect_mtimes(std::path::Path::new("src"));
432 if current != baseline {
433 profile_once(expand);
434 break;
435 }
436 }
437 }
438}