You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
6.7 KiB
208 lines
6.7 KiB
use bindgen::callbacks::ParseCallbacks; |
|
use bindgen::{Builder, RustTarget}; |
|
use itertools::Itertools; |
|
|
|
use std::env; |
|
use std::error::Error; |
|
use std::path::{Path, PathBuf}; |
|
use std::process::{Command, Output, Stdio}; |
|
|
|
#[derive(Debug)] |
|
struct CustomCallbacks; |
|
|
|
impl ParseCallbacks for CustomCallbacks { |
|
fn process_comment(&self, comment: &str) -> Option<String> { |
|
Some(doxygen_rs::transform(comment)) |
|
} |
|
} |
|
|
|
fn main() { |
|
let devkitpro = env::var("DEVKITPRO").unwrap(); |
|
let devkitarm = env::var("DEVKITARM").unwrap(); |
|
let profile = env::var("PROFILE").unwrap(); |
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); |
|
|
|
println!("cargo:rerun-if-changed=build.rs"); |
|
println!("cargo:rerun-if-env-changed=DEVKITPRO"); |
|
println!("cargo:rustc-link-search=native={devkitpro}/libctru/lib"); |
|
println!( |
|
"cargo:rustc-link-lib=static={}", |
|
match profile.as_str() { |
|
"debug" => "ctrud", |
|
_ => "ctru", |
|
} |
|
); |
|
|
|
detect_and_track_libctru(); |
|
|
|
let gcc_version = get_gcc_version(PathBuf::from(&devkitarm).join("bin/arm-none-eabi-gcc")); |
|
|
|
let include_path = PathBuf::from_iter([devkitpro.as_str(), "libctru", "include"]); |
|
let ctru_header = include_path.join("3ds.h"); |
|
|
|
let sysroot = Path::new(&devkitarm).join("arm-none-eabi"); |
|
let system_include = sysroot.join("include"); |
|
let gcc_include = PathBuf::from(format!( |
|
"{devkitarm}/lib/gcc/arm-none-eabi/{gcc_version}/include" |
|
)); |
|
let errno_header = system_include.join("errno.h"); |
|
|
|
// Build libctru bindings |
|
let bindings = Builder::default() |
|
.header(ctru_header.to_str().unwrap()) |
|
.header(errno_header.to_str().unwrap()) |
|
.rust_target(RustTarget::Nightly) |
|
.use_core() |
|
.trust_clang_mangling(false) |
|
.must_use_type("Result") |
|
.layout_tests(false) |
|
.ctypes_prefix("::libc") |
|
.prepend_enum_name(false) |
|
.blocklist_type("u(8|16|32|64)") |
|
.blocklist_type("__builtin_va_list") |
|
.blocklist_type("__va_list") |
|
.opaque_type("MiiData") |
|
.derive_default(true) |
|
.wrap_static_fns(true) |
|
.wrap_static_fns_path(out_dir.join("libctru_statics_wrapper")) |
|
.clang_args([ |
|
"--target=arm-none-eabi", |
|
"--sysroot", |
|
sysroot.to_str().unwrap(), |
|
"-isystem", |
|
system_include.to_str().unwrap(), |
|
"-isystem", |
|
gcc_include.to_str().unwrap(), |
|
"-I", |
|
include_path.to_str().unwrap(), |
|
"-mfloat-abi=hard", |
|
"-march=armv6k", |
|
"-mtune=mpcore", |
|
"-mfpu=vfp", |
|
"-DARM11", |
|
"-D__3DS__", |
|
]) |
|
.parse_callbacks(Box::new(CustomCallbacks)) |
|
.generate() |
|
.expect("unable to generate bindings"); |
|
|
|
bindings |
|
.write_to_file(out_dir.join("bindings.rs")) |
|
.expect("Couldn't write bindings!"); |
|
|
|
// Compile static inline fns wrapper |
|
let cc = Path::new(devkitarm.as_str()).join("bin/arm-none-eabi-gcc"); |
|
let ar = Path::new(devkitarm.as_str()).join("bin/arm-none-eabi-ar"); |
|
|
|
cc::Build::new() |
|
.compiler(cc) |
|
.archiver(ar) |
|
.include(&include_path) |
|
.file(out_dir.join("libctru_statics_wrapper.c")) |
|
.flag("-march=armv6k") |
|
.flag("-mtune=mpcore") |
|
.flag("-mfloat-abi=hard") |
|
.flag("-mfpu=vfp") |
|
.flag("-mtp=soft") |
|
.flag("-Wno-deprecated-declarations") |
|
.compile("ctru_statics_wrapper"); |
|
} |
|
|
|
fn get_gcc_version(path_to_gcc: PathBuf) -> String { |
|
let Output { stdout, .. } = Command::new(path_to_gcc) |
|
.arg("--version") |
|
.stderr(Stdio::inherit()) |
|
.output() |
|
.unwrap(); |
|
|
|
let stdout_str = String::from_utf8_lossy(&stdout); |
|
|
|
stdout_str |
|
.split(|c: char| c.is_whitespace()) |
|
.nth(4) |
|
.unwrap() |
|
.to_string() |
|
} |
|
|
|
fn detect_and_track_libctru() { |
|
let pacman = match which::which("dkp-pacman") |
|
.or_else(|err1| which::which("pacman").map_err(|err2| format!("{err1}; {err2}"))) |
|
{ |
|
Ok(pacman) => pacman, |
|
Err(err) => { |
|
println!("cargo:warning=unable to find `pacman` or `dkp-pacman`: {err}"); |
|
return; |
|
} |
|
}; |
|
|
|
match get_libctru_version(&pacman) { |
|
Ok((maj, min, patch, rel)) => { |
|
eprintln!("using libctru version {maj}.{min}.{patch}-{rel}"); |
|
|
|
// These are accessible by the crate during build with `env!()`. |
|
// We might consider exporting some public constants or something. |
|
println!("cargo:rustc-env=LIBCTRU_VERSION={maj}.{min}.{patch}-{rel}"); |
|
println!("cargo:rustc-env=LIBCTRU_MAJOR={maj}"); |
|
println!("cargo:rustc-env=LIBCTRU_MINOR={min}"); |
|
println!("cargo:rustc-env=LIBCTRU_PATCH={patch}"); |
|
println!("cargo:rustc-env=LIBCTRU_RELEASE={rel}"); |
|
} |
|
Err(err) => println!("cargo:warning=unknown libctru version: {err}"), |
|
} |
|
|
|
if let Err(err) = track_libctru_files(&pacman) { |
|
println!("cargo:warning=unable to track `libctru` files for changes: {err}"); |
|
} |
|
} |
|
|
|
fn get_libctru_version(pacman: &Path) -> Result<(String, String, String, String), Box<dyn Error>> { |
|
let Output { stdout, .. } = Command::new(pacman) |
|
.args(["--query", "libctru"]) |
|
.stderr(Stdio::inherit()) |
|
.output()?; |
|
|
|
let output_str = String::from_utf8_lossy(&stdout); |
|
|
|
let (_pkg, lib_version) = output_str |
|
.split_once(char::is_whitespace) |
|
.ok_or_else(|| format!("unexpected pacman output format: {output_str:?}"))?; |
|
|
|
let lib_version = lib_version.trim(); |
|
|
|
parse_libctru_version(lib_version).map_err(Into::into) |
|
} |
|
|
|
fn parse_libctru_version(version: &str) -> Result<(String, String, String, String), String> { |
|
version |
|
.split(|c| c == '.' || c == '-') |
|
.map(String::from) |
|
.collect_tuple() |
|
.ok_or_else(|| format!("unexpected number of version segments: {version:?}")) |
|
} |
|
|
|
fn track_libctru_files(pacman: &Path) -> Result<(), String> { |
|
let stdout = match Command::new(pacman) |
|
.args(["--query", "--list", "libctru"]) |
|
.stderr(Stdio::inherit()) |
|
.output() |
|
{ |
|
Ok(Output { stdout, status, .. }) if status.success() => stdout, |
|
Ok(Output { status, .. }) => { |
|
return Err(format!("pacman query failed with status {status}")); |
|
} |
|
Err(err) => { |
|
return Err(format!("pacman query failed: {err}")); |
|
} |
|
}; |
|
|
|
for line in String::from_utf8_lossy(&stdout).trim().split('\n') { |
|
let Some((_pkg, file)) = line.split_once(char::is_whitespace) else { |
|
println!("cargo:warning=unexpected line from pacman query: {line:?}"); |
|
continue; |
|
}; |
|
|
|
println!("cargo:rerun-if-changed={file}"); |
|
} |
|
|
|
Ok(()) |
|
}
|
|
|