trouble-host 0.5.0

An async Rust BLE host
Documentation
use std::collections::HashMap;
use std::fmt::Write;
use std::path::PathBuf;
use std::{env, fs};

static CONFIGS: &[(&str, usize)] = &[
    // BEGIN AUTOGENERATED CONFIG FEATURES
    // Generated by gen_config.py. DO NOT EDIT.
    ("CONNECTION_EVENT_QUEUE_SIZE", 2),
    ("L2CAP_RX_QUEUE_SIZE", 8),
    ("L2CAP_TX_QUEUE_SIZE", 8),
    ("DEFAULT_PACKET_POOL_SIZE", 16),
    ("DEFAULT_PACKET_POOL_MTU", 251),
    ("GATT_CLIENT_NOTIFICATION_MAX_SUBSCRIBERS", 1),
    ("GATT_CLIENT_NOTIFICATION_QUEUE_SIZE", 1),
    // END AUTOGENERATED CONFIG FEATURES
];

struct ConfigState {
    value: usize,
    seen_feature: bool,
    seen_env: bool,
}

fn main() {
    let crate_name = env::var("CARGO_PKG_NAME")
        .unwrap()
        .to_ascii_uppercase()
        .replace('-', "_");

    // We don't use any external files: only run the build script if it has changed.
    // Otherwise, Cargo will re-run it on each build.
    println!("cargo::rerun-if-changed=build.rs");

    println!("cargo::rustc-check-cfg=cfg(test)");

    // Check feature usage.
    //
    // Helps both document inter-feature relations, and stop builds ASAP if invariants are not
    // fulfilled. For the developer, this leads to more straightforward error messages.
    //
    // Reference: Cargo features > https://embassy.dev/trouble/#_cargo_features
    {
        // 'scan' needs 'central'
        #[cfg(all(feature = "scan", not(feature = "central")))]
        compile_error!("Feature 'scan' also needs 'central' to be enabled.");

        // Also in 'fmt.rs'
        #[cfg(all(feature = "defmt", feature = "log"))]
        compile_error!("You may not enable both `defmt` and `log` features.");

        // Also in 'lib.rs'
        #[cfg(not(any(feature = "central", feature = "peripheral")))]
        compile_error!("Must enable at least one of: `central`, `peripheral`");

        //...

        // Only one of (or none) 'default-packet-pool-size-{X}' is allowed
        {
            let n = 0;
            #[cfg(feature = "default-packet-pool-size-1")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-2")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-4")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-8")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-16")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-32")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-64")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-size-128")]
            let n = n + 1;

            assert!(
                n <= 1,
                "📍 More than one 'default-packet-pool-size-X' feature is enabled."
            );

            #[cfg(not(feature = "default-packet-pool"))]
            if n > 0 {
                panic!("📍 'default-packet-pool-size-{{X}}' feature also needs 'default-packet-pool' to be enabled.");
            }
        }

        // Only one of (or none) 'default-packet-pool-mtu-{X}' is allowed
        {
            let n = 0;
            #[cfg(feature = "default-packet-pool-mtu-27")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-48")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-64")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-128")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-251")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-255")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-512")]
            let n = n + 1;
            #[cfg(feature = "default-packet-pool-mtu-1024")]
            let n = n + 1;

            assert!(
                n <= 1,
                "📍 More than one 'default-packet-pool-mtu-X' feature is enabled."
            );

            #[cfg(not(feature = "default-packet-pool"))]
            if n > 0 {
                panic!("📍 'default-packet-pool-mtu-{{X}}' feature also needs 'default-packet-pool' to be enabled.");
            }
        }
    }

    // Rebuild if config envvar changed.
    for (name, _) in CONFIGS {
        println!("cargo::rerun-if-env-changed={crate_name}_{name}");
    }

    let mut configs = HashMap::new();
    for (name, default) in CONFIGS {
        configs.insert(
            *name,
            ConfigState {
                value: *default,
                seen_env: false,
                seen_feature: false,
            },
        );
    }

    let prefix = format!("{crate_name}_");
    for (var, value) in env::vars() {
        if let Some(name) = var.strip_prefix(&prefix) {
            let Some(cfg) = configs.get_mut(name) else {
                panic!("Unknown env var {name}")
            };

            let Ok(value) = value.parse::<usize>() else {
                panic!("Invalid value for env var {name}: {value}")
            };

            cfg.value = value;
            cfg.seen_env = true;
        }

        if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
            if let Some(i) = feature.rfind('_') {
                let name = &feature[..i];
                let value = &feature[i + 1..];
                if let Some(cfg) = configs.get_mut(name) {
                    let Ok(value) = value.parse::<usize>() else {
                        panic!("Invalid value for feature {name}: {value}")
                    };

                    // envvars take priority.
                    if !cfg.seen_env {
                        if cfg.seen_feature {
                            panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
                        }

                        cfg.value = value;
                        cfg.seen_feature = true;
                    }
                }
            }
        }
    }

    let mut data = String::new();

    for (name, cfg) in &configs {
        writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
    }

    let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
    fs::write(out_file, data).unwrap();
}