Environments can be used to customize the host system configuration in order to be capable of using extra tools. This is what we call the plugins mechanism of PID, because it allows to add new automatic behaviors into native packages in such a way that they can use those developper tools.
Step 1: a first plugin
Structure of a plugin
A plugin is defined using an environment, so their creation follow same rules and principles. So let’s suppose we have created an environment called pkg-config that provides a plugin fo the well known pkg-config tool. We simply want to generate pkg-config configuration files for the packages generated into the workspace and check that the tool itself is installed in host platform.
Here is the general project description:
Nothing really special in this description except the INFO argument of PID_Environment. This is useful to give a description of usage for the plugin. The text will be interpreted by a CMake generator so you can use surrounding @ tags to specify cmake variables that will finally be replaced by their values when text is printed. Is is printed anytime the workspace will be configured to use the plugin.
Writing the check script
The check script src/check_pkg-config.cmake is quite simple, it simply tests that the pkg-config tool is installed in host and, like for the gcc_toolchain environment it is used to check version constraint.
The main different with the previous tutorial is that we do not use predefined CMake variables since pkg-config is not a default tool in CMake (simply because it is not related to a programming language used in a build process).
Writing the configuration script
The configuration script src/configure_pkg-config.cmake looks like:
Again we can see than we use same calls than for an environment used to define new toolchains:
host_Match_Target_Platform verify that host and targets are compatible.
evaluate_Host_Platform calls the check script, and so is able to detect pkg-config tool in host platform.
install_System_Packages is used to install the tool if not found. Notice that here we provide an install procedure for host systems providing apt (debian like) or pacman (archlinux derivatives) packagers. More can be later defined for other package manager in order to adapt to various operating systems.
The main difference when writing a plugin is in the call to configure_Environment_Tool:
the EXTRA argument is used to tell that the environment generates an host configuration for an extra tool (not natively supported by PID).
the PROGRAM argument is used to specify the path to this extra tool. In this example the PKG_CONFIG_EXECUTABLE variable has been generated by the call to find_package in the check script.
the PLUGIN argument is used to specify script(s) that define custom behaviors to be added to default package behavior in order to make them capable of using the extra tool. Up to 4 scripts can be defined using the following arguments:
BEFORE_DEPS: the script that executes before any dependency is resolved into the package. For instance usefull if your plugin need to add some extra dependencies.
BEFORE_COMPS: the script that executes before any component is defined. For instance usefull if your plugin need to create components.
DURING_COMPS: the script that executes during each component definition. For instance usefull if your plugin need to set properties to components.
AFTER_COMPS: the script that executes after all component have been defined. For instance usefull if your plugin need to have a full description of the package to execute.
Since pkg-config tool is used to manage libraries at compile and link time, and since this plugin is used to generate the configuration files it uses for each library in a package, we need to define a AFTER_COMPS script to be sure that we get a complete information on libraries generated by the package.
Writing plugin scripts
So now let’s have a look at the difficult part: writing plugin scripts. Indeed these scripts implement the new behavior we want to add to PID and so it requires a deeper level of understanding on how PID works. That is why such kind of environment should be reserved to experienced users.
Here is the content of src/use_pkg-config.cmake script:
As you may see the code is quite complex. It consists in getting internal information about the package using dedicated plugin API in order to generate a pkg-config file for each library defined in the package and its dependencies. Functions of this API are for instance list_Component_Direct_Internal_Dependencies, list_Component_Direct_Native_Package_Dependencies, get_Package_Component_Target, get_Package_Version, etc. As any other CMake script you can define functions and macros to ease the description, as well as any other CMake command, like configure_file and install commands that are useful in this example to generate and install pkg-config files (look at the command install_Pkg_Config_File for instance).
You can also notice that it is possible to add extra files that can be found in the environemnt folder. Here the script uses the file src/pkg_config.pc.pre.in as a pattern of pkg-config file that is then adapted using configure_file. To do this the function get_Path_To_Environment is able to retrieve the path to the given environment making it possible to get access to some of its content.
Build the environment
Now, to test evaluation of the environemnt, we simply do as usual:
If everything works as expected, the output looks like:
It sepcifies that the executable pkg-config has been found on host and plugin call back has been registered.
Test the environment
In order to trully test the plugin you need to directly see its effects on packages. To do so you need to configure the workspace with a profile and then reconfigure a package to know what is the effect of a plugin, which make them a bit difficult to test.
Anyway we advise to use simple packages, like pid-rpath to test effects of a plugin, before testing with more complex packages.
Step 2: example of a plugin that provides new language support
Plugins can be used for many differents things, its up to developpers to decide what kind of behavior they want to add to PID.
In the following example we will show, with the f2c environment, how to deal with code generators, that is a quite common type of tool.
f2c is a code generator that transforms Fortran code into C code and that is useful if no fortran compiler is available on host platform for generating binary code for the target platform (that may be host itself).
Structure the plugin
Environment for f2c is created and described as usual:
As you can see it is very similar to the environment for pkg-config and nothing is special here. Simply notice that we defined no version constraint for the environment, simply because we consider that the f2c tool is now really stable and available versions are capable of generating code for any Fortran code.
Writing the check script
The check script src/check_f2c.cmake simply consists in checking if f2c tool is installed in host:
Writing the configuration script
The configuration script src/configure_f2c.cmake is also very simple:
The same pattern as for pkg-config configuration script is followed. The only specific aspect here is in the configure_Environment_Tool function:
the PLUGIN argument defines a script that is flagged as DURING_COMPS which means that the plugin script will execute just at beginning of the definition of each component. This is a quite common way of doing for code generators as the script needs to look into the component sources to check files with given file extensions (here fortran extensions) in order to be capable of generating C/C++ code from these files.
the ON_DEMAND argument specifies that the plugin will execute only if it is explicitly required by the package. This is also a common requirement for code generators. Indeed, those tools suppose that there are files with specific extensions among the source files of components, so a package that uses a code generator should always specify it using the check_PID_Environment function. For f2c, it is a bit more complex because the f2c tool has to be used only if no Fortran compiler is available so packages have to do a double check: first testing is Fortran language is available then if not requiring the use of f2c.
the CONFIGURATION argument is used to require, at package level, the use of a specific system configuration for target platform. In this example f2c requires f2c-libs, the libraries used to link C code generated by f2c tool. This is also a quite common case for code generators as they try to minimize the amount of generated code and so use intermediate functions provided by libraries.
Writing plugin scripts
So now let’s have a look at the script src/use_f2c.cmake:
To understand this script remmeber that:
it executes at beginning of each component definition
it generates C code from Fortran code
it only executes if the f2c environment has been explicitly required at package level.
The first action consists in testing if the Fortran language is available in current configuration of the workspace, using is_Language_Available function. If yes, then there is no need for the script to execute and even this could lead to bugs since the Fortran code could be used twice: first time directly by the Fortran compiler and second time with f2c. In this case the script simply removes the C files generated from Fortran files in a previous execution of f2c. Indeed we never know if the workspace configuration has changed in the meantime, now providing a compiler for Fortran, so we need to clean the component sources from generated code to avoid any trouble:
get_Current_Component_Files return the list of source files filtered with given extensions
convert_Files_Extensions is an utility that simply converts a list of file to a list of files with another extension.
remove_Residual_Files removes a list of files from component sources.
If Fortran is not supported then f2c can be used. So first thing to do is to get access to the f2c executable which is achieved by using the get_Environment_Configuration: the code generator can now be used from CMake code.
The use of is_First_Package_Configuration is for optimization, just to avoid regenerating the C code when the package in build in release and debug modes at same time (i.e. using the build command with default options).
The code is generated in the component sources folder using generate_Code_In_Place: the program F2C_EXE is called on all Fortran sources.
Finally, the component is configured to link with the libraries provided by f2c-libs system configuration using configure_Current_Component.
Build the environment
Now, to test evaluation of the environment, simply do:
If everything works as expected, the output looks like:
Like for pkg-config it sepcifies that the executable for f2c has been found on host and plugin call back has been registered. The only difference is that there is also the requirement that target platform satisfies f2c-libs system configuration (i.e. that f2c libraries are installed in system).