use std::cmp::Reverse;
use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use crate::clang::{
CLangStandard, ClangOptions,
clang_target::{ClangTargetInfo, TargetCodeGenInfo},
};
use crate::x86::{
X86CallingConvention, X86FrameInfo, X86InstrInfo, X86RegisterInfo,
X86Subtarget, X86TargetMachine,
};
#[derive(Debug, Clone)]
pub struct X86BuildSystems {
pub backend: X86BuildBackend,
pub cpu_target: String,
pub opt_level: X86BuildOptLevel,
pub debug_info: bool,
pub lto: bool,
pub parallel_jobs: u32,
pub c_flags: Vec<String>,
pub cxx_flags: Vec<String>,
pub ld_flags: Vec<String>,
pub build_dir: PathBuf,
pub source_dir: PathBuf,
pub install_prefix: PathBuf,
pub verbose: bool,
pub cmake: X86CMakeSupport,
pub make: X86MakeSupport,
pub ninja: X86NinjaSupport,
pub bazel: X86BazelSupport,
pub meson: X86MesonSupport,
pub autotools: X86AutotoolsSupport,
pub xmake: X86XMakeSupport,
pub build_graph: X86BuildGraph,
}
impl X86BuildSystems {
pub fn new(backend: X86BuildBackend) -> Self {
Self {
backend,
cpu_target: "x86_64".to_string(),
opt_level: X86BuildOptLevel::O2,
debug_info: false,
lto: false,
parallel_jobs: std::thread::available_parallelism().map(|n| n.get() as u32).unwrap_or(4),
c_flags: vec!["-march=x86-64".to_string(), "-mtune=generic".to_string()],
cxx_flags: vec!["-march=x86-64".to_string(), "-mtune=generic".to_string()],
ld_flags: vec![],
build_dir: PathBuf::from("build"),
source_dir: PathBuf::from("."),
install_prefix: PathBuf::from("/usr/local"),
verbose: false,
cmake: X86CMakeSupport::default(),
make: X86MakeSupport::default(),
ninja: X86NinjaSupport::default(),
bazel: X86BazelSupport::default(),
meson: X86MesonSupport::default(),
autotools: X86AutotoolsSupport::default(),
xmake: X86XMakeSupport::default(),
build_graph: X86BuildGraph::default(),
}
}
pub fn for_i686(backend: X86BuildBackend) -> Self {
Self {
cpu_target: "i686".to_string(),
c_flags: vec!["-march=i686".to_string(), "-m32".to_string()],
cxx_flags: vec!["-march=i686".to_string(), "-m32".to_string()],
..Self::new(backend)
}
}
pub fn for_pentium4(backend: X86BuildBackend) -> Self {
Self {
cpu_target: "pentium4".to_string(),
c_flags: vec!["-march=pentium4".to_string(), "-m32".to_string()],
cxx_flags: vec!["-march=pentium4".to_string(), "-m32".to_string()],
..Self::new(backend)
}
}
pub fn for_x86_64_avx2(backend: X86BuildBackend) -> Self {
Self {
cpu_target: "x86-64-v3".to_string(),
c_flags: vec!["-march=x86-64-v3".to_string(), "-mtune=generic".to_string()],
cxx_flags: vec!["-march=x86-64-v3".to_string(), "-mtune=generic".to_string()],
..Self::new(backend)
}
}
pub fn for_x86_64_avx512(backend: X86BuildBackend) -> Self {
Self {
cpu_target: "x86-64-v4".to_string(),
c_flags: vec!["-march=x86-64-v4".to_string(), "-mtune=generic".to_string()],
cxx_flags: vec!["-march=x86-64-v4".to_string(), "-mtune=generic".to_string()],
..Self::new(backend)
}
}
pub fn with_opt_level(mut self, level: X86BuildOptLevel) -> Self {
self.opt_level = level;
self
}
pub fn with_debug_info(mut self, enable: bool) -> Self {
self.debug_info = enable;
self
}
pub fn with_lto(mut self, enable: bool) -> Self {
self.lto = enable;
self
}
pub fn with_parallel_jobs(mut self, jobs: u32) -> Self {
self.parallel_jobs = jobs;
self
}
pub fn with_c_flag(mut self, flag: &str) -> Self {
self.c_flags.push(flag.to_string());
self
}
pub fn with_cxx_flag(mut self, flag: &str) -> Self {
self.cxx_flags.push(flag.to_string());
self
}
pub fn with_ld_flag(mut self, flag: &str) -> Self {
self.ld_flags.push(flag.to_string());
self
}
pub fn compiler_flags(&self) -> X86CompilerFlags {
let mut common = Vec::new();
common.push(format!("-O{}", self.opt_level.as_str()));
if self.debug_info {
common.push("-g".to_string());
}
if self.lto {
common.push("-flto".to_string());
}
common.push(format!("-march={}", self.cpu_target));
common.push("-Wall".to_string());
common.push("-Wextra".to_string());
X86CompilerFlags {
c_flags: {
let mut f = common.clone();
f.extend(self.c_flags.clone());
f
},
cxx_flags: {
let mut f = common.clone();
f.push("-std=c++17".to_string());
f.extend(self.cxx_flags.clone());
f
},
ld_flags: self.ld_flags.clone(),
}
}
pub fn generate(&self) -> io::Result<X86BuildOutput> {
match self.backend {
X86BuildBackend::CMake => self.cmake.generate(&self),
X86BuildBackend::Make => self.make.generate(&self),
X86BuildBackend::Ninja => self.ninja.generate(&self),
X86BuildBackend::Meson => self.meson.generate(&self),
X86BuildBackend::Bazel => self.bazel.generate(&self),
X86BuildBackend::Autotools => self.autotools.generate(&self),
X86BuildBackend::XMake => self.xmake.generate(&self),
}
}
pub fn analyze_build_graph(&mut self, targets: &[X86BuildTarget]) {
self.build_graph = X86BuildGraph::from_targets(targets);
}
}
impl Default for X86BuildSystems {
fn default() -> Self {
Self::new(X86BuildBackend::CMake)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86BuildBackend {
CMake,
Make,
Ninja,
Meson,
Bazel,
Autotools,
XMake,
}
impl X86BuildBackend {
pub fn as_str(&self) -> &'static str {
match self {
Self::CMake => "cmake",
Self::Make => "make",
Self::Ninja => "ninja",
Self::Meson => "meson",
Self::Bazel => "bazel",
Self::Autotools => "autotools",
Self::XMake => "xmake",
}
}
pub fn config_file(&self) -> &'static str {
match self {
Self::CMake => "CMakeLists.txt",
Self::Make => "Makefile",
Self::Ninja => "build.ninja",
Self::Meson => "meson.build",
Self::Bazel => "BUILD",
Self::Autotools => "configure.ac",
Self::XMake => "xmake.lua",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"cmake" => Some(Self::CMake),
"make" | "gnu make" | "gmake" => Some(Self::Make),
"ninja" => Some(Self::Ninja),
"meson" => Some(Self::Meson),
"bazel" => Some(Self::Bazel),
"autotools" | "autoconf" => Some(Self::Autotools),
"xmake" => Some(Self::XMake),
_ => None,
}
}
}
impl fmt::Display for X86BuildBackend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86BuildOptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
Ofast,
}
impl X86BuildOptLevel {
pub fn as_str(&self) -> &'static str {
match self {
Self::O0 => "0",
Self::O1 => "1",
Self::O2 => "2",
Self::O3 => "3",
Self::Os => "s",
Self::Oz => "z",
Self::Ofast => "fast",
}
}
pub fn flag(&self) -> String {
format!("-O{}", self.as_str())
}
}
impl Default for X86BuildOptLevel {
fn default() -> Self {
X86BuildOptLevel::O2
}
}
#[derive(Debug, Clone)]
pub struct X86CompilerFlags {
pub c_flags: Vec<String>,
pub cxx_flags: Vec<String>,
pub ld_flags: Vec<String>,
}
impl X86CompilerFlags {
pub fn join_c_flags(&self) -> String {
self.c_flags.join(" ")
}
pub fn join_cxx_flags(&self) -> String {
self.cxx_flags.join(" ")
}
pub fn join_ld_flags(&self) -> String {
self.ld_flags.join(" ")
}
}
#[derive(Debug, Clone)]
pub struct X86BuildTarget {
pub name: String,
pub target_type: String,
pub sources: Vec<String>,
pub dependencies: Vec<String>,
pub compile_time_estimate_ms: u64,
}
#[derive(Debug, Clone)]
pub struct X86BuildOutput {
pub files_generated: Vec<String>,
pub build_commands: Vec<String>,
pub warnings: Vec<String>,
pub estimated_build_time_ms: u64,
}
#[derive(Debug, Clone)]
pub struct X86CMakeSupport {
pub cmake_minimum_version: String,
pub cxx_standard: X86CxxStandard,
pub compile_commands: bool,
pub build_type: X86CMakeBuildType,
pub projects: Vec<X86CMakeProject>,
pub dependencies: Vec<X86CMakeDependency>,
pub toolchain: Option<X86CMakeToolchain>,
pub presets: Option<X86CMakePresets>,
pub ctest: Option<X86CTestConfig>,
pub cpack: Option<X86CPackConfig>,
pub install_rules: Vec<X86CMakeInstallRule>,
pub external_projects: Vec<X86CMakeExternalProject>,
pub fetch_content: Vec<X86CMakeFetchContent>,
pub cpm_enabled: bool,
pub cpm_packages: Vec<X86CMakeCPMPackage>,
}
impl X86CMakeSupport {
pub fn new() -> Self {
Self {
cmake_minimum_version: "3.20".to_string(),
cxx_standard: X86CxxStandard::CXX17,
compile_commands: true,
build_type: X86CMakeBuildType::Release,
projects: Vec::new(),
dependencies: Vec::new(),
toolchain: None,
presets: None,
ctest: None,
cpack: None,
install_rules: Vec::new(),
external_projects: Vec::new(),
fetch_content: Vec::new(),
cpm_enabled: false,
cpm_packages: Vec::new(),
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: Vec::new(),
build_commands: Vec::new(),
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
let cmake_content = self.generate_cmake_lists();
output.files_generated.push("CMakeLists.txt".to_string());
if let Some(ref tc) = self.toolchain {
let tc_content = tc.generate();
output.files_generated.push("x86-toolchain.cmake".to_string());
}
if let Some(ref p) = self.presets {
let presets_json = p.generate_cmake_presets();
output.files_generated.push("CMakePresets.json".to_string());
if p.user_presets_enabled {
output.files_generated.push("CMakeUserPresets.json".to_string());
}
}
if let Some(ref ct) = self.ctest {
output.files_generated.push("CTestConfig.cmake".to_string());
output.files_generated.push("CTestCustom.cmake".to_string());
}
if let Some(ref cp) = self.cpack {
output.files_generated.push("CPackConfig.cmake".to_string());
}
let build_dir = build_systems.build_dir.display().to_string();
output.build_commands.push(format!(
"cmake -S . -B {} -DCMAKE_BUILD_TYPE={}",
build_dir,
self.build_type.as_str()
));
if self.compile_commands {
output.build_commands.push(format!(
"cmake -S . -B {} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
build_dir
));
}
output.build_commands.push(format!(
"cmake --build {} --parallel {}",
build_dir, build_systems.parallel_jobs
));
output.build_commands.push(format!(
"cmake --install {} --prefix {}",
build_dir,
build_systems.install_prefix.display()
));
Ok(output)
}
pub fn generate_cmake_lists(&self) -> String {
let mut cmake = String::new();
cmake.push_str(&format!(
"# Auto-generated CMakeLists.txt for X86 Clang build\n\
# Target: x86_64 / i686\n\
cmake_minimum_required(VERSION {})\n\n",
self.cmake_minimum_version
));
if let Some(proj) = self.projects.first() {
cmake.push_str(&proj.generate_declaration());
cmake.push('\n');
}
cmake.push_str(&format!(
"set(CMAKE_CXX_STANDARD {})\n\
set(CMAKE_CXX_STANDARD_REQUIRED ON)\n\
set(CMAKE_CXX_EXTENSIONS OFF)\n\n",
self.cxx_standard.as_number()
));
if self.compile_commands {
cmake.push_str("set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n");
}
cmake.push_str(&self.generate_x86_flags());
for project in &self.projects {
for option in &project.options {
cmake.push_str(&option.generate());
cmake.push('\n');
}
}
for dep in &self.dependencies {
cmake.push_str(&dep.generate());
cmake.push('\n');
}
for fc in &self.fetch_content {
cmake.push_str(&fc.generate());
cmake.push('\n');
}
if self.cpm_enabled {
cmake.push_str(&self.generate_cpm_setup());
for pkg in &self.cpm_packages {
cmake.push_str(&pkg.generate());
cmake.push('\n');
}
}
for ep in &self.external_projects {
cmake.push_str(&ep.generate());
cmake.push('\n');
}
for project in &self.projects {
for target in &project.targets {
cmake.push_str(&target.generate_cmake());
cmake.push('\n');
}
}
for project in &self.projects {
for subdir in &project.subdirectories {
cmake.push_str(&format!("add_subdirectory({})\n", subdir));
}
}
if let Some(ref ct) = self.ctest {
cmake.push_str(&ct.generate());
}
for rule in &self.install_rules {
cmake.push_str(&rule.generate());
cmake.push('\n');
}
if let Some(ref cp) = self.cpack {
cmake.push_str(&cp.generate());
}
cmake
}
fn generate_x86_flags(&self) -> String {
let mut flags = String::new();
flags.push_str("# X86-specific compiler settings\n");
flags.push_str("if(CMAKE_SYSTEM_PROCESSOR MATCHES \"(x86_64|AMD64|amd64)\")\n");
flags.push_str(" add_compile_options(-m64)\n");
flags.push_str(" set(X86_ARCH \"x86_64\")\n");
flags.push_str("elseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"(i.86|i386|i486|i586|i686)\")\n");
flags.push_str(" add_compile_options(-m32)\n");
flags.push_str(" set(X86_ARCH \"i686\")\n");
flags.push_str("endif()\n\n");
flags.push_str("# Compiler flags for X86 optimization\n");
flags.push_str("set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -O2 -DNDEBUG\")\n");
flags.push_str("set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG\")\n");
flags.push_str("set(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} -O0 -g\")\n");
flags.push_str("set(CMAKE_CXX_FLAGS_DEBUG \"${CMAKE_CXX_FLAGS_DEBUG} -O0 -g\")\n\n");
flags.push_str("# X86 specific compiler flags\n");
flags.push_str("add_compile_options(\n");
flags.push_str(" $<$<COMPILE_LANGUAGE:C>:-Wall -Wextra>\n");
flags.push_str(" $<$<COMPILE_LANGUAGE:CXX>:-Wall -Wextra>\n");
flags.push_str(")\n\n");
flags
}
pub fn add_project(&mut self, project: X86CMakeProject) {
self.projects.push(project);
}
pub fn add_dependency(&mut self, dep: X86CMakeDependency) {
self.dependencies.push(dep);
}
pub fn add_fetch_content(&mut self, fc: X86CMakeFetchContent) {
self.fetch_content.push(fc);
}
pub fn add_cpm_package(&mut self, pkg: X86CMakeCPMPackage) {
self.cpm_enabled = true;
self.cpm_packages.push(pkg);
}
pub fn add_external_project(&mut self, ep: X86CMakeExternalProject) {
self.external_projects.push(ep);
}
pub fn add_install_rule(&mut self, rule: X86CMakeInstallRule) {
self.install_rules.push(rule);
}
pub fn generate_toolchain_x86_64_linux(&self) -> String {
let mut tc = String::new();
tc.push_str("# X86-64 Linux toolchain file\n");
tc.push_str("set(CMAKE_SYSTEM_NAME Linux)\n");
tc.push_str("set(CMAKE_SYSTEM_PROCESSOR x86_64)\n\n");
tc.push_str("set(CMAKE_C_COMPILER clang)\n");
tc.push_str("set(CMAKE_CXX_COMPILER clang++)\n\n");
tc.push_str("set(CMAKE_C_FLAGS \"-m64 -march=x86-64 -mtune=generic\" CACHE STRING \"\")\n");
tc.push_str("set(CMAKE_CXX_FLAGS \"-m64 -march=x86-64 -mtune=generic\" CACHE STRING \"\")\n\n");
tc.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n");
tc.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n");
tc.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n");
tc.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n");
tc
}
fn generate_cpm_setup(&self) -> String {
let mut cpm = String::new();
cpm.push_str("# CPM.cmake package manager\n");
cpm.push_str("if(NOT CPM_SOURCE_CACHE)\n");
cpm.push_str(" set(CPM_SOURCE_CACHE \"${CMAKE_BINARY_DIR}/_deps\")\n");
cpm.push_str("endif()\n");
cpm.push_str("file(DOWNLOAD\n");
cpm.push_str(" https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.5/CPM.cmake\n");
cpm.push_str(" \"${CMAKE_BINARY_DIR}/cmake/CPM.cmake\"\n");
cpm.push_str(" EXPECTED_HASH SHA256=046ebf994a0f5f3b4b0e1c4d7c8e8e7a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8\n");
cpm.push_str(")\n");
cpm.push_str("include(${CMAKE_BINARY_DIR}/cmake/CPM.cmake)\n\n");
cpm
}
}
impl Default for X86CMakeSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CxxStandard {
CXX11,
CXX14,
CXX17,
CXX20,
CXX23,
}
impl X86CxxStandard {
pub fn as_number(&self) -> u32 {
match self {
Self::CXX11 => 11,
Self::CXX14 => 14,
Self::CXX17 => 17,
Self::CXX20 => 20,
Self::CXX23 => 23,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::CXX11 => "cxx_std_11",
Self::CXX14 => "cxx_std_14",
Self::CXX17 => "cxx_std_17",
Self::CXX20 => "cxx_std_20",
Self::CXX23 => "cxx_std_23",
}
}
pub fn flag(&self) -> &'static str {
match self {
Self::CXX11 => "-std=c++11",
Self::CXX14 => "-std=c++14",
Self::CXX17 => "-std=c++17",
Self::CXX20 => "-std=c++20",
Self::CXX23 => "-std=c++23",
}
}
}
impl Default for X86CxxStandard {
fn default() -> Self {
X86CxxStandard::CXX17
}
}
#[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 {
X86CMakeBuildType::Release
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeProject {
pub name: String,
pub version: String,
pub description: String,
pub languages: Vec<String>,
pub cmake_minimum: String,
pub targets: Vec<X86CMakeTarget>,
pub options: Vec<X86CMakeOption>,
pub subdirectories: Vec<String>,
}
impl X86CMakeProject {
pub fn new(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
description: String::new(),
languages: vec!["C".to_string(), "CXX".to_string()],
cmake_minimum: "3.20".to_string(),
targets: Vec::new(),
options: Vec::new(),
subdirectories: Vec::new(),
}
}
pub fn generate_declaration(&self) -> String {
let langs = self.languages.join(" ");
let mut decl = format!(
"project({}\n VERSION {}\n DESCRIPTION \"{}\"\n LANGUAGES {})\n",
self.name, self.version, self.description, langs
);
decl
}
pub fn add_target(&mut self, target: X86CMakeTarget) {
self.targets.push(target);
}
pub fn add_option(&mut self, option: X86CMakeOption) {
self.options.push(option);
}
pub fn add_subdirectory(&mut self, dir: &str) {
self.subdirectories.push(dir.to_string());
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeTarget {
pub name: String,
pub target_type: X86CMakeTargetType,
pub sources: Vec<String>,
pub headers: Vec<String>,
pub private_includes: Vec<String>,
pub public_includes: Vec<String>,
pub interface_includes: Vec<String>,
pub link_libraries: Vec<String>,
pub compile_definitions: Vec<String>,
pub compile_features: Vec<String>,
pub compile_options: Vec<String>,
pub link_options: Vec<String>,
pub properties: HashMap<String, String>,
}
impl X86CMakeTarget {
pub fn new_executable(name: &str) -> Self {
Self {
name: name.to_string(),
target_type: X86CMakeTargetType::Executable,
sources: Vec::new(),
headers: Vec::new(),
private_includes: Vec::new(),
public_includes: Vec::new(),
interface_includes: Vec::new(),
link_libraries: Vec::new(),
compile_definitions: Vec::new(),
compile_features: Vec::new(),
compile_options: Vec::new(),
link_options: Vec::new(),
properties: HashMap::new(),
}
}
pub fn new_static_library(name: &str) -> Self {
let mut t = Self::new_executable(name);
t.target_type = X86CMakeTargetType::StaticLibrary;
t
}
pub fn new_shared_library(name: &str) -> Self {
let mut t = Self::new_executable(name);
t.target_type = X86CMakeTargetType::SharedLibrary;
t
}
pub fn new_interface_library(name: &str) -> Self {
let mut t = Self::new_executable(name);
t.target_type = X86CMakeTargetType::InterfaceLibrary;
t
}
pub fn new_object_library(name: &str) -> Self {
let mut t = Self::new_executable(name);
t.target_type = X86CMakeTargetType::ObjectLibrary;
t
}
pub fn new_custom_target(name: &str) -> Self {
let mut t = Self::new_executable(name);
t.target_type = X86CMakeTargetType::CustomTarget;
t
}
pub fn generate_cmake(&self) -> String {
let mut cmake = String::new();
let target_type_str = self.target_type.as_cmake();
cmake.push_str(&format!("# Target: {}\n", self.name));
cmake.push_str(&format!(
"{}(",
match self.target_type {
X86CMakeTargetType::CustomTarget => "add_custom_target",
_ => target_type_str,
}
));
match self.target_type {
X86CMakeTargetType::CustomTarget => {
cmake.push_str(&format!("{}", self.name));
}
_ => {
cmake.push_str(&format!("{}", self.name));
if self.sources.is_empty() && self.target_type == X86CMakeTargetType::InterfaceLibrary {
} else {
cmake.push_str("\n ");
cmake.push_str(&self.sources.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<_>>().join("\n "));
}
}
}
cmake.push_str(")\n");
if self.target_type == X86CMakeTargetType::InterfaceLibrary {
cmake.push_str(&format!("add_library({} INTERFACE)\n", self.name));
if !self.sources.is_empty() {
cmake.push_str(&format!(
"target_sources({} INTERFACE\n {})\n",
self.name,
self.sources.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<_>>().join("\n ")
));
}
}
if !self.headers.is_empty() {
cmake.push_str(&format!(
"target_sources({} PRIVATE\n FILE_SET HEADERS\n FILES\n {}\n)\n",
self.name,
self.headers.iter().map(|h| format!("\"{}\"", h)).collect::<Vec<_>>().join("\n ")
));
}
if !self.private_includes.is_empty() {
cmake.push_str(&format!(
"target_include_directories({} PRIVATE\n {})\n",
self.name,
self.private_includes.join("\n ")
));
}
if !self.public_includes.is_empty() {
cmake.push_str(&format!(
"target_include_directories({} PUBLIC\n {})\n",
self.name,
self.public_includes.join("\n ")
));
}
if !self.interface_includes.is_empty() {
cmake.push_str(&format!(
"target_include_directories({} INTERFACE\n {})\n",
self.name,
self.interface_includes.join("\n ")
));
}
if !self.link_libraries.is_empty() {
let scope = match self.target_type {
X86CMakeTargetType::InterfaceLibrary => "INTERFACE",
_ => "PRIVATE",
};
cmake.push_str(&format!(
"target_link_libraries({} {}\n {})\n",
self.name,
scope,
self.link_libraries.join("\n ")
));
}
if !self.compile_definitions.is_empty() {
let scope = match self.target_type {
X86CMakeTargetType::InterfaceLibrary => "INTERFACE",
X86CMakeTargetType::SharedLibrary | X86CMakeTargetType::StaticLibrary => "PUBLIC",
_ => "PRIVATE",
};
cmake.push_str(&format!(
"target_compile_definitions({} {}\n {})\n",
self.name,
scope,
self.compile_definitions.join("\n ")
));
}
if !self.compile_features.is_empty() {
cmake.push_str(&format!(
"target_compile_features({} PUBLIC\n {})\n",
self.name,
self.compile_features.join("\n ")
));
}
if !self.compile_options.is_empty() {
cmake.push_str(&format!(
"target_compile_options({} PRIVATE\n {})\n",
self.name,
self.compile_options.join("\n ")
));
}
if !self.link_options.is_empty() {
cmake.push_str(&format!(
"target_link_options({} PRIVATE\n {})\n",
self.name,
self.link_options.join("\n ")
));
}
for (key, value) in &self.properties {
cmake.push_str(&format!(
"set_target_properties({} PROPERTIES\n {} \"{}\")\n",
self.name, key, value
));
}
cmake
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CMakeTargetType {
Executable,
StaticLibrary,
SharedLibrary,
InterfaceLibrary,
ObjectLibrary,
CustomTarget,
}
impl X86CMakeTargetType {
pub fn as_cmake(&self) -> &'static str {
match self {
Self::Executable => "add_executable",
Self::StaticLibrary => "add_library",
Self::SharedLibrary => "add_library",
Self::InterfaceLibrary => "add_library",
Self::ObjectLibrary => "add_library",
Self::CustomTarget => "add_custom_target",
}
}
pub fn kind_str(&self) -> &'static str {
match self {
Self::Executable => "",
Self::StaticLibrary => "STATIC",
Self::SharedLibrary => "SHARED",
Self::InterfaceLibrary => "INTERFACE",
Self::ObjectLibrary => "OBJECT",
Self::CustomTarget => "",
}
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeOption {
pub name: String,
pub description: String,
pub default_value: String,
pub option_type: X86CMakeOptionType,
}
impl X86CMakeOption {
pub fn new_bool(name: &str, description: &str, default: bool) -> Self {
Self {
name: name.to_string(),
description: description.to_string(),
default_value: if default { "ON".to_string() } else { "OFF".to_string() },
option_type: X86CMakeOptionType::Bool,
}
}
pub fn new_string(name: &str, description: &str, default: &str) -> Self {
Self {
name: name.to_string(),
description: description.to_string(),
default_value: default.to_string(),
option_type: X86CMakeOptionType::String,
}
}
pub fn generate(&self) -> String {
match self.option_type {
X86CMakeOptionType::Bool => format!(
"option({} \"{}\" {})\n",
self.name, self.description, self.default_value
),
X86CMakeOptionType::String => format!(
"set({} \"{}\" CACHE STRING \"{}\")\n",
self.name, self.default_value, self.description
),
X86CMakeOptionType::FilePath => format!(
"set({} \"{}\" CACHE FILEPATH \"{}\")\n",
self.name, self.default_value, self.description
),
X86CMakeOptionType::Path => format!(
"set({} \"{}\" CACHE PATH \"{}\")\n",
self.name, self.default_value, self.description
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CMakeOptionType {
Bool,
String,
FilePath,
Path,
}
#[derive(Debug, Clone)]
pub struct X86CMakeDependency {
pub name: String,
pub version: Option<String>,
pub required: bool,
pub components: Vec<String>,
pub config_mode: bool,
}
impl X86CMakeDependency {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
version: None,
required: true,
components: Vec::new(),
config_mode: false,
}
}
pub fn generate(&self) -> String {
let mut args = vec![];
args.push(self.name.clone());
if let Some(ref v) = self.version {
args.push(v.clone());
}
if self.required {
args.push("REQUIRED".to_string());
}
if !self.components.is_empty() {
args.push("COMPONENTS".to_string());
args.extend(self.components.clone());
}
if self.config_mode {
args.push("CONFIG".to_string());
}
format!("find_package({})", args.join(" "))
}
pub fn for_opengl() -> Self {
Self {
name: "OpenGL".to_string(),
version: None,
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_sdl2() -> Self {
Self {
name: "SDL2".to_string(),
version: Some("2.0".to_string()),
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_boost(components: &[&str]) -> Self {
Self {
name: "Boost".to_string(),
version: Some("1.74".to_string()),
required: true,
components: components.iter().map(|c| c.to_string()).collect(),
config_mode: false,
}
}
pub fn for_threads() -> Self {
Self {
name: "Threads".to_string(),
version: None,
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_zlib() -> Self {
Self {
name: "ZLIB".to_string(),
version: None,
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_pkgconfig() -> Self {
Self {
name: "PkgConfig".to_string(),
version: None,
required: false,
components: vec![],
config_mode: false,
}
}
pub fn for_protobuf() -> Self {
Self {
name: "Protobuf".to_string(),
version: None,
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_python() -> Self {
Self {
name: "Python3".to_string(),
version: None,
required: true,
components: vec!["Interpreter".to_string(), "Development".to_string()],
config_mode: false,
}
}
pub fn for_opencv() -> Self {
Self {
name: "OpenCV".to_string(),
version: Some("4.0".to_string()),
required: true,
components: vec![],
config_mode: false,
}
}
pub fn for_curl() -> Self {
Self {
name: "CURL".to_string(),
version: None,
required: true,
components: vec![],
config_mode: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeFetchContent {
pub name: String,
pub git_repository: String,
pub git_tag: String,
pub git_shallow: bool,
pub source_subdir: Option<String>,
pub system: bool,
pub find_package_args: Option<String>,
}
impl X86CMakeFetchContent {
pub fn new(name: &str, repo: &str, tag: &str) -> Self {
Self {
name: name.to_string(),
git_repository: repo.to_string(),
git_tag: tag.to_string(),
git_shallow: true,
source_subdir: None,
system: false,
find_package_args: None,
}
}
pub fn generate(&self) -> String {
let mut fc = String::new();
fc.push_str("include(FetchContent)\n");
fc.push_str(&format!("FetchContent_Declare(\n {}\n", self.name));
fc.push_str(&format!(" GIT_REPOSITORY {}\n", self.git_repository));
fc.push_str(&format!(" GIT_TAG {}\n", self.git_tag));
if self.git_shallow {
fc.push_str(" GIT_SHALLOW TRUE\n");
}
if let Some(ref subdir) = self.source_subdir {
fc.push_str(&format!(" SOURCE_SUBDIR {}\n", subdir));
}
if self.system {
fc.push_str(" SYSTEM\n");
}
fc.push_str(")\n");
if let Some(ref fpa) = self.find_package_args {
fc.push_str(&format!(
"find_package({} QUIET)\nif(NOT {}_FOUND)\n",
self.name, self.name
));
fc.push_str(&format!(" FetchContent_MakeAvailable({})\n", self.name));
fc.push_str("endif()\n");
} else {
fc.push_str(&format!("FetchContent_MakeAvailable({})\n", self.name));
}
fc
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeExternalProject {
pub name: String,
pub url: Option<String>,
pub git_repository: Option<String>,
pub git_tag: Option<String>,
pub source_dir: Option<String>,
pub binary_dir: Option<String>,
pub cmake_args: Vec<String>,
pub build_command: Option<String>,
pub install_command: Option<String>,
pub depends: Vec<String>,
}
impl X86CMakeExternalProject {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
url: None,
git_repository: None,
git_tag: None,
source_dir: None,
binary_dir: None,
cmake_args: Vec::new(),
build_command: None,
install_command: None,
depends: Vec::new(),
}
}
pub fn generate(&self) -> String {
let mut ep = String::new();
ep.push_str("include(ExternalProject)\n");
ep.push_str(&format!("ExternalProject_Add(\n {}\n", self.name));
if let Some(ref url) = self.url {
ep.push_str(&format!(" URL {}\n", url));
}
if let Some(ref repo) = self.git_repository {
ep.push_str(&format!(" GIT_REPOSITORY {}\n", repo));
}
if let Some(ref tag) = self.git_tag {
ep.push_str(&format!(" GIT_TAG {}\n", tag));
}
if let Some(ref src) = self.source_dir {
ep.push_str(&format!(" SOURCE_DIR {}\n", src));
}
if let Some(ref bin) = self.binary_dir {
ep.push_str(&format!(" BINARY_DIR {}\n", bin));
}
if !self.cmake_args.is_empty() {
ep.push_str(" CMAKE_ARGS\n");
for arg in &self.cmake_args {
ep.push_str(&format!(" {}\n", arg));
}
}
if let Some(ref build) = self.build_command {
ep.push_str(&format!(" BUILD_COMMAND {}\n", build));
}
if let Some(ref install) = self.install_command {
ep.push_str(&format!(" INSTALL_COMMAND {}\n", install));
}
if !self.depends.is_empty() {
ep.push_str(&format!(" DEPENDS {}\n", self.depends.join(" ")));
}
ep.push_str(")\n");
ep
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeCPMPackage {
pub name: String,
pub version: String,
pub github_repository: Option<String>,
pub gitlab_repository: Option<String>,
pub url: Option<String>,
pub options: HashMap<String, String>,
pub find_package: bool,
}
impl X86CMakeCPMPackage {
pub fn new_github(name: &str, version: &str, repo: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
github_repository: Some(repo.to_string()),
gitlab_repository: None,
url: None,
options: HashMap::new(),
find_package: true,
}
}
pub fn generate(&self) -> String {
let mut cpm = String::new();
cpm.push_str(&format!("CPMAddPackage(\n NAME {}\n", self.name));
cpm.push_str(&format!(" VERSION {}\n", self.version));
if let Some(ref repo) = self.github_repository {
cpm.push_str(&format!(" GITHUB_REPOSITORY {}\n", repo));
}
if let Some(ref repo) = self.gitlab_repository {
cpm.push_str(&format!(" GITLAB_REPOSITORY {}\n", repo));
}
if let Some(ref url) = self.url {
cpm.push_str(&format!(" URL {}\n", url));
}
for (k, v) in &self.options {
cpm.push_str(&format!(" OPTIONS \"{}={}\"\n", k, v));
}
if self.find_package {
cpm.push_str(&format!(
" FIND_PACKAGE_ARGUMENTS \"\"\n"
));
}
cpm.push_str(")\n");
cpm
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeToolchain {
pub system_name: String,
pub system_processor: String,
pub system_version: Option<String>,
pub c_compiler: String,
pub cxx_compiler: String,
pub linker: Option<String>,
pub archiver: Option<String>,
pub c_flags: Vec<String>,
pub cxx_flags: Vec<String>,
pub ld_flags: Vec<String>,
pub find_root_path: String,
pub sysroot: Option<String>,
pub pkg_config_path: Option<String>,
}
impl X86CMakeToolchain {
pub fn for_x86_64_linux() -> Self {
Self {
system_name: "Linux".to_string(),
system_processor: "x86_64".to_string(),
system_version: None,
c_compiler: "clang".to_string(),
cxx_compiler: "clang++".to_string(),
linker: Some("lld".to_string()),
archiver: Some("llvm-ar".to_string()),
c_flags: vec!["-m64".to_string(), "-march=x86-64".to_string()],
cxx_flags: vec!["-m64".to_string(), "-march=x86-64".to_string()],
ld_flags: vec!["-fuse-ld=lld".to_string()],
find_root_path: "/".to_string(),
sysroot: None,
pkg_config_path: None,
}
}
pub fn for_i686_linux() -> Self {
let mut tc = Self::for_x86_64_linux();
tc.system_processor = "i686".to_string();
tc.c_flags = vec!["-m32".to_string(), "-march=i686".to_string()];
tc.cxx_flags = vec!["-m32".to_string(), "-march=i686".to_string()];
tc.ld_flags = vec!["-m32".to_string(), "-fuse-ld=lld".to_string()];
tc
}
pub fn for_x86_64_mingw() -> Self {
Self {
system_name: "Windows".to_string(),
system_processor: "x86_64".to_string(),
system_version: None,
c_compiler: "x86_64-w64-mingw32-clang".to_string(),
cxx_compiler: "x86_64-w64-mingw32-clang++".to_string(),
linker: Some("x86_64-w64-mingw32-ld".to_string()),
archiver: Some("x86_64-w64-mingw32-ar".to_string()),
c_flags: vec!["-target x86_64-w64-mingw32".to_string()],
cxx_flags: vec!["-target x86_64-w64-mingw32".to_string()],
ld_flags: vec![],
find_root_path: "/usr/x86_64-w64-mingw32".to_string(),
sysroot: Some("/usr/x86_64-w64-mingw32".to_string()),
pkg_config_path: None,
}
}
pub fn generate(&self) -> String {
let mut tc = String::new();
tc.push_str(&format!(
"# X86 Toolchain file — auto-generated\n\
set(CMAKE_SYSTEM_NAME {})\n\
set(CMAKE_SYSTEM_PROCESSOR {})\n",
self.system_name, self.system_processor
));
if let Some(ref ver) = self.system_version {
tc.push_str(&format!("set(CMAKE_SYSTEM_VERSION {})\n", ver));
}
tc.push_str(&format!(
"set(CMAKE_C_COMPILER {})\n\
set(CMAKE_CXX_COMPILER {})\n",
self.c_compiler, self.cxx_compiler
));
if let Some(ref linker) = self.linker {
tc.push_str(&format!(
"set(CMAKE_LINKER {})\n",
linker
));
}
if let Some(ref ar) = self.archiver {
tc.push_str(&format!(
"set(CMAKE_AR {})\n\
set(CMAKE_RANLIB {}-ranlib)\n",
ar,
ar.trim_end_matches("-ar")
));
}
if !self.c_flags.is_empty() {
tc.push_str(&format!(
"set(CMAKE_C_FLAGS_INIT \"{}\")\n",
self.c_flags.join(" ")
));
}
if !self.cxx_flags.is_empty() {
tc.push_str(&format!(
"set(CMAKE_CXX_FLAGS_INIT \"{}\")\n",
self.cxx_flags.join(" ")
));
}
if !self.ld_flags.is_empty() {
tc.push_str(&format!(
"set(CMAKE_EXE_LINKER_FLAGS_INIT \"{}\")\n\
set(CMAKE_SHARED_LINKER_FLAGS_INIT \"{}\")\n",
self.ld_flags.join(" "),
self.ld_flags.join(" ")
));
}
tc.push_str(&format!(
"set(CMAKE_FIND_ROOT_PATH {})\n\
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n",
self.find_root_path
));
if let Some(ref sysroot) = self.sysroot {
tc.push_str(&format!(
"set(CMAKE_SYSROOT {})\n\
set(CMAKE_FIND_ROOT_PATH {})\n",
sysroot, sysroot
));
}
if let Some(ref pkg) = self.pkg_config_path {
tc.push_str(&format!("set(ENV{{PKG_CONFIG_PATH}} \"{}\")\n", pkg));
}
tc
}
}
#[derive(Debug, Clone)]
pub struct X86CMakePresets {
pub version: u32,
pub configure_presets: Vec<X86CMakeConfigurePreset>,
pub build_presets: Vec<X86CMakeBuildPreset>,
pub test_presets: Vec<X86CMakeTestPreset>,
pub user_presets_enabled: bool,
}
impl X86CMakePresets {
pub fn new() -> Self {
Self {
version: 6,
configure_presets: Vec::new(),
build_presets: Vec::new(),
test_presets: Vec::new(),
user_presets_enabled: false,
}
}
pub fn with_standard_presets() -> Self {
let mut presets = Self::new();
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "debug".to_string(),
display_name: "Debug Config".to_string(),
description: "Debug build with Clang on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/debug".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Debug".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_EXPORT_COMPILE_COMMANDS".to_string(), X86CMakeCacheValue::Bool(true));
m
},
inherits: vec![],
environment: HashMap::new(),
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "debug-asan".to_string(),
display_name: "Debug with AddressSanitizer".to_string(),
description: "Debug build with Clang ASan on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/debug-asan".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Debug".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_C_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=address -fno-omit-frame-pointer".to_string()));
m.insert("CMAKE_CXX_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=address -fno-omit-frame-pointer".to_string()));
m.insert("CMAKE_EXE_LINKER_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=address".to_string()));
m
},
inherits: vec!["debug".to_string()],
environment: {
let mut env = HashMap::new();
env.insert("ASAN_OPTIONS".to_string(), "detect_leaks=1".to_string());
env
},
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "debug-tsan".to_string(),
display_name: "Debug with ThreadSanitizer".to_string(),
description: "Debug build with Clang TSan on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/debug-tsan".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Debug".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_C_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=thread".to_string()));
m.insert("CMAKE_CXX_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=thread".to_string()));
m.insert("CMAKE_EXE_LINKER_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=thread".to_string()));
m
},
inherits: vec!["debug".to_string()],
environment: HashMap::new(),
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "debug-ubsan".to_string(),
display_name: "Debug with UndefinedBehaviorSanitizer".to_string(),
description: "Debug build with Clang UBSan on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/debug-ubsan".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Debug".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_C_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=undefined".to_string()));
m.insert("CMAKE_CXX_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=undefined".to_string()));
m.insert("CMAKE_EXE_LINKER_FLAGS".to_string(), X86CMakeCacheValue::String("-fsanitize=undefined".to_string()));
m
},
inherits: vec!["debug".to_string()],
environment: HashMap::new(),
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "release".to_string(),
display_name: "Release Config".to_string(),
description: "Release build with Clang on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/release".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Release".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_INTERPROCEDURAL_OPTIMIZATION".to_string(), X86CMakeCacheValue::Bool(true));
m
},
inherits: vec![],
environment: HashMap::new(),
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "release-lto-pgo".to_string(),
display_name: "Release with LTO and PGO".to_string(),
description: "Release build with Clang LTO and PGO instrumentation on X86".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/release-lto-pgo".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Release".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m.insert("CMAKE_INTERPROCEDURAL_OPTIMIZATION".to_string(), X86CMakeCacheValue::Bool(true));
m.insert("CMAKE_C_FLAGS".to_string(), X86CMakeCacheValue::String("-fprofile-generate".to_string()));
m.insert("CMAKE_CXX_FLAGS".to_string(), X86CMakeCacheValue::String("-fprofile-generate".to_string()));
m.insert("CMAKE_EXE_LINKER_FLAGS".to_string(), X86CMakeCacheValue::String("-fprofile-generate".to_string()));
m
},
inherits: vec!["release".to_string()],
environment: HashMap::new(),
});
presets.configure_presets.push(X86CMakeConfigurePreset {
name: "relwithdebinfo".to_string(),
display_name: "Release with Debug Info".to_string(),
description: "Optimized build with debug symbols".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "${sourceDir}/build/relwithdebinfo".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("RelWithDebInfo".to_string()));
m.insert("CMAKE_C_COMPILER".to_string(), X86CMakeCacheValue::String("clang".to_string()));
m.insert("CMAKE_CXX_COMPILER".to_string(), X86CMakeCacheValue::String("clang++".to_string()));
m
},
inherits: vec![],
environment: HashMap::new(),
});
presets.build_presets.push(X86CMakeBuildPreset {
name: "debug-build".to_string(),
configure_preset: "debug".to_string(),
jobs: Some(8),
targets: vec![],
clean_first: false,
});
presets.build_presets.push(X86CMakeBuildPreset {
name: "release-build".to_string(),
configure_preset: "release".to_string(),
jobs: Some(8),
targets: vec![],
clean_first: false,
});
presets.test_presets.push(X86CMakeTestPreset {
name: "default".to_string(),
configure_preset: "debug".to_string(),
output_on_failure: true,
timeout: Some(300),
});
presets
}
pub fn generate_cmake_presets(&self) -> String {
let mut json = String::from("");
json.push_str("{\n");
json.push_str(&format!(" \"version\": {},\n", self.version));
json.push_str(" \"configurePresets\": [\n");
for (i, preset) in self.configure_presets.iter().enumerate() {
if i > 0 {
json.push_str(",\n");
}
json.push_str(&preset.to_json());
}
json.push_str("\n ],\n");
json.push_str(" \"buildPresets\": [\n");
for (i, preset) in self.build_presets.iter().enumerate() {
if i > 0 {
json.push_str(",\n");
}
json.push_str(&preset.to_json());
}
json.push_str("\n ],\n");
json.push_str(" \"testPresets\": [\n");
for (i, preset) in self.test_presets.iter().enumerate() {
if i > 0 {
json.push_str(",\n");
}
json.push_str(&preset.to_json());
}
json.push_str("\n ]\n");
json.push_str("}\n");
json
}
}
impl Default for X86CMakePresets {
fn default() -> Self {
Self::with_standard_presets()
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeConfigurePreset {
pub name: String,
pub display_name: String,
pub description: String,
pub generator: Option<String>,
pub binary_dir: String,
pub cache_variables: HashMap<String, X86CMakeCacheValue>,
pub inherits: Vec<String>,
pub environment: HashMap<String, String>,
}
impl X86CMakeConfigurePreset {
pub fn to_json(&self) -> String {
let mut json = String::from(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", self.name));
json.push_str(&format!(" \"displayName\": \"{}\",\n", self.display_name));
json.push_str(&format!(" \"description\": \"{}\",\n", self.description));
if let Some(ref r#gen) = self.generator {
json.push_str(&format!(" \"generator\": \"{}\",\n", r#gen));
}
json.push_str(&format!(" \"binaryDir\": \"{}\"", self.binary_dir));
if !self.cache_variables.is_empty() {
json.push_str(",\n \"cacheVariables\": {\n");
let entries: Vec<String> = self.cache_variables.iter().map(|(k, v)| {
match v {
X86CMakeCacheValue::Bool(b) => {
format!(" \"{}\": {{\"type\": \"BOOL\", \"value\": \"{}\"}}", k, if *b { "ON" } else { "OFF" })
}
X86CMakeCacheValue::String(s) => {
format!(" \"{}\": \"{}\"", k, s)
}
X86CMakeCacheValue::FilePath(s) => {
format!(" \"{}\": {{\"type\": \"FILEPATH\", \"value\": \"{}\"}}", k, s)
}
}
}).collect();
json.push_str(&entries.join(",\n"));
json.push_str("\n }");
}
if !self.inherits.is_empty() {
json.push_str(",\n \"inherits\": [");
json.push_str(&self.inherits.iter().map(|i| format!("\"{}\"", i)).collect::<Vec<_>>().join(", "));
json.push_str("]");
}
json.push_str("\n }");
json
}
}
#[derive(Debug, Clone)]
pub enum X86CMakeCacheValue {
Bool(bool),
String(String),
FilePath(String),
}
#[derive(Debug, Clone)]
pub struct X86CMakeBuildPreset {
pub name: String,
pub configure_preset: String,
pub jobs: Option<u32>,
pub targets: Vec<String>,
pub clean_first: bool,
}
impl X86CMakeBuildPreset {
pub fn to_json(&self) -> String {
let mut json = String::from(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", self.name));
json.push_str(&format!(" \"configurePreset\": \"{}\"", self.configure_preset));
if let Some(jobs) = self.jobs {
json.push_str(&format!(",\n \"jobs\": {}", jobs));
}
if !self.targets.is_empty() {
json.push_str(",\n \"targets\": [");
json.push_str(&self.targets.iter().map(|t| format!("\"{}\"", t)).collect::<Vec<_>>().join(", "));
json.push_str("]");
}
if self.clean_first {
json.push_str(",\n \"cleanFirst\": true");
}
json.push_str("\n }");
json
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeTestPreset {
pub name: String,
pub configure_preset: String,
pub output_on_failure: bool,
pub timeout: Option<u32>,
}
impl X86CMakeTestPreset {
pub fn to_json(&self) -> String {
let mut json = String::from(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", self.name));
json.push_str(&format!(" \"configurePreset\": \"{}\"", self.configure_preset));
if self.output_on_failure {
json.push_str(",\n \"output\": {\"onFailure\": true}");
}
if let Some(timeout) = self.timeout {
json.push_str(&format!(",\n \"execution\": {{\"timeout\": {}}}", timeout));
}
json.push_str("\n }");
json
}
}
#[derive(Debug, Clone)]
pub struct X86CTestConfig {
pub enable: bool,
pub test_directory: String,
pub timeout_per_test: u32,
pub parallel_level: u32,
pub output_on_failure: bool,
pub labels: Vec<String>,
pub tests: Vec<X86CTestTest>,
}
impl X86CTestConfig {
pub fn new() -> Self {
Self {
enable: true,
test_directory: "tests".to_string(),
timeout_per_test: 300,
parallel_level: 4,
output_on_failure: true,
labels: Vec::new(),
tests: Vec::new(),
}
}
pub fn generate(&self) -> String {
let mut ct = String::new();
ct.push_str("# CTest configuration\n");
ct.push_str("enable_testing()\n\n");
if !self.labels.is_empty() {
ct.push_str(&format!(
"set(CTEST_LABELS {})\n",
self.labels.join(" ")
));
}
for test in &self.tests {
ct.push_str(&test.generate());
}
ct.push_str(&format!(
"# CTest command hint\n\
# ctest --test-dir {} -j {} --output-on-failure --timeout {}\n",
self.test_directory, self.parallel_level, self.timeout_per_test
));
ct
}
}
#[derive(Debug, Clone)]
pub struct X86CTestTest {
pub name: String,
pub command: String,
pub args: Vec<String>,
pub working_directory: Option<String>,
pub properties: HashMap<String, String>,
pub fixtures_setup: Vec<String>,
pub fixtures_cleanup: Vec<String>,
pub fixtures_required: Vec<String>,
}
impl X86CTestTest {
pub fn new(name: &str, command: &str) -> Self {
Self {
name: name.to_string(),
command: command.to_string(),
args: Vec::new(),
working_directory: None,
properties: HashMap::new(),
fixtures_setup: Vec::new(),
fixtures_cleanup: Vec::new(),
fixtures_required: Vec::new(),
}
}
pub fn generate(&self) -> String {
let mut test = String::new();
test.push_str(&format!("add_test(NAME {} COMMAND {}", self.name, self.command));
if !self.args.is_empty() {
test.push(' ');
test.push_str(&self.args.join(" "));
}
test.push_str(")\n");
if let Some(ref wd) = self.working_directory {
test.push_str(&format!(
"set_tests_properties({} PROPERTIES\n WORKING_DIRECTORY \"{}\")\n",
self.name, wd
));
}
for (key, value) in &self.properties {
test.push_str(&format!(
"set_tests_properties({} PROPERTIES {} \"{}\")\n",
self.name, key, value
));
}
if !self.fixtures_setup.is_empty() {
test.push_str(&format!(
"set_tests_properties({} PROPERTIES FIXTURES_SETUP {})\n",
self.name,
self.fixtures_setup.join(" ")
));
}
if !self.fixtures_cleanup.is_empty() {
test.push_str(&format!(
"set_tests_properties({} PROPERTIES FIXTURES_CLEANUP {})\n",
self.name,
self.fixtures_cleanup.join(" ")
));
}
if !self.fixtures_required.is_empty() {
test.push_str(&format!(
"set_tests_properties({} PROPERTIES FIXTURES_REQUIRED {})\n",
self.name,
self.fixtures_required.join(" ")
));
}
test
}
}
#[derive(Debug, Clone)]
pub struct X86CPackConfig {
pub package_name: String,
pub package_version: String,
pub package_vendor: String,
pub package_description: String,
pub package_description_summary: String,
pub license: String,
pub generators: Vec<String>,
pub source_generator: Option<String>,
pub components: Vec<X86CPackComponent>,
pub debian_config: Option<X86CPackDebConfig>,
pub rpm_config: Option<X86CPackRpmConfig>,
}
impl X86CPackConfig {
pub fn new(name: &str, version: &str) -> Self {
Self {
package_name: name.to_string(),
package_version: version.to_string(),
package_vendor: "X86 Clang Project".to_string(),
package_description: String::new(),
package_description_summary: String::new(),
license: "MIT".to_string(),
generators: vec!["TGZ".to_string(), "DEB".to_string(), "RPM".to_string()],
source_generator: Some("TGZ".to_string()),
components: Vec::new(),
debian_config: None,
rpm_config: None,
}
}
pub fn generate(&self) -> String {
let mut cp = String::new();
cp.push_str("# CPack packaging configuration\n");
cp.push_str("set(CPACK_PACKAGE_NAME \"");
cp.push_str(&self.package_name);
cp.push_str("\")\n");
cp.push_str("set(CPACK_PACKAGE_VERSION \"");
cp.push_str(&self.package_version);
cp.push_str("\")\n");
cp.push_str("set(CPACK_PACKAGE_VENDOR \"");
cp.push_str(&self.package_vendor);
cp.push_str("\")\n");
if !self.package_description.is_empty() {
cp.push_str(&format!(
"set(CPACK_PACKAGE_DESCRIPTION \"{}\")\n",
self.package_description
));
}
cp.push_str(&format!(
"set(CPACK_PACKAGE_DESCRIPTION_SUMMARY \"{}\")\n",
self.package_description_summary
));
cp.push_str(&format!(
"set(CPACK_RESOURCE_FILE_LICENSE \"${{CMAKE_SOURCE_DIR}}/LICENSE\")\n"
));
cp.push_str(&format!(
"set(CPACK_GENERATOR \"{}\")\n",
self.generators.join(";")
));
if let Some(ref src_gen) = self.source_generator {
cp.push_str(&format!(
"set(CPACK_SOURCE_GENERATOR \"{}\")\n",
src_gen
));
}
if let Some(ref deb) = self.debian_config {
cp.push_str(&deb.generate());
}
if let Some(ref rpm) = self.rpm_config {
cp.push_str(&rpm.generate());
}
cp.push_str("include(CPack)\n");
cp
}
}
#[derive(Debug, Clone)]
pub struct X86CPackComponent {
pub name: String,
pub display_name: String,
pub description: String,
pub required: bool,
pub group: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86CPackDebConfig {
pub maintainer: String,
pub section: String,
pub priority: String,
pub depends: Vec<String>,
pub recommends: Vec<String>,
pub suggests: Vec<String>,
pub conflicts: Vec<String>,
pub replaces: Vec<String>,
pub architecture: String,
}
impl X86CPackDebConfig {
pub fn generate(&self) -> String {
let mut deb = String::new();
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_MAINTAINER \"{}\")\n", self.maintainer
));
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_SECTION \"{}\")\n", self.section
));
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_PRIORITY \"{}\")\n", self.priority
));
if !self.depends.is_empty() {
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_DEPENDS \"{}\")\n",
self.depends.join(", ")
));
}
if !self.recommends.is_empty() {
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_RECOMMENDS \"{}\")\n",
self.recommends.join(", ")
));
}
if !self.suggests.is_empty() {
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_SUGGESTS \"{}\")\n",
self.suggests.join(", ")
));
}
if !self.conflicts.is_empty() {
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_CONFLICTS \"{}\")\n",
self.conflicts.join(", ")
));
}
if !self.replaces.is_empty() {
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_REPLACES \"{}\")\n",
self.replaces.join(", ")
));
}
deb.push_str(&format!(
"set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE \"{}\")\n",
self.architecture
));
deb
}
}
#[derive(Debug, Clone)]
pub struct X86CPackRpmConfig {
pub vendor: String,
pub license: String,
pub group: String,
pub requires: Vec<String>,
pub provides: Vec<String>,
pub obsoletes: Vec<String>,
pub conflicts: Vec<String>,
pub architecture: String,
pub release: String,
}
impl X86CPackRpmConfig {
pub fn generate(&self) -> String {
let mut rpm = String::new();
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_VENDOR \"{}\")\n", self.vendor
));
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_LICENSE \"{}\")\n", self.license
));
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_GROUP \"{}\")\n", self.group
));
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_ARCHITECTURE \"{}\")\n", self.architecture
));
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_RELEASE \"{}\")\n", self.release
));
if !self.requires.is_empty() {
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_REQUIRES \"{}\")\n",
self.requires.join(", ")
));
}
if !self.provides.is_empty() {
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_PROVIDES \"{}\")\n",
self.provides.join(", ")
));
}
if !self.obsoletes.is_empty() {
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_OBSOLETES \"{}\")\n",
self.obsoletes.join(", ")
));
}
if !self.conflicts.is_empty() {
rpm.push_str(&format!(
"set(CPACK_RPM_PACKAGE_CONFLICTS \"{}\")\n",
self.conflicts.join(", ")
));
}
rpm
}
}
#[derive(Debug, Clone)]
pub struct X86CMakeInstallRule {
pub target: Option<String>,
pub files: Vec<String>,
pub directory: Option<String>,
pub destination: String,
pub component: Option<String>,
pub permissions: Option<String>,
pub configs: Vec<String>,
pub export_name: Option<String>,
pub namespace: Option<String>,
}
impl X86CMakeInstallRule {
pub fn new_install_target(target: &str, destination: &str) -> Self {
Self {
target: Some(target.to_string()),
files: Vec::new(),
directory: None,
destination: destination.to_string(),
component: None,
permissions: None,
configs: Vec::new(),
export_name: None,
namespace: None,
}
}
pub fn new_install_files(files: &[&str], destination: &str) -> Self {
Self {
target: None,
files: files.iter().map(|f| f.to_string()).collect(),
directory: None,
destination: destination.to_string(),
component: None,
permissions: None,
configs: Vec::new(),
export_name: None,
namespace: None,
}
}
pub fn new_install_directory(dir: &str, destination: &str) -> Self {
Self {
target: None,
files: Vec::new(),
directory: Some(dir.to_string()),
destination: destination.to_string(),
component: None,
permissions: None,
configs: Vec::new(),
export_name: None,
namespace: None,
}
}
pub fn generate(&self) -> String {
let mut install = String::new();
if let Some(ref target) = self.target {
install.push_str(&format!("install(TARGETS {}", target));
if let Some(ref exp) = self.export_name {
install.push_str(&format!(" EXPORT {}", exp));
}
install.push_str(&format!("\n RUNTIME DESTINATION {}", self.destination));
install.push_str(&format!("\n LIBRARY DESTINATION {}", self.destination));
install.push_str(&format!("\n ARCHIVE DESTINATION {}", self.destination));
if let Some(ref ns) = self.namespace {
install.push_str(&format!("\n NAMESPACE {}::", ns));
}
if let Some(ref comp) = self.component {
install.push_str(&format!("\n COMPONENT {}", comp));
}
if !self.configs.is_empty() {
install.push_str(&format!("\n CONFIGURATIONS {}", self.configs.join(" ")));
}
} else if !self.files.is_empty() {
let files_str = self.files.iter().map(|f| format!("\"{}\"", f)).collect::<Vec<_>>().join(" ");
install.push_str(&format!(
"install(FILES {}\n DESTINATION {}",
files_str, self.destination
));
if let Some(ref perm) = self.permissions {
install.push_str(&format!("\n PERMISSIONS {}", perm));
}
if let Some(ref comp) = self.component {
install.push_str(&format!("\n COMPONENT {}", comp));
}
} else if let Some(ref dir) = self.directory {
install.push_str(&format!(
"install(DIRECTORY {}\n DESTINATION {}",
dir, self.destination
));
if let Some(ref comp) = self.component {
install.push_str(&format!("\n COMPONENT {}", comp));
}
}
install.push_str(")\n");
install
}
}
#[derive(Debug, Clone)]
pub struct X86MakeSupport {
pub cc: String,
pub cxx: String,
pub ld: String,
pub ar: String,
pub cflags: Vec<String>,
pub cxxflags: Vec<String>,
pub ldflags: Vec<String>,
pub targets: Vec<X86MakeTarget>,
pub auto_deps: bool,
pub parallel_jobs: u32,
pub verbose: bool,
pub custom_rules: Vec<String>,
}
impl X86MakeSupport {
pub fn new() -> Self {
Self {
cc: "clang".to_string(),
cxx: "clang++".to_string(),
ld: "clang++".to_string(),
ar: "llvm-ar".to_string(),
cflags: vec!["-Wall".to_string(), "-Wextra".to_string()],
cxxflags: vec!["-Wall".to_string(), "-Wextra".to_string(), "-std=c++17".to_string()],
ldflags: vec![],
targets: Vec::new(),
auto_deps: true,
parallel_jobs: 4,
verbose: false,
custom_rules: Vec::new(),
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec!["Makefile".to_string()],
build_commands: vec![format!("make -j{}", build_systems.parallel_jobs)],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
Ok(output)
}
pub fn generate_makefile(&self) -> String {
let mut mk = String::new();
mk.push_str("# Auto-generated Makefile for X86 Clang\n");
mk.push_str("# Generated by clang_build_systems_x86\n\n");
mk.push_str(&format!("CC := {}\n", self.cc));
mk.push_str(&format!("CXX := {}\n", self.cxx));
mk.push_str(&format!("LD := {}\n", self.ld));
mk.push_str(&format!("AR := {}\n", self.ar));
mk.push_str(&format!("ARFLAGS := rcs\n\n"));
mk.push_str(&format!("CFLAGS := {}\n", self.cflags.join(" ")));
mk.push_str(&format!("CXXFLAGS := {}\n", self.cxxflags.join(" ")));
mk.push_str(&format!("LDFLAGS := {}\n", self.ldflags.join(" ")));
mk.push_str("OPTFLAGS := -O2\n");
mk.push_str("DEBUGFLAGS := -g\n\n");
mk.push_str("ifdef DEBUG\n");
mk.push_str(" CFLAGS += $(DEBUGFLAGS)\n");
mk.push_str(" CXXFLAGS += $(DEBUGFLAGS)\n");
mk.push_str("else\n");
mk.push_str(" CFLAGS += $(OPTFLAGS)\n");
mk.push_str(" CXXFLAGS += $(OPTFLAGS)\n");
mk.push_str("endif\n\n");
mk.push_str("# X86 target flags\n");
mk.push_str("ifdef X86_32\n");
mk.push_str(" CFLAGS += -m32 -march=i686\n");
mk.push_str(" CXXFLAGS += -m32 -march=i686\n");
mk.push_str(" LDFLAGS += -m32\n");
mk.push_str("else\n");
mk.push_str(" CFLAGS += -m64 -march=x86-64\n");
mk.push_str(" CXXFLAGS += -m64 -march=x86-64\n");
mk.push_str("endif\n\n");
if self.auto_deps {
mk.push_str("# Automatic dependency generation\n");
mk.push_str("DEPFLAGS = -MD -MF $(@:.o=.d) -MT $@\n\n");
}
mk.push_str("SRCDIR := src\n");
mk.push_str("OBJDIR := build/obj\n");
mk.push_str("BINDIR := build/bin\n");
mk.push_str("LIBDIR := build/lib\n\n");
mk.push_str("# Implicit rules\n");
mk.push_str("$(OBJDIR)/%.o: $(SRCDIR)/%.c\n");
if self.verbose {
mk.push_str("\t@echo \"[CC] $<\"\n");
}
if self.auto_deps {
mk.push_str("\t$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@\n");
} else {
mk.push_str("\t$(CC) $(CFLAGS) -c $< -o $@\n");
}
mk.push('\n');
mk.push_str("$(OBJDIR)/%.o: $(SRCDIR)/%.cpp\n");
if self.verbose {
mk.push_str("\t@echo \"[CXX] $<\"\n");
}
if self.auto_deps {
mk.push_str("\t$(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@\n");
} else {
mk.push_str("\t$(CXX) $(CXXFLAGS) -c $< -o $@\n");
}
mk.push('\n');
mk.push_str("$(OBJDIR)/%.o: $(SRCDIR)/%.cc\n");
if self.verbose {
mk.push_str("\t@echo \"[CXX] $<\"\n");
}
if self.auto_deps {
mk.push_str("\t$(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@\n");
} else {
mk.push_str("\t$(CXX) $(CXXFLAGS) -c $< -o $@\n");
}
mk.push('\n');
mk.push_str("$(OBJDIR)/%.o: $(SRCDIR)/%.cxx\n");
if self.verbose {
mk.push_str("\t@echo \"[CXX] $<\"\n");
}
if self.auto_deps {
mk.push_str("\t$(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@\n");
} else {
mk.push_str("\t$(CXX) $(CXXFLAGS) -c $< -o $@\n");
}
mk.push('\n');
mk.push_str("# Static library\n");
mk.push_str("$(LIBDIR)/%.a:\n");
mk.push_str("\t$(AR) $(ARFLAGS) $@ $^\n\n");
mk.push_str("# Shared library\n");
mk.push_str("$(LIBDIR)/%.so:\n");
mk.push_str("\t$(CXX) -shared $(LDFLAGS) $^ -o $@\n\n");
mk.push_str("# Link executable\n");
mk.push_str("$(BINDIR)/%:\n");
mk.push_str("\t$(LD) $(LDFLAGS) $^ -o $@\n\n");
mk.push_str(".DEFAULT_GOAL := all\n\n");
for target in &self.targets {
mk.push_str(&target.generate_make());
}
mk.push_str(".PHONY: all clean distclean install uninstall help\n\n");
mk.push_str("all: $(TARGETS)\n\n");
mk.push_str("clean:\n");
mk.push_str("\trm -rf $(OBJDIR) $(BINDIR) $(LIBDIR)\n");
mk.push_str("\t-find . -name '*.o' -delete\n");
mk.push_str("\t-find . -name '*.d' -delete\n\n");
mk.push_str("distclean: clean\n");
mk.push_str("\trm -rf build/\n\n");
mk.push_str("install: all\n");
mk.push_str("\tinstall -d $(DESTDIR)$(PREFIX)/bin\n");
mk.push_str("\tinstall -d $(DESTDIR)$(PREFIX)/lib\n");
mk.push_str("\tinstall -d $(DESTDIR)$(PREFIX)/include\n");
mk.push_str("\t-cp $(BINDIR)/* $(DESTDIR)$(PREFIX)/bin/\n");
mk.push_str("\t-cp $(LIBDIR)/* $(DESTDIR)$(PREFIX)/lib/\n\n");
mk.push_str("uninstall:\n");
mk.push_str("\t-cd $(DESTDIR)$(PREFIX)/bin && rm -f $(notdir $(wildcard $(BINDIR)/*))\n\n");
mk.push_str("help:\n");
mk.push_str("\t@echo \"Available targets:\"\n");
mk.push_str("\t@echo \" all - Build all targets (default)\"\n");
mk.push_str("\t@echo \" clean - Remove object files\"\n");
mk.push_str("\t@echo \" distclean - Remove all build artifacts\"\n");
mk.push_str("\t@echo \" install - Install to PREFIX\"\n");
mk.push_str("\t@echo \" uninstall - Uninstall from PREFIX\"\n");
mk.push_str("\t@echo \"\"\n");
mk.push_str("\t@echo \"Variables:\"\n");
mk.push_str("\t@echo \" PREFIX - Installation prefix (default: /usr/local)\"\n");
mk.push_str("\t@echo \" DEBUG=1 - Build with debug symbols\"\n");
mk.push_str("\t@echo \" X86_32=1 - Build for 32-bit x86\"\n\n");
if self.auto_deps {
mk.push_str("# Include dependency files\n");
mk.push_str("-include $(wildcard $(OBJDIR)/*.d)\n");
}
mk
}
pub fn generate_recursive_make(subdirs: &[&str]) -> String {
let mut rec = String::new();
rec.push_str("# Recursive make\n");
rec.push_str(".PHONY: subdirs $(SUBDIRS)\n\n");
rec.push_str("SUBDIRS := ");
rec.push_str(&subdirs.join(" "));
rec.push_str("\n\n");
rec.push_str("subdirs: $(SUBDIRS)\n\n");
rec.push_str("$(SUBDIRS):\n");
rec.push_str("\t$(MAKE) -C $@ $(MAKECMDGOALS)\n\n");
rec
}
pub fn add_target(&mut self, target: X86MakeTarget) {
self.targets.push(target);
}
pub fn generate_simple_makefile(executable_name: &str, sources: &[&str]) -> String {
let mut mk = String::new();
mk.push_str("# Simple Makefile for X86 Clang project\n\n");
mk.push_str("CC := clang\n");
mk.push_str("CXX := clang++\n");
mk.push_str("CFLAGS := -Wall -Wextra -O2 -march=x86-64\n");
mk.push_str("CXXFLAGS := -Wall -Wextra -O2 -march=x86-64 -std=c++17\n");
mk.push_str("LDFLAGS :=\n\n");
let obj_files: Vec<String> = sources.iter().map(|s| {
s.replace(".cpp", ".o").replace(".cc", ".o").replace(".cxx", ".o").replace(".c", ".o")
}).collect();
mk.push_str(&format!("TARGET := {}\n", executable_name));
mk.push_str(&format!("OBJS := {}\n\n", obj_files.join(" ")));
mk.push_str("$(TARGET): $(OBJS)\n");
mk.push_str("\t$(CXX) $(LDFLAGS) $^ -o $@\n\n");
mk.push_str("%.o: %.cpp\n");
mk.push_str("\t$(CXX) $(CXXFLAGS) -MD -MF $*.d -c $< -o $@\n\n");
mk.push_str("%.o: %.c\n");
mk.push_str("\t$(CC) $(CFLAGS) -MD -MF $*.d -c $< -o $@\n\n");
mk.push_str(".PHONY: clean\n");
mk.push_str("clean:\n");
mk.push_str("\trm -f $(TARGET) $(OBJS) *.d\n\n");
mk.push_str("-include *.d\n");
mk
}
}
impl Default for X86MakeSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MakeTarget {
pub name: String,
pub target_type: X86MakeTargetType,
pub sources: Vec<String>,
pub objects: Vec<String>,
pub dependencies: Vec<String>,
pub extra_flags: Vec<String>,
pub extra_ldflags: Vec<String>,
}
impl X86MakeTarget {
pub fn new_executable(name: &str, sources: &[&str]) -> Self {
let objects: Vec<String> = sources.iter().map(|s| {
s.replace(".cpp", ".o").replace(".cc", ".o")
.replace(".cxx", ".o").replace(".c", ".o")
}).collect();
Self {
name: name.to_string(),
target_type: X86MakeTargetType::Executable,
sources: sources.iter().map(|s| s.to_string()).collect(),
objects,
dependencies: Vec::new(),
extra_flags: Vec::new(),
extra_ldflags: Vec::new(),
}
}
pub fn new_static_library(name: &str, sources: &[&str]) -> Self {
let mut t = Self::new_executable(name, sources);
t.target_type = X86MakeTargetType::StaticLibrary;
t
}
pub fn new_shared_library(name: &str, sources: &[&str]) -> Self {
let mut t = Self::new_executable(name, sources);
t.target_type = X86MakeTargetType::SharedLibrary;
t
}
pub fn generate_make(&self) -> String {
let mut mk = String::new();
mk.push_str(&format!("# Target: {}\n", self.name));
let obj_var = format!("{}_OBJS", self.name.to_uppercase());
mk.push_str(&format!("{} := {}\n", obj_var, self.objects.join(" ")));
match self.target_type {
X86MakeTargetType::Executable => {
mk.push_str(&format!(
"$(BINDIR)/{}: $({})\n",
self.name, obj_var
));
mk.push_str(&format!(
"\t$(LD) $(LDFLAGS) {} $^ -o $@\n\n",
self.extra_ldflags.join(" ")
));
}
X86MakeTargetType::StaticLibrary => {
let lib_name = if self.name.starts_with("lib") {
format!("{}.a", self.name)
} else {
format!("lib{}.a", self.name)
};
mk.push_str(&format!(
"$(LIBDIR)/{}: $({})\n",
lib_name, obj_var
));
mk.push_str(&format!(
"\t$(AR) $(ARFLAGS) $@ $^\n\n"
));
}
X86MakeTargetType::SharedLibrary => {
let lib_name = if self.name.starts_with("lib") {
format!("{}.so", self.name)
} else {
format!("lib{}.so", self.name)
};
mk.push_str(&format!(
"$(LIBDIR)/{}: $({})\n",
lib_name, obj_var
));
mk.push_str(&format!(
"\t$(CXX) -shared $(LDFLAGS) {} $^ -o $@\n\n",
self.extra_ldflags.join(" ")
));
}
}
mk
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MakeTargetType {
Executable,
StaticLibrary,
SharedLibrary,
}
#[derive(Debug, Clone)]
pub struct X86NinjaSupport {
pub ninja_required_version: String,
pub build_dir: String,
pub rules: Vec<X86NinjaRule>,
pub build_edges: Vec<X86NinjaBuildEdge>,
pub pools: Vec<X86NinjaPool>,
pub variables: HashMap<String, String>,
pub default_targets: Vec<String>,
}
impl X86NinjaSupport {
pub fn new() -> Self {
Self {
ninja_required_version: "1.10".to_string(),
build_dir: "build".to_string(),
rules: Vec::new(),
build_edges: Vec::new(),
pools: Vec::new(),
variables: HashMap::new(),
default_targets: vec!["all".to_string()],
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec!["build.ninja".to_string()],
build_commands: vec![format!("ninja -j{}", build_systems.parallel_jobs)],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
Ok(output)
}
pub fn generate_ninja_file(&self) -> String {
let mut ninja = String::new();
ninja.push_str(&format!(
"# build.ninja — Auto-generated for X86 Clang\n\
ninja_required_version = {}\n\n",
self.ninja_required_version
));
for (key, value) in &self.variables {
ninja.push_str(&format!("{} = {}\n", key, value));
}
ninja.push('\n');
ninja.push_str("cc = clang\n");
ninja.push_str("cxx = clang++\n");
ninja.push_str("ld = clang++\n");
ninja.push_str("ar = llvm-ar\n");
ninja.push_str("cflags = -Wall -Wextra -O2 -march=x86-64\n");
ninja.push_str("cxxflags = -Wall -Wextra -O2 -march=x86-64 -std=c++17\n");
ninja.push_str("ldflags = \n\n");
for pool in &self.pools {
ninja.push_str(&pool.generate());
ninja.push('\n');
}
for rule in &self.rules {
ninja.push_str(&rule.generate());
ninja.push('\n');
}
if self.rules.is_empty() {
ninja.push_str("rule cc\n");
ninja.push_str(" command = $cc $cflags -MD -MF $out.d -c $in -o $out\n");
ninja.push_str(" description = CC $out\n");
ninja.push_str(" depfile = $out.d\n");
ninja.push_str(" deps = gcc\n\n");
ninja.push_str("rule cxx\n");
ninja.push_str(" command = $cxx $cxxflags -MD -MF $out.d -c $in -o $out\n");
ninja.push_str(" description = CXX $out\n");
ninja.push_str(" depfile = $out.d\n");
ninja.push_str(" deps = gcc\n\n");
ninja.push_str("rule link\n");
ninja.push_str(" command = $ld $ldflags $in -o $out\n");
ninja.push_str(" description = LINK $out\n\n");
ninja.push_str("rule ar\n");
ninja.push_str(" command = $ar rcs $out $in\n");
ninja.push_str(" description = AR $out\n\n");
ninja.push_str("rule solink\n");
ninja.push_str(" command = $cxx -shared $ldflags $in -o $out\n");
ninja.push_str(" description = SOLINK $out\n\n");
}
for edge in &self.build_edges {
ninja.push_str(&edge.generate());
ninja.push('\n');
}
ninja.push_str("default ");
ninja.push_str(&self.default_targets.join(" "));
ninja.push('\n');
ninja
}
pub fn compile_c_edge(source: &str, object: &str, implicit_deps: &[&str]) -> X86NinjaBuildEdge {
X86NinjaBuildEdge {
outputs: vec![object.to_string()],
rule: "cc".to_string(),
inputs: vec![source.to_string()],
implicit_inputs: implicit_deps.iter().map(|s| s.to_string()).collect(),
order_only_inputs: Vec::new(),
variables: HashMap::new(),
implicit_outputs: Vec::new(),
validations: Vec::new(),
}
}
pub fn compile_cxx_edge(source: &str, object: &str, implicit_deps: &[&str]) -> X86NinjaBuildEdge {
X86NinjaBuildEdge {
outputs: vec![object.to_string()],
rule: "cxx".to_string(),
inputs: vec![source.to_string()],
implicit_inputs: implicit_deps.iter().map(|s| s.to_string()).collect(),
order_only_inputs: Vec::new(),
variables: HashMap::new(),
implicit_outputs: Vec::new(),
validations: Vec::new(),
}
}
pub fn link_executable_edge(name: &str, objects: &[&str], output: &str) -> X86NinjaBuildEdge {
X86NinjaBuildEdge {
outputs: vec![output.to_string()],
rule: "link".to_string(),
inputs: objects.iter().map(|s| s.to_string()).collect(),
implicit_inputs: Vec::new(),
order_only_inputs: Vec::new(),
variables: HashMap::new(),
implicit_outputs: Vec::new(),
validations: Vec::new(),
}
}
pub fn generate_for_project(sources: &[&str], executable: &str) -> String {
let mut support = X86NinjaSupport::new();
let mut edges = Vec::new();
let mut all_objects = Vec::new();
for source in sources {
let obj = source.replace(".cpp", ".o").replace(".cc", ".o")
.replace(".cxx", ".o").replace(".c", ".o");
let rule = if source.ends_with(".c") { "cc" } else { "cxx" };
edges.push(X86NinjaBuildEdge {
outputs: vec![format!("$builddir/{}", obj)],
rule: rule.to_string(),
inputs: vec![format!("$srcdir/{}", source)],
implicit_inputs: Vec::new(),
order_only_inputs: Vec::new(),
variables: HashMap::new(),
implicit_outputs: Vec::new(),
validations: Vec::new(),
});
all_objects.push(format!("$builddir/{}", obj));
}
edges.push(X86NinjaBuildEdge {
outputs: vec![format!("$builddir/{}", executable)],
rule: "link".to_string(),
inputs: all_objects,
implicit_inputs: Vec::new(),
order_only_inputs: Vec::new(),
variables: HashMap::new(),
implicit_outputs: Vec::new(),
validations: Vec::new(),
});
support.build_edges = edges;
support.variables.insert("builddir".to_string(), "build".to_string());
support.variables.insert("srcdir".to_string(), "src".to_string());
support.generate_ninja_file()
}
pub fn add_rule(&mut self, rule: X86NinjaRule) {
self.rules.push(rule);
}
pub fn add_edge(&mut self, edge: X86NinjaBuildEdge) {
self.build_edges.push(edge);
}
}
impl Default for X86NinjaSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86NinjaRule {
pub name: String,
pub command: String,
pub description: Option<String>,
pub depfile: Option<String>,
pub deps: Option<String>,
pub generator: bool,
pub pool: Option<String>,
pub restat: bool,
pub rspfile: Option<String>,
pub rspfile_content: Option<String>,
}
impl X86NinjaRule {
pub fn generate(&self) -> String {
let mut rule = String::new();
rule.push_str(&format!("rule {}\n", self.name));
rule.push_str(&format!(" command = {}\n", self.command));
if let Some(ref desc) = self.description {
rule.push_str(&format!(" description = {}\n", desc));
}
if let Some(ref df) = self.depfile {
rule.push_str(&format!(" depfile = {}\n", df));
}
if let Some(ref deps) = self.deps {
rule.push_str(&format!(" deps = {}\n", deps));
}
if self.generator {
rule.push_str(" generator = 1\n");
}
if let Some(ref pool) = self.pool {
rule.push_str(&format!(" pool = {}\n", pool));
}
if self.restat {
rule.push_str(" restat = 1\n");
}
if let Some(ref rsp) = self.rspfile {
rule.push_str(&format!(" rspfile = {}\n", rsp));
}
if let Some(ref rspc) = self.rspfile_content {
rule.push_str(&format!(" rspfile_content = {}\n", rspc));
}
rule
}
}
#[derive(Debug, Clone)]
pub struct X86NinjaBuildEdge {
pub outputs: Vec<String>,
pub rule: String,
pub inputs: Vec<String>,
pub implicit_inputs: Vec<String>,
pub order_only_inputs: Vec<String>,
pub variables: HashMap<String, String>,
pub implicit_outputs: Vec<String>,
pub validations: Vec<String>,
}
impl X86NinjaBuildEdge {
pub fn generate(&self) -> String {
let mut edge = String::new();
edge.push_str("build ");
edge.push_str(&self.outputs.join(" "));
if !self.implicit_outputs.is_empty() {
edge.push_str(" | ");
edge.push_str(&self.implicit_outputs.join(" "));
}
edge.push_str(&format!(": {}", self.rule));
if !self.inputs.is_empty() {
edge.push(' ');
edge.push_str(&self.inputs.join(" "));
}
if !self.implicit_inputs.is_empty() {
edge.push_str(" | ");
edge.push_str(&self.implicit_inputs.join(" "));
}
if !self.order_only_inputs.is_empty() {
edge.push_str(" || ");
edge.push_str(&self.order_only_inputs.join(" "));
}
edge.push('\n');
for (key, value) in &self.variables {
edge.push_str(&format!(" {} = {}\n", key, value));
}
if !self.validations.is_empty() {
edge.push_str(&format!(" validations = {}\n", self.validations.join(" ")));
}
edge
}
}
#[derive(Debug, Clone)]
pub struct X86NinjaPool {
pub name: String,
pub depth: u32,
}
impl X86NinjaPool {
pub fn generate(&self) -> String {
format!("pool {}\n depth = {}\n", self.name, self.depth)
}
}
#[derive(Debug, Clone)]
pub struct X86BazelSupport {
pub workspace_name: String,
pub bazel_version: Option<String>,
pub toolchain: X86BazelToolchain,
pub platforms: Vec<X86BazelPlatform>,
pub targets: Vec<X86BazelTarget>,
pub external_deps: Vec<X86BazelExternalDep>,
pub use_bzlmod: bool,
}
impl X86BazelSupport {
pub fn new() -> Self {
Self {
workspace_name: "x86_project".to_string(),
bazel_version: Some("7.0.0".to_string()),
toolchain: X86BazelToolchain::default(),
platforms: vec![
X86BazelPlatform::x86_64_linux(),
X86BazelPlatform::i686_linux(),
],
targets: Vec::new(),
external_deps: Vec::new(),
use_bzlmod: true,
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec!["BUILD".to_string()],
build_commands: vec![format!("bazel build //... --jobs={}", build_systems.parallel_jobs)],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
if !self.use_bzlmod {
output.files_generated.push("WORKSPACE".to_string());
} else {
output.files_generated.push("MODULE.bazel".to_string());
}
output.files_generated.push("BUILD".to_string());
output.files_generated.push(".bazelrc".to_string());
Ok(output)
}
pub fn generate_build_file(&self) -> String {
let mut build = String::new();
build.push_str(&format!(
"# BUILD — Auto-generated for X86 Clang\n\
# Package: {}\n\n",
self.workspace_name
));
build.push_str("load(\"@rules_cc//cc:defs.bzl\", \"cc_binary\", \"cc_library\", \"cc_test\")\n\n");
for target in &self.targets {
match target.target_type {
X86BazelTargetType::CCLibrary => {
build.push_str(&self.generate_cc_library(target));
}
X86BazelTargetType::CCBinary => {
build.push_str(&self.generate_cc_binary(target));
}
X86BazelTargetType::CCTest => {
build.push_str(&self.generate_cc_test(target));
}
X86BazelTargetType::CCImport => {
build.push_str(&self.generate_cc_import(target));
}
}
build.push('\n');
}
build
}
fn generate_cc_library(&self, target: &X86BazelTarget) -> String {
let mut lib = String::new();
lib.push_str("cc_library(\n");
lib.push_str(&format!(" name = \"{}\",\n", target.name));
if !target.srcs.is_empty() {
lib.push_str(" srcs = [\n");
for src in &target.srcs {
lib.push_str(&format!(" \"{}\",\n", src));
}
lib.push_str(" ],\n");
}
if !target.hdrs.is_empty() {
lib.push_str(" hdrs = [\n");
for hdr in &target.hdrs {
lib.push_str(&format!(" \"{}\",\n", hdr));
}
lib.push_str(" ],\n");
}
if !target.deps.is_empty() {
lib.push_str(" deps = [\n");
for dep in &target.deps {
lib.push_str(&format!(" \"{}\",\n", dep));
}
lib.push_str(" ],\n");
}
if !target.copts.is_empty() {
lib.push_str(" copts = [\n");
for opt in &target.copts {
lib.push_str(&format!(" \"{}\",\n", opt));
}
lib.push_str(" ],\n");
}
if !target.linkopts.is_empty() {
lib.push_str(" linkopts = [\n");
for opt in &target.linkopts {
lib.push_str(&format!(" \"{}\",\n", opt));
}
lib.push_str(" ],\n");
}
if !target.defines.is_empty() {
lib.push_str(" defines = [\n");
for def in &target.defines {
lib.push_str(&format!(" \"{}\",\n", def));
}
lib.push_str(" ],\n");
}
if !target.includes.is_empty() {
lib.push_str(" includes = [\n");
for inc in &target.includes {
lib.push_str(&format!(" \"{}\",\n", inc));
}
lib.push_str(" ],\n");
}
if !target.visibility.is_empty() {
lib.push_str(" visibility = [\n");
for vis in &target.visibility {
lib.push_str(&format!(" \"{}\",\n", vis));
}
lib.push_str(" ],\n");
}
if target.alwayslink {
lib.push_str(" alwayslink = True,\n");
}
if target.linkstatic {
lib.push_str(" linkstatic = True,\n");
}
lib.push_str(")\n");
lib
}
fn generate_cc_binary(&self, target: &X86BazelTarget) -> String {
let mut bin = String::new();
bin.push_str("cc_binary(\n");
bin.push_str(&format!(" name = \"{}\",\n", target.name));
if !target.srcs.is_empty() {
bin.push_str(" srcs = [\n");
for src in &target.srcs {
bin.push_str(&format!(" \"{}\",\n", src));
}
bin.push_str(" ],\n");
}
if !target.deps.is_empty() {
bin.push_str(" deps = [\n");
for dep in &target.deps {
bin.push_str(&format!(" \"{}\",\n", dep));
}
bin.push_str(" ],\n");
}
if !target.copts.is_empty() {
bin.push_str(" copts = [\n");
for opt in &target.copts {
bin.push_str(&format!(" \"{}\",\n", opt));
}
bin.push_str(" ],\n");
}
if !target.linkopts.is_empty() {
bin.push_str(" linkopts = [\n");
for opt in &target.linkopts {
bin.push_str(&format!(" \"{}\",\n", opt));
}
bin.push_str(" ],\n");
}
if !target.defines.is_empty() {
bin.push_str(" defines = [\n");
for def in &target.defines {
bin.push_str(&format!(" \"{}\",\n", def));
}
bin.push_str(" ],\n");
}
if !target.visibility.is_empty() {
bin.push_str(" visibility = [\n");
for vis in &target.visibility {
bin.push_str(&format!(" \"{}\",\n", vis));
}
bin.push_str(" ],\n");
}
if target.linkstatic {
bin.push_str(" linkstatic = True,\n");
}
bin.push_str(")\n");
bin
}
fn generate_cc_test(&self, target: &X86BazelTarget) -> String {
let mut test = String::new();
test.push_str("cc_test(\n");
test.push_str(&format!(" name = \"{}\",\n", target.name));
if !target.srcs.is_empty() {
test.push_str(" srcs = [\n");
for src in &target.srcs {
test.push_str(&format!(" \"{}\",\n", src));
}
test.push_str(" ],\n");
}
if !target.deps.is_empty() {
test.push_str(" deps = [\n");
for dep in &target.deps {
test.push_str(&format!(" \"{}\",\n", dep));
}
test.push_str(" ],\n");
}
if !target.copts.is_empty() {
test.push_str(" copts = [\n");
for opt in &target.copts {
test.push_str(&format!(" \"{}\",\n", opt));
}
test.push_str(" ],\n");
}
if !target.defines.is_empty() {
test.push_str(" defines = [\n");
for def in &target.defines {
test.push_str(&format!(" \"{}\",\n", def));
}
test.push_str(" ],\n");
}
if !target.tags.is_empty() {
test.push_str(" tags = [\n");
for tag in &target.tags {
test.push_str(&format!(" \"{}\",\n", tag));
}
test.push_str(" ],\n");
}
if let Some(ref size) = target.size {
test.push_str(&format!(" size = \"{}\",\n", size));
}
if let Some(ref timeout) = target.timeout {
test.push_str(&format!(" timeout = \"{}\",\n", timeout));
}
test.push_str(")\n");
test
}
fn generate_cc_import(&self, target: &X86BazelTarget) -> String {
format!(
"cc_import(\n name = \"{}\",\n static_library = \"{}\",\n)\n",
target.name,
target.import_library.as_deref().unwrap_or("")
)
}
pub fn generate_workspace(&self) -> String {
let mut ws = String::new();
ws.push_str(&format!(
"# WORKSPACE — Auto-generated for X86 Bazel project\n\
workspace(name = \"{}\")\n\n",
self.workspace_name
));
ws.push_str("load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n");
ws.push_str("load(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\n");
ws.push_str("register_toolchains(\n");
ws.push_str(" \"//toolchain:x86_64_linux_clang\",\n");
ws.push_str(" \"//toolchain:i686_linux_clang\",\n");
ws.push_str(")\n\n");
ws.push_str("register_execution_platforms(\n");
ws.push_str(" \"//platforms:x86_64_linux\",\n");
ws.push_str(" \"//platforms:i686_linux\",\n");
ws.push_str(")\n\n");
for dep in &self.external_deps {
ws.push_str(&dep.generate_workspace());
ws.push('\n');
}
ws
}
pub fn generate_module_bazel(&self) -> String {
let mut mod_bazel = String::new();
mod_bazel.push_str(&format!(
"module(name = \"{}\", version = \"0.1.0\")\n\n",
self.workspace_name
));
mod_bazel.push_str("bazel_dep(name = \"rules_cc\", version = \"0.0.9\")\n");
mod_bazel.push_str("bazel_dep(name = \"platforms\", version = \"0.0.9\")\n");
mod_bazel.push_str("bazel_dep(name = \"rules_proto\", version = \"5.3.0-21.7\")\n\n");
for dep in &self.external_deps {
mod_bazel.push_str(&dep.generate_module_bazel());
}
mod_bazel.push_str("\n");
mod_bazel.push_str("# X86 Clang toolchain\n");
mod_bazel.push_str("register_toolchains(\n");
mod_bazel.push_str(" \"//toolchain:x86_64_linux_clang\",\n");
mod_bazel.push_str(")\n");
mod_bazel
}
pub fn generate_bazelrc(&self) -> String {
let mut rc = String::new();
rc.push_str("# .bazelrc — X86 Clang build configuration\n\n");
rc.push_str("# Build with Clang\n");
rc.push_str("build --action_env=CC=clang\n");
rc.push_str("build --action_env=CXX=clang++\n\n");
rc.push_str("# X86-64 defaults\n");
rc.push_str("build --cpu=x86_64\n");
rc.push_str("build --copt=-march=x86-64\n");
rc.push_str("build --copt=-mtune=generic\n\n");
rc.push_str("# C++ standard\n");
rc.push_str("build --cxxopt=-std=c++17\n\n");
rc.push_str("# Optimization\n");
rc.push_str("build:release --compilation_mode=opt\n");
rc.push_str("build:release --copt=-O2\n");
rc.push_str("build:debug --compilation_mode=dbg\n");
rc.push_str("build:debug --copt=-g\n\n");
rc.push_str("# Parallelism\n");
rc.push_str("build --jobs=auto\n\n");
rc.push_str("# Disk cache\n");
rc.push_str("build --disk_cache=~/.cache/bazel\n");
rc
}
pub fn add_target(&mut self, target: X86BazelTarget) {
self.targets.push(target);
}
pub fn add_external_dep(&mut self, dep: X86BazelExternalDep) {
self.external_deps.push(dep);
}
}
impl Default for X86BazelSupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BazelTargetType {
CCLibrary,
CCBinary,
CCTest,
CCImport,
}
#[derive(Debug, Clone)]
pub struct X86BazelTarget {
pub name: String,
pub target_type: X86BazelTargetType,
pub srcs: Vec<String>,
pub hdrs: Vec<String>,
pub deps: Vec<String>,
pub copts: Vec<String>,
pub linkopts: Vec<String>,
pub defines: Vec<String>,
pub includes: Vec<String>,
pub visibility: Vec<String>,
pub tags: Vec<String>,
pub alwayslink: bool,
pub linkstatic: bool,
pub size: Option<String>,
pub timeout: Option<String>,
pub import_library: Option<String>,
}
impl X86BazelTarget {
pub fn new_cc_library(name: &str) -> Self {
Self {
name: name.to_string(),
target_type: X86BazelTargetType::CCLibrary,
srcs: Vec::new(),
hdrs: Vec::new(),
deps: Vec::new(),
copts: Vec::new(),
linkopts: Vec::new(),
defines: Vec::new(),
includes: Vec::new(),
visibility: vec!["//visibility:public".to_string()],
tags: Vec::new(),
alwayslink: false,
linkstatic: false,
size: None,
timeout: None,
import_library: None,
}
}
pub fn new_cc_binary(name: &str) -> Self {
let mut t = Self::new_cc_library(name);
t.target_type = X86BazelTargetType::CCBinary;
t
}
pub fn new_cc_test(name: &str) -> Self {
let mut t = Self::new_cc_library(name);
t.target_type = X86BazelTargetType::CCTest;
t.size = Some("small".to_string());
t
}
}
#[derive(Debug, Clone)]
pub struct X86BazelToolchain {
pub name: String,
pub cpu: String,
pub compiler: String,
pub target_cpu: String,
pub cxx_builtin_include_directories: Vec<String>,
pub tool_paths: HashMap<String, String>,
pub compiler_flags: Vec<String>,
pub linker_flags: Vec<String>,
}
impl X86BazelToolchain {
pub fn x86_64_clang() -> Self {
Self {
name: "x86_64_clang".to_string(),
cpu: "x86_64".to_string(),
compiler: "clang".to_string(),
target_cpu: "x86_64".to_string(),
cxx_builtin_include_directories: vec![
"/usr/lib/clang/18/include".to_string(),
"/usr/include/c++/13".to_string(),
"/usr/include/x86_64-linux-gnu".to_string(),
],
tool_paths: {
let mut m = HashMap::new();
m.insert("gcc".to_string(), "/usr/bin/clang".to_string());
m.insert("g++".to_string(), "/usr/bin/clang++".to_string());
m.insert("ar".to_string(), "/usr/bin/llvm-ar".to_string());
m.insert("ld".to_string(), "/usr/bin/lld".to_string());
m.insert("strip".to_string(), "/usr/bin/llvm-strip".to_string());
m
},
compiler_flags: vec![
"-march=x86-64".to_string(),
"-mtune=generic".to_string(),
],
linker_flags: vec![
"-fuse-ld=lld".to_string(),
],
}
}
pub fn generate_toolchain_bzl(&self) -> String {
let mut bzl = String::new();
bzl.push_str(&format!("# Toolchain definition: {}\n", self.name));
bzl.push_str("load(\"@rules_cc//cc:defs.bzl\", \"cc_toolchain\", \"cc_toolchain_suite\")\n\n");
bzl.push_str("cc_toolchain(\n");
bzl.push_str(&format!(" name = \"{}\",\n", self.name));
bzl.push_str(" all_files = \":empty\",\n");
bzl.push_str(" compiler_files = \":empty\",\n");
bzl.push_str(" dwp_files = \":empty\",\n");
bzl.push_str(" linker_files = \":empty\",\n");
bzl.push_str(" objcopy_files = \":empty\",\n");
bzl.push_str(" strip_files = \":empty\",\n");
bzl.push_str(" supports_param_files = 0,\n");
bzl.push_str(&format!(" cpu = \"{}\",\n", self.cpu));
bzl.push_str(&format!(" compiler = \"{}\",\n", self.compiler));
bzl.push_str(")\n");
bzl
}
}
impl Default for X86BazelToolchain {
fn default() -> Self {
Self::x86_64_clang()
}
}
#[derive(Debug, Clone)]
pub struct X86BazelPlatform {
pub name: String,
pub constraint_values: Vec<String>,
pub exec_properties: HashMap<String, String>,
}
impl X86BazelPlatform {
pub fn x86_64_linux() -> Self {
Self {
name: "x86_64_linux".to_string(),
constraint_values: vec![
"@platforms//cpu:x86_64".to_string(),
"@platforms//os:linux".to_string(),
],
exec_properties: {
let mut m = HashMap::new();
m.insert("OSFamily".to_string(), "Linux".to_string());
m.insert("Arch".to_string(), "amd64".to_string());
m
},
}
}
pub fn i686_linux() -> Self {
Self {
name: "i686_linux".to_string(),
constraint_values: vec![
"@platforms//cpu:x86_32".to_string(),
"@platforms//os:linux".to_string(),
],
exec_properties: {
let mut m = HashMap::new();
m.insert("OSFamily".to_string(), "Linux".to_string());
m.insert("Arch".to_string(), "i386".to_string());
m
},
}
}
pub fn generate(&self) -> String {
let mut platform = String::new();
platform.push_str(&format!(
"platform(\n name = \"{}\",\n constraint_values = [\n",
self.name
));
for cv in &self.constraint_values {
platform.push_str(&format!(" \"{}\",\n", cv));
}
platform.push_str(" ],\n");
if !self.exec_properties.is_empty() {
platform.push_str(" exec_properties = {\n");
for (k, v) in &self.exec_properties {
platform.push_str(&format!(" \"{}\": \"{}\",\n", k, v));
}
platform.push_str(" },\n");
}
platform.push_str(")\n");
platform
}
}
#[derive(Debug, Clone)]
pub struct X86BazelExternalDep {
pub name: String,
pub version: String,
pub urls: Vec<String>,
pub sha256: Option<String>,
pub strip_prefix: Option<String>,
pub build_file: Option<String>,
pub git_repository: Option<String>,
pub git_tag: Option<String>,
pub module_name: Option<String>,
}
impl X86BazelExternalDep {
pub fn new(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
urls: Vec::new(),
sha256: None,
strip_prefix: None,
build_file: None,
git_repository: None,
git_tag: None,
module_name: None,
}
}
pub fn generate_workspace(&self) -> String {
if let Some(ref repo) = self.git_repository {
let mut ws = String::new();
ws.push_str("git_repository(\n");
ws.push_str(&format!(" name = \"{}\",\n", self.name));
ws.push_str(&format!(" remote = \"{}\",\n", repo));
if let Some(ref tag) = self.git_tag {
ws.push_str(&format!(" tag = \"{}\",\n", tag));
}
ws.push_str(")\n");
return ws;
}
let mut ws = String::new();
ws.push_str("http_archive(\n");
ws.push_str(&format!(" name = \"{}\",\n", self.name));
ws.push_str(" urls = [\n");
for url in &self.urls {
ws.push_str(&format!(" \"{}\",\n", url));
}
ws.push_str(" ],\n");
if let Some(ref sha) = self.sha256 {
ws.push_str(&format!(" sha256 = \"{}\",\n", sha));
}
if let Some(ref strip) = self.strip_prefix {
ws.push_str(&format!(" strip_prefix = \"{}\",\n", strip));
}
if let Some(ref bf) = self.build_file {
ws.push_str(&format!(" build_file = \"{}\",\n", bf));
}
ws.push_str(")\n");
ws
}
pub fn generate_module_bazel(&self) -> String {
format!(
"bazel_dep(name = \"{}\", version = \"{}\")\n",
self.name, self.version
)
}
}
#[derive(Debug, Clone)]
pub struct X86MesonSupport {
pub project_name: String,
pub project_version: String,
pub default_options: Vec<String>,
pub meson_version: String,
pub license: String,
pub subprojects: Vec<String>,
pub dependencies: Vec<X86MesonDependency>,
pub executables: Vec<X86MesonExecutable>,
pub libraries: Vec<X86MesonLibrary>,
pub tests: Vec<X86MesonTest>,
pub cross_file: Option<X86MesonCrossFile>,
pub wrap_files: Vec<X86MesonWrap>,
}
impl X86MesonSupport {
pub fn new(project_name: &str, version: &str) -> Self {
Self {
project_name: project_name.to_string(),
project_version: version.to_string(),
default_options: vec![
"c_std=c11".to_string(),
"cpp_std=c++17".to_string(),
"warning_level=3".to_string(),
"buildtype=release".to_string(),
"optimization=2".to_string(),
],
meson_version: ">=1.0.0".to_string(),
license: "MIT".to_string(),
subprojects: Vec::new(),
dependencies: Vec::new(),
executables: Vec::new(),
libraries: Vec::new(),
tests: Vec::new(),
cross_file: None,
wrap_files: Vec::new(),
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec!["meson.build".to_string()],
build_commands: vec![
format!("meson setup builddir"),
format!("meson compile -C builddir -j{}", build_systems.parallel_jobs),
],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
if self.cross_file.is_some() {
output.files_generated.push("x86_cross.txt".to_string());
}
for wrap in &self.wrap_files {
output.files_generated.push(format!("subprojects/{}.wrap", wrap.name));
}
Ok(output)
}
pub fn generate_meson_build(&self) -> String {
let mut meson = String::new();
meson.push_str(&format!(
"project(\n '{}',\n ['c', 'cpp'],\n version: '{}',\n",
self.project_name, self.project_version
));
meson.push_str(&format!(" meson_version: '{}',\n", self.meson_version));
meson.push_str(&format!(" license: '{}',\n", self.license));
if !self.default_options.is_empty() {
meson.push_str(" default_options: [\n");
for opt in &self.default_options {
meson.push_str(&format!(" '{}',\n", opt));
}
meson.push_str(" ],\n");
}
meson.push_str(")\n\n");
meson.push_str("# X86-specific compiler settings\n");
meson.push_str("cc = meson.get_compiler('c')\n");
meson.push_str("cxx = meson.get_compiler('cpp')\n\n");
meson.push_str("host_cpu = host_machine.cpu_family()\n");
meson.push_str("if host_cpu == 'x86_64'\n");
meson.push_str(" add_project_arguments('-m64', '-march=x86-64', language: ['c', 'cpp'])\n");
meson.push_str("elif host_cpu == 'x86'\n");
meson.push_str(" add_project_arguments('-m32', '-march=i686', language: ['c', 'cpp'])\n");
meson.push_str("endif\n\n");
for dep in &self.dependencies {
meson.push_str(&dep.generate());
meson.push('\n');
}
for lib in &self.libraries {
meson.push_str(&lib.generate());
meson.push('\n');
}
for exe in &self.executables {
meson.push_str(&exe.generate());
meson.push('\n');
}
for test in &self.tests {
meson.push_str(&test.generate());
meson.push('\n');
}
for sub in &self.subprojects {
meson.push_str(&format!("subproject('{}')\n", sub));
}
meson
}
pub fn add_executable(&mut self, exe: X86MesonExecutable) {
self.executables.push(exe);
}
pub fn add_library(&mut self, lib: X86MesonLibrary) {
self.libraries.push(lib);
}
pub fn add_dependency(&mut self, dep: X86MesonDependency) {
self.dependencies.push(dep);
}
pub fn add_test(&mut self, test: X86MesonTest) {
self.tests.push(test);
}
pub fn add_wrap(&mut self, wrap: X86MesonWrap) {
self.wrap_files.push(wrap);
}
}
impl Default for X86MesonSupport {
fn default() -> Self {
Self::new("x86_project", "0.1.0")
}
}
#[derive(Debug, Clone)]
pub struct X86MesonDependency {
pub name: String,
pub version: Option<String>,
pub required: bool,
pub fallback: Option<String>,
pub static_linking: bool,
pub method: Option<String>,
}
impl X86MesonDependency {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
version: None,
required: true,
fallback: None,
static_linking: false,
method: None,
}
}
pub fn generate(&self) -> String {
let mut args = vec![format!("'{}'", self.name)];
if let Some(ref ver) = self.version {
args.push(format!("version: '{}'", ver));
}
if !self.required {
args.push("required: false".to_string());
}
if let Some(ref fb) = self.fallback {
args.push(format!("fallback: ['{}', '{}_dep']", fb, fb));
}
if self.static_linking {
args.push("static: true".to_string());
}
if let Some(ref m) = self.method {
args.push(format!("method: '{}'", m));
}
format!("{}_dep = dependency({})", self.name.replace('-', "_"), args.join(", "))
}
}
#[derive(Debug, Clone)]
pub struct X86MesonExecutable {
pub name: String,
pub sources: Vec<String>,
pub dependencies: Vec<String>,
pub include_directories: Vec<String>,
pub cpp_args: Vec<String>,
pub link_args: Vec<String>,
pub install: bool,
pub gui_app: bool,
pub win_subsystem: Option<String>,
}
impl X86MesonExecutable {
pub fn new(name: &str, sources: &[&str]) -> Self {
Self {
name: name.to_string(),
sources: sources.iter().map(|s| s.to_string()).collect(),
dependencies: Vec::new(),
include_directories: Vec::new(),
cpp_args: Vec::new(),
link_args: Vec::new(),
install: true,
gui_app: false,
win_subsystem: None,
}
}
pub fn generate(&self) -> String {
let mut exe = String::new();
let sources_str = self.sources.iter()
.map(|s| format!("'{}'", s))
.collect::<Vec<_>>()
.join(", ");
exe.push_str(&format!(
"{} = executable(\n '{}',\n [{}],\n",
self.name, self.name, sources_str
));
if !self.dependencies.is_empty() {
exe.push_str(" dependencies: [");
exe.push_str(&self.dependencies.join(", "));
exe.push_str("],\n");
}
if !self.include_directories.is_empty() {
let incs = self.include_directories.iter()
.map(|d| format!("include_directories('{}')", d))
.collect::<Vec<_>>()
.join(", ");
exe.push_str(&format!(" include_directories: [{}],\n", incs));
}
if !self.cpp_args.is_empty() {
let args = self.cpp_args.iter().map(|a| format!("'{}'", a)).collect::<Vec<_>>().join(", ");
exe.push_str(&format!(" cpp_args: [{}],\n", args));
}
if !self.link_args.is_empty() {
let args = self.link_args.iter().map(|a| format!("'{}'", a)).collect::<Vec<_>>().join(", ");
exe.push_str(&format!(" link_args: [{}],\n", args));
}
if self.install {
exe.push_str(" install: true,\n");
} else {
exe.push_str(" install: false,\n");
}
if self.gui_app {
exe.push_str(" gui_app: true,\n");
}
exe.push_str(")\n");
exe
}
}
#[derive(Debug, Clone)]
pub struct X86MesonLibrary {
pub name: String,
pub sources: Vec<String>,
pub library_type: X86MesonLibraryType,
pub version: Option<String>,
pub soversion: Option<String>,
pub install: bool,
pub dependencies: Vec<String>,
}
impl X86MesonLibrary {
pub fn new_shared(name: &str, sources: &[&str]) -> Self {
Self {
name: name.to_string(),
sources: sources.iter().map(|s| s.to_string()).collect(),
library_type: X86MesonLibraryType::Shared,
version: Some("1.0.0".to_string()),
soversion: Some("1".to_string()),
install: true,
dependencies: Vec::new(),
}
}
pub fn new_static(name: &str, sources: &[&str]) -> Self {
let mut lib = Self::new_shared(name, sources);
lib.library_type = X86MesonLibraryType::Static;
lib.version = None;
lib.soversion = None;
lib
}
pub fn generate(&self) -> String {
let mut lib = String::new();
let sources_str = self.sources.iter()
.map(|s| format!("'{}'", s))
.collect::<Vec<_>>()
.join(", ");
let lib_type = match self.library_type {
X86MesonLibraryType::Shared => "shared_library",
X86MesonLibraryType::Static => "static_library",
X86MesonLibraryType::Both => "both_libraries",
};
lib.push_str(&format!(
"{} = {}(\n '{}',\n [{}],\n",
self.name, lib_type, self.name, sources_str
));
if let Some(ref ver) = self.version {
lib.push_str(&format!(" version: '{}',\n", ver));
}
if let Some(ref so) = self.soversion {
lib.push_str(&format!(" soversion: '{}',\n", so));
}
if !self.dependencies.is_empty() {
lib.push_str(" dependencies: [");
lib.push_str(&self.dependencies.join(", "));
lib.push_str("],\n");
}
lib.push_str(&format!(
" install: {},\n",
if self.install { "true" } else { "false" }
));
lib.push_str(")\n");
lib
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MesonLibraryType {
Shared,
Static,
Both,
}
impl X86MesonCrossFile {
pub fn for_x86_64_windows() -> Self {
Self {
target_system: "windows".to_string(),
cpu_family: "x86_64".to_string(),
cpu: "x86_64".to_string(),
endian: "little".to_string(),
c_compiler: vec!["x86_64-w64-mingw32-clang".to_string()],
cpp_compiler: vec!["x86_64-w64-mingw32-clang++".to_string()],
linker: vec!["x86_64-w64-mingw32-ld".to_string()],
archiver: vec!["x86_64-w64-mingw32-ar".to_string()],
strip: vec!["x86_64-w64-mingw32-strip".to_string()],
sys_root: Some("/usr/x86_64-w64-mingw32".to_string()),
pkg_config_path: Some("/usr/x86_64-w64-mingw32/lib/pkgconfig".to_string()),
cmake_toolchain_file: None,
properties: {
let mut m = HashMap::new();
m.insert("needs_exe_wrapper".to_string(), "true".to_string());
m
},
}
}
pub fn for_x86_64_macos() -> Self {
Self {
target_system: "darwin".to_string(),
cpu_family: "x86_64".to_string(),
cpu: "x86_64".to_string(),
endian: "little".to_string(),
c_compiler: vec!["clang".to_string(), "-target".to_string(), "x86_64-apple-darwin".to_string()],
cpp_compiler: vec!["clang++".to_string(), "-target".to_string(), "x86_64-apple-darwin".to_string()],
linker: vec!["ld64.lld".to_string()],
archiver: vec!["llvm-ar".to_string()],
strip: vec!["llvm-strip".to_string()],
sys_root: None,
pkg_config_path: None,
cmake_toolchain_file: None,
properties: {
let mut m = HashMap::new();
m.insert("needs_exe_wrapper".to_string(), "true".to_string());
m
},
}
}
pub fn for_native_x86_64() -> Self {
Self {
target_system: "linux".to_string(),
cpu_family: "x86_64".to_string(),
cpu: "x86_64".to_string(),
endian: "little".to_string(),
c_compiler: vec!["clang".to_string()],
cpp_compiler: vec!["clang++".to_string()],
linker: vec!["lld".to_string()],
archiver: vec!["llvm-ar".to_string()],
strip: vec!["llvm-strip".to_string()],
sys_root: None,
pkg_config_path: None,
cmake_toolchain_file: None,
properties: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86MesonTest {
pub name: String,
pub executable: String,
pub args: Vec<String>,
pub is_parallel: bool,
pub timeout: u32,
pub protocol: String,
pub env: HashMap<String, String>,
}
impl X86MesonTest {
pub fn new(name: &str, executable: &str) -> Self {
Self {
name: name.to_string(),
executable: executable.to_string(),
args: Vec::new(),
is_parallel: true,
timeout: 30,
protocol: "exitcode".to_string(),
env: HashMap::new(),
}
}
pub fn generate(&self) -> String {
let mut test = String::new();
test.push_str(&format!(
"test(\n '{}',\n {},\n",
self.name, self.executable
));
if !self.args.is_empty() {
let args = self.args.iter().map(|a| format!("'{}'", a)).collect::<Vec<_>>().join(", ");
test.push_str(&format!(" args: [{}],\n", args));
}
test.push_str(&format!(
" is_parallel: {},\n timeout: {},\n protocol: '{}',\n",
if self.is_parallel { "true" } else { "false" },
self.timeout,
self.protocol
));
if !self.env.is_empty() {
test.push_str(" env: {\n");
for (k, v) in &self.env {
test.push_str(&format!(" '{}': '{}',\n", k, v));
}
test.push_str(" },\n");
}
test.push_str(")\n");
test
}
}
#[derive(Debug, Clone)]
pub struct X86MesonCrossFile {
pub target_system: String,
pub cpu_family: String,
pub cpu: String,
pub endian: String,
pub c_compiler: Vec<String>,
pub cpp_compiler: Vec<String>,
pub linker: Vec<String>,
pub archiver: Vec<String>,
pub strip: Vec<String>,
pub sys_root: Option<String>,
pub pkg_config_path: Option<String>,
pub cmake_toolchain_file: Option<String>,
pub properties: HashMap<String, String>,
}
impl X86MesonCrossFile {
pub fn for_x86_64_linux() -> Self {
Self {
target_system: "linux".to_string(),
cpu_family: "x86_64".to_string(),
cpu: "x86_64".to_string(),
endian: "little".to_string(),
c_compiler: vec!["clang".to_string(), "-m64".to_string()],
cpp_compiler: vec!["clang++".to_string(), "-m64".to_string()],
linker: vec!["lld".to_string()],
archiver: vec!["llvm-ar".to_string()],
strip: vec!["llvm-strip".to_string()],
sys_root: None,
pkg_config_path: None,
cmake_toolchain_file: None,
properties: HashMap::new(),
}
}
pub fn for_i686_linux() -> Self {
let mut cf = Self::for_x86_64_linux();
cf.cpu_family = "x86".to_string();
cf.cpu = "i686".to_string();
cf.c_compiler = vec!["clang".to_string(), "-m32".to_string()];
cf.cpp_compiler = vec!["clang++".to_string(), "-m32".to_string()];
cf
}
pub fn generate(&self) -> String {
let mut cf = String::new();
cf.push_str("# X86 Meson cross-compilation file\n\n");
cf.push_str("[binaries]\n");
cf.push_str(&format!("c = {}\n", format_meson_array(&self.c_compiler)));
cf.push_str(&format!("cpp = {}\n", format_meson_array(&self.cpp_compiler)));
cf.push_str(&format!("ld = {}\n", format_meson_array(&self.linker)));
cf.push_str(&format!("ar = {}\n", format_meson_array(&self.archiver)));
cf.push_str(&format!("strip = {}\n", format_meson_array(&self.strip)));
if let Some(ref pkg) = self.pkg_config_path {
cf.push_str(&format!("pkg-config = '{}'\n", pkg));
}
cf.push_str("\n[host_machine]\n");
cf.push_str(&format!("system = '{}'\n", self.target_system));
cf.push_str(&format!("cpu_family = '{}'\n", self.cpu_family));
cf.push_str(&format!("cpu = '{}'\n", self.cpu));
cf.push_str(&format!("endian = '{}'\n", self.endian));
if let Some(ref sysroot) = self.sys_root {
cf.push_str(&format!("\n[built-in options]\n"));
cf.push_str(&format!("sys_root = '{}'\n", sysroot));
}
if let Some(ref cmake) = self.cmake_toolchain_file {
cf.push_str(&format!("\n[cmake]\n"));
cf.push_str(&format!("cmake_toolchain_file = '{}'\n", cmake));
}
if !self.properties.is_empty() {
cf.push_str("\n[properties]\n");
for (k, v) in &self.properties {
cf.push_str(&format!("{} = '{}'\n", k, v));
}
}
cf
}
}
fn format_meson_array(items: &[String]) -> String {
if items.len() == 1 {
format!("'{}'", items[0])
} else {
let inner = items.iter().map(|s| format!("'{}'", s)).collect::<Vec<_>>().join(", ");
format!("[{}]", inner)
}
}
#[derive(Debug, Clone)]
pub struct X86MesonWrap {
pub name: String,
pub directory: Option<String>,
pub url: Option<String>,
pub revision: Option<String>,
pub depth: Option<u32>,
pub patch_directory: Option<String>,
pub patch_url: Option<String>,
pub wrapdb_version: Option<String>,
}
impl X86MesonWrap {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
directory: None,
url: None,
revision: None,
depth: None,
patch_directory: None,
patch_url: None,
wrapdb_version: None,
}
}
pub fn generate(&self) -> String {
let mut wrap = String::new();
wrap.push_str("# Wrap file for dependency\n\n");
wrap.push_str("[wrap-file]\n");
if let Some(ref dir) = self.directory {
wrap.push_str(&format!("directory = {}\n", dir));
}
if let Some(ref url) = self.url {
wrap.push_str(&format!("source_url = {}\n", url));
}
if let Some(ref rev) = self.revision {
wrap.push_str(&format!("source_filename = {}.tar.gz\n", self.name));
wrap.push_str(&format!("source_hash = {}\n", rev));
}
if let Some(ref depth) = self.depth {
wrap.push_str(&format!("depth = {}\n", depth));
}
if let Some(ref pd) = self.patch_directory {
wrap.push_str(&format!("patch_directory = {}\n", pd));
}
if let Some(ref pu) = self.patch_url {
wrap.push_str(&format!("patch_url = {}\n", pu));
}
wrap
}
}
#[derive(Debug, Clone)]
pub struct X86AutotoolsSupport {
pub package_name: String,
pub package_version: String,
pub bug_report: String,
pub package_url: String,
pub tar_name: String,
pub init_automake: bool,
pub foreign_strictness: bool,
pub silent_rules: bool,
pub subdir_objects: bool,
pub checks: Vec<X86AutotoolsCheck>,
pub programs: Vec<X86AutotoolsProgram>,
pub libraries: Vec<X86AutotoolsLibrary>,
pub pkg_config_modules: Vec<X86AutotoolsPkgCheck>,
pub config_headers: Vec<String>,
pub ac_subst: HashMap<String, String>,
pub make_conditionals: Vec<String>,
pub extra_dist: Vec<String>,
}
impl X86AutotoolsSupport {
pub fn new(package: &str, version: &str) -> Self {
Self {
package_name: package.to_string(),
package_version: version.to_string(),
bug_report: format!("https://github.com/example/{}", package),
package_url: format!("https://github.com/example/{}", package),
tar_name: format!("{}-{}", package, version),
init_automake: true,
foreign_strictness: true,
silent_rules: true,
subdir_objects: true,
checks: vec![
X86AutotoolsCheck::new_prog_cc(),
X86AutotoolsCheck::new_prog_cxx(),
],
programs: Vec::new(),
libraries: Vec::new(),
pkg_config_modules: Vec::new(),
config_headers: Vec::new(),
ac_subst: HashMap::new(),
make_conditionals: Vec::new(),
extra_dist: Vec::new(),
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec![
"configure.ac".to_string(),
"Makefile.am".to_string(),
],
build_commands: vec![
"autoreconf --install".to_string(),
format!("./configure --prefix={}", build_systems.install_prefix.display()),
format!("make -j{}", build_systems.parallel_jobs),
],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
if !self.config_headers.is_empty() {
output.files_generated.push("config.h.in".to_string());
}
Ok(output)
}
pub fn generate_configure_ac(&self) -> String {
let mut ac = String::new();
ac.push_str("# configure.ac — Auto-generated for X86 Clang\n\n");
ac.push_str(&format!(
"AC_INIT([{}], [{}], [{}], [{}], [{}])\n",
self.package_name,
self.package_version,
self.bug_report,
self.tar_name,
self.package_url,
));
if self.init_automake {
let mut am_init = format!("AM_INIT_AUTOMAKE([");
let mut opts = Vec::new();
if self.foreign_strictness {
opts.push("foreign");
}
if self.silent_rules {
opts.push("silent-rules");
}
if self.subdir_objects {
opts.push("subdir-objects");
}
am_init.push_str(&opts.join(" "));
am_init.push_str("])\n");
ac.push_str(&am_init);
}
if !self.config_headers.is_empty() {
ac.push_str("AC_CONFIG_HEADERS([config.h])\n");
}
ac.push_str("\n# Checks for programs.\n");
for check in &self.checks {
ac.push_str(&check.generate());
ac.push('\n');
}
ac.push_str("\n# X86 target checks\n");
ac.push_str("AC_CANONICAL_HOST\n");
ac.push_str("case $host_cpu in\n");
ac.push_str(" x86_64|amd64)\n");
ac.push_str(" ARCH=x86_64\n");
ac.push_str(" CFLAGS=\"$CFLAGS -m64 -march=x86-64\"\n");
ac.push_str(" CXXFLAGS=\"$CXXFLAGS -m64 -march=x86-64\"\n");
ac.push_str(" ;;\n");
ac.push_str(" i?86)\n");
ac.push_str(" ARCH=i686\n");
ac.push_str(" CFLAGS=\"$CFLAGS -m32 -march=i686\"\n");
ac.push_str(" CXXFLAGS=\"$CXXFLAGS -m32 -march=i686\"\n");
ac.push_str(" ;;\n");
ac.push_str("esac\n");
ac.push_str("AC_SUBST([ARCH])\n\n");
for lib in &self.libraries {
ac.push_str(&lib.generate());
}
for pkg in &self.pkg_config_modules {
ac.push_str(&pkg.generate());
}
for (key, value) in &self.ac_subst {
ac.push_str(&format!("AC_SUBST([{}], [{}])\n", key, value));
}
for cond in &self.make_conditionals {
ac.push_str(&format!("AM_CONDITIONAL([{}], [test \"x${}\" = xyes])\n", cond, cond));
}
ac.push_str("\nAC_CONFIG_FILES([\n");
ac.push_str(" Makefile\n");
ac.push_str("])\n\n");
ac.push_str("AC_OUTPUT\n");
ac
}
pub fn generate_makefile_am(&self) -> String {
let mut am = String::new();
am.push_str("# Makefile.am — Auto-generated for X86 Clang\n\n");
if !self.programs.is_empty() {
for prog in &self.programs {
am.push_str(&prog.generate());
}
}
if !self.extra_dist.is_empty() {
am.push_str(&format!(
"EXTRA_DIST = {}\n",
self.extra_dist.join(" ")
));
}
am.push_str("\n# Standard Autotools targets\n");
am.push_str("ACLOCAL_AMFLAGS = -I m4\n");
am.push_str("AUTOMAKE_OPTIONS = foreign subdir-objects\n");
am
}
pub fn add_program(&mut self, prog: X86AutotoolsProgram) {
self.programs.push(prog);
}
pub fn add_library_check(&mut self, lib: X86AutotoolsLibrary) {
self.libraries.push(lib);
}
pub fn add_pkg_check(&mut self, pkg: X86AutotoolsPkgCheck) {
self.pkg_config_modules.push(pkg);
}
}
impl Default for X86AutotoolsSupport {
fn default() -> Self {
Self::new("x86_project", "0.1.0")
}
}
#[derive(Debug, Clone)]
pub struct X86AutotoolsCheck {
pub macro_name: String,
pub args: Vec<String>,
}
impl X86AutotoolsCheck {
pub fn new_prog_cc() -> Self {
Self {
macro_name: "AC_PROG_CC".to_string(),
args: vec![],
}
}
pub fn new_prog_cxx() -> Self {
Self {
macro_name: "AC_PROG_CXX".to_string(),
args: vec![],
}
}
pub fn new_check_lib(lib: &str, function: &str) -> Self {
Self {
macro_name: "AC_CHECK_LIB".to_string(),
args: vec![lib.to_string(), function.to_string()],
}
}
pub fn new_check_header(header: &str) -> Self {
Self {
macro_name: "AC_CHECK_HEADERS".to_string(),
args: vec![header.to_string()],
}
}
pub fn new_check_func(func: &str) -> Self {
Self {
macro_name: "AC_CHECK_FUNCS".to_string(),
args: vec![func.to_string()],
}
}
pub fn new_prog_install() -> Self {
Self {
macro_name: "AC_PROG_INSTALL".to_string(),
args: vec![],
}
}
pub fn new_prog_ranlib() -> Self {
Self {
macro_name: "AC_PROG_RANLIB".to_string(),
args: vec![],
}
}
pub fn new_prog_ar() -> Self {
Self {
macro_name: "AM_PROG_AR".to_string(),
args: vec![],
}
}
pub fn generate(&self) -> String {
if self.args.is_empty() {
format!("{}", self.macro_name)
} else {
format!("{}([{}])", self.macro_name, self.args.join("], ["))
}
}
}
#[derive(Debug, Clone)]
pub struct X86AutotoolsLibrary {
pub name: String,
pub function: String,
pub action_if_found: Option<String>,
pub action_if_not_found: Option<String>,
pub other_libraries: Vec<String>,
}
impl X86AutotoolsLibrary {
pub fn new(name: &str, function: &str) -> Self {
Self {
name: name.to_string(),
function: function.to_string(),
action_if_found: None,
action_if_not_found: None,
other_libraries: Vec::new(),
}
}
pub fn generate(&self) -> String {
let mut check = format!("# Check for lib{}\n", self.name);
check.push_str(&format!("AC_CHECK_LIB([{}], [{}]", self.name, self.function));
if let Some(ref found) = self.action_if_found {
check.push_str(&format!(", [{}]", found));
} else {
check.push_str(", [AC_DEFINE([HAVE_LIB");
check.push_str(&self.name.to_uppercase());
check.push_str("], [1], [Define if lib");
check.push_str(&self.name);
check.push_str(" is available])]");
}
if let Some(ref not_found) = self.action_if_not_found {
check.push_str(&format!(", [{}]", not_found));
} else {
check.push_str(", [AC_MSG_WARN([lib");
check.push_str(&self.name);
check.push_str(" not found, some features may be disabled])]");
}
if !self.other_libraries.is_empty() {
check.push_str(&format!(", [{}]", self.other_libraries.join(" ")));
}
check.push_str(")\n");
check
}
}
#[derive(Debug, Clone)]
pub struct X86AutotoolsPkgCheck {
pub modules: Vec<String>,
pub min_version: Option<String>,
pub action_if_found: Option<String>,
pub action_if_not_found: Option<String>,
}
impl X86AutotoolsPkgCheck {
pub fn new(modules: &[&str]) -> Self {
Self {
modules: modules.iter().map(|m| m.to_string()).collect(),
min_version: None,
action_if_found: None,
action_if_not_found: None,
}
}
pub fn generate(&self) -> String {
let mut check = String::new();
check.push_str("# pkg-config check\n");
check.push_str("PKG_PROG_PKG_CONFIG\n");
let modules_str = self.modules.iter()
.map(|m| format!("{} >= {}", m, self.min_version.as_deref().unwrap_or("0.0")))
.collect::<Vec<_>>()
.join(" ");
check.push_str(&format!("PKG_CHECK_MODULES([{}], [{}]", self.modules[0].to_uppercase(), modules_str));
if let Some(ref found) = self.action_if_found {
check.push_str(&format!(", [{}]", found));
}
if let Some(ref not_found) = self.action_if_not_found {
check.push_str(&format!(", [{}]", not_found));
}
check.push_str(")\n");
check
}
}
#[derive(Debug, Clone)]
pub struct X86AutotoolsProgram {
pub name: String,
pub sources: Vec<String>,
pub cppflags: Vec<String>,
pub ldadd: Vec<String>,
pub ldflags: Vec<String>,
}
impl X86AutotoolsProgram {
pub fn new(name: &str, sources: &[&str]) -> Self {
Self {
name: name.to_string(),
sources: sources.iter().map(|s| s.to_string()).collect(),
cppflags: vec![],
ldadd: vec![],
ldflags: vec![],
}
}
pub fn generate(&self) -> String {
let mut am = String::new();
am.push_str(&format!(
"bin_PROGRAMS = {}\n",
self.name
));
let src_var = format!("{}_SOURCES", self.name);
am.push_str(&format!(
"{} = {}\n",
src_var,
self.sources.join(" ")
));
if !self.cppflags.is_empty() {
let cpp_var = format!("{}_CPPFLAGS", self.name);
am.push_str(&format!(
"{} = {}\n",
cpp_var,
self.cppflags.join(" ")
));
}
if !self.ldadd.is_empty() {
let ld_var = format!("{}_LDADD", self.name);
am.push_str(&format!(
"{} = {}\n",
ld_var,
self.ldadd.join(" ")
));
}
if !self.ldflags.is_empty() {
let ldf_var = format!("{}_LDFLAGS", self.name);
am.push_str(&format!(
"{} = {}\n",
ldf_var,
self.ldflags.join(" ")
));
}
am.push('\n');
am
}
}
#[derive(Debug, Clone)]
pub struct X86XMakeSupport {
pub project_name: String,
pub project_version: String,
pub description: String,
pub targets: Vec<X86XMakeTarget>,
pub packages: Vec<X86XMakePackage>,
pub languages: Vec<String>,
pub rules: Vec<String>,
pub includes: Vec<String>,
pub set_arch: Option<String>,
pub set_plat: Option<String>,
pub set_mode: Option<String>,
}
impl X86XMakeSupport {
pub fn new(name: &str, version: &str) -> Self {
Self {
project_name: name.to_string(),
project_version: version.to_string(),
description: String::new(),
targets: Vec::new(),
packages: Vec::new(),
languages: vec!["c11".to_string(), "c++17".to_string()],
rules: vec!["mode.release".to_string(), "mode.debug".to_string()],
includes: Vec::new(),
set_arch: Some("x86_64".to_string()),
set_plat: Some("linux".to_string()),
set_mode: Some("release".to_string()),
}
}
pub fn generate(&self, build_systems: &X86BuildSystems) -> io::Result<X86BuildOutput> {
let mut output = X86BuildOutput {
files_generated: vec!["xmake.lua".to_string()],
build_commands: vec![
format!("xmake f -p linux -a x86_64 -m release"),
format!("xmake build -j{}", build_systems.parallel_jobs),
],
warnings: Vec::new(),
estimated_build_time_ms: 0,
};
Ok(output)
}
pub fn generate_xmake_lua(&self) -> String {
let mut lua = String::new();
lua.push_str("-- xmake.lua — Auto-generated for X86 Clang\n\n");
if let Some(ref arch) = self.set_arch {
lua.push_str(&format!("set_arch(\"{}\")\n", arch));
}
if let Some(ref plat) = self.set_plat {
lua.push_str(&format!("set_plat(\"{}\")\n", plat));
}
if let Some(ref mode) = self.set_mode {
lua.push_str(&format!("set_mode(\"{}\")\n", mode));
}
for rule in &self.rules {
lua.push_str(&format!("add_rules(\"{}\")\n", rule));
}
lua.push('\n');
for target in &self.targets {
lua.push_str(&target.generate());
lua.push('\n');
}
for pkg in &self.packages {
lua.push_str(&pkg.generate());
}
for inc in &self.includes {
lua.push_str(&format!("includes(\"{}\")\n", inc));
}
lua
}
pub fn add_target(&mut self, target: X86XMakeTarget) {
self.targets.push(target);
}
pub fn add_package(&mut self, pkg: X86XMakePackage) {
self.packages.push(pkg);
}
pub fn add_rule(&mut self, rule: &str) {
self.rules.push(rule.to_string());
}
}
impl Default for X86XMakeSupport {
fn default() -> Self {
Self::new("x86_project", "0.1.0")
}
}
#[derive(Debug, Clone)]
pub struct X86XMakeTarget {
pub name: String,
pub kind: String,
pub files: Vec<String>,
pub deps: Vec<String>,
pub packages: Vec<String>,
pub defines: Vec<String>,
pub includes: Vec<String>,
pub links: Vec<String>,
pub cxflags: Vec<String>,
pub ldflags: Vec<String>,
pub languages: Vec<String>,
pub targetdir: Option<String>,
pub objectdir: Option<String>,
}
impl X86XMakeTarget {
pub fn new_binary(name: &str) -> Self {
Self {
name: name.to_string(),
kind: "binary".to_string(),
files: Vec::new(),
deps: Vec::new(),
packages: Vec::new(),
defines: Vec::new(),
includes: Vec::new(),
links: Vec::new(),
cxflags: vec!["-march=x86-64".to_string(), "-mtune=generic".to_string()],
ldflags: Vec::new(),
languages: vec!["c11".to_string(), "c++17".to_string()],
targetdir: None,
objectdir: None,
}
}
pub fn new_static_library(name: &str) -> Self {
let mut t = Self::new_binary(name);
t.kind = "static".to_string();
t
}
pub fn new_shared_library(name: &str) -> Self {
let mut t = Self::new_binary(name);
t.kind = "shared".to_string();
t
}
pub fn generate(&self) -> String {
let mut lua = String::new();
lua.push_str(&format!("target(\"{}\")\n", self.name));
lua.push_str(&format!(" set_kind(\"{}\")\n", self.kind));
if !self.languages.is_empty() {
lua.push_str(" set_languages(\"");
lua.push_str(&self.languages.join("\", \""));
lua.push_str("\")\n");
}
if !self.files.is_empty() {
lua.push_str(" add_files(\"");
lua.push_str(&self.files.join("\", \""));
lua.push_str("\")\n");
}
if !self.defines.is_empty() {
lua.push_str(" add_defines(\"");
lua.push_str(&self.defines.join("\", \""));
lua.push_str("\")\n");
}
if !self.includes.is_empty() {
lua.push_str(" add_includedirs(\"");
lua.push_str(&self.includes.join("\", \""));
lua.push_str("\")\n");
}
if !self.links.is_empty() {
lua.push_str(" add_links(\"");
lua.push_str(&self.links.join("\", \""));
lua.push_str("\")\n");
}
if !self.cxflags.is_empty() {
lua.push_str(" add_cxflags(\"");
lua.push_str(&self.cxflags.join("\", \""));
lua.push_str("\")\n");
}
if !self.ldflags.is_empty() {
lua.push_str(" add_ldflags(\"");
lua.push_str(&self.ldflags.join("\", \""));
lua.push_str("\")\n");
}
if !self.deps.is_empty() {
lua.push_str(" add_deps(\"");
lua.push_str(&self.deps.join("\", \""));
lua.push_str("\")\n");
}
if !self.packages.is_empty() {
lua.push_str(" add_packages(\"");
lua.push_str(&self.packages.join("\", \""));
lua.push_str("\")\n");
}
if let Some(ref td) = self.targetdir {
lua.push_str(&format!(" set_targetdir(\"{}\")\n", td));
}
if let Some(ref od) = self.objectdir {
lua.push_str(&format!(" set_objectdir(\"{}\")\n", od));
}
lua.push_str("\n");
lua
}
}
#[derive(Debug, Clone)]
pub struct X86XMakePackage {
pub name: String,
pub version: Option<String>,
pub description: String,
pub links: Vec<String>,
pub configs: HashMap<String, String>,
}
impl X86XMakePackage {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
version: None,
description: String::new(),
links: Vec::new(),
configs: HashMap::new(),
}
}
pub fn generate(&self) -> String {
let mut lua = String::new();
lua.push_str(&format!("add_requires(\"{}\"", self.name));
if let Some(ref ver) = self.version {
lua.push_str(&format!(" {}", ver));
}
if !self.configs.is_empty() {
lua.push_str(", {configs = {");
let entries: Vec<String> = self.configs.iter()
.map(|(k, v)| format!("{} = \"{}\"", k, v))
.collect();
lua.push_str(&entries.join(", "));
lua.push_str("}}");
}
lua.push_str(")\n");
if !self.links.is_empty() {
lua.push_str(&format!(
"add_packages(\"{}\")\n",
self.name
));
}
lua
}
}
#[derive(Debug, Clone)]
pub struct X86BuildGraph {
pub nodes: Vec<X86GraphNode>,
pub edges: HashMap<String, Vec<String>>,
pub reverse_edges: HashMap<String, Vec<String>>,
pub topo_order: Option<Vec<String>>,
pub critical_path: Option<Vec<String>>,
pub critical_path_duration_ms: u64,
pub parallel_schedule: Option<Vec<Vec<String>>>,
pub estimated_time_ms: u64,
}
impl X86BuildGraph {
pub fn empty() -> Self {
Self {
nodes: Vec::new(),
edges: HashMap::new(),
reverse_edges: HashMap::new(),
topo_order: None,
critical_path: None,
critical_path_duration_ms: 0,
parallel_schedule: None,
estimated_time_ms: 0,
}
}
pub fn from_targets(targets: &[X86BuildTarget]) -> Self {
let mut graph = Self::empty();
for target in targets {
let node = X86GraphNode {
name: target.name.clone(),
target_type: target.target_type.clone(),
duration_ms: target.compile_time_estimate_ms,
earliest_start: 0,
earliest_finish: 0,
latest_start: 0,
latest_finish: 0,
slack: 0,
on_critical_path: false,
level: 0,
};
graph.add_node(node);
for dep in &target.dependencies {
graph.add_edge(&target.name, dep);
}
}
graph.compute_topological_order();
graph.compute_critical_path();
graph.compute_parallel_schedule(8);
graph
}
pub fn add_node(&mut self, node: X86GraphNode) {
self.nodes.push(node);
}
pub fn add_edge(&mut self, from: &str, to: &str) {
self.edges.entry(from.to_string()).or_default().push(to.to_string());
self.reverse_edges.entry(to.to_string()).or_default().push(from.to_string());
}
pub fn compute_topological_order(&mut self) {
let mut in_degree: HashMap<String, usize> = HashMap::new();
for node in &self.nodes {
in_degree.entry(node.name.clone()).or_insert(0);
}
for (from, to_list) in &self.edges {
in_degree.entry(from.clone()).or_insert(0);
for to in to_list {
*in_degree.entry(to.clone()).or_insert(0) += 1;
}
}
let mut queue: VecDeque<String> = VecDeque::new();
for (name, °) in &in_degree {
if deg == 0 {
queue.push_back(name.clone());
}
}
let mut order = Vec::new();
while let Some(name) = queue.pop_front() {
order.push(name.clone());
if let Some(neighbors) = self.edges.get(&name) {
for neighbor in neighbors {
if let Some(deg) = in_degree.get_mut(neighbor) {
*deg -= 1;
if *deg == 0 {
queue.push_back(neighbor.clone());
}
}
}
}
}
let mut levels: HashMap<String, u32> = HashMap::new();
for node_name in &order {
let mut max_level = 0u32;
if let Some(deps) = self.edges.get(node_name) {
for dep in deps {
if let Some(&lvl) = levels.get(dep) {
max_level = max_level.max(lvl + 1);
}
}
}
levels.insert(node_name.clone(), max_level);
}
for node in &mut self.nodes {
if let Some(&lvl) = levels.get(&node.name) {
node.level = lvl;
}
}
self.topo_order = Some(order);
}
pub fn compute_critical_path(&mut self) {
let topo = match &self.topo_order {
Some(t) => t.clone(),
None => {
self.compute_topological_order();
self.topo_order.clone().unwrap_or_default()
}
};
if topo.is_empty() {
return;
}
let mut earliest_start: HashMap<String, u64> = HashMap::new();
let mut earliest_finish: HashMap<String, u64> = HashMap::new();
for name in &topo {
let duration = self.get_node_duration(name);
let mut max_pred_finish = 0u64;
if let Some(preds) = self.edges.get(name) {
for pred in preds {
if let Some(&ef) = earliest_finish.get(pred) {
max_pred_finish = max_pred_finish.max(ef);
}
}
}
let es = max_pred_finish;
let ef = es + duration;
earliest_start.insert(name.clone(), es);
earliest_finish.insert(name.clone(), ef);
}
let last_finish = earliest_finish.values().max().copied().unwrap_or(0);
self.critical_path_duration_ms = last_finish;
let mut latest_finish: HashMap<String, u64> = HashMap::new();
let mut latest_start: HashMap<String, u64> = HashMap::new();
for name in topo.iter().rev() {
let mut min_succ_start = last_finish;
if let Some(succs) = self.reverse_edges.get(name) {
for succ in succs {
if let Some(&ls) = latest_start.get(succ) {
min_succ_start = min_succ_start.min(ls);
}
}
}
let duration = self.get_node_duration(name);
let lf = min_succ_start;
let ls = lf.saturating_sub(duration);
latest_finish.insert(name.clone(), lf);
latest_start.insert(name.clone(), ls);
}
let mut critical_nodes = Vec::new();
let mut name_to_idx: HashMap<String, usize> = HashMap::new();
for (i, node) in self.nodes.iter().enumerate() {
name_to_idx.insert(node.name.clone(), i);
}
for name in &topo {
if let Some(&idx) = name_to_idx.get(name) {
let es = earliest_start.get(name).copied().unwrap_or(0);
let ef = earliest_finish.get(name).copied().unwrap_or(0);
let ls = latest_start.get(name).copied().unwrap_or(0);
let lf = latest_finish.get(name).copied().unwrap_or(0);
let slack = ls.saturating_sub(es);
self.nodes[idx].earliest_start = es;
self.nodes[idx].earliest_finish = ef;
self.nodes[idx].latest_start = ls;
self.nodes[idx].latest_finish = lf;
self.nodes[idx].slack = slack;
if slack == 0 {
self.nodes[idx].on_critical_path = true;
critical_nodes.push(name.clone());
}
}
}
self.critical_path = Some(critical_nodes);
}
pub fn compute_parallel_schedule(&mut self, max_workers: usize) {
let topo = match &self.topo_order {
Some(t) => t.clone(),
None => {
self.compute_topological_order();
self.topo_order.clone().unwrap_or_default()
}
};
if topo.is_empty() {
self.parallel_schedule = Some(Vec::new());
return;
}
let mut level_groups: HashMap<u32, Vec<String>> = HashMap::new();
let mut name_to_node: HashMap<String, &X86GraphNode> = HashMap::new();
for node in &self.nodes {
name_to_node.insert(node.name.clone(), node);
level_groups.entry(node.level).or_default().push(node.name.clone());
}
let max_level = level_groups.keys().max().copied().unwrap_or(0);
let mut schedule: Vec<Vec<String>> = Vec::new();
let mut total_time = 0u64;
for level in 0..=max_level {
if let Some(nodes) = level_groups.get(&level) {
let mut sorted_nodes = nodes.clone();
sorted_nodes.sort_by_key(|n| {
let dur = name_to_node.get(n).map(|nd| nd.duration_ms).unwrap_or(0);
Reverse(dur)
});
let num_workers = max_workers.min(sorted_nodes.len().max(1));
let mut worker_time: Vec<u64> = vec![0; num_workers];
for node_name in sorted_nodes {
let min_worker = worker_time.iter().enumerate()
.min_by_key(|&(_, &t)| t)
.map(|(i, _)| i)
.unwrap_or(0);
let duration = name_to_node.get(&node_name).map(|nd| nd.duration_ms).unwrap_or(0);
worker_time[min_worker] += duration;
}
let level_time = worker_time.iter().max().copied().unwrap_or(0);
total_time += level_time;
let num_waves = if num_workers == 0 { 1 } else { (nodes.len() + num_workers - 1) / num_workers };
for wave in 0..num_waves {
let mut wave_nodes = Vec::new();
for (j, node_name) in nodes.iter().enumerate() {
if j % num_workers == wave {
wave_nodes.push(node_name.clone());
}
}
if !wave_nodes.is_empty() {
schedule.push(wave_nodes);
}
}
}
}
self.estimated_time_ms = total_time;
self.parallel_schedule = Some(schedule);
}
fn get_node_duration(&self, name: &str) -> u64 {
self.nodes.iter()
.find(|n| n.name == name)
.map(|n| n.duration_ms)
.unwrap_or(100)
}
pub fn topological_order(&mut self) -> Vec<String> {
if self.topo_order.is_none() {
self.compute_topological_order();
}
self.topo_order.clone().unwrap_or_default()
}
pub fn critical_path_nodes(&mut self) -> Vec<String> {
if self.critical_path.is_none() {
self.compute_critical_path();
}
self.critical_path.clone().unwrap_or_default()
}
pub fn has_cycle(&self) -> bool {
if let Some(ref topo) = self.topo_order {
let total_nodes: usize = self.nodes.len();
let mut all_names: HashSet<&str> = HashSet::new();
for node in &self.nodes {
all_names.insert(&node.name);
}
for (from, to_list) in &self.edges {
all_names.insert(from);
for to in to_list {
all_names.insert(to);
}
}
topo.len() < all_names.len()
} else {
false
}
}
pub fn to_dot(&self) -> String {
let mut dot = String::new();
dot.push_str("digraph X86BuildGraph {\n");
dot.push_str(" rankdir=LR;\n");
dot.push_str(" node [shape=box, style=filled, fillcolor=lightblue];\n\n");
for node in &self.nodes {
let color = if node.on_critical_path { "coral" } else { "lightblue" };
dot.push_str(&format!(
" \"{}\" [label=\"{}\\n{}ms\", fillcolor={}];\n",
node.name, node.name, node.duration_ms, color
));
}
for (from, to_list) in &self.edges {
for to in to_list {
dot.push_str(&format!(" \"{}\" -> \"{}\";\n", from, to));
}
}
dot.push_str("}\n");
dot
}
}
impl Default for X86BuildGraph {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone)]
pub struct X86GraphNode {
pub name: String,
pub target_type: String,
pub duration_ms: u64,
pub earliest_start: u64,
pub earliest_finish: u64,
pub latest_start: u64,
pub latest_finish: u64,
pub slack: u64,
pub on_critical_path: bool,
pub level: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_backend_as_str() {
assert_eq!(X86BuildBackend::CMake.as_str(), "cmake");
assert_eq!(X86BuildBackend::Make.as_str(), "make");
assert_eq!(X86BuildBackend::Ninja.as_str(), "ninja");
assert_eq!(X86BuildBackend::Meson.as_str(), "meson");
assert_eq!(X86BuildBackend::Bazel.as_str(), "bazel");
assert_eq!(X86BuildBackend::Autotools.as_str(), "autotools");
assert_eq!(X86BuildBackend::XMake.as_str(), "xmake");
}
#[test]
fn test_build_backend_config_file() {
assert_eq!(X86BuildBackend::CMake.config_file(), "CMakeLists.txt");
assert_eq!(X86BuildBackend::Make.config_file(), "Makefile");
assert_eq!(X86BuildBackend::Ninja.config_file(), "build.ninja");
assert_eq!(X86BuildBackend::Meson.config_file(), "meson.build");
assert_eq!(X86BuildBackend::Bazel.config_file(), "BUILD");
assert_eq!(X86BuildBackend::Autotools.config_file(), "configure.ac");
assert_eq!(X86BuildBackend::XMake.config_file(), "xmake.lua");
}
#[test]
fn test_build_backend_from_str() {
assert_eq!(X86BuildBackend::from_str("cmake"), Some(X86BuildBackend::CMake));
assert_eq!(X86BuildBackend::from_str("make"), Some(X86BuildBackend::Make));
assert_eq!(X86BuildBackend::from_str("ninja"), Some(X86BuildBackend::Ninja));
assert_eq!(X86BuildBackend::from_str("bazel"), Some(X86BuildBackend::Bazel));
assert_eq!(X86BuildBackend::from_str("unknown"), None);
}
#[test]
fn test_build_backend_display() {
assert_eq!(format!("{}", X86BuildBackend::CMake), "cmake");
assert_eq!(format!("{}", X86BuildBackend::Ninja), "ninja");
}
#[test]
fn test_opt_level_as_str() {
assert_eq!(X86BuildOptLevel::O0.as_str(), "0");
assert_eq!(X86BuildOptLevel::O1.as_str(), "1");
assert_eq!(X86BuildOptLevel::O2.as_str(), "2");
assert_eq!(X86BuildOptLevel::O3.as_str(), "3");
assert_eq!(X86BuildOptLevel::Os.as_str(), "s");
assert_eq!(X86BuildOptLevel::Oz.as_str(), "z");
}
#[test]
fn test_opt_level_flag() {
assert_eq!(X86BuildOptLevel::O2.flag(), "-O2");
assert_eq!(X86BuildOptLevel::O0.flag(), "-O0");
assert_eq!(X86BuildOptLevel::Os.flag(), "-Os");
}
#[test]
fn test_build_systems_new() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "x86_64");
assert_eq!(bs.opt_level, X86BuildOptLevel::O2);
assert!(!bs.debug_info);
}
#[test]
fn test_build_systems_for_i686() {
let bs = X86BuildSystems::for_i686(X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "i686");
assert!(bs.c_flags.iter().any(|f| f == "-m32"));
}
#[test]
fn test_build_systems_for_pentium4() {
let bs = X86BuildSystems::for_pentium4(X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "pentium4");
assert!(bs.c_flags.iter().any(|f| f == "-m32"));
}
#[test]
fn test_build_systems_for_x86_64_avx2() {
let bs = X86BuildSystems::for_x86_64_avx2(X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "x86-64-v3");
}
#[test]
fn test_build_systems_for_x86_64_avx512() {
let bs = X86BuildSystems::for_x86_64_avx512(X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "x86-64-v4");
}
#[test]
fn test_build_systems_with_opt_level() {
let bs = X86BuildSystems::new(X86BuildBackend::Make)
.with_opt_level(X86BuildOptLevel::O3);
assert_eq!(bs.opt_level, X86BuildOptLevel::O3);
}
#[test]
fn test_build_systems_with_debug_info() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake)
.with_debug_info(true);
assert!(bs.debug_info);
}
#[test]
fn test_build_systems_with_lto() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake)
.with_lto(true);
assert!(bs.lto);
}
#[test]
fn test_build_systems_with_parallel_jobs() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake)
.with_parallel_jobs(16);
assert_eq!(bs.parallel_jobs, 16);
}
#[test]
fn test_build_systems_compiler_flags() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake);
let flags = bs.compiler_flags();
assert!(flags.c_flags.iter().any(|f| f.contains("-O")));
assert!(flags.cxx_flags.iter().any(|f| f.contains("c++")));
}
#[test]
fn test_build_systems_default() {
let bs = X86BuildSystems::default();
assert_eq!(bs.backend, X86BuildBackend::CMake);
}
#[test]
fn test_compiler_flags_join() {
let flags = X86CompilerFlags {
c_flags: vec!["-Wall".to_string(), "-O2".to_string()],
cxx_flags: vec!["-Wall".to_string(), "-std=c++17".to_string()],
ld_flags: vec!["-lpthread".to_string()],
};
assert_eq!(flags.join_c_flags(), "-Wall -O2");
assert_eq!(flags.join_cxx_flags(), "-Wall -std=c++17");
assert_eq!(flags.join_ld_flags(), "-lpthread");
}
#[test]
fn test_cxx_standard_as_number() {
assert_eq!(X86CxxStandard::CXX11.as_number(), 11);
assert_eq!(X86CxxStandard::CXX14.as_number(), 14);
assert_eq!(X86CxxStandard::CXX17.as_number(), 17);
assert_eq!(X86CxxStandard::CXX20.as_number(), 20);
assert_eq!(X86CxxStandard::CXX23.as_number(), 23);
}
#[test]
fn test_cxx_standard_as_str() {
assert_eq!(X86CxxStandard::CXX17.as_str(), "cxx_std_17");
assert_eq!(X86CxxStandard::CXX20.as_str(), "cxx_std_20");
}
#[test]
fn test_cxx_standard_flag() {
assert_eq!(X86CxxStandard::CXX17.flag(), "-std=c++17");
assert_eq!(X86CxxStandard::CXX23.flag(), "-std=c++23");
}
#[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_support_new() {
let cmake = X86CMakeSupport::new();
assert_eq!(cmake.cmake_minimum_version, "3.20");
assert_eq!(cmake.cxx_standard, X86CxxStandard::CXX17);
assert!(cmake.compile_commands);
}
#[test]
fn test_cmake_support_generate_cmake_lists_empty() {
let cmake = X86CMakeSupport::new();
let content = cmake.generate_cmake_lists();
assert!(content.contains("cmake_minimum_required"));
assert!(content.contains("CMAKE_CXX_STANDARD"));
assert!(content.contains("CMAKE_EXPORT_COMPILE_COMMANDS"));
}
#[test]
fn test_cmake_support_with_project() {
let mut cmake = X86CMakeSupport::new();
let project = X86CMakeProject::new("test_project", "1.0.0");
cmake.add_project(project);
let content = cmake.generate_cmake_lists();
assert!(content.contains("test_project"));
}
#[test]
fn test_cmake_support_generate_toolchain_x86_64() {
let cmake = X86CMakeSupport::new();
let tc = cmake.generate_toolchain_x86_64_linux();
assert!(tc.contains("CMAKE_SYSTEM_NAME Linux"));
assert!(tc.contains("CMAKE_SYSTEM_PROCESSOR x86_64"));
assert!(tc.contains("clang"));
}
#[test]
fn test_cmake_support_generate_cpm_setup() {
let cmake = X86CMakeSupport::new();
let cpm = cmake.generate_cpm_setup();
assert!(cpm.contains("CPM.cmake"));
assert!(cpm.contains("CPMAddPackage"));
}
#[test]
fn test_cmake_project_new() {
let proj = X86CMakeProject::new("myproj", "2.0.0");
assert_eq!(proj.name, "myproj");
assert_eq!(proj.version, "2.0.0");
assert!(proj.languages.contains(&"C".to_string()));
assert!(proj.languages.contains(&"CXX".to_string()));
}
#[test]
fn test_cmake_project_generate_declaration() {
let proj = X86CMakeProject::new("myapp", "1.0.0");
let decl = proj.generate_declaration();
assert!(decl.contains("project(myapp"));
assert!(decl.contains("VERSION 1.0.0"));
}
#[test]
fn test_cmake_target_type_as_cmake() {
assert_eq!(X86CMakeTargetType::Executable.as_cmake(), "add_executable");
assert_eq!(X86CMakeTargetType::StaticLibrary.as_cmake(), "add_library");
assert_eq!(X86CMakeTargetType::CustomTarget.as_cmake(), "add_custom_target");
}
#[test]
fn test_cmake_target_new_executable() {
let target = X86CMakeTarget::new_executable("myexe");
assert_eq!(target.name, "myexe");
assert_eq!(target.target_type, X86CMakeTargetType::Executable);
}
#[test]
fn test_cmake_target_new_static_library() {
let target = X86CMakeTarget::new_static_library("mylib");
assert_eq!(target.target_type, X86CMakeTargetType::StaticLibrary);
}
#[test]
fn test_cmake_target_new_shared_library() {
let target = X86CMakeTarget::new_shared_library("mylib");
assert_eq!(target.target_type, X86CMakeTargetType::SharedLibrary);
}
#[test]
fn test_cmake_target_new_interface_library() {
let target = X86CMakeTarget::new_interface_library("myheaderlib");
assert_eq!(target.target_type, X86CMakeTargetType::InterfaceLibrary);
}
#[test]
fn test_cmake_target_new_object_library() {
let target = X86CMakeTarget::new_object_library("myobjlib");
assert_eq!(target.target_type, X86CMakeTargetType::ObjectLibrary);
}
#[test]
fn test_cmake_target_new_custom_target() {
let target = X86CMakeTarget::new_custom_target("mycustom");
assert_eq!(target.target_type, X86CMakeTargetType::CustomTarget);
}
#[test]
fn test_cmake_target_generate_executable() {
let mut target = X86CMakeTarget::new_executable("app");
target.sources = vec!["main.cpp".to_string(), "util.cpp".to_string()];
target.compile_features = vec!["cxx_std_17".to_string()];
target.compile_definitions = vec!["FOO=bar".to_string()];
let r#gen = target.generate_cmake();
assert!(r#gen.contains("add_executable(app"));
assert!(r#gen.contains("cxx_std_17"));
assert!(r#gen.contains("FOO=bar"));
}
#[test]
fn test_cmake_target_generate_interface_library() {
let target = X86CMakeTarget::new_interface_library("iface");
let r#gen = target.generate_cmake();
assert!(r#gen.contains("INTERFACE"));
}
#[test]
fn test_cmake_target_generate_with_includes() {
let mut target = X86CMakeTarget::new_executable("app");
target.private_includes = vec!["src".to_string()];
target.public_includes = vec!["include".to_string()];
target.interface_includes = vec!["external".to_string()];
let r#gen = target.generate_cmake();
assert!(r#gen.contains("PRIVATE\n src"));
assert!(r#gen.contains("PUBLIC\n include"));
assert!(r#gen.contains("INTERFACE\n external"));
}
#[test]
fn test_cmake_target_generate_with_link_libraries() {
let mut target = X86CMakeTarget::new_executable("app");
target.link_libraries = vec!["pthread".to_string(), "dl".to_string()];
let r#gen = target.generate_cmake();
assert!(r#gen.contains("pthread"));
assert!(r#gen.contains("dl"));
}
#[test]
fn test_cmake_target_generate_with_properties() {
let mut target = X86CMakeTarget::new_executable("app");
target.properties.insert("OUTPUT_NAME".to_string(), "custom_app".to_string());
let r#gen = target.generate_cmake();
assert!(r#gen.contains("OUTPUT_NAME"));
assert!(r#gen.contains("custom_app"));
}
#[test]
fn test_cmake_option_bool() {
let opt = X86CMakeOption::new_bool("ENABLE_FOO", "Enable foo feature", true);
let r#gen = opt.generate();
assert!(r#gen.contains("option(ENABLE_FOO"));
assert!(r#gen.contains("ON"));
}
#[test]
fn test_cmake_option_string() {
let opt = X86CMakeOption::new_string("MY_PATH", "A path", "/usr/local");
let r#gen = opt.generate();
assert!(r#gen.contains("MY_PATH"));
assert!(r#gen.contains("CACHE STRING"));
assert!(r#gen.contains("/usr/local"));
}
#[test]
fn test_cmake_dependency_generate() {
let dep = X86CMakeDependency::for_threads();
let r#gen = dep.generate();
assert!(r#gen.contains("find_package(Threads"));
assert!(r#gen.contains("REQUIRED"));
}
#[test]
fn test_cmake_dependency_for_boost() {
let dep = X86CMakeDependency::for_boost(&["system", "filesystem"]);
let r#gen = dep.generate();
assert!(r#gen.contains("Boost"));
assert!(r#gen.contains("system"));
assert!(r#gen.contains("filesystem"));
}
#[test]
fn test_cmake_dependency_for_opengl() {
let dep = X86CMakeDependency::for_opengl();
let r#gen = dep.generate();
assert!(r#gen.contains("OpenGL"));
}
#[test]
fn test_cmake_dependency_for_sdl2() {
let dep = X86CMakeDependency::for_sdl2();
let r#gen = dep.generate();
assert!(r#gen.contains("SDL2"));
}
#[test]
fn test_cmake_dependency_for_zlib() {
let dep = X86CMakeDependency::for_zlib();
let r#gen = dep.generate();
assert!(r#gen.contains("ZLIB"));
}
#[test]
fn test_cmake_dependency_for_protobuf() {
let dep = X86CMakeDependency::for_protobuf();
let r#gen = dep.generate();
assert!(r#gen.contains("Protobuf"));
}
#[test]
fn test_cmake_dependency_for_python() {
let dep = X86CMakeDependency::for_python();
let r#gen = dep.generate();
assert!(r#gen.contains("Python3"));
assert!(r#gen.contains("Interpreter"));
}
#[test]
fn test_cmake_dependency_for_opencv() {
let dep = X86CMakeDependency::for_opencv();
let r#gen = dep.generate();
assert!(r#gen.contains("OpenCV"));
}
#[test]
fn test_cmake_dependency_for_curl() {
let dep = X86CMakeDependency::for_curl();
let r#gen = dep.generate();
assert!(r#gen.contains("CURL"));
}
#[test]
fn test_fetch_content_generate() {
let fc = X86CMakeFetchContent::new(
"googletest",
"https://github.com/google/googletest.git",
"v1.14.0",
);
let r#gen = fc.generate();
assert!(r#gen.contains("FetchContent_Declare"));
assert!(r#gen.contains("googletest"));
assert!(r#gen.contains("v1.14.0"));
}
#[test]
fn test_external_project_generate() {
let mut ep = X86CMakeExternalProject::new("mylib");
ep.git_repository = Some("https://github.com/example/mylib.git".to_string());
ep.git_tag = Some("v1.0".to_string());
let r#gen = ep.generate();
assert!(r#gen.contains("ExternalProject_Add"));
assert!(r#gen.contains("mylib"));
assert!(r#gen.contains("v1.0"));
}
#[test]
fn test_cpm_package_generate() {
let pkg = X86CMakeCPMPackage::new_github("fmt", "10.0.0", "fmtlib/fmt");
let r#gen = pkg.generate();
assert!(r#gen.contains("CPMAddPackage"));
assert!(r#gen.contains("fmt"));
assert!(r#gen.contains("10.0.0"));
assert!(r#gen.contains("fmtlib/fmt"));
}
#[test]
fn test_toolchain_for_x86_64_linux() {
let tc = X86CMakeToolchain::for_x86_64_linux();
let r#gen = tc.generate();
assert!(r#gen.contains("Clang"));
assert!(r#gen.contains("x86_64"));
assert!(r#gen.contains("Linux"));
}
#[test]
fn test_toolchain_for_i686_linux() {
let tc = X86CMakeToolchain::for_i686_linux();
let r#gen = tc.generate();
assert!(r#gen.contains("i686"));
assert!(r#gen.contains("-m32"));
}
#[test]
fn test_toolchain_for_x86_64_mingw() {
let tc = X86CMakeToolchain::for_x86_64_mingw();
let r#gen = tc.generate();
assert!(r#gen.contains("Windows"));
assert!(r#gen.contains("mingw32"));
}
#[test]
fn test_presets_with_standard() {
let presets = X86CMakePresets::with_standard_presets();
assert_eq!(presets.configure_presets.len(), 7);
assert_eq!(presets.build_presets.len(), 2);
assert!(!presets.configure_presets[0].name.is_empty());
}
#[test]
fn test_presets_generate_cmake_presets() {
let presets = X86CMakePresets::with_standard_presets();
let json = presets.generate_cmake_presets();
assert!(json.contains("configurePresets"));
assert!(json.contains("buildPresets"));
assert!(json.contains("testPresets"));
assert!(json.contains("debug"));
assert!(json.contains("release"));
}
#[test]
fn test_configure_preset_to_json() {
let preset = X86CMakeConfigurePreset {
name: "test".to_string(),
display_name: "Test".to_string(),
description: "Description".to_string(),
generator: Some("Ninja".to_string()),
binary_dir: "build/test".to_string(),
cache_variables: {
let mut m = HashMap::new();
m.insert("CMAKE_BUILD_TYPE".to_string(), X86CMakeCacheValue::String("Release".to_string()));
m
},
inherits: vec!["base".to_string()],
environment: HashMap::new(),
};
let json = preset.to_json();
assert!(json.contains("\"test\""));
assert!(json.contains("Release"));
}
#[test]
fn test_build_preset_to_json() {
let preset = X86CMakeBuildPreset {
name: "build".to_string(),
configure_preset: "debug".to_string(),
jobs: Some(8),
targets: vec!["myapp".to_string()],
clean_first: true,
};
let json = preset.to_json();
assert!(json.contains("build"));
assert!(json.contains("debug"));
assert!(json.contains("cleanFirst"));
}
#[test]
fn test_test_preset_to_json() {
let preset = X86CMakeTestPreset {
name: "test".to_string(),
configure_preset: "debug".to_string(),
output_on_failure: true,
timeout: Some(120),
};
let json = preset.to_json();
assert!(json.contains("test"));
assert!(json.contains("onFailure"));
}
#[test]
fn test_ctest_config_new() {
let ct = X86CTestConfig::new();
assert!(ct.enable);
assert_eq!(ct.parallel_level, 4);
assert_eq!(ct.timeout_per_test, 300);
}
#[test]
fn test_ctest_test_generate() {
let test = X86CTestTest::new("my_test", "my_test_exe");
let r#gen = test.generate();
assert!(r#gen.contains("add_test"));
assert!(r#gen.contains("my_test"));
assert!(r#gen.contains("my_test_exe"));
}
#[test]
fn test_ctest_test_with_fixtures() {
let mut test = X86CTestTest::new("fixture_test", "test_exe");
test.fixtures_setup = vec!["setup_fix".to_string()];
test.fixtures_cleanup = vec!["cleanup_fix".to_string()];
test.fixtures_required = vec!["required_fix".to_string()];
let r#gen = test.generate();
assert!(r#gen.contains("FIXTURES_SETUP"));
assert!(r#gen.contains("FIXTURES_CLEANUP"));
assert!(r#gen.contains("FIXTURES_REQUIRED"));
}
#[test]
fn test_cpack_config_new() {
let cp = X86CPackConfig::new("myapp", "1.0.0");
assert_eq!(cp.package_name, "myapp");
assert_eq!(cp.package_version, "1.0.0");
assert!(cp.generators.contains(&"TGZ".to_string()));
}
#[test]
fn test_cpack_config_generate() {
let cp = X86CPackConfig::new("myapp", "1.0");
let r#gen = cp.generate();
assert!(r#gen.contains("CPACK_PACKAGE_NAME"));
assert!(r#gen.contains("myapp"));
assert!(r#gen.contains("include(CPack)"));
}
#[test]
fn test_cpack_deb_config_generate() {
let deb = X86CPackDebConfig {
maintainer: "dev@example.com".to_string(),
section: "devel".to_string(),
priority: "optional".to_string(),
depends: vec!["libc6".to_string()],
recommends: vec![],
suggests: vec![],
conflicts: vec![],
replaces: vec![],
architecture: "amd64".to_string(),
};
let r#gen = deb.generate();
assert!(r#gen.contains("CPACK_DEBIAN"));
assert!(r#gen.contains("devel"));
assert!(r#gen.contains("amd64"));
}
#[test]
fn test_cpack_rpm_config_generate() {
let rpm = X86CPackRpmConfig {
vendor: "X86 Project".to_string(),
license: "MIT".to_string(),
group: "Development/Libraries".to_string(),
requires: vec!["glibc".to_string()],
provides: vec![],
obsoletes: vec![],
conflicts: vec![],
architecture: "x86_64".to_string(),
release: "1".to_string(),
};
let r#gen = rpm.generate();
assert!(r#gen.contains("CPACK_RPM"));
assert!(r#gen.contains("x86_64"));
}
#[test]
fn test_install_rule_target() {
let rule = X86CMakeInstallRule::new_install_target("myapp", "bin");
let r#gen = rule.generate();
assert!(r#gen.contains("install(TARGETS myapp"));
assert!(r#gen.contains("RUNTIME DESTINATION bin"));
}
#[test]
fn test_install_rule_files() {
let rule = X86CMakeInstallRule::new_install_files(&["header.h"], "include");
let r#gen = rule.generate();
assert!(r#gen.contains("header.h"));
assert!(r#gen.contains("DESTINATION include"));
}
#[test]
fn test_install_rule_directory() {
let rule = X86CMakeInstallRule::new_install_directory("docs", "share/doc");
let r#gen = rule.generate();
assert!(r#gen.contains("DIRECTORY docs"));
assert!(r#gen.contains("share/doc"));
}
#[test]
fn test_make_support_new() {
let make = X86MakeSupport::new();
assert_eq!(make.cc, "clang");
assert_eq!(make.cxx, "clang++");
assert!(make.auto_deps);
}
#[test]
fn test_make_support_generate_makefile() {
let make = X86MakeSupport::new();
let mk = make.generate_makefile();
assert!(mk.contains("CC := clang"));
assert!(mk.contains("CXX := clang++"));
assert!(mk.contains(".PHONY"));
assert!(mk.contains("all:"));
assert!(mk.contains("clean:"));
assert!(mk.contains("install:"));
assert!(mk.contains("-MD -MF"));
}
#[test]
fn test_make_support_verbose() {
let mut make = X86MakeSupport::new();
make.verbose = true;
let mk = make.generate_makefile();
assert!(mk.contains("@echo"));
}
#[test]
fn test_make_support_simple_makefile() {
let mk = X86MakeSupport::generate_simple_makefile("hello", &["main.cpp", "util.cpp"]);
assert!(mk.contains("hello"));
assert!(mk.contains("main.o"));
assert!(mk.contains("util.o"));
}
#[test]
fn test_make_support_recursive_make() {
let rec = X86MakeSupport::generate_recursive_make(&["src", "test"]);
assert!(rec.contains("SUBDIRS"));
assert!(rec.contains("src"));
assert!(rec.contains("test"));
assert!(rec.contains("$(MAKE) -C"));
}
#[test]
fn test_make_target_new_executable() {
let t = X86MakeTarget::new_executable("app", &["main.cpp", "lib.cpp"]);
assert_eq!(t.name, "app");
assert_eq!(t.target_type, X86MakeTargetType::Executable);
assert_eq!(t.sources.len(), 2);
assert_eq!(t.objects.len(), 2);
}
#[test]
fn test_make_target_new_static_library() {
let t = X86MakeTarget::new_static_library("mylib", &["a.cpp", "b.cpp"]);
assert_eq!(t.target_type, X86MakeTargetType::StaticLibrary);
}
#[test]
fn test_make_target_new_shared_library() {
let t = X86MakeTarget::new_shared_library("mylib", &["a.cpp"]);
assert_eq!(t.target_type, X86MakeTargetType::SharedLibrary);
}
#[test]
fn test_make_target_generate_executable() {
let t = X86MakeTarget::new_executable("app", &["main.cpp"]);
let r#gen = t.generate_make();
assert!(r#gen.contains("app"));
assert!(r#gen.contains("$(LD) $(LDFLAGS)"));
}
#[test]
fn test_make_target_generate_static_library() {
let t = X86MakeTarget::new_static_library("mylib", &["a.cpp"]);
let r#gen = t.generate_make();
assert!(r#gen.contains("$(AR) $(ARFLAGS)"));
assert!(r#gen.contains(".a"));
}
#[test]
fn test_make_target_generate_shared_library() {
let t = X86MakeTarget::new_shared_library("mylib", &["a.cpp"]);
let r#gen = t.generate_make();
assert!(r#gen.contains("-shared"));
assert!(r#gen.contains(".so"));
}
#[test]
fn test_ninja_support_new() {
let ninja = X86NinjaSupport::new();
assert_eq!(ninja.ninja_required_version, "1.10");
assert_eq!(ninja.build_dir, "build");
}
#[test]
fn test_ninja_generate_ninja_file() {
let ninja = X86NinjaSupport::new();
let content = ninja.generate_ninja_file();
assert!(content.contains("ninja_required_version"));
assert!(content.contains("rule cc"));
assert!(content.contains("rule cxx"));
assert!(content.contains("rule link"));
assert!(content.contains("rule ar"));
assert!(content.contains("depfile = $out.d"));
assert!(content.contains("deps = gcc"));
}
#[test]
fn test_ninja_generate_for_project() {
let content = X86NinjaSupport::generate_for_project(
&["main.cpp", "util.cpp"],
"myapp",
);
assert!(content.contains("main.cpp"));
assert!(content.contains("util.cpp"));
assert!(content.contains("myapp"));
assert!(content.contains("$builddir"));
}
#[test]
fn test_ninja_rule_generate() {
let rule = X86NinjaRule {
name: "compile".to_string(),
command: "clang -c $in -o $out".to_string(),
description: Some("CC $out".to_string()),
depfile: Some("$out.d".to_string()),
deps: Some("gcc".to_string()),
generator: false,
pool: None,
restat: false,
rspfile: None,
rspfile_content: None,
};
let r#gen = rule.generate();
assert!(r#gen.contains("rule compile"));
assert!(r#gen.contains("command = clang"));
assert!(r#gen.contains("depfile = $out.d"));
assert!(r#gen.contains("deps = gcc"));
}
#[test]
fn test_ninja_build_edge_generate() {
let edge = X86NinjaBuildEdge {
outputs: vec!["build/main.o".to_string()],
rule: "cxx".to_string(),
inputs: vec!["src/main.cpp".to_string()],
implicit_inputs: vec!["src/main.h".to_string()],
order_only_inputs: vec!["src/generated.h".to_string()],
variables: {
let mut m = HashMap::new();
m.insert("cxxflags".to_string(), "-std=c++17".to_string());
m
},
implicit_outputs: Vec::new(),
validations: Vec::new(),
};
let r#gen = edge.generate();
assert!(r#gen.contains("build build/main.o"));
assert!(r#gen.contains("cxx"));
assert!(r#gen.contains("src/main.cpp"));
assert!(r#gen.contains("src/main.h"));
assert!(r#gen.contains("|| src/generated.h"));
assert!(r#gen.contains("cxxflags = -std=c++17"));
}
#[test]
fn test_ninja_pool_generate() {
let pool = X86NinjaPool {
name: "link_pool".to_string(),
depth: 2,
};
let r#gen = pool.generate();
assert!(r#gen.contains("pool link_pool"));
assert!(r#gen.contains("depth = 2"));
}
#[test]
fn test_ninja_compile_cxx_edge() {
let edge = X86NinjaSupport::compile_cxx_edge("src/main.cpp", "build/main.o", &["src/main.h"]);
assert_eq!(edge.rule, "cxx");
assert_eq!(edge.outputs[0], "build/main.o");
assert!(edge.implicit_inputs.contains(&"src/main.h".to_string()));
}
#[test]
fn test_ninja_compile_c_edge() {
let edge = X86NinjaSupport::compile_c_edge("src/main.c", "build/main.o", &["src/main.h"]);
assert_eq!(edge.rule, "cc");
assert_eq!(edge.outputs[0], "build/main.o");
}
#[test]
fn test_ninja_link_executable_edge() {
let edge = X86NinjaSupport::link_executable_edge(
"myapp",
&["build/main.o", "build/util.o"],
"build/myapp",
);
assert_eq!(edge.rule, "link");
assert!(edge.inputs.iter().any(|i| i == "build/main.o"));
}
#[test]
fn test_bazel_support_new() {
let bazel = X86BazelSupport::new();
assert_eq!(bazel.workspace_name, "x86_project");
assert!(bazel.use_bzlmod);
}
#[test]
fn test_bazel_generate_build_file_empty() {
let bazel = X86BazelSupport::new();
let content = bazel.generate_build_file();
assert!(content.contains("rules_cc"));
}
#[test]
fn test_bazel_generate_cc_library() {
let mut target = X86BazelTarget::new_cc_library("mylib");
target.srcs = vec!["lib.cpp".to_string()];
target.hdrs = vec!["lib.h".to_string()];
target.deps = vec!["//base:base".to_string()];
let bazel = X86BazelSupport::new();
let r#gen = bazel.generate_cc_library(&target);
assert!(r#gen.contains("cc_library"));
assert!(r#gen.contains("mylib"));
assert!(r#gen.contains("lib.cpp"));
assert!(r#gen.contains("//base:base"));
}
#[test]
fn test_bazel_generate_cc_binary() {
let mut target = X86BazelTarget::new_cc_binary("myapp");
target.srcs = vec!["main.cpp".to_string()];
target.copts = vec!["-O2".to_string()];
let bazel = X86BazelSupport::new();
let r#gen = bazel.generate_cc_binary(&target);
assert!(r#gen.contains("cc_binary"));
assert!(r#gen.contains("myapp"));
assert!(r#gen.contains("-O2"));
}
#[test]
fn test_bazel_generate_cc_test() {
let mut target = X86BazelTarget::new_cc_test("mytest");
target.srcs = vec!["test.cpp".to_string()];
target.deps = vec!["@googletest//:gtest_main".to_string()];
target.tags = vec!["unit".to_string()];
let bazel = X86BazelSupport::new();
let r#gen = bazel.generate_cc_test(&target);
assert!(r#gen.contains("cc_test"));
assert!(r#gen.contains("mytest"));
assert!(r#gen.contains("gtest_main"));
}
#[test]
fn test_bazel_generate_cc_import() {
let mut target = X86BazelTarget::new_cc_library("extlib");
target.target_type = X86BazelTargetType::CCImport;
target.import_library = Some("libext.a".to_string());
let bazel = X86BazelSupport::new();
let r#gen = bazel.generate_cc_import(&target);
assert!(r#gen.contains("cc_import"));
assert!(r#gen.contains("libext.a"));
}
#[test]
fn test_bazel_generate_workspace() {
let bazel = X86BazelSupport::new();
let ws = bazel.generate_workspace();
assert!(ws.contains("workspace"));
assert!(ws.contains("register_toolchains"));
assert!(ws.contains("x86_64_linux_clang"));
}
#[test]
fn test_bazel_generate_module_bazel() {
let bazel = X86BazelSupport::new();
let mod_file = bazel.generate_module_bazel();
assert!(mod_file.contains("module("));
assert!(mod_file.contains("rules_cc"));
}
#[test]
fn test_bazel_generate_bazelrc() {
let bazel = X86BazelSupport::new();
let rc = bazel.generate_bazelrc();
assert!(rc.contains("clang"));
assert!(rc.contains("x86_64"));
assert!(rc.contains("--cpu=x86_64"));
}
#[test]
fn test_bazel_toolchain_generate() {
let tc = X86BazelToolchain::x86_64_clang();
let bzl = tc.generate_toolchain_bzl();
assert!(bzl.contains("cc_toolchain"));
assert!(bzl.contains("x86_64_clang"));
}
#[test]
fn test_bazel_platform_generate() {
let platform = X86BazelPlatform::x86_64_linux();
let r#gen = platform.generate();
assert!(r#gen.contains("platform("));
assert!(r#gen.contains("x86_64"));
}
#[test]
fn test_bazel_external_dep_workspace() {
let mut dep = X86BazelExternalDep::new("googletest", "1.14.0");
dep.git_repository = Some("https://github.com/google/googletest.git".to_string());
dep.git_tag = Some("v1.14.0".to_string());
let ws = dep.generate_workspace();
assert!(ws.contains("git_repository"));
assert!(ws.contains("googletest"));
}
#[test]
fn test_bazel_external_dep_module() {
let dep = X86BazelExternalDep::new("rules_cc", "0.0.9");
let mod_entry = dep.generate_module_bazel();
assert!(mod_entry.contains("bazel_dep"));
assert!(mod_entry.contains("rules_cc"));
}
#[test]
fn test_meson_support_new() {
let meson = X86MesonSupport::new("proj", "1.0");
assert_eq!(meson.project_name, "proj");
assert_eq!(meson.project_version, "1.0");
assert_eq!(meson.meson_version, ">=1.0.0");
}
#[test]
fn test_meson_generate_build_empty() {
let meson = X86MesonSupport::new("test", "0.1");
let content = meson.generate_meson_build();
assert!(content.contains("project("));
assert!(content.contains("'c', 'cpp'"));
assert!(content.contains("host_machine.cpu_family()"));
}
#[test]
fn test_meson_dependency_generate() {
let dep = X86MesonDependency::new("zlib");
let r#gen = dep.generate();
assert!(r#gen.contains("dependency('zlib'"));
}
#[test]
fn test_meson_dependency_with_version() {
let mut dep = X86MesonDependency::new("fmt");
dep.version = Some("10.0.0".to_string());
let r#gen = dep.generate();
assert!(r#gen.contains("version: '10.0.0'"));
}
#[test]
fn test_meson_executable_generate() {
let exe = X86MesonExecutable::new("myapp", &["main.cpp"]);
let r#gen = exe.generate();
assert!(r#gen.contains("executable("));
assert!(r#gen.contains("'myapp'"));
assert!(r#gen.contains("main.cpp"));
}
#[test]
fn test_meson_library_shared_generate() {
let lib = X86MesonLibrary::new_shared("mylib", &["lib.cpp"]);
let r#gen = lib.generate();
assert!(r#gen.contains("shared_library("));
assert!(r#gen.contains("version: '1.0.0'"));
}
#[test]
fn test_meson_library_static_generate() {
let lib = X86MesonLibrary::new_static("mylib", &["lib.cpp"]);
let r#gen = lib.generate();
assert!(r#gen.contains("static_library("));
}
#[test]
fn test_meson_test_generate() {
let test = X86MesonTest::new("mytest", "my_test_exe");
let r#gen = test.generate();
assert!(r#gen.contains("test("));
assert!(r#gen.contains("'mytest'"));
assert!(r#gen.contains("protocol: 'exitcode'"));
}
#[test]
fn test_meson_cross_file_x86_64_linux() {
let cf = X86MesonCrossFile::for_x86_64_linux();
let r#gen = cf.generate();
assert!(r#gen.contains("x86_64"));
assert!(r#gen.contains("linux"));
assert!(r#gen.contains("clang"));
}
#[test]
fn test_meson_cross_file_i686_linux() {
let cf = X86MesonCrossFile::for_i686_linux();
let r#gen = cf.generate();
assert!(r#gen.contains("i686"));
assert!(r#gen.contains("-m32"));
}
#[test]
fn test_meson_wrap_generate() {
let wrap = X86MesonWrap::new("zlib");
let r#gen = wrap.generate();
assert!(r#gen.contains("[wrap-file]"));
}
#[test]
fn test_autotools_support_new() {
let auto = X86AutotoolsSupport::new("myproj", "1.0");
assert_eq!(auto.package_name, "myproj");
assert_eq!(auto.package_version, "1.0");
}
#[test]
fn test_autotools_generate_configure_ac() {
let auto = X86AutotoolsSupport::new("myapp", "2.0");
let ac = auto.generate_configure_ac();
assert!(ac.contains("AC_INIT([myapp]"));
assert!(ac.contains("AC_PROG_CC"));
assert!(ac.contains("AC_PROG_CXX"));
assert!(ac.contains("AC_CANONICAL_HOST"));
assert!(ac.contains("x86_64|amd64"));
assert!(ac.contains("i?86)"));
assert!(ac.contains("AC_OUTPUT"));
}
#[test]
fn test_autotools_generate_makefile_am() {
let auto = X86AutotoolsSupport::new("myapp", "1.0");
let am = auto.generate_makefile_am();
assert!(am.contains("Makefile.am"));
assert!(am.contains("ACLOCAL_AMFLAGS"));
}
#[test]
fn test_autotools_check_generate() {
let check = X86AutotoolsCheck::new_prog_cc();
assert_eq!(check.generate(), "AC_PROG_CC");
let check = X86AutotoolsCheck::new_check_lib("pthread", "pthread_create");
let r#gen = check.generate();
assert!(r#gen.contains("AC_CHECK_LIB"));
assert!(r#gen.contains("pthread"));
}
#[test]
fn test_autotools_library_generate() {
let lib = X86AutotoolsLibrary::new("m", "sin");
let r#gen = lib.generate();
assert!(r#gen.contains("AC_CHECK_LIB"));
assert!(r#gen.contains("m"));
assert!(r#gen.contains("sin"));
}
#[test]
fn test_autotools_pkg_check_generate() {
let pkg = X86AutotoolsPkgCheck::new(&["glib-2.0", "gobject-2.0"]);
let r#gen = pkg.generate();
assert!(r#gen.contains("PKG_CHECK_MODULES"));
assert!(r#gen.contains("glib-2.0"));
assert!(r#gen.contains("gobject-2.0"));
}
#[test]
fn test_autotools_program_generate() {
let prog = X86AutotoolsProgram::new("hello", &["main.c", "util.c"]);
let r#gen = prog.generate();
assert!(r#gen.contains("bin_PROGRAMS = hello"));
assert!(r#gen.contains("hello_SOURCES"));
assert!(r#gen.contains("main.c"));
}
#[test]
fn test_xmake_support_new() {
let xm = X86XMakeSupport::new("myproj", "1.0");
assert_eq!(xm.project_name, "myproj");
assert_eq!(xm.set_arch.as_deref(), Some("x86_64"));
}
#[test]
fn test_xmake_generate_lua() {
let xm = X86XMakeSupport::new("test", "0.1");
let lua = xm.generate_xmake_lua();
assert!(lua.contains("set_arch"));
assert!(lua.contains("set_plat"));
assert!(lua.contains("add_rules"));
}
#[test]
fn test_xmake_target_binary_generate() {
let mut target = X86XMakeTarget::new_binary("myapp");
target.files = vec!["src/main.cpp".to_string()];
let r#gen = target.generate();
assert!(r#gen.contains("target(\"myapp\")"));
assert!(r#gen.contains("set_kind(\"binary\")"));
assert!(r#gen.contains("add_cxflags"));
}
#[test]
fn test_xmake_target_static_library_generate() {
let mut target = X86XMakeTarget::new_static_library("mylib");
target.files = vec!["src/lib.cpp".to_string()];
let r#gen = target.generate();
assert!(r#gen.contains("set_kind(\"static\")"));
}
#[test]
fn test_xmake_target_shared_library_generate() {
let mut target = X86XMakeTarget::new_shared_library("mylib");
target.files = vec!["src/lib.cpp".to_string()];
let r#gen = target.generate();
assert!(r#gen.contains("set_kind(\"shared\")"));
}
#[test]
fn test_xmake_package_generate() {
let pkg = X86XMakePackage::new("zlib");
let r#gen = pkg.generate();
assert!(r#gen.contains("add_requires"));
assert!(r#gen.contains("zlib"));
}
#[test]
fn test_build_graph_empty() {
let graph = X86BuildGraph::empty();
assert!(graph.nodes.is_empty());
assert!(graph.edges.is_empty());
}
#[test]
fn test_build_graph_add_node() {
let mut graph = X86BuildGraph::empty();
graph.add_node(X86GraphNode {
name: "A".to_string(),
target_type: "executable".to_string(),
duration_ms: 100,
earliest_start: 0,
earliest_finish: 0,
latest_start: 0,
latest_finish: 0,
slack: 0,
on_critical_path: false,
level: 0,
});
assert_eq!(graph.nodes.len(), 1);
}
#[test]
fn test_build_graph_add_edge() {
let mut graph = X86BuildGraph::empty();
graph.add_edge("A", "B");
assert!(graph.edges.contains_key("A"));
assert!(graph.edges["A"].contains(&"B".to_string()));
assert!(graph.reverse_edges.contains_key("B"));
}
#[test]
fn test_build_graph_from_targets() {
let targets = vec![
X86BuildTarget {
name: "main".to_string(),
target_type: "executable".to_string(),
sources: vec!["main.cpp".to_string()],
dependencies: vec!["util".to_string(), "core".to_string()],
compile_time_estimate_ms: 500,
},
X86BuildTarget {
name: "util".to_string(),
target_type: "library".to_string(),
sources: vec!["util.cpp".to_string()],
dependencies: vec!["core".to_string()],
compile_time_estimate_ms: 200,
},
X86BuildTarget {
name: "core".to_string(),
target_type: "library".to_string(),
sources: vec!["core.cpp".to_string()],
dependencies: vec![],
compile_time_estimate_ms: 100,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert!(!topo.is_empty());
assert_eq!(topo.len(), 3);
let core_pos = topo.iter().position(|n| n == "core").unwrap();
let util_pos = topo.iter().position(|n| n == "util").unwrap();
let main_pos = topo.iter().position(|n| n == "main").unwrap();
assert!(core_pos < util_pos);
assert!(util_pos < main_pos);
let cp = graph.critical_path_nodes();
assert!(!cp.is_empty());
assert!(graph.critical_path_duration_ms > 0);
if let Some(ref schedule) = graph.parallel_schedule {
assert!(!schedule.is_empty());
}
}
#[test]
fn test_build_graph_topological_order() {
let mut graph = X86BuildGraph::empty();
graph.add_node(X86GraphNode {
name: "A".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_node(X86GraphNode {
name: "B".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_node(X86GraphNode {
name: "C".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_edge("C", "B");
graph.add_edge("B", "A");
let topo = graph.topological_order();
let a_pos = topo.iter().position(|n| n == "A").unwrap();
let b_pos = topo.iter().position(|n| n == "B").unwrap();
let c_pos = topo.iter().position(|n| n == "C").unwrap();
assert!(c_pos < b_pos);
assert!(b_pos < a_pos);
}
#[test]
fn test_build_graph_critical_path() {
let targets = vec![
X86BuildTarget {
name: "fast".to_string(),
target_type: "lib".to_string(),
sources: vec!["fast.cpp".to_string()],
dependencies: vec![],
compile_time_estimate_ms: 50,
},
X86BuildTarget {
name: "slow".to_string(),
target_type: "lib".to_string(),
sources: vec!["slow.cpp".to_string()],
dependencies: vec![],
compile_time_estimate_ms: 5000,
},
X86BuildTarget {
name: "app".to_string(),
target_type: "exe".to_string(),
sources: vec!["app.cpp".to_string()],
dependencies: vec!["fast".to_string(), "slow".to_string()],
compile_time_estimate_ms: 100,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
let cp = graph.critical_path_nodes();
assert!(cp.contains(&"slow".to_string()));
assert!(graph.critical_path_duration_ms >= 5000);
}
#[test]
fn test_build_graph_has_cycle() {
let mut graph = X86BuildGraph::empty();
graph.add_node(X86GraphNode {
name: "X".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_node(X86GraphNode {
name: "Y".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_edge("X", "Y");
graph.add_edge("Y", "X");
assert!(graph.has_cycle());
}
#[test]
fn test_build_graph_no_cycle() {
let mut graph = X86BuildGraph::empty();
graph.add_node(X86GraphNode {
name: "A".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_node(X86GraphNode {
name: "B".to_string(), target_type: "lib".to_string(), duration_ms: 10,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 0,
});
graph.add_edge("A", "B");
graph.compute_topological_order();
assert!(!graph.has_cycle());
}
#[test]
fn test_build_graph_to_dot() {
let mut graph = X86BuildGraph::empty();
graph.add_node(X86GraphNode {
name: "A".to_string(), target_type: "lib".to_string(), duration_ms: 100,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: true, level: 0,
});
graph.add_node(X86GraphNode {
name: "B".to_string(), target_type: "exe".to_string(), duration_ms: 200,
earliest_start: 0, earliest_finish: 0, latest_start: 0, latest_finish: 0,
slack: 0, on_critical_path: false, level: 1,
});
graph.add_edge("B", "A");
let dot = graph.to_dot();
assert!(dot.contains("digraph X86BuildGraph"));
assert!(dot.contains("\"A\""));
assert!(dot.contains("\"B\""));
assert!(dot.contains("->"));
}
#[test]
fn test_build_graph_parallel_schedule() {
let targets = vec![
X86BuildTarget {
name: "A".to_string(), target_type: "lib".to_string(),
sources: vec!["a.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "B".to_string(), target_type: "lib".to_string(),
sources: vec!["b.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "C".to_string(), target_type: "lib".to_string(),
sources: vec!["c.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "D".to_string(), target_type: "lib".to_string(),
sources: vec!["d.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "E".to_string(), target_type: "exe".to_string(),
sources: vec!["e.cpp".to_string()],
dependencies: vec!["A".to_string(), "B".to_string(), "C".to_string(), "D".to_string()],
compile_time_estimate_ms: 200,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
if let Some(ref schedule) = graph.parallel_schedule {
assert!(!schedule.is_empty());
assert_eq!(graph.estimated_time_ms, 300);
}
}
#[test]
fn test_build_graph_node_slack() {
let targets = vec![
X86BuildTarget {
name: "short".to_string(), target_type: "lib".to_string(),
sources: vec!["short.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 10,
},
X86BuildTarget {
name: "long".to_string(), target_type: "lib".to_string(),
sources: vec!["long.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 1000,
},
X86BuildTarget {
name: "final".to_string(), target_type: "exe".to_string(),
sources: vec!["final.cpp".to_string()],
dependencies: vec!["short".to_string(), "long".to_string()],
compile_time_estimate_ms: 100,
},
];
let graph = X86BuildGraph::from_targets(&targets);
let short_node = graph.nodes.iter().find(|n| n.name == "short").unwrap();
assert!(short_node.slack > 0);
let long_node = graph.nodes.iter().find(|n| n.name == "long").unwrap();
assert_eq!(long_node.slack, 0);
assert!(long_node.on_critical_path);
let final_node = graph.nodes.iter().find(|n| n.name == "final").unwrap();
assert_eq!(final_node.slack, 0);
}
#[test]
fn test_build_output_empty() {
let output = X86BuildOutput {
files_generated: vec![],
build_commands: vec![],
warnings: vec![],
estimated_build_time_ms: 0,
};
assert!(output.files_generated.is_empty());
}
#[test]
fn test_build_target_creation() {
let target = X86BuildTarget {
name: "test".to_string(),
target_type: "executable".to_string(),
sources: vec!["a.cpp".to_string(), "b.cpp".to_string()],
dependencies: vec!["dep1".to_string()],
compile_time_estimate_ms: 1234,
};
assert_eq!(target.name, "test");
assert_eq!(target.sources.len(), 2);
}
#[test]
fn test_build_systems_generate_cmake() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"CMakeLists.txt".to_string()));
assert!(!output.build_commands.is_empty());
}
#[test]
fn test_build_systems_generate_make() {
let bs = X86BuildSystems::new(X86BuildBackend::Make);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"Makefile".to_string()));
}
#[test]
fn test_build_systems_generate_ninja() {
let bs = X86BuildSystems::new(X86BuildBackend::Ninja);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"build.ninja".to_string()));
}
#[test]
fn test_build_systems_generate_meson() {
let bs = X86BuildSystems::new(X86BuildBackend::Meson);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"meson.build".to_string()));
}
#[test]
fn test_build_systems_generate_bazel() {
let bs = X86BuildSystems::new(X86BuildBackend::Bazel);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"BUILD".to_string()));
}
#[test]
fn test_build_systems_generate_autotools() {
let bs = X86BuildSystems::new(X86BuildBackend::Autotools);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"configure.ac".to_string()));
assert!(output.files_generated.contains(&"Makefile.am".to_string()));
}
#[test]
fn test_build_systems_generate_xmake() {
let bs = X86BuildSystems::new(X86BuildBackend::XMake);
let result = bs.generate();
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.files_generated.contains(&"xmake.lua".to_string()));
}
#[test]
fn test_build_systems_analyze_build_graph() {
let mut bs = X86BuildSystems::new(X86BuildBackend::CMake);
let targets = vec![
X86BuildTarget {
name: "lib".to_string(), target_type: "library".to_string(),
sources: vec!["lib.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "app".to_string(), target_type: "executable".to_string(),
sources: vec!["main.cpp".to_string()], dependencies: vec!["lib".to_string()],
compile_time_estimate_ms: 200,
},
];
bs.analyze_build_graph(&targets);
let topo = bs.build_graph.topological_order();
assert!(!topo.is_empty());
}
#[test]
fn test_cmake_full_pipeline() {
let mut cmake = X86CMakeSupport::new();
let mut project = X86CMakeProject::new("full_app", "1.0.0");
project.description = "Full X86 CMake project".to_string();
let mut exe = X86CMakeTarget::new_executable("full_app");
exe.sources = vec!["main.cpp".to_string()];
exe.compile_features = vec!["cxx_std_17".to_string()];
exe.compile_definitions = vec!["VERSION=\\\"1.0.0\\\"".to_string()];
exe.link_libraries = vec!["pthread".to_string(), "dl".to_string()];
exe.public_includes = vec!["${CMAKE_SOURCE_DIR}/include".to_string()];
project.add_target(exe);
let mut lib = X86CMakeTarget::new_static_library("core");
lib.sources = vec!["core.cpp".to_string()];
lib.public_includes = vec!["${CMAKE_SOURCE_DIR}/include".to_string()];
lib.compile_features = vec!["cxx_std_17".to_string()];
project.add_target(lib);
cmake.add_project(project);
cmake.add_dependency(X86CMakeDependency::for_threads());
cmake.add_install_rule(X86CMakeInstallRule::new_install_target("full_app", "bin"));
let content = cmake.generate_cmake_lists();
assert!(content.contains("full_app"));
assert!(content.contains("core"));
assert!(content.contains("cxx_std_17"));
assert!(content.contains("find_package(Threads"));
assert!(content.contains("install(TARGETS full_app"));
}
#[test]
fn test_make_full_pipeline() {
let mut make = X86MakeSupport::new();
make.add_target(X86MakeTarget::new_executable("hello", &["main.cpp", "util.cpp"]));
make.add_target(X86MakeTarget::new_static_library("mylib", &["lib.cpp"]));
make.add_target(X86MakeTarget::new_shared_library("myshared", &["shared.cpp"]));
let mk = make.generate_makefile();
assert!(mk.contains("hello"));
assert!(mk.contains("mylib"));
assert!(mk.contains("myshared"));
assert!(mk.contains("$(AR)"));
assert!(mk.contains("-shared"));
}
#[test]
fn test_ninja_full_pipeline() {
let mut ninja = X86NinjaSupport::new();
ninja.add_edge(X86NinjaSupport::compile_cxx_edge(
"src/main.cpp", "build/main.o", &["src/main.h"],
));
ninja.add_edge(X86NinjaSupport::compile_cxx_edge(
"src/util.cpp", "build/util.o", &["src/util.h"],
));
ninja.add_edge(X86NinjaSupport::link_executable_edge(
"myapp",
&["build/main.o", "build/util.o"],
"build/myapp",
));
let content = ninja.generate_ninja_file();
assert!(content.contains("src/main.cpp"));
assert!(content.contains("src/util.cpp"));
assert!(content.contains("myapp"));
}
#[test]
fn test_bazel_full_pipeline() {
let mut bazel = X86BazelSupport::new();
let mut lib = X86BazelTarget::new_cc_library("core");
lib.srcs = vec!["core.cc".to_string()];
lib.hdrs = vec!["core.h".to_string()];
lib.copts = vec!["-march=x86-64".to_string(), "-O2".to_string()];
let mut bin = X86BazelTarget::new_cc_binary("app");
bin.srcs = vec!["main.cc".to_string()];
bin.deps = vec![":core".to_string()];
bin.linkopts = vec!["-lpthread".to_string()];
let mut test = X86BazelTarget::new_cc_test("core_test");
test.srcs = vec!["core_test.cc".to_string()];
test.deps = vec![":core".to_string()];
bazel.add_target(lib);
bazel.add_target(bin);
bazel.add_target(test);
let build = bazel.generate_build_file();
assert!(build.contains("cc_library"));
assert!(build.contains("cc_binary"));
assert!(build.contains("cc_test"));
assert!(build.contains("core"));
}
#[test]
fn test_meson_full_pipeline() {
let mut meson = X86MesonSupport::new("fullproj", "2.0.0");
meson.add_dependency(X86MesonDependency::new("threads"));
let exe = X86MesonExecutable::new("myapp", &["src/main.cpp"]);
meson.add_executable(exe);
let lib = X86MesonLibrary::new_shared("mylib", &["src/lib.cpp"]);
meson.add_library(lib);
meson.add_test(X86MesonTest::new("unit_test", "my_test_exe"));
let content = meson.generate_meson_build();
assert!(content.contains("project("));
assert!(content.contains("'fullproj'"));
assert!(content.contains("executable("));
assert!(content.contains("shared_library("));
assert!(content.contains("test("));
}
#[test]
fn test_autotools_full_pipeline() {
let mut auto = X86AutotoolsSupport::new("fullpackage", "3.0.0");
auto.add_program(X86AutotoolsProgram::new("app", &["src/main.c", "src/util.c"]));
auto.add_library_check(X86AutotoolsLibrary::new("pthread", "pthread_create"));
auto.add_pkg_check(X86AutotoolsPkgCheck::new(&["glib-2.0"]));
auto.config_headers = vec!["config.h".to_string()];
let ac = auto.generate_configure_ac();
assert!(ac.contains("fullpackage"));
assert!(ac.contains("AC_CHECK_LIB"));
assert!(ac.contains("PKG_CHECK_MODULES"));
assert!(ac.contains("glib-2.0"));
let am = auto.generate_makefile_am();
assert!(am.contains("app_SOURCES"));
}
#[test]
fn test_xmake_full_pipeline() {
let mut xmake = X86XMakeSupport::new("fullxmake", "1.0");
let mut target = X86XMakeTarget::new_binary("app");
target.files = vec!["src/main.cpp".to_string()];
target.packages = vec!["zlib".to_string(), "fmt".to_string()];
target.defines = vec!["DEBUG=1".to_string()];
xmake.add_target(target);
xmake.add_package(X86XMakePackage::new("zlib"));
let lua = xmake.generate_xmake_lua();
assert!(lua.contains("target(\"app\")"));
assert!(lua.contains("set_kind(\"binary\")"));
assert!(lua.contains("add_packages"));
assert!(lua.contains("zlib"));
}
#[test]
fn test_meson_cross_file_for_windows() {
let cf = X86MesonCrossFile::for_x86_64_windows();
let r#gen = cf.generate();
assert!(r#gen.contains("windows"));
assert!(r#gen.contains("mingw32"));
assert!(r#gen.contains("needs_exe_wrapper"));
}
#[test]
fn test_meson_cross_file_for_macos() {
let cf = X86MesonCrossFile::for_x86_64_macos();
let r#gen = cf.generate();
assert!(r#gen.contains("darwin"));
assert!(r#gen.contains("x86_64-apple-darwin"));
}
#[test]
fn test_meson_cross_file_for_native() {
let cf = X86MesonCrossFile::for_native_x86_64();
let r#gen = cf.generate();
assert!(r#gen.contains("linux"));
assert!(r#gen.contains("lld"));
}
#[test]
fn test_presets_include_sanitizer_variants() {
let presets = X86CMakePresets::with_standard_presets();
let names: Vec<&str> = presets.configure_presets.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"debug-asan"));
assert!(names.contains(&"debug-tsan"));
assert!(names.contains(&"debug-ubsan"));
assert!(names.contains(&"release-lto-pgo"));
}
#[test]
fn test_preset_asan_has_flags() {
let presets = X86CMakePresets::with_standard_presets();
let asan = presets.configure_presets.iter().find(|p| p.name == "debug-asan").unwrap();
assert!(asan.inherits.contains(&"debug".to_string()));
assert!(asan.environment.contains_key("ASAN_OPTIONS"));
}
#[test]
fn test_preset_tsan_has_flags() {
let presets = X86CMakePresets::with_standard_presets();
let tsan = presets.configure_presets.iter().find(|p| p.name == "debug-tsan").unwrap();
let flags = tsan.cache_variables.get("CMAKE_CXX_FLAGS").unwrap();
match flags {
X86CMakeCacheValue::String(s) => assert!(s.contains("fsanitize=thread")),
_ => panic!("Expected string cache value"),
}
}
#[test]
fn test_preset_ubsan_has_flags() {
let presets = X86CMakePresets::with_standard_presets();
let ubsan = presets.configure_presets.iter().find(|p| p.name == "debug-ubsan").unwrap();
let flags = ubsan.cache_variables.get("CMAKE_CXX_FLAGS").unwrap();
match flags {
X86CMakeCacheValue::String(s) => assert!(s.contains("fsanitize=undefined")),
_ => panic!("Expected string cache value"),
}
}
#[test]
fn test_preset_lto_pgo_has_flags() {
let presets = X86CMakePresets::with_standard_presets();
let pgo = presets.configure_presets.iter().find(|p| p.name == "release-lto-pgo").unwrap();
let flags = pgo.cache_variables.get("CMAKE_CXX_FLAGS").unwrap();
match flags {
X86CMakeCacheValue::String(s) => assert!(s.contains("fprofile-generate")),
_ => panic!("Expected string cache value"),
}
assert!(pgo.inherits.contains(&"release".to_string()));
}
#[test]
fn test_build_graph_single_node() {
let targets = vec![
X86BuildTarget {
name: "single".to_string(),
target_type: "executable".to_string(),
sources: vec!["single.cpp".to_string()],
dependencies: vec![],
compile_time_estimate_ms: 42,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert_eq!(topo.len(), 1);
assert_eq!(topo[0], "single");
assert!(!graph.has_cycle());
}
#[test]
fn test_build_graph_diamond_dependency() {
let targets = vec![
X86BuildTarget {
name: "top".to_string(), target_type: "exe".to_string(),
sources: vec!["top.cpp".to_string()],
dependencies: vec!["left".to_string(), "right".to_string()],
compile_time_estimate_ms: 100,
},
X86BuildTarget {
name: "left".to_string(), target_type: "lib".to_string(),
sources: vec!["left.cpp".to_string()],
dependencies: vec!["bottom".to_string()],
compile_time_estimate_ms: 200,
},
X86BuildTarget {
name: "right".to_string(), target_type: "lib".to_string(),
sources: vec!["right.cpp".to_string()],
dependencies: vec!["bottom".to_string()],
compile_time_estimate_ms: 300,
},
X86BuildTarget {
name: "bottom".to_string(), target_type: "lib".to_string(),
sources: vec!["bottom.cpp".to_string()],
dependencies: vec![],
compile_time_estimate_ms: 50,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert_eq!(topo.len(), 4);
let b_pos = topo.iter().position(|n| n == "bottom").unwrap();
let l_pos = topo.iter().position(|n| n == "left").unwrap();
let r_pos = topo.iter().position(|n| n == "right").unwrap();
let t_pos = topo.iter().position(|n| n == "top").unwrap();
assert!(b_pos < l_pos);
assert!(b_pos < r_pos);
assert!(l_pos < t_pos);
assert!(r_pos < t_pos);
assert!(!graph.has_cycle());
}
#[test]
fn test_build_graph_chain_dependency() {
let targets = vec![
X86BuildTarget {
name: "a".to_string(), target_type: "lib".to_string(),
sources: vec!["a.cpp".to_string()], dependencies: vec![],
compile_time_estimate_ms: 10,
},
X86BuildTarget {
name: "b".to_string(), target_type: "lib".to_string(),
sources: vec!["b.cpp".to_string()], dependencies: vec!["a".to_string()],
compile_time_estimate_ms: 20,
},
X86BuildTarget {
name: "c".to_string(), target_type: "lib".to_string(),
sources: vec!["c.cpp".to_string()], dependencies: vec!["b".to_string()],
compile_time_estimate_ms: 30,
},
X86BuildTarget {
name: "d".to_string(), target_type: "lib".to_string(),
sources: vec!["d.cpp".to_string()], dependencies: vec!["c".to_string()],
compile_time_estimate_ms: 40,
},
X86BuildTarget {
name: "e".to_string(), target_type: "exe".to_string(),
sources: vec!["e.cpp".to_string()], dependencies: vec!["d".to_string()],
compile_time_estimate_ms: 50,
},
];
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
let a_pos = topo.iter().position(|n| n == "a").unwrap();
let e_pos = topo.iter().position(|n| n == "e").unwrap();
assert!(a_pos < e_pos);
let cp = graph.critical_path_nodes();
assert_eq!(cp.len(), 5);
assert_eq!(graph.critical_path_duration_ms, 150); }
#[test]
fn test_build_graph_empty_returns_cleanly() {
let targets: Vec<X86BuildTarget> = vec![];
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert!(topo.is_empty());
}
#[test]
fn test_toolchain_for_all_x86_variants() {
let x86_64 = X86CMakeToolchain::for_x86_64_linux().generate();
assert!(x86_64.contains("x86_64"));
assert!(!x86_64.contains("m32"));
let i686 = X86CMakeToolchain::for_i686_linux().generate();
assert!(i686.contains("i686"));
assert!(i686.contains("-m32"));
let mingw = X86CMakeToolchain::for_x86_64_mingw().generate();
assert!(mingw.contains("Windows"));
assert!(mingw.contains("mingw32"));
}
#[test]
fn test_build_systems_all_backends_have_valid_generation() {
let backends = vec![
X86BuildBackend::CMake,
X86BuildBackend::Make,
X86BuildBackend::Ninja,
X86BuildBackend::Meson,
X86BuildBackend::Bazel,
X86BuildBackend::Autotools,
X86BuildBackend::XMake,
];
for backend in backends {
let bs = X86BuildSystems::new(backend);
let result = bs.generate();
assert!(result.is_ok(), "Failed for backend: {}", backend);
let output = result.unwrap();
assert!(!output.files_generated.is_empty(), "No files for backend: {}", backend);
}
}
#[test]
fn test_cmake_target_type_kind_str() {
assert_eq!(X86CMakeTargetType::StaticLibrary.kind_str(), "STATIC");
assert_eq!(X86CMakeTargetType::SharedLibrary.kind_str(), "SHARED");
assert_eq!(X86CMakeTargetType::InterfaceLibrary.kind_str(), "INTERFACE");
assert_eq!(X86CMakeTargetType::ObjectLibrary.kind_str(), "OBJECT");
assert_eq!(X86CMakeTargetType::Executable.kind_str(), "");
assert_eq!(X86CMakeTargetType::CustomTarget.kind_str(), "");
}
#[test]
fn test_ctest_generate_includes_enable_testing() {
let ct = X86CTestConfig::new();
let r#gen = ct.generate();
assert!(r#gen.contains("enable_testing()"));
}
#[test]
fn test_ctest_test_with_working_dir() {
let mut test = X86CTestTest::new("wd_test", "test_exe");
test.working_directory = Some("/tmp/testdir".to_string());
let r#gen = test.generate();
assert!(r#gen.contains("WORKING_DIRECTORY"));
}
#[test]
fn test_ctest_test_with_properties() {
let mut test = X86CTestTest::new("prop_test", "test_exe");
test.properties.insert("TIMEOUT".to_string(), "60".to_string());
test.properties.insert("LABELS".to_string(), "unit".to_string());
let r#gen = test.generate();
assert!(r#gen.contains("TIMEOUT"));
assert!(r#gen.contains("LABELS"));
}
#[test]
fn test_build_graph_many_independent_nodes() {
let mut targets = Vec::new();
for i in 0..50 {
targets.push(X86BuildTarget {
name: format!("lib{}", i),
target_type: "library".to_string(),
sources: vec![format!("lib{}.cpp", i)],
dependencies: vec![],
compile_time_estimate_ms: 10,
});
}
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert_eq!(topo.len(), 50);
for node in &graph.nodes {
assert_eq!(node.level, 0);
assert_eq!(node.earliest_start, 0);
}
}
#[test]
fn test_build_graph_deep_chain() {
let mut targets = Vec::new();
let depth = 20;
for i in 0..depth {
let deps = if i > 0 { vec![format!("node{}", i - 1)] } else { vec![] };
targets.push(X86BuildTarget {
name: format!("node{}", i),
target_type: "library".to_string(),
sources: vec![format!("node{}.cpp", i)],
dependencies: deps,
compile_time_estimate_ms: 10,
});
}
let mut graph = X86BuildGraph::from_targets(&targets);
let topo = graph.topological_order();
assert_eq!(topo.len(), depth);
assert!(!graph.has_cycle());
let first_level = graph.nodes.iter().find(|n| n.name == "node0").unwrap().level;
let last_level = graph.nodes.iter().find(|n| n.name == format!("node{}", depth - 1)).unwrap().level;
assert!(first_level < last_level);
}
#[test]
fn test_x86_build_systems_defaults() {
let bs = X86BuildSystems::default();
assert_eq!(bs.backend, X86BuildBackend::CMake);
assert_eq!(bs.cpu_target, "x86_64");
assert!(bs.parallel_jobs >= 1);
assert!(!bs.verbose);
}
#[test]
fn test_cmake_support_dependency_factory_methods() {
let deps = vec![
X86CMakeDependency::for_opengl(),
X86CMakeDependency::for_sdl2(),
X86CMakeDependency::for_zlib(),
X86CMakeDependency::for_threads(),
X86CMakeDependency::for_protobuf(),
X86CMakeDependency::for_python(),
X86CMakeDependency::for_opencv(),
X86CMakeDependency::for_curl(),
X86CMakeDependency::for_pkgconfig(),
];
for dep in &deps {
let r#gen = dep.generate();
assert!(r#gen.starts_with("find_package("));
assert!(r#gen.contains(&dep.name));
}
}
#[test]
fn test_autotools_check_factory_methods() {
let checks = vec![
X86AutotoolsCheck::new_prog_cc(),
X86AutotoolsCheck::new_prog_cxx(),
X86AutotoolsCheck::new_prog_install(),
X86AutotoolsCheck::new_prog_ranlib(),
X86AutotoolsCheck::new_prog_ar(),
X86AutotoolsCheck::new_check_header("stdio.h"),
X86AutotoolsCheck::new_check_func("malloc"),
X86AutotoolsCheck::new_check_lib("m", "sin"),
];
for check in &checks {
let r#gen = check.generate();
assert!(!r#gen.is_empty());
}
}
#[test]
fn test_meson_library_type_coverage() {
let shared = X86MesonLibrary::new_shared("lib", &["lib.cpp"]);
assert_eq!(shared.library_type, X86MesonLibraryType::Shared);
assert!(shared.version.is_some());
let stat = X86MesonLibrary::new_static("lib", &["lib.cpp"]);
assert_eq!(stat.library_type, X86MesonLibraryType::Static);
assert!(stat.version.is_none());
}
#[test]
fn test_meson_executable_gui_app() {
let mut exe = X86MesonExecutable::new("gui", &["gui.cpp"]);
exe.gui_app = true;
let r#gen = exe.generate();
assert!(r#gen.contains("gui_app: true"));
}
#[test]
fn test_meson_dependency_optional() {
let mut dep = X86MesonDependency::new("optional_lib");
dep.required = false;
let r#gen = dep.generate();
assert!(r#gen.contains("required: false"));
}
#[test]
fn test_meson_dependency_with_fallback() {
let mut dep = X86MesonDependency::new("some_lib");
dep.fallback = Some("some_lib_subproject".to_string());
let r#gen = dep.generate();
assert!(r#gen.contains("fallback"));
}
#[test]
fn test_ninja_add_custom_rules() {
let mut ninja = X86NinjaSupport::new();
ninja.add_rule(X86NinjaRule {
name: "protoc".to_string(),
command: "protoc --cpp_out=$outdir $in".to_string(),
description: Some("PROTOC $in".to_string()),
depfile: None,
deps: None,
generator: true,
pool: None,
restat: false,
rspfile: None,
rspfile_content: None,
});
let content = ninja.generate_ninja_file();
assert!(content.contains("rule protoc"));
assert!(content.contains("generator = 1"));
}
#[test]
fn test_ninja_pool_integration() {
let mut ninja = X86NinjaSupport::new();
ninja.pools.push(X86NinjaPool {
name: "expensive_link".to_string(),
depth: 1,
});
let content = ninja.generate_ninja_file();
assert!(content.contains("pool expensive_link"));
}
#[test]
fn test_x86_build_systems_flags_customization() {
let bs = X86BuildSystems::new(X86BuildBackend::CMake)
.with_c_flag("-DSOME_DEFINE=1")
.with_cxx_flag("-stdlib=libc++")
.with_ld_flag("-fuse-ld=lld");
let flags = bs.compiler_flags();
assert!(flags.c_flags.iter().any(|f| f == "-DSOME_DEFINE=1"));
assert!(flags.cxx_flags.iter().any(|f| f == "-stdlib=libc++"));
assert!(flags.ld_flags.contains(&"-fuse-ld=lld".to_string()));
}
#[test]
fn test_meson_wrap_with_url() {
let mut wrap = X86MesonWrap::new("mylib");
wrap.directory = Some("mylib-1.0".to_string());
wrap.url = Some("https://example.com/mylib-1.0.tar.gz".to_string());
wrap.revision = Some("abc123hash".to_string());
let r#gen = wrap.generate();
assert!(r#gen.contains("directory = mylib-1.0"));
assert!(r#gen.contains("source_url = https://example.com"));
assert!(r#gen.contains("source_hash = abc123hash"));
}
#[test]
fn test_bazel_external_dep_http_archive() {
let mut dep = X86BazelExternalDep::new("abseil", "20240116.0");
dep.urls = vec!["https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.0.tar.gz".to_string()];
dep.sha256 = Some("abc123def456".to_string());
dep.strip_prefix = Some("abseil-cpp-20240116.0".to_string());
let ws = dep.generate_workspace();
assert!(ws.contains("http_archive"));
assert!(ws.contains("abseil"));
assert!(ws.contains("strip_prefix"));
}
#[test]
fn test_ctest_config_with_labels() {
let mut ct = X86CTestConfig::new();
ct.labels = vec!["unit".to_string(), "integration".to_string()];
let r#gen = ct.generate();
assert!(r#gen.contains("CTEST_LABELS"));
assert!(r#gen.contains("unit"));
}
#[test]
fn test_ctest_config_edge_cases() {
let ct = X86CTestConfig::new();
assert!(ct.enable);
assert!(ct.output_on_failure);
let r#gen = ct.generate();
assert!(!r#gen.is_empty());
}
#[test]
fn test_build_output_structure() {
let output = X86BuildOutput {
files_generated: vec!["a.txt".to_string(), "b.txt".to_string()],
build_commands: vec!["make".to_string()],
warnings: vec!["deprecated flag".to_string()],
estimated_build_time_ms: 5000,
};
assert_eq!(output.files_generated.len(), 2);
assert_eq!(output.build_commands.len(), 1);
assert_eq!(output.warnings.len(), 1);
assert_eq!(output.estimated_build_time_ms, 5000);
}
}