Skip to content

Commit

Permalink
feat: add bin set-version
Browse files Browse the repository at this point in the history
  • Loading branch information
hdevalke committed Aug 4, 2020
1 parent 034f6ef commit 1d454c7
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ name = "cargo-rm"
path = "src/bin/rm/main.rs"
required-features = ["rm"]

[[bin]]
name = "cargo-set-version"
path = "src/bin/set-version/main.rs"
required-features = ["set-version"]

[[bin]]
name = "cargo-upgrade"
path = "src/bin/upgrade/main.rs"
Expand Down Expand Up @@ -84,9 +89,12 @@ default = [
"add",
"rm",
"upgrade",
"set-version",
]

add = ["cli"]
rm = ["cli"]
upgrade = ["cli"]
set-version = ["cli"]
cli = ["atty", "structopt"]
test-external-apis = []
207 changes: 207 additions & 0 deletions src/bin/set-version/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//! `cargo set-version`
#![warn(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]

#[macro_use]
extern crate error_chain;

use crate::errors::*;
use cargo_edit::{find, LocalManifest};
use failure::Fail;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
use structopt::StructOpt;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use semver::Version;

mod errors {
error_chain! {
links {
CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
}
foreign_links {
CargoMetadata(::failure::Compat<::cargo_metadata::Error>);
}
}
}

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
enum Command {
/// set-version dependencies as specified in the local manifest file (i.e. Cargo.toml).
#[structopt(name = "set-version")]
#[structopt(
after_help = "This command differs from `cargo update`, which updates the dependency versions recorded in the
local lock file (Cargo.lock).
If `<dependency>`(s) are provided, only the specified dependencies will be upgraded. The version to
upgrade to for each can be specified with e.g. `[email protected]` or `serde@>=0.9,<2.0`.
Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are
supported. Git/path dependencies will be ignored.
All packages in the workspace will be upgraded if the `--all` flag is supplied. The `--all` flag may
be supplied in the presence of a virtual manifest.
If the '--to-lockfile' flag is supplied, all dependencies will be upgraded to the currently locked
version as recorded in the Cargo.lock file. This flag requires that the Cargo.lock file is
up-to-date. If the lock file is missing, or it needs to be updated, cargo-upgrade will exit with an
error. If the '--to-lockfile' flag is supplied then the network won't be accessed."
)]
SetVersion(Args),
}

#[derive(Debug, StructOpt)]
struct Args {
/// Path to the manifest to set version
#[structopt(long = "manifest-path", value_name = "path")]
manifest_path: Option<PathBuf>,

/// Set version of all packages in the workspace.
#[structopt(long = "all")]
all: bool,

/// Set a specific semantic version.
#[structopt(long = "version")]
version: Version,

/// Print changes to be made without making them.
#[structopt(long = "dry-run")]
dry_run: bool,
}

/// A collection of manifests.
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);

fn dry_run_message() -> Result<()> {
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
let mut buffer = bufwtr.buffer();
buffer
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
.chain_err(|| "Failed to set output colour")?;
write!(&mut buffer, "Starting dry run. ").chain_err(|| "Failed to write dry run message")?;
buffer
.set_color(&ColorSpec::new())
.chain_err(|| "Failed to clear output colour")?;
writeln!(&mut buffer, "Changes will not be saved.")
.chain_err(|| "Failed to write dry run message")?;
bufwtr
.print(&buffer)
.chain_err(|| "Failed to print dry run message")
}

impl Manifests {
/// Get all manifests in the workspace.
fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.no_deps();
if let Some(path) = manifest_path {
cmd.manifest_path(path);
}
let result = cmd.exec().map_err(|e| {
Error::from(e.compat()).chain_err(|| "Failed to get workspace metadata")
})?;
result
.packages
.into_iter()
.map(|package| {
Ok((
LocalManifest::try_new(Path::new(&package.manifest_path))?,
package,
))
})
.collect::<Result<Vec<_>>>()
.map(Manifests)
}

/// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
/// provided.
fn get_local_one(manifest_path: &Option<PathBuf>) -> Result<Self> {
let resolved_manifest_path: String = find(&manifest_path)?.to_string_lossy().into();

let manifest = LocalManifest::find(&manifest_path)?;

let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.no_deps();
if let Some(path) = manifest_path {
cmd.manifest_path(path);
}
let result = cmd
.exec()
.map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
let packages = result.packages;
let package = packages
.iter()
.find(|p| p.manifest_path.to_string_lossy() == resolved_manifest_path)
// If we have successfully got metadata, but our manifest path does not correspond to a
// package, we must have been called against a virtual manifest.
.chain_err(|| {
"Found virtual manifest, but this command requires running against an \
actual package in this workspace. Try adding `--all`."
})?;

Ok(Manifests(vec![(manifest, package.to_owned())]))
}

/// Upgrade the manifests on disk following the previously-determined upgrade schema.
fn set_version(self, dry_run: bool, version: Version) -> Result<()> {
if dry_run {
dry_run_message()?;
}

for (mut manifest, package) in self.0 {
println!("{}:", package.name);
manifest.set_version(dry_run, &version)?;
}

Ok(())
}
}

/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error
/// messages.
fn process(args: Args) -> Result<()> {
let Args {
manifest_path,
all,
version,
dry_run,
} = args;

let manifests = if all {
Manifests::get_all(&manifest_path)
} else {
Manifests::get_local_one(&manifest_path)
}?;

manifests.set_version(dry_run, version)
}

fn main() {
let args: Command = Command::from_args();
let Command::SetVersion(args) = args;

if let Err(err) = process(args) {
eprintln!("Command failed due to unhandled error: {}\n", err);

for e in err.iter().skip(1) {
eprintln!("Caused by: {}", e);
}

if let Some(backtrace) = err.backtrace() {
eprintln!("Backtrace: {:?}", backtrace);
}

process::exit(1);
}
}
21 changes: 21 additions & 0 deletions src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::dependency::Dependency;
use crate::errors::*;

use semver::{Version, VersionReq};
use toml_edit::Value;

const MANIFEST_FILENAME: &str = "Cargo.toml";

Expand Down Expand Up @@ -475,6 +476,26 @@ impl LocalManifest {
Manifest::find_file(&Some(self.path.clone()))
}

/// Set the version of the manifest
pub fn set_version(&mut self, dry_run: bool, version: &Version) -> Result<()> {
let table_path = &["package".to_string()];
let table = self.manifest.get_table(table_path)?.as_table_mut();
if let Some(table) = table {
let mut version_str: Value = format!("{:?}", version.to_string()).parse().expect("valid toml");
if !dry_run {
table.entry("version").as_value_mut().map(|v| *v = version_str);
let mut file = self.get_file()?;
self.write_to_file(&mut file)
.chain_err(|| "Failed to write new manifest contents")
} else {
Ok(())
}

} else {
Ok(())
}
}

/// Instruct this manifest to upgrade a single dependency. If this manifest does not have that
/// dependency, it does nothing.
pub fn upgrade(
Expand Down

0 comments on commit 1d454c7

Please sign in to comment.