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 !!