use crate::dir_context::AipackBaseDir;
use crate::exec::cli::XelfSetupArgs;
use crate::exec::exec_cmd_xelf::support;
use crate::exec::init::extract_setup_aip_env_sh_zfile; use crate::exec::init::init_base;
use crate::hub::{get_hub, hub_prompt};
use crate::support::os;
use crate::support::os::{current_os, is_windows};
use crate::{Result, term};
use simple_fs::read_to_string;
use simple_fs::{SPath, ensure_dir}; use std::fs;
use std::fs::{remove_file, write};
pub async fn exec_xelf_setup(_args: XelfSetupArgs) -> Result<()> {
init_base(false).await?;
let aipack_base_dir = AipackBaseDir::new()?;
let hub = get_hub();
hub.publish(format!(
"\n==== Executing 'self setup' ({}) ====\n",
aipack_base_dir.path()
))
.await;
let base_bin_dir = aipack_base_dir.bin_dir();
if ensure_dir(&base_bin_dir)? {
hub.publish(format!("-> {:<18} '{base_bin_dir}'", "Create dir")).await;
}
let current_exe = std::env::current_exe()?;
let current_exe_spath = SPath::from_std_path_buf(current_exe)?;
let is_current_exe_at_base_bin_dir = current_exe_spath
.parent()
.map(|p| p.as_str() == base_bin_dir.as_str())
.unwrap_or_default();
let tmp_exe_to_trash = if is_current_exe_at_base_bin_dir {
hub.publish(format!(
"WARN: Running 'self setup' on installed 'aip' at '{}'. This will just update the settings (but not update the 'aip' binary)",
aipack_base_dir.as_str()
))
.await;
None
}
else {
let target_exe_path = base_bin_dir.join(current_exe_spath.name());
super::support::atomic_replace(¤t_exe_spath, &target_exe_path)?;
hub.publish(format!(
"-> {:<18} '{current_exe_spath}' to '{target_exe_path}'",
"Copy executable"
))
.await;
Some(current_exe_spath)
};
if !support::has_aip_in_path() {
if os::is_unix() {
unix_setup_env(&base_bin_dir).await?;
} else {
#[cfg(windows)]
for_windows::windows_setup_env(&base_bin_dir).await?;
}
}
if let Some(tmp_exe_to_trash) = tmp_exe_to_trash {
let gz_sibling = tmp_exe_to_trash.new_sibling(format!("{}.tar.gz", tmp_exe_to_trash.stem()));
if gz_sibling.exists() && tmp_exe_to_trash.stem() == "aip" {
if !is_windows() {
if tmp_exe_to_trash.exists() {
remove_file(&tmp_exe_to_trash)?;
hub.publish(format!("-> {:<18} '{tmp_exe_to_trash}'", "Temp aip file deleted"))
.await;
}
}
else {
hub.publish(
r#"
NOTE: Setup complete, you can remove the 'aip.exe' and `aip.tar.gz' files with:
Remove-Item .\aip.exe
Remove-Item .\aip.tar.gz
(aip.exe has been copied to ~/.aipack-base/bin)"#,
)
.await;
}
}
}
hub.publish("\n==== 'self setup' completed ====\n").await;
Ok(())
}
#[cfg(windows)]
mod for_windows {
use super::*;
use crate::support::proc::{self, ProcOptions};
use crate::term;
pub async fn windows_setup_env(base_bin_dir: &SPath) -> Result<()> {
let hub = get_hub();
let current_path = std::env::var("PATH").unwrap_or_default();
let found_path = current_path.split(';').find(|p| p.contains(".aipack-base"));
if let Some(found_path) = found_path {
hub.publish(format!(".aipack-base path already setup. ({found_path})")).await;
return Ok(());
}
let new_path = base_bin_dir.as_str().replace("/", "\\");
let user_response = hub_prompt(
hub,
format!("\nDo you want to add '{new_path}' to your shell PATH?: Y/n "),
)
.await?;
if !term::is_input_yes(&user_response) {
hub.publish(format!(
r#"-! Answer was not 'Y' so skipping updating environment path.
Make sure to add '{new_path}' in your system path.
Then, you can run: aip -V
To check the version of aipack"#
))
.await;
return Ok(());
}
add_new_path(&new_path).await?;
let version = crate::VERSION;
hub.publish(format!(
r#"
Setup should be complete now
- You need to start a new terminal
- If you are in VSCode, it has to be restarted for the new Path to take effect
(restart the VSCode terminal is not enough :(
Then, check with
- Run: aip -V
- Should print something like "aipack {version}"
"#
))
.await;
Ok(())
}
async fn add_new_path(new_path: &str) -> Result<()> {
let hub = get_hub();
let current_user_path = exec_powershell(r#"[Environment]::GetEnvironmentVariable("Path", "User")"#).await?;
let current_user_path = current_user_path.trim();
let updated_path = format!("{};{new_path}", current_user_path.trim_end_matches(';'));
let power_set_path_cmd = &format!(
"[Environment]::SetEnvironmentVariable('Path', '{}', 'User')",
updated_path.replace("'", "''") );
exec_powershell(power_set_path_cmd).await?;
hub.publish(format!(
"-> Added to shell path (with [Environment]::SetEnvironmentVariable('Path'): '{new_path}'"
))
.await;
Ok(())
}
async fn exec_powershell(power_cmd: &str) -> Result<String> {
let args = ["-NoProfile", "-Command", power_cmd];
let mut options = ProcOptions::default();
options.creation_flags = Some(0x08000000);
let output = proc::proc_exec_to_output("powershell", &args, Some(&options)).await?;
Ok(output.trim().to_string())
}
}
async fn unix_setup_env(base_bin_dir: &SPath) -> Result<()> {
let hub = get_hub();
let env_script_zfile = extract_setup_aip_env_sh_zfile()?;
let target_env_script_path = base_bin_dir.join("aip-env");
fs::write(&target_env_script_path, env_script_zfile.content)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _; if os::is_linux() || os::is_mac() {
let mut perms = fs::metadata(&target_env_script_path)?.permissions();
perms.set_mode(0o755); fs::set_permissions(&target_env_script_path, perms)?;
}
}
hub.publish(format!("-> {:<18} '{target_env_script_path}'", "Create script"))
.await;
if let Some(home_sh_env_path) = os::get_os_env_file_path() {
let content = if home_sh_env_path.exists() {
read_to_string(&home_sh_env_path)?
} else {
"".to_string()
};
if content.contains(".aipack-base") {
hub.publish(format!(
"-! {:<18} '{home_sh_env_path}' seems to have the .aipack-base path setup. So, skipping further setup.",
"Setup PATH"
))
.await;
}
else if let Some(os_source_line) = os_source_line(&target_env_script_path) {
let action_str = if content.is_empty() { "create" } else { "update" };
let user_response = hub_prompt(
hub,
format!(
"\nDo you want to {action_str} the '{home_sh_env_path}' with the required aipack-base/bin path: Y/n "
),
)
.await?;
if term::is_input_yes(&user_response) {
let content = format!("{}\n\n{}\n", content.trim_end(), os_source_line);
write(&home_sh_env_path, content)?;
hub.publish(format!(
"-> {:<18} Added '{os_source_line}' in file '{home_sh_env_path}'",
"Setup PATH"
))
.await;
} else {
hub.publish(format!(
"-! Answer was not 'Y' so skipping updating '{home_sh_env_path}'"
))
.await;
}
} else {
hub.publish(format!(
"-! No source line for the current OS. Skipping updating '{home_sh_env_path}'"
))
.await;
}
}
let path_to_aip_env_sh = format!("$HOME/.aipack-base/bin/{}", target_env_script_path.name());
let version = crate::VERSION;
hub.publish(format!(
r#"
Setup should be complete now
- You need to start a new terminal
- Or execute: source "{path_to_aip_env_sh}"
Then, check with
- Run: which aip
- You should see something like "path/to/home/.aipack-base/bin/aip"
- Run: aip -V
- Should print something like "aipack {version}"
"#
))
.await;
Ok(())
}
fn os_source_line(target_env_script_path: &SPath) -> Option<String> {
let path_to_aip_env_sh = format!("$HOME/.aipack-base/bin/{}", target_env_script_path.name());
match current_os() {
os::OsType::Mac => Some(format!("source \"{path_to_aip_env_sh}\"")),
os::OsType::Linux => Some(format!("source \"{path_to_aip_env_sh}\"")),
os::OsType::Windows => None,
os::OsType::Unknown => None,
}
}