Read a file or print a message in CMake - cmake

I'm trying to read a file's content and set a variable on the condition whether a file exists relative to the CMakeLists.txt script file. For example, I want to conditionally set an environment variable with the content of a file that resides on disk, and if it's not there I want to print a helpful message.
if (EXISTS pkgconfig-environment)
file(READ pkgconfig-environment LOCAL_PKG_CONFIG_PATH)
set(ENV{PKG_CONFIG_PATH} ${LOCAL_PKG_CONFIG_PATH})
else()
message("
I hope you know what you're doing with your pkg-config.
")
endif ()
The logic above never detects the file pkgconfig-environment, and it instead always prints the message. The file can be read into a cmake variable, but only if it exists.
There are two problems: first, file(READ ...) will fail the build sometimes because the file doesn't exist (I don't care if it's a directory and it fails. That's not my use case). Second, the parameter expected in the call if(EXISTS path) should probably be an absolute path, but I wanted the file to be tested for existence relative to the CMakeLists.txt script file.
Given how clearly the documentation states that exists-checks are supposed to be absolute paths, it leads me to think there's some way to determine the absolute path of a file from a relative path near the CMakeLists.txt.

To get the full path to the directory containing the current CMakeLists.txt file, use ${CMAKE_CURRENT_LIST_DIR}:
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/pkgconfig-environment)
file(READ ${CMAKE_CURRENT_LIST_DIR}/pkgconfig-environment LOCAL_PKG_CONFIG_PATH)
set(ENV{PKG_CONFIG_PATH} ${LOCAL_PKG_CONFIG_PATH})
else()
message("
I hope you know what you're doing with your pkg-config.
")
endif ()

Related

CMake - Check if a directory contains a file of a specific type

I want to do a Macro that gets a list of the sub-sub-directories that contain a specific type of files, in my case .jar files.
This macro is getting me all the sub-sub-directories:
MACRO(SUBSUBDIRLIST result curdir)
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*/*)
SET(dirlist "")
FOREACH(child ${children})
IF(IS_DIRECTORY ${curdir}/${child})
LIST(APPEND dirlist ${child})
ENDIF()
ENDFOREACH()
SET(${result} ${dirlist})
ENDMACRO()
SUBSUBDIRLIST(TUTORIALS ${CMAKE_CURRENT_SOURCE_DIR})
What I now need is to find a way to check if a directory contains any .jar file.
Can I change the IF to do something like IF(child ${children} AND ${child} CONTAINS *.jar)?
While if command supports many ready-made checks, not all checks can be expressed directly via if. But you are free to use other commands, and check their result via if.
For check whether given directory contains specific type of files, FILE(GLOB) can be effectively used:
FILE(GLOB jars "${child}/*.jar")
if(jars)
# There are .jar files in directory referred by 'child'
endif()

CMake: Function is callable while script, defined it, is no longer included

I have three folders in a location, say A,B and C. I have two cmake files in folder A: FindABC.cmake and UseABC.cmake. The former is for finding the libraries and the latter contains a function, say run_command(). CMakelists.txt in folder B and folder C contains the following lines:
find_package(ABC)
include(UseABC)
run_command()
It works as intended. Now If I comment find_package() and include() in CMakelists of folder C, as far as I know, Cmake should give an error telling unknown command - run_command(). But, the controls goes into the function and executes in unpredictable manner.
How come the control goes to the function when the include line is commented? The root CMakelists that lists the sub-directories does not have any find_package or include lines in it.
Edit:
UseABC.cmake:
set(ABC_COMPILE_DEBUG FALSE)
set(ABC_COMPILE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/abc_gen")
message("USEABC1 - -> " ${ABC_COMPILE_OUTPUT_DIR})
function(run_command)
message("USEABC2 - File recurs -> " ${ABC_COMPILE_OUTPUT_DIR})
file(REMOVE_RECURSE "${ABC_COMPILE_OUTPUT_DIR}")
file(MAKE_DIRECTORY "${ABC_COMPILE_OUTPUT_DIR}")
add_custom_command() #command to be executed
endfunction()
Here, When nothing is commented(find_package and include is not commented in any CMakelists.txt), I get the correct path for the two messages I print.
When I comment include(UseABC) in the second CMakelists.txt, the configuration fails, the first message is not at all printed and the second message gets printed, but does not give the value of the variable. It also deletes all the files in Folder C (but the argument to REMOVE_RECURSE is empty).
If I correctly understand the situation, you have:
CMakeLists.txt:
add_subdirectory(B)
add_subdirectory(C)
B/CMakeLists.txt:
find_package(ABC)
include(UseABC)
In that case run_command function, defined in UseABC.cmake, is accessible in C/CMakeLists.txt, though this script doesn't define it.
In CMake function definitions are global.
By opposite, variable definitions are local to the scope, where they are defined. (Until variables are cached ones, in that case they have global visibility).
That is, variable ABC_COMPILE_DEBUG defined in UseABC.cmake is accessible in
UseABC.cmake script
B/CMakeLists.txt script, because it includes UseABC.cmake one, and include() command doesn't introduce a scope
but it is inaccessible in
CMakeLists.txt script, because add_subdirectory(B) does introduce a scope
C/CMakeLists.txt script
More details about variable's visibility can be found in documentation.

Why CMake doubles the path?

I am using UseLATEX, with commands
set(MainFile "Demo.tex")
set(InputFiles ${MainFile} Main.tex OtherFiles.tex)
then later I use it like
ADD_LATEX_DOCUMENT( ${MyFileName}
INPUTS "${InputFiles}" )
and everything works fine. If I change to
file(GLOB_RECURSE InputFiles src/*.tex)
then I receive messages with a list of files I wanted to put into InputFiles,
but preceeded with
"Could not find input file ${CMAKE_SOURCE_DIR}/${CMAKE_SOURCE_DIR}/OtherFiles.tex"
and of course that path does not exist. What is wrong?
Turning my comment into answer
Haven't worked with ADD_LATEX_DOCUMENT(), but it seems it appends the current directory itself and would need relative paths.
Just change your file(GLOB ...) command to output relative paths:
file(GLOB_RECURSE InputFiles RELATIVE "${CMAKE_SOURCE_DIR}" src/*.tex)

How to use CHECK_INCLUDE_FILES macro in cmake?

I need link my program against Kerberos authentication library (gssapi_krb5) with the corresponding headers gssapi/gssapi.h and gssapi/gssapi_krb5.h included in the source file.
Currently, the compilation will continue if headers are absent and stop with a compile time error saying header files not found.
What I want to implement in the cmake file is to check the existence of the header file and stop compiling if not found.
I add the following code into my CMakeList.txt file.
INCLUDE(CheckIncludeFiles)
CHECK_INCLUDE_FILES(gssapi/gssapi.h;gssapi/gssapi_krb5.h HAVE_KRB_HEADERS)
IF (NOT HAVE_KRB_HEADERS)
RETURN()
ENDIF (NOT HAVE_KRB_HEADERS)
But it still does not act as I expected.
I would like the following lines:
-- Looking for gssapi/gssapi.h - found
-- Looking for gssapi/gssapi_krb5.h - not found
but fail.
Also, the variable HAVE_KRB_HEADERS is empty when output with message macro.
Compile continues until the error described above occurs.
I read somewhere on the Web, this may be because CMake cache.
I'm very new to CMake and not quite clear with that concept.
My CMake version is 2.6.
How could I make this code work? Thank you!
I can't say I'm a huge fan of CheckIncludeFiles because of its difficulty to get right. In principal it's good - it actually creates tiny c files which #include the requested headers and tries to compile them, but it seems to be too easy to get wrong.
I generally prefer just using find_path and/or find_file for this job. This doesn't check the contents of any files found, but usually if you find the required header, its contents are good!
I would use find_path if I needed to know the folder where the header lived. This would usually be because I need to check for other files in the same folder (as in your case), or more commonly because I need to add the folder to an include_directories call.
find_file yields the full path to the file (if found). For headers, normally I don't need the path elsewhere in the CMakeLists - it's just used immediately after the find_file to check the file was actually found.
So, here's how I'd go about checking for "gssapi/gssapi.h" and "gssapi/gssapi_krb5.h"
find_path(GssApiIncludes gssapi.h PATHS <list of folders you'd expect to find it in>)
if(NOT GssApiIncludes)
message(FATAL_ERROR "Can't find folder containing gssapi.h")
endif()
find_file(GssKrb gssapi_krb5.h PATHS ${GssApiIncludes} NO_DEFAULT_PATH)
if(NOT GssKrb)
message(FATAL_ERROR "Can't find gssapi_krb5.h in ${GssApiIncludes}")
endif()
If you do this, then if required you could add
include_directories(${GssApiIncludes})
so that in your source code you can do
#include "gssapi.h"
#include "gssapi_krb5.h"
For anyone who has to work with CHECK_INCLUDE_FILES, the documentation lists a variable called CMAKE_REQUIRED_INCLUDES where you can set additional include paths apart from the default headers.
In a CMake file:
LIST(APPEND CMAKE_REQUIRED_INCLUDES "gssapi")
From the command line:
cmake . --DCMAKE_REQUIRED_INCLUDES="gssapi"
If all else fails, you can set the -I<dir> flag manually. However, this is not recommended as it not portable across compilers.
# note the extra space before `-I`
STRING(APPEND CMAKE_C_FLAGS " -Igssapi")
STRING(APPEND CMAKE_CXX_FLAGS " -Igssapi") # for C++
Also note that C++ headers have a different macro called CheckIncludeFileCXX.

How to change the reference path for get_filename_component in cmake?

I'm using get_filename_component in cmake to get the absolute path of a possibly relative path given in a variable.
And I want to do an out-of-tree/out-of-source-build.
It seems to me that get_filename_component is using CMAKE_SOURCE_DIR as reference-path.
Is there a way to change that or to workaround it?
One way I tried is to prefix my potential relative path with ${CMAKE_BINARY_DIR} but that stops working of the path given is not relative.
Assuming your relative path is always relative to CMAKE_BINARY_DIR, then you can handle this pretty easily using if(IS_ABSOLUTE ...):
if(NOT IS_ABSOLUTE ${MyPath})
set(MyAbsPath ${CMAKE_BINARY_DIR}/${MyPath})
endif()
If the subject file or dir exists at CMake run time, then you can always do a find_file call, passing the possible NAMES and PATHS. If the file exists and is found, the resulting variable will hold the full path to the file.
Or you can use the if(EXISTS ...) signature of if to check for the existence or not of the given file.