Skip to content

Commit

Permalink
feat: init_arg_file in dfx.json (#3721)
Browse files Browse the repository at this point in the history
* Add  support in dfx.json. (WIP)

* Trim the string read from the arg file.

* Update dfx-json-schema.json file.

* Get the absolute path of 'init_arg_file', and update changelog.

* Fix cargo format.

* simplify a bit

* comments

* improve the warning message

* changelog

* fmt

---------

Co-authored-by: Vincent Zhang <[email protected]>
Co-authored-by: Linwei Shang <[email protected]>
  • Loading branch information
3 people authored Apr 24, 2024
1 parent df0ffe7 commit e38935e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 10 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@

Please see [extension-defined-canister-types](docs/concepts/extension-defined-canister-types.md) for details.

### feat: init_arg_file in dfx.json

Introduces support for the `init_arg_file` field in `dfx.json`, providing an alternative method to specify initialization arguments.

This field accepts a relative path, from the directory containing the `dfx.json` file.

**Note**

- Only one of `init_arg` and `init_arg_file` can be defined at a time.
- If `--argument` or `--argument-file` are set, the argument from the command line takes precedence over the one in dfx.json.

### fix: dfx new failure when node is available but npm is not

`dfx new` could fail with "Failed to scaffold frontend code" if node was installed but npm was not installed.
Expand Down
8 changes: 8 additions & 0 deletions docs/dfx-json-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@
"null"
]
},
"init_arg_file": {
"title": "Init Arg File",
"description": "The Candid initialization argument file for installing the canister. If the `--argument` or `--argument-file` argument is also provided, this `init_arg_file` field will be ignored.",
"type": [
"string",
"null"
]
},
"initialization_values": {
"title": "Resource Allocation Settings",
"description": "Defines initial values for resource allocation settings.",
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests-dfx/deploy.bash
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ teardown() {
assert_match "Hello, dfx!"

assert_command dfx deploy dependency --mode reinstall --yes --argument '("icp")'
assert_contains "Canister 'dependency' has init_arg in dfx.json: (\"dfx\"),"
assert_contains "Canister 'dependency' has init_arg/init_arg_file in dfx.json: (\"dfx\"),"
assert_contains "which is different from the one specified in the command line: (\"icp\")."
assert_contains "The command line value will be used."
assert_command dfx canister call dependency greet
Expand Down
33 changes: 32 additions & 1 deletion e2e/tests-dfx/install.bash
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,44 @@ teardown() {
assert_match "Hello, dfx!"

assert_command dfx canister install dependency --mode reinstall --yes --argument '("icp")'
assert_contains "Canister 'dependency' has init_arg in dfx.json: (\"dfx\"),"
assert_contains "Canister 'dependency' has init_arg/init_arg_file in dfx.json: (\"dfx\"),"
assert_contains "which is different from the one specified in the command line: (\"icp\")."
assert_contains "The command line value will be used."
assert_command dfx canister call dependency greet
assert_match "Hello, icp!"
}

@test "install succeeds if init_arg_file is defined in dfx.json" {
install_asset deploy_deps
dfx_start
mkdir arg-files
echo '("dfx")' >> arg-files/args.txt
jq '.canisters.dependency.init_arg_file="arg-files/args.txt"' dfx.json | sponge dfx.json

dfx canister create dependency
dfx build dependency

# The following commands will be run in this sub-directory, it verifies that the init_arg_file is relative to the dfx.json file
cd arg-files
assert_command dfx canister install dependency
assert_command dfx canister call dependency greet
assert_match "Hello, dfx!"
}

@test "install fails if both init_arg and init_arg_file are defined in dfx.json" {
install_asset deploy_deps
dfx_start
echo '("dfx")' >> args.txt
jq '.canisters.dependency.init_arg="(\"dfx\")"' dfx.json | sponge dfx.json
jq '.canisters.dependency.init_arg_file="args.txt"' dfx.json | sponge dfx.json

dfx canister create dependency
dfx build dependency
assert_command_fail dfx canister install dependency
assert_contains "At most one of the fields 'init_arg' and 'init_arg_file' should be defined in \`dfx.json\`.
Please remove one of them or leave both undefined."
}

@test "install succeeds when specify canister id and wasm, in dir without dfx.json" {
dfx_start

Expand Down
5 changes: 5 additions & 0 deletions src/dfx-core/src/config/model/dfinity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ pub struct ConfigCanistersCanister {
/// The Candid initialization argument for installing the canister.
/// If the `--argument` or `--argument-file` argument is also provided, this `init_arg` field will be ignored.
pub init_arg: Option<String>,

/// # Init Arg File
/// The Candid initialization argument file for installing the canister.
/// If the `--argument` or `--argument-file` argument is also provided, this `init_arg_file` field will be ignored.
pub init_arg_file: Option<String>,
}

#[derive(Clone, Debug, Serialize, JsonSchema)]
Expand Down
31 changes: 28 additions & 3 deletions src/dfx/src/lib/canister_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![allow(dead_code)]
use crate::lib::error::DfxResult;
use crate::lib::metadata::config::CanisterMetadataConfig;
use anyhow::{anyhow, Context};

use anyhow::{anyhow, bail, Context};
use candid::Principal as CanisterId;
use candid::Principal;
use core::panic;
Expand Down Expand Up @@ -61,6 +62,7 @@ pub struct CanisterInfo {
tech_stack: Option<TechStack>,
gzip: bool,
init_arg: Option<String>,
init_arg_file: Option<String>,
}

impl CanisterInfo {
Expand Down Expand Up @@ -147,6 +149,7 @@ impl CanisterInfo {

let gzip = canister_config.gzip.unwrap_or(false);
let init_arg = canister_config.init_arg.clone();
let init_arg_file = canister_config.init_arg_file.clone();

let canister_info = CanisterInfo {
name: name.to_string(),
Expand All @@ -170,6 +173,7 @@ impl CanisterInfo {
pull_dependencies,
gzip,
init_arg,
init_arg_file,
};

Ok(canister_info)
Expand Down Expand Up @@ -371,7 +375,28 @@ impl CanisterInfo {
self.gzip
}

pub fn get_init_arg(&self) -> Option<&str> {
self.init_arg.as_deref()
/// Get the init arg from the dfx.json configuration.
///
/// If the `init_arg` field is defined, it will be returned.
/// If the `init_arg_file` field is defined, the content of the file will be returned.
/// If both fields are defined, an error will be returned.
/// If neither field is defined, `None` will be returned.
pub fn get_init_arg(&self) -> DfxResult<Option<String>> {
let init_arg_value = match (&self.init_arg, &self.init_arg_file) {
(Some(_), Some(_)) => {
bail!("At most one of the fields 'init_arg' and 'init_arg_file' should be defined in `dfx.json`.
Please remove one of them or leave both undefined.");
}
(Some(arg), None) => Some(arg.clone()),
(None, Some(arg_file)) => {
// The file path is relative to the workspace root.
let absolute_path = self.get_workspace_root().join(arg_file);
let content = dfx_core::fs::read_to_string(&absolute_path)?;
Some(content)
}
(None, None) => None,
};

Ok(init_arg_value)
}
}
10 changes: 5 additions & 5 deletions src/dfx/src/lib/operations/canister/install_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ pub async fn install_canister(
get_candid_init_type(&idl_path)
};

// The argument and argument_type from the CLI take precedence over the `init_arg` field in dfx.json
let argument_from_json = canister_info.get_init_arg();
let (argument, argument_type) = match (argument_from_cli, argument_from_json) {
// The argument and argument_type from the CLI take precedence over the dfx.json configuration.
let argument_from_json = canister_info.get_init_arg()?;
let (argument, argument_type) = match (argument_from_cli, &argument_from_json) {
(Some(a_cli), Some(a_json)) => {
// We want to warn the user when the argument from CLI and json are different.
// There are two cases to consider:
Expand All @@ -152,7 +152,7 @@ pub async fn install_canister(
if argument_type_from_cli == Some("raw") || a_cli != a_json {
warn!(
log,
"Canister '{0}' has init_arg in dfx.json: {1},
"Canister '{0}' has init_arg/init_arg_file in dfx.json: {1},
which is different from the one specified in the command line: {2}.
The command line value will be used.",
canister_info.get_name(),
Expand All @@ -163,7 +163,7 @@ The command line value will be used.",
(argument_from_cli, argument_type_from_cli)
}
(Some(_), None) => (argument_from_cli, argument_type_from_cli),
(None, Some(_)) => (argument_from_json, Some("idl")), // `init_arg` in dfx.json is always in Candid format
(None, Some(a_json)) => (Some(a_json.as_str()), Some("idl")), // `init_arg` in dfx.json is always in Candid format
(None, None) => (None, None),
};
let install_args = blob_from_arguments(
Expand Down

0 comments on commit e38935e

Please sign in to comment.