xtask: Add dry-run option for release task
This commit is contained in:
parent
1f23e8abcb
commit
c647d39c9e
@ -29,14 +29,16 @@ pub struct Package {
|
|||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
/// Update the version of this crate.
|
/// Update the version of this crate.
|
||||||
pub fn update_version(&mut self, version: &Version) -> Result<()> {
|
pub fn update_version(&mut self, version: &Version, dry_run: bool) -> Result<()> {
|
||||||
println!("Updating {} to version {}…", self.name, version);
|
println!("Updating {} to version {}…", self.name, version);
|
||||||
|
|
||||||
let mut document = read_file(&self.manifest_path)?.parse::<Document>()?;
|
if !dry_run {
|
||||||
|
let mut document = read_file(&self.manifest_path)?.parse::<Document>()?;
|
||||||
|
|
||||||
document["package"]["version"] = value(version.to_string());
|
document["package"]["version"] = value(version.to_string());
|
||||||
|
|
||||||
write_file(&self.manifest_path, document.to_string())?;
|
write_file(&self.manifest_path, document.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
self.version = version.clone();
|
self.version = version.clone();
|
||||||
|
|
||||||
@ -45,34 +47,36 @@ impl Package {
|
|||||||
|
|
||||||
/// Update the version of this crate in dependant crates' manifests, with the given version
|
/// Update the version of this crate in dependant crates' manifests, with the given version
|
||||||
/// prefix.
|
/// prefix.
|
||||||
pub(crate) fn update_dependants(&self, metadata: &Metadata) -> Result<()> {
|
pub(crate) fn update_dependants(&self, metadata: &Metadata, dry_run: bool) -> Result<()> {
|
||||||
for package in metadata.packages.iter().filter(|p| {
|
for package in metadata.packages.iter().filter(|p| {
|
||||||
p.manifest_path.starts_with(&metadata.workspace_root)
|
p.manifest_path.starts_with(&metadata.workspace_root)
|
||||||
&& p.dependencies.iter().any(|d| d.name == self.name)
|
&& p.dependencies.iter().any(|d| d.name == self.name)
|
||||||
}) {
|
}) {
|
||||||
println!("Updating dependency in {} crate…", package.name);
|
println!("Updating dependency in {} crate…", package.name);
|
||||||
|
|
||||||
let mut document = read_file(&package.manifest_path)?.parse::<Document>()?;
|
if !dry_run {
|
||||||
|
let mut document = read_file(&package.manifest_path)?.parse::<Document>()?;
|
||||||
|
|
||||||
let version = if !self.version.pre.is_empty()
|
let version = if !self.version.pre.is_empty()
|
||||||
|| self.name.strip_suffix("-macros") == Some(&package.name)
|
|| self.name.strip_suffix("-macros") == Some(&package.name)
|
||||||
{
|
{
|
||||||
format!("={}", self.version)
|
format!("={}", self.version)
|
||||||
} else {
|
} else {
|
||||||
self.version.to_string()
|
self.version.to_string()
|
||||||
};
|
|
||||||
|
|
||||||
for dependency in package.dependencies.iter().filter(|d| d.name == self.name) {
|
|
||||||
let kind = match dependency.kind {
|
|
||||||
Some(DependencyKind::Dev) => "dev-dependencies",
|
|
||||||
Some(DependencyKind::Build) => "build-dependencies",
|
|
||||||
None => "dependencies",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document[kind][&self.name]["version"] = value(version.as_str());
|
for dependency in package.dependencies.iter().filter(|d| d.name == self.name) {
|
||||||
}
|
let kind = match dependency.kind {
|
||||||
|
Some(DependencyKind::Dev) => "dev-dependencies",
|
||||||
|
Some(DependencyKind::Build) => "build-dependencies",
|
||||||
|
None => "dependencies",
|
||||||
|
};
|
||||||
|
|
||||||
write_file(&package.manifest_path, document.to_string())?;
|
document[kind][&self.name]["version"] = value(version.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file(&package.manifest_path, document.to_string())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -139,7 +143,7 @@ impl Package {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Publish this package on crates.io.
|
/// Publish this package on crates.io.
|
||||||
pub fn publish(&self, client: &HttpClient) -> Result<bool> {
|
pub fn publish(&self, client: &HttpClient, dry_run: bool) -> Result<bool> {
|
||||||
println!("Publishing {} {} on crates.io…", self.name, self.version);
|
println!("Publishing {} {} on crates.io…", self.name, self.version);
|
||||||
let _dir = pushd(&self.manifest_path.parent().unwrap())?;
|
let _dir = pushd(&self.manifest_path.parent().unwrap())?;
|
||||||
|
|
||||||
@ -150,7 +154,9 @@ impl Package {
|
|||||||
Err("Release interrupted by user.".into())
|
Err("Release interrupted by user.".into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cmd!("cargo publish").run()?;
|
if !dry_run {
|
||||||
|
cmd!("cargo publish").run()?;
|
||||||
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ fn main() -> Result<()> {
|
|||||||
Command::Doc(doc) => doc.run(),
|
Command::Doc(doc) => doc.run(),
|
||||||
#[cfg(feature = "default")]
|
#[cfg(feature = "default")]
|
||||||
Command::Release(args) => {
|
Command::Release(args) => {
|
||||||
let mut task = ReleaseTask::new(args.package, args.version)?;
|
let mut task = ReleaseTask::new(args.package, args.version, args.dry_run)?;
|
||||||
task.run()
|
task.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ pub struct ReleaseArgs {
|
|||||||
|
|
||||||
/// The new version of the crate
|
/// The new version of the crate
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
|
||||||
|
/// List the steps but don't actually change anything
|
||||||
|
#[clap(long)]
|
||||||
|
pub dry_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Task to create a new release of the given crate.
|
/// Task to create a new release of the given crate.
|
||||||
@ -44,11 +48,14 @@ pub struct ReleaseTask {
|
|||||||
|
|
||||||
/// The github configuration required to publish a release.
|
/// The github configuration required to publish a release.
|
||||||
config: GithubConfig,
|
config: GithubConfig,
|
||||||
|
|
||||||
|
/// List the steps but don't actually change anything
|
||||||
|
pub dry_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReleaseTask {
|
impl ReleaseTask {
|
||||||
/// Create a new `ReleaseTask` with the given `name` and `version`.
|
/// Create a new `ReleaseTask` with the given `name` and `version`.
|
||||||
pub(crate) fn new(name: String, version: Version) -> Result<Self> {
|
pub(crate) fn new(name: String, version: Version, dry_run: bool) -> Result<Self> {
|
||||||
let metadata = Metadata::load()?;
|
let metadata = Metadata::load()?;
|
||||||
|
|
||||||
let package = metadata
|
let package = metadata
|
||||||
@ -62,17 +69,15 @@ impl ReleaseTask {
|
|||||||
|
|
||||||
let http_client = HttpClient::new()?;
|
let http_client = HttpClient::new()?;
|
||||||
|
|
||||||
Ok(Self { metadata, package, version, http_client, config })
|
Ok(Self { metadata, package, version, http_client, config, dry_run })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the task to effectively create a release.
|
/// Run the task to effectively create a release.
|
||||||
pub(crate) fn run(&mut self) -> Result<()> {
|
pub(crate) fn run(&mut self) -> Result<()> {
|
||||||
if self.package.name == "ruma-macros" {
|
if self.package.name == "ruma-macros" {
|
||||||
return Err(
|
return Err(
|
||||||
"The ruma-macros crate is always released together with the ruma-common crate.\n\
|
"The ruma-macros crate is always released together with the ruma-common crate. \
|
||||||
To release both, simply run\n\
|
To release both, simply run `cargo xtask release ruma-common`"
|
||||||
\n\
|
|
||||||
cargo xtask release ruma-common"
|
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -123,14 +128,14 @@ impl ReleaseTask {
|
|||||||
if let Some(m) = macros.as_mut() {
|
if let Some(m) = macros.as_mut() {
|
||||||
println!("Updating version of ruma-macros crate…");
|
println!("Updating version of ruma-macros crate…");
|
||||||
|
|
||||||
m.update_version(&self.version)?;
|
m.update_version(&self.version, self.dry_run)?;
|
||||||
m.update_dependants(&self.metadata)?;
|
m.update_dependants(&self.metadata, self.dry_run)?;
|
||||||
|
|
||||||
println!("Resuming release of {}…", self.title());
|
println!("Resuming release of {}…", self.title());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.package.update_version(&self.version)?;
|
self.package.update_version(&self.version, self.dry_run)?;
|
||||||
self.package.update_dependants(&self.metadata)?;
|
self.package.update_dependants(&self.metadata, self.dry_run)?;
|
||||||
true
|
true
|
||||||
} else if !ask_yes_no(&format!(
|
} else if !ask_yes_no(&format!(
|
||||||
"Package is already version {}. Skip creating a commit and continue?",
|
"Package is already version {}. Skip creating a commit and continue?",
|
||||||
@ -141,16 +146,16 @@ impl ReleaseTask {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let changes = &self.package.changes(!prerelease)?;
|
let changes = &self.package.changes(!prerelease && !self.dry_run)?;
|
||||||
|
|
||||||
if create_commit {
|
if create_commit {
|
||||||
self.commit()?;
|
self.commit()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(m) = macros {
|
if let Some(m) = macros {
|
||||||
let published = m.publish(&self.http_client)?;
|
let published = m.publish(&self.http_client, self.dry_run)?;
|
||||||
|
|
||||||
if published {
|
if published && !self.dry_run {
|
||||||
// Crate was published, instead of publishing skipped (because release already
|
// Crate was published, instead of publishing skipped (because release already
|
||||||
// existed).
|
// existed).
|
||||||
println!("Waiting 20 seconds for the release to make it into the crates.io index…");
|
println!("Waiting 20 seconds for the release to make it into the crates.io index…");
|
||||||
@ -158,12 +163,14 @@ impl ReleaseTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.package.publish(&self.http_client)?;
|
self.package.publish(&self.http_client, self.dry_run)?;
|
||||||
|
|
||||||
let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
|
let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
|
||||||
if publish_only {
|
if publish_only {
|
||||||
println!("Pushing to remote repository…");
|
println!("Pushing to remote repository…");
|
||||||
cmd!("git push {remote} {branch}").run()?;
|
if !self.dry_run {
|
||||||
|
cmd!("git push {remote} {branch}").run()?;
|
||||||
|
}
|
||||||
|
|
||||||
println!("Crate published successfully!");
|
println!("Crate published successfully!");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -171,16 +178,20 @@ impl ReleaseTask {
|
|||||||
|
|
||||||
let tag = &self.tag_name();
|
let tag = &self.tag_name();
|
||||||
|
|
||||||
println!("Creating git tag…");
|
println!("Creating git tag '{tag}'…");
|
||||||
if cmd!("git tag -l {tag}").read()?.is_empty() {
|
if cmd!("git tag -l {tag}").read()?.is_empty() {
|
||||||
cmd!("git tag -s {tag} -m {title} -m {changes}").read()?;
|
if !self.dry_run {
|
||||||
|
cmd!("git tag -s {tag} -m {title} -m {changes}").read()?;
|
||||||
|
}
|
||||||
} else if !ask_yes_no("This tag already exists. Skip this step and continue?")? {
|
} else if !ask_yes_no("This tag already exists. Skip this step and continue?")? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Pushing to remote repository…");
|
println!("Pushing to remote repository…");
|
||||||
if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
|
if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
|
||||||
cmd!("git push {remote} {branch} {tag}").run()?;
|
if !self.dry_run {
|
||||||
|
cmd!("git push {remote} {branch} {tag}").run()?;
|
||||||
|
}
|
||||||
} else if !ask_yes_no("This tag has already been pushed. Skip this step and continue?")? {
|
} else if !ask_yes_no("This tag has already been pushed. Skip this step and continue?")? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -198,7 +209,9 @@ impl ReleaseTask {
|
|||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
self.release(request_body)?;
|
if !self.dry_run {
|
||||||
|
self.release(request_body)?;
|
||||||
|
}
|
||||||
|
|
||||||
println!("Release created successfully!");
|
println!("Release created successfully!");
|
||||||
|
|
||||||
@ -231,43 +244,48 @@ impl ReleaseTask {
|
|||||||
fn commit(&self) -> Result<()> {
|
fn commit(&self) -> Result<()> {
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
|
|
||||||
let instructions = "Ready to commit the changes. [continue/abort/diff]: ";
|
if !self.dry_run {
|
||||||
print!("{}", instructions);
|
let instructions = "Ready to commit the changes. [continue/abort/diff]: ";
|
||||||
stdout().flush()?;
|
|
||||||
|
|
||||||
let mut handle = stdin.lock();
|
|
||||||
|
|
||||||
let mut input = String::new();
|
|
||||||
loop {
|
|
||||||
let eof = handle.read_line(&mut input)? == 0;
|
|
||||||
if eof {
|
|
||||||
return Err("User aborted commit".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
match input.trim().to_ascii_lowercase().as_str() {
|
|
||||||
"c" | "con" | "continue" => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
"a" | "abort" => {
|
|
||||||
return Err("User aborted commit".into());
|
|
||||||
}
|
|
||||||
"d" | "diff" => {
|
|
||||||
cmd!("git diff").run()?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("Unknown command.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print!("{}", instructions);
|
print!("{}", instructions);
|
||||||
stdout().flush()?;
|
stdout().flush()?;
|
||||||
|
|
||||||
input.clear();
|
let mut handle = stdin.lock();
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
loop {
|
||||||
|
let eof = handle.read_line(&mut input)? == 0;
|
||||||
|
if eof {
|
||||||
|
return Err("User aborted commit".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match input.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"c" | "con" | "continue" => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
"a" | "abort" => {
|
||||||
|
return Err("User aborted commit".into());
|
||||||
|
}
|
||||||
|
"d" | "diff" => {
|
||||||
|
cmd!("git diff").run()?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Unknown command.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("{}", instructions);
|
||||||
|
stdout().flush()?;
|
||||||
|
|
||||||
|
input.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = format!("Release {}", self.title());
|
let message = format!("Release {}", self.title());
|
||||||
|
|
||||||
println!("Creating commit…");
|
println!("Creating commit with message '{message}'…");
|
||||||
cmd!("git commit -a -m {message}").read()?;
|
|
||||||
|
if !self.dry_run {
|
||||||
|
cmd!("git commit -a -m {message}").read()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user