In this tutorial we learn how to use wrappers to define system dependencies.Indeed it is sometimes difficult or even meaningless to build some dependencies from scratch, for instance:
- if the dependency is intrinsically bound to the operating system in use, like for instance the
posixlibraries, it would be risky to replace it with a custom build of this library as many other libraries and programs use the system one.
- if the dependency is complex, like for instance the
x11libraries, it would be a pain to build it, it is far simpler to directly use the system version, even if it restricts usable versions to only the one supported by the operating system.
Furthermore, you sometime must use the system version of a dependency, simply because it is used by another system dependency.
Combining all these concerns together we need a way to define external package dependencies either only or also as a system dependency. In the following sections we show how to define system dependencies (i.e. external packages directly provided by target platform) using wrappers.
First example: wrapper for posix system dependency
As a first example we will see how a dependency to
posix libraries is written. Here we consider the term
posix as the name of an external package that defines a the set of standard libraries in a posix compliant system : libdl, librt, etc.
Deploy the posix wrapper
This will given no spectacular result, this is normal. Now let’s inspect the content of the wrapper:
CMakeLists.txt is defined as usual (see previous tutorials on wrappers).
You can see that inside the
src folder of the wrapper there is a
system folder but no version folder. This is normal since we define no PID built version of
posix because we always use it as a system dependency.
system folder there are 2 files:
CMakeLists.txtfile that describes the requirements on system configuration.
- a file named
eval_posix.cmakethat is the CMake script used to evaluate if the target platform
Let’s now inspect these two files.
The content of this file should be more or less:
As you can, see there is a specific API to describe requirements on system configuration (we call this description system configuration for short).
PID_Wrapper_System_Configuration is mandatory, it defines global information on system configuration.
EVALdefines the path (relative to the
system folder) to the evaluation file, here
eval_posix.cmake. This argument is mandatory.
VARIABLESargument lists variables generated by the configuration. Each of this variable is prefix with the name of the configuration, here
posixconfiguration so generates:
VALUESargument lists the internal variables used to set the value of returned variables. Each variable in
VALUESdefine the value of the returned variable at same place in
VARIABLES. For instance
We decide to normalize names of variables returned by configurations check. Even if all configurations are not forced to return them all, we suggest to defined them anytime possible:
<config>_VERSION: is version of the configuration.
<config>_LINK_OPTIONS: is the list of linker flags (
-l...) to use when using the configuration.
<config>_LIBRARY_DIRS: is the list of path to folders where system libraries binaries can be found.
<config>_INCLUDE_DIRS: is the list of path to folders where system libraries headers can be found.
<config>_COMPILER_OPTIONS: is the list of extra compiler options to use when building a project that uses the configuration.
<config>_RPATH: is the list of path to runtime objects (typically
.sofiles) in use when using the configuration.
User can decide to define additionnal variables depending on the needs of the configuration they have to define. The difference is that standard variables can be automatically deduced andd used when a dependency between a component and a configuation is defined.
The primary goal of system configurations is so to provide CMake variables that can be used into packages description, exactly as “classical” find modules in CMake.
System configurations can also define constraints using
PID_Wrapper_System_Configuration_Constraints function. Constraints are like additional arguments used when checking if target platform fulfills a platform configuration.
Constrainst can be of one of thos 3 types:
REQUIRED: the constraint must be defined anytime the configuration is checked. It means that the parameter will be used inside the eval script (see next sections) to check additional properties of the configuration. The corresponding contraint is always propagated to expressions generated by the configuration (for instance in the description of a binary package).
OPTIONAL: the constraint can be defined or not anytime the configuration is checked. It means that, if this contraint is specified, the corresponding parameter will be used inside the evaluation script (see next sections) to check additional properties of the configuration. The corresponding contraint is never propagated to expressions generated by the configuration (for instance in the description of a binary package).
IN_BINARY: the constraint is optional at the moment the configuration is checked BUT is always propagated to expressions generated by the configuration (for instance in the description of a binary package).
Any kind and number of arguments can be defined for a configuration, but there are some standardized constraints:
sonamespecifies the list of sonames of libraries provided by the configuration.
symbolspecifies the list of symbols and symbols’ versions provided by libraries of the configuration. Each symbol is defined usinhg the pattern
versionspecifies the version of the package.
Those specific constraints are all
IN_BINARY. The configuration’s find script does not need to handle
symbol explicitly. PID internal mechanism uses this information to check binary compatiblity. If specified,
version constraint is used in the eval script to check that the given version is installed in system. It must have same value than
<config>_VERSION variable returned to script.
In th example, evaluation of
posix generate the soname of every library provided by the configuration. And this is obtained by reading the value of the
POSIX_SONAME cmake variable. This variable must be set by the evak script (see next sections).
We can also define some dependencies between system configurations, this is achieved using
PID_Wrapper_System_Configuration_Dependencies. The meaning is mostly the same as any dependency in PID: in the example
posix depends on
threads. This means that anytime the
posix system configuration is evaluated the
threads configuration will be evaluated as well. This later simply provides flags to use adequate threading libraries provided by the target platform operating system (it is based on the use of the well known
Evaluation files (let’s call them “eval files” for short) are used:
- to evaluate if the target platform fulfills the requirements, something like a classical find module in CMake. It mostly consists in finding adequate files (libraries, headers, executable) on target platform.
- to manage constraints defined in global description. In the example there is only the constraint
sonamethat is specific and does not require to be managed into the script.
- to generate CMake variables that will be used to set value or variables declared in global description, either in returned variables, constraints or even dependencies. For instance it set the value of
To write those files, PID provides a specific API that implements most of redundant operations, and common CMake command can also be used. Let’s now take a look at the code:
The first line is really common and simply sets the result of the script to false by default. Then we use classical CMake construct to determine if we are on a UNIX compliant OS. If not (e.g. on Windows) then the posix system configruation is never satisfied, which is a normal situation.
The remaining part if the code consist in finding libraries
On MacOS and FreeBSD, those libraries are directly put into the system library so no need to detect them, and so there is no additionnal flag to provide to compiler and linker. That is why all variables are set to an empty value. The line
found_PID_Configuration(posix TRUE) simply tells that system configuration is satisfied (which is always true by definition on those platforms). So main part of the implementation is for linux systems.
Linux specific code looks like what we can find in CMake Find modules:
- we try to find the headers of the libraries using
- we then try to find libraries binaries. We use
find_librarybecause this utility finds a system library in system folders the same way as the linker will do. It also gives the soname of a library in addition to its canonical path on filesystem. In other work it automate the job.
Remark: the variable
POSIX_SONAME must contain the soname of each library provided by the wrapper, so the script must set this variable with the sonames previously extracted from libraries binaries.
Finally, if everything has been found then this means the system configuration is satisfied, so we can set the local variables that are used in global description to set all values and global variables.
- variables holding include directories, path to libraries and sonames can be directly set from previous operations.
found_PID_Configuration(posix TRUE)is used to say that evaluation is successful.
- Other local variables that have not been already set (
POSIX_LIBDIRS) thay can be deduced using
convert_PID_Libraries_Into_System_Links: convert path to libraries into their equivalent system link. For a library with name
convert_PID_Libraries_Into_Library_Directories: get the path to the folders containing the libraries. For the variable containing the value
/usr/lib/lib<name>.soit returns the variable containing
Evaluate the posix system configuration
Now the description is complete, we can evaluate if the target platform fulfills the requirements of the posix system configuration. This is made in two steps:
This first command generates all the scripts and stuff used to evaluate the system configuration but does not perform the evaluation by itself. It must be used anytime you modify the description of posix. Then:
This performs the evaluation of the system configuration on target platform, and gives the resulting output. Something like:
This output is the same as the one used in
check_PID_Platform command of packages. Locally, the
eval_system_check command is only useful for debugging purpose. You may notice that library directories (
posix_LIBRARY_DIRS) is empty which is normal since the library dir of posix libraries is in default library system path, so we do not need to set it in compilation process.
That’s it you know all the basics for writing system configuration checks in wrappers.
Second example: wrapper for boost system dependency
Now let’s talk about os variants of external packages. Sometimes we need an external package that we can build to be explicitly used as an OS variant. This means that a given version of the package must be installed from system packages and not locally built.
For instance, we may use a system configuration for a given external package for which we do not provide a build procedure, like for
ROS, and this external package requires the system version of another exetrnal package for which we provide a build procedure. As an example we sometimes use
ROS for some packages and we did not want to rebuild
ROS so we never provided a build procedure for any of its versions. In the end we only provide a system configuration for
ROS and all
ROS dependencies are themselves system dependencies.
boost so we need
boost to be managed as a system dependency as well.
In this tutorial we will write the system configuration for
boost. We want to be able to use directly boost as a system dependency instead of dependency built within PID.
Deploy the boost wrapper
Let’s first deploy the wrapper for boost.
The content of this file should be more or less:
As you can see the content has mostly same information as for the
posix system configuration. But there are new informations:
- There are automatic install procedures defined for the boost package. They are defined using the name of the system packager detected at workspace level. Here two install procedures are defined : one for systems supporting
aptpackaging (debian derivatives), another for systems supporting
pacmanpackages (archlinux derivatives). If after a first evaluation the system configuration is not satisfied by target platform and host is target platform, they the adequate system manager will be used to install system packages satisfying the dependency.
- Additional find files can be provided using the
FIND_PACKAGESargument. Here we simply say that the find module named
FindBoost.cmakeis in the
- the variable
VERSIONis set. This is mandatory if you want PID to be capable of “translating” your system dependency as if if were a normal dependency in PID. To make it possible the version found must be the same as one of the versions defined in the boost wrapper: this is mandatory to make PID capable of defining components.
version is also set from same value, to enforce the use of corresponding boost version in binary. Indeed after
boost has been found, its version has been deduced, and we need to force teh usage of this version in binary objects so we give to the
version constraint the value
BOOST_VERSION (a variable supposed to hold the version of
PID_Wrapper_System_Configuration_Dependencies we simply tell that
boost configuration check requires
posix configuration check to be satisfied first.
You can see constraints as parameters to be used when checking a configuration (either from wrappers of native packages). So for instance you may want a package using a precise version of
version is a constraint
IN_BINARY which means that it is optional at source level but mandatory in resulting binaries.
- Constaints are listed inside the
characters just after configuration name.
- Each constraint as the form
name=valuewhere value may be a single value or a list. If a list is used all elements are separated by
- Constraints are separated with
If in addition to previous constraint we also wanted to check for specific components:
Since the two possible constraints specified are
IN_BINARY such kind of expression will be used in resulting binary package description.
Now that the “interface” of the system configuration is defined we need to write its behavior and this is achieved by the
Since we defined the constraint
version, the eval script must be capable of configuring the find process depending on the value of those constraints. So we need a way to access values of these constraints in the find script. Hopefully PID automatically generates adequate variables for all constraints anytime needed. These CMake variables are named following the pattern
<wrapper>_<constraint> and are used the classic way in CMake:
We manipulate two variables:
boost_librariesthat is the variable representing the constraint
boost_versionthat is the variable representing the constraint
As you may see the eval script will behave slightly differently depending on the value of those variables:
- if a
versionconstraint is defined then the script checks if this version is found in system, and if not found the check will fail. If
versionis not used then any OS installed version is eligible.
- if a
librariesconstraint is defined then the find script checks that these component (in addition to default components
filesystem) exist and collect their data. If any component specified does not exist the check will fail.
Remark: the call to
find_package refers to the find module provided by the wrapper. Indeed those find modules have priority over those provided by CMake.
On important aspect in this script is that we set the value of the variable
BOOST_VERSION. This variable will be used, as described before to set values of variables returned by system configuration evaluation (i.e. boost_VERSION).
Other parts of the code follow more or less the same logic as previously explained in
Boost_LIBRARIESvariable is provided by the original eval script of boost. We use
convert_PID_Libraries_Into_Library_Directoriescommands to easily get
BOOST_COMPONENTScontains all libraries of Boost found on system.
boostconfiguration check is successful when execution reaches the line
All those internal variables will be used to set returned variables defined in
check_boost.cmake, for instance:
boost_INCLUDE_DIRSreturned variable will be set from
Boost_INCLUDE_DIRS(take care about CAPITAL letters) that has been generated by original project find module.
BOOST_VERSION) is used to set the value of the
version) constraint in binary packages using the
Remark: the API that is used to write eval (or install) scripts is provided by the cmake file
Evaluate the boost system configuration
If everything went well, the result should look like:
Create a new system configuration
Creating a new system configuration (let’s say
my-config) starts by :
- creating the corresponding wrapper
- or reusing an existing wrapper if you already specified install procedures for the corresponding external package.
From a global point of view, the process is exactly the same as for defining any wrapper. You just have to add a
system folder where you need to write:
CMakeLists.txtdefining the system configuration.
- an eval file (referenced by the
CMakeLists.txt) that tells how to check if target platform fufills the corresponding system configuration. Reference the evaluation file using
- optionally define which system packages have to be installed, using appropriate keyword for the host possible packaging systems (you can use one or more argument whose names match those of the packaging system :
- optionally you can specify an install file (using
PID_Wrapper_System_Configuration) if the install procedure is too complex to simply require the installation of a set of packages depending on the packaging system in use. This script is called instead of a direct call to system package manager.
- optionally add find modules into the folder for each things you may want to fing using
find_package. Reference the modules using
- optionally add any kind of content (files and folder) that are required by the evaluation script. Reference this content using