From 05881e29dd6b4fd493553e1ea569cc007a438a55 Mon Sep 17 00:00:00 2001 From: Dusk Banks Date: Fri, 23 Jun 2023 02:01:28 -0700 Subject: [PATCH] feat: Support Cargo workspace inheritance Fixes kbknapp/cargo-outdated#325. --- Cargo.lock | 44 ++--- src/cargo_ops/mod.rs | 19 +- src/cargo_ops/temp_project.rs | 326 +++++++++++++++++++++------------- src/main.rs | 88 ++++++--- 4 files changed, 309 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d344c6..eb393be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,15 +55,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -331,9 +331,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc" dependencies = [ "clap_builder", "clap_derive", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" dependencies = [ "anstream", "anstyle", @@ -879,27 +879,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc02feb20ad313d52a450852f2005c2205d24f851e74d82b7807cbe12c371667" +checksum = "311e2fa997be6560c564b070c5da2d56d038b645a94e1e5796d5d85a350da33c" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7acf3bc6c4b91e8fb260086daf5e105ea3a6d913f5fd3318137f7e309d6e540" +checksum = "39db5ed0fc0a2e9b1b8265993f7efdbc30379dec268f3b91b7af0c2de4672fdd" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6141b70cfb21255223e42f3379855037cbbe8673b58dd8318d2f09b516fad1" +checksum = "bb49ab557a37b0abb2415bca2b10e541277dff0565deb5bd5e99fd95f93f51eb" dependencies = [ "bstr", ] @@ -1200,9 +1200,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d59489bff95b06dcdabe763b7266d3dc0a628cac1ac1caf65a7ca0a43eeae0" +checksum = "3874de636c2526de26a3405b8024b23ef1a327bebf4845d770d00d48700b6a40" dependencies = [ "bstr", "btoi", @@ -1330,9 +1330,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ea5845b506c7728b9d89f4227cc369a5fc5a1d5b26c3add0f0d323413a3a60" +checksum = "8d092b594c8af00a3a31fe526d363ee8a51a6f29d8496cdb991ed2f01ec0ec13" dependencies = [ "bstr", "thiserror", @@ -1792,9 +1792,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.54" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags", "cfg-if", @@ -1833,9 +1833,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", diff --git a/src/cargo_ops/mod.rs b/src/cargo_ops/mod.rs index 32481eb..2f8141c 100644 --- a/src/cargo_ops/mod.rs +++ b/src/cargo_ops/mod.rs @@ -8,29 +8,34 @@ pub use self::{elaborate_workspace::ElaborateWorkspace, pkg_status::*, temp_proj /// A continent struct for quick parsing and manipulating manifest #[derive(Debug, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "kebab-case")] struct Manifest { - #[serde(rename = "cargo-features", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub cargo_features: Option, - pub package: Table, + #[serde(skip_serializing_if = "Option::is_none")] + pub package: Option, #[serde(skip_serializing_if = "Option::is_none")] pub dependencies: Option
, - #[serde(rename = "dev-dependencies", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub dev_dependencies: Option
, - #[serde(rename = "build-dependencies", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub build_dependencies: Option
, + #[serde(skip_serializing_if = "Option::is_none")] pub lib: Option
, + #[serde(skip_serializing_if = "Option::is_none")] pub bin: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub workspace: Option
, #[serde(skip_serializing_if = "Option::is_none")] pub target: Option
, + #[serde(skip_serializing_if = "Option::is_none")] pub features: Option, } impl Manifest { - pub fn name(&self) -> String { - match self.package["name"] { - Value::String(ref name) => name.clone(), + pub fn name(&self) -> Option { + match self.package.as_ref()?["name"] { + Value::String(ref name) => Some(name.clone()), _ => unreachable!(), } } diff --git a/src/cargo_ops/temp_project.rs b/src/cargo_ops/temp_project.rs index f4e10a3..0d358bc 100644 --- a/src/cargo_ops/temp_project.rs +++ b/src/cargo_ops/temp_project.rs @@ -10,7 +10,7 @@ use std::{ use anyhow::{anyhow, Context}; use cargo::{ - core::{Dependency, PackageId, QueryKind, Source, Summary, Verbosity, Workspace}, + core::{Dependency, Package, PackageId, QueryKind, Source, Summary, Verbosity, Workspace}, ops::{update_lockfile, UpdateOptions}, sources::config::SourceConfigMap, util::{network::PollExt, CargoResult, Config}, @@ -28,7 +28,7 @@ pub struct TempProject<'tmp> { pub temp_dir: TempDir, manifest_paths: Vec, config: Config, - relative_manifest: String, + relative_manifest: PathBuf, options: &'tmp Options, is_workspace_project: bool, } @@ -37,97 +37,144 @@ impl<'tmp> TempProject<'tmp> { /// Copy needed manifest and lock files from an existing workspace pub fn from_workspace( orig_workspace: &ElaborateWorkspace<'_>, - orig_manifest: &str, + orig_manifest: &Path, options: &'tmp Options, ) -> CargoResult> { // e.g. /path/to/project let workspace_root = orig_workspace.workspace.root(); - let workspace_root_str = workspace_root.to_string_lossy(); - let temp_dir = Builder::new().prefix("cargo-outdated").tempdir()?; - let manifest_paths = manifest_paths(orig_workspace)?; + let temp_dir = Builder::new() + .prefix("cargo-outdated") + .tempdir() + .context("Creating cargo-outdated tempdir")?; + let mut manifest_paths = manifest_paths(orig_workspace) + .context("Extracting original workspace manifest paths")? + .drain(..) + .map(move |(p, pkg)| (p, Some(pkg))) + .collect::>(); let mut tmp_manifest_paths = vec![]; - for from in &manifest_paths { + // Ensure we handle workspaces first, as they may declare dependencies that are + // inherited later. + let virtual_root_path = workspace_root.join("Cargo.toml"); + match manifest_paths + .iter() + .position(|(p, _)| p == &virtual_root_path) + { + Some(0) => {} + Some(index) => { + let manifest_path = manifest_paths.remove(index); + manifest_paths.insert(0, manifest_path); + } + None if virtual_root_path.is_file() => { + manifest_paths.insert(0, (virtual_root_path, None)); + } + None => {} + }; + + let manifest_paths = manifest_paths; + + for (from, from_pkg) in &manifest_paths { // e.g. /path/to/project/src/sub let mut from_dir = from.clone(); from_dir.pop(); - let from_dir_str = from_dir.to_string_lossy(); // e.g. /tmp/cargo.xxx/src/sub - let mut dest = if workspace_root_str.len() < from_dir_str.len() { - temp_dir - .path() - .join(&from_dir_str[workspace_root_str.len() + 1..]) + let mut dest = if let Ok(sub) = from_dir.strip_prefix(workspace_root) { + temp_dir.path().join(sub) } else { temp_dir.path().to_owned() }; - fs::create_dir_all(&dest)?; + fs::create_dir_all(&dest).context("Creating temporary manifest parent directories")?; // e.g. /tmp/cargo.xxx/src/sub/Cargo.toml dest.push("Cargo.toml"); tmp_manifest_paths.push(dest.clone()); - fs::copy(from, &dest)?; + fs::copy(from, &dest) + .with_context(|| format!("Copying {:?} to temporary manifest {:?}", from, dest))?; // removing default-run key if it exists to check dependencies let mut om: Manifest = { let mut buf = String::new(); - let mut file = File::open(&dest)?; - file.read_to_string(&mut buf)?; - ::toml::from_str(&buf)? + let mut file = + File::open(&dest).context("Opening created temporary manifest for reading")?; + file.read_to_string(&mut buf) + .context("Reading opened temporary manifest")?; + ::toml::from_str(&buf).context("Parsing temporary manifest")? }; - if om.package.contains_key("default-run") { - om.package.remove("default-run"); - let om_serialized = ::toml::to_string(&om).expect("Cannot format as toml file"); + if om + .package + .as_mut() + .and_then(|package| package.remove("default-run")) + .is_some() + { + let om_serialized = ::toml::to_string(&om).expect("Cannot format as TOML file"); let mut cargo_toml = OpenOptions::new() .read(true) .write(true) .truncate(true) - .open(&dest)?; - write!(cargo_toml, "{om_serialized}")?; + .open(&dest) + .context("Opening created temporary manifest for writing")?; + write!(cargo_toml, "{om_serialized}") + .context("Writing to created temporary manifest")?; } // if build script is specified in the original Cargo.toml (from links or build) // remove it as we do not need it for checking dependencies - if om.package.contains_key("links") { - om.package.remove("links"); - let om_serialized = ::toml::to_string(&om).expect("Cannot format as toml file"); + if om + .package + .as_mut() + .and_then(|package| package.remove("links")) + .is_some() + { + let om_serialized = ::toml::to_string(&om).expect("Cannot format as TOML file"); let mut cargo_toml = OpenOptions::new() .read(true) .write(true) .truncate(true) - .open(&dest)?; - write!(cargo_toml, "{om_serialized}")?; + .open(&dest) + .context("Opening created temporary manifest for writing")?; + write!(cargo_toml, "{om_serialized}") + .context("Writing to created temporary manifest")?; } - if om.package.contains_key("build") { - om.package.remove("build"); - let om_serialized = ::toml::to_string(&om).expect("Cannot format as toml file"); + if om + .package + .as_mut() + .and_then(|package| package.remove("build")) + .is_some() + { + let om_serialized = ::toml::to_string(&om).expect("Cannot format as TOML file"); let mut cargo_toml = OpenOptions::new() .read(true) .write(true) .truncate(true) - .open(&dest)?; - write!(cargo_toml, "{om_serialized}")?; + .open(&dest) + .context("Opening created temporary manifest for writing")?; + write!(cargo_toml, "{om_serialized}") + .context("Writing to created temporary manifest")?; + } + + // Make implicit `lib` table explicit + if om.lib.is_none() && from_pkg.and_then(|pkg| pkg.library()).is_some() { + om.lib = Some(Table::new()); + let om_serialized = ::toml::to_string(&om).expect("Cannot format as TOML file"); + let mut cargo_toml = OpenOptions::new() + .read(true) + .write(true) + .truncate(true) + .open(&dest) + .context("Opening created temporary manifest for writing")?; + write!(cargo_toml, "{om_serialized}") + .context("Writing to created temporary manifest")?; } let lockfile = from_dir.join("Cargo.lock"); if lockfile.is_file() { dest.pop(); dest.push("Cargo.lock"); - fs::copy(lockfile, dest)?; - } - } - - // virtual root - let mut virtual_root = workspace_root.join("Cargo.toml"); - if !manifest_paths.contains(&virtual_root) && virtual_root.is_file() { - fs::copy(&virtual_root, temp_dir.path().join("Cargo.toml"))?; - virtual_root.pop(); - virtual_root.push("Cargo.lock"); - if virtual_root.is_file() { - fs::copy(&virtual_root, temp_dir.path().join("Cargo.lock"))?; + fs::copy(lockfile, dest).context("Copying to temporary lockfile")?; } } @@ -135,25 +182,38 @@ impl<'tmp> TempProject<'tmp> { // this is the preferred way // https://doc.rust-lang.org/cargo/reference/config.html if workspace_root.join(".cargo/config.toml").is_file() { - fs::create_dir_all(temp_dir.path().join(".cargo"))?; + fs::create_dir_all(temp_dir.path().join(".cargo")) + .context("Creating temporary `.cargo` directory")?; fs::copy( workspace_root.join(".cargo/config.toml"), temp_dir.path().join(".cargo/config.toml"), - )?; + ) + .context("Copying to temporary `.cargo/config.toml`")?; } //.cargo/config // this is legacy support for config files without the `.toml` extension if workspace_root.join(".cargo/config").is_file() { - fs::create_dir_all(temp_dir.path().join(".cargo"))?; + fs::create_dir_all(temp_dir.path().join(".cargo")) + .context("Creating temporary `.cargo` directory")?; fs::copy( workspace_root.join(".cargo/config"), temp_dir.path().join(".cargo/config"), - )?; + ) + .context("Copying to temporary `.cargo/config`")?; } - let relative_manifest = String::from(&orig_manifest[workspace_root_str.len() + 1..]); - let config = Self::generate_config(temp_dir.path(), &relative_manifest, options)?; + let relative_manifest = orig_manifest + .strip_prefix(workspace_root) + .with_context(|| { + format!( + "original manifest path {:?} is not prefixed with workspace root path {:?}", + orig_manifest, workspace_root + ) + })? + .to_owned(); + let config = Self::generate_config(temp_dir.path(), &relative_manifest, options) + .context("Generating config for temporary workspace")?; Ok(TempProject { workspace: Rc::new(RefCell::new(None)), @@ -168,12 +228,12 @@ impl<'tmp> TempProject<'tmp> { fn generate_config( root: &Path, - relative_manifest: &str, + relative_manifest: &Path, options: &Options, ) -> CargoResult { let shell = ::cargo::core::Shell::new(); let cwd = env::current_dir() - .with_context(|| "Cargo couldn't get the current directory of the process")?; + .context("Cargo couldn't get the current directory of the process")?; let homedir = ::cargo::util::homedir(&cwd).ok_or_else(|| { anyhow!( @@ -189,17 +249,19 @@ impl<'tmp> TempProject<'tmp> { let cargo_home_path = std::env::var_os("CARGO_HOME").map(std::path::PathBuf::from); let mut config = Config::new(shell, cwd, homedir); - config.configure( - 0, - options.verbose == 0, - Some(&options.color.to_string().to_ascii_lowercase()), - options.frozen(), - options.locked(), - options.offline, - &cargo_home_path, - &[], - &[], - )?; + config + .configure( + 0, + options.verbose == 0, + Some(&options.color.to_string().to_ascii_lowercase()), + options.frozen(), + options.locked(), + options.offline, + &cargo_home_path, + &[], + &[], + ) + .context("Configuring Cargo")?; Ok(config) } @@ -219,14 +281,15 @@ impl<'tmp> TempProject<'tmp> { .as_ref() .ok_or(OutdatedError::NoWorkspace)?, &update_opts, - )?; + ) + .context("Updating Cargo lockfile")?; Ok(()) } fn write_manifest>(manifest: &Manifest, path: P) -> CargoResult<()> { - let mut file = File::create(path)?; + let mut file = File::create(path).context("Creating temporary manifest file")?; let serialized = ::toml::to_string(manifest).expect("Failed to serialized Cargo.toml"); - write!(file, "{serialized}")?; + write!(file, "{serialized}").context("Writing to created temporary manifest")?; Ok(()) } @@ -234,6 +297,11 @@ impl<'tmp> TempProject<'tmp> { where F: FnMut(&mut Table) -> CargoResult<()>, { + if let Some(workspace) = manifest.workspace.as_mut() { + if let Some(Value::Table(dep)) = workspace.get_mut("dependencies") { + f(dep)?; + } + } if let Some(dep) = manifest.dependencies.as_mut() { f(dep)?; } @@ -245,14 +313,12 @@ impl<'tmp> TempProject<'tmp> { } if let Some(t) = manifest.target.as_mut() { for (_key, target) in t.iter_mut() { - if let Value::Table(ref mut target) = *target { + if let Value::Table(target) = target { for dependency_tables in &["dependencies", "dev-dependencies", "build-dependencies"] { - if let Some(&mut Value::Table(ref mut dep_table)) = - target.get_mut(*dependency_tables) - { - f(dep_table)?; + if let Some(Value::Table(dep)) = target.get_mut(*dependency_tables) { + f(dep)?; } } } @@ -284,7 +350,9 @@ impl<'tmp> TempProject<'tmp> { ::toml::from_str(&buf)? }; - manifest.bin = Some(vec![bin.clone()]); + if manifest.package.is_some() { + manifest.bin = Some(vec![bin.clone()]); + } // provide lib.path if let Some(lib) = manifest.lib.as_mut() { lib.insert("path".to_owned(), Value::String("test_lib.rs".to_owned())); @@ -300,11 +368,18 @@ impl<'tmp> TempProject<'tmp> { ) })?; - let package_name = manifest.name(); - let features = manifest.features.clone(); - Self::manipulate_dependencies(&mut manifest, &mut |deps| { - self.update_version_and_feature(deps, &features, workspace, &package_name, false) - })?; + if let Some(package_name) = manifest.name() { + let features = manifest.features.clone(); + Self::manipulate_dependencies(&mut manifest, &mut |deps| { + self.update_version_and_feature( + deps, + &features, + workspace, + &package_name, + false, + ) + })?; + } Self::write_manifest(&manifest, manifest_path)?; } @@ -329,6 +404,7 @@ impl<'tmp> TempProject<'tmp> { bin.insert("path".to_owned(), Value::String("test.rs".to_owned())); bin }; + for manifest_path in &self.manifest_paths { let mut manifest: Manifest = { let mut buf = String::new(); @@ -337,7 +413,9 @@ impl<'tmp> TempProject<'tmp> { ::toml::from_str(&buf)? }; - manifest.bin = Some(vec![bin.clone()]); + if manifest.package.is_some() { + manifest.bin = Some(vec![bin.clone()]); + } // provide lib.path if let Some(lib) = manifest.lib.as_mut() { lib.insert("path".to_owned(), Value::String("test_lib.rs".to_owned())); @@ -353,11 +431,12 @@ impl<'tmp> TempProject<'tmp> { ) })?; - let package_name = manifest.name(); - let features = manifest.features.clone(); - Self::manipulate_dependencies(&mut manifest, &mut |deps| { - self.update_version_and_feature(deps, &features, workspace, &package_name, true) - })?; + if let Some(package_name) = manifest.name() { + let features = manifest.features.clone(); + Self::manipulate_dependencies(&mut manifest, &mut |deps| { + self.update_version_and_feature(deps, &features, workspace, &package_name, true) + })?; + } Self::write_manifest(&manifest, manifest_path)?; } @@ -430,7 +509,7 @@ impl<'tmp> TempProject<'tmp> { // access to write to the terminal // if this fails it's a cargo (as a dependency) issue self.warn(format!( - "cannot compare {} crate version found in toml {} with crates.io latest {}", + "cannot compare {} crate version found in TOML {} with crates.io latest {}", name, ver_req, query_result[0].version() @@ -451,8 +530,8 @@ impl<'tmp> TempProject<'tmp> { if !optional && self.options.features.contains(&String::from("default")) { return true; } - let features_table = match *features_table { - Some(Value::Table(ref features_table)) => features_table, + let features_table = match features_table { + Some(Value::Table(features_table)) => features_table, _ => return false, }; let mut to_resolve: Vec<&str> = self @@ -474,11 +553,11 @@ impl<'tmp> TempProject<'tmp> { if features_table.contains_key(feature) { let specified_features = match features_table.get(feature) { None => panic!("Feature {feature} does not exist"), - Some(Value::Array(ref specified_features)) => specified_features, + Some(Value::Array(specified_features)) => specified_features, _ => panic!("Feature {feature} is not mapped to an array"), }; for spec in specified_features { - if let Value::String(ref spec) = *spec { + if let Value::String(spec) = spec { to_resolve.push(spec.as_str()); } } @@ -534,9 +613,9 @@ impl<'tmp> TempProject<'tmp> { }; } } - Value::Table(ref t) => { + Value::Table(t) => { let mut name = match t.get("package") { - Some(Value::String(ref s)) => s, + Some(Value::String(s)) => s, Some(_) => panic!("'package' of dependency {dep_key} is not a string"), None => &dep_key, }; @@ -565,7 +644,7 @@ impl<'tmp> TempProject<'tmp> { } let mut replaced = t.clone(); let requirement = match t.get("version") { - Some(Value::String(ref requirement)) => Some(requirement.as_str()), + Some(Value::String(requirement)) => Some(requirement.as_str()), Some(_) => panic!("Version of {name} is not a string"), _ => None, }; @@ -595,11 +674,11 @@ impl<'tmp> TempProject<'tmp> { } if replaced.contains_key("features") { let features = match replaced.get("features") { - Some(Value::Array(ref features)) => features + Some(Value::Array(features)) => features .iter() .filter(|&feature| { - let feature = match *feature { - Value::String(ref feature) => feature, + let feature = match feature { + Value::String(feature) => feature, _ => panic!( "Features section of {name} is not an array of strings" ), @@ -651,16 +730,17 @@ impl<'tmp> TempProject<'tmp> { .cloned() .ok_or(OutdatedError::NoMatchingDependency)?; match original { - Value::Table(ref t) if t.contains_key("path") => { - if let Value::String(ref orig_path) = t["path"] { - let orig_path = Path::new(orig_path); + Value::Table(t) if t.contains_key("path") => { + if let Value::String(orig_path) = &t["path"] { + let orig_path = Path::new(&orig_path); if orig_path.is_relative() { let relative = { - let delimiter: &[_] = &['/', '\\']; - let relative = &tmp_manifest.to_string_lossy() - [tmp_root.to_string_lossy().len()..]; - let mut relative = - PathBuf::from(relative.trim_start_matches(delimiter)); + let mut relative = tmp_manifest.strip_prefix(tmp_root).with_context(|| { + format!( + "original temp manifest path {:?} is not prefixed with temp workspace root path {:?}", + tmp_manifest, tmp_root + ) + })?.to_owned(); relative.pop(); relative.join(orig_path) }; @@ -669,7 +749,7 @@ impl<'tmp> TempProject<'tmp> { dependencies.remove(&name); if t.contains_key("package") { - if let Value::String(ref package_name) = t["package"] { + if let Value::String(package_name) = &t["package"] { skipped.insert(package_name.to_string()); } else { skipped.insert(name); @@ -679,13 +759,17 @@ impl<'tmp> TempProject<'tmp> { } } else { let mut replaced = t.clone(); + let replaced_path = fs::canonicalize(orig_root.join(relative))?; + let replaced_path = + replaced_path.to_str().ok_or_else(|| { + anyhow!( + "path {:?} is not a UTF-8 string", + replaced_path + ) + })?; replaced.insert( "path".to_owned(), - Value::String( - fs::canonicalize(orig_root.join(relative))? - .to_string_lossy() - .to_string(), - ), + Value::String(replaced_path.to_string()), ); dependencies.insert(name, Value::Table(replaced)); } @@ -727,23 +811,25 @@ fn features_and_options(summary: &Summary) -> HashSet<&str> { } /// Paths of all manifest files in current workspace -fn manifest_paths(elab: &ElaborateWorkspace<'_>) -> CargoResult> { +fn manifest_paths<'a>( + elab: &'a ElaborateWorkspace<'_>, +) -> CargoResult> { let mut visited: HashSet = HashSet::new(); let mut manifest_paths = vec![]; - fn manifest_paths_recursive( + fn manifest_paths_recursive<'a>( pkg_id: PackageId, - elab: &ElaborateWorkspace<'_>, - workspace_path: &str, + elab: &'a ElaborateWorkspace<'_>, + workspace_path: &Path, visited: &mut HashSet, - manifest_paths: &mut Vec, + manifest_paths: &mut Vec<(PathBuf, &'a Package)>, ) -> CargoResult<()> { if visited.contains(&pkg_id) { return Ok(()); } visited.insert(pkg_id); let pkg = &elab.pkgs[&pkg_id]; - let pkg_path = pkg.root().to_string_lossy(); + let pkg_path = pkg.root(); // Checking if there's a CARGO_HOME set and that it is not an empty string let cargo_home_path = match std::env::var_os("CARGO_HOME") { @@ -762,18 +848,19 @@ fn manifest_paths(elab: &ElaborateWorkspace<'_>) -> CargoResult> { || !pkg_path .starts_with(&cargo_home_path.expect("Error extracting CARGO_HOME string"))) { - manifest_paths.push(pkg.manifest_path().to_owned()); + manifest_paths.push((pkg.manifest_path().to_owned(), pkg)); } for &dep in elab.pkg_deps[&pkg_id].keys() { - manifest_paths_recursive(dep, elab, workspace_path, visited, manifest_paths)?; + manifest_paths_recursive(dep, elab, workspace_path, visited, manifest_paths) + .context("Extracting dependency manifest paths")?; } Ok(()) } // executed against a virtual manifest - let workspace_path = elab.workspace.root().to_string_lossy(); + let workspace_path = elab.workspace.root(); // if cargo workspace is not explicitly used, the package itself would be a // member for member in elab.workspace.members() { @@ -781,10 +868,11 @@ fn manifest_paths(elab: &ElaborateWorkspace<'_>) -> CargoResult> { manifest_paths_recursive( root_pkg_id, elab, - &workspace_path, + workspace_path, &mut visited, &mut manifest_paths, - )?; + ) + .context("Extracting root workspace member manifest paths")?; } Ok(manifest_paths) diff --git a/src/main.rs b/src/main.rs index 45cff70..bdfadaf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod error; use std::collections::HashSet; +use anyhow::Context; use cargo::{ core::{shell::Verbosity, Workspace}, ops::needs_custom_http_transport, @@ -92,7 +93,7 @@ pub fn execute(options: Options, config: &mut Config) -> CargoResult { )?; debug!(config, format!("options: {options:?}")); - verbose!(config, "Parsing...", "current workspace"); + verbose!(config, "Finding...", "current workspace"); // the Cargo.toml that we are actually working on let mut manifest_abspath: std::path::PathBuf; let curr_manifest = if let Some(ref manifest_path) = options.manifest_path { @@ -103,8 +104,14 @@ pub fn execute(options: Options, config: &mut Config) -> CargoResult { } manifest_abspath } else { - find_root_manifest_for_wd(config.cwd())? + find_root_manifest_for_wd(config.cwd()) + .with_context(|| format!("Finding root Cargo manifest for {:?}", config.cwd()))? }; + verbose!( + config, + "Parsing...", + format!("current workspace (manifest: {:?})", curr_manifest) + ); let curr_workspace = Workspace::new(&curr_manifest, config)?; verbose!(config, "Resolving...", "current workspace"); if options.verbose == 0 { @@ -120,41 +127,82 @@ pub fn execute(options: Options, config: &mut Config) -> CargoResult { verbose!(config, "Parsing...", "compat workspace"); let mut skipped = HashSet::new(); let compat_proj = - TempProject::from_workspace(&ela_curr, &curr_manifest.to_string_lossy(), &options)?; - compat_proj.write_manifest_semver( - curr_workspace.root(), - compat_proj.temp_dir.path(), - &ela_curr, - &mut skipped, - )?; + TempProject::from_workspace(&ela_curr, &curr_manifest, &options) + .with_context(|| format!("Creating temp compat workspace for {:?}", curr_manifest))?; + verbose!(config, "Writing...", format!("compat workspace (root: {:?})", compat_proj.temp_dir.path())); + compat_proj + .write_manifest_semver( + curr_workspace.root(), + compat_proj.temp_dir.path(), + &ela_curr, + &mut skipped, + ) + .with_context(|| { + format!( + "Writing semver manifest for temp compat workspace {:?} for {:?}", + compat_proj.temp_dir.path(), + curr_manifest, + ) + })?; verbose!(config, "Updating...", "compat workspace"); - compat_proj.cargo_update()?; + compat_proj.cargo_update().with_context(|| { + format!( + "cargo update in temp compat workspace for {:?}", + curr_manifest, + ) + })?; verbose!(config, "Resolving...", "compat workspace"); let compat_workspace = compat_proj.workspace.borrow(); let ela_compat = ElaborateWorkspace::from_workspace( compat_workspace .as_ref() - .ok_or(OutdatedError::CannotElaborateWorkspace)?, + .ok_or(OutdatedError::CannotElaborateWorkspace) + .with_context(|| { + format!("Elaborating temp compat workspace for {:?}", curr_manifest) + })?, &options, )?; verbose!(config, "Parsing...", "latest workspace"); let latest_proj = - TempProject::from_workspace(&ela_curr, &curr_manifest.to_string_lossy(), &options)?; - latest_proj.write_manifest_latest( - curr_workspace.root(), - compat_proj.temp_dir.path(), - &ela_curr, - &mut skipped, - )?; + TempProject::from_workspace(&ela_curr, &curr_manifest, &options) + .with_context(|| format!("Creating temp latest workspace for {:?}", curr_manifest))?; + verbose!(config, "Writing...", format!("latest workspace (root: {:?})", latest_proj.temp_dir.path())); + latest_proj + .write_manifest_latest( + curr_workspace.root(), + latest_proj.temp_dir.path(), + &ela_curr, + &mut skipped, + ) + .with_context(|| { + format!( + "Writing latest manifest for temp latest workspace {:?} for {:?}", + latest_proj.temp_dir.path(), + curr_manifest + ) + })?; verbose!(config, "Updating...", "latest workspace"); - latest_proj.cargo_update()?; + latest_proj.cargo_update().with_context(|| { + format!( + "cargo update in temp latest workspace {:?} for {:?}", + latest_proj.temp_dir.path(), + curr_manifest + ) + })?; verbose!(config, "Resolving...", "latest workspace"); let latest_workspace = latest_proj.workspace.borrow(); let ela_latest = ElaborateWorkspace::from_workspace( latest_workspace .as_ref() - .ok_or(OutdatedError::CannotElaborateWorkspace)?, + .ok_or(OutdatedError::CannotElaborateWorkspace) + .with_context(|| { + format!( + "Elaborating temp latest workspace {:?} for {:?}", + latest_proj.temp_dir.path(), + curr_manifest + ) + })?, &options, )?;