Tutorial - Management of runtime resources
One important but too often forgotten aspect when managing software is how to manage files and folders at runtime. For instance, we often need to find a configuration file in filesystem and basically we either write the path to these files directly into the code, or we need to manage parameters of a program to let the user providing path to the application. Both technics suffers limitation: the first one is not relocatable and requires to modify source code simply to change path according to the workstation filesystem ; the second may be unusable in practice if you have many path to handle.
To overcome these problems, PID is provided with a mechanism to easily manage relocatable runtime resources. By runtime resource we mean any kind of file, or folder, that can be found on a filesystem at runtime (when binary code is executed).
Let’s see how it works with the package my-first-package
defined in previous tutorial !
Step 1: Identify and create required runtime resources
Let’s suppose the application hello-app
needs to read a specific file at a given moment of its execution. This file is named hello_data.txt
.
In every native PID package there is a specific share/resources
folder. It is used to contain all runtime resources required by the components of the package. So we simply create the file hello_data.txt
in this folder:
That’s it, the file hello_data.txt
is now part of the package:
- it will be installed in a dedicated place everytime
my-first-package
is built. The install folder of the package version contains ashare/resources
subfolder that itself containshello_data.txt
. - it can be updated and commited as any other file of the project so in the end it is easy to release new version of runtime resources together with new version of the code that use them.
For now we just have a file whose lifecycle is bound to the lifecycle of component that will use it, but nothing more so we are still far from being able to write C++ code without boring with filesystem path.
Step 2: Register runtime resource into components
Now we have to configure the application hello-app
so that it will be able to find hello_data.txt
in any situation. This is achieved by modifying a bit the description of hello-app
in apps/CMakeLists.txt
.
Using RUNTIME_RESOURCES
argument of PID_Component
allows to specify which runtime resources of the package a component is using at runtime. In the example, hello-app
simply uses hello_data.txt
. Just remember that all path are expressed relative to the share/resources
folder.
For now nothing in the code of hello-app
refers to this runtime resource so we do not need to do more. Simply remember that runtime resources of a component are accessible to any component that uses (i.e. depends on) it. So we can imagine to create components that provide runtime resources to other components without using them directly. We typically use this scheme to provide some configuration files with libraries in order for instance to define alternative possible configuration of the library that may be used by a third party component.
Step 3: use the runtime resource mechanism in your code
But for now we keep things simple, and we just want to directly read hello_data.txt
content during execution.
3.1 Write the code
First edit the file named hello_main.cpp
in apps/hello
folder and paste the following code:
Now instead of printing something depending on an explicit input of the user, the code prints “hello” depending on the content of hello_data.txt
. Important part in code are:
#include <pid/rpath.h>
indicates that the code uses the runtime path resolution API. This API provides some C++ preprocessor macros that are use after.PID_EXE(argv[0]);
is used to configure the runtime resource resolution system. Call toPID_EXE
is mandatory only if you work with old GNU GCC or clang versions, or with other compilers than gcc or clang. Otherwise you can omit this call because it will be automatically placed into static section of the excutable (i.e. will be executed before main).PID_PATH("hello_data.txt")
is a call to the resolution algorithm and finally returns a canonical path to thehello_data.txt
file, that can in turn be used. In the example the target file is finally read with a c++ifstream
.
3.2 : Description of the package
We see that for using the runtime resources mechanisms we need a specific c++ API. This API if provided by a library called rpathlib
that is provided by the package called pid-rpath
(for PID runtime path). We need to describe the dependency to pid-rpath
in my-first-package
since its component hello-app
needs to use rpathlib
. In root CMakeLists.txt
of my-first-package
, write:
We simply add the dependency to pid-rpath
as explained in previous tutorials.
Then we need to declare the dependency between component hello-app
and rpathlib
. This is achieved by modifying a bit the description of hello-app
in apps/CMakeLists.txt
:
We simply added the dependency to rpathlib as usual by adding pid-rpath/rpathlib
to the DEPEND
argument. Now the code is ready to be built.
Step 4: Build and run the code
- build the package:
At runtime, the resource resolution mechanism automatically finds the file hello_data.txt
either in install tree (if the hello-app
binary called is in the install tree) or in build tree (if the hello-app
binary called is in the build tree):
- run from install tree:
- run from build tree:
Both should produce the same result something like:
. Now edit hello_data.txt
located in <my-first-package>/share/resources
:
We simply add a new line with the string /lib
, which is a valid path in the filesystem. Now run again both binaries.
- output when run from install tree: nothing change
- output when run from build tree: new output
The difference is due to the fact that when run from build tree the resolution mechanism finds the runtime resource in source tree first, while when run from install tree it finds it in the install tree. So difference is because runtime resource is not synchronized between build and install trees. To resolve this situation simply build the package:
Now for both binaries the result is the same:
Explanation is simple: the resource file has been reinstalled because it was modified in source tree compared to the version in install tree.
Step 5: Working with references on runtime resource that do not exist
Now we will see that is is also possible to declare runtime resources that do not exist in package filesystem. The main utility is to declare virtual places where content will be added. For instance if your code needs to write to a specific file you may not want this file to preexist (either in source or install tree).
5.1 Declare the “virtual” runtime resource
First declare your runtime resource as usual:
We simply added hello_report.txt
file, but we did not create corresponding file in the share/resources
folder of the package.
5.2 Write the code
Then now modify a bit the previous code to make it write this file. Edit the file named hello_main.cpp
in apps/hello
folder and paste the following code:
The code is mostly the same as previously, lined added are marked with comments //ADDED: ...
. PID_PATH
macro is used is used to resolve the path to hello_report.txt
.
5.3 Build and run the code
- build the code:
- run the code : the program
hello-app
should crash on an exception. This situation is normal. Indeed when the runtime mechanism tries to solvePID_PATH("hello_report.txt")
it does not find the corresponding resource (normal because we created none) !
5.4 Modify the source code
The resolution mechanism can handle such situations if they are explicitly specified by the source code. This is achieved by using a specific syntax. Edit the code :
- Replace the line :
- By the line:
-
Then rebuild and run
hello-app
again: it executes correctly and produces adequate output. The filehello_report.txt
now exists:- if your ran
hello-app
from build tree then is has been created in<my-first-package>/share/resources
. So now it lies in the source tree so you have to take care to avoid publishing it with git ! Simply add the file to your.gitignore
rules. This should be done any time a “virtual” resource is defined. - if your ran
hello-app
from install tree then is has been created in<pid-workspace/install/<platform>/my-first-package/0.2.0/share/resources
.
- if your ran
The only thing we changed between both codes has been to add a +
character at the beginning of the path expression. +
has a special meaning for the PID_PATH
macro: it tells the system to resolve the path even if the finally pointed file does not exist yet. To get a detailed description of path expressions that can be used with PID_PATH
macro you can consult this page.
Step 6: Working with folders
This last part of the tutorial now simply explains how to use folder as runtime resources instead of regular files. Indeed most of time it is a good idea to structure runtime resources in folders rather than putting all these files directly into share/resources
folder. Furthermore it tends to reduce collision of resources names between packages.
6.1 Declare a folder as a runtime resource
As an example, configurating a program often requires many files so it is preferable to put all this file into one specific folder and then simply reference the folder as a runtime resource. Something like:
We added hello_config_files
folder to runtime resources declared by the component.
We need to add the corresponding folder in share/resources
folder of the package as well as files in share/resources/hello_config_files
:
6.2 Write and build the code
Then now modify a bit the previous code to make it use this folder. Edit the file named hello_main.cpp
in apps/hello
folder and paste the following code:
We simply added 2 input file streams to read the two files contained in hello_config_files
. Expression will be correctly interpreted by PID_PATH
without generating exceptions because:
hello_config_files
is referenced as a runtime resource for the component so path to it can be resolved.- files
data1.txt
anddata2.txt
exist in this folder.
Simply build again the package and see the result.
6.3 Write the code to allow using non existing files in referenced folders.
It is also possible to use reference folders as containers for files that do not exist at build time. This is even a far better practice that directly writing files in the share/resources folder of a package: let’s suppose your program is generating a big amount of log files, possibly with log files whose name is labelled with a date for instance, then it is a natural way to put all the logs into a specific folder.
- modify the description by referencing a new
hello_logs
folder as a runtime resources:
- create
hello_logs
folder inshare/resources
of the package:
This folder is supposed to be empty by default because there is no logs that is why we added a .gitignore rule excluding all its content from version control.
- modify previous code:
Replace:
By:
- Build and run the code:
Now you can see that a file log.txt
with updated content has been added to the hello_logs
folder.
6.4 Test the resolution without using write access specifier (i.e. +
)
-
Remove the file
share/resources/hello_logs/logs.txt
(in source or install tree dependencing on from where you ran the program). -
In the code:
Replace:
By:
- Build then run the package.
The program now generates an exception because the resolution mechanism does not find the file share/resources/hello_logs/logs.txt
. This is normal because we did not tell the resolution mechanism to allow resource creation by using the +
specifier.
That’s it you now know all the basics of runtime resources !!