appimage/
lib.rs

1use anyhow::{Context, Result};
2use std::fs::{File, Permissions};
3use std::io::{BufReader, BufWriter, Write};
4#[cfg(unix)]
5use std::os::unix::fs::PermissionsExt;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8use xcommon::Signer;
9
10static RUNTIME: &[u8] = include_bytes!("../assets/runtime-x86_64");
11
12pub struct AppImage {
13    appdir: PathBuf,
14    name: String,
15}
16
17impl AppImage {
18    pub fn new(build_dir: &Path, name: String) -> Result<Self> {
19        let appdir = build_dir.join(format!("{}.AppDir", name));
20        std::fs::remove_dir_all(&appdir).ok();
21        std::fs::create_dir_all(&appdir)?;
22        Ok(Self { appdir, name })
23    }
24
25    pub fn appdir(&self) -> &Path {
26        &self.appdir
27    }
28
29    pub fn add_apprun(&self) -> Result<()> {
30        #[cfg(unix)]
31        std::os::unix::fs::symlink(&self.name, self.appdir.join("AppRun"))?;
32        Ok(())
33    }
34
35    pub fn add_desktop(&self) -> Result<()> {
36        let mut f = File::create(self.appdir.join(format!("{}.desktop", &self.name)))?;
37        writeln!(f, "[Desktop Entry]")?;
38        writeln!(f, "Version=1.0")?;
39        writeln!(f, "Type=Application")?;
40        writeln!(f, "Terminal=false")?;
41        writeln!(f, "Name={}", self.name)?;
42        writeln!(f, "Exec={} %u", self.name)?;
43        writeln!(f, "Icon={}", self.name)?;
44        writeln!(f, "Categories=Utility;")?;
45        Ok(())
46    }
47
48    pub fn add_icon(&self, path: &Path) -> Result<()> {
49        let ext = path
50            .extension()
51            .map(|ext| ext.to_str())
52            .unwrap_or_default()
53            .context("unsupported extension")?;
54        let name = format!("{}.{}", self.name, ext);
55        self.add_file(path, Path::new(&name))?;
56        #[cfg(unix)]
57        std::os::unix::fs::symlink(name, self.appdir.join(".DirIcon"))?;
58        Ok(())
59    }
60
61    pub fn add_file(&self, path: &Path, name: &Path) -> Result<()> {
62        let dest = self.appdir.join(name);
63        if let Some(parent) = dest.parent() {
64            std::fs::create_dir_all(parent)?;
65        }
66        std::fs::copy(path, dest)?;
67        Ok(())
68    }
69
70    pub fn add_directory(&self, source: &Path, dest: &Path) -> Result<()> {
71        let dest = self.appdir.join(dest);
72        std::fs::create_dir_all(&dest)?;
73        xcommon::copy_dir_all(source, &dest)?;
74        Ok(())
75    }
76
77    pub fn build(self, out: &Path, _signer: Option<Signer>) -> Result<()> {
78        let squashfs = self
79            .appdir
80            .parent()
81            .unwrap()
82            .join(format!("{}.squashfs", self.name));
83        let status = Command::new("mksquashfs")
84            .arg(&self.appdir)
85            .arg(&squashfs)
86            .arg("-root-owned")
87            .arg("-noappend")
88            .arg("-quiet")
89            .status()?;
90        anyhow::ensure!(
91            status.success(),
92            "mksquashfs failed with exit code {:?}",
93            status
94        );
95        let mut squashfs = BufReader::new(File::open(squashfs)?);
96        let mut f = File::create(out)?;
97        #[cfg(unix)]
98        f.set_permissions(Permissions::from_mode(0o755))?;
99        let mut out = BufWriter::new(&mut f);
100        out.write_all(RUNTIME)?;
101        std::io::copy(&mut squashfs, &mut out)?;
102        // TODO: sign
103        Ok(())
104    }
105}