Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/cmds/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use clap::Parser;

use crate::{Config, manifest, updates};

#[derive(Parser, Default)]
pub struct Args {
#[arg(short, long)]
pub silent: bool,

#[arg(short, long)]
pub force: bool,

#[arg(short, long)]
pub verbose: bool,
}

fn print_update(update: &updates::Update, manifest: &manifest::Manifest) -> anyhow::Result<()> {
let tool = manifest.tool_by_name(&update.tool).unwrap();

if let Some(current) = update.current()? {
println!("\nYour version of {} needs to be updated 😬", tool.name);
println!(" Current version: {current}");
println!(" Requested version: {}", update.requested);
} else {
println!("\nYour need to install {} 📦", tool.name);
}

Ok(())
}

pub async fn run(args: &Args, config: &Config) -> anyhow::Result<()> {
let manifest = manifest::load_manifest(config, args.force).await?;

let updates = updates::load_updates(&manifest, config, args.force).await?;

if args.silent {
return Ok(());
}

if updates.is_empty() {
println!("You are up to date 🎉");
return Ok(());
}

if !args.verbose {
println!("You have {} update/s to install 📦", updates.len());
} else {
for update in updates {
print_update(&update, &manifest)?;
}
}

Ok(())
}
61 changes: 21 additions & 40 deletions src/cmds/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use std::path::PathBuf;
use tar::Archive;
use xz2::read::XzDecoder;

use crate::bin;
use crate::manifest;
use crate::updates;
use crate::{Config, manifest::*};

#[derive(Parser, Default)]
Expand Down Expand Up @@ -163,6 +163,7 @@ pub async fn download_tool_from_asset(tool: &Tool, asset: &Asset, config: &Confi
tool.name,
install_dir.join(&tool.name).display()
);

println!();

Ok(())
Expand Down Expand Up @@ -203,23 +204,6 @@ async fn find_matching_release(
Ok(None)
}

async fn find_installed_version(tool: &Tool, config: &Config) -> anyhow::Result<Option<Version>> {
if !bin::is_installed(tool, config).await? {
return Ok(None);
}

let current_version = bin::check_current_version(tool, config).await;

match current_version {
Ok(version) => Ok(Some(version)),
Err(_) => {
// if the version command fails, we assume there's something wrong with the
// binary and respond as if it wasn't installed
Ok(None)
}
}
}

async fn install_tool(tool: &Tool, requested: &VersionReq, config: &Config) -> anyhow::Result<()> {
println!("\n> Installing {} at version {}", tool.name, requested);

Expand All @@ -240,38 +224,35 @@ async fn install_tool(tool: &Tool, requested: &VersionReq, config: &Config) -> a
Ok(())
}

pub async fn run(args: &Args, config: &Config) -> anyhow::Result<()> {
pub async fn run(_args: &Args, config: &Config) -> anyhow::Result<()> {
let manifest = manifest::load_manifest(config, true).await?;

let to_install: Vec<_> = if let Some(filter) = &args.tool {
manifest.tools().filter(|x| x.name == *filter).collect()
} else {
manifest.tools().collect()
};
let updates = updates::check_updates(&manifest, config).await?;

if to_install.is_empty() {
return Err(anyhow::anyhow!("No tools found to install"));
if updates.is_empty() {
println!("You are up to date 🎉");
return Ok(());
}

for tool in to_install.iter() {
let current = find_installed_version(tool, config).await?;
let requested = VersionReq::parse(&tool.version)?;
for update in updates {
let tool = manifest.tool_by_name(&update.tool).unwrap();

if let Some(current) = current {
if requested.matches(&current) {
println!("\nYour version of {} is up to date 👌", tool.name);

continue;
} else {
println!("\nYour version of {} needs to be updated 😬", tool.name);
println!(" Current version: {current}");
println!(" Requested version: {requested}");
}
if let Some(current) = update.current()? {
println!("\nYour version of {} needs to be updated 😬", tool.name);
println!(" Current version: {current}");
println!(" Requested version: {}", update.requested);
} else {
println!("\nYour need to install {} 📦", tool.name);
}

install_tool(tool, &requested, config).await?;
install_tool(tool, &update.requested()?, config).await?;
}

// we do a second check to make sure we have the latest updates
let after = updates::check_updates(&manifest, config).await?;

if !after.is_empty() {
println!("Seems that you still have updates to install, please run `tx3up install` again",);
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions src/cmds/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod check;
pub mod default;
pub mod install;
pub mod show;
13 changes: 9 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod bin;
mod cmds;
mod manifest;
mod perm_path;
mod updates;

pub const BANNER: &str = color_print::cstr! {
r#"
Expand All @@ -32,10 +33,10 @@ struct Cli {

#[derive(Subcommand)]
enum Commands {
/// Install the tx3 toolchain
/// Install or update the tx3 toolchain
Install(cmds::install::Args),
/// Update the tx3 toolchain to the latest version
Update,
/// Check for updates
Check(cmds::check::Args),
/// Uninstall the tx3 toolchain
Uninstall,
/// Set the default channel
Expand Down Expand Up @@ -84,6 +85,10 @@ impl Config {
pub fn manifest_file(&self) -> PathBuf {
self.channel_dir().join("manifest.json")
}

pub fn updates_file(&self) -> PathBuf {
self.channel_dir().join("updates.json")
}
}

#[tokio::main]
Expand All @@ -100,7 +105,7 @@ async fn main() -> anyhow::Result<()> {
if let Some(command) = cli.command {
match command {
Commands::Install(args) => cmds::install::run(&args, &config).await?,
Commands::Update => todo!(),
Commands::Check(args) => cmds::check::run(&args, &config).await?,
Commands::Uninstall => todo!(),
Commands::Default(args) => cmds::default::run(&args, &config).await?,
Commands::Show(args) => cmds::show::run(&args, &config).await?,
Expand Down
4 changes: 4 additions & 0 deletions src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ impl Manifest {
pub fn tools(&self) -> impl Iterator<Item = &Tool> {
self.tools.iter()
}

pub fn tool_by_name(&self, name: &str) -> Option<&Tool> {
self.tools.iter().find(|tool| tool.name == name)
}
}

async fn fetch_manifest_content(url: &str) -> anyhow::Result<String> {
Expand Down
123 changes: 123 additions & 0 deletions src/updates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use anyhow::Context as _;
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use tokio::fs;

use crate::{
Config, bin,
manifest::{Manifest, Tool},
};

#[derive(Debug, Serialize, Deserialize)]
pub struct Update {
pub tool: String,
pub current: Option<String>,
pub requested: String,
}

impl Update {
pub fn requested(&self) -> anyhow::Result<VersionReq> {
VersionReq::parse(&self.requested).context("parsing requested version")
}

pub fn current(&self) -> anyhow::Result<Option<Version>> {
if let Some(current) = &self.current {
Ok(Some(
Version::parse(current).context("parsing current version")?,
))
} else {
Ok(None)
}
}
}

async fn find_installed_version(tool: &Tool, config: &Config) -> anyhow::Result<Option<Version>> {
if !bin::is_installed(tool, config).await? {
return Ok(None);
}

let current_version = bin::check_current_version(tool, config).await;

match current_version {
Ok(version) => Ok(Some(version)),
Err(_) => {
// if the version command fails, we assume there's something wrong with the
// binary and respond as if it wasn't installed
Ok(None)
}
}
}

async fn evaluate_update(tool: &Tool, config: &Config) -> anyhow::Result<Option<Update>> {
let current = find_installed_version(tool, config).await?;
let requested = VersionReq::parse(&tool.version)?;

if let Some(current) = &current {
if requested.matches(&current) {
return Ok(None);
}
}

Ok(Some(Update {
tool: tool.name.clone(),
current: current.map(|v| v.to_string()),
requested: requested.to_string(),
}))
}

async fn save_updates(updates: &[Update], config: &Config) -> anyhow::Result<()> {
fs::create_dir_all(config.channel_dir())
.await
.context("creating channel dir")?;

fs::write(
config.updates_file(),
serde_json::to_string(&updates)?.as_bytes(),
)
.await
.context("writing updates file")?;

Ok(())
}

pub async fn clear_updates(config: &Config) -> anyhow::Result<()> {
fs::remove_file(config.updates_file())
.await
.context("removing updates file")?;

Ok(())
}

pub async fn check_updates(manifest: &Manifest, config: &Config) -> anyhow::Result<Vec<Update>> {
let mut updates = vec![];

for tool in manifest.tools() {
if let Some(update) = evaluate_update(tool, config).await? {
updates.push(update);
}
}

save_updates(&updates, config).await?;

Ok(updates)
}

pub async fn load_updates(
manifest: &Manifest,
config: &Config,
force_check: bool,
) -> anyhow::Result<Vec<Update>> {
let updates_file = config.updates_file();

if !updates_file.exists() || force_check {
check_updates(manifest, config).await?;
}

let updates = fs::read_to_string(updates_file)
.await
.context("reading updates file")?;

let updates: Vec<Update> = serde_json::from_str(&updates)?;

Ok(updates)
}