Skip to content

Commit

Permalink
Traverse up from the cwd to look for virtual environments
Browse files Browse the repository at this point in the history
Closes #75
  • Loading branch information
brettcannon committed Feb 27, 2021
1 parent 8e29b9f commit a267694
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ to have it output logging details of how it is performing its search.
### `py` (any version)

1. Use `${VIRTUAL_ENV}/bin/python` immediately if available
1. Use `.venv/bin/python` immediately if available
1. Use `.venv/bin/python` if available in the current working directory or any
of its parent directories
1. If the first argument is a file path ...
1. Check for a shebang
1. If shebang path starts with `/usr/bin/python`, `/usr/local/bin/python`,
Expand Down
38 changes: 23 additions & 15 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,23 @@ fn list_executables(executables: &HashMap<ExactVersion, PathBuf>) -> crate::Resu
Ok(table.to_string() + "\n")
}

fn relative_venv_path(add_default: bool) -> PathBuf {
let mut path = PathBuf::new();
if add_default {
path.push(DEFAULT_VENV_DIR);
}
path.push("bin");
path.push("python");
path
}

/// Returns the path to the activated virtual environment's executable.
///
/// A virtual environment is determined to be activated based on the
/// existence of the `VIRTUAL_ENV` environment variable.
fn venv_executable_path(venv_root: &str) -> PathBuf {
let mut path = PathBuf::new();
path.push(venv_root);
path.push("bin");
path.push("python");
PathBuf::from(venv_root).join(relative_venv_path(false))
// XXX: Do a is_file() check first?
path
}

fn activated_venv() -> Option<PathBuf> {
Expand All @@ -145,20 +151,22 @@ fn activated_venv() -> Option<PathBuf> {
})
}

fn venv_in_dir() -> Option<PathBuf> {
log::info!("Checking for a venv in {:?}", DEFAULT_VENV_DIR);
let venv_path = venv_executable_path(DEFAULT_VENV_DIR);
venv_path.exists().then(|| {
log::debug!(
"Virtual environment executable found in {}",
venv_path.display()
);
venv_path
fn venv_path_search() -> Option<PathBuf> {
let cwd = env::current_dir().unwrap();
log::info!(
"Searching for a venv in {} and parent directories",
cwd.display()
);
cwd.ancestors().find_map(|path| {
let venv_path = path.join(relative_venv_path(true));
log::info!("Checking {}", venv_path.display());
// bool::then_some() makes more sense, but still experimental.
venv_path.is_file().then(|| venv_path)
})
}

fn venv_executable() -> Option<PathBuf> {
activated_venv().or_else(venv_in_dir)
activated_venv().or_else(venv_path_search)
}

// https://en.m.wikipedia.org/wiki/Shebang_(Unix)
Expand Down
40 changes: 38 additions & 2 deletions tests/cli_system_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod common;

use std::env;
use std::fs;
use std::fs::File;
use std::io::Write;
Expand Down Expand Up @@ -156,7 +157,7 @@ fn from_main_activated_virtual_env() {

#[test]
#[serial]
fn from_main_default_venv_path() {
fn from_main_default_cwd_venv_path() {
let _working_dir = common::CurrentDir::new();
let env_state = common::EnvState::new();
let mut expected = PathBuf::new();
Expand All @@ -168,7 +169,42 @@ fn from_main_default_venv_path() {

match Action::from_main(&["/path/to/py".to_string()]) {
Ok(Action::Execute { executable, .. }) => {
assert_eq!(executable, expected);
assert_eq!(executable, expected.canonicalize().unwrap());
}
_ => panic!("No executable found in default virtual environment case"),
}

// VIRTUAL_ENV gets ignored if any specific version is requested.
match Action::from_main(&["/path/to/py".to_string(), "-3".to_string()]) {
Ok(Action::Execute { executable, .. }) => {
assert_eq!(executable, env_state.python37);
}
_ => panic!("No executable found in default virtual environment case"),
}
}

#[test]
#[serial]
fn from_main_default_parent_venv_path() {
let working_dir = common::CurrentDir::new();
let temp_dir = working_dir.dir.path().to_path_buf();
let env_state = common::EnvState::new();
let mut expected = temp_dir.clone();
expected.push(cli::DEFAULT_VENV_DIR);
expected.push("bin");
fs::create_dir_all(&expected).unwrap();
expected.push("python");
common::touch_file(expected.clone());

let subdir = temp_dir.join("subdir");
fs::create_dir(&subdir).unwrap();
env::set_current_dir(&subdir).unwrap();

// XXX Change working dir

match Action::from_main(&["/path/to/py".to_string()]) {
Ok(Action::Execute { executable, .. }) => {
assert_eq!(executable, expected.canonicalize().unwrap());
}
_ => panic!("No executable found in default virtual environment case"),
}
Expand Down

0 comments on commit a267694

Please sign in to comment.