|
1 | | -//! Installer logic for the edgeup toolchain manager. |
| 1 | +//! Thin CLI wrapper re-exporting from [`edgeup_lib`]. |
2 | 2 |
|
3 | | -use std::{fs, path::PathBuf}; |
4 | | - |
5 | | -use anyhow::{anyhow, Result}; |
6 | | -use serde::Deserialize; |
7 | | - |
8 | | -use crate::shell::Shell; |
9 | | - |
10 | | -const GITHUB_REPO: &str = "refcell/edge-rs"; |
11 | | - |
12 | | -/// Release information from GitHub API |
13 | | -#[derive(Debug, Deserialize)] |
14 | | -pub struct GithubRelease { |
15 | | - /// The tag name for the release (e.g. "v0.1.6") |
16 | | - pub tag_name: String, |
17 | | - /// Assets attached to the release |
18 | | - pub assets: Vec<GithubAsset>, |
19 | | -} |
20 | | - |
21 | | -/// A single asset attached to a GitHub release |
22 | | -#[derive(Debug, Deserialize)] |
23 | | -pub struct GithubAsset { |
24 | | - /// The filename of the asset |
25 | | - pub name: String, |
26 | | - /// Direct download URL for the asset |
27 | | - pub browser_download_url: String, |
28 | | -} |
29 | | - |
30 | | -/// Return the platform suffix matching the current OS and architecture. |
31 | | -/// |
32 | | -/// These correspond to the Rust target triples used in the release workflow. |
33 | | -fn platform_suffix() -> &'static str { |
34 | | - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] |
35 | | - { |
36 | | - "x86_64-unknown-linux-gnu" |
37 | | - } |
38 | | - #[cfg(all(target_os = "macos", target_arch = "x86_64"))] |
39 | | - { |
40 | | - "x86_64-apple-darwin" |
41 | | - } |
42 | | - #[cfg(all(target_os = "macos", target_arch = "aarch64"))] |
43 | | - { |
44 | | - "aarch64-apple-darwin" |
45 | | - } |
46 | | - #[cfg(not(any( |
47 | | - all(target_os = "linux", target_arch = "x86_64"), |
48 | | - all(target_os = "macos", target_arch = "x86_64"), |
49 | | - all(target_os = "macos", target_arch = "aarch64"), |
50 | | - )))] |
51 | | - { |
52 | | - compile_error!("unsupported platform"); |
53 | | - } |
54 | | -} |
55 | | - |
56 | | -/// Fetch release metadata from GitHub. |
57 | | -/// |
58 | | -/// Pass `None` or `Some("latest")` to get the latest release, |
59 | | -/// or `Some("v0.1.6")` for a specific tag. |
60 | | -fn fetch_release(version: Option<&str>) -> Result<GithubRelease> { |
61 | | - let url = match version { |
62 | | - None | Some("latest") => { |
63 | | - format!("https://api.github.com/repos/{GITHUB_REPO}/releases/latest") |
64 | | - } |
65 | | - Some(v) => { |
66 | | - format!("https://api.github.com/repos/{GITHUB_REPO}/releases/tags/{v}") |
67 | | - } |
68 | | - }; |
69 | | - let client = reqwest::blocking::Client::builder() |
70 | | - .user_agent(concat!("edgeup/", env!("CARGO_PKG_VERSION"))) |
71 | | - .build()?; |
72 | | - let resp = client.get(&url).send()?.error_for_status()?; |
73 | | - Ok(resp.json::<GithubRelease>()?) |
74 | | -} |
75 | | - |
76 | | -/// Download the raw bytes of a release asset. |
77 | | -fn download_asset(url: &str) -> Result<Vec<u8>> { |
78 | | - let client = reqwest::blocking::Client::builder() |
79 | | - .user_agent(concat!("edgeup/", env!("CARGO_PKG_VERSION"))) |
80 | | - .build()?; |
81 | | - let bytes = client.get(url).send()?.error_for_status()?.bytes()?; |
82 | | - Ok(bytes.to_vec()) |
83 | | -} |
84 | | - |
85 | | -/// Installer for managing Edge toolchain versions |
86 | | -pub struct Installer { |
87 | | - /// Installation directory (~/.edgeup) |
88 | | - install_dir: PathBuf, |
89 | | -} |
90 | | - |
91 | | -impl Installer { |
92 | | - /// Create a new installer instance |
93 | | - pub fn new() -> Result<Self> { |
94 | | - let install_dir = dirs::home_dir() |
95 | | - .ok_or_else(|| anyhow!("Could not determine home directory"))? |
96 | | - .join(".edgeup"); |
97 | | - |
98 | | - // Create install directory if it doesn't exist |
99 | | - fs::create_dir_all(&install_dir)?; |
100 | | - |
101 | | - Ok(Self { install_dir }) |
102 | | - } |
103 | | - |
104 | | - /// Get the bin directory (~/.edgeup/bin) |
105 | | - fn bin_dir(&self) -> PathBuf { |
106 | | - self.install_dir.join("bin") |
107 | | - } |
108 | | - |
109 | | - /// Get the versions directory (~/.edgeup/versions) |
110 | | - fn versions_dir(&self) -> PathBuf { |
111 | | - self.install_dir.join("versions") |
112 | | - } |
113 | | - |
114 | | - /// Get the path to the edgec binary |
115 | | - fn edgec_bin(&self) -> PathBuf { |
116 | | - self.bin_dir().join("edgec") |
117 | | - } |
118 | | - |
119 | | - /// Install a version of the Edge toolchain |
120 | | - pub fn install(&self, version: Option<String>) -> Result<()> { |
121 | | - let query = version.as_deref(); |
122 | | - eprintln!( |
123 | | - "Installing Edge toolchain (version: {})...", |
124 | | - query.unwrap_or("latest") |
125 | | - ); |
126 | | - |
127 | | - let release = fetch_release(query)?; |
128 | | - let suffix = platform_suffix(); |
129 | | - |
130 | | - // Find the asset whose name contains the platform suffix |
131 | | - let asset = release |
132 | | - .assets |
133 | | - .iter() |
134 | | - .find(|a| a.name.contains(suffix)) |
135 | | - .ok_or_else(|| { |
136 | | - anyhow!( |
137 | | - "no release asset found for platform {suffix} in release {}", |
138 | | - release.tag_name |
139 | | - ) |
140 | | - })?; |
141 | | - |
142 | | - eprintln!("Downloading {}...", asset.name); |
143 | | - let bytes = download_asset(&asset.browser_download_url)?; |
144 | | - |
145 | | - // Write binary to ~/.edgeup/versions/{tag_name}/edgec |
146 | | - let version_dir = self.versions_dir().join(&release.tag_name); |
147 | | - fs::create_dir_all(&version_dir)?; |
148 | | - |
149 | | - let binary_path = version_dir.join("edgec"); |
150 | | - fs::write(&binary_path, &bytes)?; |
151 | | - |
152 | | - // Make binary executable on Unix |
153 | | - #[cfg(unix)] |
154 | | - { |
155 | | - use std::os::unix::fs::PermissionsExt; |
156 | | - fs::set_permissions(&binary_path, fs::Permissions::from_mode(0o755))?; |
157 | | - } |
158 | | - |
159 | | - // Point the symlink at this version |
160 | | - self.use_version(&release.tag_name)?; |
161 | | - |
162 | | - // Ensure PATH includes the bin directory |
163 | | - let shell = Shell::detect()?; |
164 | | - shell.add_to_path(&self.bin_dir())?; |
165 | | - |
166 | | - eprintln!("Installation complete!"); |
167 | | - eprintln!( |
168 | | - "To start using Edge toolchain, run: source {}", |
169 | | - shell.rc_file().display() |
170 | | - ); |
171 | | - |
172 | | - Ok(()) |
173 | | - } |
174 | | - |
175 | | - /// Update to the latest version |
176 | | - pub fn update(&self) -> Result<()> { |
177 | | - self.install(None) |
178 | | - } |
179 | | - |
180 | | - /// List installed versions |
181 | | - pub fn list(&self) -> Result<()> { |
182 | | - let versions_dir = self.versions_dir(); |
183 | | - |
184 | | - if !versions_dir.exists() { |
185 | | - println!("No versions installed yet."); |
186 | | - return Ok(()); |
187 | | - } |
188 | | - |
189 | | - println!("Installed versions:"); |
190 | | - for entry in fs::read_dir(&versions_dir)? { |
191 | | - let entry = entry?; |
192 | | - let path = entry.path(); |
193 | | - if path.is_dir() { |
194 | | - if let Some(version) = path.file_name().and_then(|n| n.to_str()) { |
195 | | - println!(" - {}", version); |
196 | | - } |
197 | | - } |
198 | | - } |
199 | | - |
200 | | - Ok(()) |
201 | | - } |
202 | | - |
203 | | - /// Switch to a specific installed version |
204 | | - pub fn use_version(&self, version: &str) -> Result<()> { |
205 | | - let version_dir = self.versions_dir().join(version); |
206 | | - |
207 | | - if !version_dir.exists() { |
208 | | - return Err(anyhow!("Version {} is not installed", version)); |
209 | | - } |
210 | | - |
211 | | - eprintln!("Switching to version {}...", version); |
212 | | - |
213 | | - // Create symlink from ~/.edgeup/bin/edgec to the selected version |
214 | | - let bin_dir = self.bin_dir(); |
215 | | - fs::create_dir_all(&bin_dir)?; |
216 | | - |
217 | | - let edgec_bin = self.edgec_bin(); |
218 | | - if edgec_bin.exists() { |
219 | | - fs::remove_file(&edgec_bin)?; |
220 | | - } |
221 | | - |
222 | | - let version_bin = version_dir.join("edgec"); |
223 | | - #[cfg(unix)] |
224 | | - std::os::unix::fs::symlink(&version_bin, &edgec_bin)?; |
225 | | - #[cfg(windows)] |
226 | | - std::os::windows::fs::symlink_file(&version_bin, &edgec_bin)?; |
227 | | - |
228 | | - eprintln!("Switched to version {}", version); |
229 | | - Ok(()) |
230 | | - } |
231 | | - |
232 | | - /// Uninstall a version (or all if not specified) |
233 | | - pub fn uninstall(&self, version: Option<String>) -> Result<()> { |
234 | | - match version { |
235 | | - Some(v) => { |
236 | | - let version_dir = self.versions_dir().join(&v); |
237 | | - if !version_dir.exists() { |
238 | | - return Err(anyhow!("Version {} is not installed", v)); |
239 | | - } |
240 | | - eprintln!("Uninstalling version {}...", v); |
241 | | - fs::remove_dir_all(&version_dir)?; |
242 | | - eprintln!("Uninstalled version {}", v); |
243 | | - } |
244 | | - None => { |
245 | | - eprintln!("Uninstalling all versions..."); |
246 | | - let versions_dir = self.versions_dir(); |
247 | | - if versions_dir.exists() { |
248 | | - fs::remove_dir_all(&versions_dir)?; |
249 | | - } |
250 | | - eprintln!("All versions uninstalled"); |
251 | | - } |
252 | | - } |
253 | | - Ok(()) |
254 | | - } |
255 | | - |
256 | | - /// Update edgeup itself |
257 | | - pub fn self_update(&self) -> Result<()> { |
258 | | - eprintln!("Updating edgeup..."); |
259 | | - |
260 | | - let release = fetch_release(None)?; |
261 | | - let suffix = platform_suffix(); |
262 | | - let target_name = format!("edgeup-{suffix}"); |
263 | | - |
264 | | - // Find the edgeup asset for this platform |
265 | | - let asset = release |
266 | | - .assets |
267 | | - .iter() |
268 | | - .find(|a| a.name.contains(&target_name)) |
269 | | - .ok_or_else(|| { |
270 | | - anyhow!( |
271 | | - "no edgeup asset found for platform {suffix} in release {}", |
272 | | - release.tag_name |
273 | | - ) |
274 | | - })?; |
275 | | - |
276 | | - eprintln!("Downloading {}...", asset.name); |
277 | | - let bytes = download_asset(&asset.browser_download_url)?; |
278 | | - |
279 | | - // Atomically replace the current executable: |
280 | | - // write to a temp file next to the exe, then rename. |
281 | | - let current_exe = std::env::current_exe()?; |
282 | | - let temp_path = current_exe.with_extension("tmp"); |
283 | | - fs::write(&temp_path, &bytes)?; |
284 | | - |
285 | | - #[cfg(unix)] |
286 | | - { |
287 | | - use std::os::unix::fs::PermissionsExt; |
288 | | - fs::set_permissions(&temp_path, fs::Permissions::from_mode(0o755))?; |
289 | | - } |
290 | | - |
291 | | - fs::rename(&temp_path, ¤t_exe)?; |
292 | | - |
293 | | - eprintln!("edgeup updated successfully!"); |
294 | | - Ok(()) |
295 | | - } |
296 | | -} |
| 3 | +pub use edgeup_lib::Installer; |
0 commit comments