Skip to main content

wf_cdk_bindgen/
lib.rs

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            // User will depend on candid crate directly
24            .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
38/// Resolve the candid path and canister id from environment variables.
39///
40/// The name and format of the environment variables are standardized:
41/// https://github.com/dfinity/sdk/blob/master/docs/cli-reference/dfx-envars.md#canister_id_canistername
42///
43/// We previously used environment variables like`CANISTER_CANDID_PATH_<canister_name>` without to_uppercase.
44/// That is deprecated. To keep backward compatibility, we also check for the old format.
45/// Just in case the user run `ic-cdk-bindgen` outside `dfx`.
46/// If the old format is found, we print a warning to the user.
47/// dfx v0.13.0 only provides the old format, which can be used to check the warning logic.
48/// TODO: remove the support for the old format, in the next major release (v0.2) of `ic-cdk-bindgen`.
49fn 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(); // so that we get a better diff
135            let line = format!("pub mod {};\n", conf.canister_name);
136            module.write_all(line.as_bytes()).unwrap();
137        }
138    }
139}