cargo_flutter/package/
appimage.rs

1use crate::cargo::Cargo;
2use crate::package::Package;
3use failure::Error;
4use serde::Deserialize;
5use std::fs::Permissions;
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8use std::path::PathBuf;
9use std::process::Command;
10
11#[derive(Debug, Default, Clone, Deserialize)]
12pub struct TomlAppImage {
13    name: Option<String>,
14    icon: Option<String>,
15}
16
17pub struct AppImage {
18    toml: TomlAppImage,
19}
20
21impl AppImage {
22    pub fn new(toml: TomlAppImage) -> Self {
23        Self { toml }
24    }
25
26    #[cfg(not(unix))]
27    pub fn build(&self, cargo: &Cargo, package: &Package, sign: bool) -> Result<(), Error> {
28        Err(failure::format_err!("Creating appimages only supported from a unix host.").into())
29    }
30
31    #[cfg(unix)]
32    pub fn build(&self, cargo: &Cargo, package: &Package, sign: bool) -> Result<(), Error> {
33        let build_dir = cargo.build_dir();
34        let appimage_dir = build_dir.join("appimage");
35        let name = self.toml.name.as_ref().unwrap_or(&package.name);
36        let exec = &package.name;
37        let icon_path = self
38            .toml
39            .icon
40            .as_ref()
41            .map(PathBuf::from)
42            .unwrap_or_else(|| cargo.workspace().root().join("assets").join("icon.svg"));
43        if !icon_path.exists() {
44            return Err(failure::format_err!(
45                "Icon not found {}",
46                icon_path.display()
47            ));
48        }
49        let icon = icon_path
50            .file_stem()
51            .map(|f| f.to_str().unwrap())
52            .unwrap_or("icon")
53            .to_string();
54        std::fs::remove_dir_all(&appimage_dir).ok();
55
56        let bin_dir = appimage_dir.join("usr").join("bin");
57        std::fs::create_dir_all(&bin_dir)?;
58        for bin in package.bins() {
59            std::fs::copy(bin.path(), bin_dir.join(bin.name()))?;
60        }
61
62        let lib_dir = appimage_dir.join("usr").join("lib");
63        std::fs::create_dir_all(&lib_dir)?;
64        for lib in package.libs() {
65            std::fs::copy(lib.path(), lib_dir.join(lib.name()))?;
66        }
67
68        let asset_dir = appimage_dir.join("usr").join("share");
69        std::fs::create_dir_all(&asset_dir)?;
70        for asset in package.assets() {
71            copy_dir::copy_dir(asset.path(), asset_dir.join(asset.name()))?;
72        }
73
74        let apprun = appimage_dir.join("AppRun");
75        std::fs::write(&apprun, APP_RUN)?;
76        std::fs::set_permissions(&apprun, Permissions::from_mode(0o755))?;
77
78        let desktop = appimage_dir.join(format!("{}.desktop", exec));
79        std::fs::write(&desktop, gen_desktop(name, exec, &icon))?;
80        std::fs::set_permissions(&desktop, Permissions::from_mode(0o755))?;
81
82        std::fs::copy(
83            &icon_path,
84            appimage_dir.join(icon_path.file_name().unwrap()),
85        )?;
86
87        let appimagetool = which::which("appimagetool")
88            .or_else(|_| Err(failure::format_err!("appimagetool not found")))?;
89        let mut cmd = Command::new(appimagetool);
90        cmd.current_dir(&build_dir).arg("appimage");
91        if sign {
92            cmd.arg("--sign");
93        }
94        cmd.status().expect("Success");
95
96        Ok(())
97    }
98}
99
100const APP_RUN: &str = r#"#!/bin/sh
101SELF=$(readlink -f "$0")
102HERE=${SELF%/*}
103export PATH="${HERE}/usr/bin/${PATH:+:$PATH}"
104export LD_LIBRARY_PATH="${HERE}/usr/lib/:${LD_LIBRARY_PATH:+:$LDLIBRARY_PATH}"
105export FLUTTER_ASSET_DIR="${HERE}/usr/share/flutter_assets"
106export FLUTTER_AOT_SNAPSHOT="${HERE}/usr/lib/app.so"
107EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
108exec "${EXEC}" "$@"
109"#;
110
111fn gen_desktop(name: &str, exec: &str, icon: &str) -> String {
112    format!(
113        r#"[Desktop Entry]
114Name={}
115Exec={}
116Icon={}
117Type=Application
118Categories=Utility;
119"#,
120        name, exec, icon
121    )
122}