Files
unicorn/bindings/rust/sys/build.rs
Amaan Qureshi 1b98fec009 fix(rust): watch all source and header files (#2159)
This will trigger a rebuild when any files change, instead of having to
have to trigger it manually
2025-04-13 10:35:38 +08:00

400 lines
13 KiB
Rust

use std::{env, path::PathBuf, process::Command};
use bindgen::callbacks::{EnumVariantValue, ParseCallbacks};
use heck::ToUpperCamelCase;
fn ninja_available() -> bool {
Command::new("ninja").arg("--version").spawn().is_ok()
}
fn msvc_cmake_tools_available() -> bool {
Command::new("cmake").arg("--version").spawn().is_ok() && ninja_available()
}
fn get_tool_paths_msvc(compiler: &cc::Tool) -> Option<(PathBuf, PathBuf)> {
// If tools are already available, don't need to find them
if msvc_cmake_tools_available() {
return None;
}
let target = env::var("TARGET").unwrap();
let devenv = cc::windows_registry::find_tool(target.as_str(), "devenv");
let tool_root = devenv.map_or_else(
|| {
// if devenv (i.e. Visual Studio) was not found, assume compiler is
// from standalone Build Tools and look there instead.
let tools_name = std::ffi::OsStr::new("BuildTools");
let compiler_path = compiler.path().to_path_buf();
compiler_path
.iter()
.find(|x| *x == tools_name)
.expect("Failed to find devenv or Build Tools");
compiler_path
.iter()
.take_while(|x| *x != tools_name)
.collect::<PathBuf>()
.join(tools_name)
.join(r"Common7\IDE")
},
|devenv_tool| devenv_tool.path().parent().unwrap().to_path_buf(),
);
let cmake_pkg_dir = tool_root.join(r"CommonExtensions\Microsoft\CMake");
let cmake_path = cmake_pkg_dir.join(r"CMake\bin\cmake.exe");
let ninja_path = cmake_pkg_dir.join(r"Ninja\ninja.exe");
assert!(cmake_path.is_file(), "missing cmake");
assert!(ninja_path.is_file(), "missing ninja");
Some((cmake_path, ninja_path))
}
fn build_with_cmake() {
let current_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let uc_dir = current_dir
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
let compiler = cc::Build::new().get_compiler();
// Initialize configuration
let mut config = cmake::Config::new(uc_dir);
// Check for tools and set up configuration
let has_ninja = if compiler.is_like_msvc() {
// MSVC-specific setup
if let Some((cmake_path, ninja_path)) = get_tool_paths_msvc(&compiler) {
// Tell Cargo where to find the tools instead of modifying PATH
println!("cargo:rustc-env=CMAKE_PATH={}", cmake_path.display());
println!("cargo:rustc-env=NINJA_PATH={}", ninja_path.display());
// Set cmake path for the cmake crate
config.define("CMAKE_PROGRAM", cmake_path.to_str().unwrap());
}
true
} else {
// Non-MSVC setup
ninja_available()
};
// Configure build generator
if has_ninja {
config.generator("Ninja");
}
let mut archs = String::new();
if std::env::var("CARGO_FEATURE_ARCH_X86").is_ok() {
archs.push_str("x86;");
}
if std::env::var("CARGO_FEATURE_ARCH_ARM").is_ok() {
archs.push_str("arm;");
}
if std::env::var("CARGO_FEATURE_ARCH_AARCH64").is_ok() {
archs.push_str("aarch64;");
}
if std::env::var("CARGO_FEATURE_ARCH_RISCV").is_ok() {
archs.push_str("riscv;");
}
if std::env::var("CARGO_FEATURE_ARCH_MIPS").is_ok() {
archs.push_str("mips;");
}
if std::env::var("CARGO_FEATURE_ARCH_SPARC").is_ok() {
archs.push_str("sparc;");
}
if std::env::var("CARGO_FEATURE_ARCH_M68K").is_ok() {
archs.push_str("m68k;");
}
if std::env::var("CARGO_FEATURE_ARCH_PPC").is_ok() {
archs.push_str("ppc;");
}
if std::env::var("CARGO_FEATURE_ARCH_S390X").is_ok() {
archs.push_str("s390x;");
}
if std::env::var("CARGO_FEATURE_ARCH_TRICORE").is_ok() {
archs.push_str("tricore;");
}
if !archs.is_empty() {
archs.pop();
}
if config.get_profile() == "Debug" {
config.define("UNICORN_LOGGING", "ON");
}
// need to clear build target and append "build" to the path because
// unicorn's CMakeLists.txt doesn't properly support 'install', so we use
// the build artifacts from the build directory, which cmake crate sets
// to "<out_dir>/build/"
let dst = config
.define("UNICORN_BUILD_TESTS", "OFF")
.define("UNICORN_INSTALL", "OFF")
.define("UNICORN_ARCH", archs)
.no_build_target(true)
.build();
println!(
"cargo:rustc-link-search=native={}",
dst.join("build").display()
);
// Lazymio(@wtdcode): Dynamic link may break. See: https://github.com/rust-lang/cargo/issues/5077
if cfg!(feature = "dynamic_linkage") {
if compiler.is_like_msvc() {
println!("cargo:rustc-link-lib=dylib=unicorn-import");
} else {
println!("cargo:rustc-link-lib=dylib=unicorn");
}
} else {
println!("cargo:rustc-link-lib=static=unicorn");
}
if !compiler.is_like_msvc() {
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=m");
}
}
fn watch_source_files() {
let current_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let project_root = std::path::Path::new(&current_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
println!(
"cargo:rerun-if-changed={}",
project_root.join("uc.c").display()
);
println!(
"cargo:rerun-if-changed={}",
project_root.join("list.c").display()
);
// Directories to watch for changes
let watch_dirs = vec!["qemu", "include", "bindings", "glib_compat"];
let watch_extensions = vec![".c", ".h"];
for dir in watch_dirs {
let dir_path = project_root.join(dir);
if dir_path.exists() {
register_dir_files(&dir_path, &watch_extensions);
}
}
}
fn register_dir_files(dir: &std::path::Path, extensions: &[&str]) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.filter_map(Result::ok) {
let path = entry.path();
if path.is_dir() {
register_dir_files(&path, extensions);
} else if let Some(ext) = path.extension() {
if extensions
.iter()
.any(|&e| e == format!(".{}", ext.to_string_lossy()))
{
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
}
}
#[derive(Debug)]
struct Renamer;
impl ParseCallbacks for Renamer {
fn item_name(&self, original_item_name: &str) -> Option<String> {
// Special case for error type
if original_item_name == "uc_err" {
return Some(String::from("uc_error"));
}
if original_item_name.contains("_cpu_") {
return original_item_name
.strip_prefix("uc_cpu_")
.map(|suffix| format!("{}CpuModel", suffix.to_upper_camel_case()));
}
if original_item_name.ends_with("_reg") {
return original_item_name
.strip_prefix("uc_")
.and_then(|suffix| suffix.strip_suffix("_reg"))
.map(|suffix| format!("Register{}", suffix.to_uppercase()));
}
if original_item_name.ends_with("_insn") {
return original_item_name
.strip_prefix("uc_")
.and_then(|suffix| suffix.strip_suffix("_insn"))
.map(|suffix| format!("{}Insn", suffix.to_upper_camel_case()));
}
if original_item_name.contains("_mode_") {
return original_item_name
.strip_prefix("uc_mode_")
.map(|suffix| format!("{}Mode", suffix.to_upper_camel_case()));
}
// Map various specific types to more idiomatic Rust names
match original_item_name {
"uc_query_type" => Some(String::from("Query")),
"uc_tlb_type" => Some(String::from("TlbType")),
"uc_mem_type" => Some(String::from("MemType")),
"uc_tb" => Some(String::from("TranslationBlock")),
"uc_arch" => Some(String::from("Arch")),
"uc_mode" => Some(String::from("Mode")),
"uc_mem_region" => Some(String::from("MemRegion")),
"uc_prot" => Some(String::from("Prot")),
"uc_hook_type" => Some(String::from("HookType")),
"uc_tlb_entry" => Some(String::from("TlbEntry")),
"uc_control_type" => Some(String::from("ControlType")),
"uc_context_content" => Some(String::from("ContextMode")),
"uc_tcg_op_code" => Some(String::from("TcgOpCode")),
"uc_tcg_op_flag" => Some(String::from("TcgOpFlag")),
_ => None,
}
}
fn enum_variant_name(
&self,
enum_name: Option<&str>,
original_variant_name: &str,
_variant_value: EnumVariantValue,
) -> Option<String> {
if let Some(enum_name) = enum_name {
if enum_name.starts_with("enum uc_") {
// Prefix to strip from enum variant names
let prefix = match enum_name.strip_prefix("enum uc_").unwrap() {
"query_type" => "UC_QUERY",
"tlb_type" => "UC_TLB",
"control_type" => "UC_CTL",
"context_content" => "UC_CTL_CONTEXT",
"err" => "UC_ERR",
"mem_type" | "mem_region" => "UC_MEM",
"arch" => "UC_ARCH",
"mode" => "UC_MODE",
"prot" => "UC_PROT",
"hook_type" => "UC_HOOK",
"x86_insn" => "UC_X86_INS",
"tcg_op_code" => "UC_TCG_OP",
"tcg_op_flag" => "UC_TCG_OP_FLAG",
other => format!("UC_{}", other.to_uppercase()).leak(),
}
.to_string()
+ "_";
// Strip prefix
let mut fixed = original_variant_name
.strip_prefix(&prefix)
.map(str::to_uppercase);
// Special handling for numeric register names in PPC and MIPS
if (enum_name == "enum uc_ppc_reg" || enum_name == "enum uc_mips_reg")
&& fixed.as_ref().is_some_and(|s| s.parse::<u32>().is_ok())
{
fixed = fixed.map(|s| format!("R{s}"));
}
// Special handling for CPU variants that start with a number
if enum_name.contains("cpu")
&& fixed
.as_ref()
.is_some_and(|s| s.chars().next().unwrap().is_ascii_digit())
{
fixed = fixed.map(|s| format!("Model_{s}"));
}
// Special handling for mode values
if enum_name == "enum uc_mode" {
fixed = fixed.map(|s| match s.as_str() {
"16" | "32" | "64" => format!("MODE_{s}"),
_ => s,
});
}
return fixed;
}
}
None
}
}
fn generate_bindings() {
const HEADER_PATH: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../../include/unicorn/unicorn.h"
);
let bitflag_enums = [
"uc_hook_type",
"uc_tcg_op_flag",
"uc_prot",
"uc_mode",
"uc_context_content",
"uc_control_type",
];
let bindings = bindgen::Builder::default()
.header(HEADER_PATH)
.layout_tests(false)
.allowlist_type("^uc.*")
.allowlist_function("^uc_.*")
.allowlist_var("^uc.*")
.rustified_enum("^uc.*")
.prepend_enum_name(false)
.parse_callbacks(Box::new(Renamer))
.bitfield_enum(bitflag_enums.join("|"))
.derive_ord(true)
.derive_eq(true)
.use_core()
.generate()
.expect("Failed to generate bindings");
let bindings_rs = "src/bindings.rs";
bindings
.write_to_file(bindings_rs)
.unwrap_or_else(|_| panic!("Failed to write bindings into path: {bindings_rs:?}"));
}
fn main() {
watch_source_files();
generate_bindings();
match pkg_config::Config::new()
.atleast_version("2")
.cargo_metadata(false)
.probe("unicorn")
{
Ok(lib) => {
for dir in lib.link_paths {
println!("cargo:rustc-link-search=native={}", dir.to_str().unwrap());
}
if cfg!(feature = "dynamic_linkage") {
if cc::Build::new().get_compiler().is_like_msvc() {
println!("cargo:rustc-link-lib=dylib=unicorn-import");
} else {
println!("cargo:rustc-link-lib=dylib=unicorn");
}
} else {
println!("cargo:rustc-link-arg=-Wl,-allow-multiple-definition");
println!("cargo:rustc-link-lib=static=unicorn");
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=m");
}
}
Err(_) => {
build_with_cmake();
}
}
}