use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct X86PackageManagers {
pub conan: X86ConanSupport,
pub vcpkg: X86VcpkgSupport,
pub spack: X86SpackSupport,
pub pkg_config: X86PkgConfig,
pub cmake_find: X86CMakeFindPackage,
pub system: X86SystemPackageManager,
pub locking: X86DependencyLocking,
pub cache: HashMap<String, X86ResolvedPackage>,
pub target_triple: String,
pub microarch_level: X86MicroarchLevel,
pub build_type: X86CMakeBuildType,
pub opt_level: X86OptLevel,
}
impl X86PackageManagers {
pub fn new() -> Self {
Self {
conan: X86ConanSupport::new(),
vcpkg: X86VcpkgSupport::new(),
spack: X86SpackSupport::new(),
pkg_config: X86PkgConfig::new(),
cmake_find: X86CMakeFindPackage::new(),
system: X86SystemPackageManager::new(),
locking: X86DependencyLocking::new(),
cache: HashMap::new(),
target_triple: "x86_64-unknown-linux-gnu".to_string(),
microarch_level: X86MicroarchLevel::X86_64,
build_type: X86CMakeBuildType::Release,
opt_level: X86OptLevel::O2,
}
}
pub fn for_i686_linux() -> Self {
let mut mgr = Self::new();
mgr.target_triple = "i686-unknown-linux-gnu".to_string();
mgr.microarch_level = X86MicroarchLevel::I686;
mgr.vcpkg = X86VcpkgSupport::for_i686_linux();
mgr.conan = X86ConanSupport::for_i686();
mgr.spack = X86SpackSupport::for_i686();
mgr.system = X86SystemPackageManager::for_i686_linux();
mgr
}
pub fn for_x86_64_avx2() -> Self {
let mut mgr = Self::new();
mgr.microarch_level = X86MicroarchLevel::X86_64_V3;
mgr.conan = X86ConanSupport::for_x86_64_avx2();
mgr.spack = X86SpackSupport::for_x86_64_v3();
mgr
}
pub fn for_x86_64_avx512() -> Self {
let mut mgr = Self::new();
mgr.microarch_level = X86MicroarchLevel::X86_64_V4;
mgr.conan = X86ConanSupport::for_x86_64_avx512();
mgr.spack = X86SpackSupport::for_x86_64_v4();
mgr
}
pub fn for_x86_64_windows() -> Self {
let mut mgr = Self::new();
mgr.target_triple = "x86_64-pc-windows-msvc".to_string();
mgr.vcpkg = X86VcpkgSupport::for_x64_windows();
mgr.conan = X86ConanSupport::for_x86_64_windows();
mgr.system = X86SystemPackageManager::for_windows();
mgr
}
pub fn for_x86_64_macos() -> Self {
let mut mgr = Self::new();
mgr.target_triple = "x86_64-apple-darwin".to_string();
mgr.vcpkg = X86VcpkgSupport::for_x64_osx();
mgr.system = X86SystemPackageManager::for_macos();
mgr
}
pub fn with_build_type(mut self, bt: X86CMakeBuildType) -> Self {
self.build_type = bt;
self.conan.build_type = bt;
self
}
pub fn with_opt_level(mut self, level: X86OptLevel) -> Self {
self.opt_level = level;
self
}
pub fn resolve(&mut self, package_name: &str) -> Result<X86ResolvedPackage, String> {
if let Some(cached) = self.cache.get(package_name) {
return Ok(cached.clone());
}
if let Ok(pkg) = self.pkg_config.find(package_name) {
self.cache.insert(package_name.to_string(), pkg.clone());
return Ok(pkg);
}
if let Ok(pkg) = self.cmake_find.find_package(package_name) {
self.cache.insert(package_name.to_string(), pkg.clone());
return Ok(pkg);
}
if let Ok(pkg) = self.system.resolve(package_name) {
self.cache.insert(package_name.to_string(), pkg.clone());
return Ok(pkg);
}
Err(format!(
"Package '{}' not found in any X86 package manager",
package_name
))
}
pub fn resolve_all(&mut self, packages: &[&str]) -> X86PackageResolution {
let start = std::time::Instant::now();
let mut resolution = X86PackageResolution::default();
for pkg in packages {
match self.resolve(pkg) {
Ok(dep) => {
resolution.resolved.push(dep);
resolution.cache_hits += 1;
}
Err(e) => {
resolution.missing.push(pkg.to_string());
resolution.errors.push(e);
}
}
}
resolution.total_time_ms = start.elapsed().as_millis() as u64;
resolution
}
pub fn generate_cmake_integration(&self) -> String {
let mut cmake = String::new();
cmake.push_str("# Auto-generated by X86PackageManagers\n");
cmake.push_str("cmake_minimum_required(VERSION 3.16)\n\n");
cmake.push_str(&self.conan.generate_cmake_integration());
cmake.push('\n');
cmake.push_str(&self.vcpkg.generate_cmake_integration());
cmake.push('\n');
cmake.push_str(&self.spack.generate_cmake_hints());
cmake.push('\n');
cmake
}
pub fn generate_lock_file(&self, output_path: &Path) -> Result<(), io::Error> {
self.locking.write_lock_file(&self.cache, output_path)
}
pub fn clear_cache(&mut self) {
self.cache.clear();
self.pkg_config.clear_cache();
self.cmake_find.clear_cache();
}
pub fn cache_stats(&self) -> X86CacheStats {
X86CacheStats {
total_entries: self.cache.len(),
total_size_bytes: self.cache.values().map(|p| p.estimated_size()).sum(),
pkg_config_hits: self.pkg_config.cache_hits,
cmake_hits: self.cmake_find.cache_hits,
system_hits: self.system.cache_hits,
}
}
}
impl Default for X86PackageManagers {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86MicroarchLevel {
X86_64,
X86_64_V2,
X86_64_V3,
X86_64_V4,
I686,
Pentium4,
Prescott,
Core2,
Nehalem,
SandyBridge,
Haswell,
Skylake,
Icelake,
AlderLake,
}
impl X86MicroarchLevel {
pub fn march_flag(&self) -> &'static str {
match self {
Self::X86_64 => "x86-64",
Self::X86_64_V2 => "x86-64-v2",
Self::X86_64_V3 => "x86-64-v3",
Self::X86_64_V4 => "x86-64-v4",
Self::I686 => "i686",
Self::Pentium4 => "pentium4",
Self::Prescott => "prescott",
Self::Core2 => "core2",
Self::Nehalem => "nehalem",
Self::SandyBridge => "sandybridge",
Self::Haswell => "haswell",
Self::Skylake => "skylake",
Self::Icelake => "icelake-client",
Self::AlderLake => "alderlake",
}
}
pub fn feature_flags(&self) -> &'static [&'static str] {
match self {
Self::X86_64 => &["mmx", "sse", "sse2"],
Self::X86_64_V2 => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "cx16",
],
Self::X86_64_V3 => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "cx16", "avx",
"avx2", "bmi", "bmi2", "f16c", "fma", "lzcnt", "movbe",
],
Self::X86_64_V4 => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "cx16", "avx",
"avx2", "bmi", "bmi2", "f16c", "fma", "lzcnt", "movbe", "avx512f", "avx512bw",
"avx512cd", "avx512dq", "avx512vl",
],
Self::I686 => &["mmx", "sse"],
Self::Pentium4 => &["mmx", "sse", "sse2"],
Self::Prescott => &["mmx", "sse", "sse2", "sse3"],
Self::Core2 => &["mmx", "sse", "sse2", "sse3", "ssse3"],
Self::Nehalem => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt",
],
Self::SandyBridge => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx",
],
Self::Haswell => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2",
"bmi", "bmi2", "fma", "lzcnt", "movbe", "f16c",
],
Self::Skylake => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2",
"bmi", "bmi2", "fma", "lzcnt", "movbe", "f16c", "sgx",
],
Self::Icelake => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2",
"bmi", "bmi2", "fma", "lzcnt", "movbe", "f16c", "avx512f", "avx512bw", "avx512cd",
"avx512dq", "avx512vl", "sgx",
],
Self::AlderLake => &[
"mmx", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2",
"bmi", "bmi2", "fma", "lzcnt", "movbe", "f16c", "avx512f", "avx512bw", "avx512cd",
"avx512dq", "avx512vl", "sgx", "avxvnni",
],
}
}
pub fn is_64bit(&self) -> bool {
!matches!(self, Self::I686 | Self::Pentium4 | Self::Prescott)
}
pub fn conan_arch(&self) -> &'static str {
if self.is_64bit() {
"x86_64"
} else {
"x86"
}
}
pub fn spack_target(&self) -> &'static str {
match self {
Self::X86_64
| Self::X86_64_V2
| Self::X86_64_V3
| Self::X86_64_V4
| Self::Nehalem
| Self::SandyBridge
| Self::Haswell
| Self::Skylake
| Self::Icelake
| Self::AlderLake => "x86_64",
Self::I686 | Self::Pentium4 | Self::Prescott | Self::Core2 => "i686",
}
}
}
impl fmt::Display for X86MicroarchLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.march_flag())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86OptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
Ofast,
}
impl X86OptLevel {
pub fn flag(&self) -> &'static str {
match self {
Self::O0 => "-O0",
Self::O1 => "-O1",
Self::O2 => "-O2",
Self::O3 => "-O3",
Self::Os => "-Os",
Self::Oz => "-Oz",
Self::Ofast => "-Ofast",
}
}
}
impl Default for X86OptLevel {
fn default() -> Self {
Self::O2
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CMakeBuildType {
Debug,
Release,
RelWithDebInfo,
MinSizeRel,
}
impl X86CMakeBuildType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Debug => "Debug",
Self::Release => "Release",
Self::RelWithDebInfo => "RelWithDebInfo",
Self::MinSizeRel => "MinSizeRel",
}
}
}
impl Default for X86CMakeBuildType {
fn default() -> Self {
Self::Release
}
}
#[derive(Debug, Clone)]
pub struct X86ResolvedPackage {
pub name: String,
pub version: String,
pub description: String,
pub homepage: Option<String>,
pub license: Option<String>,
pub include_dirs: Vec<PathBuf>,
pub library_dirs: Vec<PathBuf>,
pub libraries: Vec<String>,
pub compile_flags: Vec<String>,
pub link_flags: Vec<String>,
pub defines: HashMap<String, String>,
pub dependencies: Vec<String>,
pub is_x86_optimized: bool,
pub microarch_requirement: Option<X86MicroarchLevel>,
pub source: X86PackageSource,
}
impl X86ResolvedPackage {
pub fn estimated_size(&self) -> usize {
self.name.len()
+ self.version.len()
+ self.description.len()
+ self.include_dirs.len() * 64
+ self.library_dirs.len() * 64
+ self.libraries.len() * 32
+ self.compile_flags.iter().map(|s| s.len()).sum::<usize>()
+ self.link_flags.iter().map(|s| s.len()).sum::<usize>()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86PackageSource {
Conan,
Vcpkg,
Spack,
PkgConfig,
CMakeFindPackage,
SystemApt,
SystemYum,
SystemDnf,
SystemPacman,
SystemBrew,
SystemZypper,
SystemApk,
SystemEmerge,
SystemXbps,
Custom,
}
impl fmt::Display for X86PackageSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Conan => write!(f, "conan"),
Self::Vcpkg => write!(f, "vcpkg"),
Self::Spack => write!(f, "spack"),
Self::PkgConfig => write!(f, "pkg-config"),
Self::CMakeFindPackage => write!(f, "cmake-find-package"),
Self::SystemApt => write!(f, "apt"),
Self::SystemYum => write!(f, "yum"),
Self::SystemDnf => write!(f, "dnf"),
Self::SystemPacman => write!(f, "pacman"),
Self::SystemBrew => write!(f, "brew"),
Self::SystemZypper => write!(f, "zypper"),
Self::SystemApk => write!(f, "apk"),
Self::SystemEmerge => write!(f, "emerge"),
Self::SystemXbps => write!(f, "xbps"),
Self::Custom => write!(f, "custom"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86PackageResolution {
pub resolved: Vec<X86ResolvedPackage>,
pub missing: Vec<String>,
pub errors: Vec<String>,
pub total_time_ms: u64,
pub cache_hits: usize,
}
impl Default for X86PackageResolution {
fn default() -> Self {
Self {
resolved: Vec::new(),
missing: Vec::new(),
errors: Vec::new(),
total_time_ms: 0,
cache_hits: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CacheStats {
pub total_entries: usize,
pub total_size_bytes: usize,
pub pkg_config_hits: usize,
pub cmake_hits: usize,
pub system_hits: usize,
}
#[derive(Debug, Clone)]
pub struct X86ConanSupport {
pub conanfile_path: PathBuf,
pub profile: String,
pub build_type: X86CMakeBuildType,
pub settings: HashMap<String, String>,
pub options: HashMap<String, String>,
pub requires: Vec<X86ConanRequirement>,
pub generators: Vec<X86ConanGenerator>,
pub build_policy: X86ConanBuildPolicy,
pub remote: Option<String>,
pub microarch: X86MicroarchLevel,
pub conan_v2: bool,
pub extra_args: Vec<String>,
}
impl X86ConanSupport {
pub fn new() -> Self {
let mut settings = HashMap::new();
settings.insert("os".to_string(), "Linux".to_string());
settings.insert("arch".to_string(), "x86_64".to_string());
settings.insert("compiler".to_string(), "clang".to_string());
settings.insert("compiler.version".to_string(), "17".to_string());
settings.insert("compiler.libcxx".to_string(), "libstdc++11".to_string());
settings.insert("build_type".to_string(), "Release".to_string());
Self {
conanfile_path: PathBuf::from("conanfile.txt"),
profile: "default".to_string(),
build_type: X86CMakeBuildType::Release,
settings,
options: HashMap::new(),
requires: Vec::new(),
generators: vec![
X86ConanGenerator::CMakeDeps,
X86ConanGenerator::CMakeToolchain,
],
build_policy: X86ConanBuildPolicy::Missing,
remote: None,
microarch: X86MicroarchLevel::X86_64,
conan_v2: true,
extra_args: Vec::new(),
}
}
pub fn for_i686() -> Self {
let mut s = Self::new();
s.settings.insert("arch".to_string(), "x86".to_string());
s.microarch = X86MicroarchLevel::I686;
s.profile = "i686-release".to_string();
s
}
pub fn for_x86_64_avx2() -> Self {
let mut s = Self::new();
s.microarch = X86MicroarchLevel::X86_64_V3;
s.profile = "x86_64-avx2-release".to_string();
s
}
pub fn for_x86_64_avx512() -> Self {
let mut s = Self::new();
s.microarch = X86MicroarchLevel::X86_64_V4;
s.profile = "x86_64-avx512-release".to_string();
s
}
pub fn for_x86_64_windows() -> Self {
let mut s = Self::new();
s.settings.insert("os".to_string(), "Windows".to_string());
s.settings
.insert("compiler".to_string(), "msvc".to_string());
s.settings
.insert("compiler.version".to_string(), "193".to_string());
s.settings
.insert("compiler.runtime".to_string(), "dynamic".to_string());
s.profile = "x86_64-windows-release".to_string();
s
}
pub fn add_requirement(&mut self, name: &str, version: &str) -> &mut Self {
self.requires.push(X86ConanRequirement {
name: name.to_string(),
version_range: version.to_string(),
override_version: false,
private: false,
transitive_headers: true,
});
self
}
pub fn add_generator(&mut self, r#gen: X86ConanGenerator) -> &mut Self {
if !self.generators.contains(&r#gen) {
self.generators.push(r#gen);
}
self
}
pub fn with_build_policy(mut self, policy: X86ConanBuildPolicy) -> Self {
self.build_policy = policy;
self
}
pub fn with_remote(mut self, remote: &str) -> Self {
self.remote = Some(remote.to_string());
self
}
pub fn generate_conanfile_txt(&self) -> String {
let mut txt = String::new();
txt.push_str("# Auto-generated conanfile.txt by X86ConanSupport\n\n");
txt.push_str("[requires]\n");
if self.requires.is_empty() {
txt.push_str("# Add your requires here, e.g.:\n");
txt.push_str("# boost/1.84.0\n");
} else {
for req in &self.requires {
txt.push_str(&format!("{}/{}\n", req.name, req.version_range));
}
}
txt.push('\n');
txt.push_str("[generators]\n");
for r#gen in &self.generators {
txt.push_str(&format!("{}\n", r#gen.command_arg()));
}
txt.push('\n');
if !self.options.is_empty() {
txt.push_str("[options]\n");
for (key, val) in &self.options {
txt.push_str(&format!("{}={}\n", key, val));
}
txt.push('\n');
}
if self.conan_v2 {
txt.push_str("[layout]\n");
txt.push_str("cmake_layout\n");
}
txt
}
pub fn generate_conanfile_py(&self) -> String {
let mut py = String::new();
py.push_str("# Auto-generated conanfile.py by X86ConanSupport\n");
py.push_str("from conan import ConanFile\n");
py.push_str("from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout\n");
py.push_str("from conan.tools.build import check_min_cppstd\n\n");
py.push_str("class X86ProjectConan(ConanFile):\n");
py.push_str(" name = \"x86-project\"\n");
py.push_str(" version = \"1.0.0\"\n");
py.push_str(" license = \"MIT\"\n");
py.push_str(" author = \"X86PackageManagers\"\n");
py.push_str(" description = \"X86-optimized C++ project\"\n\n");
py.push_str(" settings = \"os\", \"compiler\", \"build_type\", \"arch\"\n");
py.push_str(" options = {\"shared\": [True, False], \"fPIC\": [True, False]}\n");
py.push_str(" default_options = {\"shared\": False, \"fPIC\": True}\n\n");
py.push_str(" exports_sources = \"CMakeLists.txt\", \"src/*\"\n\n");
py.push_str(" def requirements(self):\n");
if self.requires.is_empty() {
py.push_str(" pass\n");
} else {
for req in &self.requires {
py.push_str(&format!(
" self.requires(\"{}/{}\")\n",
req.name, req.version_range
));
}
}
py.push('\n');
py.push_str(" def generate(self):\n");
py.push_str(" tc = CMakeToolchain(self)\n");
py.push_str(&format!(
" tc.variables[\"CMAKE_BUILD_TYPE\"] = \"{}\"\n",
self.build_type.as_str()
));
if self.microarch.is_64bit() {
py.push_str(" tc.variables[\"CMAKE_SYSTEM_PROCESSOR\"] = \"x86_64\"\n");
} else {
py.push_str(" tc.variables[\"CMAKE_SYSTEM_PROCESSOR\"] = \"i686\"\n");
}
py.push_str(&format!(
" tc.variables[\"X86_MARCH\"] = \"{}\"\n",
self.microarch.march_flag()
));
py.push_str(" tc.generate()\n\n");
py.push_str(" def layout(self):\n");
py.push_str(" cmake_layout(self)\n\n");
py.push_str(" def build(self):\n");
py.push_str(" cmake = CMake(self)\n");
py.push_str(" cmake.configure()\n");
py.push_str(" cmake.build()\n\n");
py.push_str(" def package(self):\n");
py.push_str(" cmake = CMake(self)\n");
py.push_str(" cmake.install()\n\n");
py.push_str(" def package_info(self):\n");
py.push_str(" self.cpp_info.libs = [\"x86-project\"]\n");
py
}
pub fn detect_profile() -> String {
if cfg!(target_arch = "x86_64") {
if cfg!(target_os = "linux") {
"x86_64-linux-release".to_string()
} else if cfg!(target_os = "windows") {
"x86_64-windows-release".to_string()
} else if cfg!(target_os = "macos") {
"x86_64-macos-release".to_string()
} else {
"x86_64-release".to_string()
}
} else if cfg!(target_arch = "x86") {
"x86-linux-release".to_string()
} else {
"default".to_string()
}
}
pub fn generate_profile(&self, name: &str) -> String {
let mut profile = String::new();
profile.push_str(&format!("# Conan profile: {} (auto-generated)\n", name));
for (key, val) in &self.settings {
profile.push_str(&format!("[settings]\n{}={}\n", key, val));
}
profile.push_str("\n[conf]\n");
profile.push_str(
"tools.build:compiler_executables={\"c\": \"clang\", \"cpp\": \"clang++\"}\n",
);
if !self.options.is_empty() {
profile.push_str("\n[options]\n");
for (key, val) in &self.options {
profile.push_str(&format!("{}={}\n", key, val));
}
}
profile.push_str("\n[buildenv]\n");
profile.push_str(&format!("CFLAGS=-march={}\n", self.microarch.march_flag()));
profile.push_str(&format!(
"CXXFLAGS=-march={}\n",
self.microarch.march_flag()
));
profile.push_str("\n[env]\n");
profile.push_str(&format!("CONAN_CMAKE_GENERATOR=Ninja\n"));
profile
}
pub fn generate_standard_profiles() -> HashMap<String, String> {
let mut profiles = HashMap::new();
let standard_configs = vec![
(
"x86_64-linux-release",
X86MicroarchLevel::X86_64,
X86CMakeBuildType::Release,
),
(
"x86_64-linux-debug",
X86MicroarchLevel::X86_64,
X86CMakeBuildType::Debug,
),
(
"x86_64-v3-linux-release",
X86MicroarchLevel::X86_64_V3,
X86CMakeBuildType::Release,
),
(
"x86_64-v4-linux-release",
X86MicroarchLevel::X86_64_V4,
X86CMakeBuildType::Release,
),
(
"i686-linux-release",
X86MicroarchLevel::I686,
X86CMakeBuildType::Release,
),
(
"haswell-linux-release",
X86MicroarchLevel::Haswell,
X86CMakeBuildType::Release,
),
(
"skylake-linux-release",
X86MicroarchLevel::Skylake,
X86CMakeBuildType::Release,
),
];
for (name, microarch, build_type) in &standard_configs {
let mut s = X86ConanSupport::new();
s.microarch = *microarch;
s.build_type = *build_type;
profiles.insert(name.to_string(), s.generate_profile(name));
}
profiles
}
pub fn search(&self, query: &str) -> Vec<X86ConanPackageInfo> {
let mut results = Vec::new();
if let Ok(output) = ProcessCommand::new("conan")
.args(["search", query, "-r", "conancenter"])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with("Existing") {
let parts: Vec<&str> = trimmed.split('/').collect();
if parts.len() >= 2 {
results.push(X86ConanPackageInfo {
name: parts[0].to_string(),
version: parts[1].to_string(),
remote: "conancenter".to_string(),
description: String::new(),
});
}
}
}
}
}
for known in X86ConanSupport::known_packages() {
if known.name.contains(query) {
results.push(known);
}
}
results
}
pub fn known_packages() -> Vec<X86ConanPackageInfo> {
vec![
X86ConanPackageInfo {
name: "boost".into(),
version: "1.84.0".into(),
remote: "conancenter".into(),
description: "Boost C++ libraries".into(),
},
X86ConanPackageInfo {
name: "openssl".into(),
version: "3.2.0".into(),
remote: "conancenter".into(),
description: "OpenSSL cryptography library".into(),
},
X86ConanPackageInfo {
name: "zlib".into(),
version: "1.3.1".into(),
remote: "conancenter".into(),
description: "Zlib compression library".into(),
},
X86ConanPackageInfo {
name: "fmt".into(),
version: "10.2.1".into(),
remote: "conancenter".into(),
description: "Formatting library".into(),
},
X86ConanPackageInfo {
name: "spdlog".into(),
version: "1.13.0".into(),
remote: "conancenter".into(),
description: "Fast C++ logging library".into(),
},
X86ConanPackageInfo {
name: "nlohmann_json".into(),
version: "3.11.3".into(),
remote: "conancenter".into(),
description: "JSON for Modern C++".into(),
},
X86ConanPackageInfo {
name: "gtest".into(),
version: "1.14.0".into(),
remote: "conancenter".into(),
description: "Google Test".into(),
},
X86ConanPackageInfo {
name: "benchmark".into(),
version: "1.8.3".into(),
remote: "conancenter".into(),
description: "Google Benchmark".into(),
},
X86ConanPackageInfo {
name: "protobuf".into(),
version: "25.2".into(),
remote: "conancenter".into(),
description: "Protocol Buffers".into(),
},
X86ConanPackageInfo {
name: "grpc".into(),
version: "1.60.0".into(),
remote: "conancenter".into(),
description: "gRPC framework".into(),
},
X86ConanPackageInfo {
name: "eigen".into(),
version: "3.4.0".into(),
remote: "conancenter".into(),
description: "Eigen linear algebra".into(),
},
X86ConanPackageInfo {
name: "openblas".into(),
version: "0.3.26".into(),
remote: "conancenter".into(),
description: "Optimized BLAS library".into(),
},
]
}
pub fn resolve(&self, package_name: &str) -> Result<X86ResolvedPackage, String> {
let known = X86ConanSupport::known_packages();
let pkg = known
.iter()
.find(|p| p.name == package_name)
.ok_or_else(|| format!("Package '{}' not found in ConanCenter", package_name))?;
Ok(X86ResolvedPackage {
name: pkg.name.clone(),
version: pkg.version.clone(),
description: pkg.description.clone(),
homepage: Some(format!("https://conan.io/center/recipes/{}", pkg.name)),
license: Some("MIT".to_string()),
include_dirs: vec![PathBuf::from(format!(
"~/.conan2/p/{}/include",
package_name
))],
library_dirs: vec![PathBuf::from(format!("~/.conan2/p/{}/lib", package_name))],
libraries: vec![package_name.to_string()],
compile_flags: vec![format!("-march={}", self.microarch.march_flag())],
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: true,
microarch_requirement: Some(self.microarch),
source: X86PackageSource::Conan,
})
}
pub fn build_dependency_graph(&self) -> X86DependencyGraph {
let mut graph = X86DependencyGraph::default();
let root = "x86-project".to_string();
for req in &self.requires {
let node = X86DepGraphNode {
name: req.name.clone(),
version: req.version_range.clone(),
is_direct: true,
is_x86_native: true,
source: X86PackageSource::Conan,
};
graph.add_node(node);
graph.add_edge(&root, &req.name, "requires");
}
graph.root = root;
graph
}
pub fn generate_cmake_integration(&self) -> String {
if self.conan_v2 {
r#"# Conan 2.x integration
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_toolchain.cmake")
message(STATUS "Running Conan install...")
execute_process(
COMMAND conan install ${CMAKE_SOURCE_DIR}
--output-folder=${CMAKE_BINARY_DIR}
--build=missing
--settings=build_type=${CMAKE_BUILD_TYPE}
RESULT_VARIABLE _conan_result
)
if(NOT _conan_result EQUAL 0)
message(FATAL_ERROR "Conan install failed")
endif()
endif()
list(APPEND CMAKE_PROJECT_TOP_LEVEL_INCLUDES
${CMAKE_BINARY_DIR}/conan_toolchain.cmake
)
"#
.to_string()
} else {
r#"# Conan 1.x integration
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake")
message(STATUS "Running Conan install...")
execute_process(
COMMAND conan install ${CMAKE_SOURCE_DIR}
--install-folder=${CMAKE_BINARY_DIR}
--build=missing
RESULT_VARIABLE _conan_result
)
endif()
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)
"#
.to_string()
}
}
pub fn conan_install_command(&self) -> Vec<String> {
let mut cmd = vec!["conan".to_string(), "install".to_string()];
cmd.push(".".to_string());
cmd.push("--output-folder=build".to_string());
cmd.push(format!(
"--settings=build_type={}",
self.build_type.as_str()
));
cmd.push(self.build_policy.as_flag().to_string());
if let Some(ref remote) = self.remote {
cmd.push(format!("--remote={}", remote));
}
for r#gen in &self.generators {
cmd.push(format!("--generator={}", r#gen.command_arg()));
}
cmd.extend(self.extra_args.clone());
cmd
}
}
impl Default for X86ConanSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ConanRequirement {
pub name: String,
pub version_range: String,
pub override_version: bool,
pub private: bool,
pub transitive_headers: bool,
}
#[derive(Debug, Clone)]
pub struct X86ConanPackageInfo {
pub name: String,
pub version: String,
pub remote: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ConanGenerator {
CMake,
CMakeDeps,
CMakeToolchain,
Xcode,
Premake,
Meson,
Bazel,
PkgConfig,
Json,
TextFiles,
CompileCommands,
MSBuildDeps,
MSBuildToolchain,
Ninja,
NinjaMultiConfig,
}
impl X86ConanGenerator {
pub fn command_arg(&self) -> &'static str {
match self {
Self::CMake => "cmake",
Self::CMakeDeps => "CMakeDeps",
Self::CMakeToolchain => "CMakeToolchain",
Self::Xcode => "Xcode",
Self::Premake => "premake",
Self::Meson => "MesonToolchain",
Self::Bazel => "BazelDeps",
Self::PkgConfig => "PkgConfigDeps",
Self::Json => "JsonDeps",
Self::TextFiles => "TXTGenerator",
Self::CompileCommands => "compile_commands",
Self::MSBuildDeps => "MSBuildDeps",
Self::MSBuildToolchain => "MSBuildToolchain",
Self::Ninja => "Ninja",
Self::NinjaMultiConfig => "NinjaMultiConfig",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ConanBuildPolicy {
Missing,
Never,
Outdated,
Cascade,
}
impl X86ConanBuildPolicy {
pub fn as_flag(&self) -> &'static str {
match self {
Self::Missing => "--build=missing",
Self::Never => "--build=never",
Self::Outdated => "--build=outdated",
Self::Cascade => "--build=cascade",
}
}
}
#[derive(Debug, Clone)]
pub struct X86DependencyGraph {
pub root: String,
pub nodes: HashMap<String, X86DepGraphNode>,
pub edges: Vec<(String, String, String)>, pub topo_order: Vec<String>,
}
impl X86DependencyGraph {
pub fn add_node(&mut self, node: X86DepGraphNode) {
self.nodes.insert(node.name.clone(), node);
}
pub fn add_edge(&mut self, from: &str, to: &str, edge_type: &str) {
self.edges
.push((from.to_string(), to.to_string(), edge_type.to_string()));
}
pub fn compute_topological_order(&mut self) -> Result<Vec<String>, String> {
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut adj: HashMap<String, Vec<String>> = HashMap::new();
for node_name in self.nodes.keys() {
in_degree.entry(node_name.clone()).or_insert(0);
adj.entry(node_name.clone()).or_default();
}
for (from, to, _) in &self.edges {
adj.entry(from.clone()).or_default().push(to.clone());
*in_degree.entry(to.clone()).or_insert(0) += 1;
in_degree.entry(from.clone()).or_insert(0);
}
let mut queue: Vec<String> = in_degree
.iter()
.filter(|&(_, °)| deg == 0)
.map(|(name, _)| name.clone())
.collect();
let mut sorted = Vec::new();
while let Some(node) = queue.pop() {
sorted.push(node.clone());
if let Some(neighbors) = adj.get(&node) {
for neighbor in neighbors {
if let Some(deg) = in_degree.get_mut(neighbor) {
*deg -= 1;
if *deg == 0 {
queue.push(neighbor.clone());
}
}
}
}
}
if sorted.len() != self.nodes.len() {
return Err("Dependency graph contains a cycle".to_string());
}
self.topo_order = sorted.clone();
Ok(sorted)
}
pub fn to_dot(&self) -> String {
let mut dot = String::from("digraph X86Dependencies {\n");
dot.push_str(" rankdir=LR;\n");
dot.push_str(" node [shape=box, style=filled, fillcolor=lightyellow];\n\n");
for (name, node) in &self.nodes {
let color = if node.is_direct {
"lightblue"
} else {
"lightgrey"
};
dot.push_str(&format!(
" \"{}\" [label=\"{}\\n{}\", fillcolor={}];\n",
name, node.name, node.version, color
));
}
for (from, to, etype) in &self.edges {
dot.push_str(&format!(
" \"{}\" -> \"{}\" [label=\"{}\"];\n",
from, to, etype
));
}
dot.push_str("}\n");
dot
}
}
impl Default for X86DependencyGraph {
fn default() -> Self {
Self {
root: String::new(),
nodes: HashMap::new(),
edges: Vec::new(),
topo_order: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DepGraphNode {
pub name: String,
pub version: String,
pub is_direct: bool,
pub is_x86_native: bool,
pub source: X86PackageSource,
}
#[derive(Debug, Clone)]
pub struct X86VcpkgSupport {
pub manifest_path: PathBuf,
pub triplet: String,
pub features: Vec<String>,
pub overlays: Vec<PathBuf>,
pub registries: Vec<X86VcpkgRegistry>,
pub vcpkg_root: PathBuf,
pub manifest: X86VcpkgManifest,
pub static_linking: bool,
}
impl X86VcpkgSupport {
pub fn new() -> Self {
Self {
manifest_path: PathBuf::from("vcpkg.json"),
triplet: "x64-linux".to_string(),
features: Vec::new(),
overlays: Vec::new(),
registries: Vec::new(),
vcpkg_root: PathBuf::from(
std::env::var("VCPKG_ROOT").unwrap_or_else(|_| "/opt/vcpkg".to_string()),
),
manifest: X86VcpkgManifest::default(),
static_linking: false,
}
}
pub fn for_i686_linux() -> Self {
let mut s = Self::new();
s.triplet = "x86-linux".to_string();
s
}
pub fn for_x64_windows() -> Self {
let mut s = Self::new();
s.triplet = "x64-windows".to_string();
s.vcpkg_root = PathBuf::from("C:/vcpkg");
s
}
pub fn for_x64_osx() -> Self {
let mut s = Self::new();
s.triplet = "x64-osx".to_string();
s
}
pub fn for_x86_windows() -> Self {
let mut s = Self::new();
s.triplet = "x86-windows".to_string();
s.vcpkg_root = PathBuf::from("C:/vcpkg");
s
}
pub fn with_static_linking(mut self) -> Self {
self.static_linking = true;
if self.triplet == "x64-linux" {
self.triplet = "x64-linux-static".to_string();
} else if self.triplet == "x86-linux" {
self.triplet = "x86-linux-static".to_string();
} else if self.triplet == "x64-windows" {
self.triplet = "x64-windows-static".to_string();
}
self
}
pub fn add_dependency(&mut self, dep: X86VcpkgDependency) -> &mut Self {
self.manifest.dependencies.push(dep);
self
}
pub fn add_simple_dep(&mut self, name: &str) -> &mut Self {
self.manifest.dependencies.push(X86VcpkgDependency {
name: name.to_string(),
version_gt: None,
features: Vec::new(),
platform: None,
default_features: None,
host: None,
});
self
}
pub fn add_overlay(&mut self, path: &Path) -> &mut Self {
self.overlays.push(path.to_path_buf());
self
}
pub fn add_registry(&mut self, registry: X86VcpkgRegistry) -> &mut Self {
self.registries.push(registry);
self
}
pub fn generate_manifest(&self) -> serde_json::Value {
let mut manifest = serde_json::Map::new();
manifest.insert(
"name".to_string(),
serde_json::Value::String(self.manifest.name.clone()),
);
manifest.insert(
"version-string".to_string(),
serde_json::Value::String(self.manifest.version_string.clone()),
);
if let Some(ref desc) = self.manifest.description {
manifest.insert(
"description".to_string(),
serde_json::Value::String(desc.clone()),
);
}
if let Some(ref homepage) = self.manifest.homepage {
manifest.insert(
"homepage".to_string(),
serde_json::Value::String(homepage.clone()),
);
}
if let Some(ref license) = self.manifest.license {
manifest.insert(
"license".to_string(),
serde_json::Value::String(license.clone()),
);
}
let mut deps = Vec::new();
for dep in &self.manifest.dependencies {
if dep.features.is_empty() && dep.version_gt.is_none() && dep.platform.is_none() {
deps.push(serde_json::Value::String(dep.name.clone()));
} else {
let mut obj = serde_json::Map::new();
obj.insert(
"name".to_string(),
serde_json::Value::String(dep.name.clone()),
);
if let Some(ref ver) = dep.version_gt {
obj.insert(
"version>=".to_string(),
serde_json::Value::String(ver.clone()),
);
}
if !dep.features.is_empty() {
obj.insert(
"features".to_string(),
serde_json::Value::Array(
dep.features
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect(),
),
);
}
if let Some(ref platform) = dep.platform {
obj.insert(
"platform".to_string(),
serde_json::Value::String(platform.clone()),
);
}
deps.push(serde_json::Value::Object(obj));
}
}
manifest.insert("dependencies".to_string(), serde_json::Value::Array(deps));
if !self.manifest.features.is_empty() {
let mut features_map = serde_json::Map::new();
for (name, feature) in &self.manifest.features {
let mut feat_obj = serde_json::Map::new();
feat_obj.insert(
"description".to_string(),
serde_json::Value::String(feature.description.clone()),
);
let feat_deps: Vec<serde_json::Value> = feature
.dependencies
.iter()
.map(|d| serde_json::Value::String(d.name.clone()))
.collect();
feat_obj.insert(
"dependencies".to_string(),
serde_json::Value::Array(feat_deps),
);
features_map.insert(name.clone(), serde_json::Value::Object(feat_obj));
}
manifest.insert(
"features".to_string(),
serde_json::Value::Object(features_map),
);
}
if !self.manifest.default_features.is_empty() {
manifest.insert(
"default-features".to_string(),
serde_json::Value::Array(
self.manifest
.default_features
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect(),
),
);
}
if !self.registries.is_empty() {
let mut config = serde_json::Map::new();
let mut regs = Vec::new();
for reg in &self.registries {
let mut reg_obj = serde_json::Map::new();
reg_obj.insert(
"kind".to_string(),
serde_json::Value::String(reg.kind.clone()),
);
reg_obj.insert(
"baseline".to_string(),
serde_json::Value::String(reg.baseline.clone()),
);
reg_obj.insert(
"repository".to_string(),
serde_json::Value::String(reg.repository.clone()),
);
if !reg.packages.is_empty() {
reg_obj.insert(
"packages".to_string(),
serde_json::Value::Array(
reg.packages
.iter()
.map(|p| serde_json::Value::String(p.clone()))
.collect(),
),
);
}
regs.push(serde_json::Value::Object(reg_obj));
}
config.insert("registries".to_string(), serde_json::Value::Array(regs));
manifest.insert(
"vcpkg-configuration".to_string(),
serde_json::Value::Object(config),
);
}
serde_json::Value::Object(manifest)
}
pub fn generate_manifest_json(&self) -> Result<String, String> {
let value = self.generate_manifest();
serde_json::to_string_pretty(&value).map_err(|e| e.to_string())
}
pub fn x86_triplets() -> Vec<&'static str> {
vec![
"x64-linux",
"x64-linux-static",
"x64-linux-dynamic",
"x86-linux",
"x86-linux-static",
"x64-windows",
"x64-windows-static",
"x64-windows-static-md",
"x86-windows",
"x86-windows-static",
"x64-osx",
"x64-osx-dynamic",
"x64-uwp",
"x86-uwp",
"x64-android",
"x86-android",
"x64-freebsd",
"x64-openbsd",
]
}
pub fn detect_triplet() -> String {
if cfg!(target_arch = "x86_64") {
if cfg!(target_os = "linux") {
"x64-linux".to_string()
} else if cfg!(target_os = "windows") {
"x64-windows".to_string()
} else if cfg!(target_os = "macos") {
"x64-osx".to_string()
} else {
"x64-linux".to_string()
}
} else if cfg!(target_arch = "x86") {
if cfg!(target_os = "linux") {
"x86-linux".to_string()
} else if cfg!(target_os = "windows") {
"x86-windows".to_string()
} else {
"x86-linux".to_string()
}
} else {
"x64-linux".to_string()
}
}
pub fn generate_triplet_file(&self) -> String {
let mut triplet = String::new();
triplet.push_str(&format!("# Custom triplet: {}\n", self.triplet));
triplet.push_str("set(VCPKG_TARGET_ARCHITECTURE ");
if self.triplet.contains("x64") {
triplet.push_str("x64");
} else {
triplet.push_str("x86");
}
triplet.push_str(")\n");
triplet.push_str("set(VCPKG_CRT_LINKAGE ");
if self.triplet.contains("static") {
triplet.push_str("static");
} else {
triplet.push_str("dynamic");
}
triplet.push_str(")\n");
triplet.push_str("set(VCPKG_LIBRARY_LINKAGE ");
if self.static_linking || self.triplet.contains("static") {
triplet.push_str("static");
} else {
triplet.push_str("dynamic");
}
triplet.push_str(")\n");
if self.triplet.contains("linux") {
triplet.push_str("set(VCPKG_CMAKE_SYSTEM_NAME Linux)\n");
} else if self.triplet.contains("windows") {
triplet.push_str("set(VCPKG_CMAKE_SYSTEM_NAME Windows)\n");
} else if self.triplet.contains("osx") {
triplet.push_str("set(VCPKG_CMAKE_SYSTEM_NAME Darwin)\n");
}
triplet
}
pub fn generate_cmake_integration(&self) -> String {
format!(
r#"# vcpkg integration for {}
if(DEFINED ENV{{VCPKG_ROOT}})
set(CMAKE_TOOLCHAIN_FILE "$ENV{{VCPKG_ROOT}}/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "vcpkg toolchain file")
elseif(EXISTS "{}")
set(CMAKE_TOOLCHAIN_FILE "{}"
CACHE STRING "vcpkg toolchain file")
endif()
message(STATUS "vcpkg triplet: {}")
"#,
self.triplet,
self.vcpkg_root
.join("scripts/buildsystems/vcpkg.cmake")
.display(),
self.vcpkg_root
.join("scripts/buildsystems/vcpkg.cmake")
.display(),
self.triplet,
)
}
pub fn install_command(&self) -> Vec<String> {
let mut cmd = vec!["vcpkg".to_string(), "install".to_string()];
for dep in &self.manifest.dependencies {
cmd.push(dep.name.clone());
}
cmd.push(format!("--triplet={}", self.triplet));
if !self.overlays.is_empty() {
for overlay in &self.overlays {
cmd.push(format!("--overlay-ports={}", overlay.display()));
}
}
if !self.features.is_empty() {
for feature in &self.features {
cmd.push(format!("--feature={}", feature));
}
}
cmd
}
pub fn known_ports() -> Vec<X86VcpkgPort> {
vec![
X86VcpkgPort {
name: "boost".into(),
description: "Boost C++ libraries".into(),
has_x86_optimizations: true,
},
X86VcpkgPort {
name: "openssl".into(),
description: "OpenSSL".into(),
has_x86_optimizations: true,
},
X86VcpkgPort {
name: "zlib".into(),
description: "Zlib compression".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "fmt".into(),
description: "fmt formatting".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "spdlog".into(),
description: "spdlog logging".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "nlohmann-json".into(),
description: "JSON library".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "gtest".into(),
description: "Google Test".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "benchmark".into(),
description: "Google Benchmark".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "protobuf".into(),
description: "Protocol Buffers".into(),
has_x86_optimizations: false,
},
X86VcpkgPort {
name: "eigen3".into(),
description: "Eigen linear algebra".into(),
has_x86_optimizations: true,
},
X86VcpkgPort {
name: "openblas".into(),
description: "BLAS library".into(),
has_x86_optimizations: true,
},
X86VcpkgPort {
name: "fftw3".into(),
description: "FFT library".into(),
has_x86_optimizations: true,
},
]
}
pub fn resolve(&self, name: &str) -> Result<X86ResolvedPackage, String> {
let ports = X86VcpkgSupport::known_ports();
let port = ports
.iter()
.find(|p| p.name == name)
.ok_or_else(|| format!("Port '{}' not found in vcpkg", name))?;
let installed = self.vcpkg_root.join("installed").join(&self.triplet);
Ok(X86ResolvedPackage {
name: port.name.clone(),
version: "latest".to_string(),
description: port.description.clone(),
homepage: Some(format!(
"https://github.com/microsoft/vcpkg/tree/master/ports/{}",
name
)),
license: None,
include_dirs: vec![installed.join("include")],
library_dirs: vec![installed.join("lib")],
libraries: vec![name.to_string()],
compile_flags: Vec::new(),
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: port.has_x86_optimizations,
microarch_requirement: None,
source: X86PackageSource::Vcpkg,
})
}
}
impl Default for X86VcpkgSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86VcpkgManifest {
pub name: String,
pub version_string: String,
pub description: Option<String>,
pub homepage: Option<String>,
pub license: Option<String>,
pub dependencies: Vec<X86VcpkgDependency>,
pub features: HashMap<String, X86VcpkgFeature>,
pub default_features: Vec<String>,
pub supports: Option<String>,
}
impl Default for X86VcpkgManifest {
fn default() -> Self {
Self {
name: "x86-project".to_string(),
version_string: "1.0.0".to_string(),
description: Some("X86-optimized C++ project".to_string()),
homepage: None,
license: Some("MIT".to_string()),
dependencies: Vec::new(),
features: HashMap::new(),
default_features: Vec::new(),
supports: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86VcpkgDependency {
pub name: String,
pub version_gt: Option<String>,
pub features: Vec<String>,
pub platform: Option<String>,
pub default_features: Option<bool>,
pub host: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct X86VcpkgFeature {
pub description: String,
pub dependencies: Vec<X86VcpkgDependency>,
}
#[derive(Debug, Clone)]
pub struct X86VcpkgRegistry {
pub kind: String,
pub baseline: String,
pub repository: String,
pub packages: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86VcpkgPort {
pub name: String,
pub description: String,
pub has_x86_optimizations: bool,
}
#[derive(Debug, Clone)]
pub struct X86SpackSupport {
pub spack_root: PathBuf,
pub opt_dir: PathBuf,
pub compilers: Vec<X86SpackCompiler>,
pub spec: String,
pub variants: HashMap<String, String>,
pub target: String,
pub opt_flags: Vec<String>,
pub ld_flags: Vec<String>,
}
impl X86SpackSupport {
pub fn new() -> Self {
Self {
spack_root: PathBuf::from("/opt/spack"),
opt_dir: PathBuf::from("/opt/spack/opt"),
compilers: vec![X86SpackCompiler {
name: "clang".to_string(),
version: "17.0.0".to_string(),
cc_path: PathBuf::from("/usr/bin/clang"),
cxx_path: PathBuf::from("/usr/bin/clang++"),
f77_path: Some(PathBuf::from("/usr/bin/flang")),
f90_path: Some(PathBuf::from("/usr/bin/flang")),
target: "x86_64".to_string(),
}],
spec: String::new(),
variants: HashMap::new(),
target: "x86_64".to_string(),
opt_flags: vec!["-O2".to_string(), "-march=x86-64".to_string()],
ld_flags: Vec::new(),
}
}
pub fn for_i686() -> Self {
let mut s = Self::new();
s.target = "i686".to_string();
s.opt_flags = vec!["-O2".to_string(), "-march=i686".to_string()];
s
}
pub fn for_x86_64_v3() -> Self {
let mut s = Self::new();
s.target = "x86_64_v3".to_string();
s.opt_flags = vec!["-O2".to_string(), "-march=x86-64-v3".to_string()];
s
}
pub fn for_x86_64_v4() -> Self {
let mut s = Self::new();
s.target = "x86_64_v4".to_string();
s.opt_flags = vec!["-O2".to_string(), "-march=x86-64-v4".to_string()];
s
}
pub fn add_compiler(&mut self, compiler: X86SpackCompiler) -> &mut Self {
self.compilers.push(compiler);
self
}
pub fn with_spec(mut self, spec: &str) -> Self {
self.spec = spec.to_string();
self
}
pub fn with_variant(mut self, key: &str, value: &str) -> Self {
self.variants.insert(key.to_string(), value.to_string());
self
}
pub fn generate_package_py(&self, pkg_name: &str, pkg_version: &str, url: &str) -> String {
let mut py = String::new();
py.push_str(&format!(
"# Spack package.py for {}@{}\n",
pkg_name, pkg_version
));
py.push_str("# Auto-generated by X86SpackSupport\n\n");
py.push_str("from spack.package import *\n\n");
py.push_str(&format!(
"class {}(CMakePackage):\n",
self._class_name(pkg_name)
));
py.push_str(&format!(" \"\"\"{}\"\"\"\n\n", pkg_name));
py.push_str(&format!(
" homepage = \"https://example.com/{}\"\n",
pkg_name
));
py.push_str(&format!(" url = \"{}\"\n\n", url));
py.push_str(&format!(
" version(\"{}\", sha256=\"<INSERT_CHECKSUM>\")\n\n",
pkg_version
));
py.push_str(
" variant(\"shared\", default=True, description=\"Build shared libraries\")\n",
);
py.push_str(
" variant(\"optimize\", default=True, description=\"Enable X86 optimizations\")\n",
);
if self.target == "x86_64_v4" || self.target == "x86_64_v3" {
py.push_str(" variant(\"avx512\", default=False, description=\"Enable AVX-512\")\n");
py.push_str(" variant(\"avx2\", default=True, description=\"Enable AVX2\")\n");
}
py.push('\n');
py.push_str(" depends_on(\"cmake@3.16:\", type=\"build\")\n");
py.push_str(" depends_on(\"ninja\", type=\"build\")\n\n");
py.push_str(" def cmake_args(self):\n");
py.push_str(" args = [\n");
py.push_str(" self.define_from_variant(\"BUILD_SHARED_LIBS\", \"shared\"),\n");
py.push_str(&format!(" \"-DCMAKE_BUILD_TYPE=Release\",\n"));
py.push_str(&format!(
" \"-DCMAKE_C_FLAGS=-march={}\",\n",
if self.target == "i686" {
"i686"
} else {
"x86-64"
}
));
py.push_str(&format!(
" \"-DCMAKE_CXX_FLAGS=-march={}\",\n",
if self.target == "i686" {
"i686"
} else {
"x86-64"
}
));
if self.target == "x86_64_v3" || self.target == "x86_64_v4" {
py.push_str(" \"-DENABLE_AVX2=ON\",\n");
}
if self.target == "x86_64_v4" {
py.push_str(" \"-DENABLE_AVX512=ON\",\n");
}
py.push_str(" ]\n");
py.push_str(" return args\n");
py
}
fn _class_name(&self, pkg_name: &str) -> String {
let mut name = String::new();
let mut capitalize = true;
for c in pkg_name.chars() {
if c == '-' || c == '_' {
capitalize = true;
continue;
}
if capitalize {
name.push(c.to_ascii_uppercase());
capitalize = false;
} else {
name.push(c);
}
}
name
}
pub fn parse_spec(spec: &str) -> X86SpackSpec {
let mut parsed = X86SpackSpec {
name: String::new(),
version: None,
compiler: None,
compiler_version: None,
variants: HashMap::new(),
target: None,
raw: spec.to_string(),
};
let parts: Vec<&str> = spec.split_whitespace().collect();
for part in parts {
if part.starts_with('@') {
parsed.version = Some(part[1..].to_string());
} else if part.starts_with('%') {
let comp = &part[1..];
if let Some(at_pos) = comp.find('@') {
parsed.compiler = Some(comp[..at_pos].to_string());
parsed.compiler_version = Some(comp[at_pos + 1..].to_string());
} else {
parsed.compiler = Some(comp.to_string());
}
} else if part.starts_with("target=") {
parsed.target = Some(part[7..].to_string());
} else if part.starts_with('+') || part.starts_with('~') {
let key = part[1..].to_string();
let value = part.starts_with('+');
parsed.variants.insert(key, value.to_string());
} else if part.contains('=') {
if let Some(eq_pos) = part.find('=') {
let key = part[..eq_pos].to_string();
let val = part[eq_pos + 1..].to_string();
if !key.starts_with("target") {
parsed.variants.insert(key, val);
}
}
} else if !part.contains('@') && !part.contains('%') {
parsed.name = part.to_string();
}
}
parsed
}
pub fn resolve_spec(&self, spec: &str) -> Result<X86SpackResolvedSpec, String> {
let parsed = X86SpackSupport::parse_spec(spec);
if parsed.name.is_empty() {
return Err("Empty package name in spec".to_string());
}
let compiler = if let Some(ref comp_name) = parsed.compiler {
self.compilers
.iter()
.find(|c| c.name == *comp_name)
.ok_or_else(|| format!("Compiler '{}' not found", comp_name))?
} else {
self.compilers.first().ok_or("No compilers registered")?
};
Ok(X86SpackResolvedSpec {
name: parsed.name.clone(),
version: parsed.version.unwrap_or_else(|| "develop".to_string()),
compiler_name: compiler.name.clone(),
compiler_version: parsed
.compiler_version
.unwrap_or_else(|| compiler.version.clone()),
target: parsed.target.unwrap_or_else(|| self.target.clone()),
variants: parsed.variants,
install_prefix: self.opt_dir.join(format!("{}-{}", parsed.name, "hash")),
})
}
pub fn generate_compilers_yaml(&self) -> String {
let mut yaml = String::new();
yaml.push_str("compilers:\n");
for compiler in &self.compilers {
yaml.push_str(&format!("- compiler:\n"));
yaml.push_str(&format!(
" spec: {}@{}\n",
compiler.name, compiler.version
));
yaml.push_str(&format!(" paths:\n"));
yaml.push_str(&format!(" cc: {}\n", compiler.cc_path.display()));
yaml.push_str(&format!(" cxx: {}\n", compiler.cxx_path.display()));
if let Some(ref f77) = compiler.f77_path {
yaml.push_str(&format!(" f77: {}\n", f77.display()));
}
if let Some(ref f90) = compiler.f90_path {
yaml.push_str(&format!(" fc: {}\n", f90.display()));
}
yaml.push_str(&format!(" flags:\n"));
yaml.push_str(&format!(" cflags: {}\n", self.opt_flags.join(" ")));
yaml.push_str(&format!(" cxxflags: {}\n", self.opt_flags.join(" ")));
yaml.push_str(&format!(
" operating_system: {}\n",
if cfg!(target_os = "linux") {
"rhel8"
} else {
"catalina"
}
));
yaml.push_str(&format!(" target: {}\n", compiler.target));
yaml.push_str(&format!(" modules: []\n"));
yaml.push_str(&format!(" environment: {{}}\n"));
yaml.push_str(&format!(" extra_rpaths: []\n\n"));
}
yaml
}
pub fn generate_modulefile_tcl(&self, pkg: &X86SpackResolvedSpec) -> String {
let mut tcl = String::new();
tcl.push_str("#%Module 1.0\n");
tcl.push_str(&format!("# Module file for {}@{}\n", pkg.name, pkg.version));
tcl.push_str(&format!(
"module-whatis \"{} - version {}\"\n\n",
pkg.name, pkg.version
));
let prefix = &pkg.install_prefix;
tcl.push_str(&format!("set root {}\n\n", prefix.display()));
tcl.push_str("prepend-path PATH $root/bin\n");
tcl.push_str("prepend-path LD_LIBRARY_PATH $root/lib\n");
tcl.push_str("prepend-path LIBRARY_PATH $root/lib\n");
tcl.push_str("prepend-path CPATH $root/include\n");
tcl.push_str("prepend-path PKG_CONFIG_PATH $root/lib/pkgconfig\n");
tcl.push_str("prepend-path CMAKE_PREFIX_PATH $root\n");
tcl.push_str("prepend-path MANPATH $root/share/man\n\n");
tcl.push_str(&format!("setenv {}_ROOT $root\n", pkg.name.to_uppercase()));
tcl.push_str(&format!("setenv {}_DIR $root\n", pkg.name.to_uppercase()));
tcl
}
pub fn generate_modulefile_lua(&self, pkg: &X86SpackResolvedSpec) -> String {
let mut lua = String::new();
lua.push_str(&format!(
"-- Module file for {}@{}\n",
pkg.name, pkg.version
));
lua.push_str(&format!(
"whatis([==[{} - version {}]==])\n\n",
pkg.name, pkg.version
));
let prefix = &pkg.install_prefix;
lua.push_str(&format!("local root = \"{}\"\n\n", prefix.display()));
lua.push_str("prepend_path(\"PATH\", pathJoin(root, \"bin\"))\n");
lua.push_str("prepend_path(\"LD_LIBRARY_PATH\", pathJoin(root, \"lib\"))\n");
lua.push_str("prepend_path(\"LIBRARY_PATH\", pathJoin(root, \"lib\"))\n");
lua.push_str("prepend_path(\"CPATH\", pathJoin(root, \"include\"))\n");
lua.push_str("prepend_path(\"PKG_CONFIG_PATH\", pathJoin(root, \"lib/pkgconfig\"))\n");
lua.push_str("prepend_path(\"CMAKE_PREFIX_PATH\", root)\n");
lua.push_str("prepend_path(\"MANPATH\", pathJoin(root, \"share/man\"))\n\n");
lua.push_str(&format!(
"setenv(\"{}_ROOT\", root)\n",
pkg.name.to_uppercase()
));
lua.push_str(&format!(
"setenv(\"{}_DIR\", root)\n",
pkg.name.to_uppercase()
));
lua
}
pub fn generate_packages_yaml(&self) -> String {
let mut yaml = String::new();
yaml.push_str("packages:\n");
yaml.push_str(" all:\n");
yaml.push_str(&format!(" target: [{}]\n", self.target));
yaml.push_str(&format!(" compiler: [clang, gcc]\n"));
yaml.push_str(" providers:\n");
yaml.push_str(" mpi: [openmpi, mpich]\n");
yaml.push_str(" blas: [openblas]\n");
yaml.push_str(" lapack: [openblas]\n");
yaml.push_str(" fftw-api: [fftw]\n");
if self.target.contains("v4") || self.target.contains("v3") {
yaml.push_str(" openblas:\n");
yaml.push_str(" variants: +avx2\n");
}
yaml
}
pub fn generate_cmake_hints(&self) -> String {
format!(
r#"# Spack integration hints
# To use Spack-installed packages, run:
# spack load <package>
# Or set CMAKE_PREFIX_PATH:
# export CMAKE_PREFIX_PATH={}/opt/spack/opt:$CMAKE_PREFIX_PATH
"#,
self.spack_root.display()
)
}
pub fn hpc_packages() -> Vec<&'static str> {
vec![
"openmpi",
"mpich",
"hdf5",
"netcdf-c",
"netcdf-cxx4",
"petsc",
"trilinos",
"hypre",
"superlu",
"scalapack",
"fftw",
"boost",
"eigen",
"gsl",
"openblas",
"blas",
"lapack",
"cgal",
"vtk",
"paraview",
"cmake",
"ninja",
"gcc",
"llvm",
"intel-mkl",
"cuda",
"kokkos",
"raja",
"amdfftw",
]
}
pub fn resolve(&self, name: &str) -> Result<X86ResolvedPackage, String> {
let hash = {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut h);
format!("{:x}", h.finish())
};
let install_prefix = self.opt_dir.join(format!("{}-{}", name, hash));
Ok(X86ResolvedPackage {
name: name.to_string(),
version: "spack".to_string(),
description: format!("Spack-installed package: {}", name),
homepage: None,
license: None,
include_dirs: vec![install_prefix.join("include")],
library_dirs: vec![install_prefix.join("lib")],
libraries: vec![name.to_string()],
compile_flags: self.opt_flags.clone(),
link_flags: vec![
format!("-L{}", install_prefix.join("lib").display()),
format!("-Wl,-rpath,{}", install_prefix.join("lib").display()),
],
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: true,
microarch_requirement: None,
source: X86PackageSource::Spack,
})
}
}
impl Default for X86SpackSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86SpackCompiler {
pub name: String,
pub version: String,
pub cc_path: PathBuf,
pub cxx_path: PathBuf,
pub f77_path: Option<PathBuf>,
pub f90_path: Option<PathBuf>,
pub target: String,
}
#[derive(Debug, Clone)]
pub struct X86SpackSpec {
pub name: String,
pub version: Option<String>,
pub compiler: Option<String>,
pub compiler_version: Option<String>,
pub variants: HashMap<String, String>,
pub target: Option<String>,
pub raw: String,
}
#[derive(Debug, Clone)]
pub struct X86SpackResolvedSpec {
pub name: String,
pub version: String,
pub compiler_name: String,
pub compiler_version: String,
pub target: String,
pub variants: HashMap<String, String>,
pub install_prefix: PathBuf,
}
#[derive(Debug, Clone)]
pub struct X86PkgConfig {
pub pc_path: Vec<PathBuf>,
pub static_linking: bool,
pub max_depth: u32,
pub pc_cache: HashMap<String, X86PcFile>,
pub cache_hits: usize,
}
impl X86PkgConfig {
pub fn new() -> Self {
let mut pc_path = vec![
PathBuf::from("/usr/lib/pkgconfig"),
PathBuf::from("/usr/share/pkgconfig"),
PathBuf::from("/usr/local/lib/pkgconfig"),
PathBuf::from("/usr/lib/x86_64-linux-gnu/pkgconfig"),
PathBuf::from("/usr/lib/i386-linux-gnu/pkgconfig"),
PathBuf::from("/usr/lib32/pkgconfig"),
];
if let Ok(env_path) = std::env::var("PKG_CONFIG_PATH") {
for p in env_path.split(':') {
pc_path.push(PathBuf::from(p));
}
}
if let Ok(env_libdir) = std::env::var("PKG_CONFIG_LIBDIR") {
for p in env_libdir.split(':') {
pc_path.push(PathBuf::from(p));
}
}
Self {
pc_path,
static_linking: false,
max_depth: 10,
pc_cache: HashMap::new(),
cache_hits: 0,
}
}
pub fn add_path(&mut self, path: &Path) -> &mut Self {
if !self.pc_path.contains(&path.to_path_buf()) {
self.pc_path.push(path.to_path_buf());
}
self
}
pub fn clear_cache(&mut self) {
self.pc_cache.clear();
self.cache_hits = 0;
}
pub fn parse_pc_file(&mut self, path: &Path) -> Result<X86PcFile, String> {
let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
let mut pc = X86PcFile {
name: String::new(),
description: String::new(),
version: String::new(),
requires: Vec::new(),
requires_private: Vec::new(),
conflicts: Vec::new(),
cflags: Vec::new(),
libs: Vec::new(),
libs_private: Vec::new(),
include_dirs: Vec::new(),
lib_dirs: Vec::new(),
url: None,
variables: HashMap::new(),
original_path: path.to_path_buf(),
};
let mut raw_fields: Vec<(String, String)> = Vec::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(eq) = line.find('=') {
let key = line[..eq].trim().to_string();
let value = line[eq + 1..].trim().to_string();
pc.variables.insert(key.clone(), value);
continue;
}
if let Some(colon) = line.find(':') {
let key = line[..colon].trim().to_string();
let value = line[colon + 1..].trim().to_string();
raw_fields.push((key, value));
}
}
for (key, value) in raw_fields {
let resolved = Self::resolve_variables(&value, &pc.variables);
match key.as_str() {
"Name" => pc.name = resolved,
"Description" => pc.description = resolved,
"Version" => pc.version = resolved,
"Requires" => pc.requires = resolved.split_whitespace().map(String::from).collect(),
"Requires.private" => {
pc.requires_private = resolved.split_whitespace().map(String::from).collect()
}
"Conflicts" => {
pc.conflicts = resolved.split_whitespace().map(String::from).collect()
}
"Cflags" => pc.cflags = Self::split_flags(&resolved),
"Libs" => pc.libs = Self::split_flags(&resolved),
"Libs.private" => pc.libs_private = Self::split_flags(&resolved),
"URL" => pc.url = Some(resolved),
_ => {}
}
}
for flag in &pc.cflags {
if flag.starts_with("-I") {
pc.include_dirs.push(PathBuf::from(&flag[2..]));
}
}
for flag in pc.libs.iter().chain(pc.libs_private.iter()) {
if flag.starts_with("-L") {
pc.lib_dirs.push(PathBuf::from(&flag[2..]));
}
}
Ok(pc)
}
pub fn resolve_variables(input: &str, vars: &HashMap<String, String>) -> String {
let mut result = input.to_string();
let mut changed = true;
let max_iterations = 10;
for _ in 0..max_iterations {
if !changed {
break;
}
changed = false;
let mut new_result = String::with_capacity(result.len());
let chars: Vec<char> = result.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '$' && i + 1 < chars.len() && chars[i + 1] == '{' {
let start = i + 2;
if let Some(end) = chars[start..].iter().position(|&c| c == '}') {
let var_name: String = chars[start..start + end].iter().collect();
if let Some(val) = vars.get(&var_name) {
new_result.push_str(val);
changed = true;
} else {
new_result.push_str(&format!("${{{}}}", var_name));
}
i = start + end + 1;
} else {
new_result.push(chars[i]);
i += 1;
}
} else {
new_result.push(chars[i]);
i += 1;
}
}
result = new_result;
}
result
}
fn split_flags(input: &str) -> Vec<String> {
let mut flags = Vec::new();
let mut current = String::new();
let mut in_quotes = false;
let mut quote_char = '"';
for c in input.chars() {
match c {
'"' | '\'' => {
if in_quotes && c == quote_char {
in_quotes = false;
} else if !in_quotes {
in_quotes = true;
quote_char = c;
}
current.push(c);
}
' ' if !in_quotes => {
if !current.is_empty() {
flags.push(current.clone());
current.clear();
}
}
_ => {
current.push(c);
}
}
}
if !current.is_empty() {
flags.push(current);
}
flags
}
pub fn find(&mut self, package_name: &str) -> Result<X86ResolvedPackage, String> {
if let Some(pc) = self.pc_cache.get(package_name) {
self.cache_hits += 1;
return Ok(self.pc_to_resolved(pc));
}
for dir in &self.pc_path.clone() {
let pc_file = dir.join(format!("{}.pc", package_name));
if pc_file.exists() {
match self.parse_pc_file(&pc_file) {
Ok(pc) => {
let result = self.pc_to_resolved(&pc);
self.pc_cache.insert(package_name.to_string(), pc);
return Ok(result);
}
Err(e) => {
eprintln!("Warning: Failed to parse {}: {}", pc_file.display(), e);
}
}
}
}
Err(format!(
"Package '{}' not found in pkg-config search path",
package_name
))
}
pub fn find_by_path(&mut self, pc_path: &Path) -> Result<X86ResolvedPackage, String> {
let pc = self.parse_pc_file(pc_path)?;
Ok(self.pc_to_resolved(&pc))
}
fn pc_to_resolved(&self, pc: &X86PcFile) -> X86ResolvedPackage {
X86ResolvedPackage {
name: pc.name.clone(),
version: pc.version.clone(),
description: pc.description.clone(),
homepage: pc.url.clone(),
license: None,
include_dirs: pc.include_dirs.clone(),
library_dirs: pc.lib_dirs.clone(),
libraries: pc
.libs
.iter()
.filter(|l| l.starts_with("-l"))
.map(|l| l[2..].to_string())
.collect(),
compile_flags: pc.cflags.clone(),
link_flags: pc.libs.clone(),
defines: HashMap::new(),
dependencies: pc.requires.clone(),
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::PkgConfig,
}
}
pub fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
let a_parts = Self::split_version(a);
let b_parts = Self::split_version(b);
let max_len = a_parts.len().max(b_parts.len());
for i in 0..max_len {
let a_num = a_parts.get(i).copied().unwrap_or(0);
let b_num = b_parts.get(i).copied().unwrap_or(0);
match a_num.cmp(&b_num) {
std::cmp::Ordering::Equal => continue,
other => return other,
}
}
std::cmp::Ordering::Equal
}
pub fn version_satisfies(version: &str, requirement: &str) -> bool {
let req = requirement.trim();
if req.starts_with(">=") {
Self::compare_versions(version, &req[2..].trim()) != std::cmp::Ordering::Less
} else if req.starts_with("<=") {
Self::compare_versions(version, &req[2..].trim()) != std::cmp::Ordering::Greater
} else if req.starts_with(">") {
Self::compare_versions(version, &req[1..].trim()) == std::cmp::Ordering::Greater
} else if req.starts_with("<") {
Self::compare_versions(version, &req[1..].trim()) == std::cmp::Ordering::Less
} else if req.starts_with('=') {
Self::compare_versions(version, &req[1..].trim()) == std::cmp::Ordering::Equal
} else {
Self::compare_versions(version, req) == std::cmp::Ordering::Equal
}
}
fn split_version(v: &str) -> Vec<u64> {
v.split(|c: char| !c.is_ascii_digit())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse::<u64>().ok())
.collect()
}
pub fn resolve_with_requires(
&mut self,
package_name: &str,
depth: u32,
) -> Result<Vec<X86ResolvedPackage>, String> {
if depth > self.max_depth {
return Err(format!("Max dependency depth {} exceeded", self.max_depth));
}
let mut resolved = Vec::new();
let mut visited = HashSet::new();
self._resolve_requires_chain(package_name, depth, &mut resolved, &mut visited)?;
Ok(resolved)
}
fn _resolve_requires_chain(
&mut self,
package_name: &str,
depth: u32,
resolved: &mut Vec<X86ResolvedPackage>,
visited: &mut HashSet<String>,
) -> Result<(), String> {
if visited.contains(package_name) {
return Ok(());
}
visited.insert(package_name.to_string());
let pkg = self.find(package_name)?;
let deps = pkg.dependencies.clone();
resolved.push(pkg);
for dep in deps {
self._resolve_requires_chain(&dep, depth + 1, resolved, visited)?;
}
Ok(())
}
pub fn extract_include_dirs(cflags: &[String]) -> Vec<PathBuf> {
cflags
.iter()
.filter(|f| f.starts_with("-I"))
.map(|f| PathBuf::from(&f[2..]))
.collect()
}
pub fn extract_defines(cflags: &[String]) -> HashMap<String, String> {
let mut defines = HashMap::new();
for flag in cflags {
if flag.starts_with("-D") {
let def = &flag[2..];
if let Some(eq) = def.find('=') {
defines.insert(def[..eq].to_string(), def[eq + 1..].to_string());
} else {
defines.insert(def.to_string(), "1".to_string());
}
}
}
defines
}
pub fn extract_libraries(libs: &[String]) -> Vec<String> {
libs.iter()
.filter(|l| l.starts_with("-l"))
.map(|l| l[2..].to_string())
.collect()
}
}
impl Default for X86PkgConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PcFile {
pub name: String,
pub description: String,
pub version: String,
pub requires: Vec<String>,
pub requires_private: Vec<String>,
pub conflicts: Vec<String>,
pub cflags: Vec<String>,
pub libs: Vec<String>,
pub libs_private: Vec<String>,
pub include_dirs: Vec<PathBuf>,
pub lib_dirs: Vec<PathBuf>,
pub url: Option<String>,
pub variables: HashMap<String, String>,
pub original_path: PathBuf,
}
#[derive(Debug, Clone)]
pub struct X86CMakeFindPackage {
pub find_modules: HashMap<String, X86FindModule>,
pub config_packages: HashMap<String, X86ConfigPackage>,
pub cache_hits: usize,
pub search_prefixes: Vec<PathBuf>,
pub prefer_config: bool,
pub cmake_minimum: String,
}
impl X86CMakeFindPackage {
pub fn new() -> Self {
let mut find_modules = HashMap::new();
let mut config_packages = HashMap::new();
for pkg in X86CMakeFindPackage::known_packages() {
match pkg.kind {
X86CMakePackageKind::FindModule => {
find_modules.insert(
pkg.name.clone(),
X86FindModule {
name: pkg.name.clone(),
description: pkg.description.clone(),
components: pkg.components,
variables: HashMap::new(),
},
);
}
X86CMakePackageKind::Config => {
config_packages.insert(
pkg.name.clone(),
X86ConfigPackage {
name: pkg.name.clone(),
version: pkg.version.clone(),
description: pkg.description.clone(),
components: pkg.components,
targets: Vec::new(),
},
);
}
}
}
let search_prefixes = vec![
PathBuf::from("/usr"),
PathBuf::from("/usr/local"),
PathBuf::from("/opt"),
];
Self {
find_modules,
config_packages,
cache_hits: 0,
search_prefixes,
prefer_config: true,
cmake_minimum: "3.16".to_string(),
}
}
pub fn clear_cache(&mut self) {
self.cache_hits = 0;
}
fn known_packages() -> Vec<X86CMakePackageTemplate> {
vec![
X86CMakePackageTemplate {
name: "Boost".into(),
description: "Boost C++ Libraries".into(),
version: "1.84.0".into(),
kind: X86CMakePackageKind::FindModule,
components: vec![
"filesystem".into(),
"system".into(),
"thread".into(),
"program_options".into(),
"regex".into(),
],
},
X86CMakePackageTemplate {
name: "OpenSSL".into(),
description: "OpenSSL cryptography".into(),
version: "3.2.0".into(),
kind: X86CMakePackageKind::FindModule,
components: vec!["Crypto".into(), "SSL".into()],
},
X86CMakePackageTemplate {
name: "ZLIB".into(),
description: "ZLib compression".into(),
version: "1.3.1".into(),
kind: X86CMakePackageKind::FindModule,
components: vec![],
},
X86CMakePackageTemplate {
name: "Threads".into(),
description: "Threading library".into(),
version: "1.0".into(),
kind: X86CMakePackageKind::FindModule,
components: vec![],
},
X86CMakePackageTemplate {
name: "OpenMP".into(),
description: "OpenMP support".into(),
version: "5.0".into(),
kind: X86CMakePackageKind::FindModule,
components: vec!["C".into(), "CXX".into()],
},
X86CMakePackageTemplate {
name: "MPI".into(),
description: "MPI library".into(),
version: "3.1".into(),
kind: X86CMakePackageKind::FindModule,
components: vec!["C".into(), "CXX".into()],
},
X86CMakePackageTemplate {
name: "Python".into(),
description: "Python development".into(),
version: "3.12".into(),
kind: X86CMakePackageKind::FindModule,
components: vec!["Interpreter".into(), "Development".into(), "NumPy".into()],
},
X86CMakePackageTemplate {
name: "BLAS".into(),
description: "Basic Linear Algebra".into(),
version: "1.0".into(),
kind: X86CMakePackageKind::FindModule,
components: vec![],
},
X86CMakePackageTemplate {
name: "LAPACK".into(),
description: "Linear Algebra Package".into(),
version: "3.12".into(),
kind: X86CMakePackageKind::FindModule,
components: vec![],
},
X86CMakePackageTemplate {
name: "fmt".into(),
description: "Formatting library".into(),
version: "10.2.1".into(),
kind: X86CMakePackageKind::Config,
components: vec![],
},
X86CMakePackageTemplate {
name: "spdlog".into(),
description: "Fast C++ logging".into(),
version: "1.13.0".into(),
kind: X86CMakePackageKind::Config,
components: vec![],
},
X86CMakePackageTemplate {
name: "nlohmann_json".into(),
description: "JSON for Modern C++".into(),
version: "3.11.3".into(),
kind: X86CMakePackageKind::Config,
components: vec![],
},
X86CMakePackageTemplate {
name: "GTest".into(),
description: "Google Test".into(),
version: "1.14.0".into(),
kind: X86CMakePackageKind::Config,
components: vec![
"gtest".into(),
"gtest_main".into(),
"gmock".into(),
"gmock_main".into(),
],
},
X86CMakePackageTemplate {
name: "benchmark".into(),
description: "Google Benchmark".into(),
version: "1.8.3".into(),
kind: X86CMakePackageKind::Config,
components: vec![],
},
X86CMakePackageTemplate {
name: "Eigen3".into(),
description: "Eigen linear algebra".into(),
version: "3.4.0".into(),
kind: X86CMakePackageKind::Config,
components: vec![],
},
X86CMakePackageTemplate {
name: "Protobuf".into(),
description: "Protocol Buffers".into(),
version: "25.2".into(),
kind: X86CMakePackageKind::Config,
components: vec!["libprotobuf".into(), "libprotoc".into()],
},
X86CMakePackageTemplate {
name: "OpenCV".into(),
description: "OpenCV computer vision".into(),
version: "4.9.0".into(),
kind: X86CMakePackageKind::Config,
components: vec![
"core".into(),
"imgproc".into(),
"highgui".into(),
"imgcodecs".into(),
],
},
]
}
pub fn generate_find_module(&self, package_name: &str) -> Result<String, String> {
let module = self
.find_modules
.get(package_name)
.ok_or_else(|| format!("No Find module template for '{}'", package_name))?;
let upper = package_name.to_uppercase();
let mut cmake = String::new();
cmake.push_str(&format!("# Find{}.cmake\n", package_name));
cmake.push_str(&format!("# Auto-generated by X86CMakeFindPackage\n"));
cmake.push_str(&format!("# {}\n\n", module.description));
cmake.push_str(&format!(
"cmake_minimum_required(VERSION {})\n\n",
self.cmake_minimum
));
cmake.push_str(&format!("# X86-specific search hints\n"));
cmake.push_str(&format!("set({}_HINTS\n", upper));
for prefix in &self.search_prefixes {
cmake.push_str(&format!(" {}\n", prefix.display()));
}
cmake.push_str(&format!(")\n\n"));
cmake.push_str(&format!("find_path({}_INCLUDE_DIR\n", upper));
cmake.push_str(&format!(
" NAMES {}.h {}/{}.h\n",
package_name.to_lowercase(),
package_name.to_lowercase(),
package_name.to_lowercase()
));
cmake.push_str(&format!(" HINTS ${{{}_HINTS}}\n", upper));
cmake.push_str(&format!(" PATH_SUFFIXES include\n"));
cmake.push_str(&format!(")\n\n"));
cmake.push_str(&format!("find_library({}_LIBRARY\n", upper));
cmake.push_str(&format!(
" NAMES {} {}\n",
package_name.to_lowercase(),
package_name
));
cmake.push_str(&format!(" HINTS ${{{}_HINTS}}\n", upper));
cmake.push_str(&format!(
" PATH_SUFFIXES lib lib64 lib/x86_64-linux-gnu\n"
));
cmake.push_str(&format!(")\n\n"));
cmake.push_str(&format!(
"if({}_INCLUDE_DIR AND EXISTS \"${{{}}}_INCLUDE_DIR}}/{}-version.h\")\n",
upper,
upper,
package_name.to_lowercase()
));
cmake.push_str(&format!(
" file(STRINGS \"${{{}}}_INCLUDE_DIR}}/{}-version.h\" {}_VERSION_STR\n",
upper,
package_name.to_lowercase(),
upper
));
cmake.push_str(&format!(
" REGEX \"^#define[ \\t]+{}_VERSION[ \\t]+\\\"([0-9.]+)\\\"\"\n",
upper
));
cmake.push_str(&format!(
" string(REGEX REPLACE \".*\\\"([0-9.]+)\\\".*\" \"\\\\1\" {}_VERSION\n",
upper
));
cmake.push_str(&format!(" ${{{}_VERSION_STR}})\n", upper));
cmake.push_str(&format!("endif()\n\n"));
if !module.components.is_empty() {
cmake.push_str(&format!("# Component handling\n"));
cmake.push_str(&format!("set({}_COMPONENTS\n", upper));
for comp in &module.components {
cmake.push_str(&format!(" {}\n", comp));
}
cmake.push_str(&format!(")\n\n"));
cmake.push_str(&format!(
"foreach(_comp IN LISTS {}_FIND_COMPONENTS)\n",
upper
));
cmake.push_str(&format!(" find_library({}_${{_comp}}_LIBRARY\n", upper));
cmake.push_str(&format!(
" NAMES {}${{_comp}} ${{_comp}}\n",
package_name.to_lowercase()
));
cmake.push_str(&format!(" HINTS ${{{}_HINTS}}\n", upper));
cmake.push_str(&format!(" PATH_SUFFIXES lib\n"));
cmake.push_str(&format!(" )\n"));
cmake.push_str(&format!(" if(NOT {}_${{_comp}}_LIBRARY)\n", upper));
cmake.push_str(&format!(" if({}_FIND_REQUIRED_${{_comp}})\n", upper));
cmake.push_str(&format!(
" message(FATAL_ERROR \"Component ${{_comp}} not found\")\n"
));
cmake.push_str(&format!(" endif()\n"));
cmake.push_str(&format!(" endif()\n"));
cmake.push_str(&format!("endforeach()\n\n"));
}
cmake.push_str(&format!("include(FindPackageHandleStandardArgs)\n"));
cmake.push_str(&format!(
"find_package_handle_standard_args({}\n",
package_name
));
cmake.push_str(&format!(
" REQUIRED_VARS {}_LIBRARY {}_INCLUDE_DIR\n",
upper, upper
));
cmake.push_str(&format!(" VERSION_VAR {}_VERSION\n", upper));
cmake.push_str(&format!(")\n\n"));
cmake.push_str(&format!(
"if({}_FOUND AND NOT TARGET {}::{})\n",
upper, package_name, package_name
));
cmake.push_str(&format!(
" add_library({}::{} UNKNOWN IMPORTED)\n",
package_name, package_name
));
cmake.push_str(&format!(
" set_target_properties({}::{} PROPERTIES\n",
package_name, package_name
));
cmake.push_str(&format!(
" IMPORTED_LOCATION \"${{{}_LIBRARY}}\"\n",
upper
));
cmake.push_str(&format!(
" INTERFACE_INCLUDE_DIRECTORIES \"${{{}_INCLUDE_DIR}}\"\n",
upper
));
cmake.push_str(&format!(" )\n"));
cmake.push_str(&format!("endif()\n"));
Ok(cmake)
}
pub fn generate_config_file(&self, package_name: &str) -> Result<String, String> {
let config = self
.config_packages
.get(package_name)
.ok_or_else(|| format!("No config template for '{}'", package_name))?;
let upper = package_name.to_uppercase();
let mut cmake = String::new();
cmake.push_str(&format!("# {}Config.cmake\n", package_name));
cmake.push_str(&format!("# Auto-generated by X86CMakeFindPackage\n\n"));
cmake.push_str(&format!("set({}_VERSION {})\n", upper, config.version));
cmake.push_str(&format!(
"set({}_VERSION_MAJOR {})\n",
upper,
config.version.split('.').next().unwrap_or("1")
));
cmake.push_str(&format!("\n# Compute install prefix\n"));
cmake.push_str(&format!(
"get_filename_component({}_CMAKE_DIR \"${{CMAKE_CURRENT_LIST_FILE}}\" PATH)\n",
upper
));
cmake.push_str(&format!(
"get_filename_component({}_PREFIX \"${{{}_CMAKE_DIR}}\" PATH)\n\n",
upper, upper
));
cmake.push_str(&format!(
"set({}_INCLUDE_DIR \"${{{}_PREFIX}}/include\")\n",
upper, upper
));
cmake.push_str(&format!(
"set({}_LIBRARY_DIR \"${{{}_PREFIX}}/lib\")\n\n",
upper, upper
));
cmake.push_str(&format!(
"if(NOT TARGET {}::{})\n",
package_name, package_name
));
cmake.push_str(&format!(
" add_library({}::{} UNKNOWN IMPORTED)\n",
package_name, package_name
));
cmake.push_str(&format!(
" set_target_properties({}::{} PROPERTIES\n",
package_name, package_name
));
cmake.push_str(&format!(
" IMPORTED_LOCATION \"${{{}_LIBRARY_DIR}}/lib{}.so\"\n",
upper,
package_name.to_lowercase()
));
cmake.push_str(&format!(
" INTERFACE_INCLUDE_DIRECTORIES \"${{{}_INCLUDE_DIR}}\"\n",
upper
));
cmake.push_str(&format!(
" INTERFACE_COMPILE_OPTIONS \"-march=x86-64\"\n"
));
cmake.push_str(&format!(" )\n"));
cmake.push_str(&format!("endif()\n\n"));
for comp in &config.components {
cmake.push_str(&format!("if(NOT TARGET {}::{})\n", package_name, comp));
cmake.push_str(&format!(
" add_library({}::{} UNKNOWN IMPORTED)\n",
package_name, comp
));
cmake.push_str(&format!(
" set_target_properties({}::{} PROPERTIES\n",
package_name, comp
));
cmake.push_str(&format!(
" IMPORTED_LOCATION \"${{{}_LIBRARY_DIR}}/lib{}_{}.so\"\n",
upper,
package_name.to_lowercase(),
comp.to_lowercase()
));
cmake.push_str(&format!(
" INTERFACE_INCLUDE_DIRECTORIES \"${{{}_INCLUDE_DIR}}\"\n",
upper
));
cmake.push_str(&format!(" )\n"));
cmake.push_str(&format!("endif()\n\n"));
}
cmake.push_str(&format!("check_required_components({})\n", package_name));
Ok(cmake)
}
pub fn check_version(package_name: &str, required: &str) -> bool {
let version = X86CMakeFindPackage::known_packages()
.iter()
.find(|p| p.name == package_name)
.map(|p| p.version.clone())
.unwrap_or_else(|| "1.0.0".to_string());
X86PkgConfig::version_satisfies(&version, required)
}
pub fn has_component(package_name: &str, component: &str) -> bool {
X86CMakeFindPackage::known_packages()
.iter()
.find(|p| p.name == package_name)
.map(|p| p.components.iter().any(|c| c == component))
.unwrap_or(false)
}
pub fn list_components(&self, package_name: &str) -> Vec<String> {
if let Some(module) = self.find_modules.get(package_name) {
module.components.clone()
} else if let Some(config) = self.config_packages.get(package_name) {
config.components.clone()
} else {
Vec::new()
}
}
pub fn find_package(&self, package_name: &str) -> Result<X86ResolvedPackage, String> {
if let Some(module) = self.find_modules.get(package_name) {
return Ok(self.module_to_resolved(module));
}
if let Some(config) = self.config_packages.get(package_name) {
return Ok(self.config_to_resolved(config));
}
if let Some(template) = X86CMakeFindPackage::known_packages()
.iter()
.find(|p| p.name == package_name)
{
return Ok(self.template_to_resolved(template));
}
Err(format!(
"Package '{}' not found via CMake find_package",
package_name
))
}
fn module_to_resolved(&self, module: &X86FindModule) -> X86ResolvedPackage {
X86ResolvedPackage {
name: module.name.clone(),
version: "found".to_string(),
description: module.description.clone(),
homepage: None,
license: None,
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![PathBuf::from("/usr/lib/x86_64-linux-gnu")],
libraries: vec![module.name.to_lowercase()],
compile_flags: vec!["-march=x86-64".to_string()],
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: true,
microarch_requirement: None,
source: X86PackageSource::CMakeFindPackage,
}
}
fn config_to_resolved(&self, config: &X86ConfigPackage) -> X86ResolvedPackage {
X86ResolvedPackage {
name: config.name.clone(),
version: config.version.clone(),
description: config.description.clone(),
homepage: None,
license: None,
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![PathBuf::from("/usr/lib/x86_64-linux-gnu")],
libraries: vec![config.name.to_lowercase()],
compile_flags: vec!["-march=x86-64".to_string()],
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: true,
microarch_requirement: None,
source: X86PackageSource::CMakeFindPackage,
}
}
fn template_to_resolved(&self, template: &X86CMakePackageTemplate) -> X86ResolvedPackage {
X86ResolvedPackage {
name: template.name.clone(),
version: template.version.clone(),
description: template.description.clone(),
homepage: None,
license: None,
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![PathBuf::from("/usr/lib/x86_64-linux-gnu")],
libraries: vec![template.name.to_lowercase()],
compile_flags: vec!["-march=x86-64".to_string()],
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: true,
microarch_requirement: None,
source: X86CMakeFindPackage::module_or_config(&template.kind),
}
}
fn module_or_config(kind: &X86CMakePackageKind) -> X86PackageSource {
match kind {
X86CMakePackageKind::FindModule => X86PackageSource::CMakeFindPackage,
X86CMakePackageKind::Config => X86PackageSource::CMakeFindPackage,
}
}
pub fn generate_snippet(&self, packages: &[&str]) -> String {
let mut cmake = String::new();
cmake.push_str("# Auto-generated find_package calls\n");
for pkg in packages {
cmake.push_str(&format!("find_package({} REQUIRED)\n", pkg));
}
cmake
}
}
impl Default for X86CMakeFindPackage {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FindModule {
pub name: String,
pub description: String,
pub components: Vec<String>,
pub variables: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct X86ConfigPackage {
pub name: String,
pub version: String,
pub description: String,
pub components: Vec<String>,
pub targets: Vec<String>,
}
#[derive(Debug, Clone)]
struct X86CMakePackageTemplate {
name: String,
description: String,
version: String,
kind: X86CMakePackageKind,
components: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum X86CMakePackageKind {
FindModule,
Config,
}
#[derive(Debug, Clone)]
pub struct X86SystemPackageManager {
pub detected: Option<X86SystemPM>,
pub install_cmd: Option<String>,
pub search_cmd: Option<String>,
pub remove_cmd: Option<String>,
pub list_cmd: Option<String>,
pub update_cmd: Option<String>,
pub package_cache: HashMap<String, X86SystemPackage>,
pub cache_hits: usize,
}
impl X86SystemPackageManager {
pub fn new() -> Self {
let detected = X86SystemPM::detect();
let mut mgr = Self {
detected: Some(detected),
install_cmd: None,
search_cmd: None,
remove_cmd: None,
list_cmd: None,
update_cmd: None,
package_cache: HashMap::new(),
cache_hits: 0,
};
match detected {
X86SystemPM::Apt => {
mgr.install_cmd = Some("apt-get install -y".to_string());
mgr.search_cmd = Some("apt-cache search".to_string());
mgr.remove_cmd = Some("apt-get remove -y".to_string());
mgr.list_cmd = Some("dpkg -l".to_string());
mgr.update_cmd = Some("apt-get update".to_string());
}
X86SystemPM::Yum => {
mgr.install_cmd = Some("yum install -y".to_string());
mgr.search_cmd = Some("yum search".to_string());
mgr.remove_cmd = Some("yum remove -y".to_string());
mgr.list_cmd = Some("rpm -qa".to_string());
mgr.update_cmd = Some("yum update".to_string());
}
X86SystemPM::Dnf => {
mgr.install_cmd = Some("dnf install -y".to_string());
mgr.search_cmd = Some("dnf search".to_string());
mgr.remove_cmd = Some("dnf remove -y".to_string());
mgr.list_cmd = Some("rpm -qa".to_string());
mgr.update_cmd = Some("dnf update".to_string());
}
X86SystemPM::Pacman => {
mgr.install_cmd = Some("pacman -S --noconfirm".to_string());
mgr.search_cmd = Some("pacman -Ss".to_string());
mgr.remove_cmd = Some("pacman -R --noconfirm".to_string());
mgr.list_cmd = Some("pacman -Q".to_string());
mgr.update_cmd = Some("pacman -Syu".to_string());
}
X86SystemPM::Brew => {
mgr.install_cmd = Some("brew install".to_string());
mgr.search_cmd = Some("brew search".to_string());
mgr.remove_cmd = Some("brew uninstall".to_string());
mgr.list_cmd = Some("brew list".to_string());
mgr.update_cmd = Some("brew update".to_string());
}
X86SystemPM::Zypper => {
mgr.install_cmd = Some("zypper install -y".to_string());
mgr.search_cmd = Some("zypper search".to_string());
mgr.remove_cmd = Some("zypper remove -y".to_string());
mgr.list_cmd = Some("rpm -qa".to_string());
mgr.update_cmd = Some("zypper update".to_string());
}
X86SystemPM::Apk => {
mgr.install_cmd = Some("apk add".to_string());
mgr.search_cmd = Some("apk search".to_string());
mgr.remove_cmd = Some("apk del".to_string());
mgr.list_cmd = Some("apk info".to_string());
mgr.update_cmd = Some("apk update".to_string());
}
X86SystemPM::Emerge => {
mgr.install_cmd = Some("emerge --ask=n".to_string());
mgr.search_cmd = Some("emerge --search".to_string());
mgr.remove_cmd = Some("emerge --unmerge".to_string());
mgr.list_cmd = Some("equery list".to_string());
mgr.update_cmd = Some("emerge --sync".to_string());
}
X86SystemPM::Xbps => {
mgr.install_cmd = Some("xbps-install -y".to_string());
mgr.search_cmd = Some("xbps-query -Rs".to_string());
mgr.remove_cmd = Some("xbps-remove -y".to_string());
mgr.list_cmd = Some("xbps-query -l".to_string());
mgr.update_cmd = Some("xbps-install -Su".to_string());
}
}
mgr
}
pub fn for_i686_linux() -> Self {
let mut mgr = Self::new();
mgr.package_cache.insert(
"gcc-multilib".into(),
X86SystemPackage {
name: "gcc-multilib".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/i386-linux-gnu"),
arch: "i386".into(),
},
);
mgr
}
pub fn for_windows() -> Self {
let mut mgr = Self::new();
mgr.detected = Some(X86SystemPM::Pacman); mgr.install_cmd = Some("pacman -S --noconfirm".to_string());
mgr.search_cmd = Some("pacman -Ss".to_string());
mgr
}
pub fn for_macos() -> Self {
let mut mgr = Self::new();
mgr.detected = Some(X86SystemPM::Brew);
mgr.install_cmd = Some("brew install".to_string());
mgr.search_cmd = Some("brew search".to_string());
mgr
}
pub fn detect_pm() -> Option<X86SystemPM> {
Some(X86SystemPM::detect())
}
pub fn generate_install_command(&self, packages: &[&str]) -> String {
let install = self.install_cmd.as_deref().unwrap_or("apt-get install -y");
format!("sudo {} {}", install, packages.join(" "))
}
pub fn generate_search_command(&self, query: &str) -> String {
let search = self.search_cmd.as_deref().unwrap_or("apt-cache search");
format!("{} {}", search, query)
}
pub fn resolve(&self, name: &str) -> Result<X86ResolvedPackage, String> {
if let Some(pkg) = self.package_cache.get(name) {
return Ok(self.system_pkg_to_resolved(pkg));
}
let candidates = X86SystemPackageManager::common_dev_packages();
if let Some(pkg) = candidates.iter().find(|p| p.name == name) {
return Ok(self.system_pkg_to_resolved(pkg));
}
let pm = self.detected.as_ref().unwrap_or(&X86SystemPM::Apt);
let source = match pm {
X86SystemPM::Apt => X86PackageSource::SystemApt,
X86SystemPM::Yum => X86PackageSource::SystemYum,
X86SystemPM::Dnf => X86PackageSource::SystemDnf,
X86SystemPM::Pacman => X86PackageSource::SystemPacman,
X86SystemPM::Brew => X86PackageSource::SystemBrew,
X86SystemPM::Zypper => X86PackageSource::SystemZypper,
X86SystemPM::Apk => X86PackageSource::SystemApk,
X86SystemPM::Emerge => X86PackageSource::SystemEmerge,
X86SystemPM::Xbps => X86PackageSource::SystemXbps,
};
let lib_dir = if cfg!(target_arch = "x86_64") {
PathBuf::from("/usr/lib/x86_64-linux-gnu")
} else {
PathBuf::from("/usr/lib/i386-linux-gnu")
};
Ok(X86ResolvedPackage {
name: name.to_string(),
version: "system".to_string(),
description: format!("System package: {}", name),
homepage: None,
license: None,
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![lib_dir],
libraries: vec![name.to_string()],
compile_flags: Vec::new(),
link_flags: vec![format!("-l{}", name)],
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: false,
microarch_requirement: None,
source,
})
}
fn system_pkg_to_resolved(&self, pkg: &X86SystemPackage) -> X86ResolvedPackage {
let source = match self.detected.as_ref().unwrap_or(&X86SystemPM::Apt) {
X86SystemPM::Apt => X86PackageSource::SystemApt,
X86SystemPM::Yum => X86PackageSource::SystemYum,
X86SystemPM::Dnf => X86PackageSource::SystemDnf,
X86SystemPM::Pacman => X86PackageSource::SystemPacman,
X86SystemPM::Brew => X86PackageSource::SystemBrew,
X86SystemPM::Zypper => X86PackageSource::SystemZypper,
X86SystemPM::Apk => X86PackageSource::SystemApk,
X86SystemPM::Emerge => X86PackageSource::SystemEmerge,
X86SystemPM::Xbps => X86PackageSource::SystemXbps,
};
X86ResolvedPackage {
name: pkg.name.clone(),
version: pkg.version.clone(),
description: format!("System package: {}", pkg.name),
homepage: None,
license: None,
include_dirs: vec![pkg.include_dir.clone()],
library_dirs: vec![pkg.lib_dir.clone()],
libraries: vec![pkg.name.clone()],
compile_flags: Vec::new(),
link_flags: vec![format!("-l{}", pkg.name)],
defines: HashMap::new(),
dependencies: Vec::new(),
is_x86_optimized: false,
microarch_requirement: None,
source,
}
}
pub fn common_dev_packages() -> Vec<X86SystemPackage> {
vec![
X86SystemPackage {
name: "build-essential".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "gcc".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "g++".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/c++"),
lib_dir: PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "clang".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/llvm"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "llvm-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/llvm"),
lib_dir: PathBuf::from("/usr/lib/llvm"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libc6-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libstdc++-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/c++"),
lib_dir: PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libssl-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/openssl"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "zlib1g-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libboost-all-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/boost"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libeigen3-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/eigen3"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libopencv-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/opencv4"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libgtest-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/gtest"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libspdlog-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/spdlog"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
X86SystemPackage {
name: "libfmt-dev".into(),
version: "latest".into(),
provides_dev: true,
include_dir: PathBuf::from("/usr/include/fmt"),
lib_dir: PathBuf::from("/usr/lib/x86_64-linux-gnu"),
arch: "amd64".into(),
},
]
}
pub fn debian_packages() -> Vec<&'static str> {
vec![
"build-essential",
"g++",
"gcc",
"clang",
"llvm-dev",
"libc6-dev",
"libstdc++-dev",
"libssl-dev",
"zlib1g-dev",
"libpng-dev",
"libjpeg-dev",
"libfreetype6-dev",
"libx11-dev",
"libxcb1-dev",
"libvulkan-dev",
"libsdl2-dev",
"libboost-all-dev",
"libeigen3-dev",
"libopencv-dev",
"libcurl4-openssl-dev",
"libxml2-dev",
"libprotobuf-dev",
"libgtest-dev",
"libgmock-dev",
"libbenchmark-dev",
"nlohmann-json3-dev",
"libspdlog-dev",
"libfmt-dev",
"libabsl-dev",
"libgflags-dev",
"libglog-dev",
]
}
pub fn rhel_packages() -> Vec<&'static str> {
vec![
"gcc",
"gcc-c++",
"clang",
"llvm-devel",
"glibc-devel",
"libstdc++-devel",
"openssl-devel",
"zlib-devel",
"libpng-devel",
"libjpeg-turbo-devel",
"freetype-devel",
"libX11-devel",
"libxcb-devel",
"vulkan-devel",
"SDL2-devel",
"boost-devel",
"eigen3-devel",
"opencv-devel",
"libcurl-devel",
"libxml2-devel",
"protobuf-devel",
"gtest-devel",
"gmock-devel",
"benchmark-devel",
"json-devel",
"spdlog-devel",
"fmt-devel",
"abseil-cpp-devel",
"gflags-devel",
"glog-devel",
]
}
pub fn arch_packages() -> Vec<&'static str> {
vec![
"base-devel",
"gcc",
"clang",
"llvm",
"cmake",
"ninja",
"boost",
"eigen",
"opencv",
"openssl",
"zlib",
"libpng",
"libjpeg-turbo",
"freetype2",
"libx11",
"libxcb",
"vulkan-devel",
"sdl2",
"curl",
"libxml2",
"protobuf",
"gtest",
"benchmark",
"nlohmann-json",
"spdlog",
"fmt",
"abseil-cpp",
"gflags",
"glog",
]
}
pub fn brew_packages() -> Vec<&'static str> {
vec![
"llvm",
"cmake",
"ninja",
"boost",
"eigen",
"opencv",
"openssl",
"zlib",
"libpng",
"libjpeg",
"freetype",
"sdl2",
"glfw",
"glew",
"curl",
"libxml2",
"protobuf",
"grpc",
"gflags",
"glog",
"abseil",
"nlohmann-json",
"spdlog",
"fmt",
]
}
}
impl Default for X86SystemPackageManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SystemPM {
Apt,
Yum,
Dnf,
Pacman,
Brew,
Zypper,
Apk,
Emerge,
Xbps,
}
impl X86SystemPM {
pub fn detect() -> Self {
if cfg!(target_os = "macos") {
return Self::Brew;
}
let checks = vec![
("apt-get", Self::Apt),
("dnf", Self::Dnf),
("yum", Self::Yum),
("pacman", Self::Pacman),
("zypper", Self::Zypper),
("apk", Self::Apk),
("emerge", Self::Emerge),
("xbps-install", Self::Xbps),
];
for (bin, pm) in &checks {
if Self::find_executable(bin) {
return *pm;
}
}
Self::Apt
}
fn find_executable(name: &str) -> bool {
if let Ok(paths) = std::env::var("PATH") {
for dir in paths.split(':') {
let candidate = PathBuf::from(dir).join(name);
if candidate.exists() {
return true;
}
}
}
let common_paths = [
"/usr/bin",
"/usr/local/bin",
"/bin",
"/sbin",
"/usr/sbin",
"/opt/bin",
"/opt/homebrew/bin",
];
for dir in &common_paths {
let candidate = PathBuf::from(dir).join(name);
if candidate.exists() {
return true;
}
}
false
}
pub fn name(&self) -> &'static str {
match self {
Self::Apt => "apt",
Self::Yum => "yum",
Self::Dnf => "dnf",
Self::Pacman => "pacman",
Self::Brew => "brew",
Self::Zypper => "zypper",
Self::Apk => "apk",
Self::Emerge => "emerge",
Self::Xbps => "xbps",
}
}
}
impl fmt::Display for X86SystemPM {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct X86SystemPackage {
pub name: String,
pub version: String,
pub provides_dev: bool,
pub include_dir: PathBuf,
pub lib_dir: PathBuf,
pub arch: String,
}
#[derive(Debug, Clone)]
pub struct X86DependencyLocking {
pub lock_version: u32,
pub locked: HashMap<String, X86LockedDependency>,
pub lock_file_path: PathBuf,
pub verify_hashes: bool,
pub hash_algorithm: String,
pub exact_pinning: bool,
pub reproducible_flags: Vec<String>,
}
impl X86DependencyLocking {
pub fn new() -> Self {
Self {
lock_version: 1,
locked: HashMap::new(),
lock_file_path: PathBuf::from("x86-deps.lock"),
verify_hashes: true,
hash_algorithm: "sha256".to_string(),
exact_pinning: true,
reproducible_flags: vec![
"-ffile-prefix-map=/build/.".to_string(),
"-fdebug-prefix-map=/build/.".to_string(),
"-Wdate-time".to_string(),
"-fno-ident".to_string(),
"-frandom-seed=0".to_string(),
],
}
}
pub fn with_lock_file(mut self, path: &Path) -> Self {
self.lock_file_path = path.to_path_buf();
self
}
pub fn with_hash_verification(mut self, enabled: bool) -> Self {
self.verify_hashes = enabled;
self
}
pub fn write_lock_file(
&self,
packages: &HashMap<String, X86ResolvedPackage>,
output_path: &Path,
) -> Result<(), io::Error> {
let lock_data = self.generate_lock_data(packages);
let json = serde_json::to_string_pretty(&lock_data)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
std::fs::write(output_path, json)
}
fn generate_lock_data(
&self,
packages: &HashMap<String, X86ResolvedPackage>,
) -> serde_json::Value {
let mut root = serde_json::Map::new();
root.insert(
"lock_version".to_string(),
serde_json::Value::Number(self.lock_version.into()),
);
root.insert(
"generated_at".to_string(),
serde_json::Value::String(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".to_string()),
),
);
let mut target = serde_json::Map::new();
target.insert(
"architecture".to_string(),
serde_json::Value::String(if cfg!(target_arch = "x86_64") {
"x86_64".into()
} else {
"i686".into()
}),
);
target.insert(
"os".to_string(),
serde_json::Value::String(std::env::consts::OS.to_string()),
);
root.insert("target".to_string(), serde_json::Value::Object(target));
root.insert(
"reproducible_flags".to_string(),
serde_json::Value::Array(
self.reproducible_flags
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect(),
),
);
let mut deps = serde_json::Map::new();
for (name, pkg) in packages {
let mut dep_obj = serde_json::Map::new();
dep_obj.insert(
"name".to_string(),
serde_json::Value::String(pkg.name.clone()),
);
dep_obj.insert(
"version".to_string(),
serde_json::Value::String(pkg.version.clone()),
);
dep_obj.insert(
"source".to_string(),
serde_json::Value::String(pkg.source.to_string()),
);
if let Some(ref homepage) = pkg.homepage {
dep_obj.insert(
"homepage".to_string(),
serde_json::Value::String(homepage.clone()),
);
}
if let Some(ref license) = pkg.license {
dep_obj.insert(
"license".to_string(),
serde_json::Value::String(license.clone()),
);
}
dep_obj.insert(
"include_dirs".to_string(),
serde_json::Value::Array(
pkg.include_dirs
.iter()
.map(|d| serde_json::Value::String(d.display().to_string()))
.collect(),
),
);
dep_obj.insert(
"library_dirs".to_string(),
serde_json::Value::Array(
pkg.library_dirs
.iter()
.map(|d| serde_json::Value::String(d.display().to_string()))
.collect(),
),
);
dep_obj.insert(
"libraries".to_string(),
serde_json::Value::Array(
pkg.libraries
.iter()
.map(|l| serde_json::Value::String(l.clone()))
.collect(),
),
);
dep_obj.insert(
"compile_flags".to_string(),
serde_json::Value::Array(
pkg.compile_flags
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect(),
),
);
dep_obj.insert(
"link_flags".to_string(),
serde_json::Value::Array(
pkg.link_flags
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect(),
),
);
let mut define_map = serde_json::Map::new();
for (key, val) in &pkg.defines {
define_map.insert(key.clone(), serde_json::Value::String(val.clone()));
}
dep_obj.insert("defines".to_string(), serde_json::Value::Object(define_map));
dep_obj.insert(
"dependencies".to_string(),
serde_json::Value::Array(
pkg.dependencies
.iter()
.map(|d| serde_json::Value::String(d.clone()))
.collect(),
),
);
dep_obj.insert(
"is_x86_optimized".to_string(),
serde_json::Value::Bool(pkg.is_x86_optimized),
);
if let Some(ref microarch) = pkg.microarch_requirement {
dep_obj.insert(
"microarch_requirement".to_string(),
serde_json::Value::String(microarch.march_flag().to_string()),
);
}
dep_obj.insert(
"integrity".to_string(),
serde_json::Value::String(format!(
"{}:{}",
self.hash_algorithm,
self.compute_placeholder_hash(&pkg.name, &pkg.version)
)),
);
deps.insert(name.clone(), serde_json::Value::Object(dep_obj));
}
root.insert("dependencies".to_string(), serde_json::Value::Object(deps));
serde_json::Value::Object(root)
}
fn compute_placeholder_hash(&self, name: &str, version: &str) -> String {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut h);
version.hash(&mut h);
format!("{:016x}", h.finish())
}
pub fn parse_lock_file(&mut self, path: &Path) -> Result<(), String> {
let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
let json: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?;
self.lock_version = json["lock_version"].as_u64().unwrap_or(1) as u32;
if let Some(deps) = json["dependencies"].as_object() {
for (name, dep_json) in deps {
let dep = X86LockedDependency {
name: dep_json["name"].as_str().unwrap_or(name).to_string(),
version: dep_json["version"]
.as_str()
.unwrap_or("unknown")
.to_string(),
source: dep_json["source"].as_str().unwrap_or("unknown").to_string(),
include_dirs: dep_json["include_dirs"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(PathBuf::from))
.collect()
})
.unwrap_or_default(),
library_dirs: dep_json["library_dirs"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(PathBuf::from))
.collect()
})
.unwrap_or_default(),
libraries: dep_json["libraries"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default(),
compile_flags: dep_json["compile_flags"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default(),
link_flags: dep_json["link_flags"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default(),
defines: dep_json["defines"]
.as_object()
.map(|o| {
o.iter()
.map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
.collect()
})
.unwrap_or_default(),
dependencies: dep_json["dependencies"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default(),
integrity: dep_json["integrity"].as_str().unwrap_or("").to_string(),
is_x86_optimized: dep_json["is_x86_optimized"].as_bool().unwrap_or(false),
microarch_requirement: dep_json["microarch_requirement"]
.as_str()
.map(String::from),
};
self.locked.insert(name.clone(), dep);
}
}
Ok(())
}
pub fn pin_dependency(&mut self, name: &str, version: &str) -> &mut Self {
self.locked.insert(
name.to_string(),
X86LockedDependency {
name: name.to_string(),
version: version.to_string(),
source: "pinned".to_string(),
include_dirs: Vec::new(),
library_dirs: Vec::new(),
libraries: Vec::new(),
compile_flags: Vec::new(),
link_flags: Vec::new(),
defines: HashMap::new(),
dependencies: Vec::new(),
integrity: String::new(),
is_x86_optimized: false,
microarch_requirement: None,
},
);
self
}
pub fn check_pinned(&self, name: &str, version: &str) -> bool {
if let Some(locked) = self.locked.get(name) {
if self.exact_pinning {
locked.version == version
} else {
X86PkgConfig::version_satisfies(version, &locked.version)
}
} else {
false
}
}
pub fn verify_hash(&self, name: &str, computed_hash: &str) -> Result<(), String> {
if !self.verify_hashes {
return Ok(());
}
if let Some(locked) = self.locked.get(name) {
if locked.integrity.is_empty() {
return Ok(()); }
if locked.integrity != computed_hash {
return Err(format!(
"Integrity check failed for '{}': expected {}, got {}",
name, locked.integrity, computed_hash
));
}
}
Ok(())
}
pub fn reproducible_cflags(&self) -> Vec<String> {
self.reproducible_flags.clone()
}
pub fn generate_source_date_epoch() -> String {
"0".to_string()
}
pub fn generate_reproducible_cmake(&self) -> String {
let mut cmake = String::new();
cmake.push_str("# Reproducible build settings\n");
cmake.push_str("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ");
for flag in &self.reproducible_flags {
cmake.push_str(&format!("{} ", flag));
}
cmake.push_str("\")\n");
cmake.push_str("set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ");
for flag in &self.reproducible_flags {
cmake.push_str(&format!("{} ", flag));
}
cmake.push_str("\")\n");
cmake.push_str("set(ENV{SOURCE_DATE_EPOCH} \"0\")\n");
cmake
}
pub fn generate_dockerfile_snippet(&self) -> String {
r#"# Reproducible build environment
ENV SOURCE_DATE_EPOCH=0
ENV DEB_BUILD_OPTIONS="reproducible=+fixfilepath,+fixdebugpath"
ENV CFLAGS="-ffile-prefix-map=/build=."
ENV CXXFLAGS="-ffile-prefix-map=/build=."
ENV LDFLAGS="-Wl,--build-id=none"
"#
.to_string()
}
pub fn generate_audit_report(&self, packages: &HashMap<String, X86ResolvedPackage>) -> String {
let mut report = String::new();
report.push_str("# X86 Dependency Lock Audit Report\n");
report.push_str(&format!("Generated: {}\n", chrono_or_now()));
report.push_str(&format!("Lock version: {}\n", self.lock_version));
report.push_str(&format!("Total packages: {}\n\n", packages.len()));
report.push_str("| Package | Version | Source | X86 Optimized |\n");
report.push_str("|---------|---------|--------|---------------|\n");
let mut sorted: Vec<&X86ResolvedPackage> = packages.values().collect();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
for pkg in &sorted {
report.push_str(&format!(
"| {} | {} | {} | {} |\n",
pkg.name,
pkg.version,
pkg.source,
if pkg.is_x86_optimized { "Yes" } else { "No" }
));
}
report.push_str("\n## Reproducible Build Flags\n\n```\n");
for flag in &self.reproducible_flags {
report.push_str(&format!("{}\n", flag));
}
report.push_str("```\n");
report
}
}
impl Default for X86DependencyLocking {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86LockedDependency {
pub name: String,
pub version: String,
pub source: String,
pub include_dirs: Vec<PathBuf>,
pub library_dirs: Vec<PathBuf>,
pub libraries: Vec<String>,
pub compile_flags: Vec<String>,
pub link_flags: Vec<String>,
pub defines: HashMap<String, String>,
pub dependencies: Vec<String>,
pub integrity: String,
pub is_x86_optimized: bool,
pub microarch_requirement: Option<String>,
}
fn chrono_or_now() -> String {
format!("{:?}", SystemTime::now())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_x86_package_managers_new() {
let mgr = X86PackageManagers::new();
assert_eq!(mgr.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(mgr.microarch_level, X86MicroarchLevel::X86_64);
assert_eq!(mgr.build_type, X86CMakeBuildType::Release);
}
#[test]
fn test_x86_package_managers_default() {
let mgr = X86PackageManagers::default();
assert_eq!(mgr.microarch_level, X86MicroarchLevel::X86_64);
}
#[test]
fn test_x86_package_managers_for_i686_linux() {
let mgr = X86PackageManagers::for_i686_linux();
assert_eq!(mgr.target_triple, "i686-unknown-linux-gnu");
assert_eq!(mgr.microarch_level, X86MicroarchLevel::I686);
assert_eq!(mgr.vcpkg.triplet, "x86-linux");
}
#[test]
fn test_x86_package_managers_for_x86_64_avx2() {
let mgr = X86PackageManagers::for_x86_64_avx2();
assert_eq!(mgr.microarch_level, X86MicroarchLevel::X86_64_V3);
}
#[test]
fn test_x86_package_managers_for_x86_64_avx512() {
let mgr = X86PackageManagers::for_x86_64_avx512();
assert_eq!(mgr.microarch_level, X86MicroarchLevel::X86_64_V4);
}
#[test]
fn test_x86_package_managers_for_x86_64_windows() {
let mgr = X86PackageManagers::for_x86_64_windows();
assert_eq!(mgr.target_triple, "x86_64-pc-windows-msvc");
assert_eq!(mgr.vcpkg.triplet, "x64-windows");
}
#[test]
fn test_x86_package_managers_for_x86_64_macos() {
let mgr = X86PackageManagers::for_x86_64_macos();
assert_eq!(mgr.target_triple, "x86_64-apple-darwin");
assert_eq!(mgr.vcpkg.triplet, "x64-osx");
}
#[test]
fn test_x86_package_managers_with_build_type() {
let mgr = X86PackageManagers::new().with_build_type(X86CMakeBuildType::Debug);
assert_eq!(mgr.build_type, X86CMakeBuildType::Debug);
assert_eq!(mgr.conan.build_type, X86CMakeBuildType::Debug);
}
#[test]
fn test_x86_package_managers_with_opt_level() {
let mgr = X86PackageManagers::new().with_opt_level(X86OptLevel::O3);
assert_eq!(mgr.opt_level, X86OptLevel::O3);
}
#[test]
fn test_x86_package_managers_resolve_all() {
let mut mgr = X86PackageManagers::new();
let resolution = mgr.resolve_all(&["zlib", "fmt"]);
assert!(resolution.total_time_ms < 10000);
}
#[test]
fn test_x86_package_managers_cache_stats() {
let mgr = X86PackageManagers::new();
let stats = mgr.cache_stats();
assert_eq!(stats.total_entries, 0);
assert_eq!(stats.total_size_bytes, 0);
}
#[test]
fn test_x86_package_managers_clear_cache() {
let mut mgr = X86PackageManagers::new();
mgr.cache.insert(
"test".into(),
X86ResolvedPackage {
name: "test".into(),
version: "1.0".into(),
description: "test".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::Custom,
},
);
mgr.clear_cache();
assert_eq!(mgr.cache.len(), 0);
}
#[test]
fn test_x86_package_managers_generate_cmake_integration() {
let mgr = X86PackageManagers::new();
let cmake = mgr.generate_cmake_integration();
assert!(cmake.contains("Conan"));
assert!(cmake.contains("vcpkg"));
assert!(cmake.contains("Spack"));
}
#[test]
fn test_x86_package_managers_generate_lock_file() {
let mgr = X86PackageManagers::new();
let tmp = std::env::temp_dir().join("test_lock.json");
let result = mgr.generate_lock_file(&tmp);
assert!(result.is_ok());
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_microarch_level_march_flag() {
assert_eq!(X86MicroarchLevel::X86_64.march_flag(), "x86-64");
assert_eq!(X86MicroarchLevel::X86_64_V3.march_flag(), "x86-64-v3");
assert_eq!(X86MicroarchLevel::Haswell.march_flag(), "haswell");
assert_eq!(X86MicroarchLevel::I686.march_flag(), "i686");
assert_eq!(X86MicroarchLevel::Icelake.march_flag(), "icelake-client");
}
#[test]
fn test_microarch_level_feature_flags() {
let flags = X86MicroarchLevel::X86_64.feature_flags();
assert!(flags.contains(&"sse"));
assert!(flags.contains(&"sse2"));
let flags_v4 = X86MicroarchLevel::X86_64_V4.feature_flags();
assert!(flags_v4.contains(&"avx512f"));
assert!(flags_v4.contains(&"avx512bw"));
}
#[test]
fn test_microarch_level_is_64bit() {
assert!(X86MicroarchLevel::X86_64.is_64bit());
assert!(X86MicroarchLevel::Skylake.is_64bit());
assert!(!X86MicroarchLevel::I686.is_64bit());
assert!(!X86MicroarchLevel::Pentium4.is_64bit());
}
#[test]
fn test_microarch_level_conan_arch() {
assert_eq!(X86MicroarchLevel::X86_64.conan_arch(), "x86_64");
assert_eq!(X86MicroarchLevel::I686.conan_arch(), "x86");
}
#[test]
fn test_microarch_level_spack_target() {
assert_eq!(X86MicroarchLevel::X86_64.spack_target(), "x86_64");
assert_eq!(X86MicroarchLevel::I686.spack_target(), "i686");
}
#[test]
fn test_microarch_level_display() {
assert_eq!(format!("{}", X86MicroarchLevel::X86_64), "x86-64");
assert_eq!(format!("{}", X86MicroarchLevel::I686), "i686");
}
#[test]
fn test_opt_level_flag() {
assert_eq!(X86OptLevel::O0.flag(), "-O0");
assert_eq!(X86OptLevel::O3.flag(), "-O3");
assert_eq!(X86OptLevel::Ofast.flag(), "-Ofast");
}
#[test]
fn test_opt_level_default() {
assert_eq!(X86OptLevel::default(), X86OptLevel::O2);
}
#[test]
fn test_cmake_build_type_as_str() {
assert_eq!(X86CMakeBuildType::Debug.as_str(), "Debug");
assert_eq!(X86CMakeBuildType::Release.as_str(), "Release");
assert_eq!(X86CMakeBuildType::RelWithDebInfo.as_str(), "RelWithDebInfo");
assert_eq!(X86CMakeBuildType::MinSizeRel.as_str(), "MinSizeRel");
}
#[test]
fn test_cmake_build_type_default() {
assert_eq!(X86CMakeBuildType::default(), X86CMakeBuildType::Release);
}
#[test]
fn test_resolved_package_estimated_size() {
let pkg = X86ResolvedPackage {
name: "test".into(),
version: "1.0".into(),
description: "desc".into(),
homepage: Some("http://example.com".into()),
license: Some("MIT".into()),
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![PathBuf::from("/usr/lib")],
libraries: vec!["libtest".into()],
compile_flags: vec!["-O2".into()],
link_flags: vec!["-ltest".into()],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: true,
microarch_requirement: None,
source: X86PackageSource::Conan,
};
assert!(pkg.estimated_size() > 0);
}
#[test]
fn test_package_source_display() {
assert_eq!(format!("{}", X86PackageSource::Conan), "conan");
assert_eq!(format!("{}", X86PackageSource::Vcpkg), "vcpkg");
assert_eq!(format!("{}", X86PackageSource::Spack), "spack");
assert_eq!(format!("{}", X86PackageSource::PkgConfig), "pkg-config");
assert_eq!(format!("{}", X86PackageSource::SystemApt), "apt");
assert_eq!(format!("{}", X86PackageSource::SystemBrew), "brew");
assert_eq!(
format!("{}", X86PackageSource::CMakeFindPackage),
"cmake-find-package"
);
}
#[test]
fn test_package_resolution_default() {
let res = X86PackageResolution::default();
assert!(res.resolved.is_empty());
assert!(res.missing.is_empty());
assert_eq!(res.total_time_ms, 0);
}
#[test]
fn test_conan_support_new() {
let conan = X86ConanSupport::new();
assert_eq!(conan.profile, "default");
assert_eq!(conan.microarch, X86MicroarchLevel::X86_64);
assert!(!conan.generators.is_empty());
}
#[test]
fn test_conan_support_for_i686() {
let conan = X86ConanSupport::for_i686();
assert_eq!(conan.settings.get("arch").unwrap(), "x86");
assert_eq!(conan.microarch, X86MicroarchLevel::I686);
}
#[test]
fn test_conan_support_for_x86_64_avx2() {
let conan = X86ConanSupport::for_x86_64_avx2();
assert_eq!(conan.microarch, X86MicroarchLevel::X86_64_V3);
}
#[test]
fn test_conan_support_for_x86_64_avx512() {
let conan = X86ConanSupport::for_x86_64_avx512();
assert_eq!(conan.microarch, X86MicroarchLevel::X86_64_V4);
}
#[test]
fn test_conan_support_for_x86_64_windows() {
let conan = X86ConanSupport::for_x86_64_windows();
assert_eq!(conan.settings.get("os").unwrap(), "Windows");
assert_eq!(conan.settings.get("compiler").unwrap(), "msvc");
}
#[test]
fn test_conan_support_add_requirement() {
let mut conan = X86ConanSupport::new();
conan.add_requirement("boost", "1.84.0");
assert_eq!(conan.requires.len(), 1);
assert_eq!(conan.requires[0].name, "boost");
}
#[test]
fn test_conan_support_with_build_policy() {
let conan = X86ConanSupport::new().with_build_policy(X86ConanBuildPolicy::Outdated);
assert_eq!(conan.build_policy, X86ConanBuildPolicy::Outdated);
}
#[test]
fn test_conan_build_policy_as_flag() {
assert_eq!(X86ConanBuildPolicy::Missing.as_flag(), "--build=missing");
assert_eq!(X86ConanBuildPolicy::Outdated.as_flag(), "--build=outdated");
assert_eq!(X86ConanBuildPolicy::Cascade.as_flag(), "--build=cascade");
}
#[test]
fn test_conan_generator_command_arg() {
assert_eq!(X86ConanGenerator::CMake.command_arg(), "cmake");
assert_eq!(X86ConanGenerator::CMakeDeps.command_arg(), "CMakeDeps");
assert_eq!(
X86ConanGenerator::CMakeToolchain.command_arg(),
"CMakeToolchain"
);
}
#[test]
fn test_conan_generate_conanfile_txt() {
let mut conan = X86ConanSupport::new();
conan.add_requirement("fmt", "10.2.1");
let txt = conan.generate_conanfile_txt();
assert!(txt.contains("[requires]"));
assert!(txt.contains("fmt/10.2.1"));
assert!(txt.contains("[generators]"));
}
#[test]
fn test_conan_generate_conanfile_py() {
let conan = X86ConanSupport::new();
let py = conan.generate_conanfile_py();
assert!(py.contains("class X86ProjectConan"));
assert!(py.contains("CMakeToolchain"));
assert!(py.contains("def requirements"));
}
#[test]
fn test_conan_generate_profile() {
let conan = X86ConanSupport::new();
let profile = conan.generate_profile("test-profile");
assert!(profile.contains("test-profile"));
assert!(profile.contains("[settings]"));
}
#[test]
fn test_conan_generate_standard_profiles() {
let profiles = X86ConanSupport::generate_standard_profiles();
assert!(profiles.contains_key("x86_64-linux-release"));
assert!(profiles.contains_key("x86_64-v3-linux-release"));
assert!(profiles.contains_key("i686-linux-release"));
}
#[test]
fn test_conan_known_packages() {
let pkgs = X86ConanSupport::known_packages();
assert!(pkgs.iter().any(|p| p.name == "boost"));
assert!(pkgs.iter().any(|p| p.name == "fmt"));
assert!(pkgs.iter().any(|p| p.name == "openssl"));
}
#[test]
fn test_conan_search() {
let conan = X86ConanSupport::new();
let results = conan.search("boost");
assert!(results.iter().any(|p| p.name == "boost"));
}
#[test]
fn test_conan_resolve() {
let conan = X86ConanSupport::new();
let result = conan.resolve("boost");
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.name, "boost");
assert!(pkg.is_x86_optimized);
assert_eq!(pkg.source, X86PackageSource::Conan);
}
#[test]
fn test_conan_resolve_unknown() {
let conan = X86ConanSupport::new();
let result = conan.resolve("nonexistent-package");
assert!(result.is_err());
}
#[test]
fn test_conan_build_dependency_graph() {
let mut conan = X86ConanSupport::new();
conan.add_requirement("boost", "1.84.0");
conan.add_requirement("fmt", "10.2.1");
let graph = conan.build_dependency_graph();
assert_eq!(graph.nodes.len(), 2);
assert_eq!(graph.edges.len(), 2);
}
#[test]
fn test_dependency_graph_topological_sort() {
let mut graph = X86DependencyGraph::default();
graph.add_node(X86DepGraphNode {
name: "A".into(),
version: "1.0".into(),
is_direct: true,
is_x86_native: true,
source: X86PackageSource::Conan,
});
graph.add_node(X86DepGraphNode {
name: "B".into(),
version: "1.0".into(),
is_direct: false,
is_x86_native: true,
source: X86PackageSource::Conan,
});
graph.add_edge("A", "B", "requires");
let sorted = graph.compute_topological_order();
assert!(sorted.is_ok());
let order = sorted.unwrap();
assert_eq!(order[0], "A");
assert_eq!(order[1], "B");
}
#[test]
fn test_dependency_graph_cycle_detection() {
let mut graph = X86DependencyGraph::default();
graph.add_node(X86DepGraphNode {
name: "A".into(),
version: "1.0".into(),
is_direct: true,
is_x86_native: true,
source: X86PackageSource::Conan,
});
graph.add_node(X86DepGraphNode {
name: "B".into(),
version: "1.0".into(),
is_direct: true,
is_x86_native: true,
source: X86PackageSource::Conan,
});
graph.add_edge("A", "B", "requires");
graph.add_edge("B", "A", "requires");
let result = graph.compute_topological_order();
assert!(result.is_err());
}
#[test]
fn test_dependency_graph_to_dot() {
let mut graph = X86DependencyGraph::default();
graph.add_node(X86DepGraphNode {
name: "A".into(),
version: "1.0".into(),
is_direct: true,
is_x86_native: true,
source: X86PackageSource::Conan,
});
let dot = graph.to_dot();
assert!(dot.contains("digraph"));
assert!(dot.contains("A"));
}
#[test]
fn test_vcpkg_support_new() {
let vcpkg = X86VcpkgSupport::new();
assert_eq!(vcpkg.triplet, "x64-linux");
assert!(!vcpkg.vcpkg_root.as_os_str().is_empty());
}
#[test]
fn test_vcpkg_support_for_i686_linux() {
let vcpkg = X86VcpkgSupport::for_i686_linux();
assert_eq!(vcpkg.triplet, "x86-linux");
}
#[test]
fn test_vcpkg_support_for_x64_windows() {
let vcpkg = X86VcpkgSupport::for_x64_windows();
assert_eq!(vcpkg.triplet, "x64-windows");
}
#[test]
fn test_vcpkg_support_for_x64_osx() {
let vcpkg = X86VcpkgSupport::for_x64_osx();
assert_eq!(vcpkg.triplet, "x64-osx");
}
#[test]
fn test_vcpkg_support_for_x86_windows() {
let vcpkg = X86VcpkgSupport::for_x86_windows();
assert_eq!(vcpkg.triplet, "x86-windows");
}
#[test]
fn test_vcpkg_support_with_static_linking() {
let vcpkg = X86VcpkgSupport::new().with_static_linking();
assert!(vcpkg.static_linking);
assert!(vcpkg.triplet.contains("static"));
}
#[test]
fn test_vcpkg_support_add_dependency() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_simple_dep("boost");
vcpkg.add_simple_dep("fmt");
assert_eq!(vcpkg.manifest.dependencies.len(), 2);
}
#[test]
fn test_vcpkg_x86_triplets() {
let triplets = X86VcpkgSupport::x86_triplets();
assert!(triplets.contains(&"x64-linux"));
assert!(triplets.contains(&"x86-linux"));
assert!(triplets.contains(&"x64-windows"));
assert!(triplets.contains(&"x64-osx"));
}
#[test]
fn test_vcpkg_detect_triplet() {
let triplet = X86VcpkgSupport::detect_triplet();
if cfg!(target_arch = "x86_64") && cfg!(target_os = "linux") {
assert_eq!(triplet, "x64-linux");
}
}
#[test]
fn test_vcpkg_generate_triplet_file() {
let vcpkg = X86VcpkgSupport::new();
let file = vcpkg.generate_triplet_file();
assert!(file.contains("VCPKG_TARGET_ARCHITECTURE"));
assert!(file.contains("x64"));
}
#[test]
fn test_vcpkg_generate_manifest() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_simple_dep("zlib");
vcpkg.add_simple_dep("fmt");
let manifest = vcpkg.generate_manifest();
assert_eq!(manifest["name"], "x86-project");
assert!(!manifest["dependencies"].as_array().unwrap().is_empty());
}
#[test]
fn test_vcpkg_generate_manifest_json() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_simple_dep("zlib");
let json = vcpkg.generate_manifest_json();
assert!(json.is_ok());
let j = json.unwrap();
assert!(j.contains("x86-project"));
assert!(j.contains("zlib"));
}
#[test]
fn test_vcpkg_generate_cmake_integration() {
let vcpkg = X86VcpkgSupport::new();
let cmake = vcpkg.generate_cmake_integration();
assert!(cmake.contains("vcpkg"));
assert!(cmake.contains("CMAKE_TOOLCHAIN_FILE"));
}
#[test]
fn test_vcpkg_known_ports() {
let ports = X86VcpkgSupport::known_ports();
assert!(ports.iter().any(|p| p.name == "boost"));
assert!(ports.iter().any(|p| p.name == "fmt"));
assert!(ports
.iter()
.any(|p| p.name == "openblas" && p.has_x86_optimizations));
}
#[test]
fn test_vcpkg_resolve() {
let vcpkg = X86VcpkgSupport::new();
let result = vcpkg.resolve("boost");
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.source, X86PackageSource::Vcpkg);
}
#[test]
fn test_vcpkg_resolve_unknown() {
let vcpkg = X86VcpkgSupport::new();
let result = vcpkg.resolve("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_spack_support_new() {
let spack = X86SpackSupport::new();
assert_eq!(spack.target, "x86_64");
assert!(!spack.compilers.is_empty());
}
#[test]
fn test_spack_support_for_i686() {
let spack = X86SpackSupport::for_i686();
assert_eq!(spack.target, "i686");
}
#[test]
fn test_spack_support_for_x86_64_v3() {
let spack = X86SpackSupport::for_x86_64_v3();
assert_eq!(spack.target, "x86_64_v3");
}
#[test]
fn test_spack_support_for_x86_64_v4() {
let spack = X86SpackSupport::for_x86_64_v4();
assert_eq!(spack.target, "x86_64_v4");
}
#[test]
fn test_spack_generate_package_py() {
let spack = X86SpackSupport::new();
let py = spack.generate_package_py("mylib", "1.0", "https://example.com/mylib-1.0.tar.gz");
assert!(py.contains("class Mylib"));
assert!(py.contains("CMakePackage"));
assert!(py.contains("version(\"1.0\""));
}
#[test]
fn test_spack_parse_spec() {
let spec = X86SpackSupport::parse_spec("hdf5@1.14.0%clang@17+mpi~fortran target=x86_64");
assert_eq!(spec.name, "hdf5");
assert_eq!(spec.version, Some("1.14.0".to_string()));
assert_eq!(spec.compiler, Some("clang".to_string()));
assert_eq!(spec.compiler_version, Some("17".to_string()));
assert_eq!(spec.target, Some("x86_64".to_string()));
assert!(spec.variants.contains_key("mpi"));
assert!(spec.variants.contains_key("fortran"));
}
#[test]
fn test_spack_parse_spec_minimal() {
let spec = X86SpackSupport::parse_spec("zlib");
assert_eq!(spec.name, "zlib");
assert!(spec.version.is_none());
}
#[test]
fn test_spack_parse_spec_with_version() {
let spec = X86SpackSupport::parse_spec("boost@1.84.0");
assert_eq!(spec.name, "boost");
assert_eq!(spec.version, Some("1.84.0".to_string()));
}
#[test]
fn test_spack_resolve_spec() {
let spack = X86SpackSupport::new();
let result = spack.resolve_spec("zlib@1.3.1");
assert!(result.is_ok());
let resolved = result.unwrap();
assert_eq!(resolved.name, "zlib");
assert_eq!(resolved.version, "1.3.1");
}
#[test]
fn test_spack_resolve_spec_empty_name() {
let spack = X86SpackSupport::new();
let result = spack.resolve_spec("@1.0");
assert!(result.is_err());
}
#[test]
fn test_spack_generate_compilers_yaml() {
let spack = X86SpackSupport::new();
let yaml = spack.generate_compilers_yaml();
assert!(yaml.contains("compilers:"));
assert!(yaml.contains("clang@17.0.0"));
}
#[test]
fn test_spack_generate_modulefile_tcl() {
let spack = X86SpackSupport::new();
let resolved = spack.resolve_spec("zlib@1.3.1").unwrap();
let tcl = spack.generate_modulefile_tcl(&resolved);
assert!(tcl.contains("#%Module"));
assert!(tcl.contains("zlib"));
}
#[test]
fn test_spack_generate_modulefile_lua() {
let spack = X86SpackSupport::new();
let resolved = spack.resolve_spec("zlib@1.3.1").unwrap();
let lua = spack.generate_modulefile_lua(&resolved);
assert!(lua.contains("whatis"));
assert!(lua.contains("zlib"));
}
#[test]
fn test_spack_generate_packages_yaml() {
let spack = X86SpackSupport::new();
let yaml = spack.generate_packages_yaml();
assert!(yaml.contains("packages:"));
assert!(yaml.contains("target:"));
}
#[test]
fn test_spack_hpc_packages() {
let pkgs = X86SpackSupport::hpc_packages();
assert!(pkgs.contains(&"openmpi"));
assert!(pkgs.contains(&"hdf5"));
assert!(pkgs.contains(&"cuda"));
assert!(pkgs.contains(&"openblas"));
}
#[test]
fn test_spack_resolve() {
let spack = X86SpackSupport::new();
let result = spack.resolve("openmpi");
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.source, X86PackageSource::Spack);
}
#[test]
fn test_spack_generate_cmake_hints() {
let spack = X86SpackSupport::new();
let hints = spack.generate_cmake_hints();
assert!(hints.contains("spack load"));
}
#[test]
fn test_pkg_config_new() {
let pc = X86PkgConfig::new();
assert!(!pc.pc_path.is_empty());
assert!(!pc.static_linking);
}
#[test]
fn test_pkg_config_add_path() {
let mut pc = X86PkgConfig::new();
let initial_len = pc.pc_path.len();
pc.add_path(&PathBuf::from("/custom/pkgconfig"));
assert_eq!(pc.pc_path.len(), initial_len + 1);
}
#[test]
fn test_pkg_config_parse_pc_file() {
let pc_content = r#"
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: zlib
Description: zlib compression library
Version: 1.2.13
Requires:
Libs: -L${libdir} -lz
Cflags: -I${includedir}
"#;
let tmp = std::env::temp_dir().join("test_zlib.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.name, "zlib");
assert_eq!(parsed.version, "1.2.13");
assert_eq!(parsed.description, "zlib compression library");
assert!(!parsed.include_dirs.is_empty());
assert!(!parsed.lib_dirs.is_empty());
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_variable_substitution() {
let mut vars = HashMap::new();
vars.insert("prefix".to_string(), "/usr".to_string());
vars.insert("libdir".to_string(), "${prefix}/lib".to_string());
let result = X86PkgConfig::resolve_variables("${libdir}", &vars);
assert_eq!(result, "/usr/lib");
}
#[test]
fn test_pkg_config_version_comparison() {
assert_eq!(
X86PkgConfig::compare_versions("1.0", "1.0"),
std::cmp::Ordering::Equal
);
assert_eq!(
X86PkgConfig::compare_versions("2.0", "1.0"),
std::cmp::Ordering::Greater
);
assert_eq!(
X86PkgConfig::compare_versions("1.0", "2.0"),
std::cmp::Ordering::Less
);
assert_eq!(
X86PkgConfig::compare_versions("1.2.3", "1.2.3"),
std::cmp::Ordering::Equal
);
assert_eq!(
X86PkgConfig::compare_versions("1.2.4", "1.2.3"),
std::cmp::Ordering::Greater
);
}
#[test]
fn test_pkg_config_version_satisfies() {
assert!(X86PkgConfig::version_satisfies("1.0", ">=1.0"));
assert!(X86PkgConfig::version_satisfies("2.0", ">=1.0"));
assert!(!X86PkgConfig::version_satisfies("0.9", ">=1.0"));
assert!(X86PkgConfig::version_satisfies("1.0", "<=1.0"));
assert!(X86PkgConfig::version_satisfies("0.5", "<=1.0"));
assert!(!X86PkgConfig::version_satisfies("2.0", "<=1.0"));
assert!(X86PkgConfig::version_satisfies("2.0", ">1.0"));
assert!(!X86PkgConfig::version_satisfies("1.0", ">1.0"));
assert!(X86PkgConfig::version_satisfies("0.5", "<1.0"));
assert!(X86PkgConfig::version_satisfies("1.0", "=1.0"));
assert!(!X86PkgConfig::version_satisfies("1.1", "=1.0"));
}
#[test]
fn test_pkg_config_split_flags() {
let flags = X86PkgConfig::split_flags("-I/usr/include -DDEBUG=1 -O2");
assert_eq!(flags.len(), 3);
assert!(flags.contains(&"-I/usr/include".to_string()));
assert!(flags.contains(&"-DDEBUG=1".to_string()));
assert!(flags.contains(&"-O2".to_string()));
}
#[test]
fn test_pkg_config_extract_include_dirs() {
let flags = vec![
"-I/usr/include".to_string(),
"-I/opt/include".to_string(),
"-DDEBUG".to_string(),
];
let dirs = X86PkgConfig::extract_include_dirs(&flags);
assert_eq!(dirs.len(), 2);
assert!(dirs.contains(&PathBuf::from("/usr/include")));
}
#[test]
fn test_pkg_config_extract_defines() {
let flags = vec![
"-DDEBUG".to_string(),
"-DVERSION=2".to_string(),
"-I/usr/include".to_string(),
];
let defines = X86PkgConfig::extract_defines(&flags);
assert_eq!(defines.get("DEBUG").unwrap(), "1");
assert_eq!(defines.get("VERSION").unwrap(), "2");
}
#[test]
fn test_pkg_config_extract_libraries() {
let libs = vec![
"-lz".to_string(),
"-L/usr/lib".to_string(),
"-lpthread".to_string(),
];
let libraries = X86PkgConfig::extract_libraries(&libs);
assert_eq!(libraries.len(), 2);
assert!(libraries.contains(&"z".to_string()));
assert!(libraries.contains(&"pthread".to_string()));
}
#[test]
fn test_pkg_config_find_nonexistent() {
let mut pc = X86PkgConfig::new();
let result = pc.find("nonexistent-pkg");
assert!(result.is_err());
}
#[test]
fn test_pkg_config_clear_cache() {
let mut pc = X86PkgConfig::new();
pc.cache_hits = 5;
pc.clear_cache();
assert_eq!(pc.cache_hits, 0);
}
#[test]
fn test_cmake_find_package_new() {
let cfp = X86CMakeFindPackage::new();
assert!(!cfp.find_modules.is_empty());
assert!(!cfp.config_packages.is_empty());
}
#[test]
fn test_cmake_find_package_find_package() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.find_package("Boost");
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.source, X86PackageSource::CMakeFindPackage);
}
#[test]
fn test_cmake_find_package_unknown() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.find_package("UnknownPackage");
assert!(result.is_err());
}
#[test]
fn test_cmake_find_package_generate_find_module() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_find_module("Boost");
assert!(result.is_ok());
let cmake = result.unwrap();
assert!(cmake.contains("FindBoost.cmake"));
assert!(cmake.contains("find_path"));
assert!(cmake.contains("find_library"));
}
#[test]
fn test_cmake_find_package_generate_find_module_unknown() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_find_module("Unknown");
assert!(result.is_err());
}
#[test]
fn test_cmake_find_package_generate_config_file() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_config_file("fmt");
assert!(result.is_ok());
let cmake = result.unwrap();
assert!(cmake.contains("fmtConfig.cmake"));
assert!(cmake.contains("add_library"));
}
#[test]
fn test_cmake_find_package_generate_config_file_unknown() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_config_file("Unknown");
assert!(result.is_err());
}
#[test]
fn test_cmake_find_package_check_version() {
assert!(X86CMakeFindPackage::check_version("Boost", ">=1.80"));
assert!(!X86CMakeFindPackage::check_version("Boost", ">2.0"));
}
#[test]
fn test_cmake_find_package_has_component() {
assert!(X86CMakeFindPackage::has_component("Boost", "filesystem"));
assert!(X86CMakeFindPackage::has_component("Boost", "thread"));
assert!(!X86CMakeFindPackage::has_component("Boost", "nonexistent"));
}
#[test]
fn test_cmake_find_package_list_components() {
let cfp = X86CMakeFindPackage::new();
let components = cfp.list_components("Boost");
assert!(components.contains(&"filesystem".to_string()));
assert!(components.contains(&"thread".to_string()));
}
#[test]
fn test_cmake_find_package_list_components_unknown() {
let cfp = X86CMakeFindPackage::new();
let components = cfp.list_components("Unknown");
assert!(components.is_empty());
}
#[test]
fn test_cmake_find_package_generate_snippet() {
let cfp = X86CMakeFindPackage::new();
let snippet = cfp.generate_snippet(&["Boost", "OpenSSL", "fmt"]);
assert!(snippet.contains("find_package(Boost"));
assert!(snippet.contains("find_package(OpenSSL"));
assert!(snippet.contains("find_package(fmt"));
}
#[test]
fn test_system_pm_new() {
let spm = X86SystemPackageManager::new();
assert!(spm.detected.is_some());
}
#[test]
fn test_system_pm_for_i686_linux() {
let spm = X86SystemPackageManager::for_i686_linux();
assert!(spm.package_cache.contains_key("gcc-multilib"));
}
#[test]
fn test_system_pm_for_windows() {
let spm = X86SystemPackageManager::for_windows();
assert_eq!(spm.detected, Some(X86SystemPM::Pacman));
}
#[test]
fn test_system_pm_for_macos() {
let spm = X86SystemPackageManager::for_macos();
assert_eq!(spm.detected, Some(X86SystemPM::Brew));
}
#[test]
fn test_system_pm_detect_pm() {
let pm = X86SystemPackageManager::detect_pm();
assert!(pm.is_some());
}
#[test]
fn test_system_pm_name() {
assert_eq!(X86SystemPM::Apt.name(), "apt");
assert_eq!(X86SystemPM::Dnf.name(), "dnf");
assert_eq!(X86SystemPM::Pacman.name(), "pacman");
assert_eq!(X86SystemPM::Brew.name(), "brew");
assert_eq!(X86SystemPM::Zypper.name(), "zypper");
assert_eq!(X86SystemPM::Apk.name(), "apk");
assert_eq!(X86SystemPM::Emerge.name(), "emerge");
assert_eq!(X86SystemPM::Xbps.name(), "xbps");
}
#[test]
fn test_system_pm_display() {
assert_eq!(format!("{}", X86SystemPM::Apt), "apt");
assert_eq!(format!("{}", X86SystemPM::Pacman), "pacman");
}
#[test]
fn test_system_pm_generate_install_command() {
let spm = X86SystemPackageManager::new();
let cmd = spm.generate_install_command(&["gcc", "cmake"]);
assert!(cmd.contains("sudo"));
assert!(cmd.contains("gcc"));
assert!(cmd.contains("cmake"));
}
#[test]
fn test_system_pm_generate_search_command() {
let spm = X86SystemPackageManager::new();
let cmd = spm.generate_search_command("boost");
assert!(cmd.contains("boost"));
}
#[test]
fn test_system_pm_resolve() {
let spm = X86SystemPackageManager::new();
let result = spm.resolve("build-essential");
assert!(result.is_ok());
}
#[test]
fn test_system_pm_common_dev_packages() {
let pkgs = X86SystemPackageManager::common_dev_packages();
assert!(pkgs.iter().any(|p| p.name == "build-essential"));
assert!(pkgs.iter().any(|p| p.name == "clang"));
assert!(pkgs.iter().any(|p| p.name == "libssl-dev"));
}
#[test]
fn test_system_pm_debian_packages() {
let pkgs = X86SystemPackageManager::debian_packages();
assert!(pkgs.contains(&"build-essential"));
assert!(pkgs.contains(&"clang"));
}
#[test]
fn test_system_pm_rhel_packages() {
let pkgs = X86SystemPackageManager::rhel_packages();
assert!(pkgs.contains(&"gcc-c++"));
assert!(pkgs.contains(&"clang"));
}
#[test]
fn test_system_pm_arch_packages() {
let pkgs = X86SystemPackageManager::arch_packages();
assert!(pkgs.contains(&"base-devel"));
assert!(pkgs.contains(&"boost"));
}
#[test]
fn test_system_pm_brew_packages() {
let pkgs = X86SystemPackageManager::brew_packages();
assert!(pkgs.contains(&"llvm"));
assert!(pkgs.contains(&"cmake"));
}
#[test]
fn test_dependency_locking_new() {
let lock = X86DependencyLocking::new();
assert_eq!(lock.lock_version, 1);
assert!(lock.verify_hashes);
assert!(lock.exact_pinning);
assert!(!lock.reproducible_flags.is_empty());
}
#[test]
fn test_dependency_locking_with_lock_file() {
let lock = X86DependencyLocking::new().with_lock_file(&PathBuf::from("custom.lock"));
assert_eq!(lock.lock_file_path, PathBuf::from("custom.lock"));
}
#[test]
fn test_dependency_locking_with_hash_verification() {
let lock = X86DependencyLocking::new().with_hash_verification(false);
assert!(!lock.verify_hashes);
}
#[test]
fn test_dependency_locking_write_lock_file() {
let lock = X86DependencyLocking::new();
let mut packages = HashMap::new();
packages.insert(
"zlib".into(),
X86ResolvedPackage {
name: "zlib".into(),
version: "1.2.13".into(),
description: "Zlib".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec!["z".into()],
compile_flags: vec![],
link_flags: vec!["-lz".into()],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::SystemApt,
},
);
let tmp = std::env::temp_dir().join("test_lock_output.json");
let result = lock.write_lock_file(&packages, &tmp);
assert!(result.is_ok());
let content = std::fs::read_to_string(&tmp).unwrap();
assert!(content.contains("zlib"));
assert!(content.contains("1.2.13"));
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_dependency_locking_parse_lock_file() {
let lock_data = r#"{
"lock_version": 1,
"generated_at": "0",
"target": {"architecture": "x86_64", "os": "linux"},
"reproducible_flags": [],
"dependencies": {
"zlib": {
"name": "zlib",
"version": "1.2.13",
"source": "apt",
"include_dirs": ["/usr/include"],
"library_dirs": ["/usr/lib"],
"libraries": ["z"],
"compile_flags": [],
"link_flags": ["-lz"],
"defines": {},
"dependencies": [],
"is_x86_optimized": false,
"integrity": "sha256:abc123",
"microarch_requirement": null
}
}
}"#;
let tmp = std::env::temp_dir().join("test_lock_parse.json");
std::fs::write(&tmp, lock_data).unwrap();
let mut lock = X86DependencyLocking::new();
let result = lock.parse_lock_file(&tmp);
assert!(result.is_ok());
assert!(lock.locked.contains_key("zlib"));
assert_eq!(lock.locked["zlib"].version, "1.2.13");
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_dependency_locking_pin_dependency() {
let mut lock = X86DependencyLocking::new();
lock.pin_dependency("boost", "1.84.0");
assert!(lock.locked.contains_key("boost"));
assert_eq!(lock.locked["boost"].version, "1.84.0");
}
#[test]
fn test_dependency_locking_check_pinned() {
let mut lock = X86DependencyLocking::new();
lock.pin_dependency("boost", "1.84.0");
assert!(lock.check_pinned("boost", "1.84.0"));
assert!(!lock.check_pinned("boost", "1.83.0"));
assert!(!lock.check_pinned("zlib", "1.2.13"));
}
#[test]
fn test_dependency_locking_verify_hash() {
let mut lock = X86DependencyLocking::new();
lock.locked.insert(
"zlib".into(),
X86LockedDependency {
name: "zlib".into(),
version: "1.0".into(),
source: "apt".into(),
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
integrity: "sha256:abc123".into(),
is_x86_optimized: false,
microarch_requirement: None,
},
);
assert!(lock.verify_hash("zlib", "sha256:abc123").is_ok());
assert!(lock.verify_hash("zlib", "sha256:wrong").is_err());
}
#[test]
fn test_dependency_locking_verify_hash_disabled() {
let mut lock = X86DependencyLocking::new();
lock.verify_hashes = false;
lock.locked.insert(
"zlib".into(),
X86LockedDependency {
name: "zlib".into(),
version: "1.0".into(),
source: "apt".into(),
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
integrity: "sha256:abc123".into(),
is_x86_optimized: false,
microarch_requirement: None,
},
);
assert!(lock.verify_hash("zlib", "sha256:wrong").is_ok());
}
#[test]
fn test_dependency_locking_reproducible_cflags() {
let lock = X86DependencyLocking::new();
let flags = lock.reproducible_cflags();
assert!(flags.iter().any(|f| f.contains("file-prefix-map")));
}
#[test]
fn test_dependency_locking_generate_reproducible_cmake() {
let lock = X86DependencyLocking::new();
let cmake = lock.generate_reproducible_cmake();
assert!(cmake.contains("SOURCE_DATE_EPOCH"));
assert!(cmake.contains("CMAKE_C_FLAGS"));
}
#[test]
fn test_dependency_locking_generate_dockerfile_snippet() {
let lock = X86DependencyLocking::new();
let docker = lock.generate_dockerfile_snippet();
assert!(docker.contains("SOURCE_DATE_EPOCH=0"));
assert!(docker.contains("DEB_BUILD_OPTIONS"));
}
#[test]
fn test_dependency_locking_generate_audit_report() {
let lock = X86DependencyLocking::new();
let mut packages = HashMap::new();
packages.insert(
"zlib".into(),
X86ResolvedPackage {
name: "zlib".into(),
version: "1.2.13".into(),
description: "Zlib".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::SystemApt,
},
);
let report = lock.generate_audit_report(&packages);
assert!(report.contains("X86 Dependency Lock Audit Report"));
assert!(report.contains("zlib"));
assert!(report.contains("1.2.13"));
}
#[test]
fn test_lock_roundtrip() {
let mut packages = HashMap::new();
packages.insert(
"fmt".into(),
X86ResolvedPackage {
name: "fmt".into(),
version: "10.2.1".into(),
description: "fmt".into(),
homepage: Some("https://fmt.dev".into()),
license: Some("MIT".into()),
include_dirs: vec![PathBuf::from("/usr/include")],
library_dirs: vec![PathBuf::from("/usr/lib")],
libraries: vec!["fmt".into()],
compile_flags: vec!["-O2".into()],
link_flags: vec!["-lfmt".into()],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: true,
microarch_requirement: Some(X86MicroarchLevel::X86_64),
source: X86PackageSource::Conan,
},
);
let lock = X86DependencyLocking::new();
let tmp = std::env::temp_dir().join("test_roundtrip.lock");
lock.write_lock_file(&packages, &tmp).unwrap();
let mut lock2 = X86DependencyLocking::new();
lock2.parse_lock_file(&tmp).unwrap();
assert!(lock2.locked.contains_key("fmt"));
assert_eq!(lock2.locked["fmt"].version, "10.2.1");
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_full_conan_pipeline() {
let mut mgr = X86PackageManagers::new();
mgr.conan.add_requirement("boost", "1.84.0");
mgr.conan.add_requirement("fmt", "10.2.1");
let conanfile = mgr.conan.generate_conanfile_txt();
assert!(conanfile.contains("boost/1.84.0"));
assert!(conanfile.contains("fmt/10.2.1"));
let mut graph = mgr.conan.build_dependency_graph();
let sorted = graph.compute_topological_order();
assert!(sorted.is_ok());
}
#[test]
fn test_full_vcpkg_pipeline() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_simple_dep("boost");
vcpkg.add_simple_dep("fmt");
let manifest = vcpkg.generate_manifest();
assert_eq!(manifest["name"], "x86-project");
let json = vcpkg.generate_manifest_json();
assert!(json.is_ok());
let cmake = vcpkg.generate_cmake_integration();
assert!(cmake.contains("vcpkg"));
}
#[test]
fn test_full_spack_pipeline() {
let spack = X86SpackSupport::new();
let spec = X86SpackSupport::parse_spec("hdf5@1.14.0+mpi%clang@17");
assert_eq!(spec.name, "hdf5");
let resolved = spack.resolve_spec("zlib@1.3.1").unwrap();
let tcl = spack.generate_modulefile_tcl(&resolved);
let lua = spack.generate_modulefile_lua(&resolved);
assert!(tcl.contains("zlib"));
assert!(lua.contains("zlib"));
}
#[test]
fn test_full_pkg_config_pipeline() {
let pc_content = r#"
prefix=/opt/mylib
exec_prefix=${prefix}
libdir=${exec_prefix}/lib64
includedir=${prefix}/include
Name: mylib
Description: My test library
Version: 2.5.0
Requires: zlib
Libs: -L${libdir} -lmylib
Cflags: -I${includedir} -DMYLIB_VERSION=2
"#;
let tmp = std::env::temp_dir().join("mylib.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.name, "mylib");
assert_eq!(parsed.version, "2.5.0");
assert_eq!(parsed.requires, vec!["zlib"]);
let defines = X86PkgConfig::extract_defines(&parsed.cflags);
assert_eq!(defines.get("MYLIB_VERSION").unwrap(), "2");
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_full_cmake_find_package_pipeline() {
let cfp = X86CMakeFindPackage::new();
let find_module = cfp.generate_find_module("Boost").unwrap();
assert!(find_module.contains("find_path"));
assert!(find_module.contains("filesystem"));
let config_file = cfp.generate_config_file("fmt").unwrap();
assert!(config_file.contains("fmtConfig"));
assert!(X86CMakeFindPackage::has_component("GTest", "gtest"));
assert!(X86CMakeFindPackage::has_component("GTest", "gmock"));
assert!(X86CMakeFindPackage::check_version("fmt", ">=10"));
}
#[test]
fn test_full_system_pm_pipeline() {
let spm = X86SystemPackageManager::new();
assert!(spm.detected.is_some());
let cmd = spm.generate_install_command(&["build-essential", "cmake", "gcc"]);
assert!(cmd.contains("build-essential"));
let deb_pkgs = X86SystemPackageManager::debian_packages();
let rhel_pkgs = X86SystemPackageManager::rhel_packages();
let arch_pkgs = X86SystemPackageManager::arch_packages();
assert!(deb_pkgs.len() > 5);
assert!(rhel_pkgs.len() > 5);
assert!(arch_pkgs.len() > 5);
}
#[test]
fn test_full_dependency_locking_pipeline() {
let lock = X86DependencyLocking::new();
let mut packages = HashMap::new();
packages.insert(
"boost".into(),
X86ResolvedPackage {
name: "boost".into(),
version: "1.84.0".into(),
description: "Boost".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: true,
microarch_requirement: Some(X86MicroarchLevel::Haswell),
source: X86PackageSource::Conan,
},
);
let tmp = std::env::temp_dir().join("pipeline_test.lock");
lock.write_lock_file(&packages, &tmp).unwrap();
let mut lock2 = X86DependencyLocking::new();
lock2.parse_lock_file(&tmp).unwrap();
assert!(lock2.locked.contains_key("boost"));
lock2.pin_dependency("zlib", "1.3.1");
assert!(lock2.check_pinned("zlib", "1.3.1"));
let report = lock2.generate_audit_report(&packages);
assert!(report.contains("boost"));
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_empty_requires_graph() {
let conan = X86ConanSupport::new();
let graph = conan.build_dependency_graph();
assert_eq!(graph.nodes.len(), 0);
}
#[test]
fn test_empty_vcpkg_manifest() {
let vcpkg = X86VcpkgSupport::new();
let manifest = vcpkg.generate_manifest();
assert!(manifest["dependencies"].as_array().unwrap().is_empty());
}
#[test]
fn test_empty_pc_file_parse() {
let tmp = std::env::temp_dir().join("empty.pc");
std::fs::write(&tmp, "").unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp);
assert!(parsed.is_ok());
let file = parsed.unwrap();
assert!(file.name.is_empty());
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_default_cache_stats() {
let mgr = X86PackageManagers::new();
let stats = mgr.cache_stats();
assert_eq!(stats.total_entries, 0);
assert_eq!(stats.pkg_config_hits, 0);
assert_eq!(stats.cmake_hits, 0);
}
#[test]
fn test_lock_file_with_no_deps() {
let lock = X86DependencyLocking::new();
let packages: HashMap<String, X86ResolvedPackage> = HashMap::new();
let tmp = std::env::temp_dir().join("empty_lock.json");
let result = lock.write_lock_file(&packages, &tmp);
assert!(result.is_ok());
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_spack_empty_compilers() {
let mut spack = X86SpackSupport::new();
spack.compilers.clear();
let result = spack.resolve_spec("zlib@1.3.1%gcc@13");
assert!(result.is_err());
}
#[test]
fn test_pkg_config_malformed_pc_file() {
let tmp = std::env::temp_dir().join("malformed.pc");
std::fs::write(&tmp, "garbage without colon\nmore garbage").unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp);
assert!(parsed.is_ok()); std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_all_package_sources_have_display() {
let sources = vec![
X86PackageSource::Conan,
X86PackageSource::Vcpkg,
X86PackageSource::Spack,
X86PackageSource::PkgConfig,
X86PackageSource::CMakeFindPackage,
X86PackageSource::SystemApt,
X86PackageSource::SystemYum,
X86PackageSource::SystemDnf,
X86PackageSource::SystemPacman,
X86PackageSource::SystemBrew,
X86PackageSource::SystemZypper,
X86PackageSource::SystemApk,
X86PackageSource::SystemEmerge,
X86PackageSource::SystemXbps,
X86PackageSource::Custom,
];
for source in &sources {
let s = format!("{}", source);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_microarch_levels_have_march() {
let levels = vec![
X86MicroarchLevel::X86_64,
X86MicroarchLevel::X86_64_V2,
X86MicroarchLevel::X86_64_V3,
X86MicroarchLevel::X86_64_V4,
X86MicroarchLevel::I686,
X86MicroarchLevel::Pentium4,
X86MicroarchLevel::Prescott,
X86MicroarchLevel::Core2,
X86MicroarchLevel::Nehalem,
X86MicroarchLevel::SandyBridge,
X86MicroarchLevel::Haswell,
X86MicroarchLevel::Skylake,
X86MicroarchLevel::Icelake,
X86MicroarchLevel::AlderLake,
];
for level in &levels {
assert!(!level.march_flag().is_empty());
assert!(!level.feature_flags().is_empty());
}
}
#[test]
fn test_conan_all_generators() {
let generators = vec![
X86ConanGenerator::CMake,
X86ConanGenerator::CMakeDeps,
X86ConanGenerator::CMakeToolchain,
X86ConanGenerator::Xcode,
X86ConanGenerator::Premake,
X86ConanGenerator::Meson,
X86ConanGenerator::Bazel,
X86ConanGenerator::PkgConfig,
X86ConanGenerator::Json,
X86ConanGenerator::TextFiles,
X86ConanGenerator::CompileCommands,
X86ConanGenerator::MSBuildDeps,
X86ConanGenerator::MSBuildToolchain,
X86ConanGenerator::Ninja,
X86ConanGenerator::NinjaMultiConfig,
];
for r#gen in &generators {
assert!(!r#gen.command_arg().is_empty());
}
}
#[test]
fn test_conan_all_build_policies() {
let policies = vec![
X86ConanBuildPolicy::Missing,
X86ConanBuildPolicy::Never,
X86ConanBuildPolicy::Outdated,
X86ConanBuildPolicy::Cascade,
];
for policy in &policies {
assert!(policy.as_flag().starts_with("--build="));
}
}
#[test]
fn test_conan_with_remote() {
let conan = X86ConanSupport::new().with_remote("https://artifactory.example.com");
assert_eq!(conan.remote, Some("https://artifactory.example.com".into()));
}
#[test]
fn test_conan_add_generator_dedup() {
let mut conan = X86ConanSupport::new();
let initial = conan.generators.len();
conan.add_generator(X86ConanGenerator::CMakeDeps);
conan.add_generator(X86ConanGenerator::CMakeDeps);
assert_eq!(conan.generators.len(), initial);
}
#[test]
fn test_conan_install_command() {
let mut conan = X86ConanSupport::new();
conan.add_requirement("zlib", "1.3.1");
let cmd = conan.conan_install_command();
assert!(cmd.contains(&"install".to_string()));
assert!(cmd.iter().any(|a| a.contains("build")));
}
#[test]
fn test_conan_detect_profile() {
let profile = X86ConanSupport::detect_profile();
assert!(!profile.is_empty());
}
#[test]
fn test_conan_default() {
let conan = X86ConanSupport::default();
assert_eq!(conan.microarch, X86MicroarchLevel::X86_64);
}
#[test]
fn test_vcpkg_add_dependency_full() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_dependency(X86VcpkgDependency {
name: "protobuf".into(),
version_gt: Some("3.21".into()),
features: vec!["zlib".into()],
platform: Some("linux".into()),
default_features: Some(true),
host: Some(false),
});
assert_eq!(vcpkg.manifest.dependencies.len(), 1);
let dep = &vcpkg.manifest.dependencies[0];
assert_eq!(dep.version_gt, Some("3.21".into()));
assert_eq!(dep.features, vec!["zlib"]);
}
#[test]
fn test_vcpkg_add_overlay() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_overlay(&PathBuf::from("/custom/ports"));
assert_eq!(vcpkg.overlays.len(), 1);
}
#[test]
fn test_vcpkg_add_registry() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_registry(X86VcpkgRegistry {
kind: "git".into(),
baseline: "abc123".into(),
repository: "https://github.com/example/vcpkg-registry".into(),
packages: vec!["my-lib".into()],
});
assert_eq!(vcpkg.registries.len(), 1);
}
#[test]
fn test_vcpkg_manifest_with_registries() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_registry(X86VcpkgRegistry {
kind: "git".into(),
baseline: "def456".into(),
repository: "https://github.com/example/registry".into(),
packages: vec!["custom-lib".into()],
});
let manifest = vcpkg.generate_manifest();
let config = manifest.get("vcpkg-configuration");
assert!(config.is_some());
}
#[test]
fn test_vcpkg_manifest_with_features() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.manifest.features.insert(
"async".into(),
X86VcpkgFeature {
description: "Async support".into(),
dependencies: vec![X86VcpkgDependency {
name: "boost-asio".into(),
version_gt: None,
features: vec![],
platform: None,
default_features: None,
host: None,
}],
},
);
vcpkg.manifest.default_features.push("async".into());
let manifest = vcpkg.generate_manifest();
assert!(manifest.get("features").is_some());
assert!(manifest.get("default-features").is_some());
}
#[test]
fn test_vcpkg_install_command() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_simple_dep("boost");
vcpkg.add_simple_dep("fmt");
let cmd = vcpkg.install_command();
assert!(cmd.contains(&"boost".to_string()));
assert!(cmd.contains(&"fmt".to_string()));
assert!(cmd.iter().any(|a| a.contains("x64-linux")));
}
#[test]
fn test_vcpkg_install_command_with_overlays() {
let mut vcpkg = X86VcpkgSupport::new();
vcpkg.add_overlay(&PathBuf::from("/my/ports"));
vcpkg.add_simple_dep("my-lib");
let cmd = vcpkg.install_command();
assert!(cmd.iter().any(|a| a.contains("overlay-ports")));
}
#[test]
fn test_spack_add_compiler() {
let mut spack = X86SpackSupport::new();
spack.add_compiler(X86SpackCompiler {
name: "gcc".into(),
version: "13.2.0".into(),
cc_path: PathBuf::from("/usr/bin/gcc"),
cxx_path: PathBuf::from("/usr/bin/g++"),
f77_path: Some(PathBuf::from("/usr/bin/gfortran")),
f90_path: Some(PathBuf::from("/usr/bin/gfortran")),
target: "x86_64".into(),
});
assert_eq!(spack.compilers.len(), 2);
}
#[test]
fn test_spack_with_spec() {
let spack = X86SpackSupport::new().with_spec("hdf5@1.14.0+mpi");
assert_eq!(spack.spec, "hdf5@1.14.0+mpi");
}
#[test]
fn test_spack_with_variant() {
let spack = X86SpackSupport::new()
.with_variant("mpi", "True")
.with_variant("openmp", "False");
assert_eq!(spack.variants.get("mpi").unwrap(), "True");
assert_eq!(spack.variants.get("openmp").unwrap(), "False");
}
#[test]
fn test_spack_class_name_generation() {
let spack = X86SpackSupport::new();
let py = spack.generate_package_py("my-awesome-lib", "1.0", "http://example.com");
assert!(py.contains("class MyAwesomeLib"));
}
#[test]
fn test_spack_resolve_spec_with_compiler() {
let mut spack = X86SpackSupport::new();
spack.add_compiler(X86SpackCompiler {
name: "gcc".into(),
version: "13.2.0".into(),
cc_path: PathBuf::from("/usr/bin/gcc"),
cxx_path: PathBuf::from("/usr/bin/g++"),
f77_path: None,
f90_path: None,
target: "x86_64".into(),
});
let result = spack.resolve_spec("zlib@1.3.1%gcc@13.2.0");
assert!(result.is_ok());
let resolved = result.unwrap();
assert_eq!(resolved.compiler_name, "gcc");
assert_eq!(resolved.compiler_version, "13.2.0");
}
#[test]
fn test_spack_generate_package_py_avx512() {
let spack = X86SpackSupport::for_x86_64_v4();
let py = spack.generate_package_py("hpc-lib", "2.0", "http://example.com/hpc-lib.tar.gz");
assert!(py.contains("avx512"));
assert!(py.contains("avx2"));
}
#[test]
fn test_pkg_config_resolve_nested_variables() {
let mut vars = HashMap::new();
vars.insert("prefix".to_string(), "/opt/mypkg".to_string());
vars.insert("exec_prefix".to_string(), "${prefix}".to_string());
vars.insert("libdir".to_string(), "${exec_prefix}/lib".to_string());
vars.insert("includedir".to_string(), "${prefix}/include".to_string());
assert_eq!(
X86PkgConfig::resolve_variables("${libdir}", &vars),
"/opt/mypkg/lib"
);
assert_eq!(
X86PkgConfig::resolve_variables("${includedir}", &vars),
"/opt/mypkg/include"
);
}
#[test]
fn test_pkg_config_requires_private_parsing() {
let pc_content = r#"
Name: testlib
Description: Test
Version: 1.0
Requires.private: zlib bzip2
Libs: -ltest
Cflags: -I/usr/include
"#;
let tmp = std::env::temp_dir().join("test_private_reqs.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.requires_private, vec!["zlib", "bzip2"]);
assert!(parsed.requires.is_empty());
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_url_parsing() {
let pc_content = r#"
Name: testlib
Description: Test
Version: 1.0
URL: https://example.com
Libs: -ltest
Cflags: -I/usr/include
"#;
let tmp = std::env::temp_dir().join("test_url.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.url, Some("https://example.com".to_string()));
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_libs_private_parsing() {
let pc_content = r#"
Name: staticlib
Description: Static library
Version: 2.0
Libs: -L/usr/lib -lmylib
Libs.private: -lpthread -ldl
Cflags: -I/usr/include
"#;
let tmp = std::env::temp_dir().join("test_libs_private.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.libs_private, vec!["-lpthread", "-ldl"]);
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_conflicts_parsing() {
let pc_content = r#"
Name: newlib
Description: New library
Version: 1.0
Conflicts: oldlib
Libs: -lnewlib
Cflags: -I/usr/include
"#;
let tmp = std::env::temp_dir().join("test_conflicts.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let parsed = pc.parse_pc_file(&tmp).unwrap();
assert_eq!(parsed.conflicts, vec!["oldlib"]);
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_find_by_path() {
let pc_content = r#"
Name: explicitlib
Description: Explicit path
Version: 3.0
Libs: -lexplicit
Cflags: -I/opt/include
"#;
let tmp = std::env::temp_dir().join("explicit.pc");
std::fs::write(&tmp, pc_content).unwrap();
let mut pc = X86PkgConfig::new();
let result = pc.find_by_path(&tmp);
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.name, "explicitlib");
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pkg_config_version_edge_cases() {
assert!(X86PkgConfig::version_satisfies("1.0.0", ">=1.0"));
assert!(X86PkgConfig::version_satisfies("2.3.1", ">=2.3"));
assert!(!X86PkgConfig::version_satisfies("2.2.9", ">=2.3"));
assert_eq!(
X86PkgConfig::compare_versions("1.10", "1.2"),
std::cmp::Ordering::Greater
);
assert_eq!(
X86PkgConfig::compare_versions("1.2.10", "1.2.9"),
std::cmp::Ordering::Greater
);
}
#[test]
fn test_cmake_find_package_all_known() {
let cfp = X86CMakeFindPackage::new();
let known = [
"Boost",
"OpenSSL",
"ZLIB",
"Threads",
"OpenMP",
"MPI",
"Python",
"BLAS",
"LAPACK",
"fmt",
"spdlog",
"nlohmann_json",
"GTest",
"benchmark",
"Eigen3",
"Protobuf",
"OpenCV",
];
for name in &known {
let result = cfp.find_package(name);
assert!(result.is_ok(), "Failed to resolve {}", name);
}
}
#[test]
fn test_cmake_find_package_clear_cache() {
let mut cfp = X86CMakeFindPackage::new();
cfp.cache_hits = 42;
cfp.clear_cache();
assert_eq!(cfp.cache_hits, 0);
}
#[test]
fn test_cmake_find_module_contains_components() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_find_module("Boost");
let cmake = result.unwrap();
assert!(cmake.contains("filesystem"));
assert!(cmake.contains("thread"));
assert!(cmake.contains("program_options"));
}
#[test]
fn test_cmake_config_contains_targets() {
let cfp = X86CMakeFindPackage::new();
let result = cfp.generate_config_file("GTest");
let cmake = result.unwrap();
assert!(cmake.contains("GTest::gtest"));
assert!(cmake.contains("GTest::gmock"));
}
#[test]
fn test_cmake_find_package_all_components_listed() {
let cfp = X86CMakeFindPackage::new();
let components = cfp.list_components("GTest");
assert_eq!(components.len(), 4);
assert!(components.contains(&"gtest".to_string()));
assert!(components.contains(&"gmock".to_string()));
}
#[test]
fn test_system_pm_all_have_commands() {
let spm = X86SystemPackageManager::new();
assert!(spm.install_cmd.is_some());
assert!(spm.search_cmd.is_some());
assert!(spm.remove_cmd.is_some());
assert!(spm.list_cmd.is_some());
}
#[test]
fn test_system_pm_resolve_cached() {
let mut spm = X86SystemPackageManager::new();
spm.package_cache.insert(
"cached-lib".into(),
X86SystemPackage {
name: "cached-lib".into(),
version: "2.0".into(),
provides_dev: true,
include_dir: PathBuf::from("/custom/include"),
lib_dir: PathBuf::from("/custom/lib"),
arch: "x86_64".into(),
},
);
let result = spm.resolve("cached-lib");
assert!(result.is_ok());
let pkg = result.unwrap();
assert_eq!(pkg.version, "2.0");
}
#[test]
fn test_system_pm_find_executable() {
assert!(X86SystemPM::find_executable("sh"));
assert!(!X86SystemPM::find_executable("nonexistent_binary_xyz123"));
}
#[test]
fn test_dependency_locking_default_file() {
let lock = X86DependencyLocking::default();
assert_eq!(lock.lock_file_path, PathBuf::from("x86-deps.lock"));
}
#[test]
fn test_dependency_locking_complex_lock_file() {
let lock = X86DependencyLocking::new();
let mut packages = HashMap::new();
packages.insert(
"pkg_a".into(),
X86ResolvedPackage {
name: "pkg_a".into(),
version: "1.0".into(),
description: "A".into(),
homepage: Some("https://a.example.com".into()),
license: Some("Apache-2.0".into()),
include_dirs: vec![PathBuf::from("/usr/include/a")],
library_dirs: vec![PathBuf::from("/usr/lib")],
libraries: vec!["a".into()],
compile_flags: vec!["-DA_VERSION=1".into()],
link_flags: vec!["-la".into()],
defines: {
let mut d = HashMap::new();
d.insert("A_ENABLED".into(), "1".into());
d
},
dependencies: vec!["pkg_b".into(), "pkg_c".into()],
is_x86_optimized: true,
microarch_requirement: Some(X86MicroarchLevel::Haswell),
source: X86PackageSource::Spack,
},
);
packages.insert(
"pkg_b".into(),
X86ResolvedPackage {
name: "pkg_b".into(),
version: "2.3.1".into(),
description: "B".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::Vcpkg,
},
);
let tmp = std::env::temp_dir().join("complex_lock.json");
lock.write_lock_file(&packages, &tmp).unwrap();
let content = std::fs::read_to_string(&tmp).unwrap();
assert!(content.contains("pkg_a"));
assert!(content.contains("pkg_b"));
assert!(content.contains("A_ENABLED"));
assert!(content.contains("Apache-2.0"));
let mut lock2 = X86DependencyLocking::new();
lock2.parse_lock_file(&tmp).unwrap();
assert_eq!(lock2.locked.len(), 2);
assert_eq!(lock2.locked["pkg_a"].dependencies, vec!["pkg_b", "pkg_c"]);
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_dependency_locking_source_date_epoch() {
let epoch = X86DependencyLocking::generate_source_date_epoch();
assert_eq!(epoch, "0");
}
#[test]
fn test_dependency_locking_verify_empty_integrity() {
let mut lock = X86DependencyLocking::new();
lock.locked.insert(
"no-hash".into(),
X86LockedDependency {
name: "no-hash".into(),
version: "1.0".into(),
source: "apt".into(),
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
integrity: String::new(),
is_x86_optimized: false,
microarch_requirement: None,
},
);
assert!(lock.verify_hash("no-hash", "anything").is_ok());
}
#[test]
fn test_dependency_locking_non_exact_pinning() {
let mut lock = X86DependencyLocking::new();
lock.exact_pinning = false;
lock.pin_dependency("boost", "1.84.0");
}
#[test]
fn test_x86_pm_all_target_variants() {
let variants = vec![
X86PackageManagers::for_i686_linux(),
X86PackageManagers::for_x86_64_avx2(),
X86PackageManagers::for_x86_64_avx512(),
X86PackageManagers::for_x86_64_windows(),
X86PackageManagers::for_x86_64_macos(),
];
for mgr in &variants {
assert!(!mgr.target_triple.is_empty());
assert!(mgr.cache.is_empty());
}
}
#[test]
fn test_x86_pm_cache_persistence() {
let mut mgr = X86PackageManagers::new();
mgr.cache.insert(
"test".into(),
X86ResolvedPackage {
name: "test".into(),
version: "1.0".into(),
description: "test".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::Custom,
},
);
assert_eq!(mgr.cache.len(), 1);
mgr.clear_cache();
assert_eq!(mgr.cache.len(), 0);
}
#[test]
fn test_x86_pm_resolve_cached() {
let mut mgr = X86PackageManagers::new();
let cached = X86ResolvedPackage {
name: "cached".into(),
version: "1.0".into(),
description: "cached".into(),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: false,
microarch_requirement: None,
source: X86PackageSource::Custom,
};
mgr.cache.insert("cached".into(), cached.clone());
let result = mgr.resolve("cached");
assert!(result.is_ok());
assert_eq!(result.unwrap().name, "cached");
}
#[test]
fn test_multiple_dep_resolution_across_managers() {
let mut mgr = X86PackageManagers::new();
let packages = vec!["boost", "fmt", "zlib", "openssl", "gtest", "benchmark"];
let resolution = mgr.resolve_all(&packages);
assert!(resolution.total_time_ms < 5000);
}
#[test]
fn test_conan_generate_all_profiles() {
let profiles = X86ConanSupport::generate_standard_profiles();
for (name, content) in &profiles {
assert!(content.contains(name));
assert!(content.contains("[settings]"));
assert!(content.contains("[conf]"));
}
}
#[test]
fn test_spack_full_pipeline_all_targets() {
let targets = vec![
("x86_64", X86SpackSupport::new()),
("i686", X86SpackSupport::for_i686()),
("x86_64_v3", X86SpackSupport::for_x86_64_v3()),
("x86_64_v4", X86SpackSupport::for_x86_64_v4()),
];
for (target, spack) in &targets {
let py = spack.generate_package_py("test", "1.0", "http://example.com");
assert!(!py.is_empty());
let yaml = spack.generate_compilers_yaml();
assert!(yaml.contains("target:"));
}
}
#[test]
fn test_dependency_graph_complex_topology() {
let mut graph = X86DependencyGraph::default();
for name in &["A", "B", "C", "D"] {
graph.add_node(X86DepGraphNode {
name: name.to_string(),
version: "1.0".into(),
is_direct: *name == "A",
is_x86_native: true,
source: X86PackageSource::Conan,
});
}
graph.add_edge("A", "B", "requires");
graph.add_edge("A", "C", "requires");
graph.add_edge("B", "D", "requires");
graph.add_edge("C", "D", "requires");
let sorted = graph.compute_topological_order().unwrap();
assert_eq!(sorted[0], "A");
assert_eq!(sorted[3], "D");
}
#[test]
fn test_conan_v1_cmake_integration() {
let mut conan = X86ConanSupport::new();
conan.conan_v2 = false;
let cmake = conan.generate_cmake_integration();
assert!(cmake.contains("conanbuildinfo.cmake"));
assert!(cmake.contains("conan_basic_setup"));
}
#[test]
fn test_conan_v2_cmake_integration() {
let mut conan = X86ConanSupport::new();
conan.conan_v2 = true;
let cmake = conan.generate_cmake_integration();
assert!(cmake.contains("conan_toolchain.cmake"));
assert!(!cmake.contains("conan_basic_setup"));
}
#[test]
fn test_vcpkg_static_triplet_for_all_platforms() {
let platforms = vec![
(X86VcpkgSupport::new().with_static_linking(), "static"),
(
X86VcpkgSupport::for_x64_windows().with_static_linking(),
"static",
),
];
for (vcpkg, expected) in &platforms {
assert!(vcpkg.triplet.contains(expected));
}
}
#[test]
fn test_pkg_config_find_nonexistent_file() {
let mut pc = X86PkgConfig::new();
let result = pc.find("completely_nonexistent_library_xyz");
assert!(result.is_err());
}
#[test]
fn test_cmake_find_both_modes() {
let cfp = X86CMakeFindPackage::new();
assert!(cfp.find_package("Boost").is_ok());
assert!(cfp.find_package("Threads").is_ok());
assert!(cfp.find_package("fmt").is_ok());
assert!(cfp.find_package("GTest").is_ok());
}
#[test]
fn test_large_lock_file_generation() {
let lock = X86DependencyLocking::new();
let mut packages = HashMap::new();
for i in 0..50 {
packages.insert(
format!("pkg_{}", i),
X86ResolvedPackage {
name: format!("pkg_{}", i),
version: format!("{}.0", i),
description: format!("Package {}", i),
homepage: None,
license: None,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
compile_flags: vec![],
link_flags: vec![],
defines: HashMap::new(),
dependencies: vec![],
is_x86_optimized: i % 2 == 0,
microarch_requirement: if i % 3 == 0 {
Some(X86MicroarchLevel::X86_64)
} else {
None
},
source: X86PackageSource::Conan,
},
);
}
let tmp = std::env::temp_dir().join("large_lock.json");
lock.write_lock_file(&packages, &tmp).unwrap();
let content = std::fs::read_to_string(&tmp).unwrap();
assert!(content.contains("pkg_0"));
assert!(content.contains("pkg_49"));
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_deep_requires_chain() {
let mut pc = X86PkgConfig::new();
let dir = std::env::temp_dir().join("pkgconfig_test");
std::fs::create_dir_all(&dir).unwrap();
let a_pc = r#"
Name: chain_a
Description: Chain A
Version: 1.0
Requires: chain_b
Libs: -la
Cflags: -I/usr/include
"#;
let b_pc = r#"
Name: chain_b
Description: Chain B
Version: 1.0
Libs: -lb
Cflags: -I/usr/include
"#;
std::fs::write(dir.join("chain_a.pc"), a_pc).unwrap();
std::fs::write(dir.join("chain_b.pc"), b_pc).unwrap();
pc.add_path(&dir);
let result = pc.resolve_with_requires("chain_a", 0);
match result {
Ok(packages) => assert!(!packages.is_empty()),
Err(_) => {} }
std::fs::remove_dir_all(&dir).ok();
}
}