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
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ reqwest = { version = "0.11", features = ["json"] }
futures-util = "0.3"
flate2 = "1.0"
tar = "0.4"
xz2 = "0.1.7"

# The profile that 'cargo dist' will build with
[profile.dist]
Expand Down
4 changes: 2 additions & 2 deletions src/cmds/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{Config, perm_path};

#[derive(Parser, Default)]
pub struct Args {
#[arg(short, long, default_value = "stable")]
#[arg(default_value = "stable")]
pub channel: String,
}

Expand All @@ -31,7 +31,7 @@ pub async fn run(args: &Args, config: &Config) -> anyhow::Result<()> {
set_default_channel(&config.root_dir(), &args.channel)?;
println!("Set default channel to {}", args.channel);

println!("Checking or updating PATH variable");
println!("updating PATH variable");
perm_path::check_or_update(config)?;
Ok(())
}
61 changes: 39 additions & 22 deletions src/cmds/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use std::fs;
use std::io::Write;
use std::path::PathBuf;
use tar::Archive;
use tar::Entry;
use xz2::read::XzDecoder;

use crate::{Config, tools::*};

Expand Down Expand Up @@ -44,39 +46,54 @@ pub async fn download_binary(url: &str, path: &PathBuf) -> Result<()> {
Ok(())
}

fn extract_binary(path: &PathBuf, install_dir: &PathBuf, tool_name: &str) -> Result<()> {
let tar_gz = fs::File::open(path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);

// Find the first file that starts with the tool name
let mut binary_entry = None;
// Find the first file that starts with the tool name
fn extract_binary_main_entry<R: std::io::Read>(
mut archive: Archive<R>,
install_dir: &PathBuf,
tool_name: &str,
) -> anyhow::Result<()> {
for entry in archive.entries()? {
let entry = entry?;
let mut entry = entry?;
let path = entry.path()?;
let filename = path
.file_name()
.and_then(|n| n.to_str())
.context("Invalid filename in archive")?;

if filename.starts_with(tool_name) && entry.header().entry_type().is_file() {
binary_entry = Some(entry);
break;
let binary_path = install_dir.join(filename);
entry.unpack(&binary_path)?;

// Make the binary executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&binary_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&binary_path, perms)?;
}
}
}

let mut binary_entry = binary_entry.context("No matching binary found in archive")?;
let binary_path = install_dir.join(tool_name);
binary_entry.unpack(&binary_path)?;

// Make the binary executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&binary_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&binary_path, perms)?;
}
anyhow::bail!("No matching binary found in archive")
}

fn extract_binary(path: &PathBuf, install_dir: &PathBuf, tool_name: &str) -> Result<()> {
let file = fs::File::open(path)?;

let extension = path.extension().and_then(|s| s.to_str());

match extension {
Some("gz") => {
let archive = Archive::new(GzDecoder::new(file));
extract_binary_main_entry(archive, install_dir, tool_name)
}
Some("xz") => {
let archive = Archive::new(XzDecoder::new(file));
extract_binary_main_entry(archive, install_dir, tool_name)
}
_ => anyhow::bail!("Unsupported archive format. Expected .gz or .xz"),
};

Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions src/cmds/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod default;
pub mod install;
pub mod show;
48 changes: 48 additions & 0 deletions src/cmds/show.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::process::Command;

use crate::Config;

#[derive(Debug, clap::Parser)]
pub struct Args {
pub tool: Option<String>,
}

fn print_tool(tool: &crate::tools::Tool, config: &Config) -> anyhow::Result<()> {
println!("bin path: {}", tool.bin_path(config).display());

println!(
"github repo: https://github.com/{}/{}",
tool.repo_owner, tool.repo_name
);

let version = Command::new(tool.bin_path(config))
.arg("--version")
.output()?;

let version = String::from_utf8(version.stdout)?;

let version = if version.is_empty() {
"not reported\n".to_string()
} else {
version
};

println!("version: {}", version);

Ok(())
}

pub async fn run(args: &Args, config: &Config) -> anyhow::Result<()> {
// for each tool, trigger a shell command to print the version
for tool in crate::tools::all_tools() {
println!("{}", tool.name);

let ok = print_tool(&tool, config);

if let Err(e) = ok {
println!("error: {}", e);
}
}

Ok(())
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ enum Commands {
Uninstall,
/// Set the default channel
Default(cmds::default::Args),
/// Show the version of the tx3 toolchain
Show(cmds::show::Args),
}

pub struct Config {
Expand Down Expand Up @@ -85,6 +87,7 @@ async fn main() -> Result<()> {
Commands::Update => todo!(),
Commands::Uninstall => todo!(),
Commands::Default(args) => cmds::default::run(&args, &config).await?,
Commands::Show(args) => cmds::show::run(&args, &config).await?,
}
} else {
cmds::install::run(&cmds::install::Args::default(), &config).await?;
Expand Down
89 changes: 61 additions & 28 deletions src/perm_path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::Context;
use dirs;
use std::env;
use std::fmt;
Expand All @@ -7,16 +8,42 @@ use std::path::{Path, PathBuf};

use crate::Config;

/// Common profile configuration file names in order of preference
const PROFILE_FILES: &[&str] = &[
//".bash_profile",
//".bash_login",
//".profile",
".zshrc",
//".zprofile",
];
enum KnownShell {
Posix,
Bash,
Zsh,
}

impl KnownShell {
fn rc_files(&self) -> Vec<&str> {
match self {
KnownShell::Posix => vec![".profile"],
KnownShell::Bash => vec![".bash_profile", ".bash_login", ".bashrc"],
KnownShell::Zsh => vec![".zshrc"],
}
}
}

fn known_shells() -> Vec<KnownShell> {
vec![KnownShell::Posix, KnownShell::Bash, KnownShell::Zsh]
}

fn source_cmd(root_dir: &Path) -> String {
format!(
r#"
export TX3_ROOT="{}"
export PATH="$TX3_ROOT/default/bin:$PATH"
"#,
root_dir.to_str().unwrap()
)
}

fn file_contains(profile_path: &Path, source_cmd: &str) -> bool {
let contents = std::fs::read_to_string(profile_path).unwrap();
contents.contains(source_cmd)
}

pub fn append_file(profile_path: PathBuf, home_dir: PathBuf) -> io::Result<()> {
fn append_file(profile_path: &Path, source_cmd: &str) -> anyhow::Result<()> {
println!(
"Appending to profile file: {}",
profile_path.to_str().unwrap()
Expand All @@ -27,39 +54,45 @@ pub fn append_file(profile_path: PathBuf, home_dir: PathBuf) -> io::Result<()> {
.create(false)
.open(profile_path)?;

writeln!(profile, "\nexport TX3_ROOT={}", home_dir.to_str().unwrap())?;
writeln!(profile, "export PATH=\"$TX3_ROOT/default/bin:$PATH\"")?;
profile.flush()
write!(profile, "{}", source_cmd)?;
profile.flush()?;

Ok(())
}

#[cfg(target_family = "unix")]
fn update_all_profiles(config: &Config) -> anyhow::Result<()> {
for profile_file in PROFILE_FILES {
let profile_path = dirs::home_dir().unwrap().join(profile_file);
for sh in known_shells() {
let source_cmd = source_cmd(&config.root_dir());

for rc in sh.rc_files() {
let profile_path = dirs::home_dir()
.context("can't find user's home dir")?
.join(rc);

if !profile_path.exists() {
continue;
}

if file_contains(&profile_path, &source_cmd) {
println!(
"{} already contains the source command",
profile_path.to_str().unwrap()
);
continue;
}

if Path::new(&profile_path).exists() {
append_file(profile_path, config.root_dir())?;
append_file(&profile_path, &source_cmd)?;
}
}

Ok(())
}

pub fn check_or_update(config: &Config) -> anyhow::Result<()> {
let path = env::var("PATH").unwrap();

if path.contains(".tx3/default/bin") {
println!("Tx3 toolchain already in $PATH");
return Ok(());
}

update_all_profiles(config)?;

println!("\nRestart your shell or run:");
println!(
"export PATH=\"{}/default/bin:$PATH\"",
config.root_dir().display()
);
println!("{}", source_cmd(&config.root_dir()));

Ok(())
}
16 changes: 14 additions & 2 deletions src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@ pub struct Tool {
pub repo_name: String,
}

use std::sync::OnceLock;
impl Tool {
pub fn bin_path(&self, config: &Config) -> PathBuf {
config.bin_dir().join(self.name.clone())
}

pub fn version_cmd(&self) -> String {
format!("{} --version", self.name)
}
}

use std::{path::PathBuf, sync::OnceLock};

use crate::Config;

static TOOLS: OnceLock<Vec<Tool>> = OnceLock::new();

Expand All @@ -22,7 +34,7 @@ pub fn all_tools() -> impl Iterator<Item = &'static Tool> {
repo_name: "trix".to_string(),
},
Tool {
name: "lsp".to_string(),
name: "tx3-lsp".to_string(),
description: "A language server for tx3".to_string(),
min_version: "0.1.0".to_string(),
repo_owner: "tx3-lang".to_string(),
Expand Down