#[macro_use]
mod types;
mod arg_parser;
mod configuration;
mod plugins;
mod utils;
use arg_parser::*;
use types::{BinaryName, CommandName, ErrBox};
#[tokio::main]
async fn main() -> Result<(), ErrBox> {
match run().await {
Ok(_) => {}
Err(err) => {
eprintln!("{}", err.to_string());
std::process::exit(1);
}
}
Ok(())
}
async fn run() -> Result<(), ErrBox> {
let args = parse_args(std::env::args().collect())?;
match args.sub_command {
SubCommand::Help(text) => print!("{}", text),
SubCommand::Version => println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
SubCommand::Resolve(resolve_command) => handle_resolve_command(resolve_command)?,
SubCommand::Install => handle_install_command().await?,
SubCommand::InstallUrl(url) => handle_install_url_command(url).await?,
SubCommand::Uninstall(uninstall_command) => handle_uninstall_command(uninstall_command)?,
SubCommand::Use(use_command) => handle_use_command(use_command)?,
SubCommand::List => handle_list_command()?,
}
Ok(())
}
fn handle_resolve_command(resolve_command: ResolveCommand) -> Result<(), ErrBox> {
let plugin_manifest = plugins::read_manifest()?;
let command_name = CommandName::from_string(resolve_command.binary_name);
let executable_path = if let Some(info) = get_executable_path_from_config_file(&plugin_manifest, &command_name)? {
if let Some(executable_path) = info.executable_path {
Some(executable_path.clone())
} else {
if info.had_uninstalled_binary {
eprintln!(
"[bvm warning]: There were uninstalled binaries (run `bvm install`). Resolving global '{}'.",
command_name.display()
);
}
None
}
} else {
None
};
let executable_path = match executable_path {
Some(path) => path,
None => get_global_binary_file_name(&plugin_manifest, &command_name)?,
};
println!("{}", executable_path);
Ok(())
}
async fn handle_install_command() -> Result<(), ErrBox> {
let config_file_path = match configuration::find_config_file()? {
Some(file_path) => file_path,
None => return err!("Could not find .bvmrc.json in the current directory or its ancestors."),
};
let config_file_text = std::fs::read_to_string(&config_file_path)?;
let config_file = configuration::read_config_file(&config_file_text)?;
let shim_dir = utils::get_shim_dir()?;
let mut plugin_manifest = plugins::read_manifest()?;
for entry in config_file.binaries.iter() {
let is_installed = plugin_manifest
.get_identifier_from_url(&entry.url)
.map(|identifier| plugin_manifest.get_binary(&identifier).is_some())
.unwrap_or(false);
if !is_installed {
let binary_item = plugins::setup_plugin(&mut plugin_manifest, &entry, &shim_dir).await?;
let command_name = binary_item.get_command_name();
let identifier = binary_item.get_identifier();
if plugin_manifest.get_global_binary_location(&command_name).is_none() {
if utils::get_path_executable_path(&command_name)?.is_some() {
plugin_manifest.use_global_version(command_name.clone(), plugins::GlobalBinaryLocation::Path);
} else {
plugin_manifest
.use_global_version(command_name.clone(), plugins::GlobalBinaryLocation::Bvm(identifier));
}
}
plugins::write_manifest(&plugin_manifest)?; }
}
Ok(())
}
async fn handle_install_url_command(url: String) -> Result<(), ErrBox> {
let checksum_url = utils::parse_checksum_url(&url);
let shim_dir = utils::get_shim_dir()?;
let mut plugin_manifest = plugins::read_manifest()?;
let was_global_version = if let Some(identifier) = plugin_manifest
.get_identifier_from_url(&url)
.map(|identifier| identifier.clone())
{
let is_global_version = plugin_manifest.is_global_version(&identifier);
plugin_manifest.remove_binary(&identifier);
plugin_manifest.remove_url(&url);
plugins::write_manifest(&plugin_manifest)?;
is_global_version
} else {
false
};
let binary_item = plugins::setup_plugin(&mut plugin_manifest, &checksum_url, &shim_dir).await?;
let identifier = binary_item.get_identifier();
let binary_name = binary_item.get_binary_name();
let version = binary_item.version.clone();
if was_global_version {
let command_name = binary_item.get_command_name();
plugin_manifest.use_global_version(command_name, plugins::GlobalBinaryLocation::Bvm(identifier.clone()));
}
let is_global_version = plugin_manifest.is_global_version(&identifier);
if !is_global_version {
let command_name = binary_name.get_command_name();
eprintln!(
"Installed. Run `bvm use {} {}` to set it as the global '{}' binary.",
binary_name.display_toggled_owner(!plugin_manifest.command_has_same_owner(&command_name)),
version,
command_name.display(),
);
}
plugins::write_manifest(&plugin_manifest)
}
fn handle_uninstall_command(uninstall_command: UninstallCommand) -> Result<(), ErrBox> {
let shim_dir = utils::get_shim_dir()?;
let mut plugin_manifest = plugins::read_manifest()?;
let binary = get_binary_with_name_and_version(
&plugin_manifest,
&uninstall_command.binary_name,
&uninstall_command.version,
)?;
let binary_name = binary.get_binary_name();
let command_name = binary_name.get_command_name();
let plugin_dir = plugins::get_plugin_dir(&binary.owner, &binary.name, &binary.version)?;
let binary_identifier = binary.get_identifier();
plugin_manifest.remove_binary(&binary_identifier);
plugins::write_manifest(&plugin_manifest)?;
if !plugin_manifest.has_binary_with_command(&command_name) {
std::fs::remove_file(plugins::get_path_script_path(&shim_dir, &command_name))?;
}
std::fs::remove_dir_all(&plugin_dir)?;
let binary_name_dir = plugin_dir.parent().unwrap();
if utils::is_dir_empty(&binary_name_dir)? {
std::fs::remove_dir_all(&binary_name_dir)?;
let owner_name_dir = binary_name_dir.parent().unwrap();
if utils::is_dir_empty(&owner_name_dir)? {
std::fs::remove_dir_all(&owner_name_dir)?;
}
}
Ok(())
}
fn handle_use_command(use_command: UseCommand) -> Result<(), ErrBox> {
let mut plugin_manifest = plugins::read_manifest()?;
let command_name = use_command.binary_name.get_command_name();
let is_binary_in_config_file = get_executable_path_from_config_file(&plugin_manifest, &command_name)?
.map(|info| info.executable_path)
.flatten()
.is_some();
if use_command.version.to_lowercase() == "path" {
if !plugin_manifest.has_binary_with_name(&use_command.binary_name) {
return err!(
"Could not find any installed binaries named '{}'",
use_command.binary_name.display()
);
}
if utils::get_path_executable_path(&command_name)?.is_none() {
return err!(
"Could not find any installed binaries on the path that matched '{}'",
command_name.display()
);
}
plugin_manifest.use_global_version(command_name, plugins::GlobalBinaryLocation::Path);
} else {
let binary =
get_binary_with_name_and_version(&plugin_manifest, &use_command.binary_name, &use_command.version)?;
let identifier = binary.get_identifier(); plugin_manifest.use_global_version(command_name, plugins::GlobalBinaryLocation::Bvm(identifier));
}
plugins::write_manifest(&plugin_manifest)?;
if is_binary_in_config_file {
eprintln!("Updated globally used version, but local version remains using version specified in the current working directory's config file. If you wish to change the local version, then update your configuration file (check the cwd and ancestor directories for a .bvmrc.json file).");
}
return Ok(());
}
fn handle_list_command() -> Result<(), ErrBox> {
let plugin_manifest = plugins::read_manifest()?;
let binaries = plugin_manifest.binaries().collect();
println!("{}", display_binaries_versions(binaries).join("\n"));
Ok(())
}
struct ConfigFileExecutableInfo {
executable_path: Option<String>,
had_uninstalled_binary: bool,
}
fn get_executable_path_from_config_file(
plugin_manifest: &plugins::PluginsManifest,
command_name: &CommandName,
) -> Result<Option<ConfigFileExecutableInfo>, ErrBox> {
let config_file_path = configuration::find_config_file()?;
Ok(if let Some(config_file_path) = config_file_path {
let config_file_text = std::fs::read_to_string(&config_file_path)?;
let config_file = configuration::read_config_file(&config_file_text)?;
let mut had_uninstalled_binary = false;
let mut config_file_binary = None;
for url in config_file.binaries.iter() {
if let Some(identifier) = plugin_manifest.get_identifier_from_url(&url.url) {
if let Some(cache_item) = plugin_manifest.get_binary(&identifier) {
if cache_item.name == command_name.as_str() {
config_file_binary = Some(cache_item);
break;
}
} else {
had_uninstalled_binary = true;
}
} else {
had_uninstalled_binary = true;
}
}
Some(ConfigFileExecutableInfo {
executable_path: config_file_binary.map(|b| b.file_name.clone()),
had_uninstalled_binary,
})
} else {
None
})
}
fn get_binary_with_name_and_version<'a>(
plugin_manifest: &'a plugins::PluginsManifest,
binary_name: &BinaryName,
version: &str,
) -> Result<&'a plugins::BinaryManifestItem, ErrBox> {
let binaries = plugin_manifest.get_binaries_by_name_and_version(&binary_name, &version);
if binaries.len() == 0 {
let binaries = plugin_manifest.get_binaries_with_name(binary_name);
if binaries.is_empty() {
err!(
"Could not find any installed binaries named '{}'",
binary_name.display()
)
} else {
err!(
"Could not find binary '{}' with version '{}'\n\nInstalled versions:\n {}",
binary_name.display(),
version,
display_binaries_versions(binaries).join("\n "),
)
}
} else if binaries.len() > 1 {
return err!(
"There were multiple binaries with the specified name '{}' with version '{}'. Please include the owner to uninstall.\n\nInstalled versions:\n {}",
binary_name.display(),
version,
display_binaries_versions(binaries).join("\n "),
);
} else {
Ok(binaries[0])
}
}
fn display_binaries_versions(binaries: Vec<&plugins::BinaryManifestItem>) -> Vec<String> {
if binaries.is_empty() {
return Vec::new();
}
let mut binaries = binaries;
binaries.sort_by(|a, b| a.compare(b));
let have_same_owner = get_have_same_owner(&binaries);
let lines = binaries
.into_iter()
.map(|b| {
if have_same_owner {
b.version.clone()
} else {
format!("{}/{} {}", b.owner, b.name, b.version)
}
})
.collect::<Vec<_>>();
return lines;
fn get_have_same_owner(binaries: &Vec<&plugins::BinaryManifestItem>) -> bool {
let first_owner = &binaries[0].owner;
binaries.iter().all(|b| &b.owner == first_owner)
}
}
fn get_global_binary_file_name(
plugin_manifest: &plugins::PluginsManifest,
command_name: &CommandName,
) -> Result<String, ErrBox> {
match plugin_manifest.get_global_binary_location(command_name) {
Some(location) => match location {
plugins::GlobalBinaryLocation::Path => {
if let Some(path_executable_path) = utils::get_path_executable_path(command_name)? {
Ok(path_executable_path.to_string_lossy().to_string())
} else {
err!("Binary '{}' is configured to use the executable on the path, but only the bvm version exists on the path. Run `bvm use {0} <some other version>` to select a version to run.", command_name.display())
}
}
plugins::GlobalBinaryLocation::Bvm(identifier) => {
if let Some(item) = plugin_manifest.get_binary(&identifier) {
Ok(item.file_name.clone())
} else {
err!("Should have found executable path for global binary. Report this as a bug and update the version used by running `bvm use {} <some other version>`", command_name.display())
}
}
},
None => {
if let Some(path_executable_path) = utils::get_path_executable_path(command_name)? {
Ok(path_executable_path.to_string_lossy().to_string())
} else {
err!("Could not find binary '{}'", command_name.display())
}
}
}
}