what is the proper way to set CUDA_VISIBLE_DEVICES in cmake file to use different GPU core - cmake

I have a project that uses cmake for compilation and unittest afterwards. The standard procedure for it is
cmake .. -DUSE_CUDA=ON ; make ; make test ARGS="-j 10"
The problem is that during the make test phase, I have 4 GPUs on my server and only one GPU is used. I can see it through nvidia-smi command. I'm wondering if there is a method to set in Cmakefiles to change the GPU being used and eventually utilize all GPUs.
Here is the unit test code that will execute all the test cases. I tried to force it on GPU 1 by setting environment variable CUDA_VISIBLE_DEVICES to 1. However the code still runs on GPU core 0.
function(py_test TARGET_NAME)
set(options "")
set(oneValueArgs "")
set(multiValueArgs SRCS DEPS ARGS ENVS)
set(ENV{CUDA_VISIBLE_DEVICES} 1)
cmake_parse_arguments(py_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
get_filename_component(work_dir ${py_test_SRCS} DIRECTORY)
add_test(NAME ${TARGET_NAME}
COMMAND ${COVERAGE_EXECUTABLE} run --parallel-mode --source=test_module "${py_test_SRCS}" ${py_test_ARGS}
WORKING_DIRECTORY ${work_dir})
endfunction()
function(add_files)
set(options "")
set(oneValueArgs "")
set(multiValueArgs SRCS)
cmake_parse_arguments(add_files "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
foreach(test_file ${add_files_SRCS})
get_filename_component(test ${test_file} NAME_WE)
get_filename_component(test_abs ${test_file} ABSOLUTE)
message(STATUS "test_file:${test_file}")
py_test(${test} SRCS ${test_abs})
endforeach()
endfunction()
# Put cases cost more time before the less ones
set(COVERAGE_EXECUTABLE /usr/local/bin/coverage-3.6)
# unit test
file(GLOB UNIT_TEST unit_test_folder/test_*.py)
add_files(SRCS ${UNIT_TEST})
I've tried to set CUDA_VISIBLE_DEVICES in the command terminal before running cmake test and it worked.
Also, do I have to preassign the tests to different dedicated GPU or can they be dynamically assigned.

I was able to preassign tests to different GPUs with code like this:
function(py_test TARGET_NAME)
set(options "")
set(oneValueArgs "")
set(multiValueArgs SRCS DEPS ARGS ENVS)
MATH(EXPR CUDA_ID "(${CUDA_ID}+1)%4")
set(CUDA_ID ${CUDA_ID} PARENT_SCOPE)
cmake_parse_arguments(py_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
get_filename_component(work_dir ${py_test_SRCS} DIRECTORY)
add_test(NAME ${TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E env CUDA_VISIBLE_DEVICES=${CUDA_ID} ${COVERAGE_EXECUTABLE} run --parallel-mode --source=horizon_nn "${py_test_SRCS}" ${py_test_ARGS}
WORKING_DIRECTORY ${work_dir})
message(STATUS CUDA_ID ${CUDA_ID})
endfunction()
set(CUDA_ID 0)
But still, dynamic assign is still not achievable since all these tests are preassigned at cmake phase

Related

fortran defined macro ignored with cmake

I am trying to create a new macro in a Fortran file. Such file is one of many in a bigger project. It is compiled through a CMake file and gfortran.
For its simplicity I just included a simple example:
#define hello call
module SIO_ncDimBounds_mod
use SIO_ncParams_mod, only: MAX_DIMLEN_NAME
...
logical, parameter :: ISDEBUG = .false.
hello -> not recognized as Macro
When It is compiled it is ignored so it raises an error:
../soulio/src/ncDimBounds_mod.F90:34:2:
34 | hello
| 1
Error: Unclassifiable statement at (1)
As far as I understand, with upper case file extension should be enough to execute the preprocessor. I also checked the '-cpp' flag is enabled. I doubled checked with verbose mode to ensure it is enabled:
[ 22%] Building Fortran object src/CMakeFiles/soulio_lib.dir/ncDimBounds_mod.F90.o
cd ../soulio/build/src && /usr/bin/gfortran -DENABLE_MPI -I../projects/soulio/src/soulshared_lib -I../soulio/extern/Library/include -I/usr/lib/x86_64-linux-gnu/openmpi/include -I/usr/lib/x86_64-linux-gnu/openmpi/lib -ffree-form -std=f2008 -fimplicit-none -cpp -g -fbounds-check -pedantic -ffpe-trap=zero,invalid,overflow,underflow -O0 -Wall -fcheck=all -fbacktrace -Wextra --coverage -fprofile-arcs -ftest-coverage -J../../lib -c ../soulio/src/ncDimBounds_mod.F90 -o CMakeFiles/soulio_lib.dir/ncDimBounds_mod.F90.o
I also include the CMakeFile:
cmake_minimum_required(VERSION 3.9)
project(soulio)
enable_language(Fortran)
find_program(FYPP fypp)
if(NOT FYPP)
message(FATAL_ERROR "Preprocessor fypp could not be found")
endif()
# custom compiler flags
if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU")
set(dialect "-ffree-form -std=f2008 -fimplicit-none -cpp")
set(debugMode "-fbounds-check -pedantic -ffpe-trap=zero,invalid,overflow,underflow -O0 -Wall -fcheck=all -fbacktrace -Wextra")
set(optimizedMode "-ftree-vectorize" )
endif()
if(CMAKE_Fortran_COMPILER_ID MATCHES "Intel")
set(dialect "-stand f08 -free -implicitnone")
set(debugMode "-check bounds")
set(optimizedMode "-O3 -xHost")
endif()
if(CMAKE_Fortran_COMPILER_ID MATCHES "PGI")
set(dialect "-Mfreeform -Mdclchk -Mstandard -Mallocatable=03")
set(debugMode "-C")
set(optimizedMode "")
endif()
set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} ${optimizedMode}")
set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${debugMode}")
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${dialect}")
# Place lib and binary files
# dynamic libraries
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
# static library
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
# target files
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
# Have the .mod files placed in the lib folder
SET(LIB ${CMAKE_SOURCE_DIR}/lib)
SET(CMAKE_Fortran_MODULE_DIRECTORY ${LIB})
# include cmake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
include(soulioUtils)
message(status ${CMAKE_SOURCE_DIR})
# call for netcdf library
if (NOT HAS_SOULSM)
set (NETCDF_C "YES")
set (NETCDF_F90 "YES")
set (NETCDF_INCLUDES ${CMAKE_SOURCE_DIR}/extern/Library/include)
set (NETCDF_INCLUDES_C ${CMAKE_SOURCE_DIR}/extern/Library/include)
set (NETCDF_INCLUDES_F77 ${CMAKE_SOURCE_DIR}/extern/Library/include)
set (NETCDF_INCLUDES_F90 ${CMAKE_SOURCE_DIR}/extern/Library/include)
set (NETCDF_INCLUDES_CXX ${CMAKE_SOURCE_DIR}/extern/Library/include)
set (NETCDF_LIBRARIES_F77 ${CMAKE_SOURCE_DIR}/extern/Library/lib/libnetcdff.so)
set (NETCDF_LIBRARIES_F90 ${CMAKE_SOURCE_DIR}/extern/Library/lib/libnetcdff.so)
set (NETCDF_LIBRARIES_C ${CMAKE_SOURCE_DIR}/extern/Library/lib/libnetcdf.so)
set (NETCDF_LIBRARIES ${CMAKE_SOURCE_DIR}/extern/Library/lib/libnetcdf.so)
find_package (NetCDF REQUIRED)
endif()
if (ENABLE_MPI)
find_package (MPI REQUIRED)
add_definitions(-DENABLE_MPI)
endif()
message(STATUS "Run: ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_MAX_NUMPROCS} ${MPIEXEC_PREFLAGS} EXECUTABLE ${MPIEXEC_POSTFLAGS} ARGS")
if(BUILD_TESTING)
enable_testing()
SET( CMAKE_BUILD_TYPE Debug )
include( cmake/CodeCoverage.cmake )
SET(coverageMode "--coverage -fprofile-arcs -ftest-coverage")
set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${coverageMode}")
add_subdirectory(tests)
endif()
if (NOT HAS_SOULSM)
add_subdirectory(soulshared)
endif()
add_subdirectory(src)
I did a simple and isolated test with a fortran file with an uppercase extension, as expected it works.
Why is the macro not replaced with CMake? It seems to me the preprocessor it is not called.
Edit:
'hello' is changed to lowercase
If we change the example to
subroutine silly()
print *, 'It works'
return
end subroutine silly
#define hello call
program main
hello silly
stop
end program main
and build using
gfortran -cpp macro.f90
This builds without any problems. If I just have hello on its own, then I get a syntax error.
First, make sure your program is valid. call on its own will generate an error. You need to call something.
Could you try building without cmake? If it works without cmake and doesn't work with cmake then it is a cmake problem. Otherwise, you just have a problem with your code.

Using cmake to create protobuf / grpc cc files

If I want to recreate the following protoc command in cmake:
protoc -I ../proto/ --cpp_out=. service.proto
I use the following lines in cmake:
file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/*.proto")
PROTOBUF_GENERATE_CPP(ProtoSources ProtoHeaders ${ProtoFiles})
If I instead want to recreate the protoc command below:
protoc -I ../proto/ --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` service.proto
In the case above I am not able to determine how to change the cmake file, please help!
The Question is how do I address the:
--plugin=EXECUTABLE Specifies a plugin executable to use.
Normally, protoc searches the PATH for
plugins, but you may specify additional
executables not in the path using this flag.
Additionally, EXECUTABLE may be of the form
NAME=PATH, in which case the given plugin name
is mapped to the given executable even if
the executable's own name differs.
I have been reading the PROTOBUF_GENERATE_CPP documentation, but did not find an answer!
Module findProtobuf.cmake defines functions-wrappers only for common protoc calls: PROTOBUF_GENERATE_CPP - for --cpp_out and PROTOBUF_GENERATE_PYTHON - for --py_out. But you can implement your own function-wrapper for needed plugin. Code below is based on PROTOBUF_GENERATE_CPP implementation.
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin) # Get full path to plugin
function(PROTOBUF_GENERATE_GRPC_CPP SRCS HDRS)
if(NOT ARGN)
message(SEND_ERROR "Error: PROTOBUF_GENERATE_GRPC_CPP() called without any proto files")
return()
endif()
if(PROTOBUF_GENERATE_CPP_APPEND_PATH) # This variable is common for all types of output.
# Create an include path for each file specified
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
else()
set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(DEFINED PROTOBUF_IMPORT_DIRS)
foreach(DIR ${Protobuf_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
endif()
set(${SRCS})
set(${HDRS})
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(FIL_WE ${FIL} NAME_WE)
list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.grpc.pb.cc")
list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.grpc.pb.h")
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.grpc.pb.cc"
"${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.grpc.pb.h"
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
ARGS --grpc_out=${CMAKE_CURRENT_BINARY_DIR}
--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
${_protobuf_include_path} ${ABS_FIL}
DEPENDS ${ABS_FIL} ${Protobuf_PROTOC_EXECUTABLE}
COMMENT "Running gRPC C++ protocol buffer compiler on ${FIL}"
VERBATIM)
endforeach()
set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
endfunction()
Usage is same as for PROTOBUF_GENERATE_CPP:
file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/*.proto")
PROTOBUF_GENERATE_GRPC_CPP(ProtoGRPCSources ProtoGRPCHeaders ${ProtoFiles})
Starting at version 3.12, protobuf_generate supports a PLUGIN argument
https://github.com/protocolbuffers/protobuf/blob/v3.12.0/cmake/protobuf-config.cmake.in#L46
so you could try something along the line:
PROTOBUF_GENERATE_CPP(ProtoSources ProtoHeaders ${ProtoFiles} PLUGIN protoc-gen-grpc=${GRPC_CPP_PLUGIN_PATH})
For me the blogpost https://www.falkoaxmann.de/dev/2020/11/08/grpc-plugin-cmake-support.html lead to success because it provides a full example (thanks #powerpete).
I'm putting the code here so it is available as an answer and not just as a comment:
project(my-service VERSION 1.0 LANGUAGES CXX C)
find_package(protobuf CONFIG REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(Threads)
set(PROTO_FILES
MyService.proto
)
# protobuf source files go into the lib just like any other CPP source file
add_library(my-service ${PROTO_FILES})
target_link_libraries(my-service
PUBLIC
protobuf::libprotobuf
gRPC::grpc
gRPC::grpc++
)
target_include_directories(my-service
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
# compile the message types
protobuf_generate(TARGET my-service LANGUAGE cpp)
# compile the GRPC services
protobuf_generate(
TARGET
my-service
LANGUAGE
grpc
GENERATE_EXTENSIONS
.grpc.pb.h
.grpc.pb.cc
PLUGIN
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
)

How to automatically move *.pb.h and *.pb.h under /include and /src directories?

I am developing a library which uses Google Protocol Buffers (protobuf) and CMake. The project has the following directory tree.
MyProject/
MyProject/include/myproject/
MyProject/include/myproject/some_classes.h
MyProject/src/
MyProject/src/some_classes.cc
MyProject/src/foo.proto
MyProject/CMakeList.txt
CMakeList.txt has the following lines to generate protobuf source and header files.
include_directories(${libCHEC_SOURCE_DIR}/include)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SOURCES)
find_package(Protobuf REQUIRED)
file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/src/*.proto")
protobuf_generate_cpp(ProtoSources ProtoHeaders ${ProtoFiles})
list(APPEND EXTLIBS ${PROTOBUF_LIBRARIES})
add_library(MyLibrary SHARED ${SOURCES} ${ProtoSources})
target_link_libraries(MyLibrary ${EXTLIBS})
When I execute cmake, foo.pb.h and foo.pb.cc are generated under the build directory (i.e. the directory where I executed cmake). It looks that this is the default behavior. But I would like to put foo.pb.h and foo.pb.cc under include/myproject and src directories, respectively.
How can I change the locations of the files generated by protoc?
I would strictly advise against placing generated files in the source tree.
CMake puts a lot of effort into separating the build and source trees. Forcing it to give up that separation has several disadvantages. Among the most prominent is the fact that version control will then have to deal with unversioned generated files in the source tree, and furthermore it may no longer be possible to have multiple builds targetting different architectures sharing the same source tree.
A better approach is to keep the files in the binary tree and adjust your target_include_directories accordingly. There is no shame in using generated files from the binary tree as sources, so don't hesitate to do it.
I added MyProject/misc/myprotobuf.cmake which had a modified version of PROTOBUF_GENERAGE_CPP.
function(MY_PROTOBUF_GENERATE_CPP PATH SRCS HDRS)
if(NOT ARGN)
message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files")
return()
endif()
if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
# Create an include path for each file specified
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
else()
set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(DEFINED PROTOBUF_IMPORT_DIRS)
foreach(DIR ${PROTOBUF_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
endif()
set(${SRCS})
set(${HDRS})
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(FIL_WE ${FIL} NAME_WE)
list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${PATH}/${FIL_WE}.pb.cc")
list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${PATH}/${FIL_WE}.pb.h")
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${PATH})
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PATH}/${FIL_WE}.pb.cc"
"${CMAKE_CURRENT_BINARY_DIR}/${PATH}/${FIL_WE}.pb.h"
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR}/${PATH} ${_protobuf_include_path} ${ABS_FIL}
DEPENDS ${ABS_FIL}
COMMENT "Running C++ protocol buffer compiler on ${FIL}"
VERBATIM )
endforeach()
set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
endfunction()
MyProject/CMakeLists.txt has the following two lines now.
include(${CMAKE_CURRENT_SOURCE_DIR}/misc/myprotobuf.cmake)
file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/misc/*.proto")
my_protobuf_generate_cpp(generated_files/myproject ProtoSources ProtoHeaders ${ProtoFiles})
include_directories(${CMAKE_CURRENT_BINARY_DIR}/generated_files)
You can manipulate the directory structure for your generated protobuf sources by modifying the source directory and cmakelists in a particular way.
An example:
MyProject/
MyProject/src
MyProject/src/CMakeLists.txt (1)
MyProject/src/ProtoProject/CMakeLists.txt (2)
MyProject/src/ProtoProject/MyProject/foo.proto
CMakeLists.txt (1):
add_subdirectory(MyProject)
# generated headers
target_include_directories(ProtoProject PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
CMakeLists.txt (2):
set(proto_files "foo.proto")
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${proto_files})
add_library(ProtoProject STATIC ${PROTO_SRCS} ${PROTO_HDRS} ${proto_files})
Why this works
Each call to add_subdirectory() adds a directory level to the $CMAKE_CURRENT_BINARY_DIRECTORY variable. The generated files will end up at MyProject/src/ProtoProject/MyProject/foo.pb.h but your include directory will be MyProject/src/ProtoProject, so you can include files as MyProject/foo.pb.h

Running install command in CMake, but only if

I am extremely new to CMake, and having trouble setting up an install rule.
I want to run the following command in make install:
update-rc.d solshare_stats_runscript defaults
But I only want to run this command if:
CMAKE_INSTALL_PREFIX="/"
How can I do this?
You can probably do this using install(SCRIPT ...) and provide a wee CMake script to be invoked.
So add this to your CMakeLists.txt:
install(SCRIPT InstallScript.cmake)
Then in the InstallScript.cmake:
if("${CMAKE_INSTALL_PREFIX}" STREQUAL "/")
execute_process(COMMAND update-rc.d solshare_stats_runscript defaults
RESULT_VARIABLE Result
OUTPUT_VARIABLE Output
ERROR_VARIABLE Error)
if(Result EQUAL 0)
message(STATUS "Ran update-rc.d as CMAKE_INSTALL_PREFIX == \"/\"")
else()
message(FATAL_ERROR "Result - ${Result}\nOutput - ${Output}\nError - Error")
endif()
else()
message(STATUS "Not running update-rc.d as CMAKE_INSTALL_PREFIX != \"/\"")
endif()
You may need to provide more arguments to the execute_process call in the script (e.g. WORKING_DIRECTORY).

How to make temp directory in CMake?

I need the CMake analog of mktemp command in linux. What macro provides this?
Was looking for this too to evaluate expressions as suggested in the CMake Wiki. Wrote some macros and an example for generating temp file names and executing them:
#!/usr/bin/cmake -P
macro(temp_name fname)
if(${ARGC} GREATER 1) # Have to escape ARGC to correctly compare
set(_base ${ARGV1})
else(${ARGC} GREATER 1)
set(_base ".cmake-tmp")
endif(${ARGC} GREATER 1)
set(_counter 0)
while(EXISTS "${_base}${_counter}")
math(EXPR _counter "${_counter} + 1")
endwhile(EXISTS "${_base}${_counter}")
set(${fname} "${_base}${_counter}")
endmacro(temp_name)
# Evaluate expression
# Suggestion from the Wiki: http://cmake.org/Wiki/CMake/Language_Syntax
# Unfortunately, no built-in stuff for this: http://public.kitware.com/Bug/view.php?id=4034
macro(eval expr)
temp_name(_fname)
file(WRITE ${_fname} "${expr}")
include(${_fname})
file(REMOVE ${_fname})
endmacro(eval)
# Examples
eval("message(\"Hai\")")
set(funcs a;b)
macro(test_a arg)
message("A: ${arg}")
endmacro(test_a)
macro(test_b arg)
message("B: ${arg}")
endmacro(test_b)
foreach(func ${funcs})
set(func_name test_${func})
eval("${func_name}(\"Test\")")
endforeach(func)
Output:
Hai
A: Test
B: Test
Note that in Linux you can set this script to executable and run it using cmake -P. Useful for testing stuff out.
There is no direct CMake analog of "mktemp".
From inside a CMake script or CMakeLists.txt file, your best bet is to use the
file(MAKE_DIRECTORY "/path/to/dir/name")
command, and give it a name of a directory that you know you have write access to. Help for the file command is found here: https://cmake.org/cmake/help/latest/command/file.html
You could also possibly simply use
$ENV{TMP}
if there is an environment variable that points you to a system-provided temp directory.
If you are invoking CMake directly, you could also use
cmake -E make_directory /path/to/dir/name
Finally, see also the execute_process command, which allows you to call arbitrary command line tools from within a cmake script or CMakeLists file and capture the output. That may prove useful if you have another tool that you can call that gives you mktemp functionality. https://cmake.org/cmake/help/latest/command/execute_process.html
I implemented the following macro:
#!/usr/bin/cmake -P
include(CMakeParseArguments)
function(MKTEMP)
set(options CREATE_FOLDER CREATE_FILE)
set(oneValueArgs PREFIX PARENT OUTPUT_VARIABLE)
cmake_parse_arguments(MKTEMP "${options}" "${oneValueArgs}" "" ${ARGN})
if(NOT DEFINED MKTEMP_CREATE_FOLDER)
set(MKTEMP_CREATE_FOLDER FALSE)
endif()
if(NOT DEFINED MKTEMP_CREATE_FILE)
set(MKTEMP_CREATE_FILE FALSE)
endif()
if(MKTEMP_CREATE_FOLDER AND MKTEMP_CREATE_FILE)
# Can not create folder and file with the same name
message(FATAL_ERROR "Both flags CREATE_FOLDER and CREATE_FILE are set")
endif()
if(NOT DEFINED MKTEMP_PREFIX)
set(MKTEMP_PREFIX "tmp")
endif()
if(NOT DEFINED MKTEMP_PARENT)
set(MKTEMP_PARENT "$ENV{TMP}")
endif()
set(_COUNTER 0)
while(EXISTS "${MKTEMP_PARENT}/${MKTEMP_PREFIX}${_COUNTER}")
math(EXPR _COUNTER "${_COUNTER} + 1")
endwhile()
set(_NAME "${MKTEMP_PARENT}/${MKTEMP_PREFIX}${_COUNTER}")
set(${MKTEMP_OUTPUT_VARIABLE} "${_NAME}" PARENT_SCOPE)
if(MKTEMP_CREATE_FOLDER)
file(MAKE_DIRECTORY "${_NAME}")
elseif(MKTEMP_CREATE_FILE)
file(WRITE "${_NAME}" "")
endif()
endfunction()
Usage:
# only generate name - with default prefix ("tmp")
MKTEMP(OUTPUT_VARIABLE TMPONLYNAME)
message("TMPONLYNAME is ${TMPONLYNAME}")
# only generate name - with custom prefix ("myapp")
MKTEMP(PREFIX "myapp" OUTPUT_VARIABLE TMPONLYNAME)
message("TMPONLYNAME is ${TMPONLYNAME}")
# only generate name - use current folder as temp
MKTEMP(PARENT "." OUTPUT_VARIABLE TMPONLYNAME)
message("TMPONLYNAME is ${TMPONLYNAME}")
# create file
MKTEMP(PREFIX "myapp" OUTPUT_VARIABLE TMPFILE CREATE_FILE)
message("TMPFILE is ${TMPFILE}")
# ... work with file ...
file(REMOVE "${TMPFILE}")
# create folder
MKTEMP(PREFIX "myapp" OUTPUT_VARIABLE TMPFOLDER CREATE_FOLDER)
message("TMPFOLDER is ${TMPFOLDER}")
# ... work with folder ...
file(REMOVE_RECURSE "${TMPFOLDER}")
Example of output on my Windows environment ("myapp7" the same because of deletion):
TMPONLYNAME is C:\Users\msuslov\AppData\Local\Temp\tmp1
TMPONLYNAME is C:\Users\msuslov\AppData\Local\Temp\myapp7
TMPONLYNAME is .\tmp0
TMPFILE is C:\Users\msuslov\AppData\Local\Temp\myapp7
TMPFOLDER is C:\Users\msuslov\AppData\Local\Temp\myapp7