![]() |
![]() |
![]() |
![]() |
In this section, we'll take a look at the supplementary files used in the QNX development environment. Although we use the "standard" make command to create libraries and executables, you'll notice some "unconventional" conventions used in the Makefile syntax.
We'll start with a general description of a full, multiplatform source tree. Then we'll look at how you can build a tree for your products. Finally, we'll wrap up with a discussion of some advanced topics, including collapsing unnecessary levels and performing partial builds.
Although you're certainly not obliged to use our format for the directory structure and related tools, you may choose to use it because it's convenient for developing multiplatform code.
Here's a sample directory tree for a product that can be built for two different operating systems (QNX 4 and QNX 6), on five CPU platforms (x86, MIPS, PowerPC, ARM, and SH4), with both endian combinations on the MIPS and PowerPC:
Source tree for a multiplatform project.
We'll talk about the names of the directory levels shortly. At each directory level is a Makefile file used by the make utility to determine what to do in order to "make" the final executable.
However, if you examine the makefiles, you can see that most of them simply contain:
include recurse.mk
Why do we have makefiles at every level? Because make can recurse into the bottommost directory level (the "Variant" level in the diagram). That's where the actual "work" of building the product occurs. This means that you could type make at the topmost directory, and it would go into all the subdirectories and compile everything. Or you could type make from a particular point in the tree, and it would compile only what's needed from that point down.
We'll discuss how to cause make to compile only certain parts of the source tree, even if invoked from the top of the tree, in the "Advanced topics" section.
![]() |
When deciding where to place source files, as a rule of thumb you should place them as high up in the directory tree as possible. This not only reduces the number of directory levels to traverse when looking for source, but also encourages you to develop source that's as generic (i.e. non-OS, non-CPU, and non-board-specific) as possible. Lower directory levels are reserved for more and more specific pieces of source code. |
If you look at the source tree that we ship, you'll notice that we follow the directory structure defined above, but with a few shortcuts. We'll cover those shortcuts in the "Advanced Topics" section.
As mentioned earlier, the makefile structure is almost identical regardless of the "level" that the makefile is found in. All makefiles (except the bottommost level) include the recurse.mk file and may set one or more macros.
Here's an example of one of our standard (non-bottommost) Makefiles:
LATE_DIRS=boards include recurse.mk
The recurse.mk file resides under /usr/include/mk. This directory contains other "include" files that are included within makefiles. Note that while the make utility automatically searches /usr/include, we've created symbolic links from there to /usr/include/mk.
The recurse.mk include file is typically used by higher-level makefiles to recurse into lower-level makefiles. All subdirectories present are scanned for files called "Makefile." Any subdirectories that contain such files are recursed into, then make is invoked from within those directories, and so on, down the directory tree.
The special filename Makefile.dnm ("dnm" stands for "Do Not Make") can be placed next to a "real" Makefile to cause recurse.mk to not descend into that directory. The contents of Makefile.dnm aren't examined in any way -- the file can be created empty via touch.
The example given above makes use of the LATE_DIRS macro. Here are the macros that can be placed within a makefile:
To give you some control over the ordering of the directories, the macros EARLY_DIRS and LATE_DIRS specify directories to be recursed into before or after all others. You'd use this facility with directory trees that contain one directory that depends on another directory at the same level -- you want the independent directory to be done first, followed by the dependent directory.
In our example above, we've specified a LATE_DIRS value of boards, because the boards directory depends on the library directory (lib).
Note that the EARLY_DIRS and LATE_DIRS macros will accept a list of directories. The list is treated as a group, with no defined ordering within that group.
The LIST macro serves as a tag for the particular directory level that the makefile is found in.
Here are the common values corresponding to the directory levels:
Note that you're free to define whatever values you wish -- these are simply conventions that we've adopted for the three directory levels specified. See the section on "More uses for LIST" below.
Once the directory has been identified via a tag in the makefile, you can specifically exclude or include the directory and its descendents in a make invocation. See "Performing partial builds" below.
Let's look at the directory levels themselves in some detail. Note that you can add as many levels as you want above the levels described here -- these levels would reflect your "product." For example, in a factory automation system, the product would consist of the entire system -- you would then have several subdirectories under that directory level to describe various projects within that product (e.g. gui, pidloop, robot_plc, etc.).
The project level directory is used mainly to store the bulk of the source code and other directories. These directories would be structured logically around the project being developed. For our factory automation example, a particular project level might be the gui directory, which would contain the source code for the graphical user interface as well as further subdirectories.
The section level directory is used to contain the source base relevant to a part of the project. It may be omitted if not required; see the section on "Collapsing unnecessary directory levels" below.
If you were building products to run on multiple operating systems, you'd include an OS level directory structure. This would serve as a branchpoint for OS-specific subdirectories. In our factory floor example, the gui section might be built for both QNX 4 and QNX 6, whereas the other sections might be built just for QNX 6.
If no OS level is detected, QNX 6 is assumed.
Since we're building executables and libraries for multiple platforms, we need a place to serve as a branchpoint for the different CPUs. Generally, the CPU level would contain nothing but subdirectories for the various CPUs, but it may also contain CPU-specific source files.
Finally, the variant level contains object, library, or executable files specific to a particular variant of the processor. For example, a MIPS processor could operate in big-endian or little-endian mode. In that case, we'd have to generate two different sets of output modules. On the other hand, an x86 processor is a little-endian machine only, so we'd need to build only one set of output modules.
At the project level, there's a file called common.mk. This file contains any "special" flags and settings that need to be in effect in order to compile and link.
At the bottommost level (the variant level), the format of the makefile is different -- it does not include recurse.mk but instead includes common.mk (from the project level).
The common.mk include file is where the "traditional" makefile options go, such as compiler options, etc.
In order for the common.mk makefile to be able to determine which system to build the particular objects, libraries, or executables for, we analyze the pathname components in the bottommost level in reverse order as follows:
The assignment isn't done if that particular macro already has something assigned to it. See the section on "Collapsing unnecessary directory levels" below for an example of where we've already assigned some macros.
For example, if we had a pathname of /source/factory/robot_plc/driver/nto/mips/o.be, then the macros would get assigned as follows:
Macro | Value |
---|---|
COMPOUND_VARIANT | o.be |
CPU | mips |
OS | nto |
SECTION | driver |
PROJECT | robot_plc |
The variant-level makefile (i.e. the bottommost makefile in the tree) contains the single line:
include ../../common.mk
The number of ../ components must be correct to get at the common.mk include file, which resides in the project level of the tree. The reason that the number of ../ components isn't necessarily the same in all cases has to do with whether directory levels are being collapsed.
The common makefiles are triggered by a number of distinguished variant names:
Variant names can be placed in any order in the compound variant, but to avoid confusing a source configuration management tool (e.g. CVS), make sure that the last variant in the list never looks like a generated file suffix. In other words, don't use variant names ending in .a, .so, or .o.
The following table lists some examples:
Variant | Purpose |
---|---|
g.le | A debugging version of a little-endian executable. |
so.be | A big-endian version of a shared object. |
403.be | A user-defined "403" variant for a big-endian system. |
In order for the source code to tell what variant(s) it's being compiled for, the common makefiles will arrange for each variant name to be postfixed to the string VARIANT_ and have that defined as a C or assembler macro on the command line. For example, if the compound variant is so.403.be, the following C macros would be defined: VARIANT_so, VARIANT_403, and VARIANT_be. Note that neither VARIANT_be nor VARIANT_le will be defined on a CPU that doesn't support bi-endian operation, so any endian-specific code should always test for the C macros __LITTLEENDIAN__ or __BIGENDIAN__ (instead of VARIANT_le or VARIANT_be) to determine what endianness it's running under.
We've described the pieces you'll provide when building your system, including the common.mk include file. There are two other include files to discuss:
We'll also look at some of the macros that are set or used by those include files.
Since the common makefiles have a lot of defaults based on the names of various directories, you can simplify your life enormously in the common.mk include file if you choose your directory names to match what the common makefiles want. For example, if the name of the project directory is the same as the name of the image, you don't have to set the NAME macro in common.mk.
The proto-typical common.mk file looks like this:
ifndef QCONFIG QCONFIG=qconfig.mk endif include $(QCONFIG) # Preset make macros go here include $(MKFILES_ROOT)/qtargets.mk # Post-set make macros go here
The qconfig.mk include file provides the root paths to various install, and usage trees on the system, along with macros that define the compilers and some utility commands that the makefiles use. The purpose of the qconfig.mk include file is to allow a user to tailor the root directories, compilers, and commands used at their site, if they differ from the standard ones that we use and ship. Therefore, nothing in a project's makefiles should refer to a compiler name, absolute path, or command name directly. Always use the qconfig.mk macros.
The qconfig.mk file resides in /usr/include/mk as qconf-os.mk (where os is the host OS, e.g. nto, qnx4, solaris, NT), which is a symbolic link from the place where make wants to find it (namely /usr/include/qconfig.mk). You can override the location of the include file by specifying a value for the QCONFIG macro.
Before including qtargets.mk, some macros need to be set to determine things like what additional libraries need to be searched in the link, the name of the image (if it doesn't match the project directory name), and so on. This would be done in the area tagged as "Preset make macros go here" in the sample above.
Following the include of qtargets.mk, you can override or (more likely) add to the macros set by qtargets.mk. This would be done in the area tagged as "Post-set make macros go here" in the sample above.
Here's a summary of the macros available from qconfig.mk:
The which parameter can be either the string HOST for compiling something for the host system or a triplet of the form os_cpu_compiler to specify a combination of target OS and CPU, as well as the compiler to be used.
The os would usually be the string nto to indicate QNX 6. The cpu would be one of x86, mips, ppc, arm or sh. Finally, the compiler would be one of gcc.
For example, the macro CC_nto_x86_gcc would be used to specify:
The following macro would contain the command-line sequence required to invoke the GCC compiler:
CC_nto_x86_gcc = qcc -Vgcc_ntox86 -c
The macros MG_HOST, CP_HOST, LN_HOST, RM_HOST, TOUCH_HOST, and PWD_HOST are used by the various makefiles to decouple the OS commands from the commands used to perform the given actions. For example, under most POSIX systems, the CP_HOST macro expands to the cp utility. Under other operating systems, it may expand to something else (e.g. copy). The MG_HOST macro does nothing at present.
In addition to the macros mentioned above, the following macros can be used to specify options to be placed at the end of the corresponding command lines:
The parameter "which" is the same as defined above: either the string "HOST" or the ordered triplet defining the OS, CPU, and compiler.
For example, specifying the following:
CCPOST_nto_x86_gcc = -ansi
would cause the command line specified by CC_nto_x86_gcc to have the additional string "-ansi" appended after it.
The qrules.mk include file has the definitions for compiling.
The following macros can be set and/or inspected when qrules.mk is used. Since the qtargets.mk file includes qrules.mk, these are available there as well. Those marked "(read-only)" are not to be modified.
Note that for the VFLAG_which, CCVFLAG_which, ASVFLAG_which, and LDVFLAG_which macros, the which part is the name of a variant. This combined macro is passed to the appropriate command line. For example, if there were a variant called "403," then the macro VFLAG_403 would be defined and passed to the C compiler, assembler, and linker.
![]() |
Don't use this mechanism to define a C macro constant that you can test in the source code to see if you're in a particular variant. The makefiles do that automatically for you. Don't set the *VFLAG_* macros for any of the distinguished variant names (listed in the "Recognized variant names" section, above). The common makefiles will get confused if you do. |
The qtargets.mk include file has the linking and installation rules.
The following macros can be set and/or inspected when qtargets.mk is used:
In this section, we'll discuss how to:
The directory structure shown above (in "Structure") defines the "complete" tree -- every possible directory level is shown. In the real world, however, some of these directory levels aren't required. For example, you may wish to build a particular module for a PowerPC in little-endian mode and never need to build it for anything else (perhaps due to hardware constraints). Therefore, it seems a waste to have a variant level that has only the directory o.le and a CPU level that has only the directory ppc.
In this situation, you can collapse unnecessary directory components out of the tree. You do this simply by not defining the directory levels that you don't need, specifying the "missing" components in the bottommost level makefile. For example, in our source tree (/usr/src/nto), let's look at the startup/boards/800fads makefile:
COMPOUND_VARIANT=be CPU=ppc include ../common.mk
In this case, we've specified both the variant (as "be" for big-endian) and the CPU (as "ppc" for PowerPC). Why did we do this? Because the 800fads directory refers to a very specific board -- it's not going to be useful for anything other than a PowerPC running in big-endian mode. Therefore, we didn't need to specify the CPU level nor the variant level -- we completely eliminated those two levels of directory hierarchy.
In this case, the makefile macros would have the following values:
Macro | Value |
---|---|
COMPOUND_VARIANT | be (set) |
CPU | ppc (set) |
OS | nto (default) |
SECTION | 800fads |
PROJECT | boards |
The COMPOUND_VARIANT and CPU macros are marked as "(set)" because they were explicitly defined in the makefile. The OS macro is marked as "(default)" because the makefile rules were able to deduce the target operating system based on the initial components of the pathname (the "/usr/src/nto" part).
By using the LIST tag in the makefile, you can cause the make command to perform a partial build, even if you're at the top of the source tree.
If you were to simply type "make" without having used the LIST tag, all directories would be recursed into and everything would be built.
However, by defining a macro on make's command line, you can cause one of two things to occur:
Or:
Let's consider an example. The following (issued from the top of the source tree):
make CPULIST=x86
will cause only the directories that are at the CPU level and below (and tagged as LIST=CPU), and that are called x86, to be recursed into.
You can specify a space-separated list of directories (note the use of quoting in the shell to capture the space character):
make "CPULIST=x86 mips"
This will cause the x86 and MIPS versions to be built.
There's also the inverse form, which will cause the specific lists to not be built:
make EXCLUDE_CPULIST=ppc
This will cause everything except the PowerPC versions to be built.
As you can see from the above examples, the following are all related to each other via the CPU portion:
Besides using the "standard" LIST values that we use, you can also define your own. Therefore, in certain makefiles, you'd put the following definition:
LIST=CONTROL
Then you can decide to build (or prevent from building) various subcomponents marked with CONTROL. This might be useful in a very big project, where compilation times are long and you need to test only a particular subsection, even though other subsections may be affected and would ordinarily be made.
For example, if you had marked two directories, robot_plc and pidloop, with the LIST=CONTROL macro within the makefile, you could then make just the robot_plc module:
make CONTROLLIST=robot_plc
Or make both (note the use of quoting in the shell to capture the space character):
make "CONTROLLIST=robot_plc pidloop"
Or make everything except the robot_plc module:
make EXCLUDE_CONTROLLIST=robot_plc
Or make only the robot_plc module for MIPS big-endian:
make CONTROLLIST=robot_plc CPULIST=mips VARIANTLIST=be
![]() |
![]() |
![]() |
![]() |