use crate::{
args::{first_arg_is_help, first_arg_is_version, parse_matches},
version_text,
};
use canic_host::canister_build::{
CanisterBuildProfile, build_current_workspace_canister_artifact,
print_current_workspace_build_context_once,
};
use clap::{Arg, Command as ClapCommand};
use std::{ffi::OsString, time::Instant};
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum BuildCommandError {
#[error("{0}")]
Usage(&'static str),
#[error(transparent)]
Build(#[from] Box<dyn std::error::Error>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BuildOptions {
pub canister_name: String,
}
impl BuildOptions {
pub fn parse<I>(args: I) -> Result<Self, BuildCommandError>
where
I: IntoIterator<Item = OsString>,
{
let matches =
parse_matches(build_command(), args).map_err(|_| BuildCommandError::Usage(usage()))?;
let canister_name = matches
.get_one::<String>("canister-name")
.expect("clap requires canister-name")
.clone();
Ok(Self { canister_name })
}
}
fn build_command() -> ClapCommand {
ClapCommand::new("build")
.disable_help_flag(true)
.arg(Arg::new("canister-name").required(true))
}
pub fn run<I>(args: I) -> Result<(), BuildCommandError>
where
I: IntoIterator<Item = OsString>,
{
let args = args.into_iter().collect::<Vec<_>>();
if first_arg_is_help(&args) {
println!("{}", usage());
return Ok(());
}
if first_arg_is_version(&args) {
println!("{}", version_text());
return Ok(());
}
let options = BuildOptions::parse(args)?;
build_canister(options).map_err(BuildCommandError::from)
}
fn build_canister(options: BuildOptions) -> Result<(), Box<dyn std::error::Error>> {
let profile = CanisterBuildProfile::current();
print_current_workspace_build_context_once(profile)?;
eprintln!(
"Canic build start: canister={} profile={}",
options.canister_name,
profile.target_dir_name()
);
let started_at = Instant::now();
let output = build_current_workspace_canister_artifact(&options.canister_name, profile)?;
let elapsed = started_at.elapsed().as_secs_f64();
println!("{}", output.wasm_gz_path.display());
eprintln!(
"Canic build done: canister={} elapsed={elapsed:.2}s",
options.canister_name
);
eprintln!();
Ok(())
}
const fn usage() -> &'static str {
"usage: canic build <canister-name>"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_build_canister_name() {
let options = BuildOptions::parse([OsString::from("root")]).expect("parse build");
assert_eq!(options.canister_name, "root");
}
#[test]
fn rejects_missing_build_canister_name() {
assert!(matches!(
BuildOptions::parse([]),
Err(BuildCommandError::Usage(_))
));
}
}