use candid::Principal;
use candid_parser::pretty_check_file;
use std::env;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
pub mod code_generator;
#[derive(Clone)]
pub struct Config {
pub canister_name: String,
pub candid_path: PathBuf,
pub skip_existing_files: bool,
pub binding: code_generator::Config,
}
impl Config {
pub fn new(canister_name: &str) -> Self {
let (candid_path, canister_id) = resolve_candid_path_and_canister_id(canister_name);
let mut binding = code_generator::Config::new();
binding
.set_candid_crate("candid".to_string())
.set_canister_id(canister_id)
.set_service_name(canister_name.to_string())
.set_target(code_generator::Target::CanisterCall);
Config {
canister_name: canister_name.to_string(),
candid_path,
skip_existing_files: false,
binding,
}
}
}
fn resolve_candid_path_and_canister_id(canister_name: &str) -> (PathBuf, Principal) {
fn warning_deprecated_env(deprecated_name: &str, new_name: &str) {
println!("cargo:warning=The environment variable {deprecated_name} is deprecated. Please set {new_name} instead. Upgrading dfx may fix this issue.");
}
let canister_name = canister_name.replace('-', "_");
let canister_name_upper = canister_name.to_uppercase();
let candid_path_var_name = format!("CANISTER_CANDID_PATH_{canister_name_upper}");
let candid_path_var_name_legacy = format!("CANISTER_CANDID_PATH_{canister_name}");
println!("cargo:rerun-if-env-changed={candid_path_var_name}");
println!("cargo:rerun-if-env-changed={candid_path_var_name_legacy}");
let candid_path_str = if let Ok(candid_path_str) = env::var(&candid_path_var_name) {
candid_path_str
} else if let Ok(candid_path_str) = env::var(&candid_path_var_name_legacy) {
warning_deprecated_env(&candid_path_var_name_legacy, &candid_path_var_name);
candid_path_str
} else {
panic!(
"Cannot find environment variable: {}",
&candid_path_var_name
);
};
let candid_path = PathBuf::from(candid_path_str);
let canister_id_var_name = format!("CANISTER_ID_{canister_name_upper}");
let canister_id_var_name_legacy = format!("CANISTER_ID_{canister_name}");
println!("cargo:rerun-if-env-changed={canister_id_var_name}");
println!("cargo:rerun-if-env-changed={canister_id_var_name_legacy}");
let canister_id_str = if let Ok(canister_id_str) = env::var(&canister_id_var_name) {
canister_id_str
} else if let Ok(canister_id_str) = env::var(&canister_id_var_name_legacy) {
warning_deprecated_env(&canister_id_var_name_legacy, &canister_id_var_name);
canister_id_str
} else {
panic!(
"Cannot find environment variable: {}",
&canister_id_var_name
);
};
let canister_id = Principal::from_text(&canister_id_str)
.unwrap_or_else(|_| panic!("Invalid principal: {}", &canister_id_str));
(candid_path, canister_id)
}
#[derive(Default)]
pub struct Builder {
configs: Vec<Config>,
}
impl Builder {
pub fn new() -> Self {
Builder {
configs: Vec::new(),
}
}
pub fn add(&mut self, config: Config) -> &mut Self {
self.configs.push(config);
self
}
pub fn build(self, out_path: Option<PathBuf>) {
let out_path = out_path.unwrap_or_else(|| {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("Cannot find manifest dir"));
manifest_dir.join("src").join("declarations")
});
fs::create_dir_all(&out_path).unwrap();
for conf in self.configs.iter() {
let (env, actor, _) =
pretty_check_file(&conf.candid_path).expect("Cannot parse candid file");
let content = code_generator::compile(&conf.binding, &env, &actor);
let generated_path = out_path.join(format!("{}.rs", conf.canister_name));
if !(conf.skip_existing_files && generated_path.exists()) {
fs::write(generated_path, content).expect("Cannot store generated binding");
}
}
let mut module = fs::File::create(out_path.join("mod.rs")).unwrap();
module.write_all(b"#![allow(unused_imports)]\n").unwrap();
module
.write_all(b"#![allow(non_upper_case_globals)]\n")
.unwrap();
module.write_all(b"#![allow(non_snake_case)]\n").unwrap();
for conf in self.configs.iter() {
module.write_all(b"#[rustfmt::skip]\n").unwrap(); let line = format!("pub mod {};\n", conf.canister_name);
module.write_all(line.as_bytes()).unwrap();
}
}
}