1use std::collections::BTreeMap;
2use std::fs;
3use std::path;
4
5use serde::{Deserialize, Serialize};
6
7use crate::format::FlatpakManifestFormat;
8use crate::module::{FlatpakBuildOptions, FlatpakModule, FlatpakModuleItem};
9use crate::source::FlatpakSourceType;
10
11#[derive(Clone)]
14#[derive(Deserialize)]
15#[derive(Serialize)]
16#[derive(Debug)]
17#[derive(Hash)]
18#[derive(Default)]
19#[serde(rename_all = "kebab-case")]
20#[serde(default)]
21pub struct FlatpakApplication {
22 #[serde(skip_serializing)]
23 pub format: FlatpakManifestFormat,
24
25 #[serde(skip_serializing_if = "String::is_empty")]
27 pub app_name: String,
28
29 #[serde(skip_serializing_if = "String::is_empty")]
32 pub app_id: String,
33 #[serde(skip_serializing_if = "String::is_empty")]
34 pub id: String,
35
36 #[serde(skip_serializing_if = "String::is_empty")]
44 pub branch: String,
45
46 #[serde(skip_serializing_if = "String::is_empty")]
49 pub default_branch: String,
50
51 #[serde(skip_serializing_if = "String::is_empty")]
57 pub collection_id: String,
58
59 #[serde(skip_serializing_if = "String::is_empty")]
61 pub runtime: String,
62
63 #[serde(skip_serializing_if = "String::is_empty")]
65 pub runtime_version: String,
66
67 #[serde(skip_serializing_if = "String::is_empty")]
69 pub sdk: String,
70
71 #[serde(skip_serializing_if = "Vec::is_empty")]
73 pub sdk_extensions: Vec<String>,
74
75 #[serde(skip_serializing_if = "String::is_empty")]
77 pub var: String,
78
79 #[serde(skip_serializing_if = "String::is_empty")]
81 pub metadata: String,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub build_runtime: Option<bool>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
91 pub build_extension: Option<bool>,
92
93 #[serde(skip_serializing_if = "String::is_empty")]
96 pub base: String,
97
98 #[serde(skip_serializing_if = "String::is_empty")]
101 pub base_version: String,
102
103 #[serde(skip_serializing_if = "Vec::is_empty")]
106 pub base_extensions: Vec<String>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub separate_locales: Option<bool>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub appstream_compose: Option<bool>,
115
116 #[serde(skip_serializing_if = "Vec::is_empty")]
119 pub inherit_extensions: Vec<String>,
120
121 #[serde(skip_serializing_if = "Vec::is_empty")]
124 pub inherit_sdk_extensions: Vec<String>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
129 pub build_options: Option<FlatpakBuildOptions>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub command: Option<String>,
134
135 #[serde(skip_serializing_if = "Vec::is_empty")]
137 pub tags: Vec<String>,
138
139 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
142 pub add_extensions: BTreeMap<String, FlatpakExtension>,
143
144 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
148 pub add_build_extensions: BTreeMap<String, FlatpakExtension>,
149
150 #[serde(skip_serializing_if = "Vec::is_empty")]
154 pub cleanup: Vec<String>,
155
156 #[serde(skip_serializing_if = "Vec::is_empty")]
158 pub cleanup_commands: Vec<String>,
159
160 #[serde(skip_serializing_if = "Vec::is_empty")]
162 pub cleanup_platform: Vec<String>,
163
164 #[serde(skip_serializing_if = "Vec::is_empty")]
166 pub cleanup_platform_commands: Vec<String>,
167
168 #[serde(skip_serializing_if = "Vec::is_empty")]
172 pub prepare_platform_commands: Vec<String>,
173
174 #[serde(skip_serializing_if = "Vec::is_empty")]
176 pub finish_args: Vec<String>,
177
178 #[serde(skip_serializing_if = "String::is_empty")]
181 pub rename_desktop_file: String,
182
183 #[serde(skip_serializing_if = "String::is_empty")]
186 pub rename_appdata_file: String,
187
188 #[serde(skip_serializing_if = "String::is_empty")]
192 pub rename_icon: String,
193
194 #[serde(skip_serializing_if = "String::is_empty")]
199 pub appdata_license: String,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub copy_icon: Option<bool>,
204
205 #[serde(skip_serializing_if = "String::is_empty")]
207 pub desktop_file_name_prefix: String,
208
209 #[serde(skip_serializing_if = "String::is_empty")]
211 pub desktop_file_name_suffix: String,
212
213 #[serde(skip_serializing_if = "Vec::is_empty")]
217 pub modules: Vec<FlatpakModuleItem>,
218}
219impl FlatpakApplication {
220 pub fn get_id(&self) -> String {
221 if !self.app_id.is_empty() {
222 return self.app_id.to_string();
223 }
224 return self.id.to_string();
225 }
226
227 pub fn load_from_file(path: String) -> Result<FlatpakApplication, String> {
228 let file_path = path::Path::new(&path);
229 if !file_path.is_file() {
230 return Err(format!("{} is not a file.", path));
231 }
232
233 let manifest_format = match FlatpakManifestFormat::from_path(&path) {
234 Some(f) => f,
235 None => return Err(format!("{} is not a Flatpak application manifest.", path)),
236 };
237
238 let manifest_content = match fs::read_to_string(file_path) {
239 Ok(content) => content,
240 Err(e) => {
241 return Err(format!("Could not read application manifest at {}: {}", &path, e));
242 }
243 };
244 match FlatpakApplication::parse(manifest_format, &manifest_content) {
245 Ok(m) => Ok(m),
246 Err(e) => Err(format!(
247 "Failed to parse Flatpak application manifest at {}: {}",
248 path, e
249 )),
250 }
251 }
252
253 pub fn file_extension_matches(path: &str) -> bool {
254 crate::filename::extension_is_valid(&path)
255 }
256
257 pub fn file_path_matches(path: &str) -> bool {
258 crate::reverse_dns::is_reverse_dns(&path)
259 }
260
261 pub fn parse(format: FlatpakManifestFormat, manifest_content: &str) -> Result<FlatpakApplication, String> {
262 let mut flatpak_manifest: FlatpakApplication = match format.parse(manifest_content) {
263 Ok(m) => m,
264 Err(e) => {
265 return Err(format!(
266 "Failed to parse the Flatpak application manifest: {}.",
267 e
268 ));
269 }
270 };
271 flatpak_manifest.format = format;
272
273 if flatpak_manifest.app_id.is_empty() && flatpak_manifest.id.is_empty() {
279 return Err(
280 "Required top-level field id (or app-id) is missing from Flatpak manifest.".to_string(),
281 );
282 }
283 if flatpak_manifest.runtime.is_empty() {
284 return Err("Required top-level field runtime is missing from Flatpak manifest.".to_string());
285 }
286 if flatpak_manifest.runtime_version.is_empty() {
287 return Err(
288 "Required top-level field runtime-version is missing from Flatpak manifest.".to_string(),
289 );
290 }
291 if flatpak_manifest.sdk.is_empty() {
292 return Err("Required top-level field sdk is missing from Flatpak manifest.".to_string());
293 }
294
295 Ok(flatpak_manifest)
296 }
297
298 pub fn dump(&self) -> Result<String, String> {
299 match self.format.dump(self) {
300 Ok(d) => Ok(d),
301 Err(e) => return Err(format!("Failed to dump the Flatpak manifest: {}.", e)),
302 }
303 }
304
305 pub fn get_urls(
306 &self,
307 include_mirror_urls: bool,
308 include_source_types: Option<Vec<FlatpakSourceType>>,
309 ) -> Vec<String> {
310 let mut urls = vec![];
311 for module in &self.modules {
312 let module: &FlatpakModule = match module {
313 FlatpakModuleItem::Path(_) => continue,
314 FlatpakModuleItem::Description(m) => &m,
315 };
316 urls.append(&mut module.get_urls(include_mirror_urls, include_source_types.clone()));
317 }
318 urls
319 }
320
321 pub fn get_main_module_url(&self) -> Option<String> {
322 let main_module = match self.modules.last() {
323 Some(m) => m,
324 None => return None,
325 };
326 let main_module: &FlatpakModule = match main_module {
327 FlatpakModuleItem::Path(_) => return None,
328 FlatpakModuleItem::Description(m) => m,
329 };
330 return main_module.get_main_url();
331 }
332
333 pub fn get_max_depth(&self) -> i32 {
334 let mut max_depth: i32 = 1;
335 for module in &self.modules {
336 if let FlatpakModuleItem::Description(module_description) = module {
337 let module_depth = module_description.get_max_depth();
338 if module_depth > max_depth {
339 max_depth = module_depth;
340 }
341 }
342 }
343 return max_depth;
344 }
345
346 pub fn is_extension(&self) -> bool {
347 if let Some(e) = self.build_extension {
348 return e;
349 }
350 false
351 }
352
353 pub fn get_all_modules_recursively(&self) -> Vec<&FlatpakModuleItem> {
354 let mut all_modules: Vec<&FlatpakModuleItem> = vec![];
355 for module in &self.modules {
356 all_modules.push(module);
357
358 let module = match module {
359 FlatpakModuleItem::Description(d) => d,
360 FlatpakModuleItem::Path(_) => continue,
361 };
362 for child_module in module.get_all_modules_recursively() {
363 all_modules.push(child_module);
364 }
365 }
366 all_modules
367 }
368}
369
370#[derive(Clone)]
371#[derive(Deserialize)]
372#[derive(Serialize)]
373#[derive(Debug)]
374#[derive(Default)]
375#[derive(Hash)]
376#[serde(rename_all = "kebab-case")]
377#[serde(default)]
378pub struct FlatpakExtension {
386 pub extension_directory: String,
389
390 #[serde(skip_serializing_if = "Option::is_none")]
393 pub bundle: Option<bool>,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
398 pub remove_after_build: Option<bool>,
399
400 pub autodelete: Option<bool>,
403
404 pub no_autodownload: Option<bool>,
407
408 pub subdirectories: Option<bool>,
412
413 pub add_ld_path: Option<String>,
415
416 pub download_if: Option<String>,
430
431 pub enable_if: Option<String>,
434
435 pub merge_dirs: Option<String>,
437
438 pub subdirectory_suffix: Option<String>,
443
444 pub locale_subset: Option<bool>,
448
449 pub version: Option<String>,
453
454 pub versions: Option<String>,
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 #[should_panic]
466 pub fn test_parse_invalid_yaml() {
467 FlatpakApplication::parse(FlatpakManifestFormat::YAML, "----------------------------").unwrap();
468 }
469
470 #[test]
471 pub fn test_parse_missing_fields() {
472 let application_manifest = r###"
473 runtime: org.gnome.Platform
474 runtime-version: "3.36"
475 sdk: org.gnome.Sdk
476 command: flatpak-rs
477 "###;
478 assert!(FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest).is_err());
479 }
480
481 #[test]
482 pub fn test_parse() {
483 let application_manifest = r###"
484 app-id: net.louib.flatpak-rs
485 runtime: org.gnome.Platform
486 runtime-version: "3.36"
487 sdk: org.gnome.Sdk
488 command: flatpak-rs
489 tags: ["nightly"]
490 modules:
491 -
492 name: "flatpak-rs"
493 buildsystem: simple
494 cleanup: [ "*" ]
495 config-opts: []
496 sources:
497 -
498 type: git
499 url: https://github.com/louib/flatpak-rs.git
500 branch: master
501 "###;
502 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
503 Err(e) => std::panic::panic_any(e),
504 Ok(app) => {
505 assert_eq!(app.get_id(), "net.louib.flatpak-rs");
506 }
507 }
508 }
509
510 #[test]
511 pub fn test_parse_external_modules() {
512 let application_manifest = r###"
513 app-id: net.louib.flatpak-rs
514 runtime: org.gnome.Platform
515 runtime-version: "3.36"
516 sdk: org.gnome.Sdk
517 command: flatpak-rs
518 tags: ["nightly"]
519 modules:
520 -
521 name: "flatpak-rs"
522 buildsystem: simple
523 cleanup: [ "*" ]
524 config-opts: []
525 sources:
526 -
527 type: git
528 url: https://github.com/louib/flatpak-rs.git
529 branch: master
530 -
531 "shared-modules/linux-audio/lv2.json"
532 "###;
533 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
534 Err(e) => std::panic::panic_any(e),
535 Ok(app) => {
536 assert_eq!(app.get_id(), "net.louib.flatpak-rs");
537 }
538 }
539 }
540
541 #[test]
542 pub fn test_parse_add_extensions() {
543 let application_manifest = r###"
544 app-id: net.pcsx2.PCSX2
545 runtime: org.freedesktop.Platform
546 runtime-version: "19.08"
547 sdk: org.freedesktop.Sdk
548 command: PCSX2
549 tags: ["nightly"]
550 modules: []
551 add-extensions:
552 "org.freedesktop.Platform.Compat.i386":
553 directory: "lib/i386-linux-gnu"
554 version: "19.08"
555 "org.freedesktop.Platform.Compat.i386.Debug":
556 directory: "lib/debug/lib/i386-linux-gnu"
557 version: "19.08"
558 no-autodownload: true
559 "org.freedesktop.Platform.GL32":
560 directory: "lib/i386-linux-gnu/GL"
561 version: "1.4"
562 versions: "19.08;1.4"
563 subdirectories: true
564 no-autodownload: true
565 autodelete: false
566 add-ld-path: "lib"
567 merge-dirs: "vulkan/icd.d;glvnd/egl_vendor.d"
568 download-if: "active-gl-driver"
569 enable-if: "active-gl-driver"
570 "###;
571 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
572 Err(e) => std::panic::panic_any(e),
573 Ok(app) => {
574 assert_eq!(app.get_id(), "net.pcsx2.PCSX2");
575 assert_eq!(app.add_extensions.len(), 3);
576 }
577 }
578 }
579
580 #[test]
581 pub fn test_parse_string_source() {
582 let application_manifest = r###"
583 app-id: net.louib.flatpak-rs
584 runtime: org.gnome.Platform
585 runtime-version: "3.36"
586 sdk: org.gnome.Sdk
587 command: flatpak-rs
588 tags: ["nightly"]
589 modules:
590 -
591 name: "flatpak-rs"
592 buildsystem: simple
593 cleanup: [ "*" ]
594 config-opts: []
595 sources:
596 -
597 "shared-modules/linux-audio/lv2.json"
598 "###;
599 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
600 Err(e) => std::panic::panic_any(e),
601 Ok(app) => {
602 assert_eq!(app.get_id(), "net.louib.flatpak-rs");
603 }
604 }
605 }
606
607 #[test]
608 pub fn test_parse_source_without_type() {
609 let application_manifest = r###"
610 app-id: net.louib.flatpak-rs
611 runtime: org.gnome.Platform
612 runtime-version: "3.36"
613 sdk: org.gnome.Sdk
614 command: flatpak-rs
615 tags: ["nightly"]
616 modules:
617 -
618 name: "gcc"
619 buildsystem: simple
620 cleanup: [ "*" ]
621 config-opts: []
622 sources:
623 -
624 url: "https://ftp.gnu.org/gnu/gcc/gcc-7.5.0/gcc-7.5.0.tar.xz"
625 sha256: "b81946e7f01f90528a1f7352ab08cc602b9ccc05d4e44da4bd501c5a189ee661"
626
627 "###;
628 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
629 Err(e) => std::panic::panic_any(e),
630 Ok(application) => {
631 assert_eq!(application.get_id(), "net.louib.flatpak-rs");
632 }
633 }
634 }
635
636 #[test]
637 pub fn test_parse_json() {
638 let application_manifest = r###"
639 {
640 "app-id": "org.gnome.SoundJuicer",
641 "runtime": "org.gnome.Platform",
642 "runtime-version": "master",
643 "sdk": "org.gnome.Sdk",
644 "command": "sound-juicer",
645 "tags": [ "nightly" ],
646 "desktop-file-name-suffix": " ☢️",
647 "finish-args": [
648 "--talk-name=org.gtk.vfs", "--talk-name=org.gtk.vfs.*",
649 "--env=GST_PLUGIN_PATH=/app/lib/codecs/lib/gstreamer-1.0"
650 ],
651 "cleanup": [ "/include", "/share/bash-completion" ],
652 "modules": [
653 {
654 "name": "cdparanoia",
655 "buildsystem": "simple",
656 "build-commands": [
657 "cp /usr/share/automake-*/config.{sub,guess} .",
658 "./configure --prefix=/app",
659 "make all slib",
660 "make install"
661 ],
662 "sources": [
663 {
664 "type": "archive",
665 "url": "http://downloads.xiph.org/releases/cdparanoia/cdparanoia-III-10.2.src.tgz",
666 "sha256": "005db45ef4ee017f5c32ec124f913a0546e77014266c6a1c50df902a55fe64df"
667 },
668 {
669 "type": "patch",
670 "path": "cdparanoia-use-proper-gnu-config-files.patch"
671 }
672 ]
673 },
674 {
675 "name": "gst-plugins-base",
676 "buildsystem": "meson",
677 "config-opts": [
678 "--prefix=/app",
679 "-Dauto_features=disabled",
680 "-Dcdparanoia=enabled"
681 ],
682 "cleanup": [ "*.la", "/share/gtk-doc" ],
683 "sources": [
684 {
685 "type": "git",
686 "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git",
687 "branch" : "1.16.2",
688 "commit" : "9d3581b2e6f12f0b7e790d1ebb63b90cf5b1ef4e"
689 }
690 ]
691 }
692 ]
693 }
694 "###;
695 match FlatpakApplication::parse(FlatpakManifestFormat::JSON, application_manifest) {
696 Err(e) => std::panic::panic_any(e),
697 Ok(app) => {
698 assert_eq!(app.get_id(), "org.gnome.SoundJuicer");
699 }
700 }
701 }
702
703 #[test]
704 pub fn test_parse_json_with_comments() {
705 let application_manifest = r###"
706 {
707 "app-id": "org.gnome.SoundJuicer",
708 "runtime": "org.gnome.Platform",
709 "runtime-version": "master",
710 "sdk": "org.gnome.Sdk",
711 "command": "sound-juicer",
712 "tags": [ "nightly" ],
713 "desktop-file-name-suffix": " ☢️",
714 "finish-args": [
715 /* X11 + XShm access */
716 "--share=ipc", "--socket=fallback-x11",
717 /* Wayland access */
718 "--socket=wayland",
719 /* audio CDs */
720 "--device=all",
721 /* Needs to talk to the network */
722 "--share=network",
723 /* Play sounds */
724 "--socket=pulseaudio",
725 /* Browse user's Music directory */
726 "--filesystem=xdg-music",
727 /* Migrate DConf settings from the host */
728 "--metadata=X-DConf=migrate-path=/org/gnome/sound-juicer/",
729 /* optical media detection */
730 "--talk-name=org.gtk.vfs", "--talk-name=org.gtk.vfs.*",
731 /* Ensure cdda gstreamer plugin is picked found for audio CD's */
732 "--env=GST_PLUGIN_PATH=/app/lib/codecs/lib/gstreamer-1.0"
733 ],
734 "cleanup": [ "/include", "/share/bash-completion" ],
735 "modules": [
736 /* gst-plugins-base needs cdparanoia to add support for cdda */
737 {
738 "name": "cdparanoia",
739 "buildsystem": "simple",
740 "build-commands": [
741 "cp /usr/share/automake-*/config.{sub,guess} .",
742 "./configure --prefix=/app",
743 "make all slib",
744 "make install"
745 ],
746 "sources": [
747 {
748 "type": "archive",
749 "url": "http://downloads.xiph.org/releases/cdparanoia/cdparanoia-III-10.2.src.tgz",
750 "sha256": "005db45ef4ee017f5c32ec124f913a0546e77014266c6a1c50df902a55fe64df"
751 },
752 {
753 "type": "patch",
754 "path": "cdparanoia-use-proper-gnu-config-files.patch"
755 }
756 ]
757 },
758 /* To play cdda */
759 {
760 "name": "gst-plugins-base",
761 "buildsystem": "meson",
762 "config-opts": [
763 "--prefix=/app",
764 "-Dauto_features=disabled",
765 "-Dcdparanoia=enabled"
766 ],
767 "cleanup": [ "*.la", "/share/gtk-doc" ],
768 "sources": [
769 {
770 "type": "git",
771 "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git",
772 "branch" : "1.16.2",
773 "commit" : "9d3581b2e6f12f0b7e790d1ebb63b90cf5b1ef4e"
774 }
775 ]
776 }
777 ]
778 }
779 "###;
780 match FlatpakApplication::parse(FlatpakManifestFormat::JSON, application_manifest) {
781 Err(e) => std::panic::panic_any(e),
782 Ok(app) => {
783 assert_eq!(app.get_id(), "org.gnome.SoundJuicer");
784 }
785 }
786 }
787
788 #[test]
789 pub fn test_parse_json_with_multi_line_comments() {
790 let application_manifest = r###"
791 {
792 "app-id": "org.gnome.Lollypop",
793 "runtime": "org.gnome.Platform",
794 "runtime-version": "40",
795 "sdk": "org.gnome.Sdk",
796 "command": "lollypop",
797 "finish-args": [
798 "--share=ipc",
799 "--own-name=org.mpris.MediaPlayer2.Lollypop",
800 "--metadata=X-DConf=migrate-path=/org/gnome/Lollypop/"
801 ],
802 /* FFmpeg-full and gst-plugins-ugly required for .wma support
803 * Due to possible legal stubbornness in the USA, it can't be downloaded automatically
804 */
805 "add-extensions": {
806 "org.freedesktop.Platform.ffmpeg-full": {
807 "directory": "lib/ffmpeg",
808 "version": "20.08",
809 "add-ld-path": ".",
810 "no-autodownload": true,
811 "autodelete": false
812 }
813 },
814 "cleanup-commands": [
815 "mkdir -p /app/lib/ffmpeg"
816 ],
817 "modules": [
818 "pypi-dependencies.json",
819 {
820 "name": "gst-plugins-ugly",
821 "buildsystem": "meson",
822 "cleanup": [
823 "*.la",
824 "/share/gtk-doc"
825 ],
826 "sources": [{
827 "type": "archive",
828 "url": "https://gstreamer.freedesktop.org/src/gst-plugins-ugly/gst-plugins-ugly-1.16.2.tar.xz",
829 "sha256": "5500415b865e8b62775d4742cbb9f37146a50caecfc0e7a6fc0160d3c560fbca"
830 }]
831 }
832 ]
833 }
834 "###;
835 match FlatpakApplication::parse(FlatpakManifestFormat::JSON, application_manifest) {
836 Err(e) => std::panic::panic_any(e),
837 Ok(app) => {
838 assert_eq!(app.get_id(), "org.gnome.Lollypop");
839 }
840 }
841 }
842
843 #[test]
844 pub fn test_parse_extension() {
845 let extension_manifest = r###"
846 {
847 "id": "org.freedesktop.Platform.Icontheme.Paper",
848 "branch": "1.0",
849 "runtime": "org.freedesktop.Sdk",
850 "build-extension": true,
851 "sdk": "org.freedesktop.Sdk",
852 "runtime-version": "1.6",
853 "sdk-extensions": [],
854 "separate-locales": false,
855 "cleanup": [ "/share/info", "/share/man" ],
856 "appstream-compose": false,
857 "build-options" : {
858 "prefix": "/usr/share/runtime"
859 },
860 "modules": []
861 }
862 "###;
863 match FlatpakApplication::parse(FlatpakManifestFormat::JSON, extension_manifest) {
864 Err(e) => std::panic::panic_any(e),
865 Ok(app) => {
866 assert_eq!(app.id, "org.freedesktop.Platform.Icontheme.Paper");
867 assert_eq!(app.get_id(), "org.freedesktop.Platform.Icontheme.Paper");
868 assert_eq!(app.separate_locales, Some(false));
869 assert_eq!(app.appstream_compose, Some(false));
870 }
871 }
872 }
873
874 #[test]
875 pub fn test_parse_recursive_modules() {
876 let application_manifest = r###"
877 app-id: com.georgefb.haruna
878 runtime: org.kde.Platform
879 runtime-version: '5.15'
880 sdk: org.kde.Sdk
881 command: haruna
882 finish-args:
883 - '--share=ipc'
884 - '--share=network'
885 - '--socket=x11'
886 - '--socket=wayland'
887 - '--socket=pulseaudio'
888 - '--device=dri'
889 - '--filesystem=host'
890 - '--talk-name=ca.desrt.dconf'
891 - '--talk-name=org.freedesktop.ScreenSaver'
892 - '--own-name=org.mpris.MediaPlayer2.haruna'
893 - '--env=DCONF_USER_CONFIG_DIR=.config/dconf'
894 - '--env=LC_NUMERIC=C'
895 - '--env=XDG_DATA_DIRS=/usr/share:/app/share/'
896 modules:
897 - name: haruna
898 buildsystem: cmake-ninja
899 sources:
900 - type: archive
901 url: 'https://github.com/g-fb/haruna/archive/refs/tags/0.6.3.tar.gz'
902 sha256: 'c79ec1e351f47faf9a58a6ba7ec3cc05cfdc5423fde0584f2d4081f5058363e3'
903 modules:
904 - name: taglib
905 buildsystem: cmake-ninja
906 config-opts:
907 - '-DBUILD_SHARED_LIBS=ON'
908 sources:
909 - type: archive
910 url: 'https://github.com/taglib/taglib/releases/download/v1.12/taglib-1.12.tar.gz'
911 sha256: '7fccd07669a523b07a15bd24c8da1bbb92206cb19e9366c3692af3d79253b703'
912 - name: ffmpegthumbs
913 buildsystem: cmake-ninja
914 sources:
915 - type: archive
916 url: 'https://invent.kde.org/multimedia/ffmpegthumbs/-/archive/v20.12.3/ffmpegthumbs-v20.12.3.tar.gz'
917 sha256: '9292a503808688b37e45ee336efcd28c39035d49c96e1df466b091f2eaf7a529'
918 - name: kio-extras
919 buildsystem: cmake-ninja
920 sources:
921 - type: archive
922 url: 'https://invent.kde.org/network/kio-extras/-/archive/v20.12.3/kio-extras-v20.12.3.tar.gz'
923 sha256: 'bedccdbf3664f2669270c2864c6f7c0e73237d18fae04067bc21ae5e12716b0b'
924 - name: libmpv
925 cleanup:
926 - /include
927 - /lib/pkgconfig
928 - /share/man
929 buildsystem: simple
930 build-commands:
931 - python3 waf configure --prefix=/app --enable-libmpv-shared --disable-cplayer --disable-build-date --disable-alsa
932 - python3 waf build
933 - python3 waf install
934 sources:
935 - type: archive
936 url: 'https://github.com/mpv-player/mpv/archive/v0.33.1.tar.gz'
937 sha256: '100a116b9f23bdcda3a596e9f26be3a69f166a4f1d00910d1789b6571c46f3a9'
938 - type: file
939 url: 'https://waf.io/waf-2.0.21'
940 sha256: '7cebf2c5efe53cbb9a4b5bdc4b49ae90ecd64a8fce7a3222d58e591b58215306'
941 dest-filename: waf
942 modules:
943 - name: luajit
944 no-autogen: true
945 cleanup:
946 - /bin
947 - /include
948 - /lib/pkgconfig
949 - /share/man
950 sources:
951 - type: archive
952 url: 'http://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz'
953 sha256: '1ad2e34b111c802f9d0cdf019e986909123237a28c746b21295b63c9e785d9c3'
954 - type: shell
955 commands:
956 - sed -i 's|/usr/local|/app|' ./Makefile
957 - name: libv4l2
958 cleanup:
959 - /sbin
960 - /bin
961 - /include
962 - /lib/pkgconfig
963 - /share/man
964 config-opts:
965 - '--disable-static'
966 - '--disable-bpf'
967 - '--with-udevdir=/app/lib/udev'
968 sources:
969 - type: archive
970 url: 'https://linuxtv.org/downloads/v4l-utils/v4l-utils-1.20.0.tar.bz2'
971 sha256: '956118713f7ccb405c55c7088a6a2490c32d54300dd9a30d8d5008c28d3726f7'
972 - name: nv-codec-headers
973 cleanup:
974 - '*'
975 no-autogen: true
976 make-install-args:
977 - PREFIX=/app
978 sources:
979 - type: archive
980 url: 'https://github.com/FFmpeg/nv-codec-headers/releases/download/n11.0.10.0/nv-codec-headers-11.0.10.0.tar.gz'
981 sha256: 'e5d1fe6b18254a3c8747a38714564030e4fda506961a11a7eafb94f2400419bb'
982 - name: ffmpeg
983 cleanup:
984 - /include
985 - /lib/pkgconfig
986 - /share/ffmpeg/examples
987 config-opts:
988 - '--enable-shared'
989 - '--disable-static'
990 - '--enable-gnutls'
991 - '--enable-gpl'
992 - '--disable-doc'
993 - '--disable-programs'
994 - '--disable-encoders'
995 - '--disable-muxers'
996 - '--enable-encoder=png,libwebp'
997 - '--enable-libv4l2'
998 - '--enable-libdav1d'
999 - '--enable-libfontconfig'
1000 - '--enable-libfreetype'
1001 - '--enable-libopus'
1002 - '--enable-librsvg'
1003 - '--enable-libvpx'
1004 - '--enable-libmp3lame'
1005 - '--enable-libwebp'
1006 sources:
1007 - type: archive
1008 url: 'https://ffmpeg.org/releases/ffmpeg-4.3.2.tar.xz'
1009 sha256: '46e4e64f1dd0233cbc0934b9f1c0da676008cad34725113fb7f802cfa84ccddb'
1010 - name: libass
1011 cleanup:
1012 - /include
1013 - /lib/pkgconfig
1014 config-opts:
1015 - '--disable-static'
1016 sources:
1017 - type: archive
1018 url: 'https://github.com/libass/libass/releases/download/0.15.0/libass-0.15.0.tar.gz'
1019 sha256: '9cbddee5e8c87e43a5fe627a19cd2aa4c36552156eb4edcf6c5a30bd4934fe58'
1020 - name: uchardet
1021 buildsystem: cmake-ninja
1022 config-opts:
1023 - '-DCMAKE_BUILD_TYPE=Release'
1024 - '-DBUILD_STATIC=0'
1025 cleanup:
1026 - /bin
1027 - /include
1028 - /lib/pkgconfig
1029 - /share/man
1030 sources:
1031 - type: archive
1032 url: 'https://gitlab.freedesktop.org/uchardet/uchardet/-/archive/v0.0.7/uchardet-v0.0.7.tar.gz'
1033 sha256: 'f3635d1d10e1470452bc42c1bf509451a9926b399a11740a9949e86069d69f58'
1034 - name: youtube-dl
1035 no-autogen: true
1036 no-make-install: true
1037 make-args:
1038 - youtube-dl
1039 - PYTHON=/usr/bin/python3
1040 post-install:
1041 - install youtube-dl /app/bin
1042 sources:
1043 - type: archive
1044 url: 'https://github.com/ytdl-org/youtube-dl/archive/2021.04.26.tar.gz'
1045 sha256: 'd80023ab221b3cb89229b632e247035a22c5afaee9a7b3c653bbd702f71c1083'
1046 "###;
1047 match FlatpakApplication::parse(FlatpakManifestFormat::YAML, application_manifest) {
1048 Err(e) => std::panic::panic_any(e),
1049 Ok(app) => {
1050 assert_eq!(app.get_id(), "com.georgefb.haruna");
1051 assert_eq!(app.get_max_depth(), 3);
1052 assert_eq!(app.modules.len(), 1);
1053 assert_eq!(app.get_all_modules_recursively().len(), 12);
1054 }
1055 }
1056 }
1057}