Skip to content
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
8 changes: 8 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,10 @@ pub static SETTINGS_META: Lazy<IndexMap<&'static str, SettingsMeta>> = Lazy::new
raw_string_literal(&description)
));
}
match props.get("env").and_then(|v| v.as_str()) {
Some(env) => lines.push(format!(" env: Some({env:?}),")),
None => lines.push(" env: None,".to_string()),
}
push_deprecated_fields(&mut lines, props);
lines.push(" },".to_string());
}
Expand All @@ -726,6 +730,10 @@ pub static SETTINGS_META: Lazy<IndexMap<&'static str, SettingsMeta>> = Lazy::new
raw_string_literal(&description)
));
}
match props.get("env").and_then(|v| v.as_str()) {
Some(env) => lines.push(format!(" env: Some({env:?}),")),
None => lines.push(" env: None,".to_string()),
}
push_deprecated_fields(&mut lines, props);
lines.push(" },".to_string());
}
Expand Down
2 changes: 2 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,8 @@ impl Cli {
// Fast-path for hook-env: exit early if nothing has changed
// This avoids expensive backend::load_tools() and config loading
if hook_env_module::should_exit_early_fast() {
measure!("logger", { logger::init() });
Settings::flush_deprecated_warnings_for_fast_exit();
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
return Ok(());
}
measure!("logger", { logger::init() });
Expand Down
186 changes: 184 additions & 2 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
use std::{
collections::{BTreeSet, HashSet},
sync::atomic::Ordering,
sync::atomic::{AtomicBool, Ordering},
};
use url::Url;

Expand All @@ -48,6 +48,7 @@ pub struct SettingsMeta {
// pub key: String,
pub type_: SettingsType,
pub description: &'static str,
pub env: Option<&'static str>,
pub deprecated: Option<&'static str>,
pub deprecated_warn_at: Option<&'static str>,
pub deprecated_remove_at: Option<&'static str>,
Expand Down Expand Up @@ -204,6 +205,9 @@ pub type SettingsPartial = <Settings as Config>::Layer;

static BASE_SETTINGS: RwLock<Option<Arc<Settings>>> = RwLock::new(None);
static CLI_SETTINGS: Mutex<Option<SettingsPartial>> = Mutex::new(None);
static PENDING_DEPRECATED_SETTINGS: Lazy<Mutex<BTreeSet<&'static str>>> =
Lazy::new(Default::default);
static DEPRECATED_WARNINGS_READY: AtomicBool = AtomicBool::new(false);
static DEFAULT_SETTINGS: Lazy<SettingsPartial> = Lazy::new(|| {
let mut s = SettingsPartial::empty();
s.python.default_packages_file = Some(env::HOME.join(".default-python-packages"));
Expand Down Expand Up @@ -247,7 +251,93 @@ fn tera_v1_from_env_config(raw: &toml::Value) -> Option<bool> {
.and_then(parse_boolish_toml_value)
}

fn warn_deprecated(key: &str) {
fn deprecated_settings_in_toml_table(settings: &toml::Table) -> Vec<&'static str> {
SETTINGS_META
.iter()
.filter_map(|(key, meta)| {
meta.deprecated?;
let value = nested_toml_value(settings, key)?;
should_warn_deprecated_value(value).then_some(*key)
})
.collect()
}

fn deprecated_settings_in_env_directives(raw: &toml::Value) -> Vec<&'static str> {
tera_v1_from_env_config(raw)
.is_some()
.then_some("tera_v1")
.into_iter()
.collect()
}

fn deprecated_settings_in_toml_config(raw: &toml::Value) -> Vec<&'static str> {
let mut deprecated = Vec::new();
if let Some(settings) = raw.get("settings").and_then(toml::Value::as_table) {
deprecated.extend(deprecated_settings_in_toml_table(settings));
}
deprecated.extend(deprecated_settings_in_env_directives(raw));
deprecated
}

fn warn_deprecated_env_settings() {
for (key, meta) in SETTINGS_META.iter() {
let Some(env_key) = meta.env else {
continue;
};
if meta.deprecated.is_some()
&& env::var_os(env_key).is_some_and(|value| !value.as_os_str().is_empty())
{
warn_deprecated(key);
}
}
}

fn queue_deprecated(key: &'static str) {
PENDING_DEPRECATED_SETTINGS.lock().unwrap().insert(key);
}

fn queue_deprecated_settings(keys: impl IntoIterator<Item = &'static str>) {
for key in keys {
warn_deprecated(key);
}
}

fn nested_toml_value<'a>(table: &'a toml::Table, key: &str) -> Option<&'a toml::Value> {
let mut parts = key.split('.').collect_vec();
let last = parts.pop()?;
let mut current = table;
for part in parts {
current = current.get(part)?.as_table()?;
}
current.get(last)
}

fn should_warn_deprecated_value(value: &toml::Value) -> bool {
match value {
toml::Value::String(value) => !value.is_empty(),
toml::Value::Array(value) => !value.is_empty(),
toml::Value::Table(value) => {
if value.get("unset").and_then(toml::Value::as_bool) == Some(true) {
return false;
}
match value.get("value") {
Some(value) => should_warn_deprecated_value(value),
None => !value.is_empty(),
}
}
_ => true,
}
}

fn warn_deprecated(key: &'static str) {
if !DEPRECATED_WARNINGS_READY.load(Ordering::SeqCst) {
queue_deprecated(key);
return;
}
warn_deprecated_now(key);
}

fn warn_deprecated_now(key: &'static str) {
if let Some(meta) = SETTINGS_META.get(key)
&& let (Some(msg), Some(warn_at), Some(remove_at)) = (
meta.deprecated,
Expand Down Expand Up @@ -329,6 +419,13 @@ impl Settings {
}

pub fn warn_default_package_file_deprecated(id: &'static str, package_type: &str) {
if SETTINGS_META
.get(id)
.is_some_and(|m| m.deprecated.is_some())
{
warn_deprecated(id);
return;
}
deprecated_at!(
"2026.11.0",
"2027.11.0",
Expand Down Expand Up @@ -458,6 +555,29 @@ impl Settings {
Ok(settings)
}

pub fn flush_deprecated_warnings() {
if CLI_SETTINGS.lock().unwrap().is_none() {
return;
}
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
Self::flush_deprecated_warnings_now();
}

pub fn flush_deprecated_warnings_for_fast_exit() {
Self::flush_deprecated_warnings_now();
}

fn flush_deprecated_warnings_now() {
DEPRECATED_WARNINGS_READY.store(true, Ordering::SeqCst);
warn_deprecated_env_settings();
let pending = {
let mut pending = PENDING_DEPRECATED_SETTINGS.lock().unwrap();
std::mem::take(&mut *pending)
};
for key in pending {
warn_deprecated_now(key);
}
}

/// Sets deprecated settings to new names
fn set_hidden_configs(&mut self) {
if let Some(v) = self.install_before.take() {
Expand Down Expand Up @@ -605,7 +725,9 @@ impl Settings {
if let Some(settings) = raw.get_mut("settings").and_then(toml::Value::as_table_mut) {
strip_local_only_settings(settings, path, crate::config::is_global_config(path));
}
let deprecated = deprecated_settings_in_toml_config(&raw);
let settings_file: SettingsFile = raw.try_into()?;
queue_deprecated_settings(deprecated);
let mut settings = normalize_hidden_config_aliases(settings_file.settings);
if settings.tera_v1.is_none() {
settings.tera_v1 = tera_v1_from_env;
Expand Down Expand Up @@ -1267,6 +1389,66 @@ mod tests {
assert_eq!(partial.tera_v1, Some(false));
}

#[test]
fn test_deprecated_settings_in_toml_table_detects_nested_settings() {
let settings = toml::from_str::<toml::Value>(
r#"
tera_v1 = true

[aqua]
registry_url = "https://example.com/aqua-registry"
"#,
)
.unwrap()
.as_table()
.unwrap()
.clone();

let deprecated = deprecated_settings_in_toml_table(&settings);

assert!(deprecated.contains(&"tera_v1"));
assert!(deprecated.contains(&"aqua.registry_url"));
}

#[test]
fn test_deprecated_settings_in_env_directives_detects_setting_env() {
let raw = toml::from_str::<toml::Value>(
r#"
[env]
MISE_TERA_V1 = true
"#,
)
.unwrap();

assert_eq!(deprecated_settings_in_env_directives(&raw), vec!["tera_v1"]);
}

#[test]
fn test_deprecated_settings_in_env_directives_ignores_unset_env() {
let raw = toml::from_str::<toml::Value>(
r#"
[env]
MISE_TERA_V1 = { unset = true }
"#,
)
.unwrap();

assert!(deprecated_settings_in_env_directives(&raw).is_empty());
}

#[test]
fn test_deprecated_settings_in_env_directives_ignores_child_only_env() {
let raw = toml::from_str::<toml::Value>(
r#"
[env]
MISE_SHORTHANDS_FILE = "~/.mise-shorthands"
"#,
)
.unwrap();

assert!(deprecated_settings_in_env_directives(&raw).is_empty());
}

#[test]
fn test_global_config_preserves_trust_controls() {
let path = Path::new("/tmp/global-config.toml");
Expand Down
59 changes: 59 additions & 0 deletions src/hook_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ pub fn should_exit_early_fast() -> bool {
if args.len() < 2 || args[1] != "hook-env" {
return false;
}
if has_preclap_logging_flag(&args) {
return false;
}
// Can't exit early if no previous session
// Check for dir being set as a proxy for "has valid session"
// (loaded_configs can be empty if there are no config files)
Expand Down Expand Up @@ -403,6 +406,13 @@ fn have_trust_state_dirs_been_modified() -> bool {
false
}

fn has_preclap_logging_flag(args: &[String]) -> bool {
args.iter().any(|arg| {
matches!(arg.as_str(), "-q" | "--quiet" | "--silent" | "--log-level")
|| arg.starts_with("--log-level=")
})
}

fn have_mise_env_vars_been_modified() -> bool {
get_mise_env_vars_hashed() != PREV_SESSION.env_var_hash
}
Expand Down Expand Up @@ -744,3 +754,52 @@ pub fn build_alias_commands(

output
}

#[cfg(test)]
mod tests {
use super::has_preclap_logging_flag;

fn args(values: &[&str]) -> Vec<String> {
values.iter().map(|value| value.to_string()).collect()
}

#[test]
fn detects_logging_flags_that_need_clap_before_fast_exit() {
assert!(has_preclap_logging_flag(&args(&[
"mise", "hook-env", "-s", "bash", "--quiet"
])));
assert!(has_preclap_logging_flag(&args(&["mise", "hook-env", "-q"])));
assert!(has_preclap_logging_flag(&args(&[
"mise", "hook-env", "--silent"
])));
assert!(has_preclap_logging_flag(&args(&[
"mise",
"hook-env",
"--log-level",
"error"
])));
assert!(has_preclap_logging_flag(&args(&[
"mise",
"hook-env",
"--log-level=error"
])));
}

#[test]
fn ignores_logging_flags_that_do_not_suppress_warnings() {
assert!(!has_preclap_logging_flag(&args(&[
"mise", "hook-env", "-s", "bash"
])));
assert!(!has_preclap_logging_flag(&args(&[
"mise", "hook-env", "--trace"
])));
assert!(!has_preclap_logging_flag(&args(&[
"mise", "hook-env", "--debug"
])));
assert!(!has_preclap_logging_flag(&args(&[
"mise",
"hook-env",
"--verbose"
])));
}
}
1 change: 1 addition & 0 deletions src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ pub fn init() {
}
}
log::set_max_level(term_level);
Settings::flush_deprecated_warnings();
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

fn init_log_file(log_file: &Path) -> Result<File> {
Expand Down
Loading