build_utils/build/
meson.rs

1use crate::BuildStep;
2use crate::util::execute_build_command;
3use std::process::Command;
4use crate::build::{BuildResult, Build, BuildStepError, LibraryType, LinkSearchKind};
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::hash::{Hasher, Hash};
8
9pub struct MesonBuild {
10    meson_options: HashMap<String, String>
11}
12
13impl MesonBuild {
14    pub fn builder() -> MesonBuildBuilder {
15        MesonBuildBuilder::new()
16    }
17}
18
19impl BuildStep for MesonBuild {
20    fn name(&self) -> &str {
21        "meson build"
22    }
23
24    fn hash(&self, hasher: &mut Box<dyn Hasher>) {
25        self.meson_options.iter().for_each(|(key, value)| {
26            key.hash(hasher);
27            value.hash(hasher);
28        });
29    }
30
31    fn execute(&mut self, build: &Build, result: &mut BuildResult) -> Result<(), BuildStepError> {
32        let build_path = build.build_path().to_str().expect("invalid build path");
33        let source_path = build.source().local_directory().to_str().expect("invalid source path");
34
35        /* setup */
36        {
37            let mut command = Command::new("meson");
38            command.arg("setup");
39
40            if let Some(prefix) = build.install_prefix() {
41                command.args(&["--prefix", prefix.to_str().expect("invalid install prefix")]);
42            }
43
44            match build.library_type {
45                LibraryType::Shared => command.arg("-Ddefault_library=shared"),
46                LibraryType::Static => command.arg("-Ddefault_library=static"),
47            };
48
49            self.meson_options.iter().for_each(|(key, value)| {
50                command.arg(format!("-D{}={}", key, value));
51            });
52
53            command.arg(&build_path);
54            command.arg(&source_path);
55
56            execute_build_command(&mut command, "failed to setup build")?;
57        }
58
59        /* compile */
60        {
61            let mut command = Command::new("meson");
62            command.arg("compile");
63            command.arg("-C");
64            command.arg(&build_path);
65            execute_build_command(&mut command, "failed to execute build")?;
66        }
67
68        /* install */
69        {
70            let mut command = Command::new("meson");
71            command.arg("install");
72            command.arg("-C");
73            command.arg(&build_path);
74            let (stdout, stderr) = execute_build_command(&mut command, "failed to install build")?;
75
76            let install_lines = stdout.lines()
77                .filter(|line| line.starts_with("Installing "));
78
79            let mut installed_elements = HashMap::with_capacity(50);
80            for full_line in install_lines {
81                /* cut of the "Installing " part */
82                let line = &full_line[11..];
83                let mut elements = line.split(" to ");
84
85                let key = elements.next().map(|e| e.to_owned());
86                let value = elements.next().map(|e| e.to_owned());
87                if elements.next().is_some() {
88                    return Err(BuildStepError::new(format!("Meson line \"{}\" contains more than one \" to \" parts.", full_line).to_owned(), stdout, stderr));
89                }
90                if key.is_none() || value.is_none() {
91                    return Err(BuildStepError::new(format!("Meson line \"{}\" misses the key or value.", full_line).to_owned(), stdout, stderr));
92                }
93
94                installed_elements.insert(key.unwrap(), value.unwrap());
95            }
96
97            /* Gather installed libraries and emit them to the build result */
98            //println!("Stdout:\n{}\nStderr:\n{}", stdout.replace("\\", "/"), stderr);
99            installed_elements.iter().for_each(|(key, value)| {
100                let source = PathBuf::from(key);
101                if let Some(extension) = source.extension().map(|e| e.to_string_lossy().into_owned()) {
102                    let target = PathBuf::from(value);
103                    if !target.is_dir() {
104                        eprintln!("meson printed install for file \"{:?}\" to \"{:?}\", but target isn't a directory.", source, target);
105                        return;
106                    }
107
108                    //println!("Installed {:?} ({}) to {:?}", source, extension, target);
109                    if matches!(extension.as_ref(), "a" | "lib") {
110                        result.add_library(source.file_name().expect("missing source file name").to_string_lossy().into_owned(), Some(LibraryType::Static));
111                    } else if matches!(extension.as_ref(), "so" | "dll") {
112                        result.add_library(source.file_name().expect("missing source file name").to_string_lossy().into_owned(), Some(LibraryType::Shared));
113                    } else {
114                        return;
115                    }
116                    result.add_library_path(target, Some(LinkSearchKind::Native));
117                }
118            });
119        }
120
121        Ok(())
122    }
123}
124
125pub struct MesonBuildBuilder {
126    inner: MesonBuild
127}
128
129impl MesonBuildBuilder {
130    fn new() -> Self {
131        MesonBuildBuilder{
132            inner: MesonBuild{
133                meson_options: HashMap::new()
134            }
135        }
136    }
137
138    pub fn meson_option<K, V>(mut self, key: K, value: V) -> Self
139        where K: Into<String>,
140              V: Into<String>
141    {
142        self.inner.meson_options.insert(key.into(), value.into());
143        self
144    }
145
146    pub fn build(self) -> MesonBuild {
147        self.inner
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use crate::build::{BuildBuilder, MesonBuild};
154    use crate::source::BuildSourceGit;
155    use std::env;
156    use crate::util::create_temporary_path;
157
158    #[test]
159    fn test_build_usrsctp() {
160        let base_url = std::env::current_dir().expect("missing current dir").join("__test_meson");
161
162        env::set_var("rbuild_meson-test_library_type", "static");
163
164        let source = BuildSourceGit::builder("https://github.com/cisco/libsrtp.git".to_owned())
165            .checkout_folder(Some(base_url.clone()))
166            .skip_revision_checkout(true)
167            .build();
168
169        let meson_step = MesonBuild::builder()
170            .meson_option("sctp_build_programs", "false")
171            .build();
172
173        /* FIXME: Use some kind of dummy system here! */
174        let build = BuildBuilder::new()
175            .name("meson-test")
176            .source(Box::new(source))
177            .install_prefix(base_url.join("install_root"))
178            .build_path(base_url.clone())
179            .add_step(Box::new(meson_step))
180            .remove_build_dir(false)
181            .build();
182
183        let mut build = build.expect("failed to create build");
184        match build.execute() {
185            Err(error) => {
186                println!("{}", error.pretty_format());
187                panic!();
188            },
189            Ok(result) => result.emit_cargo()
190        }
191    }
192}