1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: MIT

#[macro_use]
extern crate derive_builder;

use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
use quick_xml::Reader;
use quick_xml::events::Event;

#[derive(Builder, Default, Debug)]
#[builder(try_setter, setter(into), build_fn(validate = "Self::validate"))]
pub struct Resource {
    src_dir: PathBuf,
    definition_file: PathBuf,
}

impl Resource {
    pub fn builder() -> ResourceBuilder {
        ResourceBuilder::default()
    }

    pub fn compile(&self) {
        let out_dir = env::var_os("OUT_DIR").unwrap();

        let src_dir = self.src_dir.as_path();

        let definition_basename = self.definition_file.as_path().file_name()
            .unwrap_or_else(|| panic!("Bad definition path: {}", &self.definition_file.as_path().to_string_lossy()))
            .to_string_lossy()
            .to_string();

        let mut xml_reader = Reader::from_file(&self.definition_file)
            .unwrap_or_else(|error| {
                panic!("Unable to open {}: {}",
                    &self.definition_file.as_path().to_string_lossy(),
                    error
                );
            });
        let mut buf = vec![];
        let mut inside_file_tag = false;
        loop {
            match xml_reader.read_event(&mut buf) {
                Ok(Event::Start(ref e)) => {
                    if let b"file" = e.name() {
                        inside_file_tag = true;
                    }
                },
                Ok(Event::Text(e)) => {
                    if !inside_file_tag {
                        continue;
                    }

                    inside_file_tag = false;

                    let relative_path = e.unescape_and_decode(&xml_reader).unwrap();
                    let mut file_path = src_dir.to_path_buf();
                    file_path.push(relative_path);

                    println!("cargo:rerun-if-changed={}", &file_path.to_string_lossy());
                },
                Err(e) => panic!("Error at position {}: {:?}", xml_reader.buffer_position(), e),
                Ok(Event::Eof) => break,
                _ => (),
            }
            buf.clear();
        }

        let cfilename = match definition_basename.strip_suffix(".xml") {
            Some(basename) => format!("{}.c", basename),
            None => panic!("Bad extension for definition file, expected .xml: {}", definition_basename),
        };
        let cfile_path = Path::new(&out_dir).join(&cfilename);
        let dest_path = cfile_path.to_string_lossy();

        Command::new("glib-compile-resources")
            .arg("--generate-source")
            .arg(self.definition_file.as_path())
            .arg(format!("--sourcedir={}", &src_dir.to_string_lossy()).as_str())
            .arg(format!("--target={}", &dest_path))
            .output()
            .expect("unable to generate resources source file");

        let cflags = Command::new("pkg-config")
            .arg("--cflags")
            .arg("--static")
            .arg("--libs")
            .arg("gio-2.0")
            .output()
            .map_or_else(
                |e| {
                    panic!("Could not get cflags from pkg-config for gio-2.0: {}", e)
                },
                |output| {
                    std::str::from_utf8(&output.stdout).unwrap().to_string()
            });

        let old_cflags = env::var_os("CFLAGS");

        env::set_var("CFLAGS", cflags);
        cc::Build::new()
            .file(&*dest_path)
            .compile(cfilename.strip_suffix(".c").unwrap());

        match old_cflags {
            Some(old_value) => env::set_var("CFLAGS", old_value),
            None => env::remove_var("CFLAGS"),
        }

        println!("cargo:rerun-if-changed={}", &self.definition_file.as_path().to_string_lossy());
        println!("cargo:rerun-if-changed=build.rs");
    }
}

impl ResourceBuilder {
    fn validate(&self) -> Result<(), String> {
        if let Some(ref src_dir) = self.src_dir {
            if src_dir.as_path().is_dir() {
                Ok(())
            } else {
                Err(format!("{} is not a directory",
                    &src_dir.as_os_str().to_string_lossy())
                )
            }
        } else {
            Err("src_dir not provided".to_string())
        }
    }
}