CMake find function defined in sibling path in project - cmake

CMake project structure:
/project/CMakeLists.txt
/project/lib/CMakeLists.txt
/project/app/CMakeLists.txt
/project/lib/CMakeLists.txt defines a function
FUNCTION (LIB_MYFUNC ...)
...
ENDFUNCTION ()
The trouble is that /project/app/CMakeLists.txt can't see this function. It can see the function if the function lives in /project/CMakeLists.txt but that's not a sensible organization for my project because reasons.
For third party packages, functions are defined somewhere in the system installation and the project can FIND_PACKAGE. I do not want to have to define and install my library CMake stuff on the system, I want it all contained within the local project file tree.
How can I tell /project/app/CMakeLists.txt to search in sibling branches of the project for functions? (I don't want /project/app/CMakeLists.txt to have to know and name /project/lib explicitly, but for all sibling branches to be searched.)

Related

Predeclare search location for anticipated find_library()-call

I want to include an external library as a subproject into my own project and link its target(s) statically against my own lib.
The said project somewhere in its CMake calls the following find-functions:
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
The subproject expects mbedtls to already be installed somewhere on the system, but it didn't consider the fact that I want to link statically. My approach is to now FetchContent mbedtls and provide the find_library() calls with the prebuilt static libraries.
Now I need a way provide those find_library-calls with the right search directory, of course without modifying its source code. Can I somehow set a prefix path? I know I could probably set CMAKE_PREFIX_PATH but that seems like an ugly hack and it would probably affect other find_library()-calls within the project which also exist. Is there a more "constrained" way?
Can I somehow set a prefix path?
Setting a prefix path won't help find_library to locate the library, because command find_library searches the file at configuration stage, but the library is built only on build stage.
Instead, you may write the target name to the CACHE variable, which is passed to find_library as the first argument:
When find the result variable to be already set, find_library won't search the library file.
In most cases a project uses result of find_library in the call to target_link_libraries, so having the library target in the result variable will fit to the project's expectations.
Example:
FetchContent_Declare(mbedtls ...)
FetchContent_MakeAvailable(mbedtls)
set(MBEDTLS_LIBRARY MbedTLS::mbedtls CACHE INTERNAL "mbedtls library target")
With such setup the following
find_library(MBEDTLS_LIBRARY mbedtls)
will do nothing, since the variable MBEDTLS_LIBRARY is already set.
And if the project will use this variable like
target_link_libraries(<executable> ${MBEDTLS_LIBRARY})
then it effectively gets
target_link_libraries(<executable> MbedTLS::mbedtls)
Name of the target which should be assigned to the variable could sometime be found from the project's documentation, but otherwise you need to look into the project's sources (CMakeLists.txt).
E.g. in case of mbedtls project, the library target mbedtls is created with add_library() call, and MbedTLS::mbedtls is created as ALIAS for it.

How can I avoid clashes with targets "imported" with FetchContent_MakeAvailable?

Suppose I'm writing an app, and managing its build with CMake; and I also want to use a library, mylib, via the FetchContent mechanism.
Now, my own CMakeLists.txt defines a bunch of targets, and so does mylib's CMakeLists.txt. If I were to install mylib, then find_package(mylib), I would only get its exported targets, and even those would be prefixed with mylib:: (customarily, at least). But with FetchContent, both my app's and mylib's (internal and export-intended) targets are in the "global namespace", and may clash.
So, what can I do to separate those targets - other than meticulously name all of my own app's targets defensively?
I would really like it if it were possible to somehow "shove" all the mylib targets into a namespace of my choice.
Note: Relates to: How to avoid namespace collision when using CMake FetchContent?
In the current CMake (<=3.24) world, there are no features for adjusting the names of the targets in other black-box projects, whether included via find_package, add_subdirectory, or FetchContent. Thus, for now, it is incumbent on you to avoid name-clashes in targets, install components, test names, and anywhere else this could be a problem.
Craig Scott says as much in his (very good) talk at CppCon 2019, see here: https://youtu.be/m0DwB4OvDXk?t=2186
The convention he proposes is to use names that are prefixed with SomeProj_. He doesn't suggest to literally use ${PROJECT_NAME}_, and I wouldn't either, because doing so makes the code harder to read and grep (which is extremely useful for understanding a 3rd-party build).
To be a good add_subdirectory or FetchContent citizen, however, it is not enough to simply namespace your targets as SomeProj_Target; you must also provide an ALIAS target SomeProj::Target. There are a few reasons for this:
Your imported targets from find_package will almost certainly be named SomeProj::Target. It should be possible for consumers of your library to switch between FetchContent and find_package easily, without changing other parts of their code. The ALIAS target lets you expose the same interface in both cases. This will become especially pressing when CMake 3.24 lands with its new find_package-to-FetchContent redirection features.
CMake's target_link_libraries function treats names that contain :: as target names always and will throw configure-time error if the target does not exist. Without the ::, it will be treated as a target preferentially, but will turn into a linker flag if the target doesn't exist. Thus, it is preferable to link to targets with :: in their names.
Yet, only IMPORTED and ALIAS targets may have :: in their names.
Points (2) and (3) are good enough for me to define aliases.
Unfortunately, many (most?) CMake builds are not good FetchContent citizens and will flaunt this convention. Following this convention yourself reduces the chance of integration issues between your project and any other, but obviously does nothing to prevent issues between two third party projects that might define conflicting targets. In these cases, you're just out of luck.
An example of defining a library called Target that will play nice with FetchContent:
add_library(SomeProj_Target ${sources})
add_library(SomeProj::Target ALIAS SomeProj_Target)
set_target_properties(
SomeProj_Target
PROPERTIES
EXPORT_NAME Target
OUTPUT_NAME Target # optional: makes the file libTarget.so on disk
)
install(TARGETS SomeProj_Target EXPORT SomeProj_Targets)
install(EXPORT SomeProj_Targets NAMESPACE SomeProj::)
For a more complete example that plays nice with install components, include paths, and dual shared/static import, see my blog post.
See these upstream issues to track the progress/discussion of these problems.
#22687 Project-level namespaces
#16414 Namespace support for target names in nested projects
As #AlexReinking , and, in fact, Craig Scott, suggest - there's no decent current solution.
You can follow the following CMake issues through which the solution will likely be achieved:
#22687 Project-level namespaces (more current)
#16414 Namespace support for target names in nested projects (longer discussion which influenced the above, recommended reading)

Hierarchical CMake project that also works when building an "inner" (non-root) project

Suppose I have a hierarchical CMake project, composed of n different projects:
CMakeLists.txt
proj-1/CMakeLists.txt
proj-2/CMakeLists.txt
(...)
proj-n/CMakeLists.txt
Evidently there'll be the source files for each project as well.
I'd ensure all commands of interest are added to the root CMakeLists.txt file -- say, CMAKE_CXX_STANDARD, enable_testing(), add_compile_options(), etc. If I understand correctly, whichever options are included in the root CMakeLists.txt file are also applied to all children CMakeLists.txt file -- please correct me if I'm wrong, since I'm counting on this behavior. The root CMakeLists.txt also contains an add_subdirectory(proj-X) statement for each X = 1, ..., n.
Anyway. Suppose, for some reason, that I would like to build only one of the proj-X folders, say proj-1. Maybe the build is broken in one of the other projects, or maybe I need to fix a bug on proj-1, it doesn't depend on the other projects, and it would take forever to build all projects.
The point is: I would like to run cmake on proj-1/CMakeLists.txt rather than on the root CMakeLists.txt file, and yet I would like to ensure that proj-1 is built in exactly the same way it would be build, had I run cmake on the root CMakeLists.txt file. This is an issue since the root CMakeLists.txt contains statements that the children CMakeLists.txt should "inherit" from in the regular situation where it's built from the root, and yet in this scenario I'm building directly from proj-1/CMakeLists.txt (the root CMakeLists.txt file doesn't come into the picture in this scenario.)
One possibility, as I understand, would be to copy all options from the root CMakeLists.txt file to every other proj-X/CMakeLists.txt file. Of course, this is a hack and a maintenance nightmare, but I suppose it would work.
Are there other possible solutions? Can I, say, create a file containing all the common options and save it to the root, and then do the CMake equivalent of #include within each of the proj-X/CMakeLists.txt files? Would there be an issue due to running the same commands twice (once on the root CMakeLists.txt and another on the proj-X/CMakeLists.txt file, when starting the build from the root)?
You may need to rework some of your CMakeLists.txt files.
I would recommend watching Daniel Pfeifer's Effective CMake talk at CPPcon (slides available here).
The gist of it is that all of your projects should provide everything they need in order to be build or compiled, in essence build requirements and usage requirements. To achieve this in a maintainable and scalable way you have to move away from variables and setting global options (add_compile_options, include_directories, etc) and instead focus on targets (target_compile_options, target_include_directories, etc).
So, in your case proj-1/CMakeLists.txt will provide one target (let's call it proj::proj1) that sets the proper PUBLIC and INTERFACE options (by options I mean needed compiler features, dependencies, include directories, etc).
An abstract example:
project(proj1)
add_library(proj1 src.cpp)
# This are private include files, whoever uses this library does not need them
target_include_directories(proj1 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
# These are public, needed both by this target and by whoever uses it.
target_include_directories(proj1 PUBLIC
# This is used when building the target
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/public/include>
# This is used when the target is installed
$<INSTALL_INTERFACE:include>)
# Instead of asking directly for a language standard we ask for a compiler feature. We make this public so whoever depends on this target knows they also need this feature.
target_compile_features(proj1 PUBLIC cxx_strong_enums)
# As above, but this is needed only by this target during the build.
target_compile_features(proe1 PRIVATE cxx_lambdas)
# Add an alias, users can use target_link_libraries(target PRIVATE|PUBLIC proj::proj1) to add this target as a dependency (this will propagate all the PUBLIC include paths, compile options, compile features, dependencies, etc.
add_library(proj::proj1 ALIAS proj1)
This is highly abstract, it depends on what you're actually doing in your build scripts, it's hard to give a better explanation than Daniel Pfeifer, so I recommend watching his talk or at least reading the slides. It will make your build scripts a lot easier to write, read, and use.
Another great resource is this site.

Platform library name expansion using target_link_libraries with absolute path as library path?

Using target_link_libraries in CMake with just the library name, e.g.
target_link_library( myProject SomeLibrary )
will expand SomeLibrary to SomeLibrary.lib, libSomeLibrary.so, etc. depending on platform. However, if a full path is specified then the library name is not expanded based on platform, e.g.
target_link_library( myProject ${myProject_SOURCE_DIR}/libs/SomeLibrary )
How can I get the library name to be expanded based on platform? Currently, I'm detecting platform in the script and adjusting the library names myself which feels a little ugly.
(Background: over on this question I'm advised to use absolute paths when specifying libraries rather than using link_directories)
Use find_library.
Instead of hardcoding the full path, you should only give the name of the library and a list of (possibly configurable) locations where it might be found and have find_library do the rest. If successful, the result of the find_library call can be fed right into target_link_libraries.

CMake : how to link a library WITHOUT automatic search function FIND_PACKAGE?

I wonder how to find/link a library without any FIND_PACKAGE.
Assume that we have a "personal" library called testlib :
/perso/testlib/include/testlib1.h
/perso/testlib/include/testlib2.h
/perso/testlib/lib/testlib1.a
/perso/testlib/lib/testlib2.a
How to link it with CMake ?
1) What are the functions to link it directly in the code of the CMakeLists.txt ?
2) How to allow the user to select where are the files ?
3) I have difficulties to understand what is interpreted and what it's not by CMake. For example if you define a variable ${MYVARIABLE_INCLUDE_DIR} or ${MYVARIABLE_LIBRARIES} is "INCLUDE_DIR" or "LIBRARIES" an extension interpreted by CMake or there is no difference if I call this variable ${MYVARIABLE_INCDIR} ?
4) How to do the same procedures (including a "personal" library) if you have a library that contains ten library files or more in the lib directory ?
5) And finally, when you type TARGET_LINK_LIBRARIES(myexecutable gmp), how do you know that the name of the library is "gmp". Why not "Gmp" or "GMP" ? Is the name of the library to put in this function just equal to the .a file minus "lib" and ".a" ? For example libgmp.a -> gmp ? If I want to link a library called libtestlolexample.a, do I have to type TARGET_LINK_LIBRARIES(myexecutable testlolexample) ?
Thank you very much.
You can use target_link_libraries(myexecutable mylib) to link to the library "mylib". The compiler will use its default way to find the specified library (e.g. it will look for libmylib.a on Linux). The compiler will only look in the link_directories(directory1 directory2 ...), so you could try that command to add the required directories to the search path.
When "mylib" is also compiled with CMake this will be recognized and everything should work automatically.
When you want the user to specify a directory you can use a cached CMake variable. set(MYPATH "NOT-DEFINED" CACHE PATH "docstring").
For more complex stuff it is very advisable to write a CMake find module that can be used with find_package. I suggest you take a look at the FindALSA.cmake which can be used as a good starting point.
The interesting part is at the end:
if(ALSA_FOUND)
set( ALSA_LIBRARIES ${ALSA_LIBRARY} )
set( ALSA_INCLUDE_DIRS ${ALSA_INCLUDE_DIR} )
endif()
mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY)
The ALSA_LIBRARY and ALSA_INCLUDE_DIR variables are user configurable and stored in the cache, while ALSA_LIBRARIES and ALSA_INCLUDE_DIRS as well as ALSA_FOUND get computed and are the ones that the user of the find module is supposed to use.
Typically one would use the find module like this:
find_package(ALSA REQUIRED)
include_directories(${ALSA_INCLUDE_DIRS})
target_link_libraries(myexe ${ALSA_LIBRARIES})
I'm sure you can adapt this for your personal library.
Usually when you want to link against a library that doesn't have a find_package module (e.g. it's an uncommon library, or it's your own library), then you can use the basic commands (the find_X commands) to set variables with the paths you need. Then you use those variables as with find_package (include_directories, target_link_libraries).
If you're going to be using this library from multiple packages, you may want to create a find_package module; basically it's using the same commands with certain conventions.
Either of these allow you to specify paths (in the CMake module) to look in, and they allow the user to override the paths (the variables show up as options in ccmake/cmake-gui).
I'd be glad to add an example of one or both of these methods, just let me know what you're looking for.
If you just want a quick-and-dirty solution, you could do this, but I wouldn't recommend it:
include_directories(/perso/testlib/include)
add_executable(myexecutable myexecutable.cpp)
target_link_libraries(myexecutable
/perso/testlib/lib/testlib1.a
/perso/testlib/lib/testlib2.a)
Regarding your question about target_link_libraries (#5), you can do it several ways. If you want you can provide the full name (e.g. target_link_libraries(myexe libfoo.a)), but I think it's better (more portable I suppose) to use the short name (e.g. target_link_libraries(myexe foo). You can also include linker flags; I'm not sure where I read it, but I think it may translate the -L and -l flags for different linkers.
For example, if I have a bunch of libraries in the same directory, and I know the names, I might find the directory, store it in a variable, and then do this:
# First, find and set TESTLIB_LIBRARY_DIR, e.g. with find_path
# ...
# This assumes the libraries are e.g. 'libtestlib1.a' and 'libtestlib2.a'
set(TESTLIB_LIBRARIES
-L${TESTLIB_LIBRARY_DIR)
-l testlib1
-l testlib2)
add_executable(myexecutable myexecutable.cpp)
target_link_libraries(myexecutable ${TESTLIB_LIBRARIES})
If you want to make your own find_package module (like trenki mentioned, FindALSA.cmake seems to be a good starting point), you can use it by adding the directory to the CMAKE_MODULE_PATH; for example, if you put your module(s) in a cmake/modules/ subdirectory:
# Look for extra CMake modules in a subdirectory of this project
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/" ${CMAKE_MODULE_PATH})
One possible issue with FindALSA.cmake: I'm not sure CMAKE_CURRENT_LIST_DIR will work. So I think you should make this change (the second work for me in a module I wrote):
# Change this line
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
# To this (with no path/extension it will search the CMake modules path):
include(FindPackageHandleStandardArgs)
And to get the usage of FIND_PACKAGE_HANDLE_STANDARD_ARGS, look at FindPackageHandleStandardArgs.cmake in the CMake Modules directory.
CMake has a good documentation.
Static linkage (if i understand you correct) is archived by passing the STATIC keyword to add_library
I would suggest to not do that (I'm not a CMake expert) but it sounds like the expense would be to large.
There is no difference, ${MYVARIABLE_INCLUDE_DIR} ist just a variable name it whatever you want. But i would suggest that you follow the naming convention.
One libary is always one .lib/.a file so there should be no problem just use the add_library& target_link_libraries& add_dependencies function.
The library name is always the name that you pass to add_library. However Gmp or gMP would be the same as CMake is case intensitive