rt 0.19.1

A real-time operating system capable of full preemption
Documentation
use std::{env, path::PathBuf};

static SOURCES: &[&str] = &[
    "barrier.c",
    "cond.c",
    "event.c",
    "list.c",
    "mutex.c",
    "notify.c",
    "pool.c",
    "queue.c",
    "rt.c",
    "rwlock.c",
    "sem.c",
    "sleep.c",
    "timer.c",
];

struct Arch<'a> {
    name: &'a str,
    sources: &'a [&'a str],
    flags: &'a [&'a str],
    defs: &'a [(&'a str, Option<&'a str>)],
}

static SIGNAL_ARCH: Arch = Arch {
    name: "signal",
    sources: &["signal.c", "syscall.S"],
    flags: &["-pthread"],
    defs: &[("_GNU_SOURCE", None)],
};

static ARM_ARCH: Arch = Arch {
    name: "arm",
    sources: &["arm.c", "syscall.S"],
    flags: &["-fshort-enums"], // arm-none-eabi-gcc and rustc use this by default, but clang/bindgen don't
    defs: &[],
};

static RISCV_ARCH: Arch = Arch {
    name: "riscv",
    sources: &["riscv.c", "syscall.S"],
    flags: &[],
    defs: &[],
};

static AARCH64_ARCH: Arch = Arch {
    name: "aarch64",
    sources: &["aarch64.c", "syscall.S"],
    flags: &["-mgeneral-regs-only"],
    defs: &[],
};

struct ArmArFeature<'a> {
    name: &'a str,
    path: &'a str,
    arch_patterns: &'a [&'a str],
}

static ARM_AR_FEATURES: &[ArmArFeature] = &[
    ArmArFeature {
        name: "HERCULES",
        path: "hercules",
        arch_patterns: &["armebv7r", "armv7r"],
    },
    ArmArFeature {
        name: "SITARA_VIM",
        path: "sitara/vim",
        arch_patterns: &["armv7r"],
    },
    ArmArFeature {
        name: "SITARA_INTC",
        path: "sitara/intc",
        arch_patterns: &["armv7a"],
    },
];

static ARM_AR_PATTERNS: &[&str] = &["armv7r", "armebv7r", "armv8r", "armv7a"];

static ARM_MPU_ARCH_PATTERNS: &[&str] = &[
    "thumbv6m",
    "thumbv7m",
    "thumbv7em",
    "thumbv8m.base",
    "thumbv8m.main",
    "armv7r",
    "armebv7r",
    "armv8r",
];

fn main() {
    let root_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target = env::var("TARGET").unwrap();
    let rt_cflags: Vec<String> =
        env::var("RT_CFLAGS").map_or(vec![], |s| s.split(' ').map(String::from).collect());
    println!("cargo:rerun-if-env-changed=RT_CFLAGS");

    let arch = if target_arch == "arm" && target_os == "none" {
        &ARM_ARCH
    } else if target_arch == "riscv32" && target_os == "none" {
        &RISCV_ARCH
    } else if target_arch == "aarch64" && target_os == "none" {
        &AARCH64_ARCH
    } else if ["linux", "macos"].contains(&target_os.as_str())
        && ["aarch64", "x86_64"].contains(&target_arch.as_str())
    {
        &SIGNAL_ARCH
    } else {
        panic!("Unsupported (arch, os) combination: ({target_arch}, {target_os})")
    };

    let src_dir = root_dir.join("src");
    let include_dir = root_dir.join("include");
    let arch_dir = root_dir.join("arch").join(arch.name);
    let arch_include_dir = arch_dir.join("include");

    let mut cc = cc::Build::new();
    cc.no_default_flags(true);
    cc.files(SOURCES.iter().map(|s| src_dir.join(s)));
    cc.files(arch.sources.iter().map(|s| arch_dir.join(s)));
    cc.include(&include_dir);
    cc.include(&arch_include_dir);
    cc.flag("-std=c17");
    cc.flag("-O3");
    for flag in arch.flags {
        cc.flag(flag);
    }
    for &(name, val) in arch.defs {
        cc.define(name, val);
    }
    for flag in rt_cflags.iter() {
        cc.flag(flag);
    }

    if env::var("CARGO_FEATURE_TASK_MPU").is_ok() {
        assert!(
            (target_arch == "arm" || target_arch == "riscv32") && target_os == "none",
            "The task-mpu feature is not supported on {target}.",
        );
        assert!(
            rt_cflags.contains(&"-DRT_MPU_TASK_REGIONS_ENABLE".into())
                || rt_cflags.contains(&"-DRT_MPU_TASK_REGIONS_ENABLE=1".into()),
            "To use the task-mpu feature, the RT_MPU_TASK_REGIONS_ENABLE macro must also be defined in RT_CFLAGS"
        );
    }

    if arch.name == "arm" {
        cc.flag("-flto");
        cc.flag("-ffat-lto-objects");
        cc.flag("-gdwarf-5");
        cc.flag("-ffunction-sections");
        cc.flag("-fdata-sections");
        if target.contains("armeb") {
            cc.compiler("armeb-none-eabi-gcc");
            cc.archiver("armeb-none-eabi-gcc-ar");
        } else {
            cc.compiler("arm-none-eabi-gcc");
            cc.archiver("arm-none-eabi-gcc-ar");
        }
        // Optional Cortex-M7 startup for QEMU
        if env::var("CARGO_FEATURE_QEMU_M7").is_ok() {
            println!("cargo:rerun-if-changed=qemu/m7");
            cc.file("qemu/m7/start.S");
            cc.file("qemu/m7/init.c");
            println!("cargo:rustc-link-arg=-Tqemu/m7/m7.ld");
        }
        // Optional Cortex-R52 startup for QEMU
        if env::var("CARGO_FEATURE_QEMU_R52").is_ok() {
            println!("cargo:rerun-if-changed=qemu/r52");
            cc.include(arch_dir.join("ar/gic"));
            cc.file("qemu/r52/start.S");
            cc.file("qemu/r52/init.c");
            cc.file("qemu/r52/handlers.c");
            cc.file("qemu/r52/vector.S");
            println!("cargo:rustc-link-arg=-Tqemu/r52/r52.ld");
        }
        let mut arm_ar_feature_already_set = false;
        for feat in ARM_AR_FEATURES {
            if env::var(format!("CARGO_FEATURE_{}", feat.name)).is_ok() {
                // Prevent multiple arm/ar features from being enabled.
                assert!(!arm_ar_feature_already_set);
                // Check that the architecture matches what the feature expects.
                assert!(feat.arch_patterns.iter().any(|pat| target.contains(pat)));
                cc.include(arch_dir.join("ar").join(feat.path));
                arm_ar_feature_already_set = true;
            }
        }
        if ARM_AR_PATTERNS.iter().any(|pat| target.contains(pat)) {
            /* On A/R-profile, we must avoid using VFP registers in rt itself, so the lazy FP
             * context tracking works. If Neon is enabled for example, the compiler may attempt to
             * use it for list manipulation, which will be an undefined instruction exception if the
             * active task doesn't have the FPU enabled in FPEXC.  */
            cc.flag("-mgeneral-regs-only");
        }
        if ARM_MPU_ARCH_PATTERNS.iter().any(|pat| target.contains(pat)) {
            // Enable the MPU config if supported for the target architecture.
            println!("cargo:rustc-cfg=mpu");
            if target.contains("v6m") {
                println!("cargo:rustc-cfg=mpu_armv6m");
            } else if target.contains("v7r") {
                println!("cargo:rustc-cfg=mpu_armv7r");
            } else if target.contains("v8m") {
                println!("cargo:rustc-cfg=mpu_armv8m");
            } else if target.contains("v8r") {
                println!("cargo:rustc-cfg=mpu_armv8r");
            } else {
                // v7m or v7em
                println!("cargo:rustc-cfg=mpu_armv7m");
            }
        } else {
            // Otherwise, ensure that the task-mpu feature is not enabled.
            assert!(
                env::var("CARGO_FEATURE_TASK_MPU").is_err(),
                "The task-mpu feature is not supported on {target}."
            );
        }
    } else if arch.name == "aarch64" {
        cc.flag("-flto");
        cc.flag("-ffat-lto-objects");
        cc.flag("-gdwarf-5");
        cc.flag("-ffunction-sections");
        cc.flag("-fdata-sections");
        cc.compiler("aarch64-none-elf-gcc");
        cc.archiver("aarch64-none-elf-gcc-ar");
        // Optional Cortex-A53 startup for QEMU
        if env::var("CARGO_FEATURE_QEMU_A53").is_ok() {
            println!("cargo:rerun-if-changed=qemu/a53");
            cc.include(arch_dir.join("gic"));
            cc.file("qemu/a53/start.S");
            cc.file("qemu/a53/vector.S");
            cc.file("qemu/a53/init.c");
            cc.file("qemu/a53/handlers.c");
            println!("cargo:rustc-link-arg=-Tqemu/a53/a53.ld");
        }
    } else if arch.name == "riscv" {
        cc.flag("-flto");
        cc.flag("-ffat-lto-objects");
        println!("cargo:rustc-cfg=mpu");
        println!("cargo:rustc-cfg=mpu_riscv");
        if env::var("CARGO_FEATURE_QEMU_RV32").is_ok() {
            println!("cargo:rerun-if-changed=qemu/rv32");
            cc.compiler("riscv64-unknown-elf-gcc");
            cc.archiver("riscv64-unknown-elf-gcc-ar");
            cc.define("MSIP_BASE", Some("0x2000000UL"));
            cc.file("qemu/rv32/start.S");
            cc.file("qemu/rv32/vector.S");
            cc.file("qemu/rv32/init.c");
            println!("cargo:rustc-link-arg=-Tqemu/rv32/rv32.ld");
        } else if env::var("CARGO_FEATURE_RISCV_ESP32").is_ok() {
            cc.compiler("riscv64-unknown-elf-gcc");
            cc.archiver("riscv64-unknown-elf-gcc-ar");
            cc.define("MSIP_BASE", Some("0x20001800UL"));
        }
    }

    println!("cargo:rustc-check-cfg=cfg(mpu)");
    println!("cargo:rustc-check-cfg=cfg(mpu_armv6m)");
    println!("cargo:rustc-check-cfg=cfg(mpu_armv7m)");
    println!("cargo:rustc-check-cfg=cfg(mpu_armv8m)");
    println!("cargo:rustc-check-cfg=cfg(mpu_armv7r)");
    println!("cargo:rustc-check-cfg=cfg(mpu_armv8r)");
    println!("cargo:rustc-check-cfg=cfg(mpu_riscv)");

    cc.compile("rt");

    let wrapper = "rust/wrapper.h";

    println!("cargo:rerun-if-changed={wrapper}");
    for d in [&src_dir, &include_dir, &arch_dir] {
        println!("cargo:rerun-if-changed={}", d.display());
    }

    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

    let arch_def_flags = arch.defs.iter().map(|(m, ov)| match ov {
        Some(v) => format!("-D{m}={v}"),
        None => format!("-D{m}"),
    });

    bindgen::builder()
        .header(wrapper)
        .clang_arg(format!("-I{}", include_dir.display()))
        .clang_arg(format!("-I{}", arch_include_dir.display()))
        .clang_args(arch_def_flags)
        .clang_args(arch.flags)
        .clang_args(rt_cflags)
        .disable_nested_struct_naming()
        .use_core()
        .default_enum_style(bindgen::EnumVariation::NewType {
            is_bitfield: false,
            is_global: false,
        })
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Failed to generate bindings.")
        .write_to_file(out_dir.join("bindings.rs"))
        .expect("Failed to write bindings.");
}