• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 13014481108

28 Jan 2025 04:01PM UTC coverage: 90.093% (-0.003%) from 90.096%
13014481108

push

github

web-flow
Merge pull request #1010 from geo-engine/expression-compilation

check compilation exit code

11 of 15 new or added lines in 2 files covered. (73.33%)

2 existing lines in 2 files now uncovered.

125698 of 139520 relevant lines covered (90.09%)

57694.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.11
/expression/src/compiled.rs
1
use crate::{
2
    error::{self, CompilationFailed, Compiler, ExpressionExecutionError},
3
    ExpressionAst, ExpressionDependencies,
4
};
5
use libloading::{library_filename, Library, Symbol};
6
use snafu::{ensure, ResultExt};
7
use std::{
8
    borrow::Cow,
9
    fs::File,
10
    io::Write,
11
    mem::ManuallyDrop,
12
    path::{Path, PathBuf},
13
    process::Command,
14
};
15
use tempfile::TempDir;
16

17
pub type Result<T, E = ExpressionExecutionError> = std::result::Result<T, E>;
18

19
/// Compiles and links an expression as a program and offers means to call it
20
pub struct LinkedExpression {
21
    library_folder: ManuallyDrop<TempDir>,
22
    library: ManuallyDrop<Library>,
23
    function_name: String,
24
}
25

26
impl LinkedExpression {
27
    pub fn new(
24✔
28
        function_name: &str,
24✔
29
        code: &str,
24✔
30
        dependencies: &ExpressionDependencies,
24✔
31
    ) -> Result<Self> {
24✔
32
        let library_folder =
24✔
33
            tempfile::tempdir().context(error::CannotGenerateSourceCodeDirectory)?;
24✔
34

35
        // format code in debug mode
36
        let mut code = Cow::from(code);
24✔
37
        if std::cfg!(debug_assertions) {
24✔
38
            code = syn::parse_file(&code)
24✔
39
                .map_or_else(
24✔
40
                    |e| {
24✔
41
                        // fallback to unformatted code
×
42
                        log::error!("Cannot parse expression: {e}");
×
43
                        code.to_string()
×
44
                    },
24✔
45
                    |file| prettyplease::unparse(&file),
24✔
46
                )
24✔
47
                .into();
24✔
48
        }
24✔
49

50
        let input_filename = create_source_code_file(library_folder.path(), &code)
24✔
51
            .context(error::CannotGenerateSourceCodeFile)?;
24✔
52

53
        let library_filename = compile_file(library_folder.path(), &input_filename, dependencies)?;
24✔
54

55
        let library = unsafe { Library::new(library_filename) }.context(error::LinkExpression)?;
24✔
56

57
        Ok(Self {
24✔
58
            library_folder: ManuallyDrop::new(library_folder),
24✔
59
            library: ManuallyDrop::new(library),
24✔
60
            function_name: function_name.to_string(),
24✔
61
        })
24✔
62
    }
24✔
63

64
    pub fn from_ast(ast: &ExpressionAst, dependencies: &ExpressionDependencies) -> Result<Self> {
6✔
65
        let code = if std::cfg!(debug_assertions) {
6✔
66
            ast.pretty_code()
6✔
67
        } else {
68
            ast.code()
×
69
        };
70
        Self::new(ast.name(), &code, dependencies)
6✔
71
    }
6✔
72

73
    /// Returns a function with 1 input parameters
74
    ///
75
    /// # Safety
76
    ///
77
    /// The caller must ensure that the function is called with the correct type of input parameter
78
    ///
79
    #[allow(clippy::type_complexity)]
80
    pub unsafe fn function_1<A>(&self) -> Result<Symbol<fn(A) -> Option<f64>>> {
29✔
81
        self.library
29✔
82
            .get(self.function_name.as_bytes())
29✔
83
            .context(error::LinkedFunctionNotFound {
29✔
84
                name: self.function_name.clone(),
29✔
85
            })
29✔
86
    }
29✔
87
    /// Returns a function with 3 input parameters
88
    ///
89
    /// # Safety
90
    ///
91
    /// The caller must ensure that the function is called with the correct type of input parameter
92
    ///
93
    #[allow(clippy::type_complexity)]
94
    pub unsafe fn function_2<A, B>(&self) -> Result<Symbol<fn(A, B) -> Option<f64>>> {
27✔
95
        self.library
27✔
96
            .get(self.function_name.as_bytes())
27✔
97
            .context(error::LinkedFunctionNotFound {
27✔
98
                name: self.function_name.clone(),
27✔
99
            })
27✔
100
    }
27✔
101

102
    /// Returns an n-ary function
103
    ///
104
    /// # Safety
105
    ///
106
    /// The caller must ensure that the function is called with the correct type of input and output parameters
107
    ///
108
    #[allow(clippy::type_complexity)]
109
    pub unsafe fn function_nary<F>(&self) -> Result<Symbol<F>> {
10✔
110
        self.library
10✔
111
            .get(self.function_name.as_bytes())
10✔
112
            .context(error::LinkedFunctionNotFound {
10✔
113
                name: self.function_name.clone(),
10✔
114
            })
10✔
115
    }
10✔
116
}
117

118
impl Drop for LinkedExpression {
119
    fn drop(&mut self) {
24✔
120
        // first unlink program…
24✔
121
        unsafe { ManuallyDrop::drop(&mut self.library) };
24✔
122

24✔
123
        // …then delete files
24✔
124
        unsafe { ManuallyDrop::drop(&mut self.library_folder) };
24✔
125
    }
24✔
126
}
127

128
fn create_source_code_file(
24✔
129
    library_folder: &Path,
24✔
130
    source_code: &str,
24✔
131
) -> Result<PathBuf, std::io::Error> {
24✔
132
    let input_filename = library_folder.join("expression.rs");
24✔
133

134
    let mut file = File::create(&input_filename)?;
24✔
135
    file.write_all(source_code.as_bytes())?;
24✔
136

137
    Ok(input_filename)
24✔
138
}
24✔
139

140
fn compile_file(
24✔
141
    library_folder: &Path,
24✔
142
    input_filename: &Path,
24✔
143
    dependencies: &ExpressionDependencies,
24✔
144
) -> Result<PathBuf> {
24✔
145
    let output_filename = library_folder.join(library_filename("expression"));
24✔
146

24✔
147
    let mut command = Command::new("rustc");
24✔
148
    command
24✔
149
        .args(["--edition", "2021"])
24✔
150
        .args(["--crate-type", "cdylib"])
24✔
151
        .args(["-C", "opt-level=3"])
24✔
152
        .arg("-L")
24✔
153
        .arg(dependencies.linker_path());
24✔
154

24✔
155
    if !std::cfg!(debug_assertions) {
24✔
156
        command.args(["-A", "warnings"]);
×
157
    }
24✔
158

159
    let output = command
24✔
160
        .arg("-o")
24✔
161
        .arg(&output_filename)
24✔
162
        .arg(input_filename)
24✔
163
        .output()
24✔
164
        .context(Compiler)?;
24✔
165

166
    ensure!(
24✔
167
        output.status.success(),
24✔
NEW
168
        CompilationFailed {
×
NEW
169
            stderr: String::from_utf8_lossy(&output.stderr),
×
NEW
170
            stdout: String::from_utf8_lossy(&output.stdout),
×
NEW
171
        }
×
172
    );
173

174
    Ok(output_filename)
24✔
175
}
24✔
176

177
#[cfg(test)]
178
mod tests {
179
    use crate::{DataType, ExpressionParser, Parameter};
180

181
    use super::*;
182
    use geoengine_expression_deps::{MultiPoint, MultiPolygon};
183
    use quote::quote;
184

185
    #[test]
186
    #[allow(clippy::float_cmp)]
187
    fn it_compiles_hello_world() {
1✔
188
        let dependencies = ExpressionDependencies::new().unwrap();
1✔
189

1✔
190
        let source_code = quote! {
1✔
191
            extern crate geo;
1✔
192
            extern crate geoengine_expression_deps;
1✔
193

1✔
194
            use geo::{polygon};
1✔
195
            use geoengine_expression_deps::*;
1✔
196

1✔
197
            #[no_mangle]
1✔
198
            pub extern "Rust" fn area_of_polygon() -> Option<f64> {
1✔
199
                let polygon = MultiPolygon::from(polygon![
1✔
200
                    (x: 0., y: 0.),
1✔
201
                    (x: 5., y: 0.),
1✔
202
                    (x: 5., y: 6.),
1✔
203
                    (x: 0., y: 6.),
1✔
204
                    (x: 0., y: 0.),
1✔
205
                ]);
1✔
206

1✔
207
                polygon.area()
1✔
208
            }
1✔
209
        }
1✔
210
        .to_string();
1✔
211

1✔
212
        let linked_expression =
1✔
213
            LinkedExpression::new("area_of_polygon", &source_code, &dependencies).unwrap();
1✔
214

1✔
215
        assert_eq!(
1✔
216
            unsafe {
1✔
217
                linked_expression
1✔
218
                    .function_nary::<fn() -> Option<f64>>()
1✔
219
                    .unwrap()
1✔
220
            }(),
1✔
221
            Some(30.0)
1✔
222
        );
1✔
223
    }
1✔
224

225
    #[test]
226
    fn it_compiles_an_expression() {
1✔
227
        use geo::{point, polygon};
228

229
        let dependencies = ExpressionDependencies::new().unwrap();
1✔
230

1✔
231
        let ast = ExpressionParser::new(
1✔
232
            &[
1✔
233
                Parameter::MultiPolygon("geom".into()),
1✔
234
                Parameter::Number("zero".into()),
1✔
235
            ],
1✔
236
            DataType::MultiPoint,
1✔
237
        )
1✔
238
        .unwrap()
1✔
239
        .parse(
1✔
240
            "expression",
1✔
241
            "
1✔
242
                if zero > 0 {
1✔
243
                    centroid(geom)
1✔
244
                } else {
1✔
245
                    centroid(geom)
1✔
246
                }",
1✔
247
        )
1✔
248
        .unwrap();
1✔
249

1✔
250
        let linked_expression = LinkedExpression::from_ast(&ast, &dependencies).unwrap();
1✔
251

1✔
252
        assert_eq!(
1✔
253
            unsafe {
1✔
254
                linked_expression
1✔
255
                    .function_nary::<fn(MultiPolygon, f64) -> Option<MultiPoint>>()
1✔
256
                    .unwrap()
1✔
257
            }(
1✔
258
                MultiPolygon::from(polygon![
1✔
259
                    (x: 0., y: 0.),
1✔
260
                    (x: 5., y: 0.),
1✔
261
                    (x: 5., y: 6.),
1✔
262
                    (x: 0., y: 6.),
1✔
263
                    (x: 0., y: 0.),
1✔
264
                ]),
1✔
265
                0.
1✔
266
            ),
1✔
267
            Some(MultiPoint::from(point!(x: 2.5, y: 3.0)))
1✔
268
        );
1✔
269
    }
1✔
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc