Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added env._.python.venv directive #1706

Merged
merged 1 commit into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions e2e/test_python
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export MISE_EXPERIMENTAL=1
export MISE_PYTHON_DEFAULT_PACKAGES_FILE="$ROOT/e2e/.default-python-packages"

cat >.e2e.mise.toml <<EOF
[env._.python]
venv = {path = "{{env.MISE_DATA_DIR}}/venv", create=true}
[tools]
python = {version = "{{exec(command='echo 3.12.0')}}", virtualenv="{{env.MISE_DATA_DIR}}/venv"}
python = "{{exec(command='echo 3.12.0')}}"
EOF

mise i
mise x -- python -m venv "$MISE_DATA_DIR/venv"
#mise x -- python -m venv "$MISE_DATA_DIR/venv"
assert_contains "mise x [email protected] -- python --version" "Python 3.12.0"
assert_contains "mise env -s bash | grep VIRTUAL_ENV" "$MISE_DATA_DIR/venv"
assert "mise x -- which python" "$MISE_DATA_DIR/venv/bin/python"
Expand Down
27 changes: 27 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,33 @@
}
}
]
},
"python": {
"type": "object",
"description": "python environment",
"properties": {
"venv": {
"oneOf": [
{"description": "path to python virtual environment to use", "type": "string"},
{
"description": "virtualenv options",
"type": "object",
"required": ["path"],
"properties": {
"path": {
"description": "path to python virtual environment to use",
"type": "string"
},
"create": {
"description": "create a new virtual environment if one does not exist",
"type": "boolean",
"default": false
}
}
}
]
}
}
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/cli/settings/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ mod tests {
python_compile = false
python_default_packages_file = "~/.default-python-packages"
python_pyenv_repo = "https://github.com/pyenv/pyenv.git"
python_venv_auto_create = false
quiet = false
raw = false
trusted_config_paths = []
Expand Down Expand Up @@ -129,7 +128,6 @@ mod tests {
python_compile
python_default_packages_file
python_pyenv_repo
python_venv_auto_create
quiet
raw
status
Expand Down
1 change: 0 additions & 1 deletion src/cli/settings/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ pub mod tests {
python_compile = false
python_default_packages_file = "~/.default-python-packages"
python_pyenv_repo = "https://github.com/pyenv/pyenv.git"
python_venv_auto_create = false
quiet = false
raw = false
trusted_config_paths = []
Expand Down
1 change: 0 additions & 1 deletion src/cli/settings/unset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ mod tests {
python_compile = false
python_default_packages_file = "~/.default-python-packages"
python_pyenv_repo = "https://github.com/pyenv/pyenv.git"
python_venv_auto_create = false
quiet = false
raw = false
trusted_config_paths = []
Expand Down
88 changes: 88 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,18 @@ impl<'de> de::Deserialize<'de> for EnvList {
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"_" | "mise" => {
struct EnvDirectivePythonVenv {
path: PathBuf,
create: bool,
}

#[derive(Deserialize, Default)]
#[serde(deny_unknown_fields)]
struct EnvDirectivePython {
#[serde(default)]
venv: Option<EnvDirectivePythonVenv>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct EnvDirectives {
Expand All @@ -435,7 +447,77 @@ impl<'de> de::Deserialize<'de> for EnvList {
file: Vec<PathBuf>,
#[serde(default, deserialize_with = "deserialize_arr")]
source: Vec<PathBuf>,
#[serde(default)]
python: EnvDirectivePython,
}

impl<'de> de::Deserialize<'de> for EnvDirectivePythonVenv {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EnvDirectivePythonVenvVisitor;

impl<'de> Visitor<'de> for EnvDirectivePythonVenvVisitor {
type Value = EnvDirectivePythonVenv;
fn expecting(
&self,
formatter: &mut Formatter,
) -> std::fmt::Result
{
formatter.write_str("python venv directive")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(EnvDirectivePythonVenv {
path: v.into(),
create: false,
})
}

fn visit_map<M>(
self,
mut map: M,
) -> Result<Self::Value, M::Error>
where
M: de::MapAccess<'de>,
{
let mut path = None;
let mut create = false;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"path" => {
path = Some(map.next_value()?);
}
"create" => {
create = map.next_value()?;
}
_ => {
return Err(de::Error::unknown_field(
&key,
&["path", "create"],
));
}
}
}
let path = path
.ok_or_else(|| de::Error::missing_field("path"))?;
Ok(EnvDirectivePythonVenv { path, create })
}
}

const FIELDS: &[&str] = &["path", "create"];
deserializer.deserialize_struct(
"PythonVenv",
FIELDS,
EnvDirectivePythonVenvVisitor,
)
}
}

let directives = map.next_value::<EnvDirectives>()?;
// TODO: parse these in the order they're defined somehow
for path in directives.path {
Expand All @@ -447,6 +529,12 @@ impl<'de> de::Deserialize<'de> for EnvList {
for source in directives.source {
env.push(EnvDirective::Source(source));
}
if let Some(venv) = directives.python.venv {
env.push(EnvDirective::PythonVenv {
path: venv.path,
create: venv.create,
});
}
}
_ => {
enum Val {
Expand Down
77 changes: 70 additions & 7 deletions src/config/env_directive.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use std::collections::{BTreeSet, HashMap};
use std::fmt::Display;
use std::path::{Path, PathBuf};

use eyre::Context;
use indexmap::IndexMap;

use crate::cmd::CmdLineRunner;
use crate::config::config_file::trust_check;
use crate::config::Settings;
use crate::config::{Config, Settings};
use crate::env_diff::{EnvDiff, EnvDiffOperation};
use crate::file::display_path;
use crate::tera::{get_tera, BASE_CONTEXT};
use crate::toolset::ToolsetBuilder;
use crate::{dirs, env};
use eyre::Context;
use indexmap::IndexMap;
use std::collections::{BTreeSet, HashMap};
use std::fmt::Display;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub enum EnvDirective {
Expand All @@ -22,6 +26,10 @@ pub enum EnvDirective {
Path(PathBuf),
/// run a bash script and apply the resulting env diff
Source(PathBuf),
PythonVenv {
path: PathBuf,
create: bool,
},
}

impl From<(String, String)> for EnvDirective {
Expand All @@ -44,6 +52,13 @@ impl Display for EnvDirective {
EnvDirective::File(path) => write!(f, "dotenv {}", display_path(path)),
EnvDirective::Path(path) => write!(f, "path_add {}", display_path(path)),
EnvDirective::Source(path) => write!(f, "source {}", display_path(path)),
EnvDirective::PythonVenv { path, create } => {
write!(f, "python venv path={}", display_path(path))?;
if *create {
write!(f, " create")?;
}
Ok(())
}
}
}
}
Expand Down Expand Up @@ -143,6 +158,53 @@ impl EnvResults {
}
}
}
EnvDirective::PythonVenv { path, create } => {
trace!("python venv: {} create={create}", display_path(&path));
trust_check(&source)?;
let venv = r.parse_template(&ctx, &source, path.to_string_lossy().as_ref())?;
let venv = normalize_path(venv.into());
if !venv.exists() && create {
// TODO: the toolset stuff doesn't feel like it's in the right place here
let config = Config::get();
let ts = ToolsetBuilder::new().build(&config)?;
let path = ts
.list_paths()
.into_iter()
.chain(env::split_paths(&env_vars["PATH"]))
.collect::<Vec<_>>();
let cmd = CmdLineRunner::new("python3")
.args(["-m", "venv", &venv.to_string_lossy()])
.envs(&env_vars)
.env(
"PATH",
env::join_paths(&path)?.to_string_lossy().to_string(),
);
if ts
.list_missing_versions()
.iter()
.any(|tv| tv.forge.name == "python")
{
debug!("python not installed, skipping venv creation");
} else {
info!("creating venv at: {}", display_path(&venv));
cmd.execute()?;
}
}
if venv.exists() {
r.env_paths.push(venv.join("bin"));
env.insert(
"VIRTUAL_ENV".into(),
(venv.to_string_lossy().to_string(), Some(source.clone())),
);
} else {
warn!(
"no venv found at: {p}\n\n\
To create a virtualenv manually, run:\n\
python -m venv {p}",
p = display_path(&venv)
);
}
}
};
}
for (k, (v, source)) in env {
Expand Down Expand Up @@ -173,9 +235,10 @@ impl EnvResults {

#[cfg(test)]
mod tests {
use super::*;
use crate::test::replace_path;

use super::*;

#[test]
fn test_env_path() {
let mut env = HashMap::new();
Expand Down
18 changes: 14 additions & 4 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ pub struct Settings {
pub python_precompiled_os: Option<String>,
#[config(env = "MISE_PYENV_REPO", default = "https://github.com/pyenv/pyenv.git")]
pub python_pyenv_repo: String,
#[config(env = "MISE_PYTHON_VENV_AUTO_CREATE", default = false)]
pub python_venv_auto_create: bool,
#[config(env = "MISE_RAW", default = false)]
pub raw: bool,
#[config(env = "MISE_SHORTHANDS_FILE")]
Expand Down Expand Up @@ -135,6 +133,8 @@ pub struct Settings {
pub trace: bool,
#[config(env = "MISE_LOG_LEVEL", default = "info")]
pub log_level: String,
#[config(env = "MISE_PYTHON_VENV_AUTO_CREATE", default = false)]
pub python_venv_auto_create: bool,
}

#[derive(Config, Default, Debug, Clone, Serialize)]
Expand Down Expand Up @@ -349,8 +349,18 @@ impl Settings {
}

pub fn hidden_configs() -> &'static HashSet<&'static str> {
static HIDDEN_CONFIGS: Lazy<HashSet<&'static str>> =
Lazy::new(|| ["ci", "cd", "debug", "env_file", "trace", "log_level"].into());
static HIDDEN_CONFIGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
[
"ci",
"cd",
"debug",
"env_file",
"trace",
"log_level",
"python_venv_auto_create",
]
.into()
});
&HIDDEN_CONFIGS
}

Expand Down
2 changes: 0 additions & 2 deletions src/plugins/core/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,6 @@ impl PythonPlugin {
} else {
warn!(
"no venv found at: {p}\n\n\
To have mise automatically create virtualenvs, run:\n\
mise settings set python_venv_auto_create true\n\n\
To create a virtualenv manually, run:\n\
python -m venv {p}",
p = display_path(&virtualenv)
Expand Down
8 changes: 4 additions & 4 deletions tests/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,12 @@ fn test_python() -> Result<()> {
fn python_config_fixture() -> File {
File {
path: ".mise.toml".into(),
content: toml::toml!{
[settings]
python_venv_auto_create = true
content: toml::toml! {
[env]
"_".python.venv = {path="{{env.MISE_DATA_DIR}}/venv", create=true}

[tools]
python = {version = "{{exec(command='echo 3.12.0')}}", virtualenv="{{env.MISE_DATA_DIR}}/venv"}
python = "{{exec(command='echo 3.12.0')}}"
}.to_string(),
}
}
Expand Down