Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions e2e/bats/assets/transitive_deps_canisters/dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@
"dependencies": ["canister_d"],
"main": "./e/main.mo",
"type": "motoko"
},
"canister_f": {
"dependencies": ["canister_g", "canister_h"],
"main": "./f/main.mo",
"type": "motoko"
},
"canister_g": {
"dependencies": ["canister_a"],
"main": "./g/main.mo",
"type": "motoko"
},
"canister_h": {
"dependencies": ["canister_a"],
"main": "./h/main.mo",
"type": "motoko"
}
},
"defaults": {
Expand Down
5 changes: 5 additions & 0 deletions e2e/bats/assets/transitive_deps_canisters/f/main.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
actor {
public func greet(name : Text) : async Text {
return "Bună, " # name # "!";
};
};
5 changes: 5 additions & 0 deletions e2e/bats/assets/transitive_deps_canisters/g/main.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
actor {
public func greet(name : Text) : async Text {
return "Ciao, " # name # "!";
};
};
5 changes: 5 additions & 0 deletions e2e/bats/assets/transitive_deps_canisters/h/main.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
actor {
public func greet(name : Text) : async Text {
return "Olá, " # name # "!";
};
};
11 changes: 9 additions & 2 deletions e2e/bats/build_granular.bash
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,15 @@ teardown() {
install_asset transitive_deps_canisters
dfx_start
dfx canister create --all
assert_command dfx build canister_e
assert_match "Possible circular dependency detected during evaluation of canister_d's dependency on canister_e."
assert_command_fail dfx build canister_e
assert_match " There is a dependency cycle between canisters found at canister canister_e -> canister_d -> canister_e"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

}

@test "multiple non-cyclic dependency paths to the same canister are ok" {
install_asset transitive_deps_canisters
dfx_start
dfx canister create --all
assert_command dfx build canister_f
}

@test "the all flag builds everything" {
Expand Down
58 changes: 58 additions & 0 deletions e2e/bats/deploy.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bats

load utils/_

setup() {
# We want to work from a temporary directory, different for every test.
cd $(mktemp -d -t dfx-e2e-XXXXXXXX)
export RUST_BACKTRACE=1

dfx_new hello
dfx_start
}

teardown() {
dfx_stop
}

@test "deploy from a fresh project" {
install_asset greet
assert_command dfx deploy

assert_command dfx canister call hello greet '("Banzai")'
assert_eq '("Hello, Banzai!")'
}

@test "deploy a canister without dependencies" {
install_asset greet
assert_command dfx deploy hello
assert_match 'Deploying: hello'
assert_not_match 'hello_assets'
}

@test "deploy a canister with dependencies" {
install_asset greet
assert_command dfx deploy hello_assets
assert_match 'Deploying: hello hello_assets'
}

@test "deploy a canister with non-circular shared dependencies" {
install_asset transitive_deps_canisters
assert_command dfx deploy canister_f
assert_match 'Deploying: canister_a canister_f canister_g canister_h'
}

@test "report an error on attempt to deploy a canister with circular dependencies" {
install_asset transitive_deps_canisters
assert_command_fail dfx deploy canister_d
assert_match 'canister_d -> canister_e -> canister_d'
}

@test "if already registered, try to upgrade then install" {
install_asset greet
assert_command dfx canister create --all

assert_command dfx deploy
assert_match 'attempting install'
}

19 changes: 19 additions & 0 deletions e2e/bats/utils/assertions.bash
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ assert_match() {
| fail)
}

# Asserts that a string does not contain another string, using regexp.
# Arguments:
# $1 - The regex to use to match.
# $2 - The string to match against (output). By default it will use
# $output.
assert_not_match() {
regex="$1"
if [[ $# < 2 ]]; then
text="$output"
else
text="$2"
fi
if [[ "$text" =~ $regex ]]; then
(batslib_print_kv_single_or_multi 10 "regex" "$regex" "actual" "$text" \
| batslib_decorate "output matches but is expected not to" \
| fail)
fi
}

# Asserts a command will timeout. This assertion will fail if the command finishes before
# the timeout period. If the command fails, it will also fail.
# Arguments:
Expand Down
5 changes: 4 additions & 1 deletion src/dfx/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {

// Option can be None in which case --all was specified
let some_canister = args.value_of("canister_name");
let canister_names = config
.get_config()
.get_canister_names_with_dependencies(some_canister)?;

// Get pool of canisters to build
let canister_pool = CanisterPool::load(&env, build_mode_check, some_canister)?;
let canister_pool = CanisterPool::load(&env, build_mode_check, &canister_names)?;

// Create canisters on the replica and associate canister ids locally.
if args.is_present("check") {
Expand Down
31 changes: 31 additions & 0 deletions src/dfx/src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::message::UserMessage;
use crate::lib::operations::canister::deploy_canisters;
use crate::lib::provider::create_agent_environment;
use clap::{App, Arg, ArgMatches, SubCommand};

pub fn construct() -> App<'static, 'static> {
SubCommand::with_name("deploy")
.about(UserMessage::DeployCanister.to_str())
.arg(
Arg::with_name("canister_name")
.takes_value(true)
.help(UserMessage::DeployCanisterName.to_str())
.required(false),
)
.arg(
Arg::with_name("network")
.help(UserMessage::CanisterComputeNetwork.to_str())
.long("network")
.takes_value(true),
)
}

pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
let env = create_agent_environment(env, args)?;

let canister = args.value_of("canister_name");

deploy_canisters(&env, canister)
}
2 changes: 2 additions & 0 deletions src/dfx/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod build;
mod cache;
mod canister;
mod config;
mod deploy;
mod identity;
mod language_service;
mod new;
Expand Down Expand Up @@ -48,6 +49,7 @@ pub fn builtin() -> Vec<CliCommand> {
CliCommand::new(cache::construct(), cache::exec),
CliCommand::new(canister::construct(), canister::exec),
CliCommand::new(config::construct(), config::exec),
CliCommand::new(deploy::construct(), deploy::exec),
CliCommand::new(identity::construct(), identity::exec),
CliCommand::new(language_service::construct(), language_service::exec),
CliCommand::new(new::construct(), new::exec),
Expand Down
70 changes: 68 additions & 2 deletions src/dfx/src/config/dfinity.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#![allow(dead_code)]

use crate::lib::error::{DfxError, DfxResult};
use crate::lib::error::{BuildErrorKind, DfxError, DfxResult};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashSet};
use std::default::Default;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -238,6 +238,72 @@ impl ConfigInterface {
pub fn get_dfx(&self) -> Option<String> {
self.dfx.to_owned()
}

/// Return the names of the specified canister and all of its dependencies.
/// If none specified, return the names of all canisters.
pub fn get_canister_names_with_dependencies(
&self,
some_canister: Option<&str>,
) -> DfxResult<Vec<String>> {
let canister_map = (&self.canisters).as_ref().ok_or_else(|| {
DfxError::InvalidConfiguration("No canisters in the configuration file.".to_string())
})?;

let canister_names = match some_canister {
Some(specific_canister) => {
let mut names = HashSet::new();
let mut path = vec![];
add_dependencies(canister_map, &mut names, &mut path, specific_canister)?;
names.into_iter().collect()
}
None => canister_map.keys().cloned().collect(),
};

Ok(canister_names)
}
}

fn add_dependencies(
all_canisters: &BTreeMap<String, ConfigCanistersCanister>,
names: &mut HashSet<String>,
path: &mut Vec<String>,
canister_name: &str,
) -> DfxResult {
let inserted = names.insert(String::from(canister_name));

if !inserted {
return if path.contains(&String::from(canister_name)) {
path.push(String::from(canister_name));
Err(DfxError::BuildError(BuildErrorKind::CircularDependency(
path.join(" -> "),
)))
} else {
Ok(())
};
}

let canister_config = all_canisters
.get(canister_name)
.ok_or_else(|| DfxError::CannotFindCanisterName(canister_name.to_string()))?;

let deps = match canister_config.extras.get("dependencies") {
None => vec![],
Some(v) => Vec::<String>::deserialize(v).map_err(|_| {
DfxError::InvalidConfiguration(String::from(
"Field 'dependencies' is of the wrong type",
))
})?,
};

path.push(String::from(canister_name));

for canister in deps {
add_dependencies(all_canisters, names, path, &canister)?;
}

path.pop();

Ok(())
}

#[derive(Clone)]
Expand Down
4 changes: 3 additions & 1 deletion src/dfx/src/lib/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ pub enum DfxError {
/// Argument provided is invalid.
InvalidArgument(String),

#[allow(dead_code)]
/// Configuration provided is invalid.
InvalidConfiguration(String),
/// Method called invalid.
Expand Down Expand Up @@ -198,6 +197,9 @@ impl Display for DfxError {
DfxError::InvalidArgument(e) => {
f.write_fmt(format_args!("Invalid argument: {}", e))?;
}
DfxError::InvalidConfiguration(e) => {
f.write_fmt(format_args!("Invalid configuration: {}", e))?;
}
DfxError::InvalidData(e) => {
f.write_fmt(format_args!("Invalid data: {}", e))?;
}
Expand Down
6 changes: 5 additions & 1 deletion src/dfx/src/lib/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ user_message!(
ConfigureOptions => "Configures project options for your currently-selected project.",
OptionName => "Specifies the name of the configuration option to set or read. Use the period delineated path to specify the option to set or read. If this is not mentioned, outputs the whole configuration.",
OptionValue => "Specifies the new value to set. If you don't specify a value, the command displays the current value of the option from the configuration file.",
OptionFormat => "Specifies the format of the output. By default, it uses JSON.",
OptionFormat => "Specifies the format of the output. By default, the output format is JSON.",

// dfx deploy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lsgunnlsgunn could you review these messages please?

DeployCanister => "Deploys all or a specific canister from the code in your project. By default, all canisters are deployed.",
DeployCanisterName => "Specifies the name of the canister you want to deploy. If you don’t specify a canister name, all canisters defined in the dfx.json file are deployed.",

// dfx identity mod
ManageIdentity => "Manages identities used to communicate with the Internet Computer network. Setting an identity enables you to test user-based access controls.",
Expand Down
Loading