From ce2f888f6cf9443609f2014c75a792b6e840a567 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Mon, 20 Jun 2022 15:05:36 -0700 Subject: [PATCH 01/21] Remove `extra` field from config --- Cargo.lock | 36 ++++- src/dfx/Cargo.toml | 4 +- src/dfx/src/commands/canister/sign.rs | 3 +- src/dfx/src/commands/deploy.rs | 5 +- src/dfx/src/commands/generate.rs | 2 +- src/dfx/src/commands/language_service.rs | 27 ++-- .../src/commands/remote/generate_binding.rs | 28 ++-- src/dfx/src/config/dfinity.rs | 125 ++++++++++------ src/dfx/src/lib/builders/assets.rs | 19 +-- src/dfx/src/lib/builders/custom.rs | 34 +---- src/dfx/src/lib/builders/mod.rs | 28 ++-- src/dfx/src/lib/builders/motoko.rs | 4 - src/dfx/src/lib/builders/rust.rs | 12 +- src/dfx/src/lib/canister_info.rs | 141 +++++++----------- src/dfx/src/lib/canister_info/assets.rs | 14 +- src/dfx/src/lib/canister_info/custom.rs | 29 +++- src/dfx/src/lib/canister_info/motoko.rs | 18 ++- src/dfx/src/lib/canister_info/rust.rs | 17 ++- src/dfx/src/lib/ic_attributes/mod.rs | 46 +++--- src/dfx/src/lib/models/canister.rs | 25 +--- .../operations/canister/deploy_canisters.rs | 45 +++--- .../operations/canister/install_canister.rs | 4 +- src/dfx/src/util/clap/validators.rs | 8 +- src/dfx/src/util/mod.rs | 55 +++++++ 24 files changed, 388 insertions(+), 341 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3eac93b40d..8f9ec86db8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,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" @@ -1012,6 +1022,7 @@ dependencies = [ "argon2", "atty", "base64", + "byte-unit", "candid", "clap", "console", @@ -1026,7 +1037,8 @@ dependencies = [ "futures", "garcon", "hex", - "humanize-rs", + "humantime", + "humantime-serde", "ic-agent", "ic-asset", "ic-identity-hsm", @@ -1679,18 +1691,22 @@ 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 = "humantime" 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" @@ -3856,6 +3872,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/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 980a2cfa12..dc26ccc302 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -27,6 +27,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" @@ -40,13 +41,14 @@ 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" indicatif = "0.16.0" 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 94f5982dc1..a5b24c70c1 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 87ca78fa37..1ac10fee53 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 { 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..86eaf4d80d 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,25 +53,25 @@ 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 (type_env, did_types) = check_candid_file(&candid)?; let bindings = if main.ends_with(&".mo") { Some(candid::bindings::motoko::compile(&type_env, &did_types)) } else if main.ends_with(&".rs") { @@ -88,23 +85,20 @@ pub fn exec(env: &dyn Environment, opts: GenerateBindingOpts) -> DfxResult { 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 b0277f0f78..9eb362b52b 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -1,8 +1,10 @@ #![allow(dead_code)] use crate::lib::error::{BuildError, DfxError, DfxResult}; +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}; @@ -11,6 +13,7 @@ use std::collections::{BTreeMap, HashSet}; use std::default::Default; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; +use std::time::Duration; pub const CONFIG_FILE_NAME: &str = "dfx.json"; @@ -61,18 +64,67 @@ 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 initialization_values: InitializationValues, + + #[serde(default)] + pub dependencies: Vec, + + #[serde(default)] + pub frontend: bool, + #[serde(flatten)] - pub extras: BTreeMap, + pub type_specific: CanisterTypeProperties, + + pub main: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum CanisterTypeProperties { + Rust { + package: String, + candid: PathBuf, + }, + Motoko {}, + Assets { + source: Vec, + }, + Custom { + wasm: PathBuf, + candid: PathBuf, + build: SerdeVec, + }, +} + +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)] @@ -93,7 +145,7 @@ pub struct CanisterDeclarationsConfig { #[derive(Clone, Debug, Default, 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) @@ -103,15 +155,10 @@ pub struct ConfigDefaultsBitcoin { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ConfigDefaultsCanisterHttp { - #[serde(default = "default_as_false")] + #[serde(default)] pub enabled: bool, } -fn default_as_false() -> bool { - // sigh https://github.com/serde-rs/serde/issues/368 - false -} - #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ConfigDefaultsBootstrap { pub ip: Option, @@ -398,40 +445,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) } } @@ -460,16 +507,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(); @@ -810,13 +851,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 6ca9afbad8..cf41283bd2 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, bail, 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; @@ -28,12 +27,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) @@ -62,10 +56,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, @@ -126,12 +116,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 8bddc70ceb..68b2eb82ea 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}; @@ -10,7 +11,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::PathBuf; @@ -32,12 +32,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) @@ -48,23 +43,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, @@ -95,10 +77,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 fc05811591..b7f864b27b 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( @@ -335,26 +333,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 d69df6ce9b..cc0c0b02ac 100644 --- a/src/dfx/src/lib/builders/motoko.rs +++ b/src/dfx/src/lib/builders/motoko.rs @@ -101,10 +101,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 0ff7a56661..42e9827ab2 100644 --- a/src/dfx/src/lib/builders/rust.rs +++ b/src/dfx/src/lib/builders/rust.rs @@ -10,7 +10,6 @@ use crate::lib::models::canister::CanisterPool; use anyhow::{anyhow, bail, Context}; use fn_error_context::context; use ic_types::principal::Principal as CanisterId; -use serde::Deserialize; use slog::{info, o, warn}; use std::path::PathBuf; use std::process::Command; @@ -38,12 +37,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) @@ -57,10 +51,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 0d4fa55136..3b40f67593 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,8 +45,10 @@ pub struct CanisterInfo { packtool: Option, args: Option, + type_specific: CanisterTypeProperties, - extras: BTreeMap, + dependencies: Vec, + main: Option, } impl CanisterInfo { @@ -80,7 +77,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 @@ -107,15 +103,15 @@ 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 canister_info = CanisterInfo { name: name.to_string(), - canister_type, declarations_config, remote_id, remote_candid, @@ -125,28 +121,18 @@ 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(), + 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 } @@ -190,43 +176,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) + pub fn get_dependencies(&self) -> &[String] { + &self.dependencies } - #[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_extras(&self) -> &BTreeMap { - &self.extras + pub fn get_main_file(&self) -> Option<&Path> { + self.main.as_deref() } pub fn get_packtool(&self) -> &Option { @@ -273,30 +228,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 046bad4be2..d94e40e02f 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; @@ -52,20 +53,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 8ed39fb52e..a437900c7e 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, @@ -145,32 +144,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 b0e99fc30a..da0cab63f5 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -74,7 +74,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(); @@ -131,7 +131,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 645c3ffaf0..cd813d6066 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::str::FromStr; pub fn is_request_id(v: &str) -> Result<(), String> { @@ -63,9 +63,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 e08da25516..e9b026a6e8 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -4,11 +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::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; @@ -220,3 +225,53 @@ pub fn blob_from_arguments( v => Err(error_unknown!("Invalid type: {}", v)), } } + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum SerdeVec { + One(T), + Many(Vec), +} + +impl SerdeVec { + pub fn into_vec(self) -> Vec { + match self { + Self::One(t) => vec![t], + Self::Many(ts) => ts, + } + } +} + +impl Default for SerdeVec { + fn default() -> Self { + Self::Many(vec![]) + } +} + +#[derive(Serialize, 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), + } + } +} From aec799d5b543acd08353b09699ce89f75a0bc33d Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Mon, 20 Jun 2022 16:14:07 -0700 Subject: [PATCH 02/21] Restore default type:motoko behavior --- src/dfx/src/config/dfinity.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 9eb362b52b..550629d61a 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -83,7 +83,7 @@ pub struct ConfigCanistersCanister { #[serde(default)] pub frontend: bool, - #[serde(flatten)] + #[serde(flatten, default)] pub type_specific: CanisterTypeProperties, pub main: Option, @@ -107,6 +107,12 @@ pub enum CanisterTypeProperties { }, } +impl Default for CanisterTypeProperties { + fn default() -> Self { + Self::Motoko {} + } +} + impl CanisterTypeProperties { pub fn name(&self) -> &'static str { match self { From f2f553360b91610302c1ca6381acfa1b7c0f74a7 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 21 Jun 2022 11:25:13 -0700 Subject: [PATCH 03/21] does that work? --- src/dfx/src/config/dfinity.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 550629d61a..2342743a3a 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -96,7 +96,6 @@ pub enum CanisterTypeProperties { package: String, candid: PathBuf, }, - Motoko {}, Assets { source: Vec, }, @@ -105,6 +104,8 @@ pub enum CanisterTypeProperties { candid: PathBuf, build: SerdeVec, }, + #[serde(other)] + Motoko, } impl Default for CanisterTypeProperties { From 261dadddd78ea4a5d724b17a336d8ad1c4ba4248 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Tue, 5 Jul 2022 11:21:12 +0200 Subject: [PATCH 04/21] fix basic-project e2e --- src/dfx/src/config/dfinity.rs | 4 +++- src/dfx/src/lib/canister_info.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 2342743a3a..a5482c5738 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -83,8 +83,10 @@ pub struct ConfigCanistersCanister { #[serde(default)] pub frontend: bool, + /// https://github.com/serde-rs/serde/issues/1626 default doesn't work with flatten. + /// using Option as workaround, and unwrap_or_default() when accessing the field #[serde(flatten, default)] - pub type_specific: CanisterTypeProperties, + pub type_specific: Option, pub main: Option, } diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index 3b40f67593..a1a8dd0eb3 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -103,7 +103,7 @@ impl CanisterInfo { let output_root = build_root.join(name); - let type_specific = canister_config.type_specific.clone(); + let type_specific = canister_config.type_specific.clone().unwrap_or_default(); let args = match &canister_config.args { Some(args) if !args.is_empty() => canister_config.args.clone(), From f358155814318caf7b94637a8f6f15ef5c8851a5 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 10:26:21 -0700 Subject: [PATCH 05/21] `main` is Motoko-specific --- src/dfx/src/commands/language_service.rs | 11 ++++++----- .../src/commands/remote/generate_binding.rs | 4 ++-- src/dfx/src/config/dfinity.rs | 13 +++---------- src/dfx/src/lib/canister_info.rs | 6 ------ src/dfx/src/lib/canister_info/motoko.rs | 19 ++++++++++--------- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/dfx/src/commands/language_service.rs b/src/dfx/src/commands/language_service.rs index 8d1e60f767..928846d91e 100644 --- a/src/dfx/src/commands/language_service.rs +++ b/src/dfx/src/commands/language_service.rs @@ -1,4 +1,6 @@ -use crate::config::dfinity::{ConfigCanistersCanister, ConfigInterface, CONFIG_FILE_NAME}; +use crate::config::dfinity::{ + CanisterTypeProperties, ConfigCanistersCanister, ConfigInterface, CONFIG_FILE_NAME, +}; use crate::error_invalid_data; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; @@ -81,13 +83,12 @@ fn get_main_path(config: &ConfigInterface, canister_name: Option) -> Dfx } } }?; - if let Some(main) = canister.main { + if let CanisterTypeProperties::Motoko { main } = canister.type_specific { Ok(main) } else { Err(error_invalid_data!( - "Canister {0} lacks a 'main' element in {1}", - canister_name, - dfx_json + "Canister {canister_name} in {dfx_json} should be type:motoko but is type:{}", + canister.type_specific.name(), )) } } diff --git a/src/dfx/src/commands/remote/generate_binding.rs b/src/dfx/src/commands/remote/generate_binding.rs index 86eaf4d80d..a532a5dc2f 100644 --- a/src/dfx/src/commands/remote/generate_binding.rs +++ b/src/dfx/src/commands/remote/generate_binding.rs @@ -1,3 +1,4 @@ +use crate::config::dfinity::CanisterTypeProperties; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::models::canister::CanisterPool; @@ -42,8 +43,7 @@ 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 = info.get_main_file(); - if let Some(main) = main_optional { + if let CanisterTypeProperties::Motoko { main } = info.get_type_specific_properties() { if !candid.exists() { info!( log, diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 2342743a3a..d793446ad2 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -85,8 +85,6 @@ pub struct ConfigCanistersCanister { #[serde(flatten, default)] pub type_specific: CanisterTypeProperties, - - pub main: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -104,14 +102,9 @@ pub enum CanisterTypeProperties { candid: PathBuf, build: SerdeVec, }, - #[serde(other)] - Motoko, -} - -impl Default for CanisterTypeProperties { - fn default() -> Self { - Self::Motoko {} - } + Motoko { + main: PathBuf, + }, } impl CanisterTypeProperties { diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index 3b40f67593..a852a2483a 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -48,7 +48,6 @@ pub struct CanisterInfo { type_specific: CanisterTypeProperties, dependencies: Vec, - main: Option, } impl CanisterInfo { @@ -124,7 +123,6 @@ impl CanisterInfo { args, type_specific, dependencies: canister_config.dependencies.clone(), - main: canister_config.main.clone(), }; Ok(canister_info) @@ -180,10 +178,6 @@ impl CanisterInfo { &self.dependencies } - pub fn get_main_file(&self) -> Option<&Path> { - self.main.as_deref() - } - pub fn get_packtool(&self) -> &Option { &self.packtool } diff --git a/src/dfx/src/lib/canister_info/motoko.rs b/src/dfx/src/lib/canister_info/motoko.rs index a3a748a7c3..80ce88dd3a 100644 --- a/src/dfx/src/lib/canister_info/motoko.rs +++ b/src/dfx/src/lib/canister_info/motoko.rs @@ -1,4 +1,4 @@ -use anyhow::{ensure, Context}; +use anyhow::bail; use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; @@ -60,14 +60,15 @@ impl CanisterInfoFactory for MotokoCanisterInfo { let build_root = info.get_build_root(); let name = info.get_name(); let idl_path = build_root.join("idl/"); - 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 main_path = if let CanisterTypeProperties::Motoko { main } = info.type_specific.clone() + { + main + } else { + bail!( + "Attempted to construct a custom canister from a type:{} canister config", + info.type_specific.name() + ) + }; 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"); From 47d89d42b5e004de30bc69f4f375fb4ed30d5cb9 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 11:26:36 -0700 Subject: [PATCH 06/21] Work around serde-rs/serde#2231 --- src/dfx/src/config/dfinity.rs | 70 ++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index d793446ad2..954aa7419a 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -7,10 +7,12 @@ 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; @@ -87,7 +89,7 @@ pub struct ConfigCanistersCanister { pub type_specific: CanisterTypeProperties, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum CanisterTypeProperties { Rust { @@ -623,6 +625,70 @@ 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; + // package, source, candid, build, wasm, main + let (mut package, mut source, mut candid, mut build, mut wasm, mut main, mut r#type) = + (None, 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()?), + "main" => main = Some(map.next_value()?), + "type" => r#type = Some(map.next_value::()?), + _ => continue, + } + } + let props = match r#type.as_deref() { + Some("motoko") | None => CanisterTypeProperties::Motoko { + main: main.ok_or_else(|| missing_field("type"))?, // intentional + }, + 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::*; From b836726e69dae6b778052d66910ef261260f238c Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 11:31:10 -0700 Subject: [PATCH 07/21] forgot to delete that --- src/dfx/src/config/dfinity.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 3198aeb7d8..57063f1f64 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -641,7 +641,6 @@ impl<'de> Visitor<'de> for PropertiesVisitor { A: MapAccess<'de>, { let missing_field = A::Error::missing_field; - // package, source, candid, build, wasm, main let (mut package, mut source, mut candid, mut build, mut wasm, mut main, mut r#type) = (None, None, None, None, None, None, None); while let Some(key) = map.next_key::()? { From cafcf5150e79a56134bdff2890cbdeeb52349b16 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 11:58:55 -0700 Subject: [PATCH 08/21] this is never read, but it sure is written --- src/dfx/src/commands/deploy.rs | 2 +- src/dfx/src/config/dfinity.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index 7da7e9b266..de15f68177 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -164,7 +164,7 @@ 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))?; - if canister_config.frontend { + if canister_config.frontend.is_some() { let url = construct_frontend_url(network, &canister_id)?; frontend_urls.insert(canister_name, url); } diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 57063f1f64..b5537ac789 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -82,8 +82,7 @@ pub struct ConfigCanistersCanister { #[serde(default)] pub dependencies: Vec, - #[serde(default)] - pub frontend: bool, + pub frontend: Option>, #[serde(flatten)] pub type_specific: CanisterTypeProperties, From d09b1ad0c24467d335d1e7a0f916a765f97d4c53 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 12:53:25 -0700 Subject: [PATCH 09/21] Revert "`main` is Motoko-specific" This reverts commit f358155814318caf7b94637a8f6f15ef5c8851a5. --- src/dfx/src/commands/language_service.rs | 11 +++++------ .../src/commands/remote/generate_binding.rs | 4 ++-- src/dfx/src/config/dfinity.rs | 14 +++++--------- src/dfx/src/lib/canister_info.rs | 6 ++++++ src/dfx/src/lib/canister_info/motoko.rs | 19 +++++++++---------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/dfx/src/commands/language_service.rs b/src/dfx/src/commands/language_service.rs index 928846d91e..8d1e60f767 100644 --- a/src/dfx/src/commands/language_service.rs +++ b/src/dfx/src/commands/language_service.rs @@ -1,6 +1,4 @@ -use crate::config::dfinity::{ - CanisterTypeProperties, ConfigCanistersCanister, ConfigInterface, CONFIG_FILE_NAME, -}; +use crate::config::dfinity::{ConfigCanistersCanister, ConfigInterface, CONFIG_FILE_NAME}; use crate::error_invalid_data; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; @@ -83,12 +81,13 @@ fn get_main_path(config: &ConfigInterface, canister_name: Option) -> Dfx } } }?; - if let CanisterTypeProperties::Motoko { main } = canister.type_specific { + if let Some(main) = canister.main { Ok(main) } else { Err(error_invalid_data!( - "Canister {canister_name} in {dfx_json} should be type:motoko but is type:{}", - canister.type_specific.name(), + "Canister {0} lacks a 'main' element in {1}", + canister_name, + dfx_json )) } } diff --git a/src/dfx/src/commands/remote/generate_binding.rs b/src/dfx/src/commands/remote/generate_binding.rs index a532a5dc2f..86eaf4d80d 100644 --- a/src/dfx/src/commands/remote/generate_binding.rs +++ b/src/dfx/src/commands/remote/generate_binding.rs @@ -1,4 +1,3 @@ -use crate::config::dfinity::CanisterTypeProperties; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::models::canister::CanisterPool; @@ -43,7 +42,8 @@ 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() { - if let CanisterTypeProperties::Motoko { main } = info.get_type_specific_properties() { + let main_optional = info.get_main_file(); + if let Some(main) = main_optional { if !candid.exists() { info!( log, diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index b5537ac789..bf0a4de9a0 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -89,6 +89,7 @@ pub struct ConfigCanistersCanister { #[serde(default)] pub post_install: SerdeVec, + pub main: Option, } #[derive(Clone, Debug, Serialize)] @@ -106,9 +107,7 @@ pub enum CanisterTypeProperties { candid: PathBuf, build: SerdeVec, }, - Motoko { - main: PathBuf, - }, + Motoko, } impl CanisterTypeProperties { @@ -640,8 +639,8 @@ impl<'de> Visitor<'de> for PropertiesVisitor { A: MapAccess<'de>, { let missing_field = A::Error::missing_field; - let (mut package, mut source, mut candid, mut build, mut wasm, mut main, mut r#type) = - (None, None, None, None, None, None, None); + 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()?), @@ -649,15 +648,12 @@ impl<'de> Visitor<'de> for PropertiesVisitor { "candid" => candid = Some(map.next_value()?), "build" => build = Some(map.next_value()?), "wasm" => wasm = Some(map.next_value()?), - "main" => main = Some(map.next_value()?), "type" => r#type = Some(map.next_value::()?), _ => continue, } } let props = match r#type.as_deref() { - Some("motoko") | None => CanisterTypeProperties::Motoko { - main: main.ok_or_else(|| missing_field("type"))?, // intentional - }, + 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"))?, diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index 05770c9914..5ab8810c2b 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -49,6 +49,7 @@ pub struct CanisterInfo { dependencies: Vec, post_install: Vec, + main: Option, } impl CanisterInfo { @@ -127,6 +128,7 @@ impl CanisterInfo { type_specific, dependencies: canister_config.dependencies.clone(), post_install, + main: canister_config.main.clone(), }; Ok(canister_info) @@ -182,6 +184,10 @@ impl CanisterInfo { &self.dependencies } + pub fn get_main_file(&self) -> Option<&Path> { + self.main.as_deref() + } + pub fn get_packtool(&self) -> &Option { &self.packtool } diff --git a/src/dfx/src/lib/canister_info/motoko.rs b/src/dfx/src/lib/canister_info/motoko.rs index 80ce88dd3a..a3a748a7c3 100644 --- a/src/dfx/src/lib/canister_info/motoko.rs +++ b/src/dfx/src/lib/canister_info/motoko.rs @@ -1,4 +1,4 @@ -use anyhow::bail; +use anyhow::{ensure, Context}; use crate::config::dfinity::CanisterTypeProperties; use crate::lib::canister_info::{CanisterInfo, CanisterInfoFactory}; @@ -60,15 +60,14 @@ impl CanisterInfoFactory for MotokoCanisterInfo { let build_root = info.get_build_root(); let name = info.get_name(); let idl_path = build_root.join("idl/"); - let main_path = if let CanisterTypeProperties::Motoko { main } = info.type_specific.clone() - { - main - } else { - bail!( - "Attempted to construct a custom canister from a type:{} canister config", - info.type_specific.name() - ) - }; + 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"); From 3d663ca729e06bece619d0bfffda58675c3bdbd1 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 13:36:17 -0700 Subject: [PATCH 10/21] required fields --- e2e/tests-dfx/config.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests-dfx/config.bash b/e2e/tests-dfx/config.bash index 97e759c7f2..5381f7134a 100644 --- a/e2e/tests-dfx/config.bash +++ b/e2e/tests-dfx/config.bash @@ -20,6 +20,7 @@ teardown() { # shellcheck disable=SC2154 assert_eq '"motoko"' "$stdout" + cat <<<"$(jq '.canisters.e2e_project.candid="/dev/null" | .canisters.e2e_project.package="e2e_project"' dfx.json)" >dfx.json assert_command dfx config canisters.e2e_project.type "rust" # shellcheck disable=SC2154 assert_eq "" "$stdout" From fc1d1be58fa04675f45335fe5716177e2f16cf43 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 13:46:47 -0700 Subject: [PATCH 11/21] Update error_context asset --- e2e/assets/error_context/dfx.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", From 8d5dc2b749d7bd0ccb05171c70838e2037173242 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 13:48:30 -0700 Subject: [PATCH 12/21] shellcheck --- e2e/tests-dfx/config.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-dfx/config.bash b/e2e/tests-dfx/config.bash index 5381f7134a..9c0fdb654f 100644 --- a/e2e/tests-dfx/config.bash +++ b/e2e/tests-dfx/config.bash @@ -19,7 +19,7 @@ teardown() { assert_command dfx config canisters.e2e_project.type # shellcheck disable=SC2154 assert_eq '"motoko"' "$stdout" - + # shellcheck disable=SC2094 cat <<<"$(jq '.canisters.e2e_project.candid="/dev/null" | .canisters.e2e_project.package="e2e_project"' dfx.json)" >dfx.json assert_command dfx config canisters.e2e_project.type "rust" # shellcheck disable=SC2154 From 38a74e2262478a8b16979c29c908cf511c178c7a Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 14:38:09 -0700 Subject: [PATCH 13/21] paths are not strings --- src/dfx/src/commands/remote/generate_binding.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dfx/src/commands/remote/generate_binding.rs b/src/dfx/src/commands/remote/generate_binding.rs index 86eaf4d80d..008f307349 100644 --- a/src/dfx/src/commands/remote/generate_binding.rs +++ b/src/dfx/src/commands/remote/generate_binding.rs @@ -72,13 +72,14 @@ pub fn exec(env: &dyn Environment, opts: GenerateBindingOpts) -> DfxResult { } } let (type_env, did_types) = check_candid_file(&candid)?; - let bindings = if main.ends_with(&".mo") { + 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!( From a002cec99e64ca44a0d5f997f7e0b0cddc170ad1 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 14:41:56 -0700 Subject: [PATCH 14/21] Update build.bash --- e2e/tests-dfx/build.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index 3710d4e130..d7fc0d75f5 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -118,8 +118,9 @@ teardown() { @test "build fails if canister type is not supported" { dfx_start - dfx config canisters.e2e_project.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" } From 086774ea6caebc239b6774abde89a6696977754d Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 15:30:51 -0700 Subject: [PATCH 15/21] Update build.bash --- e2e/tests-dfx/build.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index d7fc0d75f5..e38913e1a8 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -122,7 +122,7 @@ teardown() { # 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" + assert_match 'unknown variant `unknown_canister_type`' } @test "can build a custom canister type" { From 6544b918c44d7bfde92eb0ff4bbeb9091c6bd60c Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 5 Jul 2022 15:33:58 -0700 Subject: [PATCH 16/21] shellcheck --- e2e/tests-dfx/build.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index e38913e1a8..2e7b422c8f 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -122,6 +122,7 @@ teardown() { # shellcheck disable=SC2094 cat <<<"$(jq '.canisters.e2e_project.type="unknown_canister_type"' dfx.json)" >dfx.json assert_command_fail dfx build + # shellcheck disable=SC2016 assert_match 'unknown variant `unknown_canister_type`' } From c8c824b0cc7514e30857a9ce9d4bf6365a76898b Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Mon, 11 Jul 2022 10:32:25 +0200 Subject: [PATCH 17/21] fix Cargo.lock --- Cargo.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e61e3fb90..c88d0595dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1739,8 +1739,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package - [[package]] name = "humansize" version = "1.1.1" From ab43a0225e9cd575f754227f6c9b628a45aad3d2 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Tue, 12 Jul 2022 08:42:21 +0200 Subject: [PATCH 18/21] valid canister type makes dfx stop work --- e2e/tests-dfx/build.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index 2e7b422c8f..88ad4bb4c8 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -124,6 +124,10 @@ teardown() { assert_command_fail dfx build # 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="unknown_canister_type"' dfx.json)" >dfx.json } @test "can build a custom canister type" { From 6942118e28a48e19a82d82b40790b144a8dee5ed Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Tue, 12 Jul 2022 09:47:14 +0200 Subject: [PATCH 19/21] fix dfx config test bc field candid is now required for rust canisters --- e2e/tests-dfx/config.bash | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e/tests-dfx/config.bash b/e2e/tests-dfx/config.bash index fb1c32403c..e8d13a7865 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,16 @@ 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" + cat dfx.json 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 From 710af37d1664b7930e24196224a52c7e36a0de93 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Tue, 12 Jul 2022 09:47:35 +0200 Subject: [PATCH 20/21] rm debug print --- e2e/tests-dfx/config.bash | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/tests-dfx/config.bash b/e2e/tests-dfx/config.bash index e8d13a7865..c5e9018482 100644 --- a/e2e/tests-dfx/config.bash +++ b/e2e/tests-dfx/config.bash @@ -23,7 +23,6 @@ teardown() { assert_command dfx config canisters.e2e_project_backend.type "motoko" # shellcheck disable=SC2154 assert_eq "" "$stdout" - cat dfx.json assert_command dfx config canisters.e2e_project_backend.type # shellcheck disable=SC2154 From c13dcd769a2461fcf35f3a7a464397d4223404f3 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Tue, 12 Jul 2022 09:53:16 +0200 Subject: [PATCH 21/21] fix copy/paste mistake --- e2e/tests-dfx/build.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index cb217c33f0..4d7c21c851 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -127,7 +127,7 @@ teardown() { # If canister type is invalid, `dfx stop` fails # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project.type="unknown_canister_type"' dfx.json)" >dfx.json + cat <<<"$(jq '.canisters.e2e_project.type="motoko"' dfx.json)" >dfx.json } @test "can build a custom canister type" {