Skip to content

Commit c167ed1

Browse files
authored
Merge pull request #38 from refcell/feat/edgeup-lib
feat: expose edgeup-lib for programmatic version resolution
2 parents 6fc210f + 903f2c2 commit c167ed1

9 files changed

Lines changed: 551 additions & 303 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ resolver = "2"
44

55
[workspace.package]
66
description = "Edge Language Compiler Workspace"
7-
version = "0.1.18"
7+
version = "0.1.19"
88
edition = "2021"
99
rust-version = "1.85"
1010
license = "MIT"
@@ -64,6 +64,7 @@ edge-ir = { path = "crates/ir" }
6464
edge-codegen = { path = "crates/codegen" }
6565
edge-driver = { path = "crates/driver" }
6666
edge-lsp = { path = "crates/lsp" }
67+
edgeup-lib = { path = "crates/edgeup-lib" }
6768
anyhow = "1.0.79"
6869
inquire = "0.6.2"
6970
tracing = "0.1.40"

bin/edgeup/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ name = "edgeup"
1313
path = "src/main.rs"
1414

1515
[dependencies]
16+
edgeup-lib = { workspace = true }
1617
clap = { workspace = true }
1718
anyhow = { workspace = true }
1819
tracing = { workspace = true }
1920
tracing-subscriber = { workspace = true }
20-
serde = { workspace = true, features = ["derive"] }
21-
serde_json = { workspace = true }
2221
dirs = { workspace = true }
23-
reqwest = { workspace = true }
24-
tokio = { version = "1", features = ["macros", "rt"] }

bin/edgeup/src/cli.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use anyhow::Result;
44
use clap::{Parser, Subcommand};
55

6-
use crate::installer::Installer;
6+
use crate::{installer::Installer, shell::Shell};
77

88
/// The Edge toolchain installer and version manager
99
#[derive(Debug, Parser)]
@@ -49,7 +49,21 @@ impl Cli {
4949
let installer = Installer::new()?;
5050

5151
match self.command {
52-
Commands::Install { version } => installer.install(version),
52+
Commands::Install { version } => {
53+
installer.install(version)?;
54+
// Add the bin directory to PATH in the user's shell RC file.
55+
let shell = Shell::detect()?;
56+
let bin_dir = dirs::home_dir()
57+
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?
58+
.join(".edgeup")
59+
.join("bin");
60+
shell.add_to_path(&bin_dir)?;
61+
eprintln!(
62+
"To start using Edge toolchain, run: source {}",
63+
shell.rc_file().display()
64+
);
65+
Ok(())
66+
}
5367
Commands::Update => installer.update(),
5468
Commands::List => installer.list(),
5569
Commands::Use { version } => installer.use_version(&version),

bin/edgeup/src/installer.rs

Lines changed: 2 additions & 295 deletions
Original file line numberDiff line numberDiff line change
@@ -1,296 +1,3 @@
1-
//! Installer logic for the edgeup toolchain manager.
1+
//! Thin CLI wrapper re-exporting from [`edgeup_lib`].
22
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, &current_exe)?;
292-
293-
eprintln!("edgeup updated successfully!");
294-
Ok(())
295-
}
296-
}
3+
pub use edgeup_lib::Installer;

clippy.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
msrv = "1.83"
1+
msrv = "1.85"

crates/edgeup-lib/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "edgeup-lib"
3+
description = "Programmatic version resolution and management for the Edge toolchain"
4+
version = { workspace = true }
5+
edition = { workspace = true }
6+
authors = { workspace = true }
7+
license = { workspace = true }
8+
repository = { workspace = true }
9+
homepage = { workspace = true }
10+
11+
[lints]
12+
workspace = true
13+
14+
[dependencies]
15+
anyhow = { workspace = true }
16+
serde = { workspace = true, features = ["derive"] }
17+
dirs = { workspace = true }
18+
reqwest = { workspace = true }

0 commit comments

Comments
 (0)