diff --git a/Cargo.lock b/Cargo.lock index e54f5b3235..c88d0595dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,6 +652,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +[[package]] +name = "byte-unit" +version = "4.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ebf10dda65f19ff0f42ea15572a359ed60d7fc74fdc984d90310937be0014b" +dependencies = [ + "serde", + "utf8-width", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -1042,6 +1052,7 @@ dependencies = [ "argon2", "atty", "base64", + "byte-unit", "candid", "clap", "console", @@ -1056,7 +1067,8 @@ dependencies = [ "futures", "garcon", "hex", - "humanize-rs", + "humantime", + "humantime-serde", "ic-agent", "ic-asset", "ic-identity-hsm", @@ -1727,12 +1739,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humanize-rs" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016b02deb8b0c415d8d56a6f0ab265e50c22df61194e37f9be75ed3a722de8a6" - [[package]] name = "humansize" version = "1.1.1" @@ -1745,6 +1751,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" version = "0.14.19" @@ -3938,6 +3954,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/e2e/assets/error_context/dfx.json b/e2e/assets/error_context/dfx.json index 0232a68fb5..0c944153f7 100644 --- a/e2e/assets/error_context/dfx.json +++ b/e2e/assets/error_context/dfx.json @@ -9,7 +9,8 @@ "main": "main.mo" }, "npm_missing": { - "type": "assets" + "type": "assets", + "source": [] }, "asset_bad_source_path": { "type": "assets", diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index 2fe5a4f097..4d7c21c851 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -118,10 +118,16 @@ teardown() { @test "build fails if canister type is not supported" { dfx_start - dfx config canisters.e2e_project_backend.type unknown_canister_type dfx canister create --all + # shellcheck disable=SC2094 + cat <<<"$(jq '.canisters.e2e_project.type="unknown_canister_type"' dfx.json)" >dfx.json assert_command_fail dfx build - assert_match "Cannot find builder for canister" + # shellcheck disable=SC2016 + assert_match 'unknown variant `unknown_canister_type`' + + # If canister type is invalid, `dfx stop` fails + # shellcheck disable=SC2094 + cat <<<"$(jq '.canisters.e2e_project.type="motoko"' dfx.json)" >dfx.json } @test "can build a custom canister type" { diff --git a/e2e/tests-dfx/config.bash b/e2e/tests-dfx/config.bash index fb1c32403c..c5e9018482 100644 --- a/e2e/tests-dfx/config.bash +++ b/e2e/tests-dfx/config.bash @@ -5,7 +5,7 @@ load ../utils/_ setup() { standard_setup - dfx_new + dfx_new_rust } teardown() { @@ -18,15 +18,15 @@ teardown() { assert_command dfx config canisters.e2e_project_backend.type # shellcheck disable=SC2154 - assert_eq '"motoko"' "$stdout" + assert_eq '"rust"' "$stdout" - assert_command dfx config canisters.e2e_project_backend.type "rust" + assert_command dfx config canisters.e2e_project_backend.type "motoko" # shellcheck disable=SC2154 assert_eq "" "$stdout" assert_command dfx config canisters.e2e_project_backend.type # shellcheck disable=SC2154 - assert_eq '"rust"' "$stdout" + assert_eq '"motoko"' "$stdout" assert_command_fail dfx config non_existent diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 717fdd2646..6c680418bf 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -28,6 +28,7 @@ anyhow = "1.0.56" argon2 = "0.4.0" atty = "0.2.13" base64 = "0.13.0" +byte-unit = { version = "4.0.14", features = ["serde"] } candid = { version = "0.7.10", features = [ "random" ] } clap = { version = "3.1.6", features = [ "derive" ] } console = "0.15.0" @@ -41,6 +42,8 @@ fn-error-context = "0.2.0" futures = "0.3.21" garcon = { version = "0.2", features = ["async"] } hex = {version = "0.4.2", features = ["serde"] } +humantime = "2.1.0" +humantime-serde = "1.1.1" ic-types = "0.3.0" ic-wasm = { version = "0.1.3", default-features = false, features = ["optimize"]} indicatif = "0.16.0" @@ -48,7 +51,6 @@ itertools = "0.10.3" lazy-init = "0.5.0" lazy_static = "1.4.0" libflate = "1.1.2" -humanize-rs = "0.1.5" mime = "0.3.16" mime_guess = "2.0.4" mockall = "0.11.0" diff --git a/src/dfx/src/commands/canister/sign.rs b/src/dfx/src/commands/canister/sign.rs index c308d667f1..f6182ca40a 100644 --- a/src/dfx/src/commands/canister/sign.rs +++ b/src/dfx/src/commands/canister/sign.rs @@ -15,7 +15,6 @@ use ic_types::principal::Principal; use anyhow::{anyhow, bail, Context}; use clap::Parser; -use humanize_rs::duration; use slog::info; use time::OffsetDateTime; @@ -130,7 +129,7 @@ pub async fn exec( .get_selected_identity_principal() .expect("Selected identity not instantiated."); - let timeout = duration::parse(&opts.expire_after) + let timeout = humantime::parse_duration(&opts.expire_after) .map_err(|_| anyhow!("Cannot parse expire_after as a duration (e.g. `1h`, `1h 30m`)"))?; //let timeout = Duration::from_secs(opts.expire_after); let expiration_system_time = SystemTime::now() diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index c35ce68349..6b5daea9aa 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -163,14 +163,13 @@ fn display_urls(env: &dyn Environment) -> DfxResult { }; if let Some(canister_id) = canister_id { let canister_info = CanisterInfo::load(&config, canister_name, Some(canister_id))?; - let is_frontend = canister_config.extras.get("frontend").is_some(); - if is_frontend { + if canister_config.frontend.is_some() { let url = construct_frontend_url(network, &canister_id)?; frontend_urls.insert(canister_name, url); } - if canister_info.get_type() != "assets" { + if !canister_info.is_assets() { let url = construct_ui_canister_url(network, &canister_id, ui_canister_id)?; if let Some(ui_canister_url) = url { candid_urls.insert(canister_name, ui_canister_url); diff --git a/src/dfx/src/commands/generate.rs b/src/dfx/src/commands/generate.rs index d7c5652f70..921da62271 100644 --- a/src/dfx/src/commands/generate.rs +++ b/src/dfx/src/commands/generate.rs @@ -41,7 +41,7 @@ pub fn exec(env: &dyn Environment, opts: GenerateOpts) -> DfxResult { let canister_name = canister.get_name(); let canister_id = store.get(canister_name)?; if let Some(info) = canister_pool.get_canister_info(&canister_id) { - if info.get_type() == "motoko" { + if info.is_motoko() { build_before_generate = true; } } diff --git a/src/dfx/src/commands/language_service.rs b/src/dfx/src/commands/language_service.rs index dadf2e59c3..8d1e60f767 100644 --- a/src/dfx/src/commands/language_service.rs +++ b/src/dfx/src/commands/language_service.rs @@ -7,6 +7,7 @@ use crate::lib::package_arguments::{self, PackageArguments}; use anyhow::{anyhow, bail, Context}; use clap::Parser; use fn_error_context::context; +use std::path::PathBuf; use std::process::Stdio; const CANISTER_ARG: &str = "canister"; @@ -47,7 +48,7 @@ pub fn exec(env: &dyn Environment, opts: LanguageServiceOpts) -> DfxResult { } #[context("Failed to determine main path.")] -fn get_main_path(config: &ConfigInterface, canister_name: Option) -> DfxResult { +fn get_main_path(config: &ConfigInterface, canister_name: Option) -> DfxResult { // TODO try and point at the actual dfx.json path let dfx_json = CONFIG_FILE_NAME; @@ -80,24 +81,20 @@ fn get_main_path(config: &ConfigInterface, canister_name: Option) -> Dfx } } }?; - - canister - .extras - .get("main") - .and_then(|v| v.as_str()) - .ok_or_else(|| { - error_invalid_data!( - "Canister {0} lacks a 'main' element in {1}", - canister_name, - dfx_json - ) - }) - .map(|s| s.to_owned()) + if let Some(main) = canister.main { + Ok(main) + } else { + Err(error_invalid_data!( + "Canister {0} lacks a 'main' element in {1}", + canister_name, + dfx_json + )) + } } fn run_ide( env: &dyn Environment, - main_path: String, + main_path: PathBuf, package_arguments: PackageArguments, ) -> DfxResult { let output = env diff --git a/src/dfx/src/commands/remote/generate_binding.rs b/src/dfx/src/commands/remote/generate_binding.rs index e8a3aede0a..008f307349 100644 --- a/src/dfx/src/commands/remote/generate_binding.rs +++ b/src/dfx/src/commands/remote/generate_binding.rs @@ -7,7 +7,6 @@ use crate::util::check_candid_file; use anyhow::Context; use clap::Parser; use slog::info; -use std::path::Path; /// Generate bindings for remote canisters from their .did declarations #[derive(Parser)] @@ -43,11 +42,9 @@ pub fn exec(env: &dyn Environment, opts: GenerateBindingOpts) -> DfxResult { for canister in canister_pool.get_canister_list() { let info = canister.get_info(); if let Some(candid) = info.get_remote_candid() { - let main_optional: Option = info.get_extra_optional("main")?; + let main_optional = info.get_main_file(); if let Some(main) = main_optional { - let main_path = Path::new(&main); - let candid_path = Path::new(&candid); - if !candid_path.exists() { + if !candid.exists() { info!( log, "Candid file {} for canister {} does not exist. Skipping.", @@ -56,55 +53,53 @@ pub fn exec(env: &dyn Environment, opts: GenerateBindingOpts) -> DfxResult { ); continue; } - if main_path.exists() { + if main.exists() { if opts.overwrite { info!( log, "Overwriting main file {} of canister {}.", - main, + main.display(), canister.get_name() ); } else { info!( log, "Main file {} of canister {} already exists. Skipping. Use --overwrite if you want to re-create it.", - main, + main.display(), canister.get_name() ); continue; } } - let (type_env, did_types) = check_candid_file(candid_path)?; - let bindings = if main.ends_with(&".mo") { + let (type_env, did_types) = check_candid_file(&candid)?; + let extension = main.extension().unwrap_or_default(); + let bindings = if extension == "mo" { Some(candid::bindings::motoko::compile(&type_env, &did_types)) - } else if main.ends_with(&".rs") { + } else if extension == "rs" { Some(candid::bindings::rust::compile(&type_env, &did_types)) - } else if main.ends_with(&".js") { + } else if extension == "js" { Some(candid::bindings::javascript::compile(&type_env, &did_types)) - } else if main.ends_with(&".ts") { + } else if extension == "ts" { Some(candid::bindings::typescript::compile(&type_env, &did_types)) } else { info!( log, "Unsupported filetype found in {}.main: {}. Use one of the following: .mo, .rs, .js, .ts", canister.get_name(), - main + main.display() ); None }; if let Some(bindings_string) = bindings { - std::fs::write(&main_path, &bindings_string).with_context(|| { - format!( - "Failed to write bindings to {}.", - main_path.to_string_lossy() - ) + std::fs::write(&main, &bindings_string).with_context(|| { + format!("Failed to write bindings to {}.", main.display()) })?; info!( log, "Generated {} using {} for canister {}.", - main, - candid.to_string_lossy(), + main.display(), + candid.display(), canister.get_name() ) } diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 91fe914e7f..4da122c0ab 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -1,18 +1,22 @@ #![allow(dead_code)] use crate::lib::bitcoin::adapter::config::BitcoinAdapterLogLevel; use crate::lib::error::{BuildError, DfxError, DfxResult}; -use crate::util::SerdeVec; +use crate::util::{PossiblyStr, SerdeVec}; use crate::{error_invalid_argument, error_invalid_config, error_invalid_data}; use anyhow::{anyhow, Context}; +use byte_unit::Byte; use fn_error_context::context; use ic_types::Principal; -use serde::{Deserialize, Serialize}; +use serde::de::{Error as _, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use std::collections::{BTreeMap, HashSet}; use std::default::Default; +use std::fmt; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; +use std::time::Duration; pub const CONFIG_FILE_NAME: &str = "dfx.json"; @@ -64,21 +68,68 @@ pub const DEFAULT_IC_GATEWAY_TRAILING_SLASH: &str = "https://ic0.app/"; /// A Canister configuration in the dfx.json config file. /// It only contains a type; everything else should be infered using the /// CanisterInfo type. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ConfigCanistersCanister { - pub r#type: Option, - #[serde(default)] pub declarations: CanisterDeclarationsConfig, #[serde(default)] pub remote: Option, + pub args: Option, + #[serde(default)] - pub post_install: SerdeVec, + pub initialization_values: InitializationValues, + + #[serde(default)] + pub dependencies: Vec, + + pub frontend: Option>, #[serde(flatten)] - pub extras: BTreeMap, + pub type_specific: CanisterTypeProperties, + + #[serde(default)] + pub post_install: SerdeVec, + pub main: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum CanisterTypeProperties { + Rust { + package: String, + candid: PathBuf, + }, + Assets { + source: Vec, + }, + Custom { + wasm: PathBuf, + candid: PathBuf, + build: SerdeVec, + }, + Motoko, +} + +impl CanisterTypeProperties { + pub fn name(&self) -> &'static str { + match self { + Self::Rust { .. } => "rust", + Self::Motoko { .. } => "motoko", + Self::Assets { .. } => "assets", + Self::Custom { .. } => "custom", + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(default)] +pub struct InitializationValues { + pub compute_allocation: Option>, + pub memory_allocation: Option, + #[serde(with = "humantime_serde")] + pub freezing_threshold: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -99,7 +150,7 @@ pub struct CanisterDeclarationsConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct ConfigDefaultsBitcoin { - #[serde(default = "default_as_false")] + #[serde(default)] pub enabled: bool, /// Addresses of nodes to connect to (in case discovery from seeds is not possible/sufficient) @@ -113,7 +164,7 @@ pub struct ConfigDefaultsBitcoin { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ConfigDefaultsCanisterHttp { - #[serde(default = "default_as_false")] + #[serde(default)] pub enabled: bool, } @@ -383,40 +434,40 @@ impl ConfigInterface { } #[context("Failed to get compute allocation for '{}'.", canister_name)] - pub fn get_compute_allocation(&self, canister_name: &str) -> DfxResult> { - self.get_initialization_value(canister_name, "compute_allocation") + pub fn get_compute_allocation(&self, canister_name: &str) -> DfxResult> { + Ok(self + .get_canister_config(canister_name)? + .initialization_values + .compute_allocation + .map(|x| x.0)) } #[context("Failed to get memory allocation for '{}'.", canister_name)] - pub fn get_memory_allocation(&self, canister_name: &str) -> DfxResult> { - self.get_initialization_value(canister_name, "memory_allocation") + pub fn get_memory_allocation(&self, canister_name: &str) -> DfxResult> { + Ok(self + .get_canister_config(canister_name)? + .initialization_values + .memory_allocation) } #[context("Failed to get freezing threshold for '{}'.", canister_name)] - pub fn get_freezing_threshold(&self, canister_name: &str) -> DfxResult> { - self.get_initialization_value(canister_name, "freezing_threshold") + pub fn get_freezing_threshold(&self, canister_name: &str) -> DfxResult> { + Ok(self + .get_canister_config(canister_name)? + .initialization_values + .freezing_threshold) } - fn get_initialization_value( - &self, - canister_name: &str, - field: &str, - ) -> DfxResult> { - let canister_map = (&self.canisters) + fn get_canister_config(&self, canister_name: &str) -> DfxResult<&ConfigCanistersCanister> { + let canister_map = self + .canisters .as_ref() .ok_or_else(|| error_invalid_config!("No canisters in the configuration file."))?; let canister_config = canister_map .get(canister_name) - .ok_or_else(|| anyhow!("Cannot find canister '{}'.", canister_name))?; - - canister_config - .extras - .get("initialization_values") - .and_then(|v| v.get(field)) - .map(String::deserialize) - .transpose() - .map_err(|_| error_invalid_config!("Field {} is of the wrong type", field)) + .with_context(|| format!("Cannot find canister '{canister_name}'."))?; + Ok(canister_config) } } @@ -445,16 +496,10 @@ fn add_dependencies( .get(canister_name) .ok_or_else(|| anyhow!("Cannot find canister '{}'.", canister_name))?; - let deps = match canister_config.extras.get("dependencies") { - None => vec![], - Some(v) => Vec::::deserialize(v) - .map_err(|_| error_invalid_config!("Field 'dependencies' is of the wrong type"))?, - }; - path.push(String::from(canister_name)); - for canister in deps { - add_dependencies(all_canisters, names, path, &canister)?; + for canister in &canister_config.dependencies { + add_dependencies(all_canisters, names, path, canister)?; } path.pop(); @@ -568,6 +613,66 @@ impl Config { } } +// grumble grumble https://github.com/serde-rs/serde/issues/2231 +impl<'de> Deserialize<'de> for CanisterTypeProperties { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(PropertiesVisitor) + } +} + +struct PropertiesVisitor; + +impl<'de> Visitor<'de> for PropertiesVisitor { + type Value = CanisterTypeProperties; + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("canister type metadata") + } + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let missing_field = A::Error::missing_field; + let (mut package, mut source, mut candid, mut build, mut wasm, mut r#type) = + (None, None, None, None, None, None); + while let Some(key) = map.next_key::()? { + match &*key { + "package" => package = Some(map.next_value()?), + "source" => source = Some(map.next_value()?), + "candid" => candid = Some(map.next_value()?), + "build" => build = Some(map.next_value()?), + "wasm" => wasm = Some(map.next_value()?), + "type" => r#type = Some(map.next_value::()?), + _ => continue, + } + } + let props = match r#type.as_deref() { + Some("motoko") | None => CanisterTypeProperties::Motoko, + Some("rust") => CanisterTypeProperties::Rust { + candid: candid.ok_or_else(|| missing_field("candid"))?, + package: package.ok_or_else(|| missing_field("package"))?, + }, + Some("assets") => CanisterTypeProperties::Assets { + source: source.ok_or_else(|| missing_field("source"))?, + }, + Some("custom") => CanisterTypeProperties::Custom { + build: build.ok_or_else(|| missing_field("build"))?, + candid: candid.ok_or_else(|| missing_field("candid"))?, + wasm: wasm.ok_or_else(|| missing_field("wasm"))?, + }, + Some(x) => { + return Err(A::Error::unknown_variant( + x, + &["motoko", "rust", "assets", "custom"], + )) + } + }; + Ok(props) + } +} + #[cfg(test)] mod tests { use super::*; @@ -738,13 +843,13 @@ mod tests { .get_compute_allocation("test_project") .unwrap() .unwrap(); - assert_eq!("100", compute_allocation); + assert_eq!(100, compute_allocation); let memory_allocation = config_interface .get_memory_allocation("test_project") .unwrap() .unwrap(); - assert_eq!("8GB", memory_allocation); + assert_eq!("8GB".parse::().unwrap(), memory_allocation); let config_no_values = Config::from_str( r#"{ diff --git a/src/dfx/src/lib/builders/assets.rs b/src/dfx/src/lib/builders/assets.rs index 666d955d04..fdf5be2c8b 100644 --- a/src/dfx/src/lib/builders/assets.rs +++ b/src/dfx/src/lib/builders/assets.rs @@ -13,7 +13,6 @@ use crate::util; use anyhow::{anyhow, Context}; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; -use serde::Deserialize; use std::fs; use std::path::Path; use std::sync::Arc; @@ -27,12 +26,7 @@ struct AssetsBuilderExtra { impl AssetsBuilderExtra { #[context("Failed to create AssetBuilderExtra for canister '{}'.", info.get_name())] fn try_from(info: &CanisterInfo, pool: &CanisterPool) -> DfxResult { - let deps = match info.get_extra_value("dependencies") { - None => vec![], - Some(v) => Vec::::deserialize(v) - .map_err(|_| anyhow!("Field 'dependencies' is of the wrong type."))?, - }; - let dependencies = deps + let dependencies = info.get_dependencies() .iter() .map(|name| { pool.get_first_canister_with_name(name) @@ -61,10 +55,6 @@ impl AssetsBuilder { } impl CanisterBuilder for AssetsBuilder { - fn supports(&self, info: &CanisterInfo) -> bool { - info.get_type() == "assets" - } - #[context("Failed to get dependencies for canister '{}'.", info.get_name())] fn get_dependencies( &self, @@ -122,12 +112,7 @@ impl CanisterBuilder for AssetsBuilder { info: &CanisterInfo, config: &BuildConfig, ) -> DfxResult { - let deps = match info.get_extra_value("dependencies") { - None => vec![], - Some(v) => Vec::::deserialize(v) - .map_err(|_| anyhow!("Field 'dependencies' is of the wrong type."))?, - }; - let dependencies = deps + let dependencies = info.get_dependencies() .iter() .map(|name| { pool.get_first_canister_with_name(name) diff --git a/src/dfx/src/lib/builders/custom.rs b/src/dfx/src/lib/builders/custom.rs index 0fbe0ffa2c..31f04a8657 100644 --- a/src/dfx/src/lib/builders/custom.rs +++ b/src/dfx/src/lib/builders/custom.rs @@ -1,6 +1,7 @@ use crate::lib::builders::{ BuildConfig, BuildOutput, CanisterBuilder, IdlBuildOutput, WasmBuildOutput, }; +use crate::lib::canister_info::custom::CustomCanisterInfo; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::{BuildError, DfxError, DfxResult}; @@ -11,7 +12,6 @@ use anyhow::{anyhow, Context}; use console::style; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; -use serde::Deserialize; use slog::info; use slog::Logger; use std::path::{Path, PathBuf}; @@ -33,12 +33,7 @@ struct CustomBuilderExtra { impl CustomBuilderExtra { #[context("Failed to create CustomBuilderExtra for canister '{}'.", info.get_name())] fn try_from(info: &CanisterInfo, pool: &CanisterPool) -> DfxResult { - let deps = match info.get_extra_value("dependencies") { - None => vec![], - Some(v) => Vec::::deserialize(v) - .map_err(|_| anyhow!("Field 'dependencies' is of the wrong type."))?, - }; - let dependencies = deps + let dependencies = info.get_dependencies() .iter() .map(|name| { pool.get_first_canister_with_name(name) @@ -49,23 +44,10 @@ impl CustomBuilderExtra { ) }) .collect::>>().with_context( || format!("Failed to collect dependencies (canister ids) of canister {}.", info.get_name()))?; - - let wasm = info - .get_output_wasm_path() - .expect("Missing wasm key in JSON."); - let candid = info - .get_output_idl_path() - .expect("Missing candid key in JSON."); - let build = if let Some(json) = info.get_extra_value("build") { - if let Ok(s) = String::deserialize(json.clone()) { - vec![s] - } else { - Vec::::deserialize(json) - .context("Failed to deserialize json in 'build'.")? - } - } else { - vec![] - }; + let info = info.as_info::()?; + let wasm = info.get_output_wasm_path().to_owned(); + let candid = info.get_output_idl_path().to_owned(); + let build = info.get_build_tasks().to_owned(); Ok(CustomBuilderExtra { dependencies, @@ -96,10 +78,6 @@ impl CustomBuilder { } impl CanisterBuilder for CustomBuilder { - fn supports(&self, info: &CanisterInfo) -> bool { - info.get_type() == "custom" - } - #[context("Failed to get dependencies for canister '{}'.", info.get_name())] fn get_dependencies( &self, diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index 6ab2eb09b4..d72e8a2d00 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -12,6 +12,7 @@ use anyhow::{bail, Context}; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; use std::borrow::Cow; +use std::collections::BTreeMap; use std::ffi::OsStr; use std::io::Read; use std::path::PathBuf; @@ -43,9 +44,6 @@ pub struct BuildOutput { /// A stateless canister builder. This is meant to not keep any state and be passed everything. pub trait CanisterBuilder { - /// Returns true if this builder supports building the canister. - fn supports(&self, info: &CanisterInfo) -> bool; - /// Returns the dependencies of this canister, if any. This should not be a transitive /// list. fn get_dependencies( @@ -330,26 +328,26 @@ impl BuildConfig { } pub struct BuilderPool { - builders: Vec>, + builders: BTreeMap<&'static str, Arc>, } impl BuilderPool { #[context("Failed to create new builder pool.")] pub fn new(env: &dyn Environment) -> DfxResult { - let builders: Vec> = vec![ - Arc::new(assets::AssetsBuilder::new(env)?), - Arc::new(custom::CustomBuilder::new(env)?), - Arc::new(motoko::MotokoBuilder::new(env)?), - Arc::new(rust::RustBuilder::new(env)?), - ]; + let builders = BTreeMap::from([ + ( + "assets", + Arc::new(assets::AssetsBuilder::new(env)?) as Arc, + ), + ("custom", Arc::new(custom::CustomBuilder::new(env)?)), + ("motoko", Arc::new(motoko::MotokoBuilder::new(env)?)), + ("rust", Arc::new(rust::RustBuilder::new(env)?)), + ]); Ok(Self { builders }) } - pub fn get(&self, info: &CanisterInfo) -> Option> { - self.builders - .iter() - .find(|builder| builder.supports(info)) - .map(Arc::clone) + pub fn get(&self, info: &CanisterInfo) -> Arc { + self.builders[info.get_type_specific_properties().name()].clone() } } diff --git a/src/dfx/src/lib/builders/motoko.rs b/src/dfx/src/lib/builders/motoko.rs index 2f6616a07d..1629c89017 100644 --- a/src/dfx/src/lib/builders/motoko.rs +++ b/src/dfx/src/lib/builders/motoko.rs @@ -102,10 +102,6 @@ impl CanisterBuilder for MotokoBuilder { .collect()) } - fn supports(&self, info: &CanisterInfo) -> bool { - info.get_type() == "motoko" - } - #[context("Failed to build Motoko canister '{}'.", canister_info.get_name())] fn build( &self, diff --git a/src/dfx/src/lib/builders/rust.rs b/src/dfx/src/lib/builders/rust.rs index 5304fa44b2..da1bbdee69 100644 --- a/src/dfx/src/lib/builders/rust.rs +++ b/src/dfx/src/lib/builders/rust.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, bail, Context}; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; use serde::Deserialize; -use slog::{info, o}; +use slog::{info, o, warn}; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; @@ -39,12 +39,7 @@ impl CanisterBuilder for RustBuilder { pool: &CanisterPool, info: &CanisterInfo, ) -> DfxResult> { - let deps = match info.get_extra_value("dependencies") { - None => vec![], - Some(v) => Vec::::deserialize(v) - .map_err(|_| anyhow!("Field 'dependencies' is of the wrong type."))?, - }; - let dependencies = deps + let dependencies = info.get_dependencies() .iter() .map(|name| { pool.get_first_canister_with_name(name) @@ -58,10 +53,6 @@ impl CanisterBuilder for RustBuilder { Ok(dependencies) } - fn supports(&self, info: &CanisterInfo) -> bool { - info.get_type() == "rust" - } - #[context("Failed to build Rust canister '{}'.", canister_info.get_name())] fn build( &self, diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index b39f79a327..5ab8810c2b 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] -use crate::config::dfinity::{CanisterDeclarationsConfig, Config}; +use crate::config::dfinity::{CanisterDeclarationsConfig, CanisterTypeProperties, Config}; use crate::lib::canister_info::assets::AssetsCanisterInfo; use crate::lib::canister_info::custom::CustomCanisterInfo; use crate::lib::canister_info::motoko::MotokoCanisterInfo; @@ -7,12 +7,11 @@ use crate::lib::error::DfxResult; use crate::lib::provider::get_network_context; use crate::util; -use anyhow::{anyhow, bail, Context}; +use anyhow::{anyhow, Context}; use core::panic; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; use ic_types::Principal; -use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use self::rust::RustCanisterInfo; @@ -23,9 +22,6 @@ pub mod motoko; pub mod rust; pub trait CanisterInfoFactory { - /// Returns true if this factory supports creating extra info for this canister info. - fn supports(info: &CanisterInfo) -> bool; - fn create(info: &CanisterInfo) -> DfxResult where Self: std::marker::Sized; @@ -35,7 +31,6 @@ pub trait CanisterInfoFactory { #[derive(Debug)] pub struct CanisterInfo { name: String, - canister_type: String, declarations_config: CanisterDeclarationsConfig, remote_id: Option, // id on the currently selected network @@ -50,10 +45,11 @@ pub struct CanisterInfo { packtool: Option, args: Option, + type_specific: CanisterTypeProperties, + dependencies: Vec, post_install: Vec, - - extras: BTreeMap, + main: Option, } impl CanisterInfo { @@ -82,7 +78,6 @@ impl CanisterInfo { .ok_or_else(|| anyhow!("Cannot find canister '{}',", name.to_string()))?; let canister_root = workspace_root.to_path_buf(); - let extras = canister_config.extras.clone(); let declarations_config_pre = canister_config.declarations.clone(); let remote_id = canister_config @@ -109,17 +104,17 @@ impl CanisterInfo { let output_root = build_root.join(name); - let canister_type = canister_config - .r#type - .as_ref() - .cloned() - .unwrap_or_else(|| "motoko".to_owned()); + let type_specific = canister_config.type_specific.clone(); + + let args = match &canister_config.args { + Some(args) if !args.is_empty() => canister_config.args.clone(), + _ => build_defaults.get_args(), + }; let post_install = canister_config.post_install.clone().into_vec(); let canister_info = CanisterInfo { name: name.to_string(), - canister_type, declarations_config, remote_id, remote_candid, @@ -129,30 +124,19 @@ impl CanisterInfo { canister_root, canister_id, packtool: build_defaults.get_packtool(), - args: build_defaults.get_args(), - extras, - + args, + type_specific, + dependencies: canister_config.dependencies.clone(), post_install, + main: canister_config.main.clone(), }; - let canister_args: Option = canister_info.get_extra_optional("args")?; - - Ok(match canister_args { - None => canister_info, - Some(v) if v.is_empty() => canister_info, - args => CanisterInfo { - args, - ..canister_info - }, - }) + Ok(canister_info) } pub fn get_name(&self) -> &str { self.name.as_str() } - pub fn get_type(&self) -> &str { - &self.canister_type - } pub fn get_declarations_config(&self) -> &CanisterDeclarationsConfig { &self.declarations_config } @@ -196,43 +180,12 @@ impl CanisterInfo { } } - pub fn get_extra_value(&self, name: &str) -> Option { - self.extras.get(name).cloned() - } - - pub fn has_extra(&self, name: &str) -> bool { - self.extras.contains_key(name) - } - - #[context("Failed while trying to get field '{}' for canister '{}'.", name, self.name)] - pub fn get_extra(&self, name: &str) -> DfxResult { - self.get_extra_value(name) - .ok_or_else(|| { - anyhow!( - "Field '{}' is mandatory for canister {}.", - name, - self.get_name() - ) - }) - .and_then(|v| { - T::deserialize(v).map_err(|_| anyhow!("Field '{}' is of the wrong type.", name)) - }) - } - - #[context("Failed while trying to get optional config field '{}' for canister '{}'.", name, self.name)] - pub fn get_extra_optional( - &self, - name: &str, - ) -> DfxResult> { - if self.has_extra(name) { - self.get_extra(name).map(Some) - } else { - Ok(None) - } + pub fn get_dependencies(&self) -> &[String] { + &self.dependencies } - pub fn get_extras(&self) -> &BTreeMap { - &self.extras + pub fn get_main_file(&self) -> Option<&Path> { + self.main.as_deref() } pub fn get_packtool(&self) -> &Option { @@ -283,30 +236,46 @@ impl CanisterInfo { } pub fn get_output_idl_path(&self) -> Option { - if let Ok(info) = self.as_info::() { - Some(info.get_output_idl_path().to_path_buf()) - } else if let Ok(info) = self.as_info::() { - Some(info.get_output_idl_path().to_path_buf()) - } else if let Ok(info) = self.as_info::() { - Some(info.get_output_idl_path().to_path_buf()) - } else if let Ok(info) = self.as_info::() { - Some(info.get_output_idl_path().to_path_buf()) - } else { - self.get_extra_optional("candid") - .unwrap_or(None) - .or_else(|| self.remote_candid.clone()) + match &self.type_specific { + CanisterTypeProperties::Motoko { .. } => self + .as_info::() + .map(|x| x.get_output_idl_path().to_path_buf()), + CanisterTypeProperties::Custom { .. } => self + .as_info::() + .map(|x| x.get_output_idl_path().to_path_buf()), + CanisterTypeProperties::Assets { .. } => self + .as_info::() + .map(|x| x.get_output_idl_path().to_path_buf()), + CanisterTypeProperties::Rust { .. } => self + .as_info::() + .map(|x| x.get_output_idl_path().to_path_buf()), } + .ok() + .or_else(|| self.remote_candid.clone()) } #[context("Failed to create CanisterInfo for canister '{}'.", self.name, )] pub fn as_info(&self) -> DfxResult { - if T::supports(self) { - T::create(self) - } else { - bail!( - "Canister does not support type '{}'.", - self.get_type().to_string() - ) - } + T::create(self) + } + + pub fn get_type_specific_properties(&self) -> &CanisterTypeProperties { + &self.type_specific + } + + pub fn is_motoko(&self) -> bool { + matches!(self.type_specific, CanisterTypeProperties::Motoko { .. }) + } + + pub fn is_custom(&self) -> bool { + matches!(self.type_specific, CanisterTypeProperties::Custom { .. }) + } + + pub fn is_rust(&self) -> bool { + matches!(self.type_specific, CanisterTypeProperties::Rust { .. }) + } + + pub fn is_assets(&self) -> bool { + matches!(self.type_specific, CanisterTypeProperties::Assets { .. }) } } diff --git a/src/dfx/src/lib/canister_info/assets.rs b/src/dfx/src/lib/canister_info/assets.rs index 8a943c374f..b738720f72 100644 --- a/src/dfx/src/lib/canister_info/assets.rs +++ b/src/dfx/src/lib/canister_info/assets.rs @@ -1,3 +1,4 @@ +use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; use crate::lib::error::DfxResult; @@ -48,20 +49,19 @@ impl AssetsCanisterInfo { } impl CanisterInfoFactory for AssetsCanisterInfo { - fn supports(info: &CanisterInfo) -> bool { - info.get_type() == "assets" - } - fn create(info: &CanisterInfo) -> DfxResult { let build_root = info.get_build_root(); let name = info.get_name(); let input_root = info.get_workspace_root().to_path_buf(); // If there are no "source" field, we just ignore this. - let source_paths = if info.has_extra("source") { - info.get_extra::>("source")? + let source_paths = if let CanisterTypeProperties::Assets { source } = &info.type_specific { + source.clone() } else { - vec![] + bail!( + "Attempted to construct an assets canister from a type:{} canister config", + info.type_specific.name() + ) }; let output_root = build_root.join(name); diff --git a/src/dfx/src/lib/canister_info/custom.rs b/src/dfx/src/lib/canister_info/custom.rs index 8939f5f1f5..19c86b167a 100644 --- a/src/dfx/src/lib/canister_info/custom.rs +++ b/src/dfx/src/lib/canister_info/custom.rs @@ -1,3 +1,6 @@ +use anyhow::bail; + +use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; use crate::lib::error::DfxResult; use std::path::{Path, PathBuf}; @@ -5,6 +8,7 @@ use std::path::{Path, PathBuf}; pub struct CustomCanisterInfo { output_wasm_path: PathBuf, output_idl_path: PathBuf, + build: Vec, } impl CustomCanisterInfo { @@ -14,26 +18,39 @@ impl CustomCanisterInfo { pub fn get_output_idl_path(&self) -> &Path { self.output_idl_path.as_path() } + pub fn get_build_tasks(&self) -> &[String] { + &self.build + } } impl CanisterInfoFactory for CustomCanisterInfo { - fn supports(info: &CanisterInfo) -> bool { - info.get_type() == "custom" - } - fn create(info: &CanisterInfo) -> DfxResult { let workspace_root = info.get_workspace_root(); - let output_wasm_path = workspace_root.join(info.get_extra::("wasm")?); + let (wasm, build, candid) = if let CanisterTypeProperties::Custom { + wasm, + build, + candid, + } = info.type_specific.clone() + { + (wasm, build.into_vec(), candid) + } else { + bail!( + "Attempted to construct a custom canister from a type:{} canister config", + info.type_specific.name() + ) + }; + let output_wasm_path = workspace_root.join(wasm); let candid = if let Some(remote_candid) = info.get_remote_candid_if_remote() { remote_candid } else { - info.get_extra::("candid")? + candid }; let output_idl_path = workspace_root.join(candid); Ok(Self { output_wasm_path, output_idl_path, + build, }) } } diff --git a/src/dfx/src/lib/canister_info/motoko.rs b/src/dfx/src/lib/canister_info/motoko.rs index efd4de3bf7..a3a748a7c3 100644 --- a/src/dfx/src/lib/canister_info/motoko.rs +++ b/src/dfx/src/lib/canister_info/motoko.rs @@ -1,3 +1,6 @@ +use anyhow::{ensure, Context}; + +use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; use crate::lib::error::DfxResult; use std::path::{Path, PathBuf}; @@ -52,18 +55,19 @@ impl MotokoCanisterInfo { } impl CanisterInfoFactory for MotokoCanisterInfo { - fn supports(info: &CanisterInfo) -> bool { - info.get_type() == "motoko" - } - fn create(info: &CanisterInfo) -> DfxResult { let workspace_root = info.get_workspace_root(); let build_root = info.get_build_root(); let name = info.get_name(); let idl_path = build_root.join("idl/"); - - let main_path = info.get_extra::("main")?; - + ensure!( + matches!(info.type_specific, CanisterTypeProperties::Motoko { .. }), + "Attempted to construct a custom canister from a type:{} canister config", + info.type_specific.name() + ); + let main_path = info + .get_main_file() + .context("`main` attribute is required on Motoko canisters in dfx.json")?; let input_path = workspace_root.join(&main_path); let output_root = build_root.join(name); let output_wasm_path = output_root.join(name).with_extension("wasm"); diff --git a/src/dfx/src/lib/canister_info/rust.rs b/src/dfx/src/lib/canister_info/rust.rs index 402c0ba929..e7fc8d5a88 100644 --- a/src/dfx/src/lib/canister_info/rust.rs +++ b/src/dfx/src/lib/canister_info/rust.rs @@ -1,3 +1,4 @@ +use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; use crate::lib::error::DfxResult; use anyhow::{bail, Context}; @@ -26,10 +27,6 @@ impl RustCanisterInfo { } impl CanisterInfoFactory for RustCanisterInfo { - fn supports(info: &CanisterInfo) -> bool { - info.get_type() == "rust" - } - fn create(info: &CanisterInfo) -> DfxResult { #[derive(Deserialize)] struct Project { @@ -46,7 +43,15 @@ impl CanisterInfoFactory for RustCanisterInfo { } let Project { target_directory } = serde_json::from_slice(&metadata.stdout) .context("Failed to read metadata from `cargo metadata`")?; - let package = info.get_extra::("package")?; + let (package, candid) = + if let CanisterTypeProperties::Rust { package, candid } = info.type_specific.clone() { + (package, candid) + } else { + bail!( + "Attempted to construct a custom canister from a type:{} canister config", + info.type_specific.name() + ); + }; let workspace_root = info.get_workspace_root(); let output_wasm_path = @@ -54,7 +59,7 @@ impl CanisterInfoFactory for RustCanisterInfo { let candid = if let Some(remote_candid) = info.get_remote_candid_if_remote() { remote_candid } else { - info.get_extra::("candid")? + candid }; let output_idl_path = workspace_root.join(candid); diff --git a/src/dfx/src/lib/ic_attributes/mod.rs b/src/dfx/src/lib/ic_attributes/mod.rs index 1d37b94658..12159004b9 100644 --- a/src/dfx/src/lib/ic_attributes/mod.rs +++ b/src/dfx/src/lib/ic_attributes/mod.rs @@ -1,8 +1,9 @@ use crate::config::dfinity::ConfigInterface; use crate::lib::error::DfxResult; +use anyhow::{anyhow, Context}; +use byte_unit::Byte; use fn_error_context::context; -use humanize_rs::bytes::Bytes; use ic_types::principal::Principal; use ic_utils::interfaces::management_canister::attributes::{ ComputeAllocation, FreezingThreshold, MemoryAllocation, @@ -23,14 +24,15 @@ pub fn get_compute_allocation( canister_name: Option<&str>, ) -> DfxResult> { let compute_allocation = match (compute_allocation, canister_name) { - (Some(compute_allocation), _) => Some(compute_allocation), - (None, Some(canister_name)) => config_interface.get_compute_allocation(canister_name)?, + (Some(compute_allocation), _) => Some(compute_allocation.parse::()?), + (None, Some(canister_name)) => config_interface.get_compute_allocation(canister_name)? as _, (None, None) => None, }; - Ok(compute_allocation.map(|arg| { - ComputeAllocation::try_from(arg.parse::().unwrap()) - .expect("Compute Allocation must be a percentage.") - })) + compute_allocation + .map(|arg| { + ComputeAllocation::try_from(arg).context("Compute Allocation must be a percentage.") + }) + .transpose() } #[context("Failed to get memory allocation.")] @@ -40,14 +42,18 @@ pub fn get_memory_allocation( canister_name: Option<&str>, ) -> DfxResult> { let memory_allocation = match (memory_allocation, canister_name) { - (Some(memory_allocation), _) => Some(memory_allocation), + (Some(memory_allocation), _) => Some(memory_allocation.parse::()?), (None, Some(canister_name)) => config_interface.get_memory_allocation(canister_name)?, (None, None) => None, }; - Ok(memory_allocation.map(|arg| { - MemoryAllocation::try_from(u64::try_from(arg.parse::().unwrap().size()).unwrap()) - .expect("Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.") - })) + memory_allocation + .map(|arg| { + u64::try_from(arg.get_bytes()) + .map_err(|e| anyhow!(e)) + .and_then(|n| Ok(MemoryAllocation::try_from(n)?)) + .context("Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.") + }) + .transpose() } #[context("Failed to get freezing threshold.")] @@ -57,12 +63,16 @@ pub fn get_freezing_threshold( canister_name: Option<&str>, ) -> DfxResult> { let freezing_threshold = match (freezing_threshold, canister_name) { - (Some(freezing_threshold), _) => Some(freezing_threshold), - (None, Some(canister_name)) => config_interface.get_freezing_threshold(canister_name)?, + (Some(freezing_threshold), _) => Some(freezing_threshold.parse::()?), + (None, Some(canister_name)) => config_interface + .get_freezing_threshold(canister_name)? + .map(|dur| dur.as_secs()), (None, None) => None, }; - Ok(freezing_threshold.map(|arg| { - FreezingThreshold::try_from(arg.parse::().unwrap()) - .expect("Must be a value between 0 and 2^64-1 inclusive.") - })) + freezing_threshold + .map(|arg| { + FreezingThreshold::try_from(arg) + .context("Must be a duration between 0 and 2^64-1 inclusive.") + }) + .transpose() } diff --git a/src/dfx/src/lib/models/canister.rs b/src/dfx/src/lib/models/canister.rs index 7ccf8254fc..f9163de319 100644 --- a/src/dfx/src/lib/models/canister.rs +++ b/src/dfx/src/lib/models/canister.rs @@ -120,18 +120,11 @@ impl CanisterPool { _ => None, }; let info = CanisterInfo::load(pool_helper.config, canister_name, canister_id)?; - - if let Some(builder) = pool_helper.builder_pool.get(&info) { - pool_helper - .canisters_map - .insert(0, Arc::new(Canister::new(info, builder))); - Ok(()) - } else { - Err(anyhow!( - "Cannot find builder for canister '{}'.", - info.get_name().to_string() - )) - } + let builder = pool_helper.builder_pool.get(&info); + pool_helper + .canisters_map + .insert(0, Arc::new(Canister::new(info, builder))); + Ok(()) } #[context( @@ -244,7 +237,7 @@ impl CanisterPool { #[context("Failed step_prebuild_all.")] fn step_prebuild_all(&self, _build_config: &BuildConfig) -> DfxResult<()> { - if self.contains_canister_of_type("rust") { + if self.canisters.iter().any(|can| can.info.is_rust()) { self.run_cargo_audit()?; } else { trace!( @@ -454,12 +447,6 @@ impl CanisterPool { Ok(()) } - fn contains_canister_of_type(&self, of_type: &str) -> bool { - self.canisters - .iter() - .any(|c| c.get_info().get_type() == of_type) - } - /// If `cargo-audit` is installed this runs `cargo audit` and displays any vulnerable dependencies. fn run_cargo_audit(&self) -> DfxResult { if Command::new("cargo") diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index e082faf3f3..e4b9864435 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -10,9 +10,8 @@ use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister::{create_canister, install_canister}; use crate::util::{blob_from_arguments, get_candid_init_type}; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Context}; use fn_error_context::context; -use humanize_rs::bytes::Bytes; use ic_agent::AgentError; use ic_utils::interfaces::management_canister::attributes::{ ComputeAllocation, FreezingThreshold, MemoryAllocation, @@ -146,32 +145,30 @@ async fn register_canisters( info!(env.get_logger(), "Creating canisters..."); for canister_name in &canisters_to_create { let config_interface = config.get_config(); - let compute_allocation = - config_interface - .get_compute_allocation(canister_name)? - .map(|arg| { - ComputeAllocation::try_from(arg.parse::().unwrap()) - .expect("Compute Allocation must be a percentage.") - }); - let memory_allocation = - config_interface - .get_memory_allocation(canister_name)? - .map(|arg| { - MemoryAllocation::try_from( - u64::try_from(arg.parse::().unwrap().size()).unwrap(), - ) - .expect( - "Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.", - ) - }); + let compute_allocation = config_interface + .get_compute_allocation(canister_name)? + .map(|arg| { + ComputeAllocation::try_from(arg) + .context("Compute Allocation must be a percentage.") + }) + .transpose()?; + let memory_allocation = config_interface + .get_memory_allocation(canister_name)? + .map(|arg| { + u64::try_from(arg.get_bytes()) + .map_err(|e| anyhow!(e)) + .and_then(|n| Ok(MemoryAllocation::try_from(n)?)) + .context( + "Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.", + ) + }) + .transpose()?; let freezing_threshold = config_interface .get_freezing_threshold(canister_name)? .map(|arg| { - FreezingThreshold::try_from( - u128::try_from(arg.parse::().unwrap().size()).unwrap(), - ) - .expect("Freezing threshold must be between 0 and 2^64-1, inclusively.") + FreezingThreshold::try_from(arg.as_secs()) + .expect("Freezing threshold must be between 0 and 2^64-1, inclusively.") }); let controllers = None; create_canister( diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 37110ddf89..6f1d6a3289 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -82,7 +82,7 @@ pub async fn install_canister( } } } - if canister_info.get_type() == "motoko" && matches!(mode, InstallMode::Upgrade) { + if canister_info.is_motoko() && matches!(mode, InstallMode::Upgrade) { use crate::lib::canister_info::motoko::MotokoCanisterInfo; let info = canister_info.as_info::()?; let stable_path = info.get_output_stable_path(); @@ -139,7 +139,7 @@ pub async fn install_canister( .await?; } - if canister_info.get_type() == "assets" { + if canister_info.is_assets() { if let CallSender::Wallet(wallet_id) = call_sender { let wallet = Identity::build_wallet_canister(*wallet_id, env).await?; let identity_name = env.get_selected_identity().expect("No selected identity."); diff --git a/src/dfx/src/util/clap/validators.rs b/src/dfx/src/util/clap/validators.rs index 6de337dabf..7665dd9798 100644 --- a/src/dfx/src/util/clap/validators.rs +++ b/src/dfx/src/util/clap/validators.rs @@ -1,5 +1,5 @@ use crate::lib::nns_types::icpts::ICPTs; -use humanize_rs::bytes::{Bytes, Unit}; +use byte_unit::{Byte, ByteUnit}; use std::path::Path; use std::str::FromStr; @@ -80,9 +80,9 @@ pub fn compute_allocation_validator(compute_allocation: &str) -> Result<(), Stri pub fn memory_allocation_validator(memory_allocation: &str) -> Result<(), String> { // This limit should track MAX_MEMORY_ALLOCATION // at https://gitlab.com/dfinity-lab/core/ic/-/blob/master/rs/types/types/src/lib.rs#L492 - let limit = Bytes::new(12, Unit::GiByte).map_err(|_| "Parse Overflow.")?; - if let Ok(bytes) = memory_allocation.parse::() { - if bytes.size() <= limit.size() { + let limit = Byte::from_unit(12., ByteUnit::GiB).expect("Parse Overflow."); + if let Ok(bytes) = memory_allocation.parse::() { + if bytes.get_bytes() <= limit.get_bytes() { return Ok(()); } } diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index 62f571c397..28d4cc156d 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -4,12 +4,16 @@ use crate::{error_invalid_argument, error_invalid_data, error_unknown}; use anyhow::Context; use candid::parser::typing::{pretty_check_file, TypeEnv}; use candid::types::{Function, Type}; +use candid::Deserialize; use candid::{parser::value::IDLValue, IDLArgs}; use fn_error_context::context; use net2::TcpListenerExt; use net2::{unix::UnixTcpBuilderExt, TcpBuilder}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; +use std::convert::TryFrom; +use std::fmt::Display; use std::net::{IpAddr, SocketAddr}; +use std::str::FromStr; use std::time::Duration; pub mod assets; @@ -243,3 +247,31 @@ impl Default for SerdeVec { Self::Many(vec![]) } } + +#[derive(Serialize, serde::Deserialize)] +#[serde(untagged)] +enum PossiblyStrInner { + NotStr(T), + Str(String), +} + +#[derive(Serialize, Deserialize, Default, Copy, Clone, Debug)] +#[serde(try_from = "PossiblyStrInner")] +pub struct PossiblyStr(pub T) +where + T: FromStr, + T::Err: Display; + +impl TryFrom> for PossiblyStr +where + T: FromStr, + T::Err: Display, +{ + type Error = T::Err; + fn try_from(inner: PossiblyStrInner) -> Result { + match inner { + PossiblyStrInner::NotStr(t) => Ok(Self(t)), + PossiblyStrInner::Str(str) => T::from_str(&str).map(Self), + } + } +}