Skip to content

Commit 78c960b

Browse files
authored
feat: Also report Motoko stable compatibility warnings (#3887)
* Report Motoko stable compatibility warnings --------- Reviewed by: Linwei Shang <[email protected]>
1 parent e6942eb commit 78c960b

File tree

4 files changed

+49
-7
lines changed

4 files changed

+49
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
# UNRELEASED
44

5+
### feat: Also report Motoko stable compatibility warnings
6+
7+
Report upgrade compatibility warnings for Motoko, such as deleted stable variables, in addition to compatibility errors.
8+
59
### feat: Support for Motoko's enhanced orthogonal persistence.
610

711
Support Motoko's enhanced orthogonal persistence by automatically setting the canister upgrade option `wasm_memory_persistence` based on the Wasm metadata.

e2e/assets/upgrade/v5.mo

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
actor {
2+
stable var newState : Int = 0;
3+
public func inc() : async Int {
4+
newState += 1;
5+
return newState;
6+
};
7+
public func f() : async ?Int {
8+
return ?42;
9+
};
10+
public query func read() : async Int { return newState; };
11+
}

e2e/tests-dfx/upgrade_check.bash

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ teardown() {
3333
jq '.canisters.hello_backend.main="v2_bad.mo"' dfx.json | sponge dfx.json
3434
echo yes | (
3535
assert_command dfx deploy
36-
assert_match "Stable interface compatibility check failed"
36+
assert_match "Stable interface compatibility check issued an ERROR"
3737
)
3838
assert_command dfx canister call hello_backend read '()'
3939
assert_match "(0 : nat)"
@@ -78,3 +78,17 @@ teardown() {
7878
assert_command dfx canister call hello_backend f '()'
7979
assert_match "(opt \"\")"
8080
}
81+
82+
@test "warning when dropping stable variable" {
83+
install_asset upgrade
84+
dfx_start
85+
dfx deploy
86+
dfx canister call hello_backend inc '()'
87+
jq '.canisters.hello_backend.main="v5.mo"' dfx.json | sponge dfx.json
88+
echo yes | (
89+
assert_command dfx deploy
90+
assert_match "Stable interface compatibility check issued an ERROR"
91+
)
92+
assert_command dfx canister call hello_backend read '()'
93+
assert_match "(0 : int)"
94+
}

src/dfx/src/lib/operations/canister/install_canister.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,13 @@ pub async fn install_canister(
105105
let stable_types = read_module_metadata(agent, canister_id, "motoko:stable-types").await;
106106
if let Some(stable_types) = &stable_types {
107107
match check_stable_compatibility(canister_info, env, stable_types) {
108-
Ok(None) => (),
109-
Ok(Some(err)) => {
110-
let msg = format!("Stable interface compatibility check failed for canister '{}'.\nUpgrade will either FAIL or LOSE some stable variable data.\n\n", canister_info.get_name()) + &err;
108+
Ok(StableCompatibility::Okay) => (),
109+
Ok(StableCompatibility::Warning(details)) => {
110+
let msg = format!("Stable interface compatibility check issued a WARNING for canister '{}'.\n\n", canister_info.get_name()) + &details;
111+
ask_for_consent(&msg)?;
112+
}
113+
Ok(StableCompatibility::Error(details)) => {
114+
let msg = format!("Stable interface compatibility check issued an ERROR for canister '{}'.\nUpgrade will either FAIL or LOSE some stable variable data.\n\n", canister_info.get_name()) + &details;
111115
ask_for_consent(&msg)?;
112116
}
113117
Err(e) => {
@@ -375,11 +379,17 @@ async fn wait_for_module_hash(
375379
Ok(())
376380
}
377381

382+
enum StableCompatibility {
383+
Okay,
384+
Warning(String),
385+
Error(String),
386+
}
387+
378388
fn check_stable_compatibility(
379389
canister_info: &CanisterInfo,
380390
env: &dyn Environment,
381391
stable_types: &str,
382-
) -> anyhow::Result<Option<String>> {
392+
) -> anyhow::Result<StableCompatibility> {
383393
use crate::lib::canister_info::motoko::MotokoCanisterInfo;
384394
let info = canister_info.as_info::<MotokoCanisterInfo>()?;
385395
let stable_path = info.get_output_stable_path();
@@ -399,10 +409,13 @@ fn check_stable_compatibility(
399409
.current_dir(canister_info.get_workspace_root())
400410
.output()
401411
.context("Failed to run 'moc'.")?;
412+
let message = String::from_utf8_lossy(&output.stderr).to_string();
402413
Ok(if !output.status.success() {
403-
Some(String::from_utf8_lossy(&output.stderr).to_string())
414+
StableCompatibility::Error(message)
415+
} else if !output.stderr.is_empty() {
416+
StableCompatibility::Warning(message)
404417
} else {
405-
None
418+
StableCompatibility::Okay
406419
})
407420
}
408421

0 commit comments

Comments
 (0)