1use candid::Principal;
2use candid_parser::pretty_check_file;
3use std::env;
4use std::fs;
5use std::io::Write;
6use std::path::PathBuf;
7
8pub mod code_generator;
9
10#[derive(Clone)]
11pub struct Config {
12 pub canister_name: String,
13 pub candid_path: PathBuf,
14 pub skip_existing_files: bool,
15 pub binding: code_generator::Config,
16}
17
18impl Config {
19 pub fn new(canister_name: &str) -> Self {
20 let (candid_path, canister_id) = resolve_candid_path_and_canister_id(canister_name);
21 let mut binding = code_generator::Config::new();
22 binding
23 .set_candid_crate("candid".to_string())
25 .set_canister_id(canister_id)
26 .set_service_name(canister_name.to_string())
27 .set_target(code_generator::Target::CanisterCall);
28
29 Config {
30 canister_name: canister_name.to_string(),
31 candid_path,
32 skip_existing_files: false,
33 binding,
34 }
35 }
36}
37
38fn resolve_candid_path_and_canister_id(canister_name: &str) -> (PathBuf, Principal) {
50 fn warning_deprecated_env(deprecated_name: &str, new_name: &str) {
51 println!("cargo:warning=The environment variable {deprecated_name} is deprecated. Please set {new_name} instead. Upgrading dfx may fix this issue.");
52 }
53
54 let canister_name = canister_name.replace('-', "_");
55 let canister_name_upper = canister_name.to_uppercase();
56
57 let candid_path_var_name = format!("CANISTER_CANDID_PATH_{canister_name_upper}");
58 let candid_path_var_name_legacy = format!("CANISTER_CANDID_PATH_{canister_name}");
59 println!("cargo:rerun-if-env-changed={candid_path_var_name}");
60 println!("cargo:rerun-if-env-changed={candid_path_var_name_legacy}");
61
62 let candid_path_str = if let Ok(candid_path_str) = env::var(&candid_path_var_name) {
63 candid_path_str
64 } else if let Ok(candid_path_str) = env::var(&candid_path_var_name_legacy) {
65 warning_deprecated_env(&candid_path_var_name_legacy, &candid_path_var_name);
66 candid_path_str
67 } else {
68 panic!(
69 "Cannot find environment variable: {}",
70 &candid_path_var_name
71 );
72 };
73 let candid_path = PathBuf::from(candid_path_str);
74
75 let canister_id_var_name = format!("CANISTER_ID_{canister_name_upper}");
76 let canister_id_var_name_legacy = format!("CANISTER_ID_{canister_name}");
77 println!("cargo:rerun-if-env-changed={canister_id_var_name}");
78 println!("cargo:rerun-if-env-changed={canister_id_var_name_legacy}");
79 let canister_id_str = if let Ok(canister_id_str) = env::var(&canister_id_var_name) {
80 canister_id_str
81 } else if let Ok(canister_id_str) = env::var(&canister_id_var_name_legacy) {
82 warning_deprecated_env(&canister_id_var_name_legacy, &canister_id_var_name);
83 canister_id_str
84 } else {
85 panic!(
86 "Cannot find environment variable: {}",
87 &canister_id_var_name
88 );
89 };
90 let canister_id = Principal::from_text(&canister_id_str)
91 .unwrap_or_else(|_| panic!("Invalid principal: {}", &canister_id_str));
92
93 (candid_path, canister_id)
94}
95
96#[derive(Default)]
97pub struct Builder {
98 configs: Vec<Config>,
99}
100
101impl Builder {
102 pub fn new() -> Self {
103 Builder {
104 configs: Vec::new(),
105 }
106 }
107 pub fn add(&mut self, config: Config) -> &mut Self {
108 self.configs.push(config);
109 self
110 }
111 pub fn build(self, out_path: Option<PathBuf>) {
112 let out_path = out_path.unwrap_or_else(|| {
113 let manifest_dir =
114 PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("Cannot find manifest dir"));
115 manifest_dir.join("src").join("declarations")
116 });
117 fs::create_dir_all(&out_path).unwrap();
118 for conf in self.configs.iter() {
119 let (env, actor, _) =
120 pretty_check_file(&conf.candid_path).expect("Cannot parse candid file");
121 let content = code_generator::compile(&conf.binding, &env, &actor);
122 let generated_path = out_path.join(format!("{}.rs", conf.canister_name));
123 if !(conf.skip_existing_files && generated_path.exists()) {
124 fs::write(generated_path, content).expect("Cannot store generated binding");
125 }
126 }
127 let mut module = fs::File::create(out_path.join("mod.rs")).unwrap();
128 module.write_all(b"#![allow(unused_imports)]\n").unwrap();
129 module
130 .write_all(b"#![allow(non_upper_case_globals)]\n")
131 .unwrap();
132 module.write_all(b"#![allow(non_snake_case)]\n").unwrap();
133 for conf in self.configs.iter() {
134 module.write_all(b"#[rustfmt::skip]\n").unwrap(); let line = format!("pub mod {};\n", conf.canister_name);
136 module.write_all(line.as_bytes()).unwrap();
137 }
138 }
139}