Configuration options can affect a build in two main ways. First, enabling a configuration option or other CDL entity can result in various files being built and added to a library, thus providing functionality to the application code. However this mechanism can only operate at a rather coarse grain, at the level of entire source files. Hence the component framework also generates configuration header files containing mainly C preprocessor #define directives. Package source code can then #include the appropriate header files and use #if, #ifdef and #ifndef directives to adapt accordingly. In this way configuration options can be used to enable or disable entire functions within a source file or just a single line, whichever is appropriate.
The configuration header files end up in the include/pkgconf subdirectory of the install tree. There will be one header file for the system as a whole, pkgconf/system.h, and there will be additional header files for each package, for example pkgconf/kernel.h. The header files are generated when creating or updating the build and install trees, which needs to happen after every change to the configuration.
The component framework processes each package in the configuration one at a time. The exact order in which the packages are processed is not defined, so the order in which #define's will end up in the global pkgconf/system.h header may vary. However for any given configuration the order should remain consistent until packages are added to or removed from the system. This avoids unnecessary changes to the global header file and hence unnecessary rebuilds of the packages and of application code because of header file dependency handling.
Within a given package the various components, options and interfaces will be processed in the order in which they were defined in the corresponding CDL scripts. Typically the data in the configuration headers consists only of a sequence of #define's so the order in which these are generated is irrelevant, but some properties such as define_proc can be used to add arbitrary data to a configuration header and hence there may be dependencies on the order. It should be noted that re-parenting an option below some other package has no effect on which header file will contain the corresponding #define: the preprocessor directives will always end up in the header file for the package that defines the option, or in the global configuration header.
There are six properties which affect the process of generating header files: define_header, no_define, define_format, define, if_define, and define_proc.
The define_header property can only occur in the body of a cdl_package command and specifies the name of the header file which should contain the package's configuration data, for example:
cdl_package <some_package> { … define_header xyzzy.h } |
Given such a define_header property the component framework will use the file pkgconf/xyzzy.h for the package's configuration data. If a package does not have a define_header property then a suitable file name is constructed from the package's name. This involves:
All characters in the package name up to and including the first underscore are removed. For example CYGPKG_KERNEL is converted to KERNEL, and CYGPKG_HAL_ARM is converted to HAL_ARM.
Any upper case letters in the resulting string will be converted to lower case, yielding e.g. kernel and hal_arm.
A .h suffix is appended, yielding e.g. kernel.h and hal_arm.h.
Because of the naming restrictions on configuration options, this should result in a valid filename. There is a small possibility of a file name class, for example CYGPKG_PLUGH and CYGPKG_plugh would both end up trying to use the same header file pkgconf/plugh.h, but the use of lower case letters for package names violates the naming conventions. It is not legal to use the define_header property to put the configuration data for several packages in a single header file. The resulting behaviour is undefined.
Once the name of the package's header file has been determined and the file has been opened, the various components, options and interfaces in the package will be processed starting with the package itself. The following steps are involved:
If the current option or other CDL entity is inactive or disabled, the option is ignored for the purposes of header file generation. #define's are only generated for options that are both active and enabled.
The next step is to generate a default #define for the current option. If this option has a no_define property then the default #define is suppressed, and processing continues for define, if_define and define_proc properties.
The header file appropriate for the default #define is determined. For a cdl_package this will be pkgconf/system.h, for any other option this will be the package's own header file. The intention here is that packages and application code can always determine which packages are in the configuration by #include'ing pkgconf/system.h. The C preprocessor lacks any facilities for including a header file only if it exists, and taking appropriate action otherwise.
For options with the flavors bool or none, a single #define will be generated. This takes the form:
#define <option> 1 |
For example:
#define CYGFUN_LIBC_TIME_POSIX 1 |
Package source code can check whether or not an option is active and enabled by using the #ifdef, #ifndef or #if defined(…)directives.
For options with the flavors data or booldata, either one or two #define's will be generated. The first of these may be affected by a define_format property. If this property is not defined then the first #define will take the form:
#define <option> <value> |
For example:
#define CYGNUM_LIBC_ATEXIT_HANDLERS 32 |
Package source code can examine this value using the #if directive, or by using the symbol in code such as:
for (i = 0; i < CYGNUM_LIBC_ATEXIT_HANDLERS; i++) { … } |
It must be noted that the #define will be generated only if the corresponding option is both active and enabled. Options with the data flavor are always enabled but may not be active. Code like the above should be written only if it is known that the symbol will always be defined, for example if the corresponding source file will only get built if the containing component is active and enabled. Otherwise the use of additional #ifdef or similar directives will be necessary.
If there is a define_format property then this controls how the option's value will appear in the header file. Given a format string such as %08x and a value 42, the component framework will execute the Tcl command format %08x 42 and the result will be used for the #define's value. It is the responsibility of the component writer to make sure that this Tcl command will be valid given the format string and the legal values for the option.
In addition a second #define may or may not be generated. This will take the form:
#define <option>_<value> |
For example:
#define CYGNUM_LIBC_ATEXIT_HANDLERS_32 |
The #define will be generated only if it would result in a valid C preprocessor symbol. If the value is a string such as "/dev/ser0" then the #define would be suppressed. This second #define is not particularly useful for numerical data, but can be valuable in other circumstances. For example if the legal values for an option XXX_COLOR are red, green and blue then code like the following can be used:
#ifdef XXX_COLOR_red … #endif #ifdef XXX_COLOR_green … #endif #ifdef XXX_COLOR_blue … #endif |
The expression syntax provided by the C preprocessor is limited to numerical data and cannot perform string comparisons. By generating two #define's in this way it is possible to work around this limitation of the C preprocessor. However some care has to be taken: if a component writer also defined a configuration option XXX_COLOR_green then there will be confusion. Since such a configuration option violates the naming conventions, the problem is unlikely to arise in practice.
For some options it may be useful to generate one or more additional #define's or, in conjunction with the no_define property, to define a symbol with a name different from the option's name. This can be achieved with the define property, which takes the following form:
define [-file=<filename>] [-format=<format>] <symbol> |
For example:
define FOPEN_MAX |
This will result in something like:
#define FOPEN_MAX 8 #define FOPEN_MAX_8 |
The specified symbol must be a valid C preprocessor symbol. Normally the #define will end up in the same header file as the default one, in other words pkgconf/system.h in the case of a cdl_package, or the package's own header file for any other option. The -file option can be used to change this. At present the only legal value is system.h, for example:
define -file=system.h <symbol> |
This will cause the #define to end up in the global configuration header rather than in the package's own header. Use of this facility should be avoided since it is very rarely necessary to make options globally visible.
The define property takes another option, -format, to provide a format string.
define -format=%08x <symbol> |
This should only be used for options with the data or booldata flavor, and has the same effect as the define_format property has on the default #define.
define properties are processed in the same way the default #define. For options with the bool or none flavors a single #define will be generated using the value 1. For options with the data or booldata flavors either one or two #define's will be generated.
After processing all define properties, the component framework will look for any if_define properties. These take the following form:
if_define [-file=<filename>] <symbol1> <symbol2> |
For example:
if_define CYGSRC_KERNEL CYGDBG_USE_ASSERTS |
The following will be generated in the configuration header file:
#ifdef CYGSRC_KERNEL # define CYGDBG_USE_ASSERTS #endif |
Typical kernel source code would begin with the following construct:
#define CYGSRC_KERNEL 1 #include <pkgconf/kernel.h> #include <cyg/infra/cyg_ass.h> |
The infrastructure header file cyg/infra/cyg_ass.h only checks for symbols such as CYGDBG_USE_ASSERTS, and has no special knowledge of the kernel or any other package. The if_define property will only affect code that defines the symbol CYGSRC_KERNEL, so typically only kernel source code. If the option is enabled then assertion support will be enabled for the kernel source code only. If the option is inactive or disabled then kernel assertions will be disabled. Assertions in other packages are not affected. Thus the if_define property allows control over assertions, tracing, and similar facilities at the level of individual packages, or at finer levels such as components or even single source files if desired.
Note: Current eCos packages do not yet make use of this facility. Instead there is a single global configuration option CYGDBG_USE_ASSERTS which is used to enable or disable assertions for all packages. This issue should be addressed in a future release of the system.
As with the define property, the if_define property takes an option -file with a single legal value system.h. This allows the output to be redirected to pkgconf/system.h if and when necessary.
The final property that is relevant to configuration header file generation is define_proc. This takes a single argument, a Tcl fragment that can add arbitrary data to the global header pkgconf/system.h and to the package's own header. When the define_proc script is invoked two variables will be set up to allow access to these headers: cdl_header will be a channel to the package's own header file, for example pkgconf/kernel.h; cdl_system_header will be a channel to pkgconf/system.h. A typical define_proc script will use the Tcl puts command to output data to one of these channels, for example:
cdl_option <name> { … define_proc { puts $::cdl_header "#define XXX 1" } } |
Note: In the current implementation the use of define_proc is limited because the Tcl script cannot access any of the configuration data. Therefore the script is limited to writing constant data to the configuration headers. This is a major limitation which will be addressed in a future release of the component framework.
Note: Generating C header files with #define's for the configuration data suffices for existing packages written in some combination of C, C++ and assembler. It can also be used in conjunction with some other languages, for example by first passing the source code through the C preprocessor and feeding the result into the appropriate compiler. In future versions of the component framework additional programming languages such as Java may be supported, and the configuration data may also be written to files in some format other than C preprocessor directives.
Note: At present there is no way for application or package source code to get hold of all the configuration details related to the current hardware. Instead that information is spread over various different configuration headers for the HAL and device driver packages, with some of the information going into pkgconf/system.h. It is possible that in some future release of the system there will be another global configuration header file pkgconf/hardware.h which either contains the configuration details for the various hardware-specific packages or which #include's all the hardware-specific configuration headers. The desirability and feasibility of such a scheme are still to be determined. To avoid future incompatibility problems as a result of any such changes, it is recommended that all hardware packages (in other packages containing the hardware property) use the define_header property to specify explicitly which configuration header should be generated.
Typically configuration header files are #include'd only by the package's source code at build time, or by a package's exported header files if the interface provided by the package may be affected by a configuration option. There should be no need for application code to know the details of individual configuration options, instead the configuration should specifically meet the needs of the application.
There are always exceptions. Application code may want to adapt to configuration options, for example to do different things for ROM and RAM booting systems, or when it is necessary to support several different target boards. This is especially true if the code in question is really re-usable library code which has not been converted to an eCos package, and hence cannot use any CDL facilities.
A major problem here is determining which packages are in the configuration: attempting to #include a header file such as pkgconf/net.h when it is not known for certain that that particular package is part of the configuration will result in compilation errors. The global header file pkgconf/system.h serves to provide such information, so application code can use techniques like the following:
#include <pkgconf/system.h> #ifdef CYGPKG_NET # include <pkgconf/net.h> #endif |
This will compile correctly irrespective of the eCos configuration, and subsequent code can use #ifdef or similar directives on CYGPKG_NET or any of the configuration options in that package.
In addition to determining whether or not a package is present, the global configuration header file can also be used to find out the specific version of a package that is being used. This can be useful if a more recent version exports additional functionality. It may also be necessary to adapt to incompatible changes in the exported interface or to changes in behaviour. For each package the configuration system will typically #define three symbols, for example for a V1.3.1 release:
#define CYGNUM_NET_VERSION_MAJOR 1 #define CYGNUM_NET_VERSION_MINOR 3 #define CYGNUM_NET_VERSION_RELEASE 1 |
There are a number of problems associated with such version #define's. The first restriction is that the package must follow the standard naming conventions, so the package name must be of the form xxxPKG_yyy. The three characters immediately preceding the first underscore must be PKG, and will be replaced with NUM when generating the version #define's. If a package does not follow the naming convention then no version #define's will be generated.
Assuming the package does follow the naming conventions, the configuration tools will always generate three version #define's for the major, minor, and release numbers. The symbol names are obtained from the package name by replacing PKG with NUM and appending _VERSION_MAJOR, _VERSION_MINOR and _VERSION_RELEASE. It is assumed that the resulting symbols will not clash with any configuration option names. The values for the #define's are determined by searching the version string for sequences of digits, optionally preceded by a minus sign. It is possible that some or all of the numbers are absent in any given version string, in which case -1 will be used in the #define. For example, given a version string of V1.12beta, the major version number is 1, the minor number is 12, and the release number is -1. Given a version string of beta all three numbers would be set to -1.
There is special case code for the version current, which typically corresponds to a development version obtained via anonymous CVS or similar means. The configuration system has special built-in knowledge of this version, and will assume it is more recent than any specific release number. The global configuration header defines a special symbol CYGNUM_VERSION_CURRENT, and this will be used as the major version number when version current of a package is used:
#define CYGNUM_VERSION_CURRENT 0x7fffff00 ... #define CYGNUM_INFRA_VERSION_MAJOR CYGNUM_VERSION_CURRENT #define CYGNUM_INFRA_VERSION_MINOR -1 #define CYGNUM_INFRA_VERSION_RELEASE -1 |
The large number used for CYGNUM_VERSION_CURRENT should ensure that major version comparisons work as expected, while still allowing for a small amount of arithmetic in case that proves useful.
It should be noted that this implementation of version #define's will not cope with all version number schemes. However for many cases it should suffice.