1use std::{
2 io::{self, prelude::*},
3 path::Path,
4};
5
6use log::info;
7
8use crate::{
9 analysis::types::IsIncomplete,
10 codegen::general,
11 config::gobjects::GStatus,
12 env::Env,
13 file_saver::save_to_file,
14 library::{self, Bitfield, Enumeration, Namespace, Type, MAIN_NAMESPACE},
15};
16
17#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
18struct CType {
19 name: String,
21 cfg_condition: Option<String>,
24}
25
26#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
27struct CConstant {
28 name: String,
30 value: String,
32 status: GStatus,
33}
34
35pub fn generate(env: &Env, crate_name: &str) {
36 let ctypes = prepare_ctypes(env);
37 let cconsts = prepare_cconsts(env);
38
39 if ctypes.is_empty() && cconsts.is_empty() {
40 return;
41 }
42
43 let tests = env.config.target_path.join("tests");
44
45 let manual_h = tests.join("manual.h");
46 if !manual_h.exists() {
47 save_to_file(&manual_h, env.config.make_backup, |w| {
48 generate_manual_h(env, &manual_h, w)
49 });
50 }
51
52 let layout_c = tests.join("layout.c");
53 save_to_file(&layout_c, env.config.make_backup, |w| {
54 generate_layout_c(env, &layout_c, w, &ctypes)
55 });
56
57 let constant_c = tests.join("constant.c");
58 save_to_file(&constant_c, env.config.make_backup, |w| {
59 generate_constant_c(env, &constant_c, w, &cconsts)
60 });
61
62 let abi_rs = tests.join("abi.rs");
63 save_to_file(&abi_rs, env.config.make_backup, |w| {
64 generate_abi_rs(env, &abi_rs, w, crate_name, &ctypes, &cconsts)
65 });
66}
67
68fn prepare_ctypes(env: &Env) -> Vec<CType> {
69 let ns = env.library.namespace(MAIN_NAMESPACE);
70 let mut types: Vec<CType> = ns
71 .types
72 .iter()
73 .filter_map(Option::as_ref)
74 .filter(|t| !t.is_incomplete(&env.library))
75 .filter_map(|t| match t {
76 Type::Record(library::Record {
77 disguised: false, ..
78 }) => prepare_ctype(env, ns, t),
79 Type::Alias(_)
80 | Type::Class(_)
81 | Type::Union(_)
82 | Type::Enumeration(_)
83 | Type::Bitfield(_)
84 | Type::Interface(_) => prepare_ctype(env, ns, t),
85 _ => None,
86 })
87 .collect();
88
89 types.sort();
90 types
91}
92
93fn prepare_ctype(env: &Env, ns: &Namespace, t: &Type) -> Option<CType> {
94 let full_name = format!("{}.{}", ns.name, t.get_name());
95 if env.type_status_sys(&full_name).ignored() {
96 return None;
97 }
98 let name = t.get_glib_name()?;
99
100 if is_name_made_up(name) {
101 return None;
102 }
103 let object = env.config.objects.get(&full_name);
104 Some(CType {
105 name: name.to_owned(),
106 cfg_condition: object.and_then(|obj| obj.cfg_condition.clone()),
107 })
108}
109
110fn prepare_cconsts(env: &Env) -> Vec<CConstant> {
111 let ns = env.library.namespace(MAIN_NAMESPACE);
112 let mut constants: Vec<CConstant> = ns
113 .constants
114 .iter()
115 .filter_map(|constant| {
116 let full_name = format!("{}.{}", &ns.name, constant.name);
117 if env.type_status_sys(&full_name).ignored() {
118 return None;
119 }
120 let value = match constant {
121 c if c.c_type == "gboolean" && c.value == "true" => "1",
122 c if c.c_type == "gboolean" && c.value == "false" => "0",
123 c => &c.value,
124 };
125 Some(CConstant {
126 name: constant.c_identifier.clone(),
127 value: value.to_owned(),
128 status: GStatus::Generate, })
130 })
131 .collect();
132
133 for typ in &ns.types {
134 let typ = if let Some(typ) = typ {
135 typ
136 } else {
137 continue;
138 };
139 let full_name = format!("{}.{}", &ns.name, typ.get_name());
140 if env.type_status_sys(&full_name).ignored() {
141 continue;
142 }
143 match typ {
144 Type::Bitfield(Bitfield { members, .. }) => {
145 for member in members {
146 constants.push(CConstant {
149 name: format!("(guint) {}", member.c_identifier),
150 value: member
151 .value
152 .parse::<i32>()
153 .map(|i| (i as u32).to_string())
154 .unwrap_or_else(|_| member.value.clone()),
155 status: member.status,
156 });
157 }
158 }
159 Type::Enumeration(Enumeration { members, .. }) => {
160 for member in members {
161 constants.push(CConstant {
164 name: format!("(gint) {}", member.c_identifier),
165 value: member.value.clone(),
166 status: member.status,
167 });
168 }
169 }
170 _ => {}
171 }
172 }
173
174 constants.sort_by(|a, b| {
175 fn strip_cast(x: &CConstant) -> &str {
176 if x.name.starts_with("(gint) ") {
177 &x.name[7..]
178 } else if x.name.starts_with("(guint) ") {
179 &x.name[8..]
180 } else {
181 x.name.as_str()
182 }
183 }
184
185 strip_cast(a).cmp(strip_cast(b))
186 });
187 constants
188}
189
190fn is_name_made_up(name: &str) -> bool {
192 name.contains('_') && !name.ends_with("_t")
195}
196
197fn generate_manual_h(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
198 info!("Generating file {:?}", path);
199 writeln!(
200 w,
201 "// Feel free to edit this file, it won't be regenerated by gir generator unless removed."
202 )?;
203 writeln!(w)?;
204
205 let ns = env.library.namespace(MAIN_NAMESPACE);
206 for include in &ns.c_includes {
207 writeln!(w, "#include <{include}>")?;
208 }
209
210 Ok(())
211}
212
213#[allow(clippy::write_literal)]
214fn generate_layout_c(
215 env: &Env,
216 path: &Path,
217 w: &mut dyn Write,
218 ctypes: &[CType],
219) -> io::Result<()> {
220 info!("Generating file {:?}", path);
221 general::start_comments(w, &env.config)?;
222 writeln!(w)?;
223 writeln!(w, "#include \"manual.h\"")?;
224 writeln!(w, "#include <stdalign.h>")?;
225 writeln!(w, "#include <stdio.h>")?;
226 writeln!(w)?;
227 writeln!(w, "{}", r"int main() {")?;
228
229 for ctype in ctypes {
230 writeln!(
231 w,
232 " printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));",
233 ctype = ctype.name
234 )?;
235 }
236
237 writeln!(w, " return 0;")?;
238 writeln!(w, "{}", r"}")
239}
240
241#[allow(clippy::write_literal)]
242fn generate_constant_c(
243 env: &Env,
244 path: &Path,
245 w: &mut dyn Write,
246 cconsts: &[CConstant],
247) -> io::Result<()> {
248 info!("Generating file {:?}", path);
249 general::start_comments(w, &env.config)?;
250 writeln!(w)?;
251 writeln!(w, "#include \"manual.h\"")?;
252 writeln!(w, "#include <stdio.h>")?;
253 writeln!(
254 w,
255 "{}",
256 r#"
257#define PRINT_CONSTANT(CONSTANT_NAME) \
258 printf("%s;", #CONSTANT_NAME); \
259 printf(_Generic((CONSTANT_NAME), \
260 char *: "%s", \
261 const char *: "%s", \
262 char: "%c", \
263 signed char: "%hhd", \
264 unsigned char: "%hhu", \
265 short int: "%hd", \
266 unsigned short int: "%hu", \
267 int: "%d", \
268 unsigned int: "%u", \
269 long: "%ld", \
270 unsigned long: "%lu", \
271 long long: "%lld", \
272 unsigned long long: "%llu", \
273 float: "%f", \
274 double: "%f", \
275 long double: "%ld"), \
276 CONSTANT_NAME); \
277 printf("\n");
278"#
279 )?;
280
281 writeln!(w, "{}", r"int main() {")?;
282
283 for cconst in cconsts {
284 if cconst.status.ignored() {
285 continue;
286 }
287 writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?;
288 }
289
290 writeln!(w, " return 0;")?;
291 writeln!(w, "{}", r"}")
292}
293
294#[allow(clippy::write_literal)]
295fn generate_abi_rs(
296 env: &Env,
297 path: &Path,
298 w: &mut dyn Write,
299 crate_name: &str,
300 ctypes: &[CType],
301 cconsts: &[CConstant],
302) -> io::Result<()> {
303 let ns = env.library.namespace(MAIN_NAMESPACE);
304 let mut package_names = ns.package_names.join("\", \"");
305 if !package_names.is_empty() {
306 package_names = format!("\"{package_names}\"");
307 }
308
309 info!("Generating file {:?}", path);
310 general::start_comments(w, &env.config)?;
311 writeln!(w)?;
312 writeln!(w, "#![cfg(unix)]")?;
313 writeln!(w)?;
314
315 if !ctypes.is_empty() {
316 writeln!(w, "use {crate_name}::*;")?;
317 writeln!(w, "use std::mem::{{align_of, size_of}};")?;
318 }
319
320 writeln!(w, "use std::env;")?;
321 writeln!(w, "use std::error::Error;")?;
322 writeln!(w, "use std::ffi::OsString;")?;
323 writeln!(w, "use std::path::Path;")?;
324 writeln!(w, "use std::process::{{Command, Stdio}};")?;
325 writeln!(w, "use std::str;")?;
326 writeln!(w, "use tempfile::Builder;")?;
327 writeln!(w)?;
328 writeln!(w, "static PACKAGES: &[&str] = &[{package_names}];")?;
329 writeln!(
330 w,
331 "{}",
332 r#"
333#[derive(Clone, Debug)]
334struct Compiler {
335 pub args: Vec<String>,
336}
337
338impl Compiler {
339 pub fn new() -> Result<Self, Box<dyn Error>> {
340 let mut args = get_var("CC", "cc")?;
341 args.push("-Wno-deprecated-declarations".to_owned());
342 // For _Generic
343 args.push("-std=c11".to_owned());
344 // For %z support in printf when using MinGW.
345 args.push("-D__USE_MINGW_ANSI_STDIO".to_owned());
346 args.extend(get_var("CFLAGS", "")?);
347 args.extend(get_var("CPPFLAGS", "")?);
348 args.extend(pkg_config_cflags(PACKAGES)?);
349 Ok(Self { args })
350 }
351
352 pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box<dyn Error>> {
353 let mut cmd = self.to_command();
354 cmd.arg(src);
355 cmd.arg("-o");
356 cmd.arg(out);
357 let status = cmd.spawn()?.wait()?;
358 if !status.success() {
359 return Err(format!("compilation command {cmd:?} failed, {status}").into());
360 }
361 Ok(())
362 }
363
364 fn to_command(&self) -> Command {
365 let mut cmd = Command::new(&self.args[0]);
366 cmd.args(&self.args[1..]);
367 cmd
368 }
369}
370
371fn get_var(name: &str, default: &str) -> Result<Vec<String>, Box<dyn Error>> {
372 match env::var(name) {
373 Ok(value) => Ok(shell_words::split(&value)?),
374 Err(env::VarError::NotPresent) => Ok(shell_words::split(default)?),
375 Err(err) => Err(format!("{name} {err}").into()),
376 }
377}
378
379fn pkg_config_cflags(packages: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
380 if packages.is_empty() {
381 return Ok(Vec::new());
382 }
383 let pkg_config = env::var_os("PKG_CONFIG")
384 .unwrap_or_else(|| OsString::from("pkg-config"));
385 let mut cmd = Command::new(pkg_config);
386 cmd.arg("--cflags");
387 cmd.args(packages);
388 cmd.stderr(Stdio::inherit());
389 let out = cmd.output()?;
390 if !out.status.success() {
391 let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout));
392 return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into());
393 }
394 let stdout = str::from_utf8(&out.stdout)?;
395 Ok(shell_words::split(stdout.trim())?)
396}
397
398
399#[derive(Copy, Clone, Debug, Eq, PartialEq)]
400struct Layout {
401 size: usize,
402 alignment: usize,
403}
404
405#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
406struct Results {
407 /// Number of successfully completed tests.
408 passed: usize,
409 /// Total number of failed tests (including those that failed to compile).
410 failed: usize,
411}
412
413impl Results {
414 fn record_passed(&mut self) {
415 self.passed += 1;
416 }
417 fn record_failed(&mut self) {
418 self.failed += 1;
419 }
420 fn summary(&self) -> String {
421 format!("{} passed; {} failed", self.passed, self.failed)
422 }
423 fn expect_total_success(&self) {
424 if self.failed == 0 {
425 println!("OK: {}", self.summary());
426 } else {
427 panic!("FAILED: {}", self.summary());
428 };
429 }
430}
431
432#[test]
433fn cross_validate_constants_with_c() {
434 let mut c_constants: Vec<(String, String)> = Vec::new();
435
436 for l in get_c_output("constant").unwrap().lines() {
437 let (name, value) = l.split_once(';').expect("Missing ';' separator");
438 c_constants.push((name.to_owned(), value.to_owned()));
439 }
440
441 let mut results = Results::default();
442
443 for ((rust_name, rust_value), (c_name, c_value)) in
444 RUST_CONSTANTS.iter().zip(c_constants.iter())
445 {
446 if rust_name != c_name {
447 results.record_failed();
448 eprintln!("Name mismatch:\nRust: {rust_name:?}\nC: {c_name:?}");
449 continue;
450 }
451
452 if rust_value != c_value {
453 results.record_failed();
454 eprintln!(
455 "Constant value mismatch for {rust_name}\nRust: {rust_value:?}\nC: {c_value:?}",
456 );
457 continue;
458 }
459
460 results.record_passed();
461 }
462
463 results.expect_total_success();
464}
465
466#[test]
467fn cross_validate_layout_with_c() {
468 let mut c_layouts = Vec::new();
469
470 for l in get_c_output("layout").unwrap().lines() {
471 let (name, value) = l.split_once(';').expect("Missing first ';' separator");
472 let (size, alignment) = value.split_once(';').expect("Missing second ';' separator");
473 let size = size.parse().expect("Failed to parse size");
474 let alignment = alignment.parse().expect("Failed to parse alignment");
475 c_layouts.push((name.to_owned(), Layout { size, alignment }));
476 }
477
478 let mut results = Results::default();
479
480 for ((rust_name, rust_layout), (c_name, c_layout)) in
481 RUST_LAYOUTS.iter().zip(c_layouts.iter())
482 {
483 if rust_name != c_name {
484 results.record_failed();
485 eprintln!("Name mismatch:\nRust: {rust_name:?}\nC: {c_name:?}");
486 continue;
487 }
488
489 if rust_layout != c_layout {
490 results.record_failed();
491 eprintln!(
492 "Layout mismatch for {rust_name}\nRust: {rust_layout:?}\nC: {c_layout:?}",
493 );
494 continue;
495 }
496
497 results.record_passed();
498 }
499
500 results.expect_total_success();
501}
502
503fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
504 let tmpdir = Builder::new().prefix("abi").tempdir()?;
505 let exe = tmpdir.path().join(name);
506 let c_file = Path::new("tests").join(name).with_extension("c");
507
508 let cc = Compiler::new().expect("configured compiler");
509 cc.compile(&c_file, &exe)?;
510
511 let mut cmd = Command::new(exe);
512 cmd.stderr(Stdio::inherit());
513 let out = cmd.output()?;
514 if !out.status.success() {
515 let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout));
516 return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into());
517 }
518
519 Ok(String::from_utf8(out.stdout)?)
520}
521
522const RUST_LAYOUTS: &[(&str, Layout)] = &["#
523 )?;
524 for ctype in ctypes {
525 general::cfg_condition(w, ctype.cfg_condition.as_ref(), false, 1)?;
526 writeln!(w, " (\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),",
527 ctype=ctype.name)?;
528 }
529 writeln!(
530 w,
531 "{}",
532 r#"];
533
534const RUST_CONSTANTS: &[(&str, &str)] = &["#
535 )?;
536 for cconst in cconsts {
537 if cconst.status.ignored() {
538 continue;
539 }
540 writeln!(
541 w,
542 " (\"{name}\", \"{value}\"),",
543 name = cconst.name,
544 value = &general::escape_string(&cconst.value)
545 )?;
546 }
547 writeln!(
548 w,
549 "{}",
550 r#"];
551
552"#
553 )
554}