From 0904b7c959635e8e6c0e16e36941a8b60cf76aa1 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Wed, 15 Apr 2026 15:05:37 -0700 Subject: [PATCH 1/7] Add support for non-pathed environment variables in path normalization - Introduced `non_pathed_env_vars` configuration to exclude specific variables from path normalization - Updated `_is_pathed_key` method to respect the new configuration - Added tests to verify behavior of non-pathed environment variables in Windows shell Signed-off-by: Gabriel Reed --- src/rez/config.py | 1 + src/rez/rex.py | 2 ++ src/rez/rezconfig.py | 8 ++++++++ src/rez/tests/test_rex.py | 33 ++++++++++++++++++++++++++++++--- src/rez/tests/test_shells.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/rez/config.py b/src/rez/config.py index d69895825a..ee2d1b21d5 100644 --- a/src/rez/config.py +++ b/src/rez/config.py @@ -389,6 +389,7 @@ def _parse_env_var(self, value): "release_hooks": StrList, "context_tracking_context_fields": StrList, "pathed_env_vars": StrList, + "non_pathed_env_vars": StrList, "prompt_release_message": Bool, "critical_styles": OptionalStrList, "error_styles": OptionalStrList, diff --git a/src/rez/rex.py b/src/rez/rex.py index da26ef5185..95edf8b25a 100644 --- a/src/rez/rex.py +++ b/src/rez/rex.py @@ -569,6 +569,8 @@ def escape_string(self, value: str | EscapedString, is_path: bool = False) -> st @classmethod def _is_pathed_key(cls, key): + if any(fnmatch(key, x) for x in config.non_pathed_env_vars): + return False return any(fnmatch(key, x) for x in config.pathed_env_vars) def normalize_path(self, path): diff --git a/src/rez/rezconfig.py b/src/rez/rezconfig.py index d596a993a2..6650fb781f 100644 --- a/src/rez/rezconfig.py +++ b/src/rez/rezconfig.py @@ -539,6 +539,14 @@ "*PATH" ] +# This setting identifies environment variables that should NOT have path +# normalization applied, even if they match a pattern in ``pathed_env_vars``. +# This is useful for variables like ``CMAKE_MODULE_PATH`` which end in ``PATH`` +# but require forward slashes regardless of the shell. Wildcards are supported. +non_pathed_env_vars = [ + "CMAKE_MODULE_PATH" +] + # Defines what suites on ``$PATH`` stay visible when a new rez environment is resolved. # Possible values are: # diff --git a/src/rez/tests/test_rex.py b/src/rez/tests/test_rex.py index ac3a8966df..23818afadd 100644 --- a/src/rez/tests/test_rex.py +++ b/src/rez/tests/test_rex.py @@ -5,9 +5,9 @@ """ test the rex command generator API """ -from rez.rex import RexExecutor, Python, Setenv, Appendenv, Prependenv, Info, \ - Comment, Alias, Command, Source, Error, Shebang, Unsetenv, expandable, \ - literal +from rez.rex import RexExecutor, Python, ActionInterpreter, Setenv, Appendenv, \ + Prependenv, Info, Comment, Alias, Command, Source, Error, Shebang, Unsetenv, \ + expandable, literal from rez.rex_bindings import VersionBinding, VariantBinding, VariantsBinding, \ RequirementsBinding, EphemeralsBinding, intersects from rez.exceptions import RexError, RexUndefinedVariableError @@ -547,6 +547,33 @@ def test_intersects_ephemerals(self) -> None: self.assertRaises(RuntimeError, # no default intersects, ephemerals.get_range("foo.bar"), "0") + def test_is_pathed_key(self): + """Test that _is_pathed_key correctly identifies path-like env vars.""" + self.assertTrue(ActionInterpreter._is_pathed_key("PATH")) + self.assertTrue(ActionInterpreter._is_pathed_key("PYTHONPATH")) + self.assertTrue(ActionInterpreter._is_pathed_key("LD_LIBRARY_PATH")) + self.assertTrue(ActionInterpreter._is_pathed_key("SOMEPATH")) + + self.assertFalse(ActionInterpreter._is_pathed_key("FOO")) + self.assertFalse(ActionInterpreter._is_pathed_key("HOME")) + + def test_non_pathed_env_vars(self): + """Test that non_pathed_env_vars excludes vars from path normalization.""" + self.assertFalse(ActionInterpreter._is_pathed_key("CMAKE_MODULE_PATH")) + + self.assertTrue(ActionInterpreter._is_pathed_key("PYTHONPATH")) + self.assertTrue(ActionInterpreter._is_pathed_key("PATH")) + + config.override("non_pathed_env_vars", []) + self.assertTrue(ActionInterpreter._is_pathed_key("CMAKE_MODULE_PATH")) + + def test_non_pathed_env_vars_wildcard_patterns(self): + """Test that wildcard patterns work in non_pathed_env_vars.""" + config.override("non_pathed_env_vars", ["*CMAKE*"]) + self.assertFalse(ActionInterpreter._is_pathed_key("CMAKE_MODULE_PATH")) + self.assertFalse(ActionInterpreter._is_pathed_key("CMAKE_PREFIX_PATH")) + self.assertTrue(ActionInterpreter._is_pathed_key("PYTHONPATH")) # not excluded + if __name__ == '__main__': unittest.main() diff --git a/src/rez/tests/test_shells.py b/src/rez/tests/test_shells.py index ee2fa3404e..62774020ef 100644 --- a/src/rez/tests/test_shells.py +++ b/src/rez/tests/test_shells.py @@ -730,5 +730,35 @@ def test_zsh_zshenv(self): ) + @unittest.skipIf(platform_.name != "windows", "cmd shell path normalization only relevant on Windows") + def test_cmd_non_pathed_env_var_not_normalized(self): + """Test that CMAKE_MODULE_PATH values are not backslash-converted in cmd shell output. + + CMAKE_MODULE_PATH ends with PATH so it matches the default pathed_env_vars + pattern, but it is excluded by default via non_pathed_env_vars. CMake + requires forward slashes and breaks if backslashes are used. + """ + sh = create_shell("cmd") + sh.setenv("CMAKE_MODULE_PATH", "C:/foo/bar") + output = sh.get_output() + + self.assertIn("C:/foo/bar", output) + self.assertNotIn("C:\\foo\\bar", output) + + @unittest.skipIf(platform_.name != "windows", "cmd shell path normalization only relevant on Windows") + def test_cmd_pathed_env_var_is_normalized(self): + """Test that a normal *PATH variable still gets backslash-converted in cmd shell output. + + Ensures that non_pathed_env_vars exclusion does not accidentally suppress + normalization for legitimate path variables. + """ + sh = create_shell("cmd") + sh.setenv("SOME_CUSTOM_PATH", "C:/foo/bar") + output = sh.get_output() + + self.assertIn("C:\\foo\\bar", output) + self.assertNotIn("C:/foo/bar", output) + + if __name__ == '__main__': unittest.main() From 15efe75663f4aa8c1d42f86e40d8b9af8347dab6 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Wed, 15 Apr 2026 15:06:06 -0700 Subject: [PATCH 2/7] Fix syntax in install_python macro Signed-off-by: Gabriel Reed --- src/rezplugins/build_system/cmake_files/InstallPython.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rezplugins/build_system/cmake_files/InstallPython.cmake b/src/rezplugins/build_system/cmake_files/InstallPython.cmake index 422ff3944c..5968e4dabf 100644 --- a/src/rezplugins/build_system/cmake_files/InstallPython.cmake +++ b/src/rezplugins/build_system/cmake_files/InstallPython.cmake @@ -107,7 +107,7 @@ macro (install_python) add_custom_command( OUTPUT ${local_fc} COMMAND ${CMAKE_COMMAND} -E make_directory ${pycopy_path} - COMMAND ${py_bin} -c 'import py_compile \; py_compile.compile(\"${fabs}\", \"${local_fc}\", None, True)' + COMMAND ${py_bin} -c "import py_compile; py_compile.compile('${fabs}', '${local_fc}', None, True)" DEPENDS ${fabs} ) From ef4c9e6caf5cf1eb76658b412c092a18b8cfe234 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Wed, 15 Apr 2026 15:15:03 -0700 Subject: [PATCH 3/7] Add VERBATIM in install_python macro Signed-off-by: Gabriel Reed --- src/rezplugins/build_system/cmake_files/InstallPython.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rezplugins/build_system/cmake_files/InstallPython.cmake b/src/rezplugins/build_system/cmake_files/InstallPython.cmake index 5968e4dabf..ba22d2296d 100644 --- a/src/rezplugins/build_system/cmake_files/InstallPython.cmake +++ b/src/rezplugins/build_system/cmake_files/InstallPython.cmake @@ -109,6 +109,7 @@ macro (install_python) COMMAND ${CMAKE_COMMAND} -E make_directory ${pycopy_path} COMMAND ${py_bin} -c "import py_compile; py_compile.compile('${fabs}', '${local_fc}', None, True)" DEPENDS ${fabs} + VERBATIM ) if(install_pyc) From 9ed8de8b9cbe9a8646b50a4f1f14431c7ff2e1f6 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Wed, 15 Apr 2026 15:29:42 -0700 Subject: [PATCH 4/7] Update documentation Signed-off-by: Gabriel Reed --- docs/source/package_commands.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/package_commands.rst b/docs/source/package_commands.rst index 731f207cd5..fc993bffc2 100644 --- a/docs/source/package_commands.rst +++ b/docs/source/package_commands.rst @@ -205,6 +205,12 @@ what environment variables are actually paths. You determine this with the variable ending in ``PATH`` will be treated as a filepath or list of filepaths, and any set/append/prepend operation on it will cause those values to be path-normalized automatically. +However, some variables match ``*PATH`` but must not be path-normalized. For example, +``CMAKE_MODULE_PATH`` requires forward slashes regardless of shell, and backslash conversion will +break CMake. You can exclude variables from normalization using the :data:`non_pathed_env_vars` +config setting, which takes priority over :data:`pathed_env_vars`. By default, +``CMAKE_MODULE_PATH`` is already excluded. Both settings support ``fnmatch``-style wildcards. + .. warning:: Avoid using :data:`os.pathsep` or hardcoded lists of paths such as ``{root}/foo:{root}/bah``. Doing so can cause your package to be incompatible with some shells or From a1c40e150397e180a8f0d119fe08a2ddcf8c8025 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Wed, 29 Jul 2026 14:34:59 -0700 Subject: [PATCH 5/7] PR review updates - Documentation improvements - Replace default value of non_pathed_env_vars from `CMAKE_MODULE_PATH` to `CMAKE_*_PATH` Signed-off-by: Gabriel Reed --- docs/source/package_commands.rst | 2 +- src/rez/rex.py | 1 + src/rez/rezconfig.py | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/package_commands.rst b/docs/source/package_commands.rst index fc993bffc2..b0bc2af24f 100644 --- a/docs/source/package_commands.rst +++ b/docs/source/package_commands.rst @@ -209,7 +209,7 @@ However, some variables match ``*PATH`` but must not be path-normalized. For exa ``CMAKE_MODULE_PATH`` requires forward slashes regardless of shell, and backslash conversion will break CMake. You can exclude variables from normalization using the :data:`non_pathed_env_vars` config setting, which takes priority over :data:`pathed_env_vars`. By default, -``CMAKE_MODULE_PATH`` is already excluded. Both settings support ``fnmatch``-style wildcards. +``CMAKE_MODULE_PATH`` is already excluded. Both settings support :func:`fnmatch`-style wildcards. .. warning:: Avoid using :data:`os.pathsep` or hardcoded lists of paths such as diff --git a/src/rez/rex.py b/src/rez/rex.py index 95edf8b25a..b9b86c1379 100644 --- a/src/rez/rex.py +++ b/src/rez/rex.py @@ -569,6 +569,7 @@ def escape_string(self, value: str | EscapedString, is_path: bool = False) -> st @classmethod def _is_pathed_key(cls, key): + """Return True if ``key`` is a path-like env var subject to normalization.""" if any(fnmatch(key, x) for x in config.non_pathed_env_vars): return False return any(fnmatch(key, x) for x in config.pathed_env_vars) diff --git a/src/rez/rezconfig.py b/src/rez/rezconfig.py index 6650fb781f..8b3960932f 100644 --- a/src/rez/rezconfig.py +++ b/src/rez/rezconfig.py @@ -539,12 +539,13 @@ "*PATH" ] -# This setting identifies environment variables that should NOT have path -# normalization applied, even if they match a pattern in ``pathed_env_vars``. +# This setting identifies environment variables that should not have path +# normalization applied, even if they match a pattern in :data:`pathed_env_vars`. # This is useful for variables like ``CMAKE_MODULE_PATH`` which end in ``PATH`` # but require forward slashes regardless of the shell. Wildcards are supported. +# Takes priority over :data:`pathed_env_vars`. non_pathed_env_vars = [ - "CMAKE_MODULE_PATH" + "CMAKE_*_PATH" ] # Defines what suites on ``$PATH`` stay visible when a new rez environment is resolved. From b33d7f40bd27003a48de2c23d1a32c572faf0c1a Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Fri, 31 Jul 2026 08:11:13 -0700 Subject: [PATCH 6/7] Formatting fix Signed-off-by: Gabriel Reed --- src/rez/tests/test_shells.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rez/tests/test_shells.py b/src/rez/tests/test_shells.py index 62774020ef..68db5c4297 100644 --- a/src/rez/tests/test_shells.py +++ b/src/rez/tests/test_shells.py @@ -729,7 +729,6 @@ def test_zsh_zshenv(self): "Expected rez wrapper .zshenv in ZDOTDIR=%s" % zdotdir, ) - @unittest.skipIf(platform_.name != "windows", "cmd shell path normalization only relevant on Windows") def test_cmd_non_pathed_env_var_not_normalized(self): """Test that CMAKE_MODULE_PATH values are not backslash-converted in cmd shell output. From f0ba2af2b21ae6254851349d7acf40337d2c9d68 Mon Sep 17 00:00:00 2001 From: Gabriel Reed Date: Tue, 4 Aug 2026 09:56:44 -0700 Subject: [PATCH 7/7] Improve py_compile step to ensure safety with backslashes or single quotes Signed-off-by: Gabriel Reed --- src/rezplugins/build_system/cmake_files/InstallPython.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rezplugins/build_system/cmake_files/InstallPython.cmake b/src/rezplugins/build_system/cmake_files/InstallPython.cmake index ba22d2296d..8d3df4b38a 100644 --- a/src/rezplugins/build_system/cmake_files/InstallPython.cmake +++ b/src/rezplugins/build_system/cmake_files/InstallPython.cmake @@ -107,7 +107,7 @@ macro (install_python) add_custom_command( OUTPUT ${local_fc} COMMAND ${CMAKE_COMMAND} -E make_directory ${pycopy_path} - COMMAND ${py_bin} -c "import py_compile; py_compile.compile('${fabs}', '${local_fc}', None, True)" + COMMAND ${py_bin} -c "import sys; import py_compile; py_compile.compile(sys.argv[1], sys.argv[2], None, True)" ${fabs} ${local_fc} DEPENDS ${fabs} VERBATIM )