summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Help/manual/cmake-modules.7.rst1
-rw-r--r--Help/module/FindEnvModules.rst1
-rw-r--r--Help/release/dev/environment-modules.rst5
-rw-r--r--Modules/FindEnvModules.cmake333
-rw-r--r--Tests/CMakeLists.txt4
-rw-r--r--Tests/FindEnvModules/CMakeLists.txt3
-rw-r--r--Tests/FindEnvModules/EnvModules.cmake35
7 files changed, 382 insertions, 0 deletions
diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst
index d9b939f3bf..fc4bfdc7ab 100644
--- a/Help/manual/cmake-modules.7.rst
+++ b/Help/manual/cmake-modules.7.rst
@@ -125,6 +125,7 @@ They are normally called through the :command:`find_package` command.
/module/FindDCMTK
/module/FindDevIL
/module/FindDoxygen
+ /module/FindEnvModules
/module/FindEXPAT
/module/FindFLEX
/module/FindFLTK2
diff --git a/Help/module/FindEnvModules.rst b/Help/module/FindEnvModules.rst
new file mode 100644
index 0000000000..72c120f221
--- /dev/null
+++ b/Help/module/FindEnvModules.rst
@@ -0,0 +1 @@
+.. cmake-module:: ../../Modules/FindEnvModules.cmake
diff --git a/Help/release/dev/environment-modules.rst b/Help/release/dev/environment-modules.rst
new file mode 100644
index 0000000000..eace35d751
--- /dev/null
+++ b/Help/release/dev/environment-modules.rst
@@ -0,0 +1,5 @@
+environment-modules
+-------------------
+
+* The :module:`FindEnvModules` module was added to use Lua- and TCL-based
+ environment modules in :ref:`CTest Scripts <CTest Script>`.
diff --git a/Modules/FindEnvModules.cmake b/Modules/FindEnvModules.cmake
new file mode 100644
index 0000000000..5d3452dfa4
--- /dev/null
+++ b/Modules/FindEnvModules.cmake
@@ -0,0 +1,333 @@
+# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+FindEnvModules
+--------------
+
+Locate an environment module implementation and make commands available to
+CMake scripts to use them. This is compatible with both Lua-based Lmod
+and TCL-based EnvironmentModules.
+
+This module is intended for the use case of setting up the compiler and library
+environment within a :ref:`CTest Script <CTest Script>` (``ctest -S``). It can
+also be used in a :ref:`CMake Script <Script Processing Mode>` (``cmake -P``).
+
+.. note::
+
+ The loaded environment will not survive past the end of the calling process.
+ Do not use this module in project code (``CMakeLists.txt`` files) to load
+ a compiler environment; it will not be available during the build. Instead
+ load the environment manually before running CMake or using the generated
+ build system.
+
+Example Usage
+^^^^^^^^^^^^^
+
+.. code-block:: cmake
+
+ set(CTEST_BUILD_NAME "CrayLinux-CrayPE-Cray-dynamic")
+ set(CTEST_BUILD_CONFIGURATION Release)
+ set(CTEST_BUILD_FLAGS "-k -j8")
+ set(CTEST_CMAKE_GENERATOR "Unix Makefiles")
+
+ ...
+
+ find_package(EnvModules REQUIRED)
+
+ env_module(purge)
+ env_module(load modules)
+ env_module(load craype)
+ env_module(load PrgEnv-cray)
+ env_module(load craype-knl)
+ env_module(load cray-mpich)
+ env_module(load cray-libsci)
+
+ set(ENV{CRAYPE_LINK_TYPE} dynamic)
+
+ ...
+
+Result Variables
+^^^^^^^^^^^^^^^^
+
+This module will set the following variables in your project:
+
+``EnvModules_FOUND``
+ Found the a compatible environment modules framework
+
+Cache Variables
+^^^^^^^^^^^^^^^
+
+The following cache variable will be set:
+
+``EnvModules_COMMAND``
+ The low level module command to use. Currently supported are
+ implementations are the Lua based Lmod and TCL based EnvironmentModules.
+
+Environment Variables
+^^^^^^^^^^^^^^^^^^^^^
+
+``ENV{MODULESHOME}``
+ Usually set by the module environment implementation, used as a hint to
+ locate the module command to execute.
+
+Provided Functions
+^^^^^^^^^^^^^^^^^^
+
+This defines the following cmake functions for interacting with environment
+modules:
+
+.. command:: env_module
+
+ Execute an aribitrary module command:
+
+ .. code-block:: cmake
+
+ env_module(cmd arg1 ... argN)
+ env_module(
+ COMMAND cmd arg1 ... argN
+ [OUTPUT_VARIABLE <out-var>]
+ [RESULT_VARIABLE <ret-var>]
+ )
+
+ The options are:
+
+ ``cmd arg1 ... argN``
+ The module sub-command and arguments to execute as if they were
+ passed directly to the module command in your shell environment.
+
+ ``OUTPUT_VARIABLE <out-var>``
+ The standard output from executing the module command.
+
+ ``RESULT_VARIABLE <ret-var>``
+ The return code from executing the module command.
+
+.. command:: env_module_swap
+
+ Swap one module for another:
+
+ .. code-block:: cmake
+
+ env_module_swap(out_mod in_mod
+ [OUTPUT_VARIABLE <out-var>]
+ [RESULT_VARIABLE <ret-var>]
+ )
+
+ This is functionally equivalent to the ``module swap out_mod in_mod`` shell
+ command. The options are:
+
+ ``OUTPUT_VARIABLE <out-var>``
+ The standard output from executing the module command.
+
+ ``RESULT_VARIABLE <ret-var>``
+ The return code from executing the module command.
+
+.. command:: env_module_list
+
+ Retrieve the list of currently loaded modules:
+
+ .. code-block:: cmake
+
+ env_module_list(<out-var>)
+
+ This is functionally equivalent to the ``module list`` shell command.
+ The result is stored in ``<out-var>`` as a properly formatted CMake
+ :ref:`semicolon-separated list <CMake Language Lists>` variable.
+
+.. command:: env_module_avail
+
+ Retrieve the list of available modules:
+
+ .. code-block:: cmake
+
+ env_module_avail([<mod-prefix>] <out-var>)
+
+ This is functionally equivalent to the ``module avail <mod-prefix>`` shell
+ command. The result is stored in ``<out-var>`` as a properly formatted
+ CMake :ref:`semicolon-separated list <CMake Language Lists>` variable.
+
+#]=======================================================================]
+
+function(env_module)
+ if(NOT EnvModules_COMMAND)
+ message(FATAL_ERROR "Failed to process module command. EnvModules_COMMAND not found")
+ return()
+ endif()
+
+ set(options)
+ set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
+ set(multiValueArgs COMMAND)
+ cmake_parse_arguments(MOD_ARGS
+ "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
+ )
+ if(NOT MOD_ARGS_COMMAND)
+ # If no explicit command argument was given, then treat the calling syntax
+ # as: module(cmd args...)
+ set(exec_cmd ${ARGV})
+ else()
+ set(exec_cmd ${MOD_ARGS_COMMAND})
+ endif()
+
+ if(MOD_ARGS_OUTPUT_VARIABLE)
+ set(err_var_args ERROR_VARIABLE err_var)
+ endif()
+
+ execute_process(
+ COMMAND mktemp -t module.cmake.XXXXXXXXXXXX
+ OUTPUT_VARIABLE tempfile_name
+ )
+ string(STRIP "${tempfile_name}" tempfile_name)
+
+ # If the $MODULESHOME/init/cmake file exists then assume that the CMake
+ # "shell" functionality exits
+ if(EXISTS "$ENV{MODULESHOME}/init/cmake")
+ execute_process(
+ COMMAND ${EnvModules_COMMAND} cmake ${exec_cmd}
+ OUTPUT_FILE ${tempfile_name}
+ ${err_var_args}
+ RESULT_VARIABLE ret_var
+ )
+
+ else() # fallback to the sh shell and manually convert to CMake
+ execute_process(
+ COMMAND ${EnvModules_COMMAND} sh ${exec_cmd}
+ OUTPUT_VARIABLE out_var
+ ${err_var_args}
+ RESULT_VARIABLE ret_var
+ )
+ endif()
+
+ # If we executed successfully then process and cleanup the temp file
+ if(ret_var EQUAL 0)
+ # No CMake shell so we need to process the sh output into CMake code
+ if(NOT EXISTS "$ENV{MODULESHOME}/init/cmake")
+ file(WRITE ${tempfile_name} "")
+ string(REPLACE "\n" ";" out_var "${out_var}")
+ foreach(sh_cmd IN LISTS out_var)
+ if(sh_cmd MATCHES "^ *unset *([^ ]*)")
+ set(cmake_cmd "unset(ENV{${CMAKE_MATCH_1}})")
+ elseif(sh_cmd MATCHES "^ *export *([^ ]*)")
+ set(cmake_cmd "set(ENV{${CMAKE_MATCH_1}} \"\${${CMAKE_MATCH_1}}\")")
+ elseif(sh_cmd MATCHES " *([^ =]*) *= *(.*)")
+ set(var_name "${CMAKE_MATCH_1}")
+ set(var_value "${CMAKE_MATCH_2}")
+ if(var_value MATCHES "^\"(.*[^\\])\"")
+ # If it's in quotes, take the value as is
+ set(var_value "${CMAKE_MATCH_1}")
+ else()
+ # Otherwise, strip trailing spaces
+ string(REGEX REPLACE "([^\\])? +$" "\\1" var_value "${var_value}")
+ endif()
+ string(REPLACE "\\ " " " var_value "${var_value}")
+ set(cmake_cmd "set(${var_name} \"${var_value}\")")
+ else()
+ continue()
+ endif()
+ file(APPEND ${tempfile_name} "${cmake_cmd}\n")
+ endforeach()
+ endif()
+
+ # Process the change in environment variables
+ include(${tempfile_name})
+ file(REMOVE ${tempfile_name})
+ endif()
+
+ # Push the output back out to the calling scope
+ if(MOD_ARGS_OUTPUT_VARIABLE)
+ set(${MOD_ARGS_OUTPUT_VARIABLE} "${err_var}" PARENT_SCOPE)
+ endif()
+ if(MOD_ARGS_RESULT_VARIABLE)
+ set(${MOD_ARGS_RESULT_VARIABLE} ${ret_var} PARENT_SCOPE)
+ endif()
+endfunction(env_module)
+
+#------------------------------------------------------------------------------
+function(env_module_swap out_mod in_mod)
+ set(options)
+ set(oneValueArgs OUTPUT_VARIABLE RESULT_VARIABLE)
+ set(multiValueArgs)
+
+ cmake_parse_arguments(MOD_ARGS
+ "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV}
+ )
+
+ env_module(COMMAND -t swap ${out_mod} ${in_mod}
+ OUTPUT_VARIABLE tmp_out
+ RETURN_VARIABLE tmp_ret
+ )
+
+ if(MOD_ARGS_OUTPUT_VARIABLE)
+ set(${MOD_ARGS_OUTPUT_VARIABLE} "${err_var}" PARENT_SCOPE)
+ endif()
+ if(MOD_ARGS_RESULT_VARIABLE)
+ set(${MOD_ARGS_RESULT_VARIABLE} ${tmp_ret} PARENT_SCOPE)
+ endif()
+endfunction()
+
+#------------------------------------------------------------------------------
+function(env_module_list out_var)
+ cmake_policy(SET CMP0007 NEW)
+ env_module(COMMAND -t list OUTPUT_VARIABLE tmp_out)
+
+ # Convert output into a CMake list
+ string(REPLACE "\n" ";" ${out_var} "${tmp_out}")
+
+ # Remove title headers and empty entries
+ list(REMOVE_ITEM ${out_var} "No modules loaded")
+ if(${out_var})
+ list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
+ endif()
+ list(FILTER ${out_var} EXCLUDE REGEX "^(.*:)?$")
+
+ set(${out_var} ${${out_var}} PARENT_SCOPE)
+endfunction()
+
+#------------------------------------------------------------------------------
+function(env_module_avail)
+ cmake_policy(SET CMP0007 NEW)
+
+ if(ARGC EQUAL 1)
+ set(mod_prefix)
+ set(out_var ${ARGV0})
+ elseif(ARGC EQUAL 2)
+ set(mod_prefix ${ARGV0})
+ set(out_var ${ARGV1})
+ else()
+ message(FATAL_ERROR "Usage: env_module_avail([mod_prefix] out_var)")
+ endif()
+ env_module(COMMAND -t avail ${mod_prefix} OUTPUT_VARIABLE tmp_out)
+
+ # Convert output into a CMake list
+ string(REPLACE "\n" ";" tmp_out "${tmp_out}")
+
+ set(${out_var})
+ foreach(MOD IN LISTS tmp_out)
+ # Remove directory entries and empty values
+ if(MOD MATCHES "^(.*:)?$")
+ continue()
+ endif()
+
+ # Convert default modules
+ if(MOD MATCHES "^(.*)/$" ) # "foo/"
+ list(APPEND ${out_var} ${CMAKE_MATCH_1})
+ elseif(MOD MATCHES "^((.*)/.*)\\(default\\)$") # "foo/1.2.3(default)"
+ list(APPEND ${out_var} ${CMAKE_MATCH_2})
+ list(APPEND ${out_var} ${CMAKE_MATCH_1})
+ else()
+ list(APPEND ${out_var} ${MOD})
+ endif()
+ endforeach()
+
+ set(${out_var} ${${out_var}} PARENT_SCOPE)
+endfunction()
+
+#------------------------------------------------------------------------------
+# Make sure we know where the underlying module command is
+find_program(EnvModules_COMMAND
+ NAMES lmod modulecmd
+ HINTS ENV MODULESHOME
+ PATH_SUFFIXES libexec
+)
+
+include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
+find_package_handle_standard_args(EnvModules DEFAULT_MSG EnvModules_COMMAND)
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 74da394734..d793adf05f 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -1421,6 +1421,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
add_subdirectory(FindDoxygen)
endif()
+ if(CMake_TEST_FindEnvModules)
+ add_subdirectory(FindEnvModules)
+ endif()
+
if(CMake_TEST_FindEXPAT)
add_subdirectory(FindEXPAT)
endif()
diff --git a/Tests/FindEnvModules/CMakeLists.txt b/Tests/FindEnvModules/CMakeLists.txt
new file mode 100644
index 0000000000..95b7d1ddbf
--- /dev/null
+++ b/Tests/FindEnvModules/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_test(FindEnvModules.Test ${CMAKE_CMAKE_COMMAND}
+ -P ${CMAKE_CURRENT_LIST_DIR}/EnvModules.cmake
+)
diff --git a/Tests/FindEnvModules/EnvModules.cmake b/Tests/FindEnvModules/EnvModules.cmake
new file mode 100644
index 0000000000..0c81bf21d8
--- /dev/null
+++ b/Tests/FindEnvModules/EnvModules.cmake
@@ -0,0 +1,35 @@
+find_package(EnvModules REQUIRED)
+message("module purge")
+env_module(COMMAND purge RESULT_VARIABLE ret_var)
+if(NOT ret_var EQUAL 0)
+ message(FATAL_ERROR "module(purge) returned ${ret_var}")
+endif()
+
+message("module avail")
+env_module_avail(avail_mods)
+foreach(mod IN LISTS avail_mods)
+ message(" ${mod}")
+endforeach()
+
+if(avail_mods)
+ list(GET avail_mods 0 mod0)
+ message("module load ${mod0}")
+ env_module(load ${mod0})
+
+ message("module list")
+ env_module_list(loaded_mods)
+ foreach(mod IN LISTS loaded_mods)
+ message(" ${mod}")
+ endforeach()
+
+ list(LENGTH loaded_mods num_loaded_mods)
+ message("Number of modules loaded: ${num_loaded_mods}")
+ if(NOT num_loaded_mods EQUAL 1)
+ message(FATAL_ERROR "Exactly 1 module should be loaded. Found ${num_loaded_mods}")
+ endif()
+
+ list(GET loaded_mods 0 mod0_actual)
+ if(NOT (mod0_actual MATCHES "^${mod0}"))
+ message(FATAL_ERROR "Loaded module does not match ${mod0}. Actual: ${mod0_actual}")
+ endif()
+endif()