diff --git a/src/app.rs b/src/app.rs index dcadec7..3303345 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,5 @@ use std::env; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::{Context, Result}; use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _}; @@ -7,6 +7,7 @@ use directories::ProjectDirs; use once_cell::sync::OnceCell; static PLATFORM_DIRS: OnceCell = OnceCell::new(); +static INSTALLATION_DIRECTORY: OnceCell = OnceCell::new(); fn platform_dirs() -> &'static ProjectDirs { PLATFORM_DIRS @@ -14,13 +15,28 @@ fn platform_dirs() -> &'static ProjectDirs { .expect("platform directories are not initialized") } +pub fn install_dir() -> &'static PathBuf { + INSTALLATION_DIRECTORY + .get() + .expect("installation directory is not initialized") +} + pub fn initialize() -> Result<()> { - let platform_dirs = ProjectDirs::from("", "", "pyapp") + let platform_directories = ProjectDirs::from("", "", "pyapp") .with_context(|| "unable to find platform directories")?; PLATFORM_DIRS - .set(platform_dirs) + .set(platform_directories) .expect("could not set platform directories"); + let installation_directory = platform_dirs() + .data_local_dir() + .join(project_name()) + .join(distribution_id()) + .join(project_version()); + INSTALLATION_DIRECTORY + .set(installation_directory) + .expect("could not set installation directory"); + Ok(()) } @@ -143,34 +159,24 @@ pub fn metadata_template() -> String { env!("PYAPP_METADATA_TEMPLATE").into() } -pub fn python_path(installation_directory: &Path) -> PathBuf { - installation_directory.join(installation_python_path()) +pub fn python_path() -> PathBuf { + install_dir().join(installation_python_path()) } -pub fn site_packages_path(installation_directory: &Path) -> PathBuf { - installation_directory.join(installation_site_packages_path()) +pub fn site_packages_path() -> PathBuf { + install_dir().join(installation_site_packages_path()) } -pub fn cache_directory() -> PathBuf { +pub fn cache_dir() -> PathBuf { platform_dirs().cache_dir().to_path_buf() } -pub fn storage_directory() -> PathBuf { - platform_dirs().data_local_dir().join(project_name()) -} - -pub fn installation_directory() -> PathBuf { - storage_directory() - .join(distribution_id()) - .join(project_version()) -} - pub fn distributions_cache() -> PathBuf { - cache_directory().join("distributions") + cache_dir().join("distributions") } pub fn external_pip_cache() -> PathBuf { - cache_directory().join("pip") + cache_dir().join("pip") } pub fn external_pip_zipapp() -> PathBuf { diff --git a/src/commands/self_cmd/metadata.rs b/src/commands/self_cmd/metadata.rs index 06cf4b4..3aefd6a 100644 --- a/src/commands/self_cmd/metadata.rs +++ b/src/commands/self_cmd/metadata.rs @@ -12,12 +12,11 @@ pub struct Cli {} impl Cli { pub fn exec(self) -> Result<()> { - let installation_directory = app::installation_directory(); - if !installation_directory.is_dir() { + if !app::install_dir().is_dir() { return Ok(()); } - let site_packages = app::site_packages_path(&installation_directory); + let site_packages = app::site_packages_path(); let expected_prefix = format!("{}-", app::project_name().replace('-', "_")); let metadata_file = fs::read_dir(site_packages).ok().and_then(|entries| { diff --git a/src/commands/self_cmd/pip.rs b/src/commands/self_cmd/pip.rs index 77e773a..1dd2064 100644 --- a/src/commands/self_cmd/pip.rs +++ b/src/commands/self_cmd/pip.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use clap::Args; -use crate::{app, distribution, process}; +use crate::{distribution, process}; /// Directly invoke pip with the installed Python #[derive(Args, Debug)] @@ -13,10 +13,9 @@ pub struct Cli { impl Cli { pub fn exec(self) -> Result<()> { - let installation_directory = app::installation_directory(); - distribution::ensure_ready(&installation_directory)?; + distribution::ensure_ready()?; - let mut command = distribution::pip_base_command(&installation_directory); + let mut command = distribution::pip_base_command(); command.args(self.args); process::exec(command) diff --git a/src/commands/self_cmd/python.rs b/src/commands/self_cmd/python.rs index 82b03f2..c614b5d 100644 --- a/src/commands/self_cmd/python.rs +++ b/src/commands/self_cmd/python.rs @@ -13,10 +13,9 @@ pub struct Cli { impl Cli { pub fn exec(self) -> Result<()> { - let installation_directory = app::installation_directory(); - distribution::ensure_ready(&installation_directory)?; + distribution::ensure_ready()?; - let mut command = distribution::python_command(&app::python_path(&installation_directory)); + let mut command = distribution::python_command(&app::python_path()); command.args(self.args); process::exec(command) diff --git a/src/commands/self_cmd/python_path.rs b/src/commands/self_cmd/python_path.rs index 87e5b4e..364f733 100644 --- a/src/commands/self_cmd/python_path.rs +++ b/src/commands/self_cmd/python_path.rs @@ -10,8 +10,7 @@ pub struct Cli {} impl Cli { pub fn exec(self) -> Result<()> { - let installation_directory = app::installation_directory(); - println!("{}", app::python_path(&installation_directory).display()); + println!("{}", app::python_path().display()); Ok(()) } diff --git a/src/commands/self_cmd/restore.rs b/src/commands/self_cmd/restore.rs index 02257ac..d7d7540 100644 --- a/src/commands/self_cmd/restore.rs +++ b/src/commands/self_cmd/restore.rs @@ -12,14 +12,13 @@ pub struct Cli {} impl Cli { pub fn exec(self) -> Result<()> { - let installation_directory = app::installation_directory(); - if installation_directory.is_dir() { + if app::install_dir().is_dir() { let spinner = terminal::spinner("Removing installation".to_string()); - let result = fs::remove_dir_all(&installation_directory); + let result = fs::remove_dir_all(app::install_dir()); spinner.finish_and_clear(); result?; } - distribution::ensure_ready(&installation_directory)?; + distribution::ensure_ready()?; Ok(()) } diff --git a/src/commands/self_cmd/update.rs b/src/commands/self_cmd/update.rs index e9cd9f4..3c74cf3 100644 --- a/src/commands/self_cmd/update.rs +++ b/src/commands/self_cmd/update.rs @@ -26,19 +26,18 @@ impl Cli { exit(1); } - let installation_directory = app::installation_directory(); - let existing_installation = installation_directory.is_dir(); + let existing_installation = app::install_dir().is_dir(); if !existing_installation { - distribution::materialize(&installation_directory)?; + distribution::materialize()?; } else if self.restore { let spinner = terminal::spinner("Removing installation".to_string()); - let result = fs::remove_dir_all(&installation_directory); + let result = fs::remove_dir_all(app::install_dir()); spinner.finish_and_clear(); result?; - distribution::materialize(&installation_directory)?; + distribution::materialize()?; } - let mut command = distribution::pip_install_command(&installation_directory); + let mut command = distribution::pip_install_command(); if self.pre { command.arg("--pre"); } @@ -48,19 +47,14 @@ impl Cli { let dependency_file = app::project_dependency_file(); let (status, output) = if dependency_file.is_empty() { command.arg(app::project_name().as_str()); - distribution::pip_install(command, wait_message, &installation_directory)? + distribution::pip_install(command, wait_message)? } else { - distribution::pip_install_dependency_file( - &dependency_file, - command, - wait_message, - &installation_directory, - )? + distribution::pip_install_dependency_file(&dependency_file, command, wait_message)? }; if !status.success() { if !existing_installation { - fs::remove_dir_all(&installation_directory).ok(); + fs::remove_dir_all(app::install_dir()).ok(); } println!("{}", output.trim_end()); exit(status.code().unwrap_or(1)); diff --git a/src/distribution.rs b/src/distribution.rs index d03acd1..78bb421 100644 --- a/src/distribution.rs +++ b/src/distribution.rs @@ -16,8 +16,8 @@ pub fn python_command(python: &PathBuf) -> Command { command } -pub fn run_project(installation_directory: &Path) -> Result<()> { - let mut command = python_command(&app::python_path(installation_directory)); +pub fn run_project() -> Result<()> { + let mut command = python_command(&app::python_path()); if app::exec_module().is_empty() { command.args(["-c", app::exec_code().as_str()]); @@ -42,20 +42,20 @@ pub fn run_project(installation_directory: &Path) -> Result<()> { .with_context(|| "project execution failed, consider restoring from scratch") } -pub fn ensure_ready(installation_directory: &PathBuf) -> Result<()> { - if !installation_directory.is_dir() { - materialize(installation_directory)?; +pub fn ensure_ready() -> Result<()> { + if !app::install_dir().is_dir() { + materialize()?; if !app::skip_install() { - install_project(installation_directory)?; + install_project()?; } } Ok(()) } -pub fn pip_base_command(installation_directory: &Path) -> Command { - let mut command = python_command(&app::python_path(installation_directory)); +pub fn pip_base_command() -> Command { + let mut command = python_command(&app::python_path()); if app::pip_external() { let external_pip = app::external_pip_zipapp(); command.arg(external_pip.to_string_lossy().as_ref()); @@ -66,8 +66,8 @@ pub fn pip_base_command(installation_directory: &Path) -> Command { command } -pub fn pip_install_command(installation_directory: &Path) -> Command { - let mut command = pip_base_command(installation_directory); +pub fn pip_install_command() -> Command { + let mut command = pip_base_command(); command.args([ "install", @@ -87,7 +87,7 @@ pub fn pip_install_command(installation_directory: &Path) -> Command { command } -pub fn materialize(installation_directory: &PathBuf) -> Result<()> { +pub fn materialize() -> Result<()> { let distributions_dir = app::distributions_cache(); let distribution_file = distributions_dir.join(app::distribution_id()); @@ -128,19 +128,19 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> { compression::unpack( app::distribution_format(), &distribution_file, - installation_directory, + app::install_dir(), ) .or_else(|err| { - fs::remove_dir_all(installation_directory).ok(); + fs::remove_dir_all(app::install_dir()).ok(); bail!( "unable to unpack to {}\n{}", - &installation_directory.display(), + &app::install_dir().display(), err ); })?; if !app::skip_install() { - ensure_base_pip(installation_directory, installation_directory)?; + ensure_base_pip(app::install_dir())?; } } else { let unpacked_distribution = distributions_dir.join(format!("_{}", app::distribution_id())); @@ -164,7 +164,7 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> { python_command(&unpacked_distribution.join(app::distribution_python_path())); if app::upgrade_virtualenv() { - ensure_base_pip(&unpacked_distribution, installation_directory)?; + ensure_base_pip(&unpacked_distribution)?; let mut upgrade_command = python_command(&unpacked_distribution.join(app::distribution_python_path())); @@ -178,12 +178,9 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> { "--no-warn-script-location", "virtualenv", ]); - let (status, output) = run_setup_command( - upgrade_command, - "Upgrading virtualenv".to_string(), - installation_directory, - )?; - check_setup_status(status, output, installation_directory)?; + let (status, output) = + run_setup_command(upgrade_command, "Upgrading virtualenv".to_string())?; + check_setup_status(status, output)?; command.args(["-m", "virtualenv"]); if app::pip_external() { @@ -196,24 +193,21 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> { } } - command.arg(installation_directory.to_string_lossy().as_ref()); - let (status, output) = run_setup_command( - command, - "Creating virtual environment".to_string(), - installation_directory, - )?; - check_setup_status(status, output, installation_directory)?; + command.arg(app::install_dir().to_string_lossy().as_ref()); + let (status, output) = + run_setup_command(command, "Creating virtual environment".to_string())?; + check_setup_status(status, output)?; } Ok(()) } -fn install_project(installation_directory: &Path) -> Result<()> { +fn install_project() -> Result<()> { let install_target = format!("{} {}", app::project_name(), app::project_version()); let binary_only = app::pip_extra_args().contains("--only-binary :all:") || app::pip_extra_args().contains("--only-binary=:all:"); - let mut command = pip_install_command(installation_directory); + let mut command = pip_install_command(); let (status, output) = if !app::embedded_project().is_empty() { let dir = tempdir().with_context(|| "unable to create temporary directory")?; let file_name = app::project_embed_file_name(); @@ -236,7 +230,7 @@ fn install_project(installation_directory: &Path) -> Result<()> { } else { format!("Installing {}", install_target) }; - pip_install(command, wait_message, installation_directory) + pip_install(command, wait_message) } else { let wait_message = if binary_only { format!("Unpacking {}", install_target) @@ -247,35 +241,25 @@ fn install_project(installation_directory: &Path) -> Result<()> { let dependency_file = app::project_dependency_file(); if dependency_file.is_empty() { command.arg(format!("{}=={}", app::project_name(), app::project_version()).as_str()); - pip_install(command, wait_message, installation_directory) + pip_install(command, wait_message) } else { - pip_install_dependency_file( - &dependency_file, - command, - wait_message, - installation_directory, - ) + pip_install_dependency_file(&dependency_file, command, wait_message) } }?; - check_setup_status(status, output, installation_directory)?; + check_setup_status(status, output)?; Ok(()) } -pub fn pip_install( - command: Command, - wait_message: String, - installation_directory: &Path, -) -> Result<(ExitStatus, String)> { +pub fn pip_install(command: Command, wait_message: String) -> Result<(ExitStatus, String)> { ensure_external_pip()?; - run_setup_command(command, wait_message, installation_directory) + run_setup_command(command, wait_message) } pub fn pip_install_dependency_file( dependency_file: &String, mut command: Command, wait_message: String, - installation_directory: &Path, ) -> Result<(ExitStatus, String)> { let dir = tempdir().with_context(|| "unable to create temporary directory")?; let file_name = app::project_dependency_file_name(); @@ -293,10 +277,10 @@ pub fn pip_install_dependency_file( command.args(["-r", temp_path.to_string_lossy().as_ref()]); ensure_external_pip()?; - run_setup_command(command, wait_message, installation_directory) + run_setup_command(command, wait_message) } -fn ensure_base_pip(distribution_directory: &Path, installation_directory: &Path) -> Result<()> { +fn ensure_base_pip(distribution_directory: &Path) -> Result<()> { if app::distribution_pip_available() { return Ok(()); } @@ -304,11 +288,7 @@ fn ensure_base_pip(distribution_directory: &Path, installation_directory: &Path) let mut command = python_command(&distribution_directory.join(app::distribution_python_path())); command.args(["-m", "ensurepip"]); - run_setup_command( - command, - "Validating pip".to_string(), - installation_directory, - )?; + run_setup_command(command, "Validating pip".to_string())?; Ok(()) } @@ -355,28 +335,20 @@ fn ensure_external_pip() -> Result<()> { fs_utils::move_temp_file(&temp_path, &external_pip) } -fn run_setup_command( - command: Command, - message: String, - installation_directory: &Path, -) -> Result<(ExitStatus, String)> { +fn run_setup_command(command: Command, message: String) -> Result<(ExitStatus, String)> { let (status, output) = process::wait_for(command, message).with_context(|| { format!( "could not run Python, verify distribution build metadata options: {}", - app::python_path(installation_directory).display() + app::python_path().display() ) })?; Ok((status, output)) } -fn check_setup_status( - status: ExitStatus, - output: String, - installation_directory: &Path, -) -> Result<()> { +fn check_setup_status(status: ExitStatus, output: String) -> Result<()> { if !status.success() { - fs::remove_dir_all(installation_directory).ok(); + fs::remove_dir_all(app::install_dir()).ok(); println!("{}", output.trim_end()); exit(status.code().unwrap_or(1)); } diff --git a/src/main.rs b/src/main.rs index 3814f6b..9d85b5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,8 @@ fn main() -> Result<()> { match env::args().nth(1).as_deref() { Some(env!("PYAPP_SELF_COMMAND")) => Cli::parse().exec(), _ => { - let installation_directory = app::installation_directory(); - distribution::ensure_ready(&installation_directory)?; - distribution::run_project(&installation_directory)?; + distribution::ensure_ready()?; + distribution::run_project()?; Ok(()) }