From 0a1235e27944f152ca195c32e7eef8985d475989 Mon Sep 17 00:00:00 2001 From: David Knaack Date: Wed, 20 Jul 2022 11:17:27 +0200 Subject: [PATCH] feat(package): support cargo workspace versions (#4161) --- src/modules/package.rs | 125 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/src/modules/package.rs b/src/modules/package.rs index 7d707a66..2906d992 100644 --- a/src/modules/package.rs +++ b/src/modules/package.rs @@ -7,6 +7,8 @@ use quick_xml::events::Event as QXEvent; use quick_xml::Reader as QXReader; use regex::Regex; use serde_json as json; +use std::fs; +use std::io::Read; /// Creates a module with the current package version pub fn module<'a>(context: &'a Context) -> Option> { @@ -213,10 +215,45 @@ fn get_sbt_version(context: &Context, config: &PackageConfig) -> Option } fn get_cargo_version(context: &Context, config: &PackageConfig) -> Option { - let file_contents = context.read_file_from_pwd("Cargo.toml")?; + let mut file_contents = context.read_file_from_pwd("Cargo.toml")?; - let cargo_toml: toml::Value = toml::from_str(&file_contents).ok()?; - let raw_version = cargo_toml.get("package")?.get("version")?.as_str()?; + let mut cargo_toml: toml::Value = toml::from_str(&file_contents).ok()?; + let cargo_version = cargo_toml.get("package").and_then(|p| p.get("version")); + let raw_version = if let Some(v) = cargo_version.and_then(|v| v.as_str()) { + // regular version string + v + } else if cargo_version + .and_then(|v| v.get("workspace")) + .and_then(|w| w.as_bool()) + .unwrap_or_default() + { + // workspace version string (`package.version.worspace = true`) + // need to read the Cargo.toml file from the workspace root + let mut version = None; + // disover the workspace root + for path in context.current_dir.ancestors().skip(1) { + // Assume the workspace root is the first ancestor that contains a Cargo.toml file + if let Ok(mut file) = fs::File::open(path.join("Cargo.toml")) { + file.read_to_string(&mut file_contents).ok()?; + cargo_toml = toml::from_str(&file_contents).ok()?; + // Read workspace.package.version + version = cargo_toml + .get("workspace")? + .get("package")? + .get("version")? + .as_str(); + break; + } + } + version? + } else { + // This might be a workspace file + cargo_toml + .get("workspace")? + .get("package")? + .get("version")? + .as_str()? + }; format_version(raw_version, config.version_format) } @@ -356,6 +393,88 @@ mod tests { project_dir.close() } + #[test] + fn test_extract_cargo_version_ws() -> io::Result<()> { + let ws_config_name = "Cargo.toml"; + let ws_config_content = toml::toml! { + [workspace.package] + version = "0.1.0" + } + .to_string(); + + let config_name = "member/Cargo.toml"; + let config_content = toml::toml! { + [package] + version.workspace = true + } + .to_string(); + + let project_dir = create_project_dir()?; + fs::create_dir(project_dir.path().join("member"))?; + + fill_config(&project_dir, ws_config_name, Some(&ws_config_content))?; + fill_config(&project_dir, config_name, Some(&config_content))?; + + // Version can be read both from the workspace and the member. + expect_output(&project_dir, Some("v0.1.0"), None); + let actual = ModuleRenderer::new("package") + .path(project_dir.path().join("member")) + .collect(); + let expected = Some(format!( + "is {} ", + Color::Fixed(208).bold().paint("📦 v0.1.0") + )); + assert_eq!(actual, expected); + project_dir.close() + } + + #[test] + fn test_extract_cargo_version_ws_false() -> io::Result<()> { + let ws_config_name = "Cargo.toml"; + let ws_config_content = toml::toml! { + [workspace.package] + version = "0.1.0" + } + .to_string(); + + let config_name = "member/Cargo.toml"; + let config_content = toml::toml! { + [package] + version.workspace = false + } + .to_string(); + + let project_dir = create_project_dir()?; + fs::create_dir(project_dir.path().join("member"))?; + + fill_config(&project_dir, ws_config_name, Some(&ws_config_content))?; + fill_config(&project_dir, config_name, Some(&config_content))?; + + // Version can be read both from the workspace and the member. + let actual = ModuleRenderer::new("package") + .path(project_dir.path().join("member")) + .collect(); + let expected = None; + assert_eq!(actual, expected); + project_dir.close() + } + + #[test] + fn test_extract_cargo_version_ws_missing_parent() -> io::Result<()> { + let config_name = "Cargo.toml"; + let config_content = toml::toml! { + [package] + name = "starship" + version.workspace = true + } + .to_string(); + + let project_dir = create_project_dir()?; + fill_config(&project_dir, config_name, Some(&config_content))?; + expect_output(&project_dir, None, None); + project_dir.close() + } + #[test] fn test_extract_nimble_package_version() -> io::Result<()> { let config_name = "test_project.nimble";