diff --git a/Cargo.lock b/Cargo.lock index 845f2d81..52be9412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2133,9 +2133,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2610,6 +2610,7 @@ dependencies = [ "self_update", "semver", "serde", + "serde_json", "shell-words", "shellexpand", "strum", diff --git a/Cargo.toml b/Cargo.toml index 37566a79..ccdbdea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ merge = "~0.1" regex-split = "~0.1" notify-rust = "~4.10" wildmatch = "2.3.0" +serde_json = "1.0.117" [package.metadata.generate-rpm] assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }] diff --git a/src/steps/generic.rs b/src/steps/generic.rs index 2db01bae..851f0a07 100644 --- a/src/steps/generic.rs +++ b/src/steps/generic.rs @@ -1,5 +1,6 @@ #![allow(unused_imports)] +use std::fs::File; use std::path::PathBuf; use std::process::Command; use std::{env, path::Path}; @@ -8,9 +9,12 @@ use std::{fs, io::Write}; use color_eyre::eyre::eyre; use color_eyre::eyre::Context; use color_eyre::eyre::Result; -use semver::Version; +use semver::{Version, VersionReq}; +use serde_json::Value; use tempfile::tempfile_in; +use tracing::instrument::WithSubscriber; use tracing::{debug, error}; +use tracing_subscriber::fmt::writer::MakeWriterExt; use crate::command::{CommandExt, Utf8Output}; use crate::execution_context::ExecutionContext; @@ -54,6 +58,22 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> { return Err(SkipStep(format!("{} exists but empty", &toml_file.display())).into()); } + let required = VersionReq::parse(">=1.77.0").unwrap(); + + let version = Command::new("cargo").arg("--version").output_checked_utf8()?.stdout; + + let version = version + .split_whitespace() + .nth(1) + .ok_or(SkipStep(format!("cargo --version output is malformed: {version}")))?; + debug!("{version}"); + + let version = Version::parse(version)?; + + if required.matches(&version) { + return manual_cargo_updates(cargo_dir.join(".crates2.json").require()?); + } + print_separator("Cargo"); let cargo_update = require("cargo-install-update") .ok() @@ -90,6 +110,64 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> { Ok(()) } +fn manual_cargo_updates(crates_json: PathBuf) -> Result<()> { + let file = File::open(crates_json)?; + let value: Value = serde_json::from_reader(file)?; + + let crate_map = value + .get("installs") + .ok_or(SkipStep("Improper .crates2.json".to_string()))?; + + let mut install_command = Command::new("cargo"); + install_command.arg("install"); + + let mut feature_packages = Vec::new(); + + match crate_map { + Value::Object(object) => { + for (package, meta) in object { + let to_install = package.contains("registry"); + let package = package.split_whitespace().next().unwrap(); + if to_install + && !meta["no_default_features"].as_bool().unwrap() + && !meta["all_features"].as_bool().unwrap() + && meta["features"].as_array().unwrap().is_empty() + { + install_command.arg(package); + } else if to_install { + feature_packages.push(( + package, + meta["all_features"].as_bool().unwrap(), + meta["no_default_features"].as_bool().unwrap(), + meta["features"].as_array().unwrap(), + )); + } + } + } + _ => { + return Err(SkipStep("Improper .crates2.json".to_string()).into()); + } + } + println!("{feature_packages:?}"); + + for (package, all_features, no_default, feature) in feature_packages { + let feature: Vec<_> = feature.iter().map(|feat| feat.as_str().unwrap()).collect(); + let mut command = Command::new("cargo"); + command.args(["install", package]); + if all_features { + command.arg("--all-features"); + } + if no_default { + command.arg("--no-default-features"); + } + command.arg("--features"); + command.args(feature); + + command.status_checked()?; + } + install_command.status_checked() +} + pub fn run_flutter_upgrade(ctx: &ExecutionContext) -> Result<()> { let flutter = require("flutter")?;