#![allow(clippy::doc_markdown)] #![allow(clippy::must_use_candidate)] #![allow(clippy::redundant_closure_for_method_calls)]
pub const QUANTRS2_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const VERSION: &str = QUANTRS2_VERSION;
pub const QUANTRS2_CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const SCIRS2_MIN_VERSION: &str = "0.1.0";
pub const SCIRS2_VERSION: &str = "0.1.0";
pub const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
pub const GIT_COMMIT_HASH: Option<&str> = option_env!("VERGEN_GIT_SHA");
pub const GIT_BRANCH: Option<&str> = option_env!("VERGEN_GIT_BRANCH");
pub const RUSTC_VERSION: &str = env!("VERGEN_RUSTC_SEMVER");
pub const TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE");
pub const BUILD_PROFILE: &str = env!("VERGEN_CARGO_PROFILE");
pub const MIN_RUST_VERSION: &str = "1.86.0";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionInfo {
pub quantrs2: String,
pub scirs2: String,
pub rustc: String,
pub build_time: String,
pub git_commit: Option<String>,
pub git_branch: Option<String>,
pub target: String,
pub profile: String,
}
impl VersionInfo {
pub fn current() -> Self {
Self {
quantrs2: QUANTRS2_VERSION.to_string(),
scirs2: SCIRS2_VERSION.to_string(),
rustc: RUSTC_VERSION.to_string(),
build_time: BUILD_TIMESTAMP.to_string(),
git_commit: GIT_COMMIT_HASH.map(|s| s.to_string()),
git_branch: GIT_BRANCH.map(|s| s.to_string()),
target: TARGET_TRIPLE.to_string(),
profile: BUILD_PROFILE.to_string(),
}
}
pub fn version_string(&self) -> String {
format!("QuantRS2 v{}", self.quantrs2)
}
pub fn detailed_version_string(&self) -> String {
let mut parts = vec![
format!("QuantRS2 v{}", self.quantrs2),
format!("SciRS2 v{}", self.scirs2),
format!("rustc v{}", self.rustc),
];
if let Some(ref commit) = self.git_commit {
parts.push(format!("commit {}", &commit[..7.min(commit.len())]));
}
if let Some(ref branch) = self.git_branch {
parts.push(format!("branch {branch}"));
}
parts.push(format!("built {}", self.build_time));
parts.push(format!("target {}", self.target));
parts.push(format!("profile {}", self.profile));
parts.join(" | ")
}
}
impl std::fmt::Display for VersionInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.detailed_version_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompatibilityIssue {
RustVersionTooOld {
current: String,
required: String,
},
UnsupportedFeatureCombination {
description: String,
},
DependencyVersionMismatch {
dependency: String,
expected: String,
detected: Option<String>,
},
}
impl std::fmt::Display for CompatibilityIssue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RustVersionTooOld { current, required } => {
write!(
f,
"Rust version {current} is too old, {required} or newer is required"
)
}
Self::UnsupportedFeatureCombination { description } => {
write!(f, "Unsupported feature combination: {description}")
}
Self::DependencyVersionMismatch {
dependency,
expected,
detected,
} => {
if let Some(detected) = detected {
write!(
f,
"Dependency '{dependency}' version mismatch: expected {expected}, detected {detected}"
)
} else {
write!(
f,
"Dependency '{dependency}' version mismatch: expected {expected}, but version could not be detected"
)
}
}
}
}
}
pub fn check_compatibility() -> Result<(), Vec<CompatibilityIssue>> {
let mut issues = Vec::new();
if let Err(issue) = check_rust_version() {
issues.push(issue);
}
if let Err(mut feature_issues) = check_feature_compatibility() {
issues.append(&mut feature_issues);
}
if issues.is_empty() {
Ok(())
} else {
Err(issues)
}
}
fn check_rust_version() -> Result<(), CompatibilityIssue> {
let current = RUSTC_VERSION;
let required = MIN_RUST_VERSION;
if version_compare(current, required) >= 0 {
Ok(())
} else {
Err(CompatibilityIssue::RustVersionTooOld {
current: current.to_string(),
required: required.to_string(),
})
}
}
fn check_feature_compatibility() -> Result<(), Vec<CompatibilityIssue>> {
let mut issues = Vec::new();
#[cfg(all(feature = "sim", not(feature = "circuit")))]
{
issues.push(CompatibilityIssue::UnsupportedFeatureCombination {
description: "Feature 'sim' requires feature 'circuit'".to_string(),
});
}
#[cfg(all(feature = "tytan", not(feature = "anneal")))]
{
issues.push(CompatibilityIssue::UnsupportedFeatureCombination {
description: "Feature 'tytan' requires feature 'anneal'".to_string(),
});
}
#[cfg(all(feature = "ml", not(all(feature = "sim", feature = "anneal"))))]
{
issues.push(CompatibilityIssue::UnsupportedFeatureCombination {
description: "Feature 'ml' requires features 'sim' and 'anneal'".to_string(),
});
}
if issues.is_empty() {
Ok(())
} else {
Err(issues)
}
}
fn version_compare(v1: &str, v2: &str) -> i32 {
let v1_parts: Vec<&str> = v1.split('.').collect();
let v2_parts: Vec<&str> = v2.split('.').collect();
for (p1, p2) in v1_parts.iter().zip(v2_parts.iter()) {
let n1 = p1
.split('-')
.next()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
let n2 = p2
.split('-')
.next()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
if n1 < n2 {
return -1;
} else if n1 > n2 {
return 1;
}
}
match v1_parts.len().cmp(&v2_parts.len()) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Greater => 1,
std::cmp::Ordering::Equal => 0,
}
}
pub fn print_version() {
let info = VersionInfo::current();
println!("{}", info.version_string());
}
pub fn print_detailed_version() {
let info = VersionInfo::current();
println!("{}", info.detailed_version_string());
}
pub fn validate_environment() {
if let Err(issues) = check_compatibility() {
eprintln!("QuantRS2 compatibility issues detected:");
for issue in &issues {
eprintln!(" - {issue}");
}
panic!("Cannot continue due to compatibility issues");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_constants() {
assert!(!QUANTRS2_VERSION.is_empty());
assert!(!SCIRS2_VERSION.is_empty());
assert!(!RUSTC_VERSION.is_empty());
assert!(!BUILD_TIMESTAMP.is_empty());
assert!(!TARGET_TRIPLE.is_empty());
assert!(!BUILD_PROFILE.is_empty());
}
#[test]
fn test_version_info() {
let info = VersionInfo::current();
assert_eq!(info.quantrs2, QUANTRS2_VERSION);
assert_eq!(info.scirs2, SCIRS2_VERSION);
let version_str = info.version_string();
assert!(version_str.contains(QUANTRS2_VERSION));
let detailed_str = info.detailed_version_string();
assert!(detailed_str.contains(QUANTRS2_VERSION));
assert!(detailed_str.contains(SCIRS2_VERSION));
}
#[test]
fn test_version_compare() {
assert_eq!(version_compare("1.70.0", "1.70.0"), 0);
assert_eq!(version_compare("1.71.0", "1.70.0"), 1);
assert_eq!(version_compare("1.70.0", "1.71.0"), -1);
assert_eq!(version_compare("1.70.1", "1.70.0"), 1);
assert_eq!(version_compare("2.0.0", "1.99.0"), 1);
assert!(version_compare("1.70.0-beta.1", "1.70.0") >= 0);
assert_eq!(version_compare("1.71.0-rc.2", "1.70.0"), 1);
}
#[test]
fn test_compatibility_check() {
let result = check_compatibility();
if let Err(ref issues) = result {
for issue in issues {
eprintln!("Compatibility issue: {issue}");
}
}
match result {
Ok(()) => {
}
Err(issues) => {
eprintln!("Note: {} compatibility issues detected (this may be expected in some build configurations)", issues.len());
}
}
}
#[test]
fn test_rust_version_check() {
let result = check_rust_version();
assert!(result.is_ok(), "Rust version check failed: {result:?}");
}
#[test]
fn test_display_implementations() {
let info = VersionInfo::current();
let display_str = format!("{info}");
assert!(!display_str.is_empty());
let issue = CompatibilityIssue::RustVersionTooOld {
current: "1.60.0".to_string(),
required: "1.70.0".to_string(),
};
let issue_str = format!("{issue}");
assert!(issue_str.contains("1.60.0"));
assert!(issue_str.contains("1.70.0"));
}
}