Using PID packages from outside of PID
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:
- If you want to use packages from a third party project configured with CMake then use the CMake dedicated API provided by PID.
- Otherwise you may use pkg-config.
- If you do not want to use a PID workspace, then you have to deploy a workspace generated content into a system folder.
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.
- Create a folder called
extern_project
outside of your PID workspace. - Inside this folder, create 3 subfolders:
build
(build tree),src
(source tree) andinstall
(install tree). This structure is just for demonstration, you can use any kind of project structure. - Create an empty
CMakeLists.txt
file at the root of<extern_project>
- Create 3 empty files:
src/lib.h
,src/lib.cpp
andsrc/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 libraryrpathlib
defined in packagepid-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
ofboost
, 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
withmylib
:
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 allbin
folders of packages.LD_LIBRARY_PATH
must contain path to alllib
folders of packages.RUNTIME_RESOURCE_PATH
must contain path to allshare/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 sysinstall 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 sysinstall 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 install
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 sysinstall 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.