1mod msg_gen;
2
3use std::{env, fs};
4use std::path::{Path, PathBuf};
5use std::process::Command;
6
7pub use crate::msg_gen::generate_rust_wrappers;
8
9fn collect_c_files(dir: &Path) -> Vec<PathBuf> {
11 let mut result = Vec::new();
12 if let Ok(entries) = fs::read_dir(dir) {
13 for entry in entries.flatten() {
14 let path = entry.path();
15 if path.is_dir() {
16 result.extend(collect_c_files(&path));
17 } else if path.extension().map_or(false, |e| e == "c") {
18 result.push(path);
19 }
20 }
21 }
22 result
23}
24
25pub fn compile_idl_libs(lib_name :&str, dds_include_paths: &Vec<PathBuf>, idl_out_dir: &Path) {
27 compile_idl_files(&dds_include_paths, idl_out_dir);
28 let c_files = collect_c_files(idl_out_dir);
29 if c_files.is_empty() {
30 return;
31 }
32
33 let mut build = cc::Build::new();
34 for inc in dds_include_paths {
35 build.include(inc);
36 }
37 build.include(idl_out_dir);
38 for f in &c_files {
39 build.file(f);
40 }
41 build.out_dir(idl_out_dir);
42 build.compile(lib_name);
43}
44
45fn collect_h_files(dir: &Path) -> Vec<PathBuf> {
47 let mut result = Vec::new();
48 if let Ok(entries) = fs::read_dir(dir) {
49 for entry in entries.flatten() {
50 let path = entry.path();
51 if path.is_dir() {
52 result.extend(collect_h_files(&path));
53 } else if path.extension().map_or(false, |e| e == "h") {
54 result.push(path);
55 }
56 }
57 }
58 result
59}
60
61pub fn generate_msg_bindings(idl_out_dir: &Path, out_dir: &Path) {
63 let h_files = collect_h_files(idl_out_dir);
64 if h_files.is_empty() {
65 let out_path = out_dir.join("msg_bindings.rs");
67 if let Err(e) = fs::write(&out_path, "") {
68 println!("cargo:warning=Failed to write empty msg_bindings.rs: {e}");
69 }
70 return;
71 }
72
73 let wrapper_path = out_dir.join("msg_bindings_wrapper.h");
74 let includes: String = h_files
75 .iter()
76 .filter_map(|p| p.to_str())
77 .map(|s| format!("#include \"{s}\"\n"))
78 .collect();
79 if let Err(e) = fs::write(&wrapper_path, &includes) {
80 println!("cargo:warning=Failed to write wrapper header: {e}");
81 return;
82 }
83
84 let idl_dir_pattern: String = idl_out_dir
85 .to_str()
86 .unwrap_or("")
87 .chars()
88 .flat_map(|c| {
89 if r"\^$.|?*+()[]{}".contains(c) {
90 vec!['\\', c]
91 } else {
92 vec![c]
93 }
94 })
95 .collect();
96
97 let builder = bindgen::Builder::default()
98 .header(wrapper_path.to_str().unwrap())
99 .clang_arg(format!("-I{}", idl_out_dir.display()))
100 .allowlist_file(format!("{idl_dir_pattern}/.*"))
101 .blocklist_type("dds_key_.*")
102 .blocklist_type("dds_topic_.*")
103 .blocklist_type("dds_type_.*")
104 .size_t_is_usize(true)
105 .merge_extern_blocks(true)
106 .derive_partialeq(true)
107 .generate_comments(true);
108
109 match builder.generate() {
110 Ok(b) => {
111 let out_path = out_dir.join("msg_bindings.rs");
112 if let Err(e) = b.write_to_file(&out_path) {
113 println!("cargo:warning=Failed to write {}: {e}", out_path.display());
114 }
115 }
116 Err(e) => println!("cargo:warning=bindgen failed for msg_bindings: {e}"),
117 }
118}
119
120fn find_idlc() -> Option<PathBuf> {
122 let which_cmd = if cfg!(target_os = "windows") {
123 "where"
124 } else {
125 "which"
126 };
127 if let Ok(output) = Command::new(which_cmd).arg("idlc").output() {
128 if output.status.success() {
129 if let Ok(s) = std::str::from_utf8(&output.stdout) {
130 let path = PathBuf::from(s.lines().next().unwrap_or("").trim());
131 if path.exists() {
132 return Some(path);
133 }
134 }
135 }
136 }
137
138 None
139}
140
141struct IdlEntry {
143 path: PathBuf,
144 base: PathBuf,
145}
146
147fn collect_idl_files() -> Vec<IdlEntry> {
149 let split_char = if cfg!(target_os = "windows") {
150 ';'
151 } else {
152 ':'
153 };
154 let mut result = Vec::new();
155
156 if let Ok(val) = env::var("DDS_IDL_PATH") {
157 for entry in val.split(split_char) {
158 let entry = entry.trim();
159 if entry.is_empty() {
160 continue;
161 }
162 let path = PathBuf::from(entry);
163 if path.is_file() && path.extension().map_or(false, |e| e == "idl") {
164 let base = path.parent().unwrap_or(Path::new(".")).to_path_buf();
165 result.push(IdlEntry {
166 path,
167 base,
168 });
169 } else if path.is_dir() {
170 let mut stack = vec![path.clone()];
171 while let Some(dir) = stack.pop() {
172 let Ok(rd) = fs::read_dir(&dir) else { continue };
173 let mut entries: Vec<_> = rd.flatten().collect();
174 entries.sort_by_key(|e| e.file_name());
175 for e in entries {
176 let p = e.path();
177 if p.is_dir() {
178 stack.push(p);
179 } else if p.extension().map_or(false, |ext| ext == "idl") {
180 result.push(IdlEntry {
181 path: p,
182 base: path.clone(),
183 });
184 }
185 }
186 }
187 }
188 }
189 }
190
191 fs::write(
192 PathBuf::from(env::var("OUT_DIR").unwrap()).join("msg_list.txt"),
193 result
194 .iter()
195 .map(|e| e.path.display().to_string())
196 .collect::<Vec<_>>()
197 .join("\n"),
198 )
199 .expect("Failed to write msg_list.txt");
200 result
201}
202
203fn compile_idl_files(dds_include_paths: &Vec<PathBuf>, out_dir: &Path) {
205 let idl_files = collect_idl_files();
206 if idl_files.is_empty() {
207 return;
208 }
209 let Some(idlc) = find_idlc() else {
210 println!("cargo:warning=idlc not found, skipping IDL compilation");
211 return;
212 };
213
214 let mut include_dirs: Vec<PathBuf> = dds_include_paths.clone();
215 for base in idl_files.iter().map(|e| &e.base) {
216 if !include_dirs.contains(base) {
217 include_dirs.push(base.clone());
218 }
219 }
220
221 for entry in idl_files {
222 let mut cmd = Command::new(&idlc);
223 cmd.arg("-f").arg("case-sensitive");
224 cmd.arg("-b").arg(&entry.base);
225 cmd.arg("-o").arg(out_dir);
226 for inc in &include_dirs {
227 cmd.arg("-I").arg(inc);
228 }
229 cmd.arg(&entry.path);
230
231 match cmd.status() {
232 Ok(_) => {}
233 Err(e) => println!(
234 "cargo:warning=Failed to run idlc for {}: {e}",
235 entry.path.display()
236 )
237 }
238 }
239}