1pub use crate::error::BindGenError;
2use std::fs::{read_dir, File};
3use std::io::{self, BufWriter};
4use std::ops::Deref;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8pub mod binder;
9pub mod error;
10pub mod parser;
11mod util;
12
13#[derive(Debug)]
14pub struct GeneratedBinding {
15 pub module_name: String,
16 pub mavlink_xml: PathBuf,
17 pub rust_module: PathBuf,
18}
19
20#[derive(Debug)]
21pub struct GeneratedBindings {
22 pub bindings: Vec<GeneratedBinding>,
23 pub mod_rs: PathBuf,
24}
25
26pub enum XmlDefinitions<T: AsRef<Path>> {
29 Files(Vec<T>),
31 Directory(T),
33}
34
35pub fn generate<P1: AsRef<Path>, P2: AsRef<Path>>(
40 xml_definitions: XmlDefinitions<P1>,
41 destination_dir: P2,
42) -> Result<GeneratedBindings, BindGenError> {
43 let destination_dir = destination_dir.as_ref();
44
45 let mut bindings = vec![];
46
47 match xml_definitions {
48 XmlDefinitions::Files(files) => {
49 if files.is_empty() {
50 return Err(
51 BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
52 source: io::Error::new(
53 io::ErrorKind::InvalidInput,
54 "At least one file must be given.",
55 ),
56 path: PathBuf::default(),
57 },
58 );
59 }
60
61 for file in files {
62 let file = file.as_ref();
63
64 bindings.push(generate_single_file(file, destination_dir)?);
65 }
66 }
67 XmlDefinitions::Directory(definitions_dir) => {
68 let definitions_dir = definitions_dir.as_ref();
69
70 if !definitions_dir.is_dir() {
71 return Err(
72 BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
73 source: io::Error::new(
74 io::ErrorKind::InvalidInput,
75 format!("{} is not a directory.", definitions_dir.display()),
76 ),
77 path: definitions_dir.to_owned(),
78 },
79 );
80 }
81
82 for entry_maybe in read_dir(definitions_dir).map_err(|source| {
83 BindGenError::CouldNotReadDefinitionsDirectory {
84 source,
85 path: definitions_dir.to_path_buf(),
86 }
87 })? {
88 let entry = entry_maybe.map_err(|source| {
89 BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
90 source,
91 path: definitions_dir.to_path_buf(),
92 }
93 })?;
94
95 let definition_filename = PathBuf::from(entry.file_name());
96 if !definition_filename.extension().is_some_and(|e| e == "xml") {
98 continue;
99 }
100
101 bindings.push(generate_single_file(entry.path(), destination_dir)?);
102 }
103 }
104 }
105
106 let dest_path = destination_dir.join("mod.rs");
108 let mut outf = File::create(&dest_path).map_err(|source| {
109 BindGenError::CouldNotCreateRustBindingsFile {
110 source,
111 dest_path: dest_path.clone(),
112 }
113 })?;
114
115 binder::generate(
117 bindings
118 .iter()
119 .map(|binding| binding.module_name.deref())
120 .collect(),
121 &mut outf,
122 );
123
124 Ok(GeneratedBindings {
125 bindings,
126 mod_rs: dest_path,
127 })
128}
129
130fn generate_single_file<P1: AsRef<Path>, P2: AsRef<Path>>(
134 source_file: P1,
135 destination_dir: P2,
136) -> Result<GeneratedBinding, BindGenError> {
137 let source_file = source_file.as_ref();
138 let destination_dir = destination_dir.as_ref();
139
140 let definitions_dir = source_file.parent().unwrap_or(Path::new(""));
141
142 if !source_file.exists() {
143 return Err(
144 BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
145 source: io::Error::new(io::ErrorKind::NotFound, "File not found."),
146 path: definitions_dir.to_owned(),
147 },
148 );
149 }
150
151 if !source_file.extension().is_some_and(|e| e == "xml") {
152 return Err(
153 BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
154 source: io::Error::new(
155 io::ErrorKind::InvalidInput,
156 "Non-XML files are not supported.",
157 ),
158 path: definitions_dir.to_owned(),
159 },
160 );
161 }
162
163 let definition_filename = PathBuf::from(source_file.file_name().unwrap());
164 let module_name = util::to_module_name(&definition_filename);
165 let definition_rs = PathBuf::from(&module_name).with_extension("rs");
166
167 let dest_path = destination_dir.join(definition_rs);
168 let mut outf = BufWriter::new(File::create(&dest_path).map_err(|source| {
169 BindGenError::CouldNotCreateRustBindingsFile {
170 source,
171 dest_path: dest_path.clone(),
172 }
173 })?);
174
175 parser::generate(definitions_dir, &definition_filename, &mut outf)?;
177
178 Ok(GeneratedBinding {
179 module_name,
180 mavlink_xml: source_file.to_owned(),
181 rust_module: dest_path,
182 })
183}
184
185pub fn format_generated_code(result: &GeneratedBindings) {
187 if let Err(error) = Command::new("rustfmt")
188 .args(
189 result
190 .bindings
191 .iter()
192 .map(|binding| binding.rust_module.clone()),
193 )
194 .arg(result.mod_rs.clone())
195 .status()
196 {
197 if std::env::args()
198 .next()
199 .unwrap_or_default()
200 .contains("build-script-")
201 {
202 println!("cargo:warning=Failed to run rustfmt: {error}");
203 } else {
204 eprintln!("Failed to run rustfmt: {error}");
205 }
206 }
207}
208
209pub fn emit_cargo_build_messages(result: &GeneratedBindings) {
211 for binding in &result.bindings {
212 println!(
214 "cargo:rerun-if-changed={}",
215 binding.mavlink_xml.to_string_lossy()
216 );
217 }
218}