libgir/codegen/sys/
lib_.rs

1use std::{
2    fs,
3    io::{Error, ErrorKind, Result, Write},
4};
5
6use log::info;
7
8use super::{ffi_type::ffi_type, fields, functions, statics};
9use crate::{
10    codegen::general::{self, cfg_condition, version_condition},
11    config::constants,
12    env::Env,
13    file_saver::*,
14    library::*,
15    nameutil::*,
16    traits::*,
17};
18
19pub fn generate(env: &Env) {
20    info!("Generating sys for {}", env.config.library_name);
21
22    let path = env.config.auto_path.join(file_name_sys("lib"));
23
24    info!("Generating file {:?}", path);
25    save_to_file(&path, env.config.make_backup, |w| generate_lib(w, env));
26}
27
28fn generate_lib(w: &mut dyn Write, env: &Env) -> Result<()> {
29    general::start_comments(w, &env.config)?;
30    statics::begin(w)?;
31
32    generate_extern_crates(w, env)?;
33    include_custom_modules(w, env)?;
34    statics::after_extern_crates(w)?;
35
36    if env.config.library_name != "GLib" {
37        statics::use_glib(w)?;
38    }
39    match &*env.config.library_name {
40        "GLib" => statics::only_for_glib(w)?,
41        "GObject" => statics::only_for_gobject(w)?,
42        "Gtk" => statics::only_for_gtk(w)?,
43        _ => (),
44    }
45    writeln!(w)?;
46
47    let ns = env.library.namespace(MAIN_NAMESPACE);
48    let records = prepare(ns);
49    let classes = prepare(ns);
50    let interfaces = prepare(ns);
51    let bitfields = prepare(ns);
52    let enums = prepare(ns);
53    let unions = prepare(ns);
54
55    generate_aliases(w, env, &prepare(ns))?;
56    generate_enums(w, env, &enums)?;
57    generate_constants(w, env, &ns.constants)?;
58    generate_bitfields(w, env, &bitfields)?;
59    generate_unions(w, env, &unions)?;
60    functions::generate_callbacks(w, env, &prepare(ns))?;
61    generate_records(w, env, &records)?;
62    generate_classes_structs(w, env, &classes)?;
63    generate_interfaces_structs(w, env, &interfaces)?;
64
65    if env.namespaces.main().shared_libs.is_empty()
66        && !(records.iter().all(|x| x.functions.is_empty())
67            && classes.iter().all(|x| x.functions.is_empty())
68            && interfaces.iter().all(|x| x.functions.is_empty())
69            && bitfields.iter().all(|x| x.functions.is_empty())
70            && enums.iter().all(|x| x.functions.is_empty())
71            && unions.iter().all(|x| x.functions.is_empty()))
72    {
73        return Err(Error::new(
74            ErrorKind::Other,
75            "No shared library found, but functions were found",
76        ));
77    }
78
79    if !env.namespaces.main().shared_libs.is_empty() {
80        writeln!(w, "extern \"C\" {{")?;
81        functions::generate_enums_funcs(w, env, &enums)?;
82        functions::generate_bitfields_funcs(w, env, &bitfields)?;
83        functions::generate_unions_funcs(w, env, &unions)?;
84        functions::generate_records_funcs(w, env, &records)?;
85        functions::generate_classes_funcs(w, env, &classes)?;
86        functions::generate_interfaces_funcs(w, env, &interfaces)?;
87        functions::generate_other_funcs(w, env, &ns.functions)?;
88
89        writeln!(w, "\n}}")?;
90    }
91
92    Ok(())
93}
94
95fn generate_extern_crates(w: &mut dyn Write, env: &Env) -> Result<()> {
96    for library in &env.config.external_libraries {
97        w.write_all(
98            format!(
99                "use {}_sys as {};\n",
100                library.crate_name.replace('-', "_"),
101                crate_name(&library.namespace)
102            )
103            .as_bytes(),
104        )?;
105    }
106
107    Ok(())
108}
109
110fn include_custom_modules(w: &mut dyn Write, env: &Env) -> Result<()> {
111    let modules = find_modules(env)?;
112    if !modules.is_empty() {
113        writeln!(w)?;
114        for module in &modules {
115            writeln!(w, "mod {module};")?;
116        }
117        writeln!(w)?;
118        for module in &modules {
119            writeln!(w, "pub use {module}::*;")?;
120        }
121    }
122
123    Ok(())
124}
125
126fn find_modules(env: &Env) -> Result<Vec<String>> {
127    let mut vec = Vec::<String>::new();
128    for entry in fs::read_dir(&env.config.auto_path)? {
129        let path = entry?.path();
130        let Some(ext) = path.extension() else {
131            continue;
132        };
133        if ext != "rs" {
134            continue;
135        }
136        let file_stem = path.file_stem().expect("No file name");
137        if file_stem == "lib" {
138            continue;
139        }
140        let file_stem = file_stem
141            .to_str()
142            .expect("Can't convert file name to string")
143            .to_owned();
144        vec.push(file_stem);
145    }
146    vec.sort();
147
148    Ok(vec)
149}
150
151fn prepare<T: Ord>(ns: &Namespace) -> Vec<&T>
152where
153    Type: MaybeRef<T>,
154{
155    let mut vec: Vec<&T> = Vec::with_capacity(ns.types.len());
156    for typ in ns.types.iter().filter_map(Option::as_ref) {
157        if let Some(x) = typ.maybe_ref() {
158            vec.push(x);
159        }
160    }
161    vec.sort();
162    vec
163}
164
165fn generate_aliases(w: &mut dyn Write, env: &Env, items: &[&Alias]) -> Result<()> {
166    if !items.is_empty() {
167        writeln!(w, "// Aliases")?;
168    }
169    for item in items {
170        let full_name = format!("{}.{}", env.namespaces.main().name, item.name);
171        if !env.type_status_sys(&full_name).need_generate() {
172            continue;
173        }
174        let (comment, c_type) = match ffi_type(env, item.typ, &item.target_c_type) {
175            Ok(x) => ("", x.into_string()),
176            x @ Err(..) => ("//", x.into_string()),
177        };
178        let cfg_condition_ = env
179            .config
180            .objects
181            .get(&full_name)
182            .and_then(|obj| obj.cfg_condition.as_ref());
183        cfg_condition(w, cfg_condition_, false, 0)?;
184        writeln!(w, "{}pub type {} = {};", comment, item.c_identifier, c_type)?;
185    }
186    if !items.is_empty() {
187        writeln!(w)?;
188    }
189
190    Ok(())
191}
192
193fn generate_bitfields(w: &mut dyn Write, env: &Env, items: &[&Bitfield]) -> Result<()> {
194    if !items.is_empty() {
195        writeln!(w, "// Flags")?;
196    }
197    for item in items {
198        let full_name = format!("{}.{}", env.namespaces.main().name, item.name);
199        let config = env.config.objects.get(&full_name);
200        if let Some(false) = config.map(|c| c.status.need_generate()) {
201            continue;
202        }
203        writeln!(w, "pub type {} = c_uint;", item.c_type)?;
204        for member in &item.members {
205            let member_config = config
206                .as_ref()
207                .map_or_else(Vec::new, |c| c.members.matched(&member.name));
208            let version = member_config
209                .iter()
210                .find_map(|m| m.version)
211                .or(member.version);
212
213            if member_config.iter().any(|m| m.status.ignored()) {
214                continue;
215            }
216
217            let val: i64 = member.value.parse().unwrap();
218
219            version_condition(w, env, None, version, false, 0)?;
220            writeln!(
221                w,
222                "pub const {}: {} = {};",
223                member.c_identifier, item.c_type, val as u32,
224            )?;
225        }
226        writeln!(w)?;
227    }
228
229    Ok(())
230}
231
232fn generate_constant_cfg_configure(
233    w: &mut dyn Write,
234    configured_constants: &[&constants::Constant],
235    commented: bool,
236) -> Result<()> {
237    let cfg_condition_ = configured_constants
238        .iter()
239        .find_map(|f| f.cfg_condition.as_ref());
240    cfg_condition(w, cfg_condition_, commented, 1)?;
241    Ok(())
242}
243
244fn generate_constants(w: &mut dyn Write, env: &Env, constants: &[Constant]) -> Result<()> {
245    if !constants.is_empty() {
246        writeln!(w, "// Constants")?;
247    }
248    for constant in constants {
249        let full_name = format!("{}.{}", env.namespaces.main().name, constant.name);
250        let config = env.config.objects.get(&full_name);
251        if let Some(false) = config.map(|c| c.status.need_generate()) {
252            continue;
253        }
254        let (comment, mut type_) = match ffi_type(env, constant.typ, &constant.c_type) {
255            Ok(x) => ("", x.into_string()),
256            x @ Err(..) => ("//", x.into_string()),
257        };
258        let mut value = constant.value.clone();
259        if type_ == "*mut c_char" {
260            type_ = "&[u8]".into();
261            value = format!("b\"{}\\0\"", general::escape_string(&value));
262        } else if type_ == "gboolean" {
263            value = if value == "true" {
264                use_glib_if_needed(env, "GTRUE")
265            } else {
266                use_glib_if_needed(env, "GFALSE")
267            };
268        } else if env
269            .library
270            .type_(constant.typ)
271            .maybe_ref_as::<Bitfield>()
272            .is_some()
273        {
274            let val: i64 = constant.value.parse().unwrap();
275            value = (val as u32).to_string();
276        }
277
278        if let Some(obj) = config {
279            let configured_constants = obj.constants.matched(&full_name);
280            generate_constant_cfg_configure(w, &configured_constants, !comment.is_empty())?;
281        }
282
283        writeln!(
284            w,
285            "{}pub const {}: {} = {};",
286            comment, constant.c_identifier, type_, value
287        )?;
288    }
289    if !constants.is_empty() {
290        writeln!(w)?;
291    }
292
293    Ok(())
294}
295
296fn generate_enums(w: &mut dyn Write, env: &Env, items: &[&Enumeration]) -> Result<()> {
297    if !items.is_empty() {
298        writeln!(w, "// Enums")?;
299    }
300    for item in items {
301        let full_name = format!("{}.{}", env.namespaces.main().name, item.name);
302        let config = env.config.objects.get(&full_name);
303        if let Some(false) = config.map(|c| c.status.need_generate()) {
304            continue;
305        }
306        let cfg_condition_ = env
307            .config
308            .objects
309            .get(&full_name)
310            .and_then(|obj| obj.cfg_condition.as_ref());
311        cfg_condition(w, cfg_condition_, false, 0)?;
312        writeln!(w, "pub type {} = c_int;", item.c_type)?;
313        for member in &item.members {
314            let member_config = config
315                .as_ref()
316                .map_or_else(Vec::new, |c| c.members.matched(&member.name));
317            let version = member_config
318                .iter()
319                .find_map(|m| m.version)
320                .or(member.version);
321
322            if member_config.iter().any(|m| m.status.ignored()) {
323                continue;
324            }
325
326            cfg_condition(w, cfg_condition_, false, 0)?;
327            version_condition(w, env, None, version, false, 0)?;
328            writeln!(
329                w,
330                "pub const {}: {} = {};",
331                member.c_identifier, item.c_type, member.value,
332            )?;
333        }
334        writeln!(w)?;
335    }
336
337    Ok(())
338}
339
340fn generate_unions(w: &mut dyn Write, env: &Env, unions: &[&Union]) -> Result<()> {
341    if !unions.is_empty() {
342        writeln!(w, "// Unions")?;
343    }
344    for union in unions {
345        if union.c_type.is_none() {
346            continue;
347        }
348        let full_name = format!("{}.{}", env.namespaces.main().name, union.name);
349        let config = env.config.objects.get(&full_name);
350
351        if let Some(false) = config.map(|c| c.status.need_generate()) {
352            continue;
353        }
354
355        let align = config.and_then(|c| c.align);
356        let fields = fields::from_union(env, union);
357        generate_from_fields(w, &fields, align)?;
358    }
359    Ok(())
360}
361
362fn generate_debug_impl(w: &mut dyn Write, name: &str, impl_content: &str) -> Result<()> {
363    writeln!(
364        w,
365        "impl ::std::fmt::Debug for {name} {{\n\
366         \tfn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{\n\
367         \t\t{impl_content}\n\
368         \t}}\n\
369         }}\n"
370    )
371}
372
373fn generate_classes_structs(w: &mut dyn Write, env: &Env, classes: &[&Class]) -> Result<()> {
374    if !classes.is_empty() {
375        writeln!(w, "// Classes")?;
376    }
377    for class in classes {
378        let full_name = format!("{}.{}", env.namespaces.main().name, class.name);
379        let config = env.config.objects.get(&full_name);
380
381        if let Some(false) = config.map(|c| c.status.need_generate()) {
382            continue;
383        }
384
385        let align = config.and_then(|c| c.align);
386        let fields = fields::from_class(env, class);
387        generate_from_fields(w, &fields, align)?;
388    }
389    Ok(())
390}
391
392fn generate_opaque_type(w: &mut dyn Write, name: &str) -> Result<()> {
393    writeln!(
394        w,
395        r#"#[repr(C)]
396#[allow(dead_code)]
397pub struct {name} {{
398    _data: [u8; 0],
399    _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
400}}
401"#
402    )
403}
404
405fn generate_interfaces_structs(
406    w: &mut dyn Write,
407    env: &Env,
408    interfaces: &[&Interface],
409) -> Result<()> {
410    if !interfaces.is_empty() {
411        writeln!(w, "// Interfaces")?;
412    }
413    for interface in interfaces {
414        let full_name = format!("{}.{}", env.namespaces.main().name, interface.name);
415        if !env.type_status_sys(&full_name).need_generate() {
416            continue;
417        }
418        let cfg_condition_ = env
419            .config
420            .objects
421            .get(&full_name)
422            .and_then(|obj| obj.cfg_condition.as_ref());
423        cfg_condition(w, cfg_condition_, false, 0)?;
424        generate_opaque_type(w, &interface.c_type)?;
425        cfg_condition(w, cfg_condition_, false, 0)?;
426        generate_debug_impl(
427            w,
428            &interface.c_type,
429            &format!(
430                "write!(f, \"{name} @ {{self:p}}\")",
431                name = interface.c_type
432            ),
433        )?;
434    }
435    if !interfaces.is_empty() {
436        writeln!(w)?;
437    }
438
439    Ok(())
440}
441
442fn generate_records(w: &mut dyn Write, env: &Env, records: &[&Record]) -> Result<()> {
443    if !records.is_empty() {
444        writeln!(w, "// Records")?;
445    }
446    for record in records {
447        let full_name = format!("{}.{}", env.namespaces.main().name, record.name);
448        let config = env.config.objects.get(&full_name);
449
450        if let Some(false) = config.map(|c| c.status.need_generate()) {
451            continue;
452        }
453
454        if record.c_type == "GHookList" {
455            // 1. GHookList is useful.
456            // 2. GHookList contains bitfields.
457            // 3. Bitfields are unrepresentable in Rust.
458            // 4. ...
459            // 5. Thus, we use custom generated GHookList.
460            //    Hopefully someone will profit from all this.
461            generate_ghooklist(w)?;
462        } else if record.disguised || record.pointer {
463            generate_disguised(w, env, record)?;
464        } else {
465            let align = config.and_then(|c| c.align);
466            let fields = fields::from_record(env, record);
467            generate_from_fields(w, &fields, align)?;
468        }
469    }
470    Ok(())
471}
472
473fn generate_ghooklist(w: &mut dyn Write) -> Result<()> {
474    w.write_all(
475        br#"#[repr(C)]
476#[derive(Copy, Clone)]
477pub struct GHookList {
478    pub seq_id: c_ulong,
479    #[cfg(any(not(windows), not(target_pointer_width = "64")))]
480    pub hook_size_and_setup: gpointer,
481    #[cfg(all(windows, target_pointer_width = "64"))]
482    pub hook_size_and_setup: c_ulong,
483    pub hooks: *mut GHook,
484    pub dummy3: gpointer,
485    pub finalize_hook: GHookFinalizeFunc,
486    pub dummy: [gpointer; 2],
487}
488
489impl ::std::fmt::Debug for GHookList {
490    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
491        write!(f, "GHookList @ {self:p}")
492    }
493}
494
495"#,
496    )
497}
498
499fn generate_disguised(w: &mut dyn Write, env: &Env, record: &Record) -> Result<()> {
500    let full_name = format!("{}.{}", env.namespaces.main().name, record.name);
501    let cfg_condition_ = env
502        .config
503        .objects
504        .get(&full_name)
505        .and_then(|obj| obj.cfg_condition.as_ref());
506    cfg_condition(w, cfg_condition_, false, 0)?;
507    generate_opaque_type(w, &format!("_{}", record.c_type))?;
508    cfg_condition(w, cfg_condition_, false, 0)?;
509    if record.pointer {
510        writeln!(w, "pub type {name} = *mut _{name};", name = record.c_type)?;
511    } else {
512        writeln!(w, "pub type {name} = _{name};", name = record.c_type)?;
513    }
514    writeln!(w)
515}
516
517fn generate_from_fields(
518    w: &mut dyn Write,
519    fields: &fields::Fields,
520    align: Option<u32>,
521) -> Result<()> {
522    cfg_condition(w, fields.cfg_condition.as_ref(), false, 0)?;
523    if let Some(align) = align {
524        writeln!(w, "#[repr(align({align}))]")?;
525    }
526    let traits = fields.derived_traits().join(", ");
527    if !traits.is_empty() {
528        writeln!(w, "#[derive({traits})]")?;
529    }
530    if fields.external {
531        // It would be nice to represent those using extern types
532        // from RFC 1861, once they are available in stable Rust.
533        // https://github.com/rust-lang/rust/issues/43467
534        generate_opaque_type(w, &fields.name)?;
535    } else {
536        writeln!(w, "#[repr(C)]")?;
537
538        if fields.truncated.is_some() {
539            writeln!(w, "#[allow(dead_code)]")?;
540        }
541
542        writeln!(
543            w,
544            "pub {kind} {name} {{",
545            kind = fields.kind,
546            name = &fields.name
547        )?;
548        for field in &fields.fields {
549            writeln!(
550                w,
551                "\tpub {field_name}: {field_type},",
552                field_name = &field.name,
553                field_type = &field.typ
554            )?;
555        }
556        if let Some(ref reason) = fields.truncated {
557            writeln!(w, "\t_truncated_record_marker: c_void,")?;
558            writeln!(w, "\t// {reason}")?;
559        }
560        writeln!(w, "}}\n")?;
561    }
562
563    cfg_condition(w, fields.cfg_condition.as_ref(), false, 0)?;
564    writeln!(
565        w,
566        "impl ::std::fmt::Debug for {name} {{",
567        name = &fields.name
568    )?;
569    writeln!(
570        w,
571        "\tfn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{"
572    )?;
573    writeln!(
574        w,
575        "\t\tf.debug_struct(&format!(\"{name} @ {{self:p}}\"))",
576        name = &fields.name
577    )?;
578    for field in fields.fields.iter().filter(|f| f.debug) {
579        // TODO: We should generate debug for field manually if automatic one is not
580        // available.
581        writeln!(
582            w,
583            "\t\t .field(\"{field_name}\", {field_get})",
584            field_name = &field.name,
585            field_get = &field.access_str()
586        )?;
587    }
588    writeln!(w, "\t\t .finish()")?;
589    writeln!(w, "\t}}")?;
590    writeln!(w, "}}")?;
591    writeln!(w)
592}