It may be sometimes useful to reuse PID packages from outside of a PID workspace. This may be the case if you must use another build system than PID. For instance, if you develop ROS packages within a catkin workspace (so you have to use catkin build system) but using libraries defined in PID packages (defined with PID build system).

To do that you can use of the following methods:

Using CMake

PID provides a dedicated CMake API that can be used to work with PID packages from outside of a PID workspace. This allows to provide software defined with PID (and benefit of PID features like automated deployment for instance) to people that do not work with PID.

Step 1: Create a simple CMake project

The first step consists in creating a CMake project somewhere in your filesystem. We denote <extern_project> as the path to this project.

  1. Create a folder called extern_project outside of your PID workspace.
  2. Inside this folder, create 3 subfolders: build (build tree), src (source tree) and install (install tree). This structure is just for demonstration, you can use any kind of project structure.
  3. Create an empty CMakeLists.txt file at the root of <extern_project>
  4. Create 3 empty files: src/lib.h, src/lib.cpp and src/main.cpp.

Now we have to edit the files.

  • In src/lib.h paste the code:
#pragma once

std::string get_Absolute_Path(const std::string& rel_path);
  • In src/lib.cpp paste the code:
#include <lib.h>

#include<pid/rpath.h>

std::string get_Absolute_Path(const std::string& rel_path){
  return (PID_PATH(rel_path));
}
  • In src/main.cpp paste the code:
#include <lib.h>
#include <boost/filesystem.h>
#include <iostream>
using namespace boost;

int main(int argc, char* argv[]){
  if(argc < 2){
    std::cout<<"you must input a path !"<<std::endl;
    exit(0);
  }

  path p(get_Absolute_Path(argv[1]));
  if(exists(p)){
    std::cout<<"path exists"<<std::endl;
    return(0);
  }
  else{
    std::cout<<"path does NOT exist"<<std::endl;
    return(-1);
  }
}

Step 2: Define content of the CMake project

Use the dedicated API in the external project’s CMakeLists.txt:

cmake_minimum_required(VERSION 2.4.6)
project(extern_project)
############################################
##### initializing Reference to PID ########
############################################
set(PATH_TO_PID_WS /opt/pid/pid-workspace CACHE PATH "Path to the root folder of PID workspace")

if(NOT EXISTS ${PATH_TO_PID_WS})
	message("ERROR path ${PATH_TO_PID_WS} does not exist !!")
	return()
endif()

include(${PATH_TO_PID_WS}/Use_PID.cmake)
import_PID_Workspace(PATH ${PATH_TO_PID_WS})

set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install)

import_PID_Package(PACKAGE pid-rpath VERSION 2.1.1)
import_PID_Package(PACKAGE boost VERSION 1.64.0)

include_directories(src)

### creating a library
add_library(mylib SHARED src/lib.cpp src/lib.h)
bind_PID_Components(LIB mylib COMPONENTS pid-rpath/rpathlib)

### creating an executable
add_executable(myapp src/main.cpp)
bind_PID_Components(EXE myapp COMPONENTS boost/boost-filesystem)
bind_Local_Component(EXE myapp COMPONENT mylib)


install(TARGETS mylib myapp
	RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
	ARCHIVE DESTINATION lib)

First 2 lines are quite common in CMake then the following lines are used to provide a user cache entry to set the path to the target PID workspace. Then:

  • including the CMake API provided by the workspace
include(${PATH_TO_PID_WS}/Use_PID.cmake)
  • configure the CMake project to interface it with a PID workspace.
import_PID_Workspace(PATH ${PATH_TO_PID_WS} MODE Release)

The MODE keyword is used to specify which build mode of PID coponents is used locally. This way you can bind local debug code with PID Release code for instance. If no mode is specified the default mode in use is the one defined by local project or if none Release mode is selected by default. If no path is given then the project will try to find the pid-workspace directly inside local project source folder.

  • import the PID packages you want to use:
import_PID_Package(PACKAGE boost VERSION 1.64.0)
import_PID_Package(PACKAGE pid-rpath VERSION 2.1.1)

In this example we import the native package called pid-rpath (management of runtime path to resources) and the external package boost (for using the filesystem library). A limitation here is that if you want to use boost locally with a specific version you need to import boost BEFORE pid-rpath in order to impose the boost version constraint to pid-rpath. If you do not care about boost version you could do:

import_PID_Package(PACKAGE pid-rpath VERSION 2.1.1)
import_PID_Package(PACKAGE boost)

This way you simply use the same version of boost that the one defined by default by pid-rpath.

  • create a library mylib from source code, as usual with CMake:
add_library(mylib SHARED src/lib.cpp src/lib.h)
  • bind the library mylib to the library rpathlib defined in package pid-rpath :
bind_PID_Components(LIB mylib COMPONENTS pid-rpath/rpathlib)

The bind_PID_Components command performs adequate CMake configuration actions to allow mylib to use rpathlib, like target_link_libraries, target_compile_definitions, etc. The LIB argument is used to specify that mylib is a shared library. The COMPONENTS argument is used to list libraries used by mylib, in this example the library rpathlib.

  • We then create an executable the usual way in CMake:
add_executable(myapp src/main.cpp)
  • This executable is using the library filesystem of boost, so we need to bind it:
bind_PID_Components(EXE myapp COMPONENTS boost/boost-filesystem)

You may notice that the syntax is more or less the same as for native package, while boost is managed as an external package in PID. The EXE argument specifies that myapp is an executable.

  • Bind myapp with mylib:
bind_Local_Component(EXE myapp COMPONENT mylib)

The command bind_Local_Component must be use when binding locally defined components together if the dependency is also bound to a set of PID components. In other words you must use bind_Local_Component instead of a classical target_link_libraries for instance, in order for you local project to be able to resolve dependencies transitivity and fusion. The EXE argument specifies that myapp is an executable (you can use the keyword LIB to bind local libraries together). The COMPONENT argument define the local target to bind with.

Step 3: Configure and run the CMake project

That’s it, your project is ready to be configured, built and installed :

cd <extern_project>/build
cmake ..
make
make install

The build process should work properly then you can run the executable:

cd <extern_project>/install
./myapp <extern_project>/src

The output should be:

path exists

Step 4: Using system dependencies

No let’s suppose you need to use system dependencies you will need to constraint deployment of PID code in order to make it use those system dependnecies instead of PID compiled ones. In previous example, let’s suppose you want to use Boost as a system dependencies. To do this you need to modify the previous code.

  • configure the CMake project to interface it with a PID workspace AND use given system dependencies.
import_PID_Workspace(PATH ${PATH_TO_PID_WS} MODE Release SYSTEM_DEPENDENCIES boost)

We simply said to use Boost as a system dependnecies. In PID all system configurations are all full lower case string expressions. Then to use boost locally do:

  • import the PID packages you want to use:
import_PID_Package(PACKAGE pid-rpath VERSION 2.1.0)
import_PID_Package(PACKAGE boost)

Here you can use boost system dependency locally and furthermore pid-rpath will be recompiled to use this same version ensuring global consistency of your project’s dependencies. In this situation there is no constraint on package import order since the version constraint is undirectly defined by the use of the corresponding system dependency.

Now let’s imagine you just want a system dependency that uses boost but you do not want to use boost (or maybe even don’t know that this system dependency used boost). Since this top level dependency uses boost system version we must ensure that pid-rpath also uses same version of boost. This can be completely transparent for the user if the system configuration is defined in PID. For instance let’s manage that with ros:

  • configure the CMake project to interface it with a PID workspace AND use given system dependencies.
import_PID_Workspace(PATH ${PATH_TO_PID_WS} MODE Release SYSTEM_DEPENDENCIES ros)
...
import_PID_Package(PACKAGE pid-rpath VERSION 2.1.0)

As you may notice there is no direct reference to boost but as ros is (well) defined into PID the system deduces that anytime boost is used by PID packages it must be the system version of boost.

That’s it, there is nothing more to learn !

Using pkgconfig tool

When you want to use the content of a PID workspace from another build system than one written with CMake, you cannot use the dedicated CMake API of PID. That is why PID also provides a way to use another tool: pkg-config.

Step 1: Generating configuration files for pkg-config

pkg-config needs specific .pc files that describe dependencies of each library. So in order to make it possible to use PID generated libraries we need to generate those .pc files. There is a PID environment that can be used to do that, we simply need to add it to current profile:

pid cd
pid profiles cmd=add env=pkg-config

Now everytime a PID package is built, it installs a set of .pc files and also launch the generation and install of .pc files of its dependencies. Let’s suppose we want to use the package pid-os-utilities from outside of a PID workspace:

pid cd pid-os-utilities
pid build

The generated .pc files are installed in <pid-workspace>/install/<platform>/__pkgconfig__ and follow the pattern <package>_<library>.pc. For the pid-signal-manager component the file will have name: pid-os-utilities_pid-signal-manager.pc.

Step 2: Configuring pkg-config

The pkg-config needs to find the generated .pc file somewhere in the file system, to be able to resolve dependencies. To do that you need to set adequately the PKG_CONFIG_PATH environment variable:

export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:<pid-workspace>/install/<platform>/__pkgconfig__

You can put this line directly into your .bashrc file if you want to avoid typing this export command in every terminal.

Step 3: Using pkg-config descriptions

The final step simply consists in calling pkg-config to get adequate compilation/linker flags:

  • Getting compilation flags:
pkg-config --static --cflags pid-os-utilities_pid-signal-manager
#C standard to use
pkg-config --variable=c_standard something_lib
#C++ standard to use
pkg-config --variable=cxx_standard something_lib
  • Getting linker flags:
pkg-config --static --libs pid-os-utilities_pid-signal-manager

You simply have to use the output of pkg-config to pass adequate options to compiler and linker.

Step 4 : Management of PID specific runtime mechanisms

Warning: the runtime resource mechanism of PID will not work by using pkg-config because it requires additionnal configuration steps that cannot be performed with pkg-config. So only libraries that do not use rpathlib or pid-log internally will work with this approach. Look at the tutorial on runtime resources management to understand what are runtime resources.

To make it possible to use those library from your own project you also need to set other environment variables:

  • PATH must contain path to all bin folders of packages.
  • LD_LIBRARY_PATH must contain path to all lib folders of packages.
  • RUNTIME_RESOURCE_PATH must contain path to all share/resources folders of packages.

All of this make the pkg-config approach a bit difficult to use in the general context, that is why either using CMake or a global system install (see next section) is preferred.

Using system installed package

The last possibility is to be used when, for any reason, you prefer avoid using PID at all when developping a project, but you want to use packages developped with PID. This is also possible if you deploy the package (and its dependencies) directly is a system folder. This method faces the same limitation as any other classical system install, and you should be aware that it may have severe consequences on your system (for instance it may replace the version of an external package provided by the OS by one built with PID and may so break dependencies in your system). Furthermore, contrarily to using the PID external API, you will not be capable of using PID runtime mechanisms provided by pid-rpath and pid-log packages, even if PID packages installed in system will still work even if using those mechanisms.

You should consider that using the CMake API for using a PID workspace is always a better solution because:

  • it allows to control the build/deployment process of packages you want to use by enabling a fine tuning of versions of dependencies and of constraints coming from the OS.
  • it is completely isolated from OS, so no polution of OS folders, no setting of environment variables and no undesired side effect on system.
  • it implements the use of PID runtime mechanisms. Particularly it allows to manage runtime resources without restrictions.

Now that you are warned you can continue reading !

First step : deploying the package into a PID workspace

In a first time deploy the package as usual in PID, so first deploy the workspace:

cd <somewhere>
git clone https://gite.lirmm.fr/pid/pid-workspace.git
cd pid-workspace
pid configure

Then deploy the package (for instance for a package named something):

pid deploy package=something version=someversion

This will install the package and all its dependencies.

Second step : installing the package in a system folder

Then use the command to deploy this package into a system folder. By default the install folder is defined by the CMAKE_INSTALL_PREFIX variable of pid-workspace project. You may change it as usual by setting this CMake variables. By default the installed binaires are in the mode defined by the CMAKE_BUILD_TYPE variable of pid-workspace project (or Release if not defined). You may change it as usual by setting this CMake variables. Those two variables can then be use for all system install, ensuring same configuration for all your system installs.

Then use the dedicated command:

pid install package=something version=someversion

All required content will be put in CMAKE_INSTALL_PREFIX adequate subfolders.

You can also control the install folder and build mode directly with this command:

pid install package=something version=someversion folder=/usr/local mode=Debug

Third step : extra system configuration required

Remember that now loading shared object or executables will rely on globaly referenced path. So if CMAKE_INSTALL_PREFIX is NOT a default system install folder you will need to set LD_LIBRARY_PATH and PATH environment variables to make the binaries usable.

Then you also need to set an extra environment variable RUNTIME_RESOURCE_PATH to the value of CMAKE_INSTALL_PREFIX. It is required for some pid generated components that use PID runtime mechanisms.

Finally to ensure that dynamic runtime path will also resolve correctly you have to set the PID_ROOT environment variable to the path of your workspace.

You can set in your .bashrc file to make it permanent, this way:

export RUNTIME_RESOURCE_PATH=/usr/local
export PID_ROOT=<pid-workspace>

That’s it, your package is installed in system.

Fourth step : using package with classic CMake

To use the package libraries in your CMake project you will first need to tell CMake where to find definitions of the deployed packages content by setting the CMAKE_MODULE_PATH variable to the folder pid_cmake where intall command has generated CMake find files. As those files have been generated for a specific install folder, it is more safe to let them here (if you install another version of same package in same place they will be updated) BUT you can copy them in your project.

list(APPEND CMAKE_MODULE_PATH /usr/local/share/pid_cmake)#change with the adequate path to `pid_cmake` folder

Then simply use the CMake find files the usual way:

find_package(something)
if(something_FOUND)
  # do what you want
endif()

Those find files generate a set of imported targets (one per component) that you can link with your code the usual way:

target_link_libraries(yourprogram something::somecomponent)

Simply remmber that each component (say somecomponent) define by a package (say something) can be linked with the target something::somecomponent.

Alternative fourth step : using package with pkg-config

The benefit of a system install compared to a raw pkg-config install is that you will be capable of using runtime mechanisms of PID by setting environment variables in a very common and simple way.

But then by default you must use CMake. If you want to avoid using CMake, you can use pkg-config. Here are the step to follow:

  • configure the profile to generate pkg-config files:
pid cd
pid profiles cmd=add env=pkg-config

Look at the output to know which value to give to PKG_CONFIG_PATH, something that looks like <pid-workspace>/install/<platform>/__pkgconfig__.

  • deploy the package than install in system as previously:
pid deploy package=something version=someversion
pid install package=something version=someversion folder=/usr/local/my-install
export RUNTIME_RESOURCE_PATH=/usr/local/my-install
export PID_ROOT=<pid-workspace>
export LD_LIBRARY_PATH=/usr/local/my-install/lib
export PATH=/usr/local/my-install/bin
  • configure the path to pkg-config files:
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:<pid-workspace>/install/<platform>/__pkgconfig__

From now we just combined the system install with a pkg-config install. The only main difference is to use pkg-config tool in a slightly different way:

  • Getting compilation flags:
#general compilation flags
pkg-config --define-variable=prefix=/usr/local/my-install --static --cflags something_lib
#C standard to use
pkg-config --variable=c_standard something_lib
#C++ standard to use
pkg-config --variable=cxx_standard something_lib
  • Getting linker flags:
pkg-config --define-variable=prefix=/usr/local/my-install --static --libs something_lib

That’s it: you can use pkg-config tool to get all compilation flags you need and their will be no restriction on usage of pid generated libraries.