Skip to content

Commit

Permalink
Automatically generate proposal texts
Browse files Browse the repository at this point in the history
  • Loading branch information
anchpop committed Feb 4, 2025
1 parent 156e27a commit b86b4c1
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rs/nervous_system/tools/release-runscript/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ DEPENDENCIES = [
"//rs/types/base_types",
"@crate_index//:anyhow",
"@crate_index//:candid",
"@crate_index//:chrono",
"@crate_index//:clap",
"@crate_index//:colored",
"@crate_index//:futures",
Expand Down
1 change: 1 addition & 0 deletions rs/nervous_system/tools/release-runscript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ path = "src/main.rs"
[dependencies]
anyhow = { workspace = true }
candid = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
colored = "2.0.0"
futures = { workspace = true }
Expand Down
195 changes: 173 additions & 22 deletions rs/nervous_system/tools/release-runscript/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,30 @@ use std::path::PathBuf;
use std::process::Command;

#[derive(Debug, Parser)]
struct DetermineTargets;
struct DetermineTargets {
#[arg(long)]
commit: String,
}

#[derive(Debug, Parser)]
struct RunTests;
struct RunTests {
#[arg(long)]
commit: String,
#[arg(long, num_args = 0..,)]
nns_canisters: Vec<String>,
#[arg(long, num_args = 0..,)]
sns_canisters: Vec<String>,
}

#[derive(Debug, Parser)]
struct CreateProposalTexts;
struct CreateProposalTexts {
#[arg(long)]
commit: String,
#[arg(long, num_args = 0..,)]
nns_canisters: Vec<String>,
#[arg(long, num_args = 0..,)]
sns_canisters: Vec<String>,
}

#[derive(Debug, Parser)]
struct SubmitProposals;
Expand Down Expand Up @@ -127,10 +144,11 @@ fn run_pick_commit() {
);

// Continue to next step.
run_determine_targets(DetermineTargets);
run_determine_targets(DetermineTargets { commit });
}

fn run_determine_targets(_: DetermineTargets) {
fn run_determine_targets(cmd: DetermineTargets) {
let DetermineTargets { commit } = cmd;
println!("Now choose which canisters to upgrade. You can run ./testnet/tools/nns-tools/list-new-commits.sh to see the changes for each canister.");

// Define the candidate canisters.
Expand Down Expand Up @@ -175,10 +193,19 @@ fn run_determine_targets(_: DetermineTargets) {
),
);

run_run_tests(RunTests);
run_run_tests(RunTests {
commit,
nns_canisters,
sns_canisters,
});
}

fn run_run_tests(_: RunTests) {
fn run_run_tests(cmd: RunTests) {
let RunTests {
commit,
nns_canisters,
sns_canisters,
} = cmd;
print_step(3,
"Run NNS Upgrade Tests",
"Verify the commit you chose at the previous step has a green check on this page: https://github.com/dfinity/ic/actions/workflows/ci-main.yml?query=branch:master+event:push+is:success
Expand All @@ -192,26 +219,150 @@ If not, you can also run the upgrade tests manually:
- Example: Test at rs/nervous_system/integration_tests/tests/sns_release_qualification.rs",
);

run_create_proposal_texts(CreateProposalTexts);
run_create_proposal_texts(CreateProposalTexts {
commit,
nns_canisters,
sns_canisters,
});
}

fn run_create_proposal_texts(_: CreateProposalTexts) {
fn run_create_proposal_texts(cmd: CreateProposalTexts) {
let CreateProposalTexts {
commit,
nns_canisters,
sns_canisters,
} = cmd;

// Create proposals directory under ic_dir/proposals/release-{ISO_DATE}
let ic = ic_dir();
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
let proposals_dir = ic
.join("..")
.join("proposals")
.join(format!("release-{}", today));

// ensure the directory is empty
// check if the directory exists
if proposals_dir.exists() {
println!("Removing existing proposals/ directory");
std::fs::remove_dir_all(&proposals_dir).unwrap();
}
std::fs::create_dir_all(&proposals_dir).expect("Failed to create proposals directory");

let proposals_dir = proposals_dir.canonicalize().unwrap();

println!(
"Creating proposal texts for {} NNS canisters and {} SNS canisters at commit {}",
nns_canisters.len(),
sns_canisters.len(),
commit
);

let mut nns_proposal_text_paths = Vec::new();
let mut sns_proposal_text_paths = Vec::new();

// For each NNS canister, run the prepare-nns-upgrade-proposal-text.sh script and write its output to a file.
for canister in &nns_canisters {
println!("Creating proposal text for NNS canister: {}", canister);
let script = ic.join("testnet/tools/nns-tools/prepare-nns-upgrade-proposal-text.sh");
// cycles minting requires an upgrade arg, usually '()'
let output = if canister != "cycles-minting" {
Command::new(script)
.arg(canister)
.arg(&commit)
.current_dir(&ic)
.output()
.expect("Failed to run NNS proposal text script")
} else {
let upgrade_arg = input_with_default("Upgrade arg for CMC?", "'()'");
Command::new(script)
.arg(canister)
.arg(&commit)
.arg(upgrade_arg)
.current_dir(&ic)
.output()
.expect("Failed to run NNS proposal text script")
};
if !output.status.success() {
panic!(
"Failed to create proposal text for NNS canister {} due to error: stdout: {}, stderr: {}",
canister,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
let file_path = proposals_dir.join(format!("nns-{}.md", canister));
std::fs::write(&file_path, output.stdout).expect("Failed to write NNS proposal file");
nns_proposal_text_paths.push(file_path);
}

// For each SNS canister, run the prepare-publish-sns-wasm-proposal-text.sh script.
for canister in &sns_canisters {
println!("Creating proposal text for SNS canister: {}", canister);
let script = ic.join("testnet/tools/nns-tools/prepare-publish-sns-wasm-proposal-text.sh");
// The SNS script is expected to write directly to the file provided as an argument.
let file_path = proposals_dir.join(format!("sns-{}.md", canister));
let file_path_str = file_path.to_str().expect("Invalid file path");
let output = Command::new(script)
.arg(canister)
.arg(&commit)
.arg(file_path_str)
.current_dir(&ic)
.output()
.expect("Failed to run SNS proposal text script");
if !output.status.success() {
panic!(
"Failed to create proposal text for SNS canister {} due to error: {}",
canister,
String::from_utf8_lossy(&output.stderr)
);
}
sns_proposal_text_paths.push(file_path);
}

for proposal_text_path in &nns_proposal_text_paths {
println!(
"Viewing NNS proposal with vscode: {}",
proposal_text_path.display()
);
let mut cmd = Command::new("code");
cmd.arg(proposal_text_path);
cmd.current_dir(&ic);
cmd.output()
.expect("Failed to view NNS proposal with vscode");
}

for proposal_text_path in &sns_proposal_text_paths {
println!(
"Viewing SNS proposal with vscode: {}",
proposal_text_path.display()
);
let mut cmd = Command::new("code");
cmd.arg(proposal_text_path);
cmd.current_dir(&ic);
cmd.output()
.expect("Failed to view SNS proposal with vscode");
}

use std::fmt::Write;
print_step(
4,
"Create Proposal Texts",
"Create proposal text for each canister to be upgraded.
This can be done in parallel with the previous testing step.
Instructions:
1. Follow format in: testnet/tools/nns-tools/README.md#nnssns-canister-upgrade-proposal-process
2. Name conventions:
- NNS proposals: nns-*.md
- SNS proposals: sns-*.md
3. Organization:
- Put all proposal files in a dedicated directory
- Keep directory clean (nothing else in there)
- This will help with forum post generation later",
&format!("I created proposal texts for each canister to be upgraded in the following directory: {}.
NNS proposal texts: {}
SNS proposal texts: {}",
proposals_dir.display(),
nns_proposal_text_paths.iter().fold(String::new(), |mut acc, path| {
let _ = write!(acc, "\n - {}", path.display());
acc
}),
sns_proposal_text_paths.iter().fold(String::new(), |mut acc, path| {
let _ = write!(acc, "\n - {}", path.display());
acc
})
)
);

run_submit_proposals(SubmitProposals);
}

Expand Down Expand Up @@ -385,7 +536,7 @@ fn input(text: &str) -> String {
}

fn input_with_default(text: &str, default: &str) -> String {
let input = input(&format!("{} ({})", text, default));
let input = input(&format!("{} (default: {})", text, default));
if input.is_empty() {
default.to_string()
} else {
Expand Down

0 comments on commit b86b4c1

Please sign in to comment.