[Previous] [Contents] [Index] [Next]

Appendix: Conventions for Makefiles and Directories

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.

Structure

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:


Figure showing a full source tree


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.


Note:

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.

Makefile structure

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

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.

Macros

The example given above makes use of the LATE_DIRS macro. Here are the macros that can be placed within a makefile:

The EARLY_DIRS and LATE_DIRS macros

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

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.

Directory structure

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

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 (optional)

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.

The OS level

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.

The CPU level

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.

The variant level

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.

Specifying options

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 file

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

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.

Recognized variant names

The common makefiles are triggered by a number of distinguished variant names:

a
The image being built is an object library.
so
The image being built is a shared object. If neither a nor so is present in the compound variant, an executable is being built.
shared
Compiles the object files for .so use, but doesn't create an actual shared object. Typically used in an a.shared variant to create a static link archive that can be linked into a shared object.
g
The source should be compiled and linked with the debugging flag set.
be, le
The source should be compiled and linked to generate big (if be) or little (if le) endian code. If a CPU supports bi-endian operation, one of these variants should always be present in the compound variant name. Conversely, if the CPU is mono-endian, neither be nor le should be specified in the compound variant.
gcc
GCC (gcc) compiler to compile the source. If none is specified, the makefiles will provide a default.
o
This is the NULL variant name. It's used when building an image that doesn't really have any variant components to it (e.g an executable for an x86 CPU, which doesn't support bi-endian operation).

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.

Using the standard macros and include files

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.

The qconfig.mk include file

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.

The "preset" macros

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.

The "post-set" macros

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.

qconfig.mk macros

Here's a summary of the macros available from qconfig.mk:

MG_HOST
Do something to mark a file as generated by a makefile.
CP_HOST
Copy files from one spot to another.
LN_HOST
Create a symbolic link from one file to another.
RM_HOST
Remove files from the filesystem.
TOUCH_HOST
Update a file's access and modification times.
PWD_HOST
Print the full path of the current working directory.
CL_which
Compile and link.
CC_which
Compile C/C++ source to an object file.
AS_which
Assemble something to an object file.
AR_which
Generate an object file library (archive).
LR_which
Link a list of objects/libraries to a relocatable object file.
LD_which
Link a list of objects/libraries to a executable/shared object.
UM_which
Add a usage message to an executable.

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

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.

COMPOUND_VARIANT
A dot-separated list of variant names. Defaults to the name of the current working directory with all parent directories stripped off (e.g. basename() of the current working directory).
VARIANTS (read-only)
A space-separated list of the variant names from the COMPOUND_VARIANT macro. Useful with the $(filter ...) make function for picking out individual variant names.
CPU
The name of the target CPU. Defaults to the name of the next directory up with all parent directories stripped off.
OS
The name of the target OS. Defaults to the name of the directory two levels up with all parent directories stripped off.
SECTION
The name of the section. Only set if there's a section level in the tree.
PROJECT_ROOT (read-only)
The full pathname of the directory tree containing the project.
PROJECT (read-only)
The basename() of $(PROJECT_ROOT).
PRODUCT_ROOT (read-only)
The full pathname of the directory tree containing the product (one level up from the project level).
PRODUCT (read-only)
The basename() of $(PRODUCT_ROOT).
NAME
The basename() of the executable or library being built. Defaults to $(PROJECT).
SRCVPATH
A space-separated list of directories to search for source files. Defaults to all the directories from the current working directory up to and including the project root directory. You'd almost never want to set this; use EXTRA_SRCVPATH to add paths instead.
EXTRA_SRCVPATH
Added to the end of SRCVPATH. Defaults to none.
INCVPATH
A space-separated list of directories to search for include files. Defaults to $(SRCVPATH) plus $(USE_ROOT_INCLUDE). You'd almost never want to set this; use EXTRA_INCVPATH to add paths instead.
EXTRA_INCVPATH
Added to INCVPATH just before the $(USE_ROOT_INCLUDE). Default is none.
INCTEST
If set, indicates that $(SRC_ROOT_$(OS))/include should be searched just after EXTRA_INCVPATH and before $(USE_ROOT_INCLUDE).
LIBVPATH
A space-separated list of directories to search for library files. Defaults to:
. $(INSTALL_ROOT_support)/$(OS)/$(CPUDIR)/lib $(USE_ROOT_LIB).
You'd almost never want to use this; use EXTRA_LIBVPATH to add paths instead.
EXTRA_LIBVPATH
Added to LIBVPATH just before $(INSTALL_ROOT_support)/$(OS)/$(CPUDIR)/lib. Default is none.
DEFFILE
The name of an assembler define file created by mkasmoff. Default is none.
SRCS
A space-separated list of source files to be compiled. Defaults to all *.s, *.S, *.c, and *.cc files in SRCVPATH.
EXCLUDE_OBJS
A space-separated list of object files not to be included in the link/archive step. Defaults to none.
EXTRA_OBJS
A space-separated list of object files to be added to the link/archive step even though they don't have corresponding source files (or have been excluded by EXCLUDE_OBJS). Default is none.
LIBS
A space-separated list of library stems to be included in the link. Default is none.
CCFLAGS
Add the flags to the C compiler command line.
ASFLAGS
Add the flags to the assembler command line.
LDFLAGS
Add the flags to the linker command line.
VFLAG_which
Add to command line for C compiles, assemblies, links; see below.
CCVFLAG_which
Add to C compiles; see below.
ASVFLAG_which
Add to assemblies; see below.
LDVFLAG_which
Add to links; see below.
OPTIMIZE_TYPE
Set the optimization type. It can be set to one of OPTIMIZE_TYPE=TIME (to optimize for execution speed), OPTIMIZE_TYPE=SIZE (to optimize for executable size, the default), or OPTIMIZE_TYPE=NONE (to turn off optimization).

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.


Note: 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

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:

INSTALLDIR
Subdirectory where the executable or library is to be installed. Defaults to bin for executables and lib/dll for dll's. If set to /dev/null, then no installation is done.
USEFILE
The file containing the usage message for the application. Defaults to none for archives and shared objects and to $(PROJECT_ROOT)/$(NAME).use for executables. The application-specific makefile can set the macro to a null string, in which case nothing is added to the executable.
LINKS
A space-separated list of symbolic link names that are aliases for the image being installed. They're placed in the same directory as the image. Default is none.

Advanced topics

In this section, we'll discuss how to:

Collapsing unnecessary directory levels

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).

Performing partial builds

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:

More uses for LIST

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

[Previous] [Contents] [Index] [Next]