diff --git a/README.md b/README.md index a08de69e977..49a4b3ad116 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,6 @@ UDP, TCP, file connection protocols with a rich set of features. | [`mavlink-core`](https://crates.io/crates/mavlink-core) | Core protocol types, parser/serializer, and connection traits | | [`mavlink-bindgen`](https://crates.io/crates/mavlink-bindgen) | XML-to-Rust code generator used by `mavlink` | -## Build requirements - -Building the `mavlink` crate runs a build script that initializes -the bundled MAVLink definition submodule, so `git` must be available. - ## Quick start Add the crate: diff --git a/mavlink/build/main.rs b/mavlink/build/main.rs index de63fdd8b0a..93bdddbd5ab 100644 --- a/mavlink/build/main.rs +++ b/mavlink/build/main.rs @@ -1,5 +1,6 @@ #![recursion_limit = "256"] +use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::fs::read_dir; use std::path::Path; @@ -9,29 +10,29 @@ use mavlink_bindgen::XmlDefinitions; fn main() -> ExitCode { let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let mavlink_dir = src_dir.join("mavlink"); - // Check if git is installed - if Command::new("git").arg("--version").status().is_err() { - eprintln!("error: Git is not installed or could not be found."); - return ExitCode::FAILURE; - } - - // Update and init submodule - if let Err(error) = Command::new("git") - .arg("submodule") - .arg("update") - .arg("--init") - .current_dir(src_dir) - .status() - { - eprintln!("Failed to update MAVLink definitions submodule: {error}"); - return ExitCode::FAILURE; + // It is a submodule if it contains `.git` or if it's completely empty (uninitialized) + let is_mavlink_empty = read_dir(&mavlink_dir) + .map(|mut d| d.next().is_none()) + .unwrap_or(true); + let is_submodule = mavlink_dir.join(".git").exists() || is_mavlink_empty; + + if is_submodule { + if let Err(error) = Command::new("git") + .arg("submodule") + .arg("update") + .arg("--init") + .current_dir(src_dir) + .status() + { + eprintln!("Failed to update MAVLink definitions submodule: {error}"); + return ExitCode::FAILURE; + } } // find & apply patches to XML definitions to avoid crashes let patch_dir = src_dir.join("build/patches"); - let mavlink_dir = src_dir.join("mavlink"); - if let Ok(dir) = read_dir(patch_dir) { for entry in dir.flatten() { if let Err(error) = Command::new("git") @@ -46,40 +47,65 @@ fn main() -> ExitCode { } } - let out_dir = env::var("OUT_DIR").unwrap(); + let source_definitions_dir = mavlink_dir.join("message_definitions/v1.0"); - let source_definitions_dir = src_dir.join("mavlink/message_definitions/v1.0"); + // Check if the source definitions directory exists + if !source_definitions_dir.is_dir() { + eprintln!( + "MAVLink message definitions directory not found at: {}\n\ + Ensure submodules are included.", + source_definitions_dir.display(), + ); + return ExitCode::FAILURE; + } - let enabled_dialects: Vec = env::vars() + let enabled_dialects: BTreeSet = env::vars() .filter_map(|(key, _)| { key.strip_prefix("CARGO_FEATURE_DIALECT_") .map(str::to_lowercase) }) .collect(); - let mut definitions_to_bind = vec![]; + let mut definitions_to_bind = BTreeSet::new(); + + if !enabled_dialects.is_empty() { + // Handle case-insensitive Cargo features against case-sensitive files e.g., `csAirLink.xml`. + let mut available_dialects = BTreeMap::new(); + for entry in read_dir(&source_definitions_dir) + .into_iter() + .flatten() + .flatten() + { + let path = entry.path(); + let Some(stem) = path.file_stem() else { + continue; + }; + + available_dialects.insert(stem.to_string_lossy().to_lowercase(), path); + } - if let Ok(dir) = read_dir(&source_definitions_dir) { - for entry in dir.flatten() { - let filename = entry - .path() - .file_stem() - .unwrap() - .to_string_lossy() - .to_lowercase(); - - if enabled_dialects.contains(&filename) { - definitions_to_bind.push(entry.path()); - } + // Check if the expected dialects requested by Cargo features are missing + for dialect in &enabled_dialects { + let Some(actual_path) = available_dialects.get(dialect) else { + eprintln!( + "Dialect definition for '{}' not found in {}", + dialect, + source_definitions_dir.display(), + ); + return ExitCode::FAILURE; + }; + + definitions_to_bind.insert(actual_path.clone()); } } let xml_definitions = if definitions_to_bind.is_empty() { XmlDefinitions::Directory(source_definitions_dir) } else { - XmlDefinitions::Files(definitions_to_bind) + XmlDefinitions::Files(definitions_to_bind.into_iter().collect()) }; + let out_dir = env::var("OUT_DIR").unwrap(); let result = match mavlink_bindgen::generate(xml_definitions, out_dir) { Ok(r) => r, Err(e) => {