use std::ffi::CStr;
use std::path::Path;
pub const SELF_COMPILE_C: &str = r##"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Forward declarations of llvm-native ABI functions */
extern int llvm_native_compile_string(const char *source, const char *options);
extern const char *llvm_native_version(void);
extern const char *llvm_native_get_error(void);
extern void llvm_native_clear_error(void);
/* Simple test: compile a C string and verify it produced output */
int main(void) {
const char *ver = llvm_native_version();
if (ver == NULL || ver[0] == '\0') {
fprintf(stderr, "FAIL: version string is empty\n");
return 1;
}
printf("llvm-native version: %s\n", ver);
/* Compile a tiny C program */
const char *source = "int main(void) { return 42; }\n";
int result = llvm_native_compile_string(source, "-O0");
if (result != 0) {
fprintf(stderr, "FAIL: compile_string returned %d\n", result);
fprintf(stderr, " error: %s\n", llvm_native_get_error());
return 2;
}
printf("Self-compile test passed: version=%s, compile=OK\n", ver);
return 42;
}
"##;
pub fn build_and_run_self_compile() -> Result<(), String> {
let tmp = std::env::temp_dir().join("llvm_native_self_compile");
std::fs::create_dir_all(&tmp).map_err(|e| format!("mkdir: {}", e))?;
let c_path = tmp.join("self_compile_test.c");
std::fs::write(&c_path, SELF_COMPILE_C).map_err(|e| format!("write c source: {}", e))?;
let header_path = tmp.join("llvm_native.h");
let header_content = llvm_native_core::clang::clang_abi_bridge::llvm_native_abi_header::HEADER_C;
std::fs::write(&header_path, header_content).map_err(|e| format!("write header: {}", e))?;
let obj_path = tmp.join("self_compile_test.o");
let bin_path = tmp.join("self_compile_test");
let cli_binary = if Path::new("target/release/llvm-native").exists() {
"target/release/llvm-native"
} else if Path::new("target/debug/llvm-native").exists() {
"target/debug/llvm-native"
} else {
return Err("llvm-native CLI not found — build first".into());
};
let status = std::process::Command::new(cli_binary)
.args(&["clang", "-c", "-O0", "-o"])
.arg(&obj_path)
.arg(&c_path)
.status()
.map_err(|e| format!("clang execute: {}", e))?;
if !status.success() {
return Err(format!("clang compilation failed with status {:?}", status));
}
let static_lib = if Path::new("target/release/libllvm_native_core.a").exists() {
"target/release/libllvm_native_core.a"
} else if Path::new("target/debug/libllvm_native_core.a").exists() {
"target/debug/libllvm_native_core.a"
} else {
return Err("libllvm_native_core.a not found — rebuild with staticlib".into());
};
let link_status = std::process::Command::new("gcc")
.args(&["-nostdlib", "-o"])
.arg(&bin_path)
.arg(&obj_path)
.arg(static_lib)
.args(&[
"-lc",
"-lpthread",
"-ldl",
"-lm",
"-lrt",
"-L/usr/lib",
"-L/lib/x86_64-linux-gnu",
])
.status()
.map_err(|e| format!("link execute: {}", e))?;
if !link_status.success() {
return Err(format!("linking failed with status {:?}", link_status));
}
let output = std::process::Command::new(&bin_path)
.output()
.map_err(|e| format!("run execute: {}", e))?;
if output.status.success() {
let exit_code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout);
println!("Self-compile test output: {}", stdout.trim());
println!("Exit code: {}", exit_code);
if exit_code == 42 {
Ok(())
} else {
Err(format!("expected exit 42, got {}", exit_code))
}
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("run failed: {}", stderr.trim()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self_compile_abi_bridge_exists() {
let header = llvm_native_core::clang::clang_abi_bridge::llvm_native_abi_header::HEADER_C;
assert!(
header.contains("llvm_native_compile_string"),
"HEADER_C should contain compile_string declaration"
);
assert!(
header.contains("llvm_native_version"),
"HEADER_C should contain version declaration"
);
println!("ABI header is {} bytes", header.len());
}
#[test]
fn test_self_compile_source_valid() {
let source = SELF_COMPILE_C;
assert!(source.contains("main(void)"));
assert!(source.contains("llvm_native_version()"));
assert!(source.contains("return 42"));
}
#[test]
fn test_self_compile_cli_exists() {
let cli = if Path::new("target/release/llvm-native").exists() {
"target/release/llvm-native"
} else if Path::new("target/debug/llvm-native").exists() {
"target/debug/llvm-native"
} else {
return; };
assert!(
Path::new(cli).exists(),
"CLI binary should exist at {}",
cli
);
}
#[test]
fn test_self_compile_static_lib_exists() {
let lib = if Path::new("target/release/libllvm_native_core.a").exists() {
"target/release/libllvm_native_core.a"
} else if Path::new("target/debug/libllvm_native_core.a").exists() {
"target/debug/libllvm_native_core.a"
} else {
return; };
assert!(
Path::new(lib).exists(),
"Static library should exist at {}",
lib
);
}
}