localization and parsing program output
cmake sometimes needs to execute_process(), try_compile(), try_run(), etc. a program and then parse the program's output. For example, to determine a compiler's implicit include directory path cmake can call try_compile() with verbose compiler output and parse out the path from the program output. Unfortunately, many programs (e.g. gcc) use localizations to allow users to customize the language of their output. The localization is set using environment variables like LANG, LC_ALL, LANGUAGE, etc. This behavior may confuse cmake modules trying to parse program output.
Example: a cmake gcc implicit include directory parser may look for:
#include "..." search starts here:
in the verbose compiler output, but if LANG is set to de_DE (German) gcc could print something like this instead:
Suche für »#include \"...\"« beginnt hier:
and the parser would miss it.
An easy way to handle this problem is to set LANG, et al. to the default value of "C" to switch off localization while running a program and then restoring LANG, et al. once the run is complete.
cmake currently does not provide a mechanism to do this, so contributors to cmake modules in the Modules directory have handled it in an ad hoc way, leading to duplicated and inconsistent code in the Modules directory to address this issue:
h3:src/cmake % fgrep -R ENV . | fgrep LANG ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(_orig_lang $ENV{LANG}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LANG} C) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LANG} ${_orig_lang}) ./Modules/FindHg.cmake: set(_saved_language "$ENV{LANGUAGE}") ./Modules/FindHg.cmake: set(ENV{LANGUAGE}) ./Modules/FindHg.cmake: set(ENV{LANGUAGE} ${_saved_language}) ./Source/cmGlobalGenerator.cxx: // put ${CMake_(LANG)_COMPILER_ENV_VAR}=${CMAKE_(LANG)_COMPILER h3:src/cmake % fgrep -R ENV . | egrep 'LANG|LC_' ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(_orig_lc_all $ENV{LC_ALL}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(_orig_lc_messages $ENV{LC_MESSAGES}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(_orig_lang $ENV{LANG}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LC_ALL} C) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LC_MESSAGES} C) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LANG} C) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LC_ALL} ${_orig_lc_all}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LC_MESSAGES} ${_orig_lc_messages}) ./Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake:set(ENV{LANG} ${_orig_lang}) ./Modules/FindBISON.cmake: set(_Bison_SAVED_LC_ALL "$ENV{LC_ALL}") ./Modules/FindBISON.cmake: set(ENV{LC_ALL} C) ./Modules/FindBISON.cmake: set(ENV{LC_ALL} ${_Bison_SAVED_LC_ALL}) ./Modules/FindHg.cmake: set(_saved_lc_all "$ENV{LC_ALL}") ./Modules/FindHg.cmake: set(ENV{LC_ALL} "C") ./Modules/FindHg.cmake: set(_saved_language "$ENV{LANGUAGE}") ./Modules/FindHg.cmake: set(ENV{LANGUAGE}) ./Modules/FindHg.cmake: set(ENV{LC_ALL} ${_saved_lc_all}) ./Modules/FindHg.cmake: set(ENV{LANGUAGE} ${_saved_language}) ./Modules/FindIce.cmake: set(_Ice_SAVED_LC_ALL "$ENV{LC_ALL}") ./Modules/FindIce.cmake: set(ENV{LC_ALL} C) ./Modules/FindIce.cmake: set(ENV{LC_ALL} ${_Ice_SAVED_LC_ALL}) ./Modules/FindSubversion.cmake: set(_Subversion_SAVED_LC_ALL "$ENV{LC_ALL}") ./Modules/FindSubversion.cmake: set(ENV{LC_ALL} C) ./Modules/FindSubversion.cmake: set(ENV{LC_ALL} ${_Subversion_SAVED_LC_ALL}) ./Modules/FindSubversion.cmake: set(_Subversion_SAVED_LC_ALL "$ENV{LC_ALL}") ./Modules/FindSubversion.cmake: set(ENV{LC_ALL} C) ./Modules/FindSubversion.cmake: set(ENV{LC_ALL} ${_Subversion_SAVED_LC_ALL}) ./Source/cmGlobalGenerator.cxx: // put ${CMake_(LANG)_COMPILER_ENV_VAR}=${CMAKE_(LANG)_COMPILER ./Tests/CMakeLists.txt: set_tests_properties(CTest.UpdateBZR.CLocale PROPERTIES ENVIRONMENT LC_ALL=C) h3:src/cmake %
We discussed this in !2716 (merged) and several solutions are possible:
- do nothing. let each module handle localization issues on its own, as above.
- use something like
${CMAKE_COMMAND} -E env ..
when starting processes. The module will need a list of LC_*/LANG/LANGUAGE variables to set. It needs to work with try_compile() too. - provide a standard cmake API to save and restore the localization variables using a .cmake file in Modules. There is a working proof-of-concept version of this in !2781 (closed)
- add a new option keyword arg to the execute_process(), try_compile(), try_run(), etc. builtin APIs that disables localization just for processes forked off by those calls.
Other solutions may be possible as well?