From 552d33c22f0c6c9c214806c843ced25db9b52828 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Wed, 17 Jun 2026 14:57:23 +0200 Subject: [PATCH 01/14] doc: add init-envvars design doc Signed-off-by: Xavier Delaruelle --- NEWS.rst | 1 + doc/source/design/init-envvars.rst | 37 ++++++++++++++++++++++++++++ doc/source/design/linked-envvars.rst | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 doc/source/design/init-envvars.rst diff --git a/NEWS.rst b/NEWS.rst index e433ab867..72b0bc4f2 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -96,6 +96,7 @@ Modules 5.7.0 (not yet released) :mconfig:`linked_envvars` is changed with :subcmd:`config` sub-command, it sets the :envvar:`MODULES_LINKED_ENVVARS` environment variable. (fix issue #609) +* Doc: add :ref:`init-envvars` design notes. .. _5.6 release notes: diff --git a/doc/source/design/init-envvars.rst b/doc/source/design/init-envvars.rst new file mode 100644 index 000000000..086d5ad61 --- /dev/null +++ b/doc/source/design/init-envvars.rst @@ -0,0 +1,37 @@ +.. _init-envvars: + +Initialize environment variables +================================ + +This document describes the mechanism to initialize an environment variable to +a given value the first time this variable is changed. + +This is especially useful for variable like ``MANPATH`` that requires a +leading or finishing colon character in addition to the specific paths defined +to still be able to query the system man pages. + +Design choices: + +* If environment variable is changed and currently not defined, set it to the + defined initial value prior applying the change +* When removing value to path-like environment variable, if after the removal + it equals the initial value without specific reference counter, unset the + environment variable + +This mechanism applies to all path-like environment variable management +modulefile commands and module sub-commands: + +* :mfcmd:`append-path` +* :mfcmd:`prepend-path` +* :mfcmd:`remove-path` + +``init_envvars`` configuration option +------------------------------------- + +New configuration option :mconfig:`init_envvars` is made to define the initial +value of the environment variables. + +* Items are separated by colon character +* Each item has the following syntax: ``VARNAME=initial_value`` + +.. vim:set tabstop=2 shiftwidth=2 expandtab autoindent: diff --git a/doc/source/design/linked-envvars.rst b/doc/source/design/linked-envvars.rst index 7b2ed9c96..ff9ebe73b 100644 --- a/doc/source/design/linked-envvars.rst +++ b/doc/source/design/linked-envvars.rst @@ -3,7 +3,7 @@ Linked environment variables ============================ -This document describes the mechanism to link one environment variables onto +This document describes the mechanism to link one environment variable onto others and apply the same value changes. The goal is to link environment variables together and apply all changes to From a4818ca055a275fbaca1e303c5868e7ad4bf41f5 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Wed, 17 Jun 2026 14:57:00 +0200 Subject: [PATCH 02/14] Introduce "init_envvars" configuration option Add "init_envvars" configuration option to define initial value to set environment variables to the first time they are defined and before being modified. When configuration is modified it sets the MODULES_INIT_ENVVARS environment variable. Closes #572 Signed-off-by: Xavier Delaruelle --- tcl/envmngt.tcl.in | 38 +++++++++++++++++++++++++++++++++++++- tcl/init.tcl.in | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tcl/envmngt.tcl.in b/tcl/envmngt.tcl.in index 9683f78f3..521120cfb 100644 --- a/tcl/envmngt.tcl.in +++ b/tcl/envmngt.tcl.in @@ -1476,6 +1476,17 @@ proc isEnvVarProtected {var_name} { return [expr {$var_name in [getConfList protected_envvars]}] } +proc hasEnvVarInitValue {var_name} { + if {![isConfDefined init_envvars]} { + getConf init_envvars + } + return [info exists ::g_initEnvvars($var_name)] +} + +proc envVarInitValue {var_name} { + return $::g_initEnvvars($var_name) +} + proc reset-to-unset-env {var {val {}}} { set ::env($var) $val # set var as cleared if val is empty @@ -1766,6 +1777,19 @@ proc unload-path {cmd mode dflbhv args} { set newpath $dir_list } + # clear environment variable if new value equals initial value + if {[hasEnvVarInitValue $var] && [envVarInitValue $var] eq [join $newpath\ + $separator]} { + set init_value [envVarInitValue $var] + if {!$isrefcount || ![info exists countarr($init_value)] ||\ + $countarr($init_value) <= 1} { + set newpath [list] + if {[info exists countarr($init_value)]} { + unset countarr($init_value) + } + } + } + # set env variable and corresponding reference counter in any case if {![llength $newpath]} { unset-env $var @@ -1801,8 +1825,20 @@ proc add-path {cmd mode dflbhv args} { # clean any previously defined pushenv stack unset-env [getPushenvVarName $var] 1 + set sharevar [getModshareVarName $var] + set isrefcount [expr {$sharevar ne {}}] + + # use initial value defined for env var if not yet defined + if {![isEnvVarDefined $var] && [hasEnvVarInitValue $var]} { + set init_value [envVarInitValue $var] + set-env $var $init_value + # setup reference counter in case of empty string entry + if {$init_value eq {} && $isrefcount} { + set-env $sharevar $init_value:1 + } + } + # no reference count management when no share variable is returned - set isrefcount [expr {[set sharevar [getModshareVarName $var]] ne {}}] if {$isrefcount} { array set countarr [getReferenceCountArray $var $separator] } diff --git a/tcl/init.tcl.in b/tcl/init.tcl.in index 573d254be..ad848c0ba 100644 --- a/tcl/init.tcl.in +++ b/tcl/init.tcl.in @@ -71,7 +71,7 @@ array set g_state_defs [list\ # value, is configuration lockable to default value, value kind, valid value # list?, internal value representation?, proc to call to initialize option # value, valid value list kind -##nagelfar ignore #102 Too long line +##nagelfar ignore #103 Too long line array set g_config_defs [list\ contact {MODULECONTACT root@localhost 0 s}\ abort_on_error {MODULES_ABORT_ON_ERROR {@abortonerror@} 0 l {load ml\ @@ -108,6 +108,8 @@ array set g_config_defs [list\ implicit_requirement {MODULES_IMPLICIT_REQUIREMENT @implicitrequirement@ 0\ b {0 1}}\ info_extension {MODULES_INFO_EXTENSION 0 0 b {0 1}}\ + init_envvars {MODULES_INIT_ENVVARS {} 0 l {} {}\ + initConfInitEnvvars}\ linked_envvars {MODULES_LINKED_ENVVARS {} 0 l {} {}\ initConfLinkedEnvvars}\ list_output {MODULES_LIST_OUTPUT {@listoutput@} 0 l {header idx variant\ @@ -371,6 +373,11 @@ proc getConf {option {valifundef {}}} { } } +# Check if configuration option has been defined +proc isConfDefined {option} { + return [info exists ::g_configs($option)] +} + # Set configuration option value proc setConf {option value} { set ::g_configs($option) $value @@ -819,6 +826,39 @@ proc initConfTagColorName {envvar value validvallist intvallist} { return $value } +# Initialize env variable initial value array +proc initConfInitEnvvars {envvar value validvallist intvallist} { + # overridden value coming from environment + if {[isEnvVarDefined $envvar]} { + if {[catch { + # try to set the variable init value mapping table + array set testarr [split $::env($envvar) {:=}] + set value $::env($envvar) + set setfromenv 1 + } errMsg ]} { + reportWarning "Ignore invalid value set in $envvar ($::env($envvar))" + } + } + + # test default value + if {![info exists setfromenv]} { + if {[catch { + array set testarr [split $value {:=}] + } errMsg ]} { + reportWarning "Ignore invalid default value for 'init_envvars'\ + config ($value)" + # define an empty list if no valid value set + set value {} + } + } + + foreach {envvar init_value} [split $value {:=}] { + set ::g_initEnvvars($envvar) $init_value + } + + return $value +} + # Initialize env variable link array proc initConfLinkedEnvvars {envvar value validvallist intvallist} { # overridden value coming from environment From d5d5a51bc6a4c1b54cded6ec6a2af18c943d3368 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Wed, 17 Jun 2026 07:06:30 +0200 Subject: [PATCH 03/14] init: add init_envvars config to completion script Signed-off-by: Xavier Delaruelle --- init/Makefile | 2 +- init/fish_completion | 2 +- init/zsh-functions/_module.in | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/init/Makefile b/init/Makefile index 6a4f36f0c..518142900 100644 --- a/init/Makefile +++ b/init/Makefile @@ -136,7 +136,7 @@ comp_lint_opts := -a -i --all --icase comp_modtosh_opts := --auto --no-auto --force -f --icase -i comp_path_opts := -d --delim --duplicates comp_rm_path_opts := -d --delim --index -comp_config_opts := --dump-state --reset abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277 +comp_config_opts := --dump-state --reset abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement init_envvars info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277 define translate-in-script $(ECHO_GEN) diff --git a/init/fish_completion b/init/fish_completion index bce62fc39..4e50f9ac1 100644 --- a/init/fish_completion +++ b/init/fish_completion @@ -87,7 +87,7 @@ complete -c module -n '__fish_module_use_stashlist' -f -a "(module stashlist --c /Stash collection list\$/d; \ /:\$/d; \ /:ERROR:/d;')" -complete -c module -n '__fish_module_use_config' -f -a "--dump-state --reset abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277" +complete -c module -n '__fish_module_use_config' -f -a "--dump-state --reset abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement init_envvars info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277" complete -f -n '__fish_module_no_subcommand' -c module -a 'help' --description 'Print this or modulefile(s) help info' complete -f -n '__fish_module_no_subcommand' -c module -a 'avail' --description 'List all or matching available modules' diff --git a/init/zsh-functions/_module.in b/init/zsh-functions/_module.in index 921fd891b..3be62d9c5 100644 --- a/init/zsh-functions/_module.in +++ b/init/zsh-functions/_module.in @@ -378,7 +378,7 @@ _module() { _arguments \ '--dump-state[Report each state value of current Modules execution]' \ '--reset[Unset environment variable relative to configuration key]' \ - '1:configuration key:(abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277)' \ + '1:configuration key:(abort_on_error advanced_version_spec auto_handling avail_indepth avail_output avail_terse_output cache_buffer_bytes cache_expiry_secs collection_pin_version collection_pin_tag collection_target color colors conflict_unload contact editor extended_default extra_siteconfig hide_auto_loaded home icase ignore_cache ignore_user_rc implicit_default implicit_requirement init_envvars info_extension linked_envvars list_output list_terse_output locked_configs logged_events logger mcookie_check mcookie_version_check ml nearly_forbidden_days non_exportable_tags pager paginate path_entry_reorder protected_envvars quarantine_support rcfile redirect_output require_via reset_target_state run_quarantine search_match set_shell_startup shells_with_ksh_fpath silent_shell_debug source_cache spider_indepth spider_output spider_terse_output sticky_purge tag_abbrev tag_color_name tcl_linter term_background term_width unique_name_loaded unload_match_order variant_shortcut verbosity wa_277)' \ && ret=0 ;; (edit) From c803e71e6f424a5f2cb7ec04a0495fb882c236d1 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Wed, 17 Jun 2026 15:04:05 +0200 Subject: [PATCH 04/14] install: add --with-init-envvars configure option Introduce the --with-init-envvars configure option to choose at installation time the default initial value of listed environment variables. Empty by default. Signed-off-by: Xavier Delaruelle --- Makefile | 1 + Makefile.inc.in | 3 +++ configure | 10 +++++++++- site.exp.in | 2 ++ tcl/init.tcl.in | 2 +- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 84156b28f..c137c0ada 100644 --- a/Makefile +++ b/Makefile @@ -462,6 +462,7 @@ sed -e 's|@prefix@|$(prefix)|g' \ -e 's|@searchmatch@|$(searchmatch)|g' \ -e 's|@wa277@|$(setwa277)|g' \ -e 's|@pathentryreorder@|$(setpathentryreorder)|g' \ + -e 's|@initenvvars@|$(initenvvars)|g' \ -e 's|@icase@|$(icase)|g' \ -e 's|@nearlyforbiddendays@|$(nearlyforbiddendays)|g' \ -e 's|@tagabbrev@|$(tagabbrev)|g' \ diff --git a/Makefile.inc.in b/Makefile.inc.in index 950e1bc59..5e8cb279b 100644 --- a/Makefile.inc.in +++ b/Makefile.inc.in @@ -164,6 +164,9 @@ editor := @editor@ # path change behavior pathentryreorder := @pathentryreorder@ +# environment variable configurations +initenvvars := @initenvvars@ + # shell completion location bashcompletiondir := @bashcompletiondir@ fishcompletiondir := @fishcompletiondir@ diff --git a/configure b/configure index b563c003c..cc524fdea 100755 --- a/configure +++ b/configure @@ -41,7 +41,7 @@ listterseoutput editor variantshortcut bashcompletiondir fishcompletiondir \ zshcompletiondir tcllinter tcllinteropts nagelfardatadir nagelfaraddons \ stickypurge uniquenameloaded abortonerror sourcecache logger loggeropts \ loggedevents conflictunload spideroutput spiderterseoutput spiderindepth \ -emacsdatadir emacsaddons requirevia compressedchangelog paginate" +emacsdatadir emacsaddons requirevia compressedchangelog paginate initenvvars" libarglist=() # flags to know if argument has been specified on command-line @@ -131,6 +131,7 @@ modulefilesdir= moduleshome= modulepath= windowssupport=n +initenvvars= nearlyforbiddendays=14 tagabbrev='auto-loaded=aL:loaded=L:hidden=H:hidden-loaded=H:forbidden=F:nearly-forbidden=nF:sticky=S:super-sticky=sS:keep-loaded=kL:warning=W' tagcolorname= @@ -389,6 +390,11 @@ Optional Packages: (\`search'), on all sub-commands and modulefile Tcl commands (\`always') or disable case insensitive match (\`never') [$icase] + --with-init-envvars=LIST + initial value to set listed environment variables to + when they are firstly defined (elements have + \`VAR=value' syntax and are separated by \`:' in + LIST) [$initenvvars] --with-initconf-in=VALUE location where to install Modules initialization configuration files. Either \`initdir' or \`etcdir' @@ -867,6 +873,8 @@ for arg in "$@"; do --with-terminal-background=*|--without-terminal-background) allowedval=" dark light " ; check_and_get_package_value "termbg" "$arg" "$allowedval" ;; + --with-init-envvars=*|--without-init-envvars) + initenvvars=$(get_package_value "$arg" "") ;; --with-locked-configs=*|--disable-locked-configs) lockedconfigs=$(get_package_value "$arg" "") ; allowedval=" extra_siteconfig implicit_default logged_events logger " ; diff --git a/site.exp.in b/site.exp.in index 0bdf008bf..32be6167f 100644 --- a/site.exp.in +++ b/site.exp.in @@ -120,6 +120,8 @@ set install_editor "@editor@" set install_pathentryreorder "@pathentryreorder@" +set install_initenvvars "@initenvvars@" + set install_bashcompletiondir "@bashcompletiondir@" set install_fishcompletiondir "@fishcompletiondir@" set install_zshcompletiondir "@zshcompletiondir@" diff --git a/tcl/init.tcl.in b/tcl/init.tcl.in index ad848c0ba..9db694cdf 100644 --- a/tcl/init.tcl.in +++ b/tcl/init.tcl.in @@ -108,7 +108,7 @@ array set g_config_defs [list\ implicit_requirement {MODULES_IMPLICIT_REQUIREMENT @implicitrequirement@ 0\ b {0 1}}\ info_extension {MODULES_INFO_EXTENSION 0 0 b {0 1}}\ - init_envvars {MODULES_INIT_ENVVARS {} 0 l {} {}\ + init_envvars {MODULES_INIT_ENVVARS {@initenvvars@} 0 l {} {}\ initConfInitEnvvars}\ linked_envvars {MODULES_LINKED_ENVVARS {} 0 l {} {}\ initConfLinkedEnvvars}\ From 94640a464bab48ec60422de6291dc12378d501c3 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Wed, 17 Jun 2026 07:10:39 +0200 Subject: [PATCH 05/14] ts: test init_envvars config option Signed-off-by: Xavier Delaruelle --- testsuite/install.00-init/010-environ.exp | 1 + testsuite/modules.00-init/010-environ.exp | 1 + testsuite/modules.70-maint/220-config.exp | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/testsuite/install.00-init/010-environ.exp b/testsuite/install.00-init/010-environ.exp index 4d9f2a918..e2fbfd8a6 100644 --- a/testsuite/install.00-init/010-environ.exp +++ b/testsuite/install.00-init/010-environ.exp @@ -95,6 +95,7 @@ unsetenv_var MODULES_LOGGED_EVENTS unsetenv_var MODULES_LOGGER # clean any environment variable-specific configuration +setenv_var MODULES_INIT_ENVVARS {} unsetenv_var MODULES_PROTECTED_ENVVARS unsetenv_var MODULES_LINKED_ENVVARS diff --git a/testsuite/modules.00-init/010-environ.exp b/testsuite/modules.00-init/010-environ.exp index bdc13240d..93a6fe8a7 100644 --- a/testsuite/modules.00-init/010-environ.exp +++ b/testsuite/modules.00-init/010-environ.exp @@ -96,6 +96,7 @@ unsetenv_var MODULES_LOGGED_EVENTS unsetenv_var MODULES_LOGGER # clean any environment variable-specific configuration +setenv_var MODULES_INIT_ENVVARS {} unsetenv_var MODULES_PROTECTED_ENVVARS unsetenv_var MODULES_LINKED_ENVVARS diff --git a/testsuite/modules.70-maint/220-config.exp b/testsuite/modules.70-maint/220-config.exp index faab73681..f855fa811 100644 --- a/testsuite/modules.70-maint/220-config.exp +++ b/testsuite/modules.70-maint/220-config.exp @@ -32,6 +32,7 @@ unsetenv_var MODULES_MCOOKIE_VERSION_CHECK # cache buffer size may be set if cache files have been prebuilt unsetenv_var MODULES_CACHE_BUFFER_BYTES unsetenv_var MODULES_STICKY_PURGE +unsetenv_var MODULES_INIT_ENVVARS unsetenv_var MODULES_ABORT_ON_ERROR unsetenv_var MODULES_SOURCE_CACHE unsetenv_var MODULES_HIDE_AUTO_LOADED @@ -84,6 +85,7 @@ array set configdfl [list\ ignored_dirs {CVS RCS SCCS .svn .git .SYNC .sos}\ implicit_default [expr {$install_implicitdefault eq {y}}]\ implicit_requirement [expr {$install_implicitrequirement eq {y}}]\ + init_envvars $install_initenvvars\ info_extension 0\ linked_envvars {}\ list_output $install_listoutput\ @@ -155,6 +157,7 @@ array set configvar [list\ ignore_user_rc MODULES_IGNORE_USER_RC\ implicit_default MODULES_IMPLICIT_DEFAULT\ implicit_requirement MODULES_IMPLICIT_REQUIREMENT\ + init_envvars MODULES_INIT_ENVVARS\ info_extension MODULES_INFO_EXTENSION\ linked_envvars MODULES_LINKED_ENVVARS\ list_output MODULES_LIST_OUTPUT\ @@ -275,6 +278,7 @@ array set configkind [list\ avail_output l\ avail_terse_output l\ colors l\ + init_envvars l\ linked_envvars l\ list_output l\ list_terse_output l\ @@ -422,6 +426,8 @@ foreach param [array names configvar] { set val foo=% } elseif {$param eq {pager}} { set val $configdfl(pager) + } elseif {$param eq {init_envvars}} { + set val FOO=val } elseif {$param eq {linked_envvars}} { set val FOO&BAR } elseif {[info exists configvalid($param)]} { From 9030ed09198c9042ecef19667778aa8bd6275049 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sat, 20 Jun 2026 15:28:23 +0200 Subject: [PATCH 06/14] doc: desc. --with-init-envvars in INSTALL Signed-off-by: Xavier Delaruelle --- INSTALL.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/INSTALL.rst b/INSTALL.rst index e208e906b..d0b6af65b 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1161,6 +1161,20 @@ instance :instopt:`--without-modulepath<--with-modulepath>`): .. versionchanged:: 5.0 Configuration option default set to ``etcdir`` +.. instopt:: --with-init-envvars=LIST + + Initial values to assign to environment variables before they are modified + for the first time. Each entry in LIST must use the format + ``VARIABLE_NAME=initial_value``. Entries are separated by ``:``. + + This installation option defines the default value of the + :mconfig:`init_envvars` configuration option which could be changed after + installation with the :subcmd:`config` sub-command. + + .. only:: html or latex + + .. versionadded:: 5.7 + .. instopt:: --with-light-background-colors=SGRLIST Default color set to apply if terminal background color is defined to @@ -1723,6 +1737,9 @@ installation. +-----------------------------------+----------------------------------------------+----------------------------------------------+--------------+-----------+ | :mconfig:`info_extension` | ``0`` | :envvar:`MODULES_INFO_EXTENSION` | | | +-----------------------------------+----------------------------------------------+----------------------------------------------+--------------+-----------+ +| :mconfig:`init_envvars` | *Empty by default* | :instopt:`--with-init-envvars` | | | +| | | :envvar:`MODULES_INIT_ENVVARS` | | | ++-----------------------------------+----------------------------------------------+----------------------------------------------+--------------+-----------+ | :mconfig:`linked_envvars` | *Empty by default* | :envvar:`MODULES_LINKED_ENVVARS` | | | +-----------------------------------+----------------------------------------------+----------------------------------------------+--------------+-----------+ | :mconfig:`list_output` | ``header:idx:variant:sym:tag:key`` | :instopt:`--with-list-output`, | | | From 1d1434f27b9f5d491e964960a37b12c9200d61c5 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sat, 20 Jun 2026 15:29:47 +0200 Subject: [PATCH 07/14] doc: desc. init_envvars in NEWS Signed-off-by: Xavier Delaruelle --- NEWS.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 72b0bc4f2..c5c152f38 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -97,6 +97,12 @@ Modules 5.7.0 (not yet released) sets the :envvar:`MODULES_LINKED_ENVVARS` environment variable. (fix issue #609) * Doc: add :ref:`init-envvars` design notes. +* Add the :mconfig:`init_envvars` configuration option to define initial + values to assign to environment variables before they are modified for the + first time. This option can be changed at installation time with + :instopt:`--with-init-envvars`. When :mconfig:`init_envvars` is changed with + :subcmd:`config` sub-command, it sets the :envvar:`MODULES_INIT_ENVVARS` + environment variable. (fix issue #572) .. _5.6 release notes: From 634ef941c66b5461321cd16ffdf02738923fba73 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sat, 20 Jun 2026 16:31:43 +0200 Subject: [PATCH 08/14] doc: desc. init_envvars config in man/changes Signed-off-by: Xavier Delaruelle --- doc/source/changes.rst | 2 +- doc/source/module.rst | 16 ++++++++++++++++ doc/source/modulefile.rst | 19 ++++++++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 7e3dbefae..85a59c3d5 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -1292,7 +1292,7 @@ The following Modules configuration option has been introduced on Modules 5. +------------+-----------------------------------------------------------------+ | 5.7 | :mconfig:`path_entry_reorder`, :mconfig:`paginate`, | | | :mconfig:`non_exportable_tags`, :mconfig:`info_extension`, | -| | :mconfig:`linked_envvars` | +| | :mconfig:`linked_envvars`, :mconfig:`init_envvars` | +------------+-----------------------------------------------------------------+ :mconfig:`auto_handling` diff --git a/doc/source/module.rst b/doc/source/module.rst index 97b39717f..8676c7c53 100644 --- a/doc/source/module.rst +++ b/doc/source/module.rst @@ -1248,6 +1248,22 @@ Module Sub-Commands .. versionadded:: 5.7 + .. mconfig:: init_envvars + + Initial values to assign to environment variables right before they are + modified for the first time. + + This configuration option is set to an empty value by default. It can be + changed at installation time with :instopt:`--with-init-envvars` option. The + :envvar:`MODULES_INIT_ENVVARS` environment variable is defined by + :subcmd:`config` sub-command when changing this configuration option from + its default value. See :envvar:`MODULES_INIT_ENVVARS` description for + details + + .. only:: html or latex + + .. versionadded:: 5.7 + .. mconfig:: linked_envvars Define links between environment variables to apply same value modification diff --git a/doc/source/modulefile.rst b/doc/source/modulefile.rst index 772cb0c0c..39169c8e7 100644 --- a/doc/source/modulefile.rst +++ b/doc/source/modulefile.rst @@ -1196,10 +1196,14 @@ the *modulefile* is being loaded. configuration option is activated, the *modulefile* is considered a dependency by the loaded modulefiles stored in the added modulepaths. - If a link is defined for variable through the :mconfig:`linked_envvars` + If a link is defined for *variable* through the :mconfig:`linked_envvars` configuration option, the same :mfcmd:`prepend-path` command is applied to all associated environment variables. + If an initial value is defined for the *variable* through the + :mconfig:`init_envvars` configuration option, that value is assigned before + the modification is applied when the *variable* is not already set. + .. only:: html or latex .. versionchanged:: 4.1 @@ -1363,7 +1367,7 @@ the *modulefile* is being loaded. which is named by prefixing *variable* by :envvar:`__MODULES_PUSHENV_\ <__MODULES_PUSHENV_\>`. - If a link is defined for variable through the :mconfig:`linked_envvars` + If a link is defined for *variable* through the :mconfig:`linked_envvars` configuration option, the same :mfcmd:`pushenv` command is applied to all associated environment variables. @@ -1456,10 +1460,15 @@ the *modulefile* is being loaded. An error is raised if *value* equals *delimiter* character. - If a link is defined for variable through the :mconfig:`linked_envvars` + If a link is defined for *variable* through the :mconfig:`linked_envvars` configuration option, the same :mfcmd:`remove-path` command is applied to all associated environment variables. + If an initial value is defined for *variable* through the + :mconfig:`init_envvars` configuration option, variable is unset when, after + the modification, its value matches the configured initial value and no + explicit reference counter is defined. + .. only:: html or latex .. versionchanged:: 4.1 @@ -1549,7 +1558,7 @@ the *modulefile* is being loaded. Any newline character in *value* is chopped if using *csh* or *tcsh* shells. - If a link is defined for variable through the :mconfig:`linked_envvars` + If a link is defined for *variable* through the :mconfig:`linked_envvars` configuration option, the same :mfcmd:`setenv` command is applied to all associated environment variables. @@ -1677,7 +1686,7 @@ the *modulefile* is being loaded. environment *variable* is also unset when *modulefile* is unloaded. These behaviors are applied even if an optional *value* is defined. - If a link is defined for variable through the :mconfig:`linked_envvars` + If a link is defined for *variable* through the :mconfig:`linked_envvars` configuration option, the same :mfcmd:`unsetenv` command is applied to all associated environment variables. From 5e56a0c70cb2fbf13aa86747766cf453a28b40dd Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sat, 20 Jun 2026 16:32:15 +0200 Subject: [PATCH 09/14] doc: desc. MODULES_INIT_ENVVARS in man/changes Signed-off-by: Xavier Delaruelle --- doc/source/changes.rst | 3 ++- doc/source/module.rst | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 85a59c3d5..b4ab6f329 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -1077,7 +1077,8 @@ The following environment variables appeared on Modules 5. | | :envvar:`MODULES_PAGINATE`, | | | :envvar:`MODULES_NON_EXPORTABLE_TAGS`, | | | :envvar:`MODULES_INFO_EXTENSION`, | -| | :envvar:`MODULES_LINKED_ENVVARS` | +| | :envvar:`MODULES_LINKED_ENVVARS`, | +| | :envvar:`MODULES_INIT_ENVVARS` | +------------+-----------------------------------------------------------------+ Modules Specific Tcl Commands diff --git a/doc/source/module.rst b/doc/source/module.rst index 8676c7c53..807573a9c 100644 --- a/doc/source/module.rst +++ b/doc/source/module.rst @@ -5180,6 +5180,29 @@ ENVIRONMENT .. versionadded:: 5.7 +.. envvar:: MODULES_INIT_ENVVARS + + A colon-separated list of environment variable initial definitions. Each + definition must use the format ``VARIABLE_NAME=initial_value``. When an + environment variable is modified for the first time, the corresponding + initial value is assigned before the modification is applied. + + This mechanism applies to all path-like environment variable management + modulefile commands and module sub-commands (:mfcmd:`append-path`, + :mfcmd:`prepend-path` and :mfcmd:`remove-path`). + + When an entry is removed from an environment variable, the variable is unset + if its resulting value matches the configured initial value and no explicit + reference counter is associated with it. + + This environment variable value supersedes the default value set in the + :mconfig:`init_envvars` configuration option. It can be defined with the + :subcmd:`config` sub-command. + + .. only:: html or latex + + .. versionadded:: 5.7 + .. envvar:: MODULES_LINKED_ENVVARS A colon-separated list of environment variable link sets. Each link set is a From 67a4102b59aec9bd8841bf4a87aa485f3095185f Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sun, 21 Jun 2026 20:57:46 +0200 Subject: [PATCH 10/14] ts: add tests to check init_envvars Signed-off-by: Xavier Delaruelle --- testsuite/modulefiles.4/init/1 | 26 ++ testsuite/modulefiles.4/init/2 | 1 + .../modules.50-cmds/730-linked_envvars.exp | 3 +- .../modules.50-cmds/731-init_envvars.exp | 410 ++++++++++++++++++ 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 testsuite/modulefiles.4/init/1 create mode 100644 testsuite/modulefiles.4/init/2 create mode 100644 testsuite/modules.50-cmds/731-init_envvars.exp diff --git a/testsuite/modulefiles.4/init/1 b/testsuite/modulefiles.4/init/1 new file mode 100644 index 000000000..7e4b883ff --- /dev/null +++ b/testsuite/modulefiles.4/init/1 @@ -0,0 +1,26 @@ +#%Module +if {[info exists env(TESTSUITE_INIT_ENVVARS)]} { + switch -- $env(TESTSUITE_INIT_ENVVARS) { + append1 { + append-path FOO value + } + prepend1 { + prepend-path FOO value + } + remove1 { + remove-path FOO value + } + setenv1 { + setenv FOO value + } + unsetenv1 { + unsetenv FOO + } + pushenv1 { + pushenv FOO value + } + append_dup1 { + append-path --duplicates FOO value + } + } +} diff --git a/testsuite/modulefiles.4/init/2 b/testsuite/modulefiles.4/init/2 new file mode 100644 index 000000000..1c148cdd2 --- /dev/null +++ b/testsuite/modulefiles.4/init/2 @@ -0,0 +1 @@ +#%Module diff --git a/testsuite/modules.50-cmds/730-linked_envvars.exp b/testsuite/modules.50-cmds/730-linked_envvars.exp index 2bf56c64d..579e548c9 100644 --- a/testsuite/modules.50-cmds/730-linked_envvars.exp +++ b/testsuite/modules.50-cmds/730-linked_envvars.exp @@ -11,7 +11,8 @@ # # Description: Testuite testsequence # Command: load, unload, display, help, show, test, -# refresh, whatis +# refresh, whatis, append-path, prepend-path, +# remove-path # Modulefiles: link, source-sh # Sub-Command: # diff --git a/testsuite/modules.50-cmds/731-init_envvars.exp b/testsuite/modules.50-cmds/731-init_envvars.exp new file mode 100644 index 000000000..cc99c12d8 --- /dev/null +++ b/testsuite/modules.50-cmds/731-init_envvars.exp @@ -0,0 +1,410 @@ +############################################################################## +# Modules Revision 3.0 +# Providing a flexible user environment +# +# File: modules.50-cmds/%M% +# Revision: %I% +# First Edition: 2026/06/21 +# Last Mod.: %U%, %G% +# +# Authors: Xavier Delaruelle, xavier.delaruelle@cea.fr +# +# Description: Testuite testsequence +# Command: load, unload, append-path, prepend-path, remove-path +# Modulefiles: init, source-sh +# Sub-Command: +# +# Comment: %C{ +# Test init_envvars mechanism +# }C% +# +############################################################################## + +set mp $modpath.4 +set mpre $modpathre.4 +setenv_var MODULEPATH $mp + +# basic init test +setenv_var MODULES_INIT_ENVVARS FOO=init +unsetenv_var FOO + +setenv_var TESTSUITE_INIT_ENVVARS append1 +set ans [list] +lappend ans [list set FOO init:value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +set ans [list] +lappend ans [list set FOO value:init] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS remove1 +set ans [list] +lappend ans [list unset FOO] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list set FOO init:value] +testouterr_cmd bash {append-path FOO value} $ans {} + +set ans [list] +lappend ans [list set FOO value:init] +testouterr_cmd bash {prepend-path FOO value} $ans {} + +set ans [list] +lappend ans [list unset FOO] +testouterr_cmd bash {remove-path FOO value} $ans {} + + +setenv_var FOO base + +setenv_var TESTSUITE_INIT_ENVVARS append1 +set ans [list] +lappend ans [list set FOO base:value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +set ans [list] +lappend ans [list set FOO value:base] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list set FOO base:value] +testouterr_cmd bash {append-path FOO value} $ans {} + +set ans [list] +lappend ans [list set FOO value:base] +testouterr_cmd bash {prepend-path FOO value} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS remove1 +set ans [list] +lappend ans [list set FOO base] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list set FOO base] +testouterr_cmd bash {remove-path FOO value} $ans {} + +setenv_var FOO value +setenv_var TESTSUITE_INIT_ENVVARS remove1 +set ans [list] +lappend ans [list unset FOO] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list unset FOO] +testouterr_cmd bash {remove-path FOO value} $ans {} + +# unset env var has it equals initial value +setenv_var FOO init:value +setenv_var TESTSUITE_INIT_ENVVARS remove1 +set ans [list] +lappend ans [list unset FOO] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list unset FOO] +testouterr_cmd bash {remove-path FOO value} $ans {} + +skip_if_quick_mode + + +# no impact on non-path-like modulefile commands +unsetenv_var FOO +setenv_var TESTSUITE_INIT_ENVVARS setenv1 +set ans [list] +lappend ans [list set FOO value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS pushenv1 +set ans [list] +lappend ans [list set FOO value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +lappend ans [list set __MODULES_PUSHENV_FOO init/1&value] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS unsetenv1 +set ans [list] +lappend ans [list unset FOO] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + + +# unload then unset +setenv_loaded_module [list init/1] [list $mp/init/1] + +setenv_var TESTSUITE_INIT_ENVVARS append1 +setenv_var FOO init:value +set ans [list] +lappend ans [list unset FOO] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +setenv_var FOO value:init +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS append1 +setenv_var FOO init:base:value +set ans [list] +lappend ans [list set FOO init:base] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +testouterr_cmd bash {unload init/1} $ans {} + + +# some reference counter defined +setenv_var FOO init:value +setenv_var __MODULES_SHARE_FOO init:2 +setenv_var TESTSUITE_INIT_ENVVARS append1 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO init:2] +lappend ans [list set FOO init] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +testouterr_cmd bash {unload init/1} $ans {} + +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO init:2] +lappend ans [list set FOO init] +testouterr_cmd bash {remove-path FOO value} $ans {} + +unsetenv_var __MODULES_SHARE_FOO +unsetenv_var FOO +unsetenv_loaded_module + + +# multiple value defined in config +setenv_var MODULES_INIT_ENVVARS BAR=value:FOO=init +setenv_var TESTSUITE_INIT_ENVVARS append1 + +set ans [list] +lappend ans [list set FOO init:value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS FOO=base:BAR=value:FOO=init +testouterr_cmd bash {load init/1} $ans {} + + +# initial value identical to added value +setenv_var MODULES_INIT_ENVVARS FOO=value +setenv_var TESTSUITE_INIT_ENVVARS append1 + +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO value:2] +lappend ans [list set FOO value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list set FOO value] +testouterr_cmd bash {append-path FOO value} $ans {} + + +# initial value identical to added value and duplicates allowed +setenv_var MODULES_INIT_ENVVARS FOO=value +setenv_var TESTSUITE_INIT_ENVVARS append_dup1 + +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO value:2] +lappend ans [list set FOO value:value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO value:2] +lappend ans [list set FOO value:value] +testouterr_cmd bash {append-path --duplicates FOO value} $ans {} + + +# coupled effect with linked_envvars +setenv_var MODULES_INIT_ENVVARS FOO=init +setenv_var MODULES_LINKED_ENVVARS FOO&BAR +setenv_var TESTSUITE_INIT_ENVVARS append1 +set ans [list] +lappend ans [list set FOO init:value] +lappend ans [list set BAR value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS BAR=base:FOO=init +set ans [list] +lappend ans [list set FOO init:value] +lappend ans [list set BAR base:value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS BAR=value:FOO=init +set ans [list] +lappend ans [list set FOO init:value] +lappend ans [list set __MODULES_SHARE_BAR value:2] +lappend ans [list set BAR value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +unsetenv_var MODULES_LINKED_ENVVARS + + +# initial value set to empty string +setenv_var MODULES_INIT_ENVVARS FOO= +unsetenv_var FOO +setenv_var TESTSUITE_INIT_ENVVARS append1 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :1] +lappend ans [list set FOO :value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :1] +lappend ans [list set FOO value:] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +testouterr_cmd bash {load init/1} $ans {} + + +# envvar not set, but share counter set (should not happen) +setenv_var __MODULES_SHARE_FOO :1 +testouterr_cmd bash {load init/1} $ans {} +unsetenv_var __MODULES_SHARE_FOO + + +# unload that leads to unset with reference counter +setenv_loaded_module [list init/1] [list $mp/init/1] +setenv_var __MODULES_SHARE_FOO :1 +setenv_var MODULES_INIT_ENVVARS FOO= + +setenv_var TESTSUITE_INIT_ENVVARS append1 +setenv_var FOO :value +set ans [list] +lappend ans [list unset __MODULES_SHARE_FOO] +lappend ans [list unset FOO] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var TESTSUITE_INIT_ENVVARS prepend1 +setenv_var FOO value: +testouterr_cmd bash {unload init/1} $ans {} + +# unload that does not lead to unset with reference counter +setenv_var __MODULES_SHARE_FOO :2 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :2] +lappend ans [list set FOO {}] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +setenv_var __MODULES_SHARE_FOO :2:value:2 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :2] +lappend ans [list set FOO value:] +lappend ans [list unset _LMFILES_] +lappend ans [list unset LOADEDMODULES] +testouterr_cmd bash {unload init/1} $ans {} + +# check with sub-command that ignores reference counter +setenv_var __MODULES_SHARE_FOO :2 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :2] +lappend ans [list set FOO {}] +testouterr_cmd bash {remove-path FOO value} $ans {} + +setenv_var __MODULES_SHARE_FOO :2:value:2 +set ans [list] +lappend ans [list set __MODULES_SHARE_FOO :2] +lappend ans [list set FOO {}] +testouterr_cmd bash {remove-path FOO value} $ans {} + +setenv_var __MODULES_SHARE_FOO :1:value:2 +set ans [list] +lappend ans [list unset __MODULES_SHARE_FOO] +lappend ans [list unset FOO] +testouterr_cmd bash {remove-path FOO value} $ans {} + +unsetenv_loaded_module +unsetenv_var FOO +unsetenv_var __MODULES_SHARE_FOO + + +if {$install_initenvvars eq {}} { + +# bad value in config envvar +setenv_var MODULES_INIT_ENVVARS :FOO= +setenv_var TESTSUITE_INIT_ENVVARS append1 +unsetenv_var FOO +set ans [list] +lappend ans [list set FOO value] +set tserr "$warn_msgs: Ignore invalid value set in MODULES_INIT_ENVVARS (:FOO=)" +testouterr_cmd bash {append-path FOO value} $ans $tserr + +setenv_var MODULES_INIT_ENVVARS FOO=: +set ans [list] +lappend ans [list set FOO value] +lappend ans [list set _LMFILES_ $mp/init/1] +lappend ans [list set LOADEDMODULES init/1] +set tserr [msg_load init/1 "$warn_msgs: Ignore invalid value set in MODULES_INIT_ENVVARS (FOO=:)"] +testouterr_cmd bash {load init/1} $ans $tserr + +setenv_var MODULES_INIT_ENVVARS : +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS {} +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS =value +testouterr_cmd bash {load init/1} $ans {} + +setenv_var MODULES_INIT_ENVVARS FOO +set tserr [msg_load init/1 "$warn_msgs: Ignore invalid value set in MODULES_INIT_ENVVARS (FOO)"] +testouterr_cmd bash {load init/1} $ans $tserr + +setenv_var MODULES_INIT_ENVVARS FOO=value:BAR +set tserr [msg_load init/1 "$warn_msgs: Ignore invalid value set in MODULES_INIT_ENVVARS (FOO=value:BAR)"] +testouterr_cmd bash {load init/1} $ans $tserr + +} + + +# +# Cleanup +# + +reset_test_env From 42368115caa074ecad63577d3bcc14076cbd0aa9 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Sun, 21 Jun 2026 21:19:53 +0200 Subject: [PATCH 11/14] doc: desc. init_envvars in MIGRATING Signed-off-by: Xavier Delaruelle --- MIGRATING.rst | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/MIGRATING.rst b/MIGRATING.rst index a72b49580..a737971fe 100644 --- a/MIGRATING.rst +++ b/MIGRATING.rst @@ -135,6 +135,53 @@ The output of ``module display`` also includes the environment variable changes applied to linked environment variables, making these propagated updates visible before a module is loaded. +.. _Init environment variables: + +Initialize environment variables on first modification +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A new configuration option, :mconfig:`init_envvars`, has been added to define +initial values for environment variables that are modified through path-like +modulefile commands. + +This feature is particularly useful for variables such as ``MANPATH``, which +require a leading or trailing colon character to preserve access to the system +default search paths. Previously, modifying an undefined ``MANPATH`` could +prevent system manual pages from being found. See the :ref:`man-path` cookbook +for details. + +The :mconfig:`init_envvars` option defines a colon-separated list of +``VARIABLE_NAME=initial_value`` entries. When a path-like operation is applied +to an environment variable that is not currently set, the configured initial +value is assigned before the modification is performed. + +For example, with the following configuration: + +.. parsed-literal:: + + :ps:`$` module config init_envvars MANPATH= + +loading a module that prepends a directory to ``MANPATH``: + +.. parsed-literal:: + + :sgrcm:`prepend-path` MANPATH /opt/pkg/man + +will keep a colon character at the end of ``MANPATH`` + +.. parsed-literal:: + + :ps:`$` echo $MANPATH + /opt/pkg/man: + +This mechanism applies to all path-like environment variable management +commands, including :mfcmd:`append-path`, :mfcmd:`prepend-path` and +:mfcmd:`remove-path`. + +When values are later removed from an environment variable, it is +automatically unset if its resulting value matches the configured initial +value and no explicit reference counter is associated with it. + v5.6 ---- From 1ed4ccd95991f43ceafc129403e46748adbd5b17 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Mon, 22 Jun 2026 13:32:50 +0200 Subject: [PATCH 12/14] rpm: initialize MANPATH to an empty string when first set Signed-off-by: Xavier Delaruelle --- share/rpm/environment-modules.spec.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/rpm/environment-modules.spec.in b/share/rpm/environment-modules.spec.in index 01316a88a..d458800b1 100644 --- a/share/rpm/environment-modules.spec.in +++ b/share/rpm/environment-modules.spec.in @@ -121,7 +121,8 @@ have access to the module alias. --enable-modulespath \ --with-python=%{pythonbin} \ --with-modulepath=%{_datadir}/Modules/modulefiles:%{_sysconfdir}/modulefiles:%{_datadir}/modulefiles \ - --with-quarantine-vars='LD_LIBRARY_PATH LD_PRELOAD' + --with-quarantine-vars='LD_LIBRARY_PATH LD_PRELOAD' \ + --with-init-envvars='MANPATH=' %if 0%{?fedora} >= 22 || 0%{?rhel} >= 7 %make_build From b294d2282c2f8a0690dffd449ec35861fd5f012c Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Mon, 22 Jun 2026 13:51:51 +0200 Subject: [PATCH 13/14] doc: update man-path cookbook to describe init_envvars Signed-off-by: Xavier Delaruelle --- doc/source/cookbook/man-path.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/doc/source/cookbook/man-path.rst b/doc/source/cookbook/man-path.rst index f1225f927..7ed336a03 100644 --- a/doc/source/cookbook/man-path.rst +++ b/doc/source/cookbook/man-path.rst @@ -70,8 +70,22 @@ man page directories. If your modulefiles modify :envvar:`MANPATH`, it is recommended to initialize this environment variable with a single colon (``:``) during Modules startup. -To do this, add the following line to the ``initrc`` configuration file -(typically located in ``/etc/environment-modules``): + +To do this on Modules 5.7 or any newer version, add the following line to the +``initrc`` configuration file (typically located in +``/etc/environment-modules``): + +.. code-block:: tcl + + module config init_envvars MANPATH= + +With this configuration, the ``MANPATH`` environment variable will be +initialized to an empty string right before its first modification. As a +result a leading or finishing colon character will be set the first time a +path entry is added to ``MANPATH``. + +With older versions of Modules, add the following line to the ``initrc`` +configuration file: .. code-block:: tcl From f531527f0c79d0952c03c973e2d9cfcda8871676 Mon Sep 17 00:00:00 2001 From: Xavier Delaruelle Date: Mon, 22 Jun 2026 19:32:01 +0200 Subject: [PATCH 14/14] Adapt autoinit process if init value set for MANPATH Use init_envvars process rather manually try to add colon character to MANPATH. Signed-off-by: Xavier Delaruelle --- tcl/subcmd.tcl.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tcl/subcmd.tcl.in b/tcl/subcmd.tcl.in index 0f0a7cf73..8cee80aa7 100644 --- a/tcl/subcmd.tcl.in +++ b/tcl/subcmd.tcl.in @@ -2157,7 +2157,10 @@ proc cmdModuleAutoinit {} { @setmanpath@@notusemanpath@} @setmanpath@if {{@mandir@} ni [split $manpath :]} { @setmanpath@ if {![isEnvVarDefined MANPATH]} { - @setmanpath@ append-path MANPATH {} + @setmanpath@ if {![hasEnvVarInitValue MANPATH] || [envVarInitValue MANPATH] ne\ + @setmanpath@ {}} { + @setmanpath@ append-path MANPATH {} + @setmanpath@ } @setmanpath@ # ensure no duplicate ':' is set @setmanpath@ } elseif {[envVarEquals MANPATH :]} { @setmanpath@ remove-path MANPATH {}