flatpak_rs/
module.rs

1use std::collections::BTreeMap;
2use std::ffi::OsStr;
3use std::fs;
4use std::path;
5use std::process::Command;
6
7use serde::{Deserialize, Serialize};
8
9use crate::build_system::FlatpakBuildSystem;
10use crate::format::FlatpakManifestFormat;
11use crate::source::{FlatpakSourceItem, FlatpakSourceType};
12
13#[derive(Clone)]
14#[derive(Deserialize)]
15#[derive(Serialize)]
16#[derive(Debug)]
17#[derive(Hash)]
18#[serde(untagged)]
19/// Items in a module list can be either paths to external module manifests, or inline descriptions
20/// of flatpak modules.
21pub enum FlatpakModuleItem {
22    Path(String),
23    Description(FlatpakModule),
24}
25
26#[derive(Clone)]
27#[derive(Deserialize)]
28#[derive(Serialize)]
29#[derive(Debug)]
30#[derive(Default)]
31#[derive(Hash)]
32#[serde(rename_all = "kebab-case")]
33#[serde(default)]
34/// Each module specifies a source that has to be separately built and installed.
35/// It contains the build options and a list of sources to download and extract before
36/// building.
37///
38/// Modules can be nested, in order to turn related modules on and off with a single key.
39pub struct FlatpakModule {
40    #[serde(skip_serializing)]
41    pub format: FlatpakManifestFormat,
42
43    /// The name of the module, used in e.g. build logs. The name is also
44    /// used for constructing filenames and commandline arguments,
45    /// therefore using spaces or '/' in this string is a bad idea.
46    pub name: String,
47
48    /// If true, skip this module
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub disabled: Option<bool>,
51
52    /// An array of objects defining sources that will be downloaded and extracted in order.
53    /// String members in the array are interpreted as the name of a separate
54    /// json or yaml file that contains sources. See below for details.
55    #[serde(skip_serializing_if = "Vec::is_empty")]
56    pub sources: Vec<FlatpakSourceItem>,
57
58    /// An array of options that will be passed to configure
59    #[serde(skip_serializing_if = "Vec::is_empty")]
60    pub config_opts: Vec<String>,
61
62    /// An array of arguments that will be passed to make
63    #[serde(skip_serializing_if = "Vec::is_empty")]
64    pub make_args: Vec<String>,
65
66    /// An array of arguments that will be passed to make install
67    #[serde(skip_serializing_if = "Vec::is_empty")]
68    pub make_install_args: Vec<String>,
69
70    /// If true, remove the configure script before starting build
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub rm_configure: Option<bool>,
73
74    /// Ignore the existence of an autogen script
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub no_autogen: Option<bool>,
77
78    /// Don't call make with arguments to build in parallel
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub no_parallel_make: Option<bool>,
81
82    /// Name of the rule passed to make for the install phase, default is install
83    #[serde(skip_serializing_if = "String::is_empty")]
84    pub install_rule: String,
85
86    /// Don't run the make install (or equivalent) stage
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub no_make_install: Option<bool>,
89
90    /// Don't fix up the python (*.pyo or *.pyc) header timestamps for ostree use.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub no_python_timestamp_fix: Option<bool>,
93
94    /// Use cmake instead of configure (deprecated: use buildsystem instead)
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub cmake: Option<bool>,
97
98    /// Build system to use.
99    #[serde(deserialize_with = "crate::build_system::FlatpakBuildSystem::deserialize")]
100    #[serde(serialize_with = "crate::build_system::FlatpakBuildSystem::serialize")]
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub buildsystem: Option<FlatpakBuildSystem>,
103
104    /// Use a build directory that is separate from the source directory
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub builddir: Option<bool>,
107
108    /// Build inside this subdirectory of the extracted sources
109    #[serde(skip_serializing_if = "String::is_empty")]
110    pub subdir: String,
111
112    /// A build options object that can override global options
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub build_options: Option<FlatpakBuildOptions>,
115
116    /// An array of commands to run during build (between make and make install if those are used).
117    /// This is primarily useful when using the "simple" buildsystem.
118    /// Each command is run in /bin/sh -c, so it can use standard POSIX shell syntax such as piping output.
119    #[serde(skip_serializing_if = "Vec::is_empty")]
120    pub build_commands: Vec<String>,
121
122    /// An array of shell commands that are run after the install phase.
123    /// Can for example clean up the install dir, or install extra files.
124    #[serde(skip_serializing_if = "Vec::is_empty")]
125    pub post_install: Vec<String>,
126
127    /// An array of file patterns that should be removed at the end.
128    /// Patterns starting with / are taken to be full pathnames (without the /app prefix), otherwise
129    /// they just match the basename. Note that any patterns will only match
130    /// files installed by this module.
131    #[serde(skip_serializing_if = "Vec::is_empty")]
132    pub cleanup: Vec<String>,
133
134    /// The way the builder works is that files in the install directory are hard-links to the cached files,
135    /// so you're not allowed to modify them in-place. If you list a file in this then the hardlink
136    /// will be broken and you can modify it. This is a workaround, ideally installing files should
137    /// replace files, not modify existing ones.
138    #[serde(skip_serializing_if = "Vec::is_empty")]
139    pub ensure_writable: Vec<String>,
140
141    /// If non-empty, only build the module on the arches listed.
142    #[serde(skip_serializing_if = "Vec::is_empty")]
143    pub only_arches: Vec<String>,
144
145    /// Don't build on any of the arches listed.
146    #[serde(skip_serializing_if = "Vec::is_empty")]
147    pub skip_arches: Vec<String>,
148
149    /// Extra files to clean up in the platform.
150    #[serde(skip_serializing_if = "Vec::is_empty")]
151    pub cleanup_platform: Vec<String>,
152
153    /// If true this will run the tests after installing.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub run_tests: Option<bool>,
156
157    /// The target to build when running the tests. Defaults to "check" for make and "test" for ninja.
158    /// Set to empty to disable.
159    #[serde(skip_serializing_if = "String::is_empty")]
160    pub test_rule: String,
161
162    /// Array of commands to run during the tests.
163    #[serde(skip_serializing_if = "Vec::is_empty")]
164    pub test_commands: Vec<String>,
165
166    /// An array of objects specifying nested modules to be built before this one.
167    /// String members in the array are interpreted as names of a separate json or
168    /// yaml file that contains a module.
169    #[serde(skip_serializing_if = "Vec::is_empty")]
170    pub modules: Vec<FlatpakModuleItem>,
171}
172impl FlatpakModule {
173    pub fn uses_external_data_checker(&self) -> bool {
174        for source in &self.sources {
175            let source_description = match source {
176                FlatpakSourceItem::Description(d) => d,
177                FlatpakSourceItem::Path(_) => continue,
178            };
179            if source_description.x_checker_data.is_some() {
180                return true;
181            }
182        }
183        return false;
184    }
185
186    pub fn get_mirror_urls(&self) -> Vec<String> {
187        let mut mirror_urls = vec![];
188        for module in &self.modules {
189            if let FlatpakModuleItem::Description(module_description) = module {
190                mirror_urls.append(&mut module_description.get_mirror_urls());
191            }
192        }
193        for source in &self.sources {
194            let source_description = match source {
195                FlatpakSourceItem::Description(d) => d,
196                FlatpakSourceItem::Path(_p) => continue,
197            };
198            for url in source_description.get_mirror_urls() {
199                mirror_urls.push(url.to_string());
200            }
201        }
202        mirror_urls
203    }
204
205    pub fn get_buildsystem(&self) -> Option<String> {
206        if self.cmake.unwrap_or(false) {
207            return Some(crate::build_system::CMAKE.to_string());
208        }
209        if let Some(buildsystem) = &self.buildsystem {
210            return Some(buildsystem.to_string());
211        }
212        None
213    }
214
215    pub fn is_patched(&self) -> bool {
216        for source in &self.sources {
217            if let FlatpakSourceItem::Description(sd) = source {
218                if sd.get_type() == Some(FlatpakSourceType::Patch) {
219                    return true;
220                }
221            }
222        }
223        false
224    }
225
226    pub fn load_from_file(path: String) -> Result<FlatpakModule, String> {
227        let file_path = path::Path::new(&path);
228        if !file_path.is_file() {
229            return Err(format!("{} is not a file.", path));
230        }
231
232        let manifest_format = match FlatpakManifestFormat::from_path(&path) {
233            Some(f) => f,
234            None => return Err(format!("{} is not a Flatpak module manifest.", path)),
235        };
236
237        let manifest_content = match fs::read_to_string(file_path) {
238            Ok(content) => content,
239            Err(e) => {
240                return Err(format!("Could not read module manifest at {}: {}", &path, e));
241            }
242        };
243        match FlatpakModule::parse(manifest_format, &manifest_content) {
244            Ok(m) => Ok(m),
245            Err(e) => Err(format!(
246                "Failed to parse Flatpak module manifest at {}: {}",
247                path, e
248            )),
249        }
250    }
251
252    pub fn parse(format: FlatpakManifestFormat, manifest_content: &str) -> Result<FlatpakModule, String> {
253        let mut flatpak_module: FlatpakModule = match format.parse(manifest_content) {
254            Ok(m) => m,
255            Err(e) => {
256                return Err(format!("Failed to parse the Flatpak module manifest: {}.", e));
257            }
258        };
259        flatpak_module.format = format;
260
261        if flatpak_module.name.is_empty() {
262            return Err("Required top-level field name is missing from Flatpak module.".to_string());
263        }
264        if flatpak_module.sources.is_empty() {
265            return Err("Required sources were not found in Flatpak module.".to_string());
266        }
267        for source in &flatpak_module.sources {
268            let source_path = match source {
269                FlatpakSourceItem::Description(_) => continue,
270                FlatpakSourceItem::Path(p) => p,
271            };
272            // The string elements of the source array should only be FS paths, not
273            // URLs or anything else.
274            if source_path.starts_with("http://") || source_path.starts_with("https://") {
275                return Err("Sources provided as strings cannot be URLs!".to_string());
276            }
277        }
278        for source in &flatpak_module.sources {
279            let source_description = match source {
280                FlatpakSourceItem::Path(_) => continue,
281                FlatpakSourceItem::Description(d) => d,
282            };
283            if let Err(e) = source_description.is_valid() {
284                return Err(e);
285            }
286        }
287
288        Ok(flatpak_module)
289    }
290
291    pub fn dump(&self) -> Result<String, String> {
292        match self.format.dump(self) {
293            Ok(d) => Ok(d),
294            Err(e) => return Err(format!("Failed to dump the Flatpak manifest: {}.", e)),
295        }
296    }
297
298    pub fn file_path_matches(path: &str) -> bool {
299        // The file path for a module is not necessarily in reverse DNS, so we can only test
300        // for the extension of the file.
301        crate::filename::extension_is_valid(path)
302    }
303
304    pub fn get_urls(
305        &self,
306        include_mirror_urls: bool,
307        include_source_types: Option<Vec<FlatpakSourceType>>,
308    ) -> Vec<String> {
309        let mut urls = vec![];
310        for module in &self.modules {
311            if let FlatpakModuleItem::Description(module_description) = module {
312                urls.append(
313                    &mut module_description.get_urls(include_mirror_urls, include_source_types.clone()),
314                );
315            }
316        }
317        for source in &self.sources {
318            let source_description = match source {
319                FlatpakSourceItem::Description(d) => d,
320                FlatpakSourceItem::Path(_p) => continue,
321            };
322            for url in source_description.get_urls(include_mirror_urls, include_source_types.clone()) {
323                urls.push(url);
324            }
325        }
326        urls
327    }
328
329    pub fn get_max_depth(&self) -> i32 {
330        let mut max_depth: i32 = 0;
331        for module in &self.modules {
332            if let FlatpakModuleItem::Description(module_description) = module {
333                let module_depth = module_description.get_max_depth();
334                if module_depth > max_depth {
335                    max_depth = module_depth;
336                }
337            }
338        }
339        return max_depth + 1;
340    }
341
342    pub fn get_main_url(&self) -> Option<String> {
343        if self.sources.len() < 1 {
344            return None;
345        }
346
347        // Here we assume that the first source is the actual project, and
348        // anything after is a patch or an additional file.
349        let main_module_source = self.sources.first().unwrap();
350
351        let main_module_source_description = match main_module_source {
352            FlatpakSourceItem::Description(d) => d,
353            FlatpakSourceItem::Path(_p) => return None,
354        };
355
356        let main_module_source_url: Option<String> = main_module_source_description.get_url();
357
358        match &main_module_source_url {
359            Some(s) => Some(s.to_string()),
360            None => None,
361        }
362    }
363
364    pub fn get_all_modules_recursively(&self) -> Vec<&FlatpakModuleItem> {
365        let mut all_modules: Vec<&FlatpakModuleItem> = vec![];
366        let mut next_modules: Vec<&FlatpakModuleItem> = vec![];
367        for module in &self.modules {
368            next_modules.push(module);
369        }
370        while !next_modules.is_empty() {
371            let module = next_modules.pop().unwrap();
372            all_modules.push(module);
373
374            let module = match module {
375                FlatpakModuleItem::Description(d) => d,
376                FlatpakModuleItem::Path(_) => continue,
377            };
378            for next_module in &module.modules {
379                next_modules.push(next_module);
380            }
381        }
382        all_modules
383    }
384
385    /// A module is composite if it links to multiple software projects.
386    /// This is determined by the type of the sources contained in the module.
387    pub fn is_composite(&self) -> bool {
388        let mut code_sources_count = 0;
389        for source in &self.sources {
390            let source_description = match source {
391                FlatpakSourceItem::Description(d) => d,
392                FlatpakSourceItem::Path(_) => continue,
393            };
394
395            let source_type = match source_description.get_type() {
396                Some(t) => t,
397                None => continue,
398            };
399
400            if source_type.is_code() {
401                code_sources_count += 1;
402            }
403            if code_sources_count > 1 {
404                return true;
405            }
406        }
407        false
408    }
409
410    /// Gets the build commands associated with a module.
411    pub fn get_commands<I, S>(
412        &self,
413        args: I,
414        reconfigure: bool,
415        root_path: &str,
416        build_path: &str,
417        out_path: Option<&str>,
418        num_cpus: i64,
419    ) -> Vec<Command>
420    where
421        I: IntoIterator<Item = S>,
422        S: AsRef<OsStr>,
423    {
424        let args: Vec<_> = args.into_iter().collect();
425
426        let out_path = out_path.unwrap_or("/app");
427
428        let build_system = self
429            .buildsystem
430            .to_owned()
431            .unwrap_or(FlatpakBuildSystem::default());
432
433        match build_system {
434            FlatpakBuildSystem::Autotools => {
435                let mut commands = Vec::new();
436
437                if !reconfigure {
438                    let mut cmd = Command::new("./configure");
439                    cmd.arg(format!("--prefix={}", out_path));
440                    cmd.args(self.config_opts.clone());
441                    cmd.current_dir(root_path);
442                    commands.push(cmd);
443                }
444
445                let mut cmd = Command::new("make");
446                cmd.args(&["-p", "-n", "-s"]);
447                cmd.current_dir(root_path);
448                commands.push(cmd);
449
450                let mut cmd = Command::new("make");
451                cmd.arg("V=0");
452                cmd.arg(format!("-j{}", num_cpus));
453                cmd.arg("install");
454                cmd.args(args);
455                cmd.current_dir(root_path);
456                commands.push(cmd);
457
458                commands
459            }
460            FlatpakBuildSystem::CMake | FlatpakBuildSystem::CMakeNinja => {
461                let mut commands = Vec::new();
462
463                if !reconfigure {
464                    let mut cmd = Command::new("mkdir");
465                    cmd.arg("-p").arg(build_path);
466                    commands.push(cmd);
467
468                    let mut cmd = Command::new("cmake");
469                    cmd.args(&["-G", "Ninja", "..", "."]);
470                    cmd.arg("-DCMAKE_EXPORT_COMPILE_COMMANDS=1");
471                    cmd.arg("-DCMAKE_BUILD_TYPE=RelWithDebInfo");
472                    cmd.arg(format!("-DCMAKE_INSTALL_PREFIX={}", out_path));
473                    cmd.args(self.config_opts.clone());
474                    cmd.current_dir(build_path);
475                    commands.push(cmd);
476                }
477
478                let mut cmd = Command::new("ninja");
479                cmd.current_dir(build_path);
480                commands.push(cmd);
481
482                let mut cmd = Command::new("ninja");
483                cmd.arg("install");
484                cmd.current_dir(build_path);
485                commands.push(cmd);
486
487                commands
488            }
489            FlatpakBuildSystem::Meson => {
490                let mut commands = Vec::new();
491
492                if !reconfigure {
493                    let mut cmd = Command::new("meson");
494                    cmd.arg(format!("--prefix={}", out_path));
495                    cmd.arg(build_path);
496                    cmd.args(self.config_opts.clone());
497                    cmd.current_dir(root_path);
498                    commands.push(cmd);
499                }
500
501                let mut cmd = Command::new("ninja");
502                cmd.arg("-C");
503                cmd.arg(build_path);
504                cmd.current_dir(root_path);
505                commands.push(cmd);
506
507                let mut cmd = Command::new("meson");
508                cmd.args(args);
509                cmd.args(&["install", "-C"]);
510                cmd.arg(build_path);
511                cmd.current_dir(root_path);
512                commands.push(cmd);
513
514                commands
515            }
516            FlatpakBuildSystem::QMake => panic!("Not implemented yet."),
517            FlatpakBuildSystem::Simple => self
518                .build_commands
519                .iter()
520                .map(|step| {
521                    let mut cmd = Command::new("/bin/sh");
522                    cmd.arg("-c");
523                    cmd.arg(step);
524                    cmd.current_dir(root_path);
525                    cmd
526                })
527                .collect(),
528        }
529    }
530}
531
532#[derive(Clone)]
533#[derive(Deserialize)]
534#[derive(Serialize)]
535#[derive(Debug)]
536#[derive(Hash)]
537#[serde(untagged)]
538/// This is a dictionary defining environment variables to be set during the build.
539/// Elements in this override the properties that set the environment, like
540/// cflags and ldflags. Keys with a null value unset the corresponding variable.
541/// FIXME the doc says this should be an object, but when defined in the modules,
542/// it is actually an array with values like PPC_CONFIG_PATH=/app/etc.
543pub enum FlatpakBuildOptionsEnv {
544    Dict(BTreeMap<String, String>),
545    Array(Vec<String>),
546}
547impl Default for FlatpakBuildOptionsEnv {
548    fn default() -> Self {
549        FlatpakBuildOptionsEnv::Array(vec![])
550    }
551}
552
553#[derive(Clone)]
554#[derive(Deserialize)]
555#[derive(Serialize)]
556#[derive(Debug)]
557#[derive(Default)]
558#[derive(Hash)]
559#[serde(rename_all = "kebab-case")]
560#[serde(default)]
561/// Build options specify the build environment of a module,
562/// and can be specified globally as well as per-module.
563/// Options can also be specified on a per-architecture basis using the arch property.
564pub struct FlatpakBuildOptions {
565    /// This is set in the environment variable CFLAGS during the build.
566    /// Multiple specifications of this (in e.g. per-arch area) are concatenated, separated by spaces.
567    #[serde(skip_serializing_if = "String::is_empty")]
568    pub cflags: String,
569
570    /// If this is true, clear cflags from previous build options before adding it from these options.
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub cflags_override: Option<bool>,
573
574    /// This is set in the environment variable CPPFLAGS during the build.
575    /// Multiple specifications of this (in e.g. per-arch area) are concatenated, separated by spaces.
576    #[serde(skip_serializing_if = "String::is_empty")]
577    pub cppflags: String,
578
579    /// If this is true, clear cppflags from previous build options before adding it from these options.
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub cppflags_override: Option<bool>,
582
583    /// This is set in the environment variable CXXFLAGS during the build.
584    /// Multiple specifications of this (in e.g. per-arch area) are concatenated, separated by spaces.
585    #[serde(skip_serializing_if = "String::is_empty")]
586    pub cxxflags: String,
587
588    /// If this is true, clear cxxflags from previous build options before adding it from these options.
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub cxxflags_override: Option<bool>,
591
592    /// This is set in the environment variable LDFLAGS during the build.
593    /// Multiple specifications of this (in e.g. per-arch area) are concatenated,
594    /// separated by spaces.
595    #[serde(skip_serializing_if = "String::is_empty")]
596    pub ldflags: String,
597
598    /// If this is true, clear ldflags from previous build options before adding it from these options.
599    #[serde(skip_serializing_if = "Option::is_none")]
600    pub ldflags_override: Option<bool>,
601
602    /// The build prefix for the modules (defaults to /app for applications and /usr for runtimes).
603    #[serde(skip_serializing_if = "String::is_empty")]
604    pub prefix: String,
605
606    /// The build libdir for the modules (defaults to /app/lib for applications and /usr/lib for runtimes).
607    #[serde(skip_serializing_if = "String::is_empty")]
608    pub libdir: String,
609
610    /// This will get appended to PATH in the build environment (with an leading colon if needed).
611    #[serde(skip_serializing_if = "String::is_empty")]
612    pub append_path: String,
613
614    /// This will get prepended to PATH in the build environment (with an trailing colon if needed).
615    #[serde(skip_serializing_if = "String::is_empty")]
616    pub prepend_path: String,
617
618    /// This will get appended to LD_LIBRARY_PATH in the build environment (with an leading colon if needed).
619    #[serde(skip_serializing_if = "String::is_empty")]
620    pub append_ld_library_path: String,
621
622    /// This will get prepended to LD_LIBRARY_PATH in the build environment (with an trailing colon if needed).
623    #[serde(skip_serializing_if = "String::is_empty")]
624    pub prepend_ld_library_path: String,
625
626    /// This will get appended to PKG_CONFIG_PATH in the build environment (with an leading colon if needed).
627    #[serde(skip_serializing_if = "String::is_empty")]
628    pub append_pkg_config_path: String,
629
630    /// This will get prepended to PKG_CONFIG_PATH in the build environment (with an trailing colon if needed).
631    #[serde(skip_serializing_if = "String::is_empty")]
632    pub prepend_pkg_config_path: String,
633
634    pub env: FlatpakBuildOptionsEnv,
635
636    /// This is an array containing extra options to pass to flatpak build.
637    #[serde(skip_serializing_if = "Vec::is_empty")]
638    pub build_args: Vec<String>,
639
640    /// Similar to build-args but affects the tests, not the normal build.
641    #[serde(skip_serializing_if = "Vec::is_empty")]
642    pub test_args: Vec<String>,
643
644    /// This is an array containing extra options to pass to configure.
645    #[serde(skip_serializing_if = "Vec::is_empty")]
646    pub config_opts: Vec<String>,
647
648    /// An array of extra arguments that will be passed to make
649    #[serde(skip_serializing_if = "Vec::is_empty")]
650    pub make_args: Vec<String>,
651
652    /// An array of extra arguments that will be passed to make install
653    #[serde(skip_serializing_if = "Vec::is_empty")]
654    pub make_install_args: Vec<String>,
655
656    /// If this is true (the default is false) then all ELF files will be stripped after install.
657    #[serde(skip_serializing_if = "Option::is_none")]
658    pub strip: Option<bool>,
659
660    /// By default (if strip is not true) flatpak-builder extracts all debug info in ELF files to a
661    /// separate files and puts this in an extension. If you want to disable this, set no-debuginfo
662    /// to true.
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub no_debuginfo: Option<bool>,
665
666    /// By default when extracting debuginfo we compress the debug sections.
667    /// If you want to disable this, set no-debuginfo-compression to true.
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub no_debuginfo_compression: Option<bool>,
670
671    /// This is a dictionary defining for each arch a separate build options object that override the main one.
672    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
673    pub arch: BTreeMap<String, FlatpakBuildOptions>,
674}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679
680    #[test]
681    pub fn test_parse_build_options() {
682        let module_manifest = r###"
683            name: "flatpak-rs"
684            buildsystem: simple
685            cleanup: [ "*" ]
686            build-options:
687               cflags: "-O2 -g"
688               cxxflags: "-O2 -g"
689               env:
690                   V: "1"
691                   X: "2"
692               arch:
693                   x86_64:
694                       cflags: "-O3 -g"
695            config-opts: []
696            sources:
697              -
698                "shared-modules/linux-audio/lv2.json"
699        "###;
700        match FlatpakModule::parse(FlatpakManifestFormat::YAML, module_manifest) {
701            Err(e) => std::panic::panic_any(e),
702            Ok(module) => {
703                assert_eq!(module.name, "flatpak-rs");
704                assert!(module.build_options.is_some());
705                let env: BTreeMap<String, String> = match module.build_options.unwrap().env {
706                    FlatpakBuildOptionsEnv::Dict(env) => env,
707                    FlatpakBuildOptionsEnv::Array(env) => panic!("Env should be a dict."),
708                };
709                assert_eq!(env.get("V").unwrap(), "1");
710                assert_eq!(env.get("X").unwrap(), "2");
711            }
712        }
713    }
714
715    #[test]
716    pub fn test_parse_build_options_env() {
717        let module_manifest = r###"
718            name: "flatpak-rs"
719            buildsystem: simple
720            cleanup: [ "*" ]
721            build-options:
722               cflags: "-O2 -g"
723               cxxflags: "-O2 -g"
724               env:
725                   - "V=1"
726                   - "Y=2"
727               arch:
728                   x86_64:
729                       cflags: "-O3 -g"
730            config-opts: []
731            sources:
732              -
733                "shared-modules/linux-audio/lv2.json"
734        "###;
735        match FlatpakModule::parse(FlatpakManifestFormat::YAML, module_manifest) {
736            Err(e) => std::panic::panic_any(e),
737            Ok(module) => {
738                assert_eq!(module.name, "flatpak-rs");
739                assert!(module.build_options.is_some());
740                let env: Vec<String> = match module.build_options.unwrap().env {
741                    FlatpakBuildOptionsEnv::Array(env) => env,
742                    FlatpakBuildOptionsEnv::Dict(env) => panic!("Env should be an array."),
743                };
744                assert_eq!(env, vec!["V=1", "Y=2"]);
745            }
746        }
747    }
748
749    #[test]
750    #[ignore]
751    pub fn test_parse_builddir() {
752        // FIXME why is this failing?
753        // yes should be a valid boolean value, no?
754        let module_manifest = r###"
755            name: fmt
756            buildsystem: cmake-ninja
757            builddir: yes
758            config-opts:
759              - "-DFMT_TEST=OFF"
760            sources:
761              - type: git
762                url: https://github.com/fmtlib/fmt.git
763                commit: 561834650aa77ba37b15f7e5c9d5726be5127df9
764        "###;
765        match FlatpakModule::parse(FlatpakManifestFormat::YAML, module_manifest) {
766            Err(e) => std::panic::panic_any(e),
767            Ok(module) => {
768                assert_eq!(module.name, "fmt");
769            }
770        }
771    }
772
773    #[test]
774    pub fn test_parse_extra_data() {
775        let module_manifest = r###"
776            name: wps
777            buildsystem: simple
778            build-commands:
779              - install -Dm755 apply_extra.sh /app/bin/apply_extra
780              - install -Dm755 wps.sh /app/bin/wps
781              - ln -s wps /app/bin/et
782              - ln -s wps /app/bin/wpp
783              - ln -s wps /app/bin/wpspdf
784              - install -Dm644 ${FLATPAK_ID}.metainfo.xml -t /app/share/metainfo/
785              - install -Dm755 /usr/bin/desktop-file-edit -t /app/bin/
786              - install -Dm755 /usr/bin/ar -t /app/bin/
787              - install -Dm755 /usr/lib/$(gcc -print-multiarch)/libbfd-*.so -t /app/lib/
788            sources:
789              - type: file
790                path: apply_extra.sh
791
792              - type: file
793                path: com.wps.Office.metainfo.xml
794
795              - type: file
796                path: wps.sh
797
798              - type: extra-data
799                filename: wps-office.deb
800                only-arches:
801                  - x86_64
802                url: https://wdl1.pcfg.cache.wpscdn.com/wps-office_11.1.0.10702.XA_amd64.deb
803                sha256: 390a8b358aaccdfda54740d10d5306c2543c5cd42a7a8fd5c776ccff38492992
804                size: 275210770
805                installed-size: 988671247
806                x-checker-data:
807                  type: html
808                  url: https://linux.wps.com/js/meta.js
809                  version-pattern: version\s*=\s*"([\d.-]+)"
810                  url-pattern: download_link_deb\s*=\s*"(http[s]?://[\w\d$-_@.&+]+)"
811        "###;
812        let module = FlatpakModule::parse(FlatpakManifestFormat::YAML, module_manifest).unwrap();
813        assert_eq!(module.name, "wps");
814    }
815
816    #[test]
817    pub fn test_parse_helm_files() {
818        let helm_file = r###"
819            name: wps
820            sources:
821              - "https://github.com/user/repo"
822        "###;
823        assert!(FlatpakModule::parse(FlatpakManifestFormat::YAML, helm_file,).is_err())
824    }
825
826    #[test]
827    pub fn test_parse_invalid_source() {
828        let file = r###"
829            name: wps
830            sources:
831              - path: "^empty\\.c$"
832                isGenerated: false
833                sourceGroupName: "Source Files",
834                compileGroupLanguage: C
835        "###;
836        assert!(FlatpakModule::parse(FlatpakManifestFormat::YAML, file).is_err())
837    }
838
839    #[test]
840    pub fn test_parse_no_buildsystem() {
841        let module_manifest = r###"
842            name: dbus-glib
843            sources:
844              - type: archive
845                url: "https://dbus.freedesktop.org/releases/dbus-glib/dbus-glib-0.110.tar.gz"
846                sha256: 7ce4760cf66c69148f6bd6c92feaabb8812dee30846b24cd0f7395c436d7e825
847            config-opts:
848              - "--disable-static"
849              - "--disable-gtk-doc"
850            cleanup:
851              - "*.la"
852              - /bin
853              - /etc
854              - /include
855              - /libexec
856              - /share/gtk-doc
857              - /share/man
858        "###;
859        let flatpak_application = FlatpakModule::parse(FlatpakManifestFormat::YAML, module_manifest).unwrap();
860        assert!(flatpak_application.buildsystem.is_none());
861
862        let application_dump = flatpak_application.dump().unwrap();
863        assert!(!application_dump.contains("buildsystem"))
864    }
865}