Tutorial - Defining system requirements
In previous tutorials you learned how to develop native packages that depends either on native packages and or external packages. This tutorial show you how to define a dependency between a native package and the target platform using system configurations.
Technically a configuration is a PID entity used to:
- check if a system requirement is valid.
- extract important information if it is valid.
- optionally install (if possible) this system requirement if not valid.
Configurations are basically use to manage OS dependencies that we cannot reasonnably or for any reason do not want to wrap as an external package.
Step 1: Define a system requirement
Let’s suppose we continue to work on my-first-package
starting from previous tutorial.
We now want to add threading and we want to use threads posix API. To do this we have to check that the system supports the posix
configuration that will check that all basic posix API are available on the system. In the root CMakeLists.txt
of my-first-package
:
cmake_minimum_required(VERSION 3.15.7)
set(WORKSPACE_DIR ${CMAKE_SOURCE_DIR}/../.. CACHE PATH "root of the packages workspace directory")
list(APPEND CMAKE_MODULE_PATH ${WORKSPACE_DIR}/cmake) # using generic scripts/modules of the workspace
include(Package_Definition NO_POLICY_SCOPE)
project(my-first-package)
PID_Package(
AUTHOR My Name
INSTITUTION LIRMM
YEAR 2015
ADDRESS git@gite.lirmm.fr:own/my-first-package.git
LICENSE CeCILL
DESCRIPTION TODO: input a short description of package toto utility here
VERSION 0.2.0
)
check_PID_Platform(CONFIGURATION posix) #adding this line to previous content
PID_Dependency(boost)
build_PID_Package()
The call to check_PID_Platform
checks that the system provides the posix
configuration. If the system does not provide this configuration then CMake configuration will fail. To test if everything goes well:
cd <my-first-package>/build
cmake ..
Step 2: Using variables from configurations
If a system configuration is satisfied then it produces somes variables that may be used by components. For instance the posix
configuration provides the CMake variable posix_LINK_OPTIONS
that contains linker flags to use (e.g. -lpthread
) in order to use posix API.
2.1 Write the code
First edit the file named hello_main.cpp
in apps/hello
folder and paste the following code:
#include <hello.h>
#include <iostream>
#include <boost/filesystem.hpp>
#include <pthread.h>
using namespace std;
using namespace boost::filesystem;
static void* threaded(void* data){
if (data != NULL){
path p((char*)data);
std::string str((char*)data);
if(! exists(p)){
str+=" is NOT a valid path !!"
}
else{
str+=" is a valid path !!"
}
print_Hello(str);
}
return (NULL);
}
int main(int argc, char* argv[]){
if(argc == 1){
cout<<"Only one argument ... error !! Please input a string as argument of the program"<<endl;
return 1;
}
pthread_t the_thread;//using the posix API for threads
if(pthread_create (& the_thread, NULL, threaded, argv[1]) != 0){
std::cout << "Error: cannot create thread" << std::endl;
return (-1);
}
pthread_join (the_thread, NULL);
return 0;
}
The modification simply creates a thread to do exactly the same job as previously so the output should be the same as previously. But now the code explicitly depends on an API found in pthread.h
header that is provided by the target platform.
2.2 Modify the description
We need to modify the description of component hello-app
to be able to build it: we need to provide some flags to compiler and linker to make it possible to build it. These flags are contained in variables provided by the posix
configuration.
PID_Component(hello-app APP DIRECTORY hello
DEPEND hello-static boost/boost-filesystem)
PID_Component_Dependency(hello-app LINKS SHARED ${posix_LINK_OPTIONS})
We simply added the dependency to posix by setting adequate link options (using LINKS SHARED
argument) with those coming from the posix
configuration. Indeed the variable posix_LINK_OPTIONS
holds the list of linker flags to use when linking with libraries referenced by this configuration.
We here suppose that include dirs containing headers are in default system path and will so be found without additionnal include directive. This should be always true for the posix
configuration but sometimes we need to adapt to custom include folders. Let’s suppose we have to do that with posix
, the code would have been:
PID_Component(hello-app APP DIRECTORY hello
DEPEND hello-static boost/boost-filesystem)
PID_Component_Dependency(hello-app INCLUDE_DIRS posix_INCLUDE_DIRS LINKS SHARED ${posix_LINK_OPTIONS})
We use the INCLUDE_DIRS
argument of PID_Component_Dependency
to set the list of include folders required by the posix
configuration. The difference with previous example is that these include folders may change depending on the distributions and version of system the binary is deployed in. So the registered element is now directly the variable posix_INCLUDE_DIRS
and not its value (like for ${posix_LINK_OPTIONS}
, i.e. usage of ${}
). Indeed when a binary is linked, the name of the dependency is somehow registered in the binary itself so we need exact names of dependencies used to build the packages when we use the library. But the binary does not need to memorize where was include or library folders when it was build, it needs to be able to retrieve these folders for the given platform it is installed in. Using variable names like posix_INCLUDE_DIRS
(instead of values) allows to specify that corresponding information can be modified when package binary is used. So in the example, when my-first-package
is used the include dirs pointed by posix_INCLUDE_DIRS
may be different from those used to build it. This is an important feature to develop relocatable binary code while keeping consistency of meta-data.
2.3 Use a simple and robust description
Of course there are different standard variables generated by configurations like posix
, and depending on their nature each must be used either as a value or as a variable. Furthermore sometimes only part of these standard variables are generated by a configuration simply because they are not useful. For instance posix
does not define posix_COMPILER_OPTIONS
or posix_DEFINITIONS
simply because they are not required.
All of this make it a bit difficult, for an end user to known what exactly must be used and in which way. That is why recent versions of PID API now allows to use shortcuts names when a component uses a configuration:
PID_Component(hello-app APP DIRECTORY hello
DEPEND hello-static boost/boost-filesystem)
PID_Component_Dependency(hello-app CONFIGURATION posix)
We now specifically use the CONFIGURATION
keyword to tell hello-app
to use libraries from posix
configuration. Technically the system automatically deduces what standard variables are available for the given configruation and automatically and adequately (using variables or values) configure the dependency accordingly.
To make it even more simple you can simply do:
PID_Component(hello-app APP DIRECTORY hello
DEPEND hello-static boost/boost-filesystem
posix)
In this situation the system autmatically deduces that posix
is a configuration and then performs same operations as if PID_Component_Dependency
were explicitly used.
Step 3: Configure, build and run
Now that the source code is ready, let’s build it.
- configure and build the package:
cd <my-first-package>/build
cmake ..
make build
Or simpler, use the pid
script:
cd <my-first-package>
pid build
This command checks that posix
configuration is supported by currrent platform.
- build the package:
cd <my-first-package>
pid build
- run
hello-app
:
cd <my-first-package>/build/release
./hello-app /lib
./hello-app /path/to/nowhere
The output of the command should print something like:
Hello /lib is a valid path !!
Hello /path/to/nowhere is NOT a valid path !!