Skip to content
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

Let cargo decide when to upgrade #2

Merged
merged 1 commit into from
Apr 9, 2023
Merged
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
Let cargo decide when to upgrade
  • Loading branch information
phil-opp committed Apr 9, 2023
commit 1a1bf4c6b85de5834a45d2ba7c964dc959614944
117 changes: 3 additions & 114 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::BTreeMap;
use std::io::{stderr, Write};
use std::process::{Command, ExitStatus};

@@ -34,121 +34,10 @@ pub fn installed_crates() -> Result<BTreeMap<String, Crate>, String> {
Ok(crates)
}

pub fn get_latest_versions(
required_crates: &HashMap<String, Crate>,
) -> Result<HashMap<String, String>, String> {
use std::fs;
use tempfile::TempDir;

fn dependency_string(required_crates: &HashMap<String, Crate>) -> String {
let mut string = String::new();
for c in required_crates.values() {
match c.kind {
CrateKind::CratesIo => {
string.push_str(&format!(r#"{} = "{}"{}"#, c.name, c.version, '\n'));
}
}
}
string
}

fn create_dummy_crate(required_crates: &HashMap<String, Crate>) -> Result<TempDir, String> {
let tmpdir = TempDir::new()
.map_err(|e| format!("I/O Error while creating temporary directory: {}", e))?;
let cargo_toml_path = tmpdir.path().join("Cargo.toml");
let src_dir_path = tmpdir.path().join("src");
let lib_rs_path = src_dir_path.join("lib.rs");

let cargo_toml_content = format!(
r#"[package]
name = "cargo-update-installed-dummy"
version = "0.1.0"
authors = [""]

[dependencies]
{}
"#,
dependency_string(required_crates)
);

fs::create_dir(src_dir_path)
.map_err(|e| format!("I/O Error while creating src dir in temp dir: {}", e))?;
fs::write(cargo_toml_path, cargo_toml_content)
.map_err(|e| format!("I/O Error while writing dummy Cargo.toml: {}", e))?;
fs::write(lib_rs_path, "")
.map_err(|e| format!("I/O Error while writing dummy lib.rs: {}", e))?;
Ok(tmpdir)
}

fn run_cargo_update(tmpdir: &TempDir) -> Result<ExitStatus, String> {
let mut cargo_update_command = Command::new("cargo");
cargo_update_command.arg("update");
cargo_update_command.arg("--manifest-path");
cargo_update_command.arg(tmpdir.path().join("Cargo.toml"));
cargo_update_command
.status()
.map_err(|e| format!("I/O Error while running `cargo update`: {}", e))
}

fn parse_cargo_lock(
tmpdir: &TempDir,
required_crates: &HashMap<String, Crate>,
) -> Result<HashMap<String, String>, String> {
use std::fs;
use toml::Value;

let cargo_lock_path = tmpdir.path().join("Cargo.lock");
let cargo_lock = fs::read_to_string(cargo_lock_path)
.map_err(|e| format!("I/O Error while reading dummy Cargo.lock: {}", e))?;

let root_value: Value = cargo_lock
.parse()
.map_err(|e| format!("Error while parsing dummy Cargo.lock: {}", e))?;
let packages = root_value
.get("package")
.and_then(|v| v.as_array())
.ok_or("Error: package array not found in dummy Cargo.lock")?;

let mut latest_versions = HashMap::new();
for crate_name in required_crates.keys() {
let package = packages
.iter()
.find(|p| p.get("name").and_then(|v| v.as_str()) == Some(crate_name))
.ok_or(format!(
"Error: package {} not found in dummy Cargo.lock",
crate_name
))?;
let version = package
.get("version")
.and_then(|v| v.as_str())
.ok_or(format!(
"Error: package {} has no version number in dummy Cargo.lock",
crate_name
))?;
if latest_versions
.insert(crate_name.clone(), String::from(version))
.is_some()
{
writeln!(stderr(), "Warning: package {} is present multiple times in dummy Cargo.lock. Choosing version {}.", crate_name, version);
}
}
Ok(latest_versions)
}

let tmpdir = create_dummy_crate(required_crates)?;
if !run_cargo_update(&tmpdir)?.success() {
return Err("Error: `cargo update` failed".into());
}
parse_cargo_lock(&tmpdir, required_crates)
}

pub fn install_update(name: &str, version: &str) -> Result<ExitStatus, String> {
pub fn install_update(name: &str) -> Result<ExitStatus, String> {
let mut cargo_install_command = Command::new("cargo");
cargo_install_command.arg("install");
cargo_install_command.arg("--force");
cargo_install_command.arg(name);
cargo_install_command.arg("--version");
cargo_install_command.arg(version);
cargo_install_command
.status()
.map_err(|e| format!("I/O Error while running `cargo install`: {}", e))
@@ -196,7 +85,7 @@ impl Crate {
let version = version.trim_end_matches(":");
Ok(Some(Crate {
name: name.into(),
version: version.into(),
version: version.parse().map_err(|_| ParseListOutputError)?,
kind: CrateKind::CratesIo,
}))
} else {
52 changes: 3 additions & 49 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -14,60 +14,14 @@ fn main() {
}

fn run() -> Result<(), String> {
use std::collections::HashMap;

let installed_crates = installed_crates()?;
let mut required_crates = HashMap::new();
for c in installed_crates.values() {
if required_crates.contains_key(&c.name) {
println!("Ignoring duplicate installed crate: {:?}", c);
continue;
}

let mut required_crate = c.clone();
// require latest version with no constraints
required_crate.version = "*".into();
if required_crates
.insert(c.name.clone(), required_crate)
.is_some()
{
unreachable!("duplicate key");
}
}

let latest_versions = get_latest_versions(&required_crates)?;

let mut updates = Vec::new();
for c in installed_crates.values() {
let latest_version = latest_versions
.get(&c.name)
.ok_or(format!("Error: No latest version found for {}", c.name))?;
if &c.version != latest_version {
updates.push((c, latest_version));
} else {
println!("Up to date: {} {}", c.name, c.version);
}
}
updates.sort_unstable_by_key(|(c, _)| &c.name);

if updates.len() > 1 {
println!("\nThe following updates will be performed:");
for (_crate, latest_version) in &updates {
println!(" {} from {} to {}", _crate.name, _crate.version, latest_version);
}
}

for (_crate, latest_version) in &updates {
println!(
"\nUpdating {} from {} to {}",
_crate.name, _crate.version, latest_version
);
if !install_update(&_crate.name, latest_version)?.success() {
for c in installed_crates.keys() {
println!("Updating `{c}`");
if !install_update(c)?.success() {
return Err("Error: `cargo install` failed".into());
}
}

println!("\nAll packages up to date.");

Ok(())
}