In previous chapters of this tutorial you learned how to manage a package, now its time to work with many … PID has just been designed for that !

Step 9: Create a new package that depends on the previous one

9.1 Create the new package and its repository

  • Create a new remote repository in gitlab called my-second-package

  • On your workstation create the corresponding package:

cd <pid-worskspace>
pid create package=my-second-package url=git@gite.lirmm.fr:own/my-second-package.git

9.2 Create a library called hello-user

  • create folders containing sources for the new library
cd <my-second-package>
mkdir src/hello_user
mkdir include/hello_user
mkdir include/hello_user/hello
  • write interface of the library by creating and editing the file <my-second-package>/include/hello_user/hello/use_hello.h
#include <vector>
#include <string>
class Helloer{
public:
  Helloer(){};
  ~Helloer(){};
  void memorize(const std::string& message);
  void hello_Everybody();
private:
    std::vector<std::string> messages_;
};

We created a class for objects capable of memorizing and printing a sequence of “hello” sentences.

Remark: Of course you can place as many headers as you want into the target folder (here include/hello_user).

  • write source of the library by creating and editing the file <my-second-package>/src/hello_user/use_hello.cpp
#include <hello.h> #notice that a library providing this header is used. This library is defined in the first package
#include <hello/use_hello.h> #notice that the header is referenced relative to the include folder !
using namespace std;

void Helloer::memorize(const std::string& message){
    messages_.push_back(message);
  }

void Helloer::hello_Everybody(){
  for(int i = 0; i < messages_.size();++i){
    print_Hello(messages_[i]);
  }
}

Remark: Of course you can place as many sources as you want into the target folder (here src/hello_user). They will all be build (and linked if necessary) by the project. For the sake of simplicity we limit the implementation to one file here.

The class implementation is straightforward, we simply define that this code uses the header file hello.h. This header is the interface of the libraries defined in previous package my-first-package. So we need:

  • To tell which library to use in order to resolve include directive for hello.h as well as linking.
  • To tell where to find this library, in other words in which package and for which version this library can be found

  • Defining where to find the library (in which package) is done by editing <my-second-package>/CMakeLists.txt:
...

PID_Dependency(my-first-package VERSION 0.1)

build_PID_Package()

The PID_Dependency (equivalent full signature is declare_PID_Package_Dependency) command simply tells that the current package depends on the package my-first-package with version 0.1.0 or compatible. And equivalent signature could be:

...

declare_PID_Package_Dependency(PACKAGE my-first-package NATIVE VERSION 0.1)

build_PID_Package()

The only utility of PACKAGE keyword is to simply allow to define the name of the dependency not as first argument. The ǸATIVE keyword is useful to precise the nature of the package, here that the dependency is a PID native package. Most of time precising the nature is not necessary since it is automatically deduced.

  • Defining the library to use by editing <my-second-package>/src/CMakeLists.txt:
PID_Component(hello-user SHARED DIRECTORY hello_user
              DEPEND my-first-package/hello-shared)

The expression my-first-package/hello-shared of DEPEND argument explicitly tells that the shared library hello-user depends on hello-shared that can be found in package my-first-package. We recommand to always specify the package name EVEN if it can be omitted most of times since PID is able to find which package defines the target component. Indeed when integrating lots of packages the risk to have component names collision is not zero, and if your dependency is not fully specified it would lead to an error (due to an ambiguity that cannot be automatically resolved).

And equivalent expression, using full signatures:

declare_PID_Component(SHARED_LIB NAME hello-user DIRECTORY hello_user)
declare_PID_Component_Dependency(COMPONENT hello-user NATIVE hello-shared PACKAGE my-first-package)

We chose to use the hello-shared library rather than the static version. The PACKAGE keyword is used to tell where the library is defined. The ǸATIVE keyword is used to specify that the target component is a native one (one defined in a PID package). The result of using this latter expression is exactly the same as the first one.

9.2.1 About C/C++ language standards

In previous exampel we specified no c++ langauge standard for hello-user library. By default the standard used is 98, the original c++ standard. For C code the default standard is 90.

This implicitly means that the build process will force this standard and so if you use for instance c++11 features the build will fail. Indeed PID enforces the use of explicit standards whenever required. So for instance if hello-user code was using c++11 features its description should be:

PID_Component(hello-user SHARED
              CXX_STANDARD 11
              DIRECTORY hello_user
              DEPEND my-first-package/hello-shared)

The standard used in the library is propagated to any library or executable that uses it. So even if this later does not define a c++ standard (and so is declared as being c++98 conformant) in the end it will c++ 11 standard.

The same logic applies for c standard (using the keyword C_STANDARD in component description).

Note: the default C standard is 90 but it is sommon that C code uses c++ style comment (//) instead of C style comment (/* ... */). In this is the case remember to explicitly use the 99 standard of C.

Also it is good practice to ensure that the build environment provides a compiler that supports the targetted C or C++ standard. Indeed, when passing a target standard to a compiler you tell that compiler to use all features of the standard that are currently implemented in the given versio of the compiler: nothing ensure you that the standard features you use are available.

To get this done in a secure way you have to explicit a constraint on the build environment:

PID_Package(...)
#just after description of the package
check_PID_Environment(LANGUAGE CXX[std=11])

By adding the check_PID_Environment command, you tell PID to check that the standard is fully supported by the compiler version and standard library in use. If the check fails, the CMake configuration will fail with an explicit error message.

To know what kind of constraint (like std) a language can support, you can use the info command of the workspace:

pid cd
pid info language=CXX

Output should give something like:

CXX available, possible CONSTRAINTS:
+ optimization
+ soname
+ symbol
+ proc_optimization
+ std
  • symbol, soname and proc_optimization are specific constraints that are used to check that binaries are compatible with current platform and so should never be used by en user.
  • other contraints can be used by the end-user: std chack that toolchain support the given standard, proc_optimization is used to check that a set of otimizations are available for the target platform processor and optimization does the same as optimization and in addition ensure that these optimizations will be automatically used for the build.

9.3 Create an application called app-hello-user

  • create the folder containing sources for a new example:
mkdir <my-second-package>/apps/user_user
  • edit the content of the new app:
#include <hello/use_hello.h>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]){
    if(argc == 1){
    	cout<<"No argument passed ... error !! Please input as many message as you want to print"<<endl;
    	return 1;
    }
    Helloer h;
    int tot = argc-1;
    for(int i = 0 ; i < tot; ++i){
      h.memorize(argv[i]);
    }
    h.hello_Everybody();
    return 0;
}
  • defining the application by editing <my-second-package>/apps/CMakeLists.txt
PID_Component(APP NAME app-hello-user DIRECTORY user_user
              DEPEND hello-user)
  • Building the package and running
cd <my-second-package>
pid build
#wait the build to finish
cd <pid-workspace>/install/my-second-package/0.1.0/bin
./app-hello-user "one time" "second time" "last time"

The console should print :

Not Hello one time
Not Hello second time
Not Hello last time

Step 10 : Publishing the package

10.1 Save the current state

cd <my-second-package>
git add -A
git commit -m "first functional state"

10.2 Release the package version

cd <pid-worskspace>
pid release package=my-second-package

Remark: Remember that you need to release a first version before registering.

10.3 Register the package

cd <pid-worskspace>
pid register package=my-second-package

Step 11 : Redeploying automatically the packages

The goal of this last section is to demonstrate how PID can automatically deploy packages that have been previously registered.

11.1 Remove packages from your local worskpace

cd <pid-worskspace>
pid remove package=my-second-package
pid remove package=my-first-package

This command will remove any trace of both packages in your workspace. Now you can reinstall them as if your were using them for the first time.

11.2 Reinstall the second package

cd <pid-worskspace>
pid deploy package=my-second-package

The PID mechanism launches a complete download, build and install procedure. At the end you shoud see either an error or something like “All packages deployed during this process : “ followed by a list of packages. Here you should see your two packages.

PID mechanism has no limit in terms of recursive search and deploy procedure so you can put in place a complex deployment with packages depending on packages depending on packages, etc.