Skip to content

Internal tools: cut-release #853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Mar 22, 2025
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
5 changes: 4 additions & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[alias]
42 = "xtask 42"
xtask = "run --package xtask --bin xtask --"
prep = "xtask r"
prep = "xtask reportgen"
patch = "xtask cut-release --patch"
minor = "xtask cut-release --minor"
fixpr = "xtask fixpr"
28 changes: 17 additions & 11 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ changed or not.
## Requirements

- [cargo-release](https://github.com/crate-ci/cargo-release): `cargo install cargo-release`
- [gh](https://cli.github.com/): `brew install gh`

Stay logged in with `gh auth login`

## Pre-requisites

Expand All @@ -17,24 +20,27 @@ changed or not.

## Steps

- [ ] Cut a release from `dev` branch.
- [ ] Run `git checkout dev && git pull`
- [ ] Run `cargo release patch --no-publish --exclude xtask --no-tag` to see a dry run of all the incremented versions.
- [ ] Run `cargo release patch --no-publish --exclude xtask --execute` and expect the following in CI:
- [ ] Sarif report tests fail because of version mismatch.
- [ ] After the building of global artifacts, library crates fail to publish.
- [ ] Binary crate `aderyn` is successfully published.
- [ ] Wait for the CI/CD process to be over. (Important before proceeding)
- [ ] Run `cli/reportgen.sh` to regenerate sarif-report locally, then commit & push.
- [ ] Verify now that all the tests pass.
- [ ] Cut a release.
- [ ] Switch to dev branch and pull latest changes.
- [ ] Run `cargo patch` and wait for the release process to bo over. (Important before proceeding)
- [ ] Run `cargo fixpr` to regenerate sarif report and push the changes.
- [ ] Generate Release Notes in the Github's Release page

- **NOTE: Expect the following in CI**:
* Sarif report tests fail because of version mismatch.
* After the building of global artifacts, library crates fail to publish.
* Binary crate `aderyn` is successfully published.

- [ ] Create a checkpoint on `master`.
- [ ] Run `git checkout master && git pull`
- [ ] Run `git checkout -b master-merge-<version-to-be-released>`.
- [ ] Merge `dev` into it and preserve all changes in `dev`. `git merge --squash -X theirs dev`.
- [ ] Sometimes if conflicts are not auto-resolved, pick to keep all the changes from `dev`
- [ ] Merge it back to master by creating a PR (Verify CI tests)

- [ ] Switch back to `dev` branch with `git checkout dev`.

> NOTE: Replace `patch` with `minor` or `major` based on the needs.
> NOTE: Replace `patch` with `minor` based on the needs.

## Breaking change

Expand Down
132 changes: 132 additions & 0 deletions tools/xtask/src/cut_release.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::{io::BufRead, time::Duration};

use xshell::{Shell, cmd};

use crate::flags::CutRelease;

pub fn cut_release(cut_release: CutRelease) -> anyhow::Result<()> {
let sh = Shell::new()?;
sh.change_dir(env!("CARGO_MANIFEST_DIR"));
sh.change_dir("../../");

// Wait for existing release completion
wait_for_release_completion(&sh)?;

// Sanity checks and syncs
sync_tags(&sh)?;
perform_prechecks(&sh)?;

// Release process
dry_run(&sh, &cut_release)?;
kick_off_release(&sh, &cut_release)?;

// Wait for release completion
wait_for_release_completion(&sh)?;

Ok(())
}

fn wait_for_release_completion(sh: &Shell) -> anyhow::Result<()> {
let poll_time = Duration::from_secs(12);
println!("A relase or a release plan is in progress ... [next poll: {}s]", poll_time.as_secs());

// Check if actions are still pending
let actions_completed = {
let cmd = cmd!(sh, "gh run list --workflow release.yml");
let x = cmd.output()?.stdout.to_vec();
let stdout = String::from_utf8_lossy(&x);
stdout.lines().filter(|l| !l.is_empty()).all(|l| l.starts_with("completed"))
};

if !actions_completed {
std::thread::sleep(Duration::from_secs(12));
wait_for_release_completion(sh)?;
return Ok(());
}

Ok(())
}

fn kick_off_release(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
let execute_cmd = if cut_release.patch {
cmd!(sh, "cargo release patch --no-publish --exclude xtask --execute")
} else if cut_release.minor {
cmd!(sh, "cargo release minor --no-publish --exclude xtask --execute")
} else {
unreachable!()
};

println!("Kick off the release process\n\taderyn\n?[y/N]");
let mut line = String::new();
let stdin = std::io::stdin();
stdin.lock().read_line(&mut line).unwrap();

if line.contains("y") {
println!("Kicked-off release process!");
let d = execute_cmd.stdin(line.clone());
d.run()?;
} else {
println!("Declined release process!");
}

Ok(())
}

fn dry_run(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
let dry_run_command = if cut_release.patch {
cmd!(sh, "cargo release patch --no-publish --exclude xtask --no-tag")
} else if cut_release.minor {
cmd!(sh, "cargo release minor --no-publish --exclude xtask --no-tag")
} else {
unreachable!();
};
dry_run_command.run()?;
Ok(())
}

fn sync_tags(sh: &Shell) -> anyhow::Result<()> {
let sync = cmd!(sh, "git fetch --prune-tags origin");
sync.run()?;
Ok(())
}

fn perform_prechecks(sh: &Shell) -> anyhow::Result<()> {
// Exit if not on dev branch
let curr_branch = {
let cmd = cmd!(sh, "git rev-parse --abbrev-ref HEAD");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if curr_branch != "dev" {
eprintln!("Please switch to dev branch and retry!");
std::process::exit(1);
}

// Error out if there are untracked files/staging changes
let uncommited_stuff = {
let cmd = cmd!(sh, "git status --porcelain");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if !uncommited_stuff.is_empty() {
eprintln!("Please clear your staging area and retry!");
std::process::exit(1);
}

// Error if dev branch is not in sync with remote
let local_commit_hash = {
let cmd = cmd!(sh, "git rev-parse dev");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
let remote_commit_hash = {
let cmd = cmd!(sh, "git rev-parse dev");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if remote_commit_hash != local_commit_hash {
eprintln!("dev branch is not in sync with origin. Do that and retry!");
std::process::exit(1);
}
Ok(())
}
46 changes: 46 additions & 0 deletions tools/xtask/src/fixpr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use xshell::{Shell, cmd};

pub fn fixpr() -> anyhow::Result<()> {
let sh = Shell::new()?;
sh.change_dir(env!("CARGO_MANIFEST_DIR"));
sh.change_dir("../../");

// Run cargo build (sanity test)
let cmd = cmd!(sh, "cargo build");
cmd.run()?;

// Fix format
let cmd = cmd!(sh, "cargo +nightly fmt --all");
cmd.run()?;

// Check fixed format
let cmd = cmd!(sh, "cargo +nightly fmt --all --check");
cmd.run()?;

// Fix clippy
let cmd = cmd!(
sh,
"cargo clippy --quiet --workspace --all-targets --all-features --allow-dirty --fix"
);
cmd.run()?;

// Check clippy
let cmd = cmd!(sh, "cargo clippy").arg("--").arg("-D").arg("warnings");
cmd.run()?;

// Create reportgen
let cmd = cmd!(sh, "chmod +x ./cli/reportgen.sh");
cmd.run()?;
let cmd = cmd!(sh, "./cli/reportgen.sh");
cmd.run()?;

// Push changes
let cmd = cmd!(sh, "git add .");
cmd.run()?;
let cmd = cmd!(sh, "git commit -am").arg("chore: cargo fixpr");
cmd.run()?;
let cmd = cmd!(sh, "git push");
cmd.run()?;

Ok(())
}
26 changes: 23 additions & 3 deletions tools/xtask/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ xflags::xflags! {
src "./src/flags.rs"

cmd xtask {
cmd r {
cmd cut-release {
/// Cut a patch release
optional -p, --patch

/// Cut a minor release
optional -m, --minor
}
cmd fixpr {
}
cmd reportgen {
optional -a, --all

/// Sablier
Expand Down Expand Up @@ -51,11 +60,22 @@ pub struct Xtask {

#[derive(Debug)]
pub enum XtaskCmd {
R(R),
CutRelease(CutRelease),
Fixpr(Fixpr),
Reportgen(Reportgen),
}

#[derive(Debug)]
pub struct CutRelease {
pub patch: bool,
pub minor: bool,
}

#[derive(Debug)]
pub struct R {
pub struct Fixpr;

#[derive(Debug)]
pub struct Reportgen {
pub all: bool,
pub sablier: bool,
pub tg: bool,
Expand Down
94 changes: 6 additions & 88 deletions tools/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,16 @@

#![allow(unreachable_pub, unexpected_cfgs)]

use xshell::{Shell, cmd};

mod cut_release;
mod fixpr;
mod flags;

fn run_command(args: &str, release: bool) -> anyhow::Result<()> {
let sh = Shell::new()?;
let mut cmd = cmd!(sh, "cargo run");
if release {
cmd = cmd.arg("--release");
}
cmd = cmd.arg("--").arg("--skip-update-check");
cmd.args(args.split(" ")).run()?;
Ok(())
}

fn run_command_with_env(args: &str, key: &str, val: &str, release: bool) -> anyhow::Result<()> {
let sh = Shell::new()?;
let mut cmd = cmd!(sh, "cargo run");
cmd = cmd.env(key, val);
if release {
cmd = cmd.arg("--release");
}
cmd = cmd.arg("--").arg("--skip-update-check");
cmd.args(args.split(" ")).run()?;
Ok(())
}
mod reportgen;

fn main() -> anyhow::Result<()> {
let flags = flags::Xtask::from_env_or_exit();
match flags.subcommand {
flags::XtaskCmd::R(choice) => {
if choice.cpg || choice.all {
run_command(
"-i src/ -x lib/ ./tests/contract-playground -o ./reports/report.md",
choice.release,
)?;
}
if choice.adhoc || choice.all {
run_command(
"./tests/adhoc-sol-files -o ./reports/adhoc-sol-files-report.md",
choice.release,
)?;
}
if choice.sablier || choice.all {
run_command(
"./tests/2024-05-Sablier -o ./reports/sablier-aderyn-toml-nested-root.md",
choice.release,
)?;
}
if choice.fnft || choice.all {
run_command(
"./tests/foundry-nft-f23 -i src/ -x lib/ -o ./reports/nft-report.md",
choice.release,
)?;
}
if choice.fnft_icm || choice.all {
run_command(
"./tests/foundry-nft-f23-icm -o ./reports/nft-report-icm.md",
choice.release,
)?;
}

if choice.ccip || choice.all {
run_command(
"tests/ccip-contracts/contracts --src src/v0.8/functions/ -x tests/,test/,mocks/ -o ./reports/ccip-functions-report.md",
choice.release,
)?;
}
if choice.cpgu || choice.all {
run_command_with_env(
"tests/contract-playground/ -o ./reports/uniswap_profile.md",
"FOUNDRY_PROFILE",
"uniswap",
choice.release,
)?;
}
if choice.prb_math || choice.all {
run_command("tests/prb-math -o reports/prb-math-report.md", choice.release)?;
}
if choice.tg || choice.all {
run_command(
"tests/2024-07-templegold/protocol -o reports/templegold-report.md",
choice.release,
)?;
}
if choice.hhpg || choice.all {
run_command(
"tests/hardhat-js-playground -o reports/hardhat-playground-report.md",
choice.release,
)?;
}
}
flags::XtaskCmd::Reportgen(choice) => reportgen::reportgen(choice),
flags::XtaskCmd::CutRelease(cut_release) => cut_release::cut_release(cut_release),
flags::XtaskCmd::Fixpr(..) => fixpr::fixpr(),
}
Ok(())
}
Loading
Loading