From d50a978efb8e538db90dad7128cc74f19ec2bc8d Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Sat, 11 Nov 2023 19:45:00 +0100 Subject: [PATCH 1/2] Add version_suffix_components setting --- README.md | 4 ++ src/build.rs | 102 +++++++++++++++++++++++++++++++++++++++++- src/install.rs | 25 ++++++----- src/lib.rs | 15 ------- src/pkg_config_gen.rs | 1 + src/target.rs | 7 ++- 6 files changed, 122 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e53231b..5a651d0 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,10 @@ version = "1.2.3" install_subdir = "gstreamer-1.0" # Used to disable versioning links when installing the dynamic library versioning = false +# Instead of using semver, select a fixed number of version components for your SONAME version suffix: +# Setting this to 1 with a version of 0.0.0 allows a suffix of `.so.0` +# Setting this to 3 always includes the full version in the SONAME (indicate any update is ABI breaking) +#version_suffix_components = 2 # Add `-Cpanic=abort` to the RUSTFLAGS automatically, it may be useful in case # something might panic in the crates used by the library. rustflags = "-Cpanic=abort" diff --git a/src/build.rs b/src/build.rs index 4e8279f..45c4538 100644 --- a/src/build.rs +++ b/src/build.rs @@ -14,6 +14,7 @@ use cargo::util::command_prelude::{ArgMatches, ArgMatchesExt, CompileMode, Profi use cargo::util::interning::InternedString; use cargo::{CliResult, Config}; +use anyhow::Context as _; use cargo_util::paths::{copy, create, create_dir_all, open, read, read_bytes, write}; use semver::Version; @@ -406,10 +407,33 @@ pub struct LibraryCApiConfig { pub version: Version, pub install_subdir: Option, pub versioning: bool, + pub version_suffix_components: Option, pub import_library: bool, pub rustflags: Vec, } +impl LibraryCApiConfig { + pub fn sover(&self) -> anyhow::Result { + let major = self.version.major; + let minor = self.version.minor; + let patch = self.version.patch; + + let sover = match self.version_suffix_components { + None => match (major, minor, patch) { + (0, 0, patch) => format!("0.0.{patch}"), + (0, minor, _) => format!("0.{minor}"), + (major, _, _) => format!("{major}"), + }, + Some(1) => format!("{major}"), + Some(2) => format!("{major}.{minor}"), + Some(3) => format!("{major}.{minor}.{patch}"), + Some(num) => anyhow::bail!("Unexpected number of suffix components: {num}"), + }; + + Ok(sover) + } +} + #[derive(Debug, Default)] pub struct InstallCApiConfig { pub include: Vec, @@ -619,6 +643,7 @@ fn load_manifest_capi_config(pkg: &Package) -> anyhow::Result { let mut version = pkg.version().clone(); let mut install_subdir = None; let mut versioning = true; + let mut version_suffix_components = None; let mut import_library = true; let mut rustflags = Vec::new(); @@ -636,6 +661,17 @@ fn load_manifest_capi_config(pkg: &Package) -> anyhow::Result { .get("versioning") .and_then(|v| v.as_bool()) .unwrap_or(true); + + if let Some(value) = library.get("version_suffix_components") { + let value = value.as_integer().with_context(|| { + format!("Value for `version_suffix_components` is not an integer: {value:?}") + })?; + let value = value + .try_into() + .with_context(|| format!("Value is too large: {value:?}"))?; + version_suffix_components = Some(value); + } + import_library = library .get("import_library") .and_then(|v| v.as_bool()) @@ -655,6 +691,7 @@ fn load_manifest_capi_config(pkg: &Package) -> anyhow::Result { version, install_subdir, versioning, + version_suffix_components, import_library, rustflags, }; @@ -886,7 +923,7 @@ fn compile_with_exec( let pkg_rustflags = &capi_config.library.rustflags; let mut leaf_args: Vec = rustc_target - .shared_object_link_args(&capi_config, &install_paths.libdir, root_output) + .shared_object_link_args(&capi_config, &install_paths.libdir, root_output)? .into_iter() .flat_map(|l| vec!["-C".to_string(), format!("link-arg={l}")]) .collect(); @@ -1246,3 +1283,66 @@ pub fn ctest( ops::run_tests(ws, &ops, &test_args) } + +#[cfg(test)] +mod tests { + use super::*; + use semver::Version; + + fn make_test_library_config(version: &str) -> LibraryCApiConfig { + LibraryCApiConfig { + name: "example".to_string(), + version: Version::parse(version).unwrap(), + install_subdir: None, + versioning: true, + version_suffix_components: None, + import_library: true, + rustflags: vec![], + } + } + + #[test] + pub fn test_semver_zero_zero_zero() { + let library = make_test_library_config("0.0.0"); + let sover = library.sover().unwrap(); + assert_eq!(sover, "0.0.0"); + } + + #[test] + pub fn test_semver_zero_one_zero() { + let library = make_test_library_config("0.1.0"); + let sover = library.sover().unwrap(); + assert_eq!(sover, "0.1"); + } + + #[test] + pub fn test_semver_one_zero_zero() { + let library = make_test_library_config("1.0.0"); + let sover = library.sover().unwrap(); + assert_eq!(sover, "1"); + } + + #[test] + pub fn text_one_fixed_zero_zero_zero() { + let mut library = make_test_library_config("0.0.0"); + library.version_suffix_components = Some(1); + let sover = library.sover().unwrap(); + assert_eq!(sover, "0"); + } + + #[test] + pub fn text_two_fixed_one_zero_zero() { + let mut library = make_test_library_config("1.0.0"); + library.version_suffix_components = Some(2); + let sover = library.sover().unwrap(); + assert_eq!(sover, "1.0"); + } + + #[test] + pub fn text_three_fixed_one_zero_zero() { + let mut library = make_test_library_config("1.0.0"); + library.version_suffix_components = Some(3); + let sover = library.sover().unwrap(); + assert_eq!(sover, "1.0.0"); + } +} diff --git a/src/install.rs b/src/install.rs index d0725b9..9ad4104 100644 --- a/src/install.rs +++ b/src/install.rs @@ -3,11 +3,9 @@ use std::path::{Component, Path, PathBuf}; use cargo::core::Workspace; use cargo_util::paths::{copy, create_dir_all}; -use semver::Version; use crate::build::*; use crate::build_targets::BuildTargets; -use crate::VersionExt; fn append_to_destdir(destdir: Option<&Path>, path: &Path) -> PathBuf { if let Some(destdir) = destdir { @@ -107,8 +105,13 @@ pub(crate) struct UnixLibNames { } impl UnixLibNames { - pub(crate) fn new(lib_type: LibType, lib_name: &str, lib_version: &Version) -> Option { - let main_version = lib_version.main_version(); + pub(crate) fn new( + lib_type: LibType, + library: &LibraryCApiConfig, + ) -> anyhow::Result> { + let lib_name = &library.name; + let lib_version = &library.version; + let main_version = library.sover()?; match lib_type { LibType::So => { @@ -119,11 +122,11 @@ impl UnixLibNames { ); let lib_with_main_ver = format!("{}.{}", lib, main_version); - Some(Self { + Ok(Some(Self { canonical: lib, with_main_ver: lib_with_main_ver, with_full_ver: lib_with_full_ver, - }) + })) } LibType::Dylib => { let lib = format!("lib{lib_name}.dylib"); @@ -133,13 +136,13 @@ impl UnixLibNames { "lib{}.{}.{}.{}.dylib", lib_name, lib_version.major, lib_version.minor, lib_version.patch ); - Some(Self { + Ok(Some(Self { canonical: lib, with_main_ver: lib_with_main_ver, with_full_ver: lib_with_full_ver, - }) + })) } - LibType::Windows => None, + LibType::Windows => Ok(None), } } @@ -236,12 +239,10 @@ pub fn cinstall(ws: &Workspace, packages: &[CPackage]) -> anyhow::Result<()> { if let Some(ref shared_lib) = build_targets.shared_lib { ws.config().shell().status("Installing", "shared library")?; - let lib_name = &capi_config.library.name; let lib_type = LibType::from_build_targets(build_targets); match lib_type { LibType::So | LibType::Dylib => { - let lib = UnixLibNames::new(lib_type, lib_name, &capi_config.library.version) - .unwrap(); + let lib = UnixLibNames::new(lib_type, &capi_config.library)?.unwrap(); lib.install(capi_config, shared_lib, &install_path_lib)?; } LibType::Windows => { diff --git a/src/lib.rs b/src/lib.rs index 66d7b9b..c63a297 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,18 +5,3 @@ pub mod config; pub mod install; pub mod pkg_config_gen; pub mod target; - -trait VersionExt { - /// build the main version string - fn main_version(&self) -> String; -} - -impl VersionExt for semver::Version { - fn main_version(&self) -> String { - match (self.major, self.minor, self.patch) { - (0, 0, patch) => format!("0.0.{patch}"), - (0, minor, _) => format!("0.{minor}"), - (major, _, _) => format!("{major}"), - } - } -} diff --git a/src/pkg_config_gen.rs b/src/pkg_config_gen.rs index 91d8b4d..c825d95 100644 --- a/src/pkg_config_gen.rs +++ b/src/pkg_config_gen.rs @@ -298,6 +298,7 @@ mod test { version: Version::parse("0.1.0").unwrap(), install_subdir: None, versioning: true, + version_suffix_components: None, import_library: true, rustflags: Vec::default(), }, diff --git a/src/target.rs b/src/target.rs index 136cf54..7ada1be 100644 --- a/src/target.rs +++ b/src/target.rs @@ -3,7 +3,6 @@ use std::path::Path; use anyhow::*; use crate::build::CApiConfig; -use crate::VersionExt; /// Split a target string to its components /// @@ -57,7 +56,7 @@ impl Target { capi_config: &CApiConfig, libdir: &Path, target_dir: &Path, - ) -> Vec { + ) -> anyhow::Result> { let mut lines = Vec::new(); let lib_name = &capi_config.library.name; @@ -70,7 +69,7 @@ impl Target { let os = &self.os; let env = &self.env; - let sover = version.main_version(); + let sover = capi_config.library.sover()?; if os == "android" { lines.push(format!("-Wl,-soname,lib{lib_name}.so")); @@ -112,6 +111,6 @@ impl Target { // See: https://github.com/emscripten-core/emscripten/blob/3.1.39/emcc.py#L92-L94 // else if os == "emscripten" - lines + Ok(lines) } } From 3f9e452500b37204efce3091c9d0fd9366393e5b Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Sun, 12 Nov 2023 14:47:40 +0100 Subject: [PATCH 2/2] Refactor to validate version_suffix_components while parsing --- src/build.rs | 54 ++++++++++++++++++++++++++++---------------------- src/install.rs | 19 ++++++++---------- src/target.rs | 6 +++--- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/build.rs b/src/build.rs index 45c4538..33b77f2 100644 --- a/src/build.rs +++ b/src/build.rs @@ -401,36 +401,40 @@ pub struct PkgConfigCApiConfig { pub strip_include_path_components: usize, } +#[derive(Debug)] +pub enum VersionSuffix { + Major, + MajorMinor, + MajorMinorPatch, +} + #[derive(Debug)] pub struct LibraryCApiConfig { pub name: String, pub version: Version, pub install_subdir: Option, pub versioning: bool, - pub version_suffix_components: Option, + pub version_suffix_components: Option, pub import_library: bool, pub rustflags: Vec, } impl LibraryCApiConfig { - pub fn sover(&self) -> anyhow::Result { + pub fn sover(&self) -> String { let major = self.version.major; let minor = self.version.minor; let patch = self.version.patch; - let sover = match self.version_suffix_components { + match self.version_suffix_components { None => match (major, minor, patch) { (0, 0, patch) => format!("0.0.{patch}"), (0, minor, _) => format!("0.{minor}"), (major, _, _) => format!("{major}"), }, - Some(1) => format!("{major}"), - Some(2) => format!("{major}.{minor}"), - Some(3) => format!("{major}.{minor}.{patch}"), - Some(num) => anyhow::bail!("Unexpected number of suffix components: {num}"), - }; - - Ok(sover) + Some(VersionSuffix::Major) => format!("{major}"), + Some(VersionSuffix::MajorMinor) => format!("{major}.{minor}"), + Some(VersionSuffix::MajorMinorPatch) => format!("{major}.{minor}.{patch}"), + } } } @@ -666,10 +670,12 @@ fn load_manifest_capi_config(pkg: &Package) -> anyhow::Result { let value = value.as_integer().with_context(|| { format!("Value for `version_suffix_components` is not an integer: {value:?}") })?; - let value = value - .try_into() - .with_context(|| format!("Value is too large: {value:?}"))?; - version_suffix_components = Some(value); + version_suffix_components = Some(match value { + 1 => VersionSuffix::Major, + 2 => VersionSuffix::MajorMinor, + 3 => VersionSuffix::MajorMinorPatch, + _ => anyhow::bail!("Out of range value for version suffix components: {value}"), + }); } import_library = library @@ -923,7 +929,7 @@ fn compile_with_exec( let pkg_rustflags = &capi_config.library.rustflags; let mut leaf_args: Vec = rustc_target - .shared_object_link_args(&capi_config, &install_paths.libdir, root_output)? + .shared_object_link_args(&capi_config, &install_paths.libdir, root_output) .into_iter() .flat_map(|l| vec!["-C".to_string(), format!("link-arg={l}")]) .collect(); @@ -1304,45 +1310,45 @@ mod tests { #[test] pub fn test_semver_zero_zero_zero() { let library = make_test_library_config("0.0.0"); - let sover = library.sover().unwrap(); + let sover = library.sover(); assert_eq!(sover, "0.0.0"); } #[test] pub fn test_semver_zero_one_zero() { let library = make_test_library_config("0.1.0"); - let sover = library.sover().unwrap(); + let sover = library.sover(); assert_eq!(sover, "0.1"); } #[test] pub fn test_semver_one_zero_zero() { let library = make_test_library_config("1.0.0"); - let sover = library.sover().unwrap(); + let sover = library.sover(); assert_eq!(sover, "1"); } #[test] pub fn text_one_fixed_zero_zero_zero() { let mut library = make_test_library_config("0.0.0"); - library.version_suffix_components = Some(1); - let sover = library.sover().unwrap(); + library.version_suffix_components = Some(VersionSuffix::Major); + let sover = library.sover(); assert_eq!(sover, "0"); } #[test] pub fn text_two_fixed_one_zero_zero() { let mut library = make_test_library_config("1.0.0"); - library.version_suffix_components = Some(2); - let sover = library.sover().unwrap(); + library.version_suffix_components = Some(VersionSuffix::MajorMinor); + let sover = library.sover(); assert_eq!(sover, "1.0"); } #[test] pub fn text_three_fixed_one_zero_zero() { let mut library = make_test_library_config("1.0.0"); - library.version_suffix_components = Some(3); - let sover = library.sover().unwrap(); + library.version_suffix_components = Some(VersionSuffix::MajorMinorPatch); + let sover = library.sover(); assert_eq!(sover, "1.0.0"); } } diff --git a/src/install.rs b/src/install.rs index 9ad4104..c087e74 100644 --- a/src/install.rs +++ b/src/install.rs @@ -105,13 +105,10 @@ pub(crate) struct UnixLibNames { } impl UnixLibNames { - pub(crate) fn new( - lib_type: LibType, - library: &LibraryCApiConfig, - ) -> anyhow::Result> { + pub(crate) fn new(lib_type: LibType, library: &LibraryCApiConfig) -> Option { let lib_name = &library.name; let lib_version = &library.version; - let main_version = library.sover()?; + let main_version = library.sover(); match lib_type { LibType::So => { @@ -122,11 +119,11 @@ impl UnixLibNames { ); let lib_with_main_ver = format!("{}.{}", lib, main_version); - Ok(Some(Self { + Some(Self { canonical: lib, with_main_ver: lib_with_main_ver, with_full_ver: lib_with_full_ver, - })) + }) } LibType::Dylib => { let lib = format!("lib{lib_name}.dylib"); @@ -136,13 +133,13 @@ impl UnixLibNames { "lib{}.{}.{}.{}.dylib", lib_name, lib_version.major, lib_version.minor, lib_version.patch ); - Ok(Some(Self { + Some(Self { canonical: lib, with_main_ver: lib_with_main_ver, with_full_ver: lib_with_full_ver, - })) + }) } - LibType::Windows => Ok(None), + LibType::Windows => None, } } @@ -242,7 +239,7 @@ pub fn cinstall(ws: &Workspace, packages: &[CPackage]) -> anyhow::Result<()> { let lib_type = LibType::from_build_targets(build_targets); match lib_type { LibType::So | LibType::Dylib => { - let lib = UnixLibNames::new(lib_type, &capi_config.library)?.unwrap(); + let lib = UnixLibNames::new(lib_type, &capi_config.library).unwrap(); lib.install(capi_config, shared_lib, &install_path_lib)?; } LibType::Windows => { diff --git a/src/target.rs b/src/target.rs index 7ada1be..1cbdef4 100644 --- a/src/target.rs +++ b/src/target.rs @@ -56,7 +56,7 @@ impl Target { capi_config: &CApiConfig, libdir: &Path, target_dir: &Path, - ) -> anyhow::Result> { + ) -> Vec { let mut lines = Vec::new(); let lib_name = &capi_config.library.name; @@ -69,7 +69,7 @@ impl Target { let os = &self.os; let env = &self.env; - let sover = capi_config.library.sover()?; + let sover = capi_config.library.sover(); if os == "android" { lines.push(format!("-Wl,-soname,lib{lib_name}.so")); @@ -111,6 +111,6 @@ impl Target { // See: https://github.com/emscripten-core/emscripten/blob/3.1.39/emcc.py#L92-L94 // else if os == "emscripten" - Ok(lines) + lines } }