Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Extending imake


JUN94: Extending imake

Extending imake

Taking a tool beyond the X Window System

Kamran Husain

Kamran is an independent consultant specializing in designing X/Motif and real-time systems for geophysical and telecommunications applications. He can be reached at 713-265-1635.


Imake is a utility that works with make so that code can automatically be configured, compiled, and installed on different UNIX platforms. It is currently used to configure systems such as the X Window System and Kerberos authentication. In his book, Software Portability with imake (O'Reilly & Associates, 1993), Paul DuBois points out that much of X's success can be credited to its portability, and this portability is in large part due to imake. While primarily an X tool, imake is useful for any project that involves porting to multiple UNIX systems.

Imake generates makefiles from the Imakefiles template--a set of C preprocessor macros. Makefiles are generally not portable across different machines. Separating machine dependencies from items being built, however, renders Imakefiles platform independent. imake uses Imakefiles to generate a makefile for each platform for a given application; see Figure 1. It is invaluable for making a release available on a wide variety of machines. The X Window System imake (distributed by MIT with the standard X Window System release 3 and greater) generates platform-specific makefiles by using descriptions defined in Imakefiles. In this article, I'll discuss imake, its template and rule files, and Imakefiles. And since imake isn't restricted to X, I'll also show you how to extend Imakefiles beyond the X Window System to AIX, SunOs, Linux, and the like. I'll refer to the site-specific config files as siteConfFile (and the directory they're stored in as siteConfDir) and project-specific configuration files as projConfFile (and the directory they're stored in as projConfDir).

How imake Works

Imake uses the cpp preprocessor's conditionals to determine how to create a makefile: It requires you to define an Imakefile and a template file with all default rules, definitions, compiler options, and special make rules.

Just as make relies on makefiles, imake relies on Imakefiles. Imakefiles contain definitions of make variables and one or more invocations of macro functions to build the desired parts of a makefile; see Example 1.

Imake looks at template files to determine how to create a makefile from your instructions. This makefile will have the clean, depend, install, and all targets defined for you. imake looks for its template file, Imake.tmpl, through the path specified in the command-line option --Ipathname.

The Imake.tmpl file contains definitions common to all makefiles. The general structure of this file is in Example 2. The preprocessor picks up definitions in the files defined. If these definitions are not overridden in subsequent files, they will be used to create makefiles. Think of the Project.tmpl file as a way to override definitions in the site.def file which, in turn, can override declarations in the platform.cf file.

Using the Imake.rules file, imake appends a set of common make rules at the bottom of a makefile. These rules are generally for the all, depend, clean, and install directives.

The Imake.tmpl file should be modified with great care since it contains platform-specific definitions. These files can usually be found in the ./lib/X11/config directory where you've installed the X Window System. If you don't have this directory, use a find command to find the files.

Imakefiles

Example 3 shows the definition of a platform in the Imake.tmpl file. If you're running on a Sun, for instance, you should have a #define sun declared before these constructs. There are about ten such constructs for machines in the template file (DEC, Apollo, Cray, and the like) and one for a generic file, in case a machine isn't defined. For example, if #define sun is declared at the top, then the sun.cf file will be included in Imake.tmpl. If nothing is declared, the generic template file will be used.

The site.def file defines a preprocessor variable called ProjectRoot, which is the root directory for all your projects; see Example 4. The Project.tmpl file has multiple levels of nested if/ifndef/else/endif pairs that can be confusing. Consequently, it's a good idea to print out the file and examine the nest levels. Since in most cases, you'll have to modify variables related only to your project, you probably should take the Project.tmpl provided here and modify it to fit your needs.

Extending Imakefiles for Multiple Projects

The file structures I've defined so far are for general imake use. However, you can modify these file structures. Example 5, for instance, shows how the Imake.tmpl file can be modified to work with multiple projects.

Once Imake.tmpl has been modified, you can create four empty files (platform.pcf, site.pdef, Project.ptmpl, Imake.prules) in the same directory as Imake.tmpl. Next you can create a pmkmf from the xmkmf shell script, as in Example 6. The inclusion of the search path for the template files forces imake to look at the current project directory before the standard directory. (I'm using a flag called UseInstalled in the xmkmf file. If this flag is not defined in the command line, imake will attempt to make itself.)

In each of the project directories, you should have the option of overriding anything in the standard directories by creating your own platform.pcf, site.pdef, Project.ptmpl, and Imake.prules files. If you choose to work with the standard files, then you don't have to do anything for that project. The empty files in the standard directory will be picked up, satisfying the preprocessor requirements.

If you do have to override the declarations in the standard files, then you can create your own files in your project directory with those changes. The empty file will not be picked up in that event since you search your local directory (PROJECTDIR) first.

In Example 6, I defined two empty files, Motif.rules and Motif.tmpl, for my particular installation in the project-files area. You may have to do the same if your Imake.tmpl file requires some files unnecessary to your project. This makefile is available electronically (see "Availability," page 3). Of special interest are the targets depend, all, and clean that have been defined in the makefile by default.

Recall that almost all the declarations in the standard files are of type:

#ifdef variable
#define variable
#endif

This forces the need for the template files for projects to be in front of the standard files so that these declarations can be seen by the preprocessor first. However, you must be careful to check if this declaration is not defined elsewhere. If a similar variable is declared earlier in the execution cycle, then this particular declaration will not be used. If a similar variable is declared later in the execution cycle, then this particular declaration will override the latter declaration. Be sure to grep all template files just before declaring a new variable.

However, if you want to explicitly override a variable (even if it was not previously defined), then use the following construct if you do not want warning messages from the preprocessor:

#ifdef variable
#undef variable
#endif
#define variable <I>asSomethingDifferent</I>

Working with Multiple Projects

Assume that you have three projects--alpha, beta, and theta-- under your directory. You would include in each of these directories an Imakefile similar to Example 1. To invoke imake, you would then add a script file in your path similar to Example 6. This Imakefile would contain the rule necessary to create your directory's project.

You then have two options: You can either browse through the Imake.rules to find the rule that best fits your needs or write your own. In most cases, you'll find a rule close to what you need. Obviously, any new global rules you create will go in the standard Imake.rules file; if they're specific to this project, they'll go in the Imake.prules file.

Imake rules have the format in Example 7(a), which is illustrated in the rule, Example 7(b). When called, MakeMyProgram(OurProject,BigFile.o Onefile.o OurProject.o,--lm) will expand as in Example 7(c). There are two preconditions for parsers in the C preprocessor and imake, respectively. The entire macro must be one continuous line and the end of the line is terminated by the @@\ symbol. If you forget to add one or the other, you'll see some really unusual rule expansion. The @@\ symbol is stripped away by imake before handing off to the C preprocessor.

In almost all cases, you want to consider macros offered by Imake.rules before writing your own. Table 1 lists some of the macros in the Imake.rules file. Table 2 lists some of the predefined variables that you can use in your Imakefiles and macros. You can always look in the generated makefile for the complete list of predefined symbols.

For my projects, I typically use:

  • SimpleProgramTarget, which lets you specify only one simple target with just one source and one object file.
  • SingleProgramTarget, which lets you specify a target composed of the objects defined in the list OBJS (see Example 1).
  • ComplexProgramTarget, for when you have more than one executable or new libraries to install on your system (use ComplexProgramTarget_1, ComplexProgramTarget_2, and so on).
Actually, SingleProgramTarget is sometimes considered an obsolete version of NormalProgramTarget, which allows you to have dependent libraries. I still find it simpler to use, and in most cases, I don't have dependent libraries.

If an existing macro doesn't meet your particular rule requirements, you can modify a similar one that's defined in Imake.rules.

The Imake.rules macro MakeSubdirs($(SUBDIRS)) allows you to descend into a list, issuing $(MAKE) in subdirectories listed in $(SUBDIRS). Since I didn't find the rule to fit my exact needs, I wrote my own macro (see Example 8), which lets you issue a command that can be executed in each of the directories listed in dir, with flags passed into cmd. (In your particular case, the cmd would be your shell script to invoke imake.)

This lets you invoke your own command set using imake on various sets of directories. This shell script is extensible for adding in man pages, copying binaries to a passed location, and so on. However, Imake.rules can still use all of the rules if you generalize it. If you want to use other predefined rules within this rule, then put your rule either at the end of the Imake.rules file, or in another file (say, Imake.postRules) that's searched after the Imake.rules file. Then include the reference to it, as in Example 2.

Testing Macros and/or Rules

Naturally, you want to test macros without clobbering existing makefiles. To avoid overwriting these files, use imake's -- sFilename option. If the -- option precedes the filename, the output is written to standard output; otherwise it's written to the filename specified. Therefore, if you invoke the imake command with the -- option and redirect the output to a temporary file (say, myMakefile), you can compare the myMakefile and the old makefile file using diff to see what changes were incurred by including your new macro. The --v option on the imake command line will tell you what's being passed to the C preprocessor.

Remember that macros are difficult to debug. When in doubt, put parentheses around any variable that might be expanded and ensure that the number of parentheses match up. If you see unusual expansions, check to see if you have placed the \ and @@\ terminators correctly at ends of all noncontiguous lines. When in doubt, place echo statements in the rule to show what's going on during expansion. It's important to remember that in makefiles the $ is a special character that expands the next field in front of itself. To keep $MAKE from expanding $M instead of $MAKE, be sure to use $(MAKE).

For example, consider this construct expanded to a rule in a makefile:

a.o b.o : [email protected]
cp [email protected] $@

This is used to derive a.o from a.o.bkp and b.o from b.o.backup. make always reads a dependency list twice, once when it's initially reading the makefile, and again when it's generating the dependency list for the target's dependencies. In each pass, it performs a macro expansion. The $$@ is thus expanded to $@ on the first pass, and $@ is expanded to the actual dependency on the second.

Another catch when using special make macros in imake rules is that, unlike dependency lists, the target-name part is scanned only once per invocation of make. So a $$(NAME) in the target area will not expand beyond $(NAME) for a target name and will yield unexpected results.

Figure 1: Building programs with imake and make.


Example 1: Definitions of make variables.

# This is a simple Imakefile for a
# single program target myfile
SRCS=myfile.c another.c
OBJS=$(SRCS:.c=.o)
MYLIBS=-lm

SingleProgramTarget(myfile,$(OBJS),
$(MYLIBS),NullParameter)

Example 2: General structure of Imake.tmpl file; (a) system descriptions; (b) general rules.

<b>(a)</b>
#include <platform.cf>
#include <site.def>

<b>(b)</b>
#include <Project.tmpl>
#include <Imake.rules>

Example 3: Definition of a platform in the Imake.tmpl file.

#ifdef sun

#define MacroIncludeFile <sun.cf>
#define MacroFile sun.cf
#undef sun
#define SunArchitecture
#endif /* sun */

#ifdef hpux
#define MacroIncludeFile <hp.cf>
#define MacroFile hp.cf
#undef hpux
#define HPArchitecture
#endif /* hpux */

Example 4: The site.def file defines a preprocessor variable called ProjectRoot, the root directory for all your projects.

/* This is the site.def file for my projects */

#define ProjectRoot /home/kamran/proj
#define BinDir      Concat(ProjectRoot,/bin)

Example 5: Modifying the Imake.tmpl file to work with multiple projects; (a) system descriptions; (b) general rules.

<b>(a)</b>
#include <platform.pcf>
#include <platform.cf>

#include <site.pdef>
#include <site.def>


<b>(b)</b>
#include <Project.ptmpl>
#include <Project.tmpl>
#include <Imake.prules>
#include <Imake.rules>

Example 6: Creating a pmkmf from the xmkmf shell script.

#!/usr/bin/sh
CONFIGSTDDIR=/usr/local/X4M1.1/usr/lib/X11/config
PROJECTDIR=.
imake -DUseInstalled -I$PROJECTDIR -I$CONFIGSTDDIR $MAKEDEFINES

Example 7: (a) imake rule format; (b) typical imake rule; (c) expanding the imake rule.

<b>(a)</b>
   #define RuleName(arg1,arg2,..,argN)           @@\
   definitions                               @@\
   definitions                               @@\
   definitions

<b>(b)</b>
   #define MakeMyProgram(program,objs,libs)       @@\
   program: objs                             @@\
     cc -o program objs libs

<b>(c)</b>
   OurProject: BigFile.o Onefile.o OurProject.o 
   cc -o OurProject BigFile.o Onefile.o OurProject.o -lm

Table 1: Some sample macros in the Imake.rules file.

Macro                      Description
--------------------------------------------------------------------
<I>NormalProgramTarget</I>        program,objects,deplibs,locallibs,syslibs
<I>SimpleProgramTarget</I>        program
<I>ComplexProgramTarget</I>       program
<I>ComplexProgramTarget_1</I>     program,locallib,syslib
<I>ComplexProgramTarget_2</I>     program,locallib,syslib
<I>ComplexProgramTarget_3</I>     program,locallib,syslib
<I>ServerTarget</I>               server,subdirs,objects,libs,syslibs
<I>InstallLibrary</I>             libname,dest
<I>InstallSharedLibrary</I>       ibname,rev,dest
<I>InstallLibraryAlias</I>        ibname,alias,dest
<I>InstallLintLibrary</I>         libname,dest

Table 2: Some Imakefile variables.

Variable          Description
------------------------------------------------
CC                C compiler invocation
SRCS              C sources files
OBJS              Object files
DEFINES           Application-specific preprocessor symbols
INCLUDES          Application-specific header files
SYS_LIBRARIES     X11 libraries
DEPLIBS           Libraries used for dependencies
CDEBUGFLAGS       Compiler options using --g, --o, and so on
LOCAL_LDFLAGS     Linker options

Example 8: Macro which lets you issue a command that can be executed in each of the directories listed in dir, with flags passed into cmd.

/* An example rule for the top level directory of a multi-project
 * directory. This rule should be placed in Imake.rules or
 * Imake.prules.
 * FireUpSubDirs - For each directory in dirs do cmd with flags. Look
 * at a similar example in Imake.rules called MakeSubDirs
 * for an alternative.
 */

#ifndef FireUpSubDirs
#define FireUpSubDirs(name,dirs,cmd,flags)            @@\
    for i in dirs ;\                                 @@\
    do \                                             @@\
       (cd ./$$i ; echo "In $(CURRENT_DIR)/$$i..."; \  @@\
            cmd flags ); \                          @@\
    done
#endif


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.