#![allow(dead_code)]
extern crate bindgen;
use std::collections::HashSet;
use std::env;
use std::path::{Path, PathBuf};
const LATEST_RAYLIB_VERSION: &str = "5.0.0";
const LATEST_RAYLIB_API_VERSION: &str = "5";
#[derive(Debug)]
struct IgnoreMacros(HashSet<String>);
impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}
#[cfg(feature = "nobuild")]
fn build_with_cmake(_src_path: &str) {}
#[cfg(not(feature = "nobuild"))]
fn build_with_cmake(src_path: &str) {
fn join_cmake_lib_directory(path: PathBuf) -> PathBuf {
let possible_cmake_lib_directories = ["lib", "lib64", "lib32"];
for lib_directory in &possible_cmake_lib_directories {
let lib_path = path.join(lib_directory);
if lib_path.exists() {
return lib_path;
}
}
path
}
let target = env::var("TARGET").expect("Cargo build scripts always have TARGET");
let (platform, platform_os) = platform_from_target(&target);
configure_windows_cmake_generator();
configure_windows_emscripten_wrappers(&target);
let mut conf = cmake::Config::new(src_path);
let mut builder;
let profile;
#[cfg(debug_assertions)]
{
builder = conf.profile("Debug");
builder = builder.define("CMAKE_BUILD_TYPE", "Debug");
profile = "Debug";
}
#[cfg(not(debug_assertions))]
{
builder = conf.profile("Release");
builder = builder.define("CMAKE_BUILD_TYPE", "Release");
profile = "Release";
}
builder
.define("BUILD_EXAMPLES", "OFF")
.define("CMAKE_BUILD_TYPE", profile);
if platform == Platform::Web && cfg!(windows) {
let cflags = emscripten_cmake_flags();
builder
.define("CMAKE_C_FLAGS", &cflags)
.define("CMAKE_CXX_FLAGS", &cflags)
.define("CMAKE_ASM_FLAGS", &cflags);
}
#[cfg(feature = "custom_frame_control")]
builder.cflag("-DSUPPORT_CUSTOM_FRAME_CONTROL=1");
#[cfg(feature = "wayland")]
{
builder.define("GLFW_BUILD_WAYLAND", "ON");
builder.define("USE_EXTERNAL_GLFW", "ON"); }
{
#[cfg(feature = "opengl_33")]
builder.define("OPENGL_VERSION", "3.3");
#[cfg(feature = "opengl_21")]
builder.define("OPENGL_VERSION", "2.1");
#[cfg(feature = "opengl_es_20")]
{
builder.define("OPENGL_VERSION", "ES 2.0");
println!("cargo:rustc-link-lib=GLESv2");
println!("cargo:rustc-link-lib=GLdispatch");
}
#[cfg(feature = "opengl_es_30")]
{
builder.define("OPENGL_VERSION", "ES 3.0");
println!("cargo:rustc-link-lib=GLESv2");
println!("cargo:rustc-link-lib=GLdispatch");
}
#[cfg(all(feature = "software_render", not(feature = "platform_memory")))]
builder.define("OPENGL_VERSION", "Software");
#[cfg(not(any(
feature = "opengl_33",
feature = "opengl_21",
// feature = "opengl_11",
feature = "opengl_es_20",
feature = "opengl_es_30",
feature = "software_render",
feature = "platform_memory"
)))]
builder.define("OPENGL_VERSION", "OFF");
}
#[cfg(feature = "noscreenshot")]
builder.cflag("-DSUPPORT_SCREEN_CAPTURE=0");
match platform {
Platform::Desktop => {
#[cfg(feature = "platform_memory")]
{
conf.define("PLATFORM", "Memory")
}
#[cfg(all(feature = "sdl", not(feature = "platform_memory")))]
{
let sdl3_present = std::process::Command::new("pkg-config")
.args(["--exists", "sdl3"])
.status()
.map(|s| s.success())
.unwrap_or(false);
if sdl3_present {
println!("cargo:rustc-link-lib=SDL3");
} else {
println!("cargo:rustc-link-lib=SDL2");
}
conf.define("PLATFORM", "SDL")
}
#[cfg(not(any(feature = "sdl", feature = "platform_memory")))]
{
conf.define("PLATFORM", "Desktop")
}
}
Platform::Web => {
#[cfg(feature = "platform_web_rgfw")]
{
conf.define("PLATFORM", "WebRGFW")
}
#[cfg(not(feature = "platform_web_rgfw"))]
{
conf.define("PLATFORM", "Web")
}
}
Platform::RPI => conf.define("PLATFORM", "Raspberry Pi"),
Platform::Android => {
let android_ndk_home = env::var("ANDROID_NDK_HOME")
.expect("Please set the environment variable: ANDROID_NDK_HOME:(e.g /home/u/Android/Sdk/ndk/VXXX/)");
let android_platform = target.split("-").last().expect("fail to parse the android version of the target triple, example:'aarch64-linux-android25'");
let abi_version = android_platform
.split("-")
.last()
.expect("Could not get abi version. Is ANDROID_PLATFORM valid?");
let toolchain_file =
format!("{}/build/cmake/android.toolchain.cmake", &android_ndk_home);
let android_arch_abi = match target.as_str() {
"aarch64-linux-android" => "arm64-v8a",
"armv7-linux-androideabi" => "armeabi-v7a",
_ => panic!("Unsupported target triple for Android"),
};
conf.define("CMAKE_SYSTEM_NAME", "Android")
.define("PLATFORM", "Android")
.define("CMAKE_SYSTEM_VERSION", abi_version)
.define("ANDROID_ABI", android_arch_abi)
.define("CMAKE_ANDROID_ARCH_ABI", android_arch_abi)
.define("CMAKE_ANDROID_NDK", &android_ndk_home)
.define("ANDROID_PLATFORM", android_platform)
.define("CMAKE_TOOLCHAIN_FILE", &toolchain_file)
}
};
let dst = conf.build();
let dst_lib = join_cmake_lib_directory(dst);
if platform_os == PlatformOS::Windows {
if Path::new(&dst_lib.join("raylib.lib")).exists() {
} else if Path::new(&dst_lib.join("raylib_static.lib")).exists() {
std::fs::copy(
dst_lib.join("raylib_static.lib"),
dst_lib.join("raylib.lib"),
)
.expect("failed to create windows library");
} else if Path::new(&dst_lib.join("libraylib_static.a")).exists() {
std::fs::copy(
dst_lib.join("libraylib_static.a"),
dst_lib.join("libraylib.a"),
)
.expect("failed to create windows library");
} else if Path::new(&dst_lib.join("libraylib.a")).exists() {
} else {
panic!("failed to create windows library");
}
} if platform == Platform::Web && !Path::new(&dst_lib.join("libraylib.a")).exists() {
std::fs::copy(dst_lib.join("libraylib.bc"), dst_lib.join("libraylib.a"))
.expect("failed to create wasm library");
}
println!("cargo:rustc-link-search=native={}", dst_lib.display());
if platform == Platform::Android {
println!("cargo:rustc-link-lib=log");
println!("cargo:rustc-link-lib=android");
println!("cargo:rustc-link-lib=EGL");
println!("cargo:rustc-link-lib=GLESv2");
println!("cargo:rustc-link-lib=OpenSLES");
println!("cargo:rustc-link-lib=c");
println!("cargo:rustc-link-lib=m");
}
}
fn gen_bindings() {
let target = env::var("TARGET").expect("Cargo build scripts always have TARGET");
let (platform, os) = platform_from_target(&target);
let plat = match platform {
Platform::Desktop => {
#[cfg(feature = "platform_memory")]
{
"-DPLATFORM_MEMORY"
}
#[cfg(not(feature = "platform_memory"))]
{
"-DPLATFORM_DESKTOP"
}
}
Platform::RPI => "-DPLATFORM_RPI",
Platform::Android => "-DPLATFORM_ANDROID",
Platform::Web => {
#[cfg(feature = "platform_web_rgfw")]
{
"-DPLATFORM_WEB_RGFW"
}
#[cfg(not(feature = "platform_web_rgfw"))]
{
"-DPLATFORM_WEB"
}
}
};
let ignored_macros = IgnoreMacros(
vec![
"FP_INFINITE".into(),
"FP_NAN".into(),
"FP_NORMAL".into(),
"FP_SUBNORMAL".into(),
"FP_ZERO".into(),
"IPPORT_RESERVED".into(),
]
.into_iter()
.collect(),
);
let mut builder = bindgen::Builder::default()
.header("binding/binding.h")
.rustified_enum(".+")
.clang_arg("-std=c99")
.clang_arg("-I./raylib/src")
.clang_arg(plat)
.parse_callbacks(Box::new(ignored_macros));
if platform == Platform::Desktop && os == PlatformOS::Windows {
builder = builder.clang_arg("-D__STDC__");
}
if platform == Platform::Web {
builder = builder
.clang_arg("-fvisibility=default")
.clang_arg("--target=wasm32-emscripten");
if let Some(sysroot) = emscripten_sysroot() {
builder = builder
.clang_arg(format!("--sysroot={}", sysroot.display()))
.clang_arg(format!("-I{}", sysroot.join("include").display()));
}
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
fn gen_rgui() {
#[cfg(target_os = "windows")]
{
cc::Build::new()
.files(vec!["binding/rgui_wrapper.cpp", "binding/utils_log.cpp"])
.include("binding")
.include("raylib/src")
.warnings(false)
.extra_warnings(false)
.compile("rgui");
}
#[cfg(not(target_os = "windows"))]
{
cc::Build::new()
.files(vec!["binding/rgui_wrapper.c", "binding/utils_log.c"])
.include("binding")
.include("raylib/src")
.warnings(false)
.extra_warnings(false)
.compile("rgui");
}
}
fn configure_windows_emscripten_wrappers(target: &str) {
if !target.contains("wasm32-unknown-emscripten") || !cfg!(windows) {
return;
}
let Some(root) = emscripten_root() else {
return;
};
set_env_if_missing("EMCMAKE", root.join("emcmake.bat"));
set_env_if_missing("EMMAKE", root.join("emmake.bat"));
}
fn configure_windows_cmake_generator() {
if !cfg!(windows) || env::var_os("CMAKE_GENERATOR").is_some() {
return;
}
if command_exists("ninja") {
env::set_var("CMAKE_GENERATOR", "Ninja");
}
}
fn command_exists(name: &str) -> bool {
env::var_os("PATH")
.and_then(|paths| {
env::split_paths(&paths)
.map(|path| path.join(name))
.find(|path| {
path.is_file()
|| path.with_extension("exe").is_file()
|| path.with_extension("bat").is_file()
|| path.with_extension("cmd").is_file()
})
})
.is_some()
}
fn set_env_if_missing(name: &str, value: PathBuf) {
if env::var_os(name).is_some() || !value.exists() {
return;
}
env::set_var(name, value);
}
fn emscripten_cmake_flags() -> String {
"-fwasm-exceptions -sSUPPORT_LONGJMP=wasm".to_string()
}
fn emscripten_sysroot() -> Option<PathBuf> {
emscripten_root()
.map(|root| root.join("cache").join("sysroot"))
.filter(|path| path.join("include").exists())
}
fn emscripten_root() -> Option<PathBuf> {
if let Some(path) = env::var_os("EMSCRIPTEN") {
let path = PathBuf::from(path);
if path.exists() {
return Some(path);
}
}
if let Some(path) = env::var_os("EMSDK") {
let path = PathBuf::from(path).join("upstream").join("emscripten");
if path.exists() {
return Some(path);
}
}
#[cfg(windows)]
{
if let Some(profile) = env::var_os("USERPROFILE") {
let path = PathBuf::from(profile)
.join(".local")
.join("share")
.join("emsdk")
.join("upstream")
.join("emscripten");
if path.exists() {
return Some(path);
}
}
}
#[cfg(not(windows))]
{
if let Some(home) = env::var_os("HOME") {
let path = PathBuf::from(home)
.join(".local")
.join("share")
.join("emsdk")
.join("upstream")
.join("emscripten");
if path.exists() {
return Some(path);
}
}
}
None
}
#[cfg(feature = "nobuild")]
fn link(_platform: Platform, _platform_os: PlatformOS) {}
#[cfg(not(feature = "nobuild"))]
fn link(platform: Platform, platform_os: PlatformOS) {
match platform_os {
PlatformOS::Windows => {
println!("cargo:rustc-link-lib=dylib=winmm");
println!("cargo:rustc-link-lib=dylib=gdi32");
println!("cargo:rustc-link-lib=dylib=user32");
println!("cargo:rustc-link-lib=dylib=shell32");
}
PlatformOS::Linux => {
#[cfg(not(feature = "wayland"))]
{
println!("cargo:rustc-link-search=/usr/local/lib");
println!("cargo:rustc-link-lib=X11");
}
#[cfg(feature = "wayland")]
{
println!("cargo:rustc-link-search=/usr/local/lib");
println!("cargo:rustc-link-lib=wayland-client");
println!("cargo:rustc-link-lib=glfw"); }
}
PlatformOS::OSX => {
println!("cargo:rustc-link-search=native=/usr/local/lib");
println!("cargo:rustc-link-lib=framework=OpenGL");
println!("cargo:rustc-link-lib=framework=Cocoa");
println!("cargo:rustc-link-lib=framework=IOKit");
println!("cargo:rustc-link-lib=framework=CoreFoundation");
println!("cargo:rustc-link-lib=framework=CoreVideo");
}
_ => (),
}
if platform == Platform::Web {
} else if platform == Platform::RPI {
println!("cargo:rustc-link-search=/opt/vc/lib");
println!("cargo:rustc-link-lib=bcm_host");
println!("cargo:rustc-link-lib=brcmEGL");
println!("cargo:rustc-link-lib=brcmGLESv2");
println!("cargo:rustc-link-lib=vcos");
}
println!("cargo:rustc-link-lib=static=raylib");
}
#[cfg(not(feature = "nobuild"))]
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=./binding/binding.h");
emit_env_change_triggers();
let target = env::var("TARGET").expect("Cargo build scripts always have TARGET");
let (platform, platform_os) = platform_from_target(&target);
let raylib_src = "./raylib";
if is_directory_empty(raylib_src) {
panic!("raylib source does not exist in: `raylib-sys/raylib`. Please copy it in");
}
build_with_cmake(raylib_src);
gen_bindings();
link(platform, platform_os);
gen_rgui();
}
#[cfg(feature = "nobuild")]
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=./binding/binding.h");
emit_env_change_triggers();
let target = env::var("TARGET").expect("Cargo build scripts always have TARGET");
if target.contains("wasm32-unknown-emscripten") {
if let Err(e) = env::var("EMCC_CFLAGS") {
if e == std::env::VarError::NotPresent {
panic!("\nYou must have to set EMCC_CFLAGS yourself to compile for WASM.\n{}{}\"\n",{
#[cfg(target_family = "windows")]
{"set EMCC_CFLAGS="}
#[cfg(not(target_family = "windows"))]
{"export EMCC_CFLAGS="}
},"\"-O3 -sUSE_GLFW=3 -sASSERTIONS=1 -sWASM=1 -sASYNCIFY -sGL_ENABLE_GET_PROC_ADDRESS=1\"");
} else {
panic!("\nError regarding EMCC_CFLAGS: {:?}\n", e);
}
}
}
#[cfg(feature = "bindgen")]
gen_bindings();
gen_rgui();
}
fn emit_env_change_triggers() {
for name in [
"ANDROID_NDK_HOME",
"CMAKE_GENERATOR",
"EMCC_CFLAGS",
"EMCMAKE",
"EMMAKE",
"EMSDK",
"EMSCRIPTEN",
"HOME",
"OS",
"PATH",
"USERPROFILE",
] {
println!("cargo:rerun-if-env-changed={name}");
}
}
#[must_use]
fn is_directory_empty(path: &str) -> bool {
match std::fs::read_dir(path) {
Ok(mut dir) => dir.next().is_none(),
Err(_) => true,
}
}
fn run_command(cmd: &str, args: &[&str]) {
use std::process::Command;
match Command::new(cmd).args(args).output() {
Ok(output) => {
if !output.status.success() {
let error = std::str::from_utf8(&output.stderr).unwrap();
panic!("Command '{}' failed: {}", cmd, error);
}
}
Err(error) => {
panic!("Error running command '{}': {:#}", cmd, error);
}
}
}
fn platform_from_target(target: &str) -> (Platform, PlatformOS) {
let platform = if target.contains("wasm") {
Platform::Web
} else if target.contains("armv7-unknown-linux") {
Platform::RPI
} else if target.contains("android") {
Platform::Android
} else {
Platform::Desktop
};
let platform_os = if platform == Platform::Desktop {
if env::var("OS")
.unwrap_or("".to_owned())
.contains("Windows_NT")
|| env::var("TARGET")
.unwrap_or("".to_owned())
.contains("windows")
{
PlatformOS::Windows
} else {
let un: &str = &uname();
match un {
"Linux" => PlatformOS::Linux,
"FreeBSD" => PlatformOS::BSD,
"OpenBSD" => PlatformOS::BSD,
"NetBSD" => PlatformOS::BSD,
"DragonFly" => PlatformOS::BSD,
"Darwin" => PlatformOS::OSX,
_ => panic!("Unknown platform {}", uname()),
}
}
} else if platform == Platform::RPI {
let un: &str = &uname();
if un == "Linux" {
PlatformOS::Linux
} else {
PlatformOS::Unknown
}
} else {
PlatformOS::Unknown
};
(platform, platform_os)
}
fn uname() -> String {
use std::process::Command;
String::from_utf8_lossy(
&Command::new("uname")
.output()
.expect("failed to run uname")
.stdout,
)
.trim()
.to_owned()
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
enum Platform {
Web,
Desktop,
Android,
RPI, }
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
enum PlatformOS {
Windows,
Linux,
BSD,
OSX,
Unknown,
}
#[derive(Debug, PartialEq)]
enum LibType {
Static,
_Shared,
}
#[derive(Debug, PartialEq)]
enum BuildMode {
Release,
Debug,
}
struct BuildSettings {
pub platform: Platform,
pub platform_os: PlatformOS,
pub bundled_glfw: bool,
}