ic_cdk_bindgen/
lib.rs

1use candid_parser::{bindings::rust, pretty_check_file, Principal};
2use std::env;
3use std::fs;
4use std::io::Write;
5use std::path::PathBuf;
6
7#[derive(Clone)]
8pub struct Config {
9    pub canister_name: String,
10    pub candid_path: PathBuf,
11    pub skip_existing_files: bool,
12    pub binding: rust::Config,
13}
14
15impl Config {
16    pub fn new(canister_name: &str) -> Self {
17        let (candid_path, canister_id) = resolve_candid_path_and_canister_id(canister_name);
18        let mut binding = rust::Config::new();
19        binding
20            // User will depend on candid crate directly
21            .set_candid_crate("candid".to_string())
22            .set_canister_id(canister_id)
23            .set_service_name(canister_name.to_string())
24            .set_target(rust::Target::CanisterCall);
25
26        Config {
27            canister_name: canister_name.to_string(),
28            candid_path,
29            skip_existing_files: false,
30            binding,
31        }
32    }
33}
34
35/// Resolve the candid path and canister id from environment variables.
36///
37/// The name and format of the environment variables are standardized:
38/// https://github.com/dfinity/sdk/blob/master/docs/cli-reference/dfx-envars.md#canister_id_canistername
39///
40/// We previously used environment variables like`CANISTER_CANDID_PATH_<canister_name>` without to_uppercase.
41/// That is deprecated. To keep backward compatibility, we also check for the old format.
42/// Just in case the user run `ic-cdk-bindgen` outside `dfx`.
43/// If the old format is found, we print a warning to the user.
44/// dfx v0.13.0 only provides the old format, which can be used to check the warning logic.
45/// TODO: remove the support for the old format, in the next major release (v0.2) of `ic-cdk-bindgen`.
46fn resolve_candid_path_and_canister_id(canister_name: &str) -> (PathBuf, Principal) {
47    fn warning_deprecated_env(deprecated_name: &str, new_name: &str) {
48        println!("cargo:warning=The environment variable {} is deprecated. Please set {} instead. Upgrading dfx may fix this issue.", deprecated_name, new_name);
49    }
50
51    let canister_name = canister_name.replace('-', "_");
52    let canister_name_upper = canister_name.to_uppercase();
53
54    let candid_path_var_name = format!("CANISTER_CANDID_PATH_{}", canister_name_upper);
55    let candid_path_var_name_legacy = format!("CANISTER_CANDID_PATH_{}", canister_name);
56
57    let candid_path_str = if let Ok(candid_path_str) = env::var(&candid_path_var_name) {
58        candid_path_str
59    } else if let Ok(candid_path_str) = env::var(&candid_path_var_name_legacy) {
60        warning_deprecated_env(&candid_path_var_name_legacy, &candid_path_var_name);
61        candid_path_str
62    } else {
63        panic!(
64            "Cannot find environment variable: {}",
65            &candid_path_var_name
66        );
67    };
68    let candid_path = PathBuf::from(candid_path_str);
69
70    let canister_id_var_name = format!("CANISTER_ID_{}", canister_name_upper);
71    let canister_id_var_name_legacy = format!("CANISTER_ID_{}", canister_name);
72    let canister_id_str = if let Ok(canister_id_str) = env::var(&canister_id_var_name) {
73        canister_id_str
74    } else if let Ok(canister_id_str) = env::var(&canister_id_var_name_legacy) {
75        warning_deprecated_env(&canister_id_var_name_legacy, &canister_id_var_name);
76        canister_id_str
77    } else {
78        panic!(
79            "Cannot find environment variable: {}",
80            &canister_id_var_name
81        );
82    };
83    let canister_id = Principal::from_text(&canister_id_str)
84        .unwrap_or_else(|_| panic!("Invalid principal: {}", &canister_id_str));
85
86    (candid_path, canister_id)
87}
88
89#[derive(Default)]
90pub struct Builder {
91    configs: Vec<Config>,
92}
93
94impl Builder {
95    pub fn new() -> Self {
96        Builder {
97            configs: Vec::new(),
98        }
99    }
100    pub fn add(&mut self, config: Config) -> &mut Self {
101        self.configs.push(config);
102        self
103    }
104    pub fn build(self, out_path: Option<PathBuf>) {
105        let out_path = out_path.unwrap_or_else(|| {
106            let manifest_dir =
107                PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("Cannot find manifest dir"));
108            manifest_dir.join("src").join("declarations")
109        });
110        fs::create_dir_all(&out_path).unwrap();
111        for conf in self.configs.iter() {
112            let (env, actor) =
113                pretty_check_file(&conf.candid_path).expect("Cannot parse candid file");
114            let content = rust::compile(&conf.binding, &env, &actor);
115            let generated_path = out_path.join(format!("{}.rs", conf.canister_name));
116            if !(conf.skip_existing_files && generated_path.exists()) {
117                fs::write(generated_path, content).expect("Cannot store generated binding");
118            }
119        }
120        let mut module = fs::File::create(out_path.join("mod.rs")).unwrap();
121        module.write_all(b"#![allow(unused_imports)]\n").unwrap();
122        module
123            .write_all(b"#![allow(non_upper_case_globals)]\n")
124            .unwrap();
125        module.write_all(b"#![allow(non_snake_case)]\n").unwrap();
126        for conf in self.configs.iter() {
127            module.write_all(b"#[rustfmt::skip]\n").unwrap(); // so that we get a better diff
128            let line = format!("pub mod {};\n", conf.canister_name);
129            module.write_all(line.as_bytes()).unwrap();
130        }
131    }
132}