Jump to content

  • Log In with Google      Sign In   
  • Create Account


Like
16Likes
Dislike

Cross Platform Test Driven Development Environment Using CMake (Part 5)

By AllEightUp | Published Aug 19 2013 01:21 PM in General Programming
Peer Reviewed by (Josh Vega, Michael Tanczos, Dave Hunt)

make cmake cross platform makefile c++ c++11

With the release of CMake 2.8.11, some new features have been made available which greatly simplify certain areas of work compared to the prior articles. The primary feature of note is what the CMake documentation calls ‘target usage requirements‘. In general, CMake now maintains a hierarchy of information about targets, what is needed to not only link them but to also use them from other targets. Previously much of the information required to use a target had to be manually maintained and exported in variables, which was the focus of the prior 4 articles. With the new abilities, I have no problem throwing all that prior work out the door and hopefully once you get through reading this, you won’t either. The simplifications to using CMake is impressive and will greatly speed up work with CMake in the future, hopefully this short article will be useful in applying the new feature.

Parts 1 2 3 4 5

Target Usage Requirements


In versions of CMake prior to 2.8.11, it was possible to tell CMake to link with libraries by simply specifying the target name. So, if you have a library such as Core, and you wanted an executable to link against it, you could write the following:

ADD_EXECUTABLE( Main main.cpp )
TARGET_LINK_LIBRARIES( Main Core )

CMake would take care of figuring out the path and extension of the Core library automatically. This simplification was a great utility given that paths could change based on different build settings and having to track such things would be a chore. Unfortunately this still left the user to deal with dependencies, include paths and compiler definitions in order to use the library correctly. As such, the above example would fail in practice since you can not actually use the library without the additional information. In the prior articles, the missing information was provided through a set of standardized variable names which were created at the same time as the Core library target and exposed to the parent CMake context. In Core, the following items were exposed:

SET( CORE_STATIC_LIBS Core ... )
SET( CORE_INCLUDE_DIRS ... )
SET( CORE_STATIC_DEFS ... )

With those items, it was then possible to properly setup the usage of the Core library as follows:

INCLUDE_DIRECTORIES( ${CORE_INCLUDE_DIRS} )
ADD_EXECUTABLE( Main main.cpp )
TARGET_LINK_LIBRARIES( Main ${CORE_STATIC_LIBS} )
TARGET_DEFINITIONS( Main ${CORE_STATIC_DEFS} )

Note:  
This is not exactly how it was setup, this is a simplification for example purposes only.


While this is a working solution, it is also tiresome work which was prone to error even with the standardized names. Thankfully with target usage requirements, this additional work is no longer required; a more encapsulated solution is now possible.

Dependency Tracking


Going back to the original example of defining the Main target:

ADD_EXECUTABLE( Main main.cpp )
TARGET_LINK_LIBRARIES( Main Core )

Wouldn’t it be nice if that were to work without all the manual hoops to jump through? This is exactly what target usage requirements does; it adds the data which was previously manually tracked into the target information itself. As such, not only does the target know where the library exists, it also has information about what other libraries it requires, which include paths are needed and what compile time definitions need to be set in order for another target to use it. This feature is added through a minor modification of one command and the addition of two new commands:

TARGET_LINK_LIBRARIES: Modified to track library dependencies and not just the location of the target.
TARGET_INCLUDE_DIRECTORIES: New command which adds include directory requirements to the target.
TARGET_COMPILE_DEFINITIONS: New command which adds compile time definitions to the target.

Ignoring some of the additional details for the moment and simply applying the new features to the Core library is very simple. Much of the old setup work can be removed and you should consider certain commands as deprecated in the future, among the items to remove: ‘INCLUDE_DIRECTORIES’ and ‘SET_TARGET_PROPERTIES’ for definitions should be stripped out. After removing some of the old setup work, after ‘ADD_LIBRARY’ you can simply add the three commands:

TARGET_LINK_LIBRARIES( Core GFlags GLog )
TARGET_INCLUDE_DIRECTORIES( Core PUBLIC "${INCLUDE_DIRECTORIES}" )
TARGET_COMPILE_DEFINITIONS( Core PRIVATE BUILDING_CORE PUBLIC PUBLIC "${DEFINITIONS}" )

Now the simplified Main target will automatically get all the information it requires to link and compile against the Core library. Anyone who has done anything beyond the trivial with CMake should quickly realize just how much of a time saver this is going to be once pushed out to all the list files.

New Command Details


Both of the new commands have some details which were ignored for the simple example. For instance, when defining the compile definitions, what are the “PUBLIC” and “PRIVATE” attributes about? There are actually three attributes which the two new commands support, the missing one being “INTERFACE”. The concepts are quite easy to grasp in terms of some simple usage examples.

Core provides inclusion of GFlags and GLog as part of its job. Unfortunately GFlags and GLog need some special definitions (primarily for Windows) in order to both compile and be used by others correctly. For this purpose, the attribute “PUBLIC” is used to specify the definitions for GFlags and GLog. The public attribute specifies that the definitions exist both when the target itself is being compiled and also when a user of the target is being compiled. The other definition being supplied “BUILDING_CORE” is another Windows-only requirement used only by Core when it is being compiled. It is marked as “PRIVATE” such that it is only defined while Core itself is compiling and any user of the target will not receive the definition. The final unused attribute “INTERFACE” is the opposite of “PRIVATE” in that anything defined after that attribute is not defined while the target is built, it is only defined for users of the target.

The same behavior of the definition attributes is used for the include directories. So, for instance, if Core wrapped GFlags instead of exposing it, the include directory for GFlags could be marked “PRIVATE” such that it is only used while compiling Core. Any user of Core that tried to directly include “gflags.h” would have to do the inclusion themselves since it would not be inherited from Core.

There are also additional attributes which can be applied to the link libraries but those are mostly special cases which will be ignored. The only real notable attribute is “LINK_PRIVATE” for static libraries which are being placed in shared libraries. Otherwise, the default public linkage is usually appropriate.

Conclusion


The new CMake release is a welcome simplification to maintaining larger modular projects. The simplification and removal of manual information tracking reduces the verbosity of list files and removes considerable chance for errors. It should be hoped that many projects which use CMake will not hesitate in transitioning to the target usage requirements system and hopefully, if needed, this article will prove useful in making the transition. For the individual user targeting cross platform work, the new release provides even less likelihood of having to deal with difficult to track build problems.



License


GDOL (Gamedev.net Open License)




Comments

Another great article. If you can add some links to the previous articles, that would be even better.

Oops, was meaning to do that when I moved it from Scrivener, seems I forgotted...  Will fix

Great addition to an already great article.

These latest cmake features are really great and simplify things a lot.

If anyone's interested, I've created a very simple folder structure with relevant CMakeLists.txt files and sample code that implements most of what's discussed in this series.

Just a note: I mainly work on OSX so this is a bit biased towards clang/libc++

 

You can check it out at https://github.com/abigagli/CMakeEnvironment


Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS