Extending imake

Imake generates platform-specific makefiles. Kamran examines imake templates and rule files, then extends Imakefiles beyond the X Window System.


June 01, 1994
URL:http://www.drdobbs.com/tools/extending-imake/184409256

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 1


Copyright © 1994, Dr. Dobb's Journal

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 asSomethingDifferent

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:

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.

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

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

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

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


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

(a)
   #define RuleName(arg1,arg2,..,argN)           @@\
   definitions                               @@\
   definitions                               @@\
   definitions

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

(c)
   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
--------------------------------------------------------------------
NormalProgramTarget        program,objects,deplibs,locallibs,syslibs
SimpleProgramTarget        program
ComplexProgramTarget       program
ComplexProgramTarget_1     program,locallib,syslib
ComplexProgramTarget_2     program,locallib,syslib
ComplexProgramTarget_3     program,locallib,syslib
ServerTarget               server,subdirs,objects,libs,syslibs
InstallLibrary             libname,dest
InstallSharedLibrary       ibname,rev,dest
InstallLibraryAlias        ibname,alias,dest
InstallLintLibrary         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

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.