fn main() {
println!("cargo::rustc-check-cfg=cfg(cbindgen)");
println!("cargo::rustc-check-cfg=cfg(has_unwind)");
if std::env::var("CARGO_FEATURE_NODEJS").is_ok() {
napi_build::setup();
}
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if target_arch != "wasm32" && target_env != "msvc" {
println!("cargo::rustc-link-lib=c");
}
#[cfg(target_os = "macos")]
if target_arch != "wasm32" {
println!("cargo::rustc-link-arg=-undefined");
println!("cargo::rustc-link-arg=dynamic_lookup");
}
if let Ok(panic_strat) = std::env::var("CARGO_CFG_PANIC")
&& panic_strat == "unwind"
{
println!("cargo::rustc-cfg=has_unwind");
}
println!("cargo::rerun-if-env-changed=MBUS_MAX_TCP_CLIENTS");
let max_tcp: usize = match std::env::var("MBUS_MAX_TCP_CLIENTS") {
Ok(val) => {
let n: usize = val.parse().unwrap_or_else(|_| {
panic!("MBUS_MAX_TCP_CLIENTS must be a valid integer, got: \"{val}\"")
});
if n == 0 {
panic!("MBUS_MAX_TCP_CLIENTS must be >= 1, got: 0");
}
if n > 255 {
panic!("MBUS_MAX_TCP_CLIENTS must be <= 255, got: {n}");
}
n
}
Err(_) => 1, };
println!("cargo::rerun-if-env-changed=MBUS_MAX_SERIAL_CLIENTS");
let max_serial: usize = match std::env::var("MBUS_MAX_SERIAL_CLIENTS") {
Ok(val) => {
let n: usize = val.parse().unwrap_or_else(|_| {
panic!("MBUS_MAX_SERIAL_CLIENTS must be a valid integer, got: \"{val}\"")
});
if n == 0 {
panic!("MBUS_MAX_SERIAL_CLIENTS must be >= 1, got: 0");
}
if n > 255 {
panic!("MBUS_MAX_SERIAL_CLIENTS must be <= 255, got: {n}");
}
n
}
Err(_) => 1, };
println!("cargo::rerun-if-env-changed=MBUS_MAX_TCP_SERVERS");
let max_tcp_servers: usize = match std::env::var("MBUS_MAX_TCP_SERVERS") {
Ok(val) => {
let n: usize = val.parse().unwrap_or_else(|_| {
panic!("MBUS_MAX_TCP_SERVERS must be a valid integer, got: \"{val}\"")
});
if n == 0 {
panic!("MBUS_MAX_TCP_SERVERS must be >= 1, got: 0");
}
if n > 255 {
panic!("MBUS_MAX_TCP_SERVERS must be <= 255, got: {n}");
}
n
}
Err(_) => 1, };
println!("cargo::rerun-if-env-changed=MBUS_MAX_SERIAL_SERVERS");
let max_serial_servers: usize = match std::env::var("MBUS_MAX_SERIAL_SERVERS") {
Ok(val) => {
let n: usize = val.parse().unwrap_or_else(|_| {
panic!("MBUS_MAX_SERIAL_SERVERS must be a valid integer, got: \"{val}\"")
});
if n == 0 {
panic!("MBUS_MAX_SERIAL_SERVERS must be >= 1, got: 0");
}
if n > 255 {
panic!("MBUS_MAX_SERIAL_SERVERS must be <= 255, got: {n}");
}
n
}
Err(_) => 1, };
println!("cargo::rerun-if-env-changed=MBUS_MAX_GATEWAYS");
let max_gateways: usize = match std::env::var("MBUS_MAX_GATEWAYS") {
Ok(val) => {
let n: usize = val.parse().unwrap_or_else(|_| {
panic!("MBUS_MAX_GATEWAYS must be a valid integer, got: \"{val}\"")
});
if n == 0 {
panic!("MBUS_MAX_GATEWAYS must be >= 1, got: 0");
}
if n > 255 {
panic!("MBUS_MAX_GATEWAYS must be <= 255, got: {n}");
}
n
}
Err(_) => 1, };
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
let config_path = format!("{out_dir}/pool_config.rs");
std::fs::write(
&config_path,
format!(
"/// Maximum number of TCP client slots (set via `MBUS_MAX_TCP_CLIENTS` env var, default 1).\n\
#[allow(dead_code)]\n\
pub(crate) const MAX_TCP_CLIENTS: usize = {max_tcp};\n\
/// Maximum number of Serial client slots (set via `MBUS_MAX_SERIAL_CLIENTS` env var, default 1).\n\
#[allow(dead_code)]\n\
pub(crate) const MAX_SERIAL_CLIENTS: usize = {max_serial};\n\
/// Maximum number of TCP server slots (set via `MBUS_MAX_TCP_SERVERS` env var, default 1).\n\
#[allow(dead_code)]\n\
pub(crate) const MAX_TCP_SERVERS: usize = {max_tcp_servers};\n\
/// Maximum number of Serial server slots (set via `MBUS_MAX_SERIAL_SERVERS` env var, default 1).\n\
#[allow(dead_code)]\n\
pub(crate) const MAX_SERIAL_SERVERS: usize = {max_serial_servers};\n\
/// Maximum number of gateway slots (set via `MBUS_MAX_GATEWAYS` env var, default 1).\n\
#[allow(dead_code)]\n\
pub(crate) const MAX_GATEWAYS: usize = {max_gateways};\n"
),
)
.expect("failed to write pool_config.rs");
if std::env::var("CARGO_FEATURE_C_CLIENT").is_err()
&& std::env::var("CARGO_FEATURE_C_SERVER").is_err()
&& std::env::var("CARGO_FEATURE_C_GATEWAY").is_err()
&& std::env::var("CARGO_FEATURE_DOTNET").is_err()
&& std::env::var("CARGO_FEATURE_GO").is_err()
{
return;
}
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let workspace_root = std::path::Path::new(&crate_dir)
.parent()
.expect("CARGO_MANIFEST_DIR has no parent")
.to_path_buf();
let target_dir = std::env::var("CARGO_TARGET_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("target"));
let include_dir = target_dir.join("mbus-ffi/include");
println!("cargo::rerun-if-changed=cbindgen.toml");
println!("cargo::rerun-if-changed=src");
std::fs::create_dir_all(&include_dir).expect("failed to create target include directory");
let output_file = include_dir.join("modbus_rs.h");
let config_path = format!("{crate_dir}/cbindgen.toml");
let mut config = cbindgen::Config::from_file(&config_path)
.unwrap_or_else(|err| panic!("failed to parse {config_path}: {err}"));
let active_defines = get_active_defines();
config.autogen_warning = Some(format!(
"/* Auto-generated by cbindgen - DO NOT EDIT */\n{}",
active_defines
));
cbindgen::Builder::new()
.with_crate(&crate_dir)
.with_language(cbindgen::Language::C)
.with_define("feature", "c-client", "MBUS_FEATURE_C_CLIENT")
.with_define("feature", "c-server", "MBUS_FEATURE_C_SERVER")
.with_define("feature", "c-gateway", "MBUS_FEATURE_C_GATEWAY")
.with_define("feature", "coils", "MBUS_FEATURE_COILS")
.with_define(
"feature",
"holding-registers",
"MBUS_FEATURE_HOLDING_REGISTERS",
)
.with_define("feature", "input-registers", "MBUS_FEATURE_INPUT_REGISTERS")
.with_define("feature", "discrete-inputs", "MBUS_FEATURE_DISCRETE_INPUTS")
.with_define("feature", "fifo", "MBUS_FEATURE_FIFO")
.with_define("feature", "file-record", "MBUS_FEATURE_FILE_RECORD")
.with_define("feature", "diagnostics", "MBUS_FEATURE_DIAGNOSTICS")
.with_define("feature", "traffic", "MBUS_FEATURE_TRAFFIC")
.with_define("feature", "server-traffic", "MBUS_FEATURE_SERVER_TRAFFIC")
.with_define("feature", "network-tcp", "MBUS_FEATURE_NETWORK_TCP")
.with_define("feature", "serial-rtu", "MBUS_FEATURE_SERIAL_RTU")
.with_define("feature", "serial-ascii", "MBUS_FEATURE_SERIAL_ASCII")
.with_define("feature", "dotnet", "MBUS_FEATURE_DOTNET")
.with_define("feature", "go", "MBUS_FEATURE_GO")
.with_config(config)
.generate()
.expect("cbindgen failed to generate unified C header")
.write_to_file(&output_file);
let client_h = include_dir.join("modbus_rs_client.h");
let server_h = include_dir.join("modbus_rs_server.h");
let gateway_h = include_dir.join("modbus_rs_gateway.h");
let dotnet_h = include_dir.join("modbus_rs_dotnet.h");
let go_h = include_dir.join("modbus_rs_go.h");
let _ = std::fs::copy(&output_file, &client_h);
let _ = std::fs::copy(&output_file, &server_h);
let _ = std::fs::copy(&output_file, &gateway_h);
let _ = std::fs::copy(&output_file, &dotnet_h);
let _ = std::fs::copy(&output_file, &go_h);
if std::env::var("CARGO_FEATURE_GO").is_ok() {
let go_include_dir = std::path::Path::new(&crate_dir)
.join("go")
.join("internal")
.join("cgo")
.join("include");
if go_include_dir.exists() {
let dst = go_include_dir.join("modbus_rs_go.h");
let _ = std::fs::copy(&go_h, &dst);
}
}
if std::env::var("CARGO_FEATURE_C_SERVER").is_ok() {
println!("cargo::rerun-if-env-changed=MBUS_SERVER_APP_CONFIG");
let example_yaml = std::path::Path::new(&crate_dir)
.join("examples/c_server_demo_yaml/mbus_server_app.example.yaml");
let app_config_raw = std::env::var("MBUS_SERVER_APP_CONFIG").unwrap_or_else(|_| {
if example_yaml.exists() {
example_yaml.to_string_lossy().into_owned()
} else {
panic!(
"\n\nError: MBUS_SERVER_APP_CONFIG is not set.\n\
The `c-server` feature requires a YAML device config to generate the server app layer.\n\
Set the environment variable to the path of your server_app.yaml:\n\
\n\
MBUS_SERVER_APP_CONFIG=/path/to/server_app.yaml cargo build -p mbus-ffi --features c-server\n\
\n\
See mbus-ffi/examples/c_server_demo_yaml/mbus_server_app.example.yaml for the format.\n"
)
}
});
let app_config_path = {
let p = std::path::Path::new(&app_config_raw);
if p.is_absolute() {
p.to_path_buf()
} else {
std::path::Path::new(&crate_dir)
.parent()
.expect("CARGO_MANIFEST_DIR has no parent")
.join(p)
}
};
println!("cargo::rerun-if-changed={}", app_config_path.display());
let app_config_text = std::fs::read_to_string(&app_config_path).unwrap_or_else(|e| {
panic!(
"failed to read MBUS_SERVER_APP_CONFIG={}: {e}",
app_config_path.display()
)
});
let mut app_config = mbus_codegen::parse_yaml(&app_config_text)
.unwrap_or_else(|e| panic!("invalid YAML in {}: {e}", app_config_path.display()));
mbus_codegen::validate_config(&app_config)
.unwrap_or_else(|e| panic!("config error in {}: {e}", app_config_path.display()));
if std::env::var("CARGO_FEATURE_COILS").is_err() && !app_config.memory_map.coils.is_empty()
{
println!(
"cargo::warning=YAML defines {} coil(s) but the `coils` feature is not enabled; coil handlers will NOT be compiled.",
app_config.memory_map.coils.len()
);
app_config.memory_map.coils.clear();
}
if std::env::var("CARGO_FEATURE_DISCRETE_INPUTS").is_err()
&& !app_config.memory_map.discrete_inputs.is_empty()
{
println!(
"cargo::warning=YAML defines {} discrete input(s) but the `discrete-inputs` feature is not enabled; discrete-input handlers will NOT be compiled.",
app_config.memory_map.discrete_inputs.len()
);
app_config.memory_map.discrete_inputs.clear();
}
if std::env::var("CARGO_FEATURE_HOLDING_REGISTERS").is_err()
&& !app_config.memory_map.holding_registers.is_empty()
{
println!(
"cargo::warning=YAML defines {} holding register(s) but the `holding-registers` feature is not enabled; holding-register handlers will NOT be compiled.",
app_config.memory_map.holding_registers.len()
);
app_config.memory_map.holding_registers.clear();
}
if std::env::var("CARGO_FEATURE_INPUT_REGISTERS").is_err()
&& !app_config.memory_map.input_registers.is_empty()
{
println!(
"cargo::warning=YAML defines {} input register(s) but the `input-registers` feature is not enabled; input-register handlers will NOT be compiled.",
app_config.memory_map.input_registers.len()
);
app_config.memory_map.input_registers.clear();
}
let rust_src = mbus_codegen::render_rust_dispatcher(&app_config);
let gen_path = format!("{out_dir}/generated_server.rs");
std::fs::write(&gen_path, rust_src)
.unwrap_or_else(|e| panic!("failed to write {gen_path}: {e}"));
}
}
fn get_active_defines() -> String {
let mut s = String::new();
s.push_str("\n/* Active feature configuration defines built into the library */\n");
let features = [
("CARGO_FEATURE_C_CLIENT", "MBUS_FEATURE_C_CLIENT"),
("CARGO_FEATURE_C_SERVER", "MBUS_FEATURE_C_SERVER"),
("CARGO_FEATURE_C_GATEWAY", "MBUS_FEATURE_C_GATEWAY"),
("CARGO_FEATURE_COILS", "MBUS_FEATURE_COILS"),
(
"CARGO_FEATURE_HOLDING_REGISTERS",
"MBUS_FEATURE_HOLDING_REGISTERS",
),
(
"CARGO_FEATURE_INPUT_REGISTERS",
"MBUS_FEATURE_INPUT_REGISTERS",
),
(
"CARGO_FEATURE_DISCRETE_INPUTS",
"MBUS_FEATURE_DISCRETE_INPUTS",
),
("CARGO_FEATURE_FIFO", "MBUS_FEATURE_FIFO"),
("CARGO_FEATURE_FILE_RECORD", "MBUS_FEATURE_FILE_RECORD"),
("CARGO_FEATURE_DIAGNOSTICS", "MBUS_FEATURE_DIAGNOSTICS"),
("CARGO_FEATURE_TRAFFIC", "MBUS_FEATURE_TRAFFIC"),
(
"CARGO_FEATURE_SERVER_TRAFFIC",
"MBUS_FEATURE_SERVER_TRAFFIC",
),
("CARGO_FEATURE_NETWORK_TCP", "MBUS_FEATURE_NETWORK_TCP"),
("CARGO_FEATURE_SERIAL_RTU", "MBUS_FEATURE_SERIAL_RTU"),
("CARGO_FEATURE_SERIAL_ASCII", "MBUS_FEATURE_SERIAL_ASCII"),
("CARGO_FEATURE_DOTNET", "MBUS_FEATURE_DOTNET"),
("CARGO_FEATURE_GO", "MBUS_FEATURE_GO"),
];
for &(cargo_feat, c_macro) in &features {
if std::env::var(cargo_feat).is_ok() {
s.push_str(&format!("#define {c_macro} 1\n"));
} else {
s.push_str(&format!("/* #undef {c_macro} */\n"));
}
}
s
}