CMake/Modules/FindEnvModules.cmake

334 lines
9.7 KiB
CMake

# 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``
True if a compatible environment modules framework was found.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variable will be set:
``EnvModules_COMMAND``
The low level module command to use. Currently supported
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)