xtask_lib/tasks/
bundle.rs1use std::path::Path;
23
24use anyhow::{Context, Result, bail};
25use clap::Args;
26
27use super::util::{fs, github, http, workspace};
28pub const NICKEL_VERSION: &str = "0.6.0";
30
31#[derive(Debug, Args)]
33pub struct BundleArgs {
34 #[arg(long)]
36 pub no_nickel: bool,
37
38 #[arg(long)]
40 pub test: bool,
41
42 #[arg(long)]
44 pub skip_download: bool,
45}
46
47pub fn run(args: BundleArgs) -> Result<()> {
54 let root = workspace::root()?;
55
56 let dist_dir = root.join("dist");
57 if !dist_dir.exists() {
58 bail!("dist/ not found. Run `cargo xtask dist` first.");
59 }
60
61 let bundle_dir = root.join("bundle");
62 if bundle_dir.exists() {
63 std::fs::remove_dir_all(&bundle_dir).context("failed to remove existing bundle/")?;
64 }
65
66 if args.no_nickel {
67 create_bundle_cadmus_only(&root, args.test)?;
68 } else {
69 let archive = ensure_nickel_menu(&root, args.skip_download)?;
70 create_bundle_with_nickel(&root, &archive, args.test)?;
71 }
72
73 Ok(())
74}
75
76fn ensure_nickel_menu(root: &Path, skip_download: bool) -> Result<std::path::PathBuf> {
78 let cache_dir = root.join(".cache/nickelmenu");
79 let archive = cache_dir.join(format!("NickelMenu-{NICKEL_VERSION}-KoboRoot.tgz"));
80
81 if archive.exists() {
82 println!("Using cached NickelMenu v{NICKEL_VERSION}");
83 return Ok(archive);
84 }
85
86 if skip_download {
87 bail!(
88 "NickelMenu archive not found at {}.\n\
89 Remove --skip-download to auto-download.",
90 archive.display()
91 );
92 }
93
94 download_nickel_menu(&cache_dir, &archive)?;
95 Ok(archive)
96}
97
98fn download_nickel_menu(cache_dir: &Path, archive: &Path) -> Result<()> {
100 std::fs::create_dir_all(cache_dir)?;
101
102 println!("Downloading NickelMenu v{NICKEL_VERSION}…");
103
104 let asset = github::fetch_release_asset(
105 "pgaskin/NickelMenu",
106 &format!("v{NICKEL_VERSION}"),
107 "KoboRoot.tgz",
108 )?;
109
110 match asset.sha256() {
111 Some(expected) => {
112 http::download_verified(&asset.browser_download_url, archive, expected)?;
113 }
114 None => {
115 http::download(&asset.browser_download_url, archive)
116 .context("failed to download NickelMenu archive")?;
117 }
118 }
119
120 println!("Downloaded NickelMenu to {}", archive.display());
121 Ok(())
122}
123
124fn create_bundle_cadmus_only(root: &Path, test: bool) -> Result<()> {
126 let bundle_dir = root.join("bundle");
127 let (adds_subdir, archive_name) = if test {
128 ("cadmus-tst", "KoboRoot-test.tgz")
129 } else {
130 ("cadmus", "KoboRoot.tgz")
131 };
132
133 let install_dir = bundle_dir.join("mnt/onboard/.adds").join(adds_subdir);
134 std::fs::create_dir_all(&install_dir)?;
135
136 fs::copy_dir_all(&root.join("dist"), &install_dir)?;
137
138 let archive = bundle_dir.join(archive_name);
139 fs::create_tarball(&archive, &bundle_dir, &["mnt"])?;
140
141 std::fs::remove_dir_all(bundle_dir.join("mnt"))?;
142
143 println!("Bundle created: bundle/{archive_name}");
144 println!("Place this file in the .kobo directory on your Kobo device");
145 Ok(())
146}
147
148fn create_bundle_with_nickel(root: &Path, nickel_archive: &Path, test: bool) -> Result<()> {
150 let bundle_dir = root.join("bundle");
151 std::fs::create_dir_all(&bundle_dir)?;
152
153 fs::extract_tarball(nickel_archive, &bundle_dir)?;
154
155 let adds_src = bundle_dir.join("mnt/onboard/.adds");
156 let adds_dst = bundle_dir.join(".adds");
157 std::fs::rename(&adds_src, &adds_dst)?;
158 std::fs::remove_dir_all(bundle_dir.join("mnt"))?;
159
160 let (adds_subdir, nm_config, archive_name) = if test {
161 ("cadmus-tst", "cadmus-tst", "KoboRoot-nm-test.tgz")
162 } else {
163 ("cadmus", "cadmus", "KoboRoot-nm.tgz")
164 };
165
166 fs::copy_dir_all(&root.join("dist"), &adds_dst.join(adds_subdir))?;
167
168 std::fs::copy(
169 root.join(format!("contrib/NickelMenu/{nm_config}")),
170 adds_dst.join(format!("nm/{nm_config}")),
171 )?;
172
173 let final_adds = bundle_dir.join("mnt/onboard/.adds");
174 std::fs::create_dir_all(bundle_dir.join("mnt/onboard"))?;
175 std::fs::rename(&adds_dst, &final_adds)?;
176
177 let archive = bundle_dir.join(archive_name);
178 fs::create_tarball(&archive, &bundle_dir, &["usr", "mnt"])?;
179
180 std::fs::remove_dir_all(bundle_dir.join("usr")).ok();
181 std::fs::remove_dir_all(bundle_dir.join("mnt"))?;
182
183 println!("Bundle created: bundle/{archive_name}");
184 println!("Place this file in the .kobo directory on your Kobo device");
185 Ok(())
186}