FindEnvModules.cmake 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file LICENSE.rst or https://cmake.org/licensing for details.
  3. #[=======================================================================[.rst:
  4. FindEnvModules
  5. --------------
  6. .. versionadded:: 3.15
  7. Finds an Environment Modules implementation and provides commands for use in
  8. CMake scripts:
  9. .. code-block:: cmake
  10. find_package(EnvModules [...])
  11. The Environment Modules system is a command-line tool that manages Unix-like
  12. shell environments by dynamically modifying environment variables.
  13. It is commonly used in High-Performance Computing (HPC) environments to
  14. support multiple software versions or configurations.
  15. This module is compatible with the two most common implementations:
  16. * Lua-based Lmod
  17. * TCL-based Environment Modules
  18. This module is primarily intended for setting up compiler and library
  19. environments within a :ref:`CTest Script <CTest Script>` (``ctest -S``).
  20. It may also be used in a :ref:`CMake Script <Script Processing Mode>`
  21. (``cmake -P``).
  22. .. note::
  23. The loaded environment will not persist beyond the end of the calling
  24. process. Do not use this module in CMake project code (such as
  25. ``CMakeLists.txt``) to load compiler environments, as the environment
  26. changes will not be available during the build phase. In such a case, load
  27. the desired environment before invoking CMake or the generated build system.
  28. Result Variables
  29. ^^^^^^^^^^^^^^^^
  30. This module defines the following variables:
  31. ``EnvModules_FOUND``
  32. Boolean indicating whether a compatible Environment Modules framework was
  33. found.
  34. Cache Variables
  35. ^^^^^^^^^^^^^^^
  36. The following cache variables may also be set:
  37. ``EnvModules_COMMAND``
  38. The path to a low level module command to use.
  39. Hints
  40. ^^^^^
  41. This module accepts the following variables before calling the
  42. ``find_package(EnvModules)``:
  43. ``ENV{MODULESHOME}``
  44. This environment variable is usually set by the Environment Modules
  45. implementation, and can be used as a hint to locate the module command to
  46. execute.
  47. Commands
  48. ^^^^^^^^
  49. This module provides the following commands for interacting with the
  50. Environment Modules system, if found:
  51. .. command:: env_module
  52. Executes an arbitrary module command:
  53. .. code-block:: cmake
  54. env_module(<command> <args>...)
  55. env_module(
  56. COMMAND <command> <args>...
  57. [OUTPUT_VARIABLE <out-var>]
  58. [RESULT_VARIABLE <ret-var>]
  59. )
  60. The options are:
  61. ``COMMAND <command> <args>...``
  62. The module sub-command and arguments to execute as if passed directly to
  63. the module command in the shell environment.
  64. ``OUTPUT_VARIABLE <out-var>``
  65. Stores the standard output of the executed module command in the specified
  66. variable.
  67. ``RESULT_VARIABLE <ret-var>``
  68. Stores the return code of the executed module command in the specified
  69. variable.
  70. .. command:: env_module_swap
  71. Swaps one module for another:
  72. .. code-block:: cmake
  73. env_module_swap(
  74. <out-mod>
  75. <in-mod>
  76. [OUTPUT_VARIABLE <out-var>]
  77. [RESULT_VARIABLE <ret-var>]
  78. )
  79. This is functionally equivalent to the ``module swap <out-mod> <in-mod>``
  80. shell command.
  81. The options are:
  82. ``OUTPUT_VARIABLE <out-var>``
  83. Stores the standard output of the executed module command in the specified
  84. variable.
  85. ``RESULT_VARIABLE <ret-var>``
  86. Stores the return code of the executed module command in the specified
  87. variable.
  88. .. command:: env_module_list
  89. Retrieves the list of currently loaded modules:
  90. .. code-block:: cmake
  91. env_module_list(<out-var>)
  92. This is functionally equivalent to the ``module list`` shell command.
  93. The result is stored in ``<out-var>`` as a properly formatted CMake
  94. :ref:`semicolon-separated list <CMake Language Lists>` variable.
  95. .. command:: env_module_avail
  96. Retrieves the list of available modules:
  97. .. code-block:: cmake
  98. env_module_avail([<mod-prefix>] <out-var>)
  99. This is functionally equivalent to the ``module avail <mod-prefix>`` shell
  100. command. The result is stored in ``<out-var>`` as a properly formatted
  101. CMake :ref:`semicolon-separated list <CMake Language Lists>` variable.
  102. Examples
  103. ^^^^^^^^
  104. In the following example, this module is used in a CTest script to configure
  105. the compiler and libraries for a Cray Programming Environment.
  106. After the Environment Modules system is found, the ``env_module()`` command is
  107. used to load the necessary compiler, MPI, and scientific libraries to set up
  108. the build environment. The ``CRAYPE_LINK_TYPE`` environment variable is set
  109. to ``dynamic`` to specify dynamic linking. This instructs the Cray Linux
  110. Environment compiler driver to link against dynamic libraries at runtime,
  111. rather than linking static libraries at compile time. As a result, the
  112. compiler produces dynamically linked executable files.
  113. .. code-block:: cmake
  114. :caption: ``example-script.cmake``
  115. set(CTEST_BUILD_NAME "CrayLinux-CrayPE-Cray-dynamic")
  116. set(CTEST_BUILD_CONFIGURATION Release)
  117. set(CTEST_BUILD_FLAGS "-k -j8")
  118. set(CTEST_CMAKE_GENERATOR "Unix Makefiles")
  119. # ...
  120. find_package(EnvModules REQUIRED)
  121. # Clear all currently loaded Environment Modules to start with a clean state
  122. env_module(purge)
  123. # Load the base module-handling system to use other modules
  124. env_module(load modules)
  125. # Load Cray Programming Environment (Cray PE) support, which manages
  126. # platform-specific optimizations and architecture selection
  127. env_module(load craype)
  128. # Load the Cray programming environment
  129. env_module(load PrgEnv-cray)
  130. # Load settings targeting the Intel Knights Landing (KNL) CPU architecture
  131. env_module(load craype-knl)
  132. # Load the Cray MPI (Message Passing Interface) library, needed for
  133. # distributed computing
  134. env_module(load cray-mpich)
  135. # Load Cray's scientific library package, which includes optimized math
  136. # libraries (like BLAS, LAPACK)
  137. env_module(load cray-libsci)
  138. set(ENV{CRAYPE_LINK_TYPE} dynamic)
  139. # ...
  140. #]=======================================================================]
  141. function(env_module)
  142. if(NOT EnvModules_COMMAND)
  143. message(FATAL_ERROR "Failed to process module command. EnvModules_COMMAND not found")
  144. return()
  145. endif()
  146. set(options)
  147. set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
  148. set(multiValueArgs COMMAND)
  149. cmake_parse_arguments(MOD_ARGS
  150. "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
  151. )
  152. if(NOT MOD_ARGS_COMMAND)
  153. # If no explicit command argument was given, then treat the calling syntax
  154. # as: module(cmd args...)
  155. set(exec_cmd ${ARGV})
  156. else()
  157. set(exec_cmd ${MOD_ARGS_COMMAND})
  158. endif()
  159. if(MOD_ARGS_OUTPUT_VARIABLE)
  160. set(err_var_args ERROR_VARIABLE err_var)
  161. endif()
  162. execute_process(
  163. COMMAND mktemp -t module.cmake.XXXXXXXXXXXX
  164. OUTPUT_VARIABLE tempfile_name
  165. )
  166. string(STRIP "${tempfile_name}" tempfile_name)
  167. # If the $MODULESHOME/init/cmake file exists then assume that the CMake
  168. # "shell" functionality exits
  169. if(EXISTS "$ENV{MODULESHOME}/init/cmake")
  170. execute_process(
  171. COMMAND ${EnvModules_COMMAND} cmake ${exec_cmd}
  172. OUTPUT_FILE ${tempfile_name}
  173. ${err_var_args}
  174. RESULT_VARIABLE ret_var
  175. )
  176. else() # fallback to the sh shell and manually convert to CMake
  177. execute_process(
  178. COMMAND ${EnvModules_COMMAND} sh ${exec_cmd}
  179. OUTPUT_VARIABLE out_var
  180. ${err_var_args}
  181. RESULT_VARIABLE ret_var
  182. )
  183. endif()
  184. # If we executed successfully then process and cleanup the temp file
  185. if(ret_var EQUAL 0)
  186. # No CMake shell so we need to process the sh output into CMake code
  187. if(NOT EXISTS "$ENV{MODULESHOME}/init/cmake")
  188. file(WRITE ${tempfile_name} "")
  189. string(REPLACE "\n" ";" out_var "${out_var}")
  190. foreach(sh_cmd IN LISTS out_var)
  191. if(sh_cmd MATCHES "^ *unset *([^ ]*)")
  192. set(cmake_cmd "unset(ENV{${CMAKE_MATCH_1}})")
  193. elseif(sh_cmd MATCHES "^ *export *([^ ]*)")
  194. set(cmake_cmd "set(ENV{${CMAKE_MATCH_1}} \"\${${CMAKE_MATCH_1}}\")")
  195. elseif(sh_cmd MATCHES " *([^ =]*) *= *(.*)")
  196. set(var_name "${CMAKE_MATCH_1}")
  197. set(var_value "${CMAKE_MATCH_2}")
  198. if(var_value MATCHES "^\"(.*[^\\])\"")
  199. # If it's in quotes, take the value as is
  200. set(var_value "${CMAKE_MATCH_1}")
  201. else()
  202. # Otherwise, strip trailing spaces
  203. string(REGEX REPLACE "([^\\])? +$" "\\1" var_value "${var_value}")
  204. endif()
  205. string(REPLACE "\\ " " " var_value "${var_value}")
  206. set(cmake_cmd "set(${var_name} \"${var_value}\")")
  207. else()
  208. continue()
  209. endif()
  210. file(APPEND ${tempfile_name} "${cmake_cmd}\n")
  211. endforeach()
  212. endif()
  213. # Process the change in environment variables
  214. include(${tempfile_name})
  215. file(REMOVE ${tempfile_name})
  216. endif()
  217. # Push the output back out to the calling scope
  218. if(MOD_ARGS_OUTPUT_VARIABLE)
  219. set(${MOD_ARGS_OUTPUT_VARIABLE} "${err_var}" PARENT_SCOPE)
  220. endif()
  221. if(MOD_ARGS_RESULT_VARIABLE)
  222. set(${MOD_ARGS_RESULT_VARIABLE} ${ret_var} PARENT_SCOPE)
  223. endif()
  224. endfunction()
  225. #------------------------------------------------------------------------------
  226. function(env_module_swap out_mod in_mod)
  227. set(options)
  228. set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
  229. set(multiValueArgs)
  230. cmake_parse_arguments(MOD_ARGS
  231. "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
  232. )
  233. env_module(COMMAND -t swap ${out_mod} ${in_mod}
  234. OUTPUT_VARIABLE tmp_out
  235. RETURN_VARIABLE tmp_ret
  236. )
  237. if(MOD_ARGS_OUTPUT_VARIABLE)
  238. set(${MOD_ARGS_OUTPUT_VARIABLE} "${tmp_out}" PARENT_SCOPE)
  239. endif()
  240. if(MOD_ARGS_RESULT_VARIABLE)
  241. set(${MOD_ARGS_RESULT_VARIABLE} ${tmp_ret} PARENT_SCOPE)
  242. endif()
  243. endfunction()
  244. #------------------------------------------------------------------------------
  245. function(env_module_list out_var)
  246. env_module(COMMAND -t list OUTPUT_VARIABLE tmp_out)
  247. # Convert output into a CMake list
  248. string(REPLACE "\n" ";" ${out_var} "${tmp_out}")
  249. # Remove title headers and empty entries
  250. list(REMOVE_ITEM ${out_var} "No modules loaded")
  251. if(${out_var})
  252. list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
  253. endif()
  254. list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
  255. set(${out_var} ${${out_var}} PARENT_SCOPE)
  256. endfunction()
  257. #------------------------------------------------------------------------------
  258. function(env_module_avail)
  259. if(ARGC EQUAL 1)
  260. set(mod_prefix)
  261. set(out_var ${ARGV0})
  262. elseif(ARGC EQUAL 2)
  263. set(mod_prefix ${ARGV0})
  264. set(out_var ${ARGV1})
  265. else()
  266. message(FATAL_ERROR "Usage: env_module_avail([mod_prefix] out_var)")
  267. endif()
  268. env_module(COMMAND -t avail ${mod_prefix} OUTPUT_VARIABLE tmp_out)
  269. # Convert output into a CMake list
  270. string(REPLACE "\n" ";" tmp_out "${tmp_out}")
  271. set(${out_var})
  272. foreach(MOD IN LISTS tmp_out)
  273. # Remove directory entries and empty values
  274. if(MOD MATCHES "^(.*:)?$")
  275. continue()
  276. endif()
  277. # Convert default modules
  278. if(MOD MATCHES "^(.*)/$" ) # "foo/"
  279. list(APPEND ${out_var} ${CMAKE_MATCH_1})
  280. elseif(MOD MATCHES "^((.*)/.*)\\(default\\)$") # "foo/1.2.3(default)"
  281. list(APPEND ${out_var} ${CMAKE_MATCH_2})
  282. list(APPEND ${out_var} ${CMAKE_MATCH_1})
  283. else()
  284. list(APPEND ${out_var} ${MOD})
  285. endif()
  286. endforeach()
  287. set(${out_var} ${${out_var}} PARENT_SCOPE)
  288. endfunction()
  289. #------------------------------------------------------------------------------
  290. # Make sure we know where the underlying module command is
  291. find_program(EnvModules_COMMAND
  292. NAMES lmod modulecmd
  293. HINTS ENV MODULESHOME
  294. PATH_SUFFIXES libexec
  295. )
  296. include(FindPackageHandleStandardArgs)
  297. find_package_handle_standard_args(EnvModules DEFAULT_MSG EnvModules_COMMAND)