Skip to content
Open
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
24 changes: 12 additions & 12 deletions rs/nervous_system/tools/release-runscript/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,15 @@ fn run_pick_commit() -> Result<()> {
let ic = ic_dir();

// Build the absolute path to the cmd.sh script.
let cmd_path = ic.join("rs/nervous_system/tools/helpers/cmd.sh");
let cmd_path = ic.join("rs/nervous_system/tools/release/cmd.sh");

// Run the command with the required argument.
let output = run_script(cmd_path, &["latest_commit_with_prebuilt_artifacts"], &ic)?;

let commit = if output.status.success() {
let commit = String::from_utf8_lossy(&output.stdout).trim().to_string();
println!(
"A commit with prebuilt artifacts was found with the following command: `./rs/nervous_system/tools/helpers/cmd.sh latest_commit_with_prebuilt_artifacts`."
"A commit with prebuilt artifacts was found with the following command: `./rs/nervous_system/tools/release/cmd.sh latest_commit_with_prebuilt_artifacts`."
);
input_with_default("Commit to release", &commit)?
} else {
Expand All @@ -160,7 +160,7 @@ fn run_pick_commit() -> Result<()> {
);
// get input from user for the commit
input(
"Enter the commit hash, which you can find by running `./rs/nervous_system/tools/helpers/cmd.sh latest_commit_with_prebuilt_artifacts`",
"Enter the commit hash, which you can find by running `./rs/nervous_system/tools/release/cmd.sh latest_commit_with_prebuilt_artifacts`",
)?
};

Expand All @@ -177,7 +177,7 @@ fn run_pick_commit() -> Result<()> {
fn run_determine_targets(cmd: DetermineTargets) -> Result<()> {
let DetermineTargets { commit } = cmd;
println!(
"Now choose which canisters to upgrade. You can run ./rs/nervous_system/tools/helpers/list-new-commits.sh to see the changes for each canister."
"Now choose which canisters to upgrade. You can run ./rs/nervous_system/tools/release/list-new-commits.sh to see the changes for each canister."
);

// Define the candidate canisters.
Expand Down Expand Up @@ -258,7 +258,7 @@ fn run_run_tests(cmd: RunTests) -> Result<()> {
"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

If not, you can also run the upgrade tests manually:
- Follow instructions in: rs/nervous_system/tools/helpers/README.md#upgrade-testing-via-bazel
- Follow instructions in: rs/nervous_system/tools/release/README.md#upgrade-testing-via-bazel

2. SNS Testing Note:
- No manual testing needed for SNS
Expand Down Expand Up @@ -313,7 +313,7 @@ fn run_create_proposal_texts(cmd: CreateProposalTexts) -> Result<()> {
for canister in &nns_canisters {
println!("Creating proposal text for NNS canister: {canister}");
let script =
ic.join("rs/nervous_system/tools/helpers/prepare-nns-upgrade-proposal-text.sh");
ic.join("rs/nervous_system/tools/release/prepare-nns-upgrade-proposal-text.sh");
// cycles minting requires an upgrade arg, usually '()'
let output = if canister != "cycles-minting" {
run_script(script, &[canister, &commit], &ic)
Expand All @@ -340,7 +340,7 @@ fn run_create_proposal_texts(cmd: CreateProposalTexts) -> Result<()> {
for canister in &sns_canisters {
println!("Creating proposal text for SNS canister: {canister}");
let script = ic
.join("rs/nervous_system/tools/helpers/prepare-publish-sns-wasm-proposal-text.sh");
.join("rs/nervous_system/tools/release/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-{canister}.md"));
let file_path_str = file_path.to_str().expect("Invalid file path");
Expand Down Expand Up @@ -443,7 +443,7 @@ fn run_submit_proposals(cmd: SubmitProposals) -> Result<()> {
for proposal_path in &nns_proposal_text_paths {
println!("Submitting NNS proposal: {}", proposal_path.display());
let script =
ic.join("rs/nervous_system/tools/helpers/submit-mainnet-nns-upgrade-proposal.sh");
ic.join("rs/nervous_system/tools/release/submit-mainnet-nns-upgrade-proposal.sh");
let output = run_script_in_current_process(
script,
&[
Expand All @@ -469,7 +469,7 @@ fn run_submit_proposals(cmd: SubmitProposals) -> Result<()> {
for proposal_path in &sns_proposal_text_paths {
println!("Submitting SNS proposal: {}", proposal_path.display());
let script =
ic.join("rs/nervous_system/tools/helpers/submit-mainnet-publish-sns-wasm-proposal.sh");
ic.join("rs/nervous_system/tools/release/submit-mainnet-publish-sns-wasm-proposal.sh");
let output = run_script_in_current_process(
script,
&[
Expand Down Expand Up @@ -538,7 +538,7 @@ fn run_create_forum_post(cmd: CreateForumPost) -> Result<()> {

// --- Generate NNS forum post ---
if !nns_proposal_text_paths.is_empty() {
let script = ic.join("rs/nervous_system/tools/helpers/cmd.sh");
let script = ic.join("rs/nervous_system/tools/release/cmd.sh");
let mut args = vec!["generate_forum_post_nns_upgrades"];
let path_strs: Vec<&str> = nns_proposal_text_paths
.iter()
Expand Down Expand Up @@ -580,7 +580,7 @@ fn run_create_forum_post(cmd: CreateForumPost) -> Result<()> {

// --- Generate SNS forum post ---
if !sns_proposal_text_paths.is_empty() {
let script = ic.join("rs/nervous_system/tools/helpers/cmd.sh");
let script = ic.join("rs/nervous_system/tools/release/cmd.sh");
let mut args = vec!["generate_forum_post_sns_wasm_publish"];
let path_strs: Vec<&str> = sns_proposal_text_paths
.iter()
Expand Down Expand Up @@ -742,7 +742,7 @@ SNS: {}",
// update the changelog for each proposal
for proposal_id in nns_proposal_ids.iter().chain(sns_proposal_ids.iter()) {
println!("Updating changelog for proposal {proposal_id}");
let script = ic.join("rs/nervous_system/tools/helpers/add-release-to-changelog.sh");
let script = ic.join("rs/nervous_system/tools/release/add-release-to-changelog.sh");
let output = run_script(script, &[proposal_id], &ic)?;

if !output.status.success() {
Expand Down
140 changes: 116 additions & 24 deletions rs/nervous_system/tools/release-runscript/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,78 @@ pub(crate) fn input_with_default(text: &str, default: &str) -> Result<String> {
pub(crate) fn open_webpage(url: &Url) -> Result<()> {
println!("Opening webpage: {url}");

#[cfg(target_os = "macos")]
let command = "open";
#[cfg(target_os = "linux")]
let command = "xdg-open";
#[cfg(target_os = "windows")]
let command = "start";
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
let command = "open";

Command::new(command).arg(url.to_string()).spawn()?.wait()?;

Ok(())
}

pub(crate) fn copy(text: &[u8]) -> Result<()> {
let mut copy = Command::new("pbcopy").stdin(Stdio::piped()).spawn()?;
copy.stdin
.take()
.ok_or(anyhow::anyhow!("Failed to take stdin"))?
.write_all(text)?;
copy.wait()?;
#[cfg(target_os = "macos")]
{
let mut copy = Command::new("pbcopy").stdin(Stdio::piped()).spawn()?;
copy.stdin
.take()
.ok_or(anyhow::anyhow!("Failed to take stdin"))?
.write_all(text)?;
copy.wait()?;
}
#[cfg(target_os = "linux")]
{
// Try xclip first, then xsel as fallback
let mut copy = if Command::new("xclip")
.arg("-version")
.output()
.is_ok_and(|o| o.status.success())
{
Command::new("xclip")
.arg("-selection")
.arg("clipboard")
.stdin(Stdio::piped())
.spawn()?
} else if Command::new("xsel")
.arg("--version")
.output()
.is_ok_and(|o| o.status.success())
{
Command::new("xsel")
.arg("--clipboard")
.arg("--input")
.stdin(Stdio::piped())
.spawn()?
} else {
println!("{}", "Warning: Neither xclip nor xsel is installed. Text will not be copied to clipboard.".bright_yellow());
println!("Please install xclip or xsel, or manually copy the text.");
return Ok(());
};
copy.stdin
.take()
.ok_or(anyhow::anyhow!("Failed to take stdin"))?
.write_all(text)?;
copy.wait()?;
}
#[cfg(target_os = "windows")]
{
// On Windows, we can use clip.exe
let mut copy = Command::new("clip").stdin(Stdio::piped()).spawn()?;
copy.stdin
.take()
.ok_or(anyhow::anyhow!("Failed to take stdin"))?
.write_all(text)?;
copy.wait()?;
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
println!("{}", "Warning: Clipboard copy not supported on this platform. Please manually copy the text.".bright_yellow());
}

Ok(())
}
Expand Down Expand Up @@ -95,28 +154,54 @@ pub(crate) fn press_enter_to_continue() -> Result<()> {
}

pub(crate) fn ensure_coreutils_setup() -> Result<()> {
let output = Command::new("brew").arg("list").output()?;
if !output.status.success() {
// If they don't even have brew installed, we can't ensure anything. Let's just ask them if they want to continue.
#[cfg(target_os = "macos")]
{
let output = Command::new("brew").arg("list").output()?;
if !output.status.success() {
// If they don't even have brew installed, we can't ensure anything. Let's just ask them if they want to continue.
println!(
"{}",
"brew is not installed. This is not necessarily a problem, but it is suspicious."
.bright_yellow()
);
press_enter_to_continue()?;
return Ok(());
}

// If they do have brew installed, let's make sure coreutils is installed.
let stdout = String::from_utf8(output.stdout)?;
if !stdout.contains("coreutils") {
bail!(
"'coreutils' is not installed. This is not necessarily a problem, but you may encounter issues running some of the bash scripts which are written by developers that generally will have coreutils installed. Try running `brew install coreutils`."
)
}

println!("{}", "brew and coreutils installed ✓".bright_green());
}
#[cfg(target_os = "linux")]
{
// On Linux, coreutils is typically installed by default
// Check if coreutils commands are available
let output = Command::new("which").arg("cp").output();
if output.is_err() || !output.unwrap().status.success() {
println!(
"{}",
"Warning: coreutils may not be installed. This is unusual on Linux."
.bright_yellow()
);
press_enter_to_continue()?;
} else {
println!("{}", "coreutils available ✓".bright_green());
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
println!(
"{}",
"brew is not installed. This is not necessarily a problem, but it is suspicious."
.bright_yellow()
"Skipping coreutils check on this platform.".bright_yellow()
);
press_enter_to_continue()?;
return Ok(());
}

// If they do have brew installed, let's make sure coreutils is installed.
let stdout = String::from_utf8(output.stdout)?;
if !stdout.contains("coreutils") {
bail!(
"'coreutils' is not installed. This is not necessarily a problem, but you may encounter issues running some of the bash scripts which are written by developers that generally will have coreutils installed. Try running `brew install coreutils`."
)
}

println!("{}", "brew and coreutils installed ✓".bright_green());

Ok(())
}

Expand All @@ -141,7 +226,14 @@ pub(crate) fn ensure_gh_setup() -> Result<()> {
// Check if gh is installed
let output = Command::new("gh").arg("--version").output()?;
if !output.status.success() {
bail!("gh is not installed. Try installing with `brew install gh`")
#[cfg(target_os = "macos")]
bail!("gh is not installed. Try installing with `brew install gh`");
#[cfg(target_os = "linux")]
bail!(
"gh is not installed. Try installing with your package manager (e.g., `sudo apt install gh` or `sudo dnf install gh`)"
);
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
bail!("gh is not installed. Please install the GitHub CLI for your platform.");
}

// Check if the user is logged in to gh
Expand Down
10 changes: 8 additions & 2 deletions rs/nervous_system/tools/release/lib/canister_wasms.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,17 @@ _download_canister_gz() {
DOWNLOAD_URL="https://download.dfinity.systems/ic/${GIT_HASH}/canisters/${DOWNLOAD_NAME}.wasm.gz"
OUTPUT_FILE="$MY_DOWNLOAD_DIR/$DOWNLOAD_NAME-$GIT_HASH.wasm.gz"

curl \
# Download the file, but don't fail the script if it doesn't exist (404)
# The caller will check if the file exists and is valid by trying to ungzip it
if ! curl \
"${DOWNLOAD_URL}" \
--output "${OUTPUT_FILE}" \
--fail \
--silent
--silent \
2>/dev/null; then
# If download failed, ensure the file doesn't exist so caller can detect failure
rm -f "${OUTPUT_FILE}"
fi

echo "${OUTPUT_FILE}"
}
Expand Down
26 changes: 22 additions & 4 deletions rs/nervous_system/tools/release/lib/util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,26 @@ hex_to_idl_byte_array() {
# TODO deduplicate this from icos_deploy.sh by moving into lib.sh
disk_image_exists() {
GIT_REVISION=$1
# Check for update-img.tar.zst (current path where disk images are uploaded)
curl --output /dev/null --silent --head --fail \
"https://download.dfinity.systems/ic/${GIT_REVISION}/guest-os/disk-img-dev/disk-img.tar.zst" \
"https://download.dfinity.systems/ic/${GIT_REVISION}/guest-os/update-img/update-img.tar.zst" \
|| curl --output /dev/null --silent --head --fail \
"https://download.dfinity.systems/ic/${GIT_REVISION}/guest-os/update-img-dev/update-img.tar.zst" \
|| curl --output /dev/null --silent --head --fail \
"https://download.dfinity.systems/ic/${GIT_REVISION}/guest-os/disk-img-dev/disk-img.tar.zst" \
|| curl --output /dev/null --silent --head --fail \
"https://download.dfinity.systems/ic/${GIT_REVISION}/guest-os/disk-img.tar.zst"
}

##: latest_commit_with_prebuilt_artifacts
## Gets the latest git commit with a prebuilt governance canister WASM and a disk image
## Gets the latest git commit with a prebuilt governance canister WASM and optionally a disk image
## Usage: latest_commit_with_prebuilt_artifacts [--require-disk-image]
## --require-disk-image: Also require disk image to exist (default: false, only WASM required)
latest_commit_with_prebuilt_artifacts() {
REQUIRE_DISK_IMAGE=false
if [ "${1:-}" = "--require-disk-image" ]; then
REQUIRE_DISK_IMAGE=true
fi

IC_REPO=$(repo_root)
pushd "$IC_REPO" >/dev/null
Expand All @@ -103,10 +114,17 @@ latest_commit_with_prebuilt_artifacts() {

for HASH in $RECENT_CHANGES; do
echo >&2 "Checking $HASH..."
GZ=$(_download_canister_gz "governance-canister" "$HASH")
GZ=$(_download_canister_gz "node-rewards-canister" "$HASH")

if ungzip "$GZ" >/dev/null 2>&1; then
if disk_image_exists "$HASH"; then
# If disk image is required, check for it; otherwise, just return the commit with WASM
if [ "$REQUIRE_DISK_IMAGE" = "true" ]; then
if disk_image_exists "$HASH"; then
echo "$HASH"
return 0
fi
else
# WASM exists and is valid, return this commit
echo "$HASH"
return 0
fi
Expand Down
Loading