FindEnvModules.cmake 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file Copyright.txt or https://cmake.org/licensing for details.
  3. #[=======================================================================[.rst:
  4. FindEnvModules
  5. --------------
  6. .. versionadded:: 3.15
  7. Locate an environment module implementation and make commands available to
  8. CMake scripts to use them. This is compatible with both Lua-based Lmod
  9. and TCL-based EnvironmentModules.
  10. This module is intended for the use case of setting up the compiler and library
  11. environment within a :ref:`CTest Script <CTest Script>` (``ctest -S``). It can
  12. also be used in a :ref:`CMake Script <Script Processing Mode>` (``cmake -P``).
  13. .. note::
  14. The loaded environment will not survive past the end of the calling process.
  15. Do not use this module in project code (``CMakeLists.txt`` files) to load
  16. a compiler environment; it will not be available during the build. Instead
  17. load the environment manually before running CMake or using the generated
  18. build system.
  19. Example Usage
  20. ^^^^^^^^^^^^^
  21. .. code-block:: cmake
  22. set(CTEST_BUILD_NAME "CrayLinux-CrayPE-Cray-dynamic")
  23. set(CTEST_BUILD_CONFIGURATION Release)
  24. set(CTEST_BUILD_FLAGS "-k -j8")
  25. set(CTEST_CMAKE_GENERATOR "Unix Makefiles")
  26. ...
  27. find_package(EnvModules REQUIRED)
  28. env_module(purge)
  29. env_module(load modules)
  30. env_module(load craype)
  31. env_module(load PrgEnv-cray)
  32. env_module(load craype-knl)
  33. env_module(load cray-mpich)
  34. env_module(load cray-libsci)
  35. set(ENV{CRAYPE_LINK_TYPE} dynamic)
  36. ...
  37. Result Variables
  38. ^^^^^^^^^^^^^^^^
  39. This module will set the following variables in your project:
  40. ``EnvModules_FOUND``
  41. True if a compatible environment modules framework was found.
  42. Cache Variables
  43. ^^^^^^^^^^^^^^^
  44. The following cache variable will be set:
  45. ``EnvModules_COMMAND``
  46. The low level module command to use. Currently supported
  47. implementations are the Lua based Lmod and TCL based EnvironmentModules.
  48. Environment Variables
  49. ^^^^^^^^^^^^^^^^^^^^^
  50. ``ENV{MODULESHOME}``
  51. Usually set by the module environment implementation, used as a hint to
  52. locate the module command to execute.
  53. Provided Functions
  54. ^^^^^^^^^^^^^^^^^^
  55. This defines the following CMake functions for interacting with environment
  56. modules:
  57. .. command:: env_module
  58. Execute an arbitrary module command:
  59. .. code-block:: cmake
  60. env_module(cmd arg1 ... argN)
  61. env_module(
  62. COMMAND cmd arg1 ... argN
  63. [OUTPUT_VARIABLE <out-var>]
  64. [RESULT_VARIABLE <ret-var>]
  65. )
  66. The options are:
  67. ``cmd arg1 ... argN``
  68. The module sub-command and arguments to execute as if they were
  69. passed directly to the module command in your shell environment.
  70. ``OUTPUT_VARIABLE <out-var>``
  71. The standard output from executing the module command.
  72. ``RESULT_VARIABLE <ret-var>``
  73. The return code from executing the module command.
  74. .. command:: env_module_swap
  75. Swap one module for another:
  76. .. code-block:: cmake
  77. env_module_swap(out_mod in_mod
  78. [OUTPUT_VARIABLE <out-var>]
  79. [RESULT_VARIABLE <ret-var>]
  80. )
  81. This is functionally equivalent to the ``module swap out_mod in_mod`` shell
  82. command. The options are:
  83. ``OUTPUT_VARIABLE <out-var>``
  84. The standard output from executing the module command.
  85. ``RESULT_VARIABLE <ret-var>``
  86. The return code from executing the module command.
  87. .. command:: env_module_list
  88. Retrieve the list of currently loaded modules:
  89. .. code-block:: cmake
  90. env_module_list(<out-var>)
  91. This is functionally equivalent to the ``module list`` shell command.
  92. The result is stored in ``<out-var>`` as a properly formatted CMake
  93. :ref:`semicolon-separated list <CMake Language Lists>` variable.
  94. .. command:: env_module_avail
  95. Retrieve the list of available modules:
  96. .. code-block:: cmake
  97. env_module_avail([<mod-prefix>] <out-var>)
  98. This is functionally equivalent to the ``module avail <mod-prefix>`` shell
  99. command. The result is stored in ``<out-var>`` as a properly formatted
  100. CMake :ref:`semicolon-separated list <CMake Language Lists>` variable.
  101. #]=======================================================================]
  102. function(env_module)
  103. if(NOT EnvModules_COMMAND)
  104. message(FATAL_ERROR "Failed to process module command. EnvModules_COMMAND not found")
  105. return()
  106. endif()
  107. set(options)
  108. set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
  109. set(multiValueArgs COMMAND)
  110. cmake_parse_arguments(MOD_ARGS
  111. "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
  112. )
  113. if(NOT MOD_ARGS_COMMAND)
  114. # If no explicit command argument was given, then treat the calling syntax
  115. # as: module(cmd args...)
  116. set(exec_cmd ${ARGV})
  117. else()
  118. set(exec_cmd ${MOD_ARGS_COMMAND})
  119. endif()
  120. if(MOD_ARGS_OUTPUT_VARIABLE)
  121. set(err_var_args ERROR_VARIABLE err_var)
  122. endif()
  123. execute_process(
  124. COMMAND mktemp -t module.cmake.XXXXXXXXXXXX
  125. OUTPUT_VARIABLE tempfile_name
  126. )
  127. string(STRIP "${tempfile_name}" tempfile_name)
  128. # If the $MODULESHOME/init/cmake file exists then assume that the CMake
  129. # "shell" functionality exits
  130. if(EXISTS "$ENV{MODULESHOME}/init/cmake")
  131. execute_process(
  132. COMMAND ${EnvModules_COMMAND} cmake ${exec_cmd}
  133. OUTPUT_FILE ${tempfile_name}
  134. ${err_var_args}
  135. RESULT_VARIABLE ret_var
  136. )
  137. else() # fallback to the sh shell and manually convert to CMake
  138. execute_process(
  139. COMMAND ${EnvModules_COMMAND} sh ${exec_cmd}
  140. OUTPUT_VARIABLE out_var
  141. ${err_var_args}
  142. RESULT_VARIABLE ret_var
  143. )
  144. endif()
  145. # If we executed successfully then process and cleanup the temp file
  146. if(ret_var EQUAL 0)
  147. # No CMake shell so we need to process the sh output into CMake code
  148. if(NOT EXISTS "$ENV{MODULESHOME}/init/cmake")
  149. file(WRITE ${tempfile_name} "")
  150. string(REPLACE "\n" ";" out_var "${out_var}")
  151. foreach(sh_cmd IN LISTS out_var)
  152. if(sh_cmd MATCHES "^ *unset *([^ ]*)")
  153. set(cmake_cmd "unset(ENV{${CMAKE_MATCH_1}})")
  154. elseif(sh_cmd MATCHES "^ *export *([^ ]*)")
  155. set(cmake_cmd "set(ENV{${CMAKE_MATCH_1}} \"\${${CMAKE_MATCH_1}}\")")
  156. elseif(sh_cmd MATCHES " *([^ =]*) *= *(.*)")
  157. set(var_name "${CMAKE_MATCH_1}")
  158. set(var_value "${CMAKE_MATCH_2}")
  159. if(var_value MATCHES "^\"(.*[^\\])\"")
  160. # If it's in quotes, take the value as is
  161. set(var_value "${CMAKE_MATCH_1}")
  162. else()
  163. # Otherwise, strip trailing spaces
  164. string(REGEX REPLACE "([^\\])? +$" "\\1" var_value "${var_value}")
  165. endif()
  166. string(REPLACE "\\ " " " var_value "${var_value}")
  167. set(cmake_cmd "set(${var_name} \"${var_value}\")")
  168. else()
  169. continue()
  170. endif()
  171. file(APPEND ${tempfile_name} "${cmake_cmd}\n")
  172. endforeach()
  173. endif()
  174. # Process the change in environment variables
  175. include(${tempfile_name})
  176. file(REMOVE ${tempfile_name})
  177. endif()
  178. # Push the output back out to the calling scope
  179. if(MOD_ARGS_OUTPUT_VARIABLE)
  180. set(${MOD_ARGS_OUTPUT_VARIABLE} "${err_var}" PARENT_SCOPE)
  181. endif()
  182. if(MOD_ARGS_RESULT_VARIABLE)
  183. set(${MOD_ARGS_RESULT_VARIABLE} ${ret_var} PARENT_SCOPE)
  184. endif()
  185. endfunction(env_module)
  186. #------------------------------------------------------------------------------
  187. function(env_module_swap out_mod in_mod)
  188. set(options)
  189. set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
  190. set(multiValueArgs)
  191. cmake_parse_arguments(MOD_ARGS
  192. "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
  193. )
  194. env_module(COMMAND -t swap ${out_mod} ${in_mod}
  195. OUTPUT_VARIABLE tmp_out
  196. RETURN_VARIABLE tmp_ret
  197. )
  198. if(MOD_ARGS_OUTPUT_VARIABLE)
  199. set(${MOD_ARGS_OUTPUT_VARIABLE} "${tmp_out}" PARENT_SCOPE)
  200. endif()
  201. if(MOD_ARGS_RESULT_VARIABLE)
  202. set(${MOD_ARGS_RESULT_VARIABLE} ${tmp_ret} PARENT_SCOPE)
  203. endif()
  204. endfunction()
  205. #------------------------------------------------------------------------------
  206. function(env_module_list out_var)
  207. cmake_policy(SET CMP0007 NEW)
  208. env_module(COMMAND -t list OUTPUT_VARIABLE tmp_out)
  209. # Convert output into a CMake list
  210. string(REPLACE "\n" ";" ${out_var} "${tmp_out}")
  211. # Remove title headers and empty entries
  212. list(REMOVE_ITEM ${out_var} "No modules loaded")
  213. if(${out_var})
  214. list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
  215. endif()
  216. list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
  217. set(${out_var} ${${out_var}} PARENT_SCOPE)
  218. endfunction()
  219. #------------------------------------------------------------------------------
  220. function(env_module_avail)
  221. cmake_policy(SET CMP0007 NEW)
  222. if(ARGC EQUAL 1)
  223. set(mod_prefix)
  224. set(out_var ${ARGV0})
  225. elseif(ARGC EQUAL 2)
  226. set(mod_prefix ${ARGV0})
  227. set(out_var ${ARGV1})
  228. else()
  229. message(FATAL_ERROR "Usage: env_module_avail([mod_prefix] out_var)")
  230. endif()
  231. env_module(COMMAND -t avail ${mod_prefix} OUTPUT_VARIABLE tmp_out)
  232. # Convert output into a CMake list
  233. string(REPLACE "\n" ";" tmp_out "${tmp_out}")
  234. set(${out_var})
  235. foreach(MOD IN LISTS tmp_out)
  236. # Remove directory entries and empty values
  237. if(MOD MATCHES "^(.*:)?$")
  238. continue()
  239. endif()
  240. # Convert default modules
  241. if(MOD MATCHES "^(.*)/$" ) # "foo/"
  242. list(APPEND ${out_var} ${CMAKE_MATCH_1})
  243. elseif(MOD MATCHES "^((.*)/.*)\\(default\\)$") # "foo/1.2.3(default)"
  244. list(APPEND ${out_var} ${CMAKE_MATCH_2})
  245. list(APPEND ${out_var} ${CMAKE_MATCH_1})
  246. else()
  247. list(APPEND ${out_var} ${MOD})
  248. endif()
  249. endforeach()
  250. set(${out_var} ${${out_var}} PARENT_SCOPE)
  251. endfunction()
  252. #------------------------------------------------------------------------------
  253. # Make sure we know where the underlying module command is
  254. find_program(EnvModules_COMMAND
  255. NAMES lmod modulecmd
  256. HINTS ENV MODULESHOME
  257. PATH_SUFFIXES libexec
  258. )
  259. include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
  260. find_package_handle_standard_args(EnvModules DEFAULT_MSG EnvModules_COMMAND)