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 Ok(())
104 }
105}