feat: add PESDE_HOME variable

Adds the PESDE_HOME env variable to allow changing
where pesde will store data like engines, bins,
etc. to support e.g. small OS disks & large
data disks.
This commit is contained in:
daimond113 2025-04-26 20:45:27 +02:00
parent 398763a171
commit 6aeedd8602
No known key found for this signature in database
GPG key ID: 640DC95EC1190354
7 changed files with 62 additions and 54 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Add dev only installs in #32 by @Stefanuk12 - Add dev only installs in #32 by @Stefanuk12
- Add `PESDE_HOME` variable (defaults to $HOME/.pesde) to override pesde's directory's location by @daimond113
### Fixed ### Fixed
- Download engines in install step rather than lazily by @daimond113 - Download engines in install step rather than lazily by @daimond113

View file

@ -1,7 +1,6 @@
use crate::cli::{ use crate::cli::{
style::{ADDED_STYLE, CLI_STYLE}, style::{ADDED_STYLE, CLI_STYLE},
version::replace_pesde_bin_exe, version::replace_pesde_bin_exe,
HOME_DIR,
}; };
use anyhow::Context as _; use anyhow::Context as _;
use clap::Args; use clap::Args;
@ -25,16 +24,18 @@ impl SelfInstallCommand {
use anyhow::Context as _; use anyhow::Context as _;
use windows_registry::CURRENT_USER; use windows_registry::CURRENT_USER;
let bin_dir = crate::cli::bin_dir().await?; let bin_dir = crate::cli::bin_dir()?;
let env = CURRENT_USER let env = CURRENT_USER
.create("Environment") .create("Environment")
.context("failed to open Environment key")?; .context("failed to open Environment key")?;
let path = env.get_string("Path").context("failed to get Path value")?; let path = env.get_string("Path").context("failed to get Path value")?;
let bin_dir = bin_dir.to_string_lossy(); let bin_dir = bin_dir
.to_str()
.context("bin directory path contains invalid characters")?;
let exists = path.split(';').any(|part| *part == bin_dir); let exists = path.split(';').any(|part| part == bin_dir);
if !exists { if !exists {
let new_path = format!("{path};{bin_dir}"); let new_path = format!("{path};{bin_dir}");
@ -43,7 +44,7 @@ impl SelfInstallCommand {
println!( println!(
"\nin order to allow proper functionality {} was added to PATH.\n\n{}", "\nin order to allow proper functionality {} was added to PATH.\n\n{}",
style(format!("`~/{HOME_DIR}/bin`")).green(), style(format!("`{bin_dir}`")).green(),
WARN_STYLE.apply_to("please restart your shell for this to take effect") WARN_STYLE.apply_to("please restart your shell for this to take effect")
); );
} }

View file

@ -1,9 +1,11 @@
use crate::cli::{auth::Tokens, home_dir}; use crate::cli::auth::Tokens;
use anyhow::Context as _; use anyhow::Context as _;
use fs_err::tokio as fs; use fs_err::tokio as fs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::instrument; use tracing::instrument;
use super::config_path;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct CliConfig { pub struct CliConfig {
@ -33,7 +35,7 @@ impl Default for CliConfig {
#[instrument(level = "trace")] #[instrument(level = "trace")]
pub async fn read_config() -> anyhow::Result<CliConfig> { pub async fn read_config() -> anyhow::Result<CliConfig> {
let config_string = match fs::read_to_string(home_dir()?.join("config.toml")).await { let config_string = match fs::read_to_string(config_path()?).await {
Ok(config_string) => config_string, Ok(config_string) => config_string,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => { Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(CliConfig::default()); return Ok(CliConfig::default());
@ -49,7 +51,7 @@ pub async fn read_config() -> anyhow::Result<CliConfig> {
#[instrument(level = "trace")] #[instrument(level = "trace")]
pub async fn write_config(config: &CliConfig) -> anyhow::Result<()> { pub async fn write_config(config: &CliConfig) -> anyhow::Result<()> {
let config_string = toml::to_string(config).context("failed to serialize config")?; let config_string = toml::to_string(config).context("failed to serialize config")?;
fs::write(home_dir()?.join("config.toml"), config_string) fs::write(config_path()?, config_string)
.await .await
.context("failed to write config file")?; .context("failed to write config file")?;

View file

@ -301,8 +301,11 @@ pub async fn install(
root_progress.set_message("download"); root_progress.set_message("download");
root_progress.set_style(reporters::root_progress_style_with_progress()); root_progress.set_style(reporters::root_progress_style_with_progress());
let bin_dir = bin_dir()?;
fs::create_dir_all(&bin_dir).await.context("failed to create bin directory")?;
let hooks = InstallHooks { let hooks = InstallHooks {
bin_folder: bin_dir().await?, bin_folder: bin_dir,
}; };
#[allow(unused_variables)] #[allow(unused_variables)]

View file

@ -3,7 +3,6 @@ use crate::cli::{
style::{ERROR_STYLE, INFO_STYLE, WARN_STYLE}, style::{ERROR_STYLE, INFO_STYLE, WARN_STYLE},
}; };
use anyhow::Context as _; use anyhow::Context as _;
use fs_err::tokio as fs;
use futures::StreamExt as _; use futures::StreamExt as _;
use pesde::{ use pesde::{
errors::ManifestReadError, errors::ManifestReadError,
@ -39,20 +38,31 @@ pub mod style;
#[cfg(feature = "version-management")] #[cfg(feature = "version-management")]
pub mod version; pub mod version;
pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME")); pub const PESDE_DIR: &str = concat!(".", env!("CARGO_PKG_NAME"));
pub fn home_dir() -> anyhow::Result<PathBuf> { fn base_dir() -> anyhow::Result<PathBuf> {
Ok(dirs::home_dir() Ok(match std::env::var("PESDE_HOME") {
.context("failed to get home directory")? Ok(base) => PathBuf::from(base),
.join(HOME_DIR)) _ => dirs::home_dir()
.context("failed to get home directory")?
.join(PESDE_DIR),
})
} }
pub async fn bin_dir() -> anyhow::Result<PathBuf> { pub fn bin_dir() -> anyhow::Result<PathBuf> {
let bin_dir = home_dir()?.join("bin"); Ok(base_dir()?.join("bin"))
fs::create_dir_all(&bin_dir) }
.await
.context("failed to create bin folder")?; pub fn engines_dir() -> anyhow::Result<PathBuf> {
Ok(bin_dir) Ok(base_dir()?.join("engines"))
}
pub fn config_path() -> anyhow::Result<PathBuf> {
Ok(base_dir()?.join("config.toml"))
}
pub fn data_dir() -> anyhow::Result<PathBuf> {
Ok(base_dir()?.join("data"))
} }
pub fn resolve_overrides( pub fn resolve_overrides(

View file

@ -3,7 +3,6 @@ use crate::{
bin_dir, bin_dir,
config::{read_config, write_config, CliConfig}, config::{read_config, write_config, CliConfig},
files::make_executable, files::make_executable,
home_dir,
style::{ADDED_STYLE, CLI_STYLE, REMOVED_STYLE, URL_STYLE}, style::{ADDED_STYLE, CLI_STYLE, REMOVED_STYLE, URL_STYLE},
}, },
util::no_build_metadata, util::no_build_metadata,
@ -32,6 +31,8 @@ use std::{
}; };
use tracing::instrument; use tracing::instrument;
use super::engines_dir;
pub fn current_version() -> Version { pub fn current_version() -> Version {
Version::parse(env!("CARGO_PKG_VERSION")).unwrap() Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
} }
@ -115,12 +116,10 @@ pub async fn check_for_updates(reqwest: &reqwest::Client) -> anyhow::Result<()>
Ok(()) Ok(())
} }
const ENGINES_DIR: &str = "engines";
#[instrument(level = "trace")] #[instrument(level = "trace")]
async fn get_installed_versions(engine: EngineKind) -> anyhow::Result<BTreeSet<Version>> { async fn get_installed_versions(engine: EngineKind) -> anyhow::Result<BTreeSet<Version>> {
let source = engine.source(); let source = engine.source();
let path = home_dir()?.join(ENGINES_DIR).join(source.directory()); let path = engines_dir()?.join(source.directory());
let mut installed_versions = BTreeSet::new(); let mut installed_versions = BTreeSet::new();
let mut read_dir = match fs::read_dir(&path).await { let mut read_dir = match fs::read_dir(&path).await {
@ -152,7 +151,7 @@ pub async fn get_or_download_engine(
reporter: Arc<impl DownloadsReporter>, reporter: Arc<impl DownloadsReporter>,
) -> anyhow::Result<(PathBuf, Version)> { ) -> anyhow::Result<(PathBuf, Version)> {
let source = engine.source(); let source = engine.source();
let path = home_dir()?.join(ENGINES_DIR).join(source.directory()); let path = engines_dir()?.join(source.directory());
let installed_versions = get_installed_versions(engine).await?; let installed_versions = get_installed_versions(engine).await?;
@ -229,8 +228,7 @@ pub async fn get_or_download_engine(
#[instrument(level = "trace")] #[instrument(level = "trace")]
pub async fn replace_pesde_bin_exe(with: &Path) -> anyhow::Result<()> { pub async fn replace_pesde_bin_exe(with: &Path) -> anyhow::Result<()> {
let bin_exe_path = bin_dir() let bin_exe_path = bin_dir()?
.await?
.join(EngineKind::Pesde.to_string()) .join(EngineKind::Pesde.to_string())
.with_extension(std::env::consts::EXE_EXTENSION); .with_extension(std::env::consts::EXE_EXTENSION);
@ -255,6 +253,12 @@ pub async fn replace_pesde_bin_exe(with: &Path) -> anyhow::Result<()> {
} }
} }
if let Some(parent) = bin_exe_path.parent() {
fs::create_dir_all(parent)
.await
.context("failed to create bin directory")?;
}
fs::copy(with, &bin_exe_path) fs::copy(with, &bin_exe_path)
.await .await
.context("failed to copy executable to bin folder")?; .context("failed to copy executable to bin folder")?;
@ -264,14 +268,19 @@ pub async fn replace_pesde_bin_exe(with: &Path) -> anyhow::Result<()> {
#[instrument(level = "trace")] #[instrument(level = "trace")]
pub async fn make_linker_if_needed(engine: EngineKind) -> anyhow::Result<()> { pub async fn make_linker_if_needed(engine: EngineKind) -> anyhow::Result<()> {
let bin_dir = bin_dir().await?; let linker = bin_dir()?
let linker = bin_dir
.join(engine.to_string()) .join(engine.to_string())
.with_extension(std::env::consts::EXE_EXTENSION); .with_extension(std::env::consts::EXE_EXTENSION);
if fs::metadata(&linker).await.is_err() { if fs::metadata(&linker).await.is_err() {
let exe = current_exe().context("failed to get current exe path")?; let exe = current_exe().context("failed to get current exe path")?;
if let Some(parent) = linker.parent() {
fs::create_dir_all(parent)
.await
.context("failed to create linker directory")?;
}
#[cfg(windows)] #[cfg(windows)]
let result = fs::symlink_file(exe, linker); let result = fs::symlink_file(exe, linker);
#[cfg(not(windows))] #[cfg(not(windows))]

View file

@ -1,8 +1,9 @@
#[cfg(feature = "version-management")] #[cfg(feature = "version-management")]
use crate::cli::version::{check_for_updates, current_version, get_or_download_engine}; use crate::cli::version::{check_for_updates, current_version, get_or_download_engine};
use crate::cli::{auth::get_tokens, display_err, home_dir, style::ERROR_STYLE, HOME_DIR}; use crate::cli::{auth::get_tokens, display_err, style::ERROR_STYLE, PESDE_DIR};
use anyhow::Context as _; use anyhow::Context as _;
use clap::{builder::styling::AnsiColor, Parser}; use clap::{builder::styling::AnsiColor, Parser};
use cli::data_dir;
use fs_err::tokio as fs; use fs_err::tokio as fs;
use indicatif::MultiProgress; use indicatif::MultiProgress;
use pesde::{ use pesde::{
@ -207,15 +208,6 @@ async fn run() -> anyhow::Result<()> {
if bin_folder.file_name().is_some_and(|parent| parent != "bin") { if bin_folder.file_name().is_some_and(|parent| parent != "bin") {
break 'scripts; break 'scripts;
} }
// we're not in {path}/.pesde/bin/{exe}
if bin_folder
.parent()
.and_then(|home_folder| home_folder.file_name())
.is_some_and(|home_folder| home_folder != HOME_DIR)
{
break 'scripts;
}
} }
let linker_file_name = format!("{exe_name}.bin.luau"); let linker_file_name = format!("{exe_name}.bin.luau");
@ -287,27 +279,17 @@ async fn run() -> anyhow::Result<()> {
std::process::exit(status.code().unwrap_or(1i32)); std::process::exit(status.code().unwrap_or(1i32));
}; };
let home_dir = home_dir()?; let cas_dir = get_linkable_dir(&project_root_dir)
let data_dir = home_dir.join("data");
fs::create_dir_all(&data_dir)
.await .await
.expect("failed to create data directory"); .join(PESDE_DIR)
.join("cas");
let cas_dir = get_linkable_dir(&project_root_dir).await.join(HOME_DIR);
let cas_dir = if cas_dir == home_dir {
&data_dir
} else {
&cas_dir
}
.join("cas");
tracing::debug!("using cas dir in {}", cas_dir.display()); tracing::debug!("using cas dir in {}", cas_dir.display());
let project = Project::new( let project = Project::new(
project_root_dir, project_root_dir,
project_workspace_dir, project_workspace_dir,
data_dir, data_dir()?,
cas_dir, cas_dir,
AuthConfig::new().with_tokens(get_tokens().await?.0), AuthConfig::new().with_tokens(get_tokens().await?.0),
); );