use {
clap::{ArgAction, Args, CommandFactory, Parser, Subcommand},
std::path::PathBuf,
};
pub mod build;
pub mod cfg;
pub mod clean;
pub mod config;
pub mod deploy;
pub mod dump;
pub mod error;
pub mod idl;
pub mod init;
pub mod new;
pub mod style;
pub mod test;
pub mod toolchain;
pub mod utils;
pub use error::CliResult;
#[derive(Parser, Debug)]
#[command(
name = "quasar",
version,
about = "Build programs that execute at the speed of light",
disable_help_subcommand = true
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Init(InitCommand),
Add(AddCommand),
Build(BuildCommand),
Test(TestCommand),
Deploy(DeployCommand),
Clean(CleanCommand),
Config(ConfigCommand),
Idl(IdlCommand),
Profile(ProfileCommand),
Dump(DumpCommand),
Completions(CompletionsCommand),
}
#[derive(Args, Debug, Default)]
pub struct InitCommand {
#[arg(value_name = "NAME")]
pub name: Option<String>,
#[arg(long, short, action = ArgAction::SetTrue)]
pub yes: bool,
#[arg(long, action = ArgAction::SetTrue)]
pub no_git: bool,
#[arg(long)]
pub framework: Option<String>,
#[arg(long)]
pub template: Option<String>,
#[arg(long)]
pub toolchain: Option<String>,
}
#[derive(Args, Debug)]
pub struct AddCommand {
#[arg(short, long, value_name = "NAME")]
pub instruction: Option<String>,
#[arg(short, long, value_name = "NAME")]
pub state: Option<String>,
#[arg(short, long, value_name = "NAME")]
pub error: Option<String>,
}
#[derive(Args, Debug, Default)]
pub struct BuildCommand {
#[arg(long, action = ArgAction::SetTrue)]
pub debug: bool,
#[arg(long, short, action = ArgAction::SetTrue)]
pub watch: bool,
#[arg(long, value_name = "FEATURES")]
pub features: Option<String>,
}
#[derive(Args, Debug, Default)]
pub struct TestCommand {
#[arg(long, action = ArgAction::SetTrue)]
pub debug: bool,
#[arg(long, short, value_name = "PATTERN")]
pub filter: Option<String>,
#[arg(long, short, action = ArgAction::SetTrue)]
pub watch: bool,
#[arg(long, action = ArgAction::SetTrue)]
pub no_build: bool,
#[arg(long, value_name = "FEATURES")]
pub features: Option<String>,
}
#[derive(Args, Debug, Default)]
pub struct DeployCommand {
#[arg(long, value_name = "KEYPAIR")]
pub program_keypair: Option<PathBuf>,
#[arg(long, value_name = "KEYPAIR")]
pub upgrade_authority: Option<PathBuf>,
#[arg(long, short, value_name = "KEYPAIR")]
pub keypair: Option<PathBuf>,
#[arg(long, short, value_name = "URL")]
pub url: Option<String>,
#[arg(long, action = ArgAction::SetTrue)]
pub skip_build: bool,
}
#[derive(Args, Debug, Default)]
pub struct CleanCommand {
#[arg(long, short, action = ArgAction::SetTrue)]
pub all: bool,
}
#[derive(Args, Debug)]
pub struct ConfigCommand {
#[command(subcommand)]
pub action: Option<ConfigAction>,
}
#[derive(Subcommand, Debug)]
pub enum ConfigAction {
Get {
#[arg(value_name = "KEY")]
key: String,
},
Set {
#[arg(value_name = "KEY")]
key: String,
#[arg(value_name = "VALUE")]
value: String,
},
List,
Reset,
}
#[derive(Args, Debug)]
pub struct IdlCommand {
#[arg(value_name = "PATH")]
pub crate_path: PathBuf,
}
#[derive(Args, Debug, Clone)]
pub struct DumpCommand {
#[arg(value_name = "ELF")]
pub elf_path: Option<PathBuf>,
#[arg(long, short, value_name = "SYMBOL")]
pub function: Option<String>,
#[arg(long, short = 'S', action = ArgAction::SetTrue)]
pub source: bool,
}
#[derive(Args, Debug, Clone)]
pub struct ProfileCommand {
#[arg(value_name = "ELF")]
pub elf_path: Option<PathBuf>,
#[arg(long = "diff", value_name = "PROGRAM", conflicts_with = "elf_path")]
pub diff_program: Option<String>,
#[arg(long, action = ArgAction::SetTrue, conflicts_with = "diff_program")]
pub share: bool,
#[arg(long, action = ArgAction::SetTrue)]
pub expand: bool,
#[arg(long, short, action = ArgAction::SetTrue)]
pub watch: bool,
}
#[derive(Args, Debug)]
pub struct CompletionsCommand {
#[arg(value_enum)]
pub shell: clap_complete::Shell,
}
pub fn run(cli: Cli) -> CliResult {
match cli.command {
Command::Init(cmd) => init::run(
cmd.name,
cmd.yes,
cmd.no_git,
cmd.framework,
cmd.template,
cmd.toolchain,
),
Command::Add(cmd) => {
if cmd.instruction.is_none() && cmd.state.is_none() && cmd.error.is_none() {
eprintln!(
" {}",
style::fail(
"specify at least one of -i/--instruction, -s/--state, or -e/--error"
)
);
std::process::exit(1);
}
if let Some(name) = cmd.instruction {
new::run_instruction(&name)?;
}
if let Some(name) = cmd.state {
new::run_state(&name)?;
}
if let Some(name) = cmd.error {
new::run_error(&name)?;
}
Ok(())
}
Command::Build(cmd) => build::run(cmd.debug, cmd.watch, cmd.features),
Command::Test(cmd) => {
test::run(cmd.debug, cmd.filter, cmd.watch, cmd.no_build, cmd.features)
}
Command::Deploy(cmd) => deploy::run(
cmd.program_keypair,
cmd.upgrade_authority,
cmd.keypair,
cmd.url,
cmd.skip_build,
),
Command::Clean(cmd) => clean::run(cmd.all),
Command::Config(cmd) => cfg::run(cmd.action),
Command::Idl(cmd) => idl::run(cmd),
Command::Dump(cmd) => dump::run(cmd.elf_path, cmd.function, cmd.source),
Command::Completions(cmd) => {
clap_complete::generate(
cmd.shell,
&mut Cli::command(),
"quasar",
&mut std::io::stdout(),
);
Ok(())
}
Command::Profile(cmd) => {
if cmd.watch {
return profile_watch(cmd.expand);
}
let elf_path = if let Some(path) = cmd.elf_path {
path
} else if cmd.diff_program.is_none() {
build::profile_build()?
} else {
std::path::PathBuf::new()
};
quasar_profile::run(quasar_profile::ProfileCommand {
elf_path: if elf_path.as_os_str().is_empty() {
None
} else {
Some(elf_path)
},
diff_program: cmd.diff_program,
share: cmd.share,
expand: cmd.expand,
});
Ok(())
}
}
}
pub fn print_help() {
let v = env!("CARGO_PKG_VERSION");
println!();
println!(
" {} {}",
style::bold("quasar"),
style::dim(&format!("v{v}"))
);
println!(
" {}",
style::dim("Build programs that execute at the speed of light")
);
println!();
println!(" {}", style::bold("Commands:"));
print_cmd(
"init [name] [-y] [--no-git] [--template]",
"Scaffold a new project",
);
print_cmd(
"add [-i name] [-s name] [-e name]",
"Add instructions, state, errors",
);
print_cmd(
"build [--debug] [-w] [--features]",
"Compile the on-chain program",
);
print_cmd(
"test [--debug] [-f] [-w] [--features]",
"Run the test suite",
);
print_cmd(
"deploy [-u url] [-k keypair] [--skip-build]",
"Deploy to a cluster",
);
print_cmd("clean [-a]", "Remove build artifacts");
print_cmd("config [get|set|list|reset]", "Manage global settings");
print_cmd("idl <path>", "Generate the program IDL");
print_cmd(
"profile [elf] [--expand] [--diff] [-w]",
"Measure compute-unit usage",
);
print_cmd("dump [elf] [-f] [-S]", "Dump sBPF assembly");
println!();
println!(" {}", style::bold("Options:"));
print_cmd("-h, --help", "Print help");
print_cmd("-V, --version", "Print version");
println!();
println!(
" Run {} for details on any command.",
style::bold("quasar <command> --help")
);
println!();
}
fn print_cmd(cmd: &str, desc: &str) {
println!(" {} {}", style::color(45, &format!("{cmd:<34}")), desc);
}
fn profile_watch(expand: bool) -> CliResult {
fn profile_once(expand: bool) {
match build::profile_build() {
Ok(elf) => {
quasar_profile::run(quasar_profile::ProfileCommand {
elf_path: Some(elf),
diff_program: None,
share: false,
expand,
});
}
Err(e) => {
eprintln!(" {}", style::fail(&format!("{e}")));
}
}
}
profile_once(expand);
loop {
let baseline = build::collect_mtimes(std::path::Path::new("src"));
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
let current = build::collect_mtimes(std::path::Path::new("src"));
if current != baseline {
profile_once(expand);
break;
}
}
}
}