Coding Guidelines And Rules For Imake

Obsolete

This documentation is obsolute, the Imake world having been replaced with the cmake world. This documentation is only kept for historical reasons, or for people working on very old versions of HTCondor. Otherwise, IGNORE THIS.

HTCondor's Imake Build System Coding Guidlines and Conventions

Revised: Tue Jun 22 14:24:56 CDT 2004

Background

I, Pete Keller, am writing this in May 2004. The Imake system has been a painful thorn in the side of HTCondor since anyone could remember. This document describes the work which I have done and a new coding style for Imakefiles et. al. which will change our Imake system from a terrible collection of unrewarding migraines into manageable collection of rewarding migraines. After performing all of this work, I have come to the realization that eventually GNU M4 should replace imake as the macro processor in our build system. Imake's initial designers had made a terrible choice in using a preprocessor for a nonwhitespace sensitive language, i.e., C, for a whitespace sensitive language, i.e, Makefiles. Currently, things should work for a good many number of ports, but the headaches of "keeping everything just right" might get to much, and the evolution will continue.

The biggest problem with using imake seemed to be a fundamental lack of understanding for how it was supposed to work in the first place. Poorly implemented rules which "worked" on old cpps were simply copied around and their implementation became canon. Until it stopped working. This situation happened because GCC's cpp started conforming to the rules that they had avoided for so long. However, many OSes vendor cpps tread into undefined territory, so I worked hard to find a common ground, the stuff that all cpps are supposed to implement.

The main culprits for the failure of our Imake system to be resilient and robust comes down to two systemic problems. Serious misuse of whitespace and the '##' concatenation operator. These two problems alone caused the most headaches while trying to port HTCondor to a new compiler which happens often and will, if it had been left alone, cause problems for the rest of eternity.

Guidelines and Rules for the Use of Imake with HTCondor

The CPP

The build system has been changed to use the same cpp that comes with the distribution of the target OS. This would be the same cpp that the X Consortium source code would need to compile. On most machines it is /lib/cpp, or even /usr/lib/cpp, etc. It doesn't matter the revision of the cpp since the rest of the guildlines explain how to write generalized preprocessor macros. There is a new script, ansi_cpp, in the imake/ directory which figures out which cpp to use for each architecture. On some architectures, we are forced to use the vendor compiler, and so that must be installed. On other architectures, there are a lot of cpp programs to choose from and we must choose the right one. At all times, I've tried to stay canon to what the X Consortium code does to compile X.

TAB characters

TAB characters passed through cpp and then imake are preserved in all cases except an errant gcc 3.1 revision. This errant version was fixed by hacking our imake code to invoke the cpp differently.

#endif Comments

Comments on the same line as the #endif, but after it, must be C style comments, not bare text.

#define [thing] [stuff]

[thing] MUST be a true C Identifier:

CORRECT :

#define IS_I386_LINUX_RH9 YES

INCORRECT :

#define IS_I386_LINUX-RH9 YES

Generated Makefile Comments

If you'd like comments in the generated Makefile, please use XCOMM in the Imakefile like this: XCOMM This is a comment in the generated Makefile

XCOMM is an imake program built in.

Macro Names

Macro names must not unintentionally appear in any XCOMM comments as they can be possibly expanded by various revisions of cpps.

Macro Definitions

There can be no whitespace in the parameter list of a macro definition. Also, no whitespace around the interior of the parenthesis, which is a common idiom in HTCondor C/C++ code. Here is an example:

CORRECT : #define Concat(x,y)x##y

INCORRECT : #define Concat( x, y )x##y

In addition, there should be no spaces after the closing parenthesis and the start of the nonwhitespace text--especially in a one line macro rule:

CORRECT : #define copy(x,y)cp x y

INCORRECT : #define copy(x,y) cp x y

If you have a multiline rule where you use @@\ to end the line(but continue the rule to the next line), any whitespace from the @@\ to the previous nonwhitespace character on that line is undefined for its preservation. Do not rely on the existance of that particular sort of whitespace. In practice, however, no rules rely on this and it would be out of the ordinary if a rule does.

Rule Invocation in an Imakefile

There must be NO WHITESPACE around the open/closing parenthesis and commas in rule invocation since this whitespace is undefined in its preservation between cpps. However, whitespace could exist in the invocation rule if it is separating items within a parameter:

CORRECT :

program_target(thingy,$(OBJS) foo.o bar.o,$(LIBS)) Notice the whitespace in the second parameter, whitespace of this specific kind is valid.

INCORRECT :

program_target( thingy, $(OBJS) foo.o bar.o, $(LIBS) )

In addition, you may NOT use the line continuation character: '\' anywhere in the line when invoking a rule:

INCORRECT :

program_target(thingy,$(OBJS) foo.o bar.o,\
$(LIBS))

Different pre-processors preserve or don't preserve the whitespace up to the $(LIBS) lexeme and that can cause trouble depending how your rules are layed out. Since it COULD be possible to use the '\' character in some places and not others validly, I'm striking out the use of it altogether so we don't have to worry about the details of when you can or can't use it.

Use of the concatenation operator: ##

This operator is the main reason for the problems we've experienced using the Imake system. People had been using ## incorrectly in the sense that they were compressing out whitespace preserved by cpp during incorrect Rule Invocations but, in doing so, produced invalid C identifier tokens with respect to the usage of ## according to the specifications in the ANSI C manual.

Since using ## to produce a non-C token was "undefined" according to the specification, many, but not all, cpp programmers honored the pasting together of two lexemes for the reason of whitespace removal. But ultimately, it was bad behavior and since the specification has been tightened to reject arbitrary lexeme pastings by many modern cpp versions.

If you do need to use ##, then use one of the Concat*() rules which exist for this purpose that are found in Global.h. These rules embody special behavior for certain cpp programs where ## can be replaced with /**/ because ## wasn't supported correctly or even available at all in the cpp.

Since we now FORCE, by the creation of this document, that ANY and ALL rule invocations must not have any extraneous whitespace, we can consign the use of ## back to where it is supposed to be, in the Concat*() rules.

This is the _ONLY VALID USE _ of the ## operator: c_identifier1##c_identifer2 -> c_identifier1c_identifier2

These are examples of INCORRECT uses of the ## operator:

=path##/##to##/##some##/##dir##=
=directory##.static=
=##make_target##: stuff things other=
=perl condor_scripts/make_both_tarball -cmd "$(TAR_CMD)" strip##, name##, files##, contribfiles=
=DEPEND_C_SRC := $(##obj_list##:.o=.c)=

Now, here is the important thing:

YOU WILL NEVER NEED TO USE THE ## OPERATOR EVER! USE ONE OF THE =Concat*()= RULES INSTEAD AND ONLY WHEN PRODUCING A TRUE C IDENTIFIER.

=#= and Preprocessor Directives

When using preprocessor directives like #define , etc, please be sure to place the # character in the FIRST column of the text window. However, there can be whitespace between the hash mark and the preprocessor directive like this:
#    define FOO BAR

Conventions in the Imake Build Files

Macro Namespace

The name of a macro can only be used during the invocation of a macro.

For example, suppose we have a macro called BUILD(x,y) . Now in an Imakefile rule, we have a cleanup rule for a target which looks like this, rm -rf BUILD=--which is completely seperate from the invocation of the macro rule =BUILD(x,y) . In this case the cleanup rule is an illegal use of the macro BUILD(x,y) .

In short, do not mix the namespaces of the macro names and the entities the generated Makefile will manage.

Code Reuse

Suppose in a rule you'd like to have _static appended to a directory name in multiple places or something similar. Instead of using Concat(name,_static) everywhere, write a simple rule like this (toward the beginning of the Imake.rules file):

#define sufstatic(x)Concat(x,_static) And now whenever you want foo_static, you write sufstatic(foo) instead. This will greatly help in producing maintainable and defensive code.

Writing New Imake Rules

New rules should be written in this sort of style:
#ifndef release_target
#define release_target(file,dir,mode)           @@\
XCOMM Begin translation of func(release_target) @@\
$(RELEASE_DIR)/dir/file: file                   @@\
/bin/rm -f $(RELEASE_DIR)/dir/file          @@\
cp file $(RELEASE_DIR)/dir                  @@\
chmod mode $(RELEASE_DIR)/dir/file          @@\
release:: $(RELEASE_DIR)/dir/file           @@\
XCOMM End translation of func(release_target)
#endif /* release_target */

Notice the XCOMM comments dictating that we are beginning and ending the translation of this particular function. The macro func() is defined in Imake.rules which expands(even in the comment) the given function name into a nice piece of text in the Makefile which describes the line of translation of the associated function in the original Imakefile. While these are not required for the rule to function, I STRONGLY ENCOURAGE you to follow it. Obviously, for very simple rules like the sufstatic() rule above, you don't need it, but for anything more complicated you should strive to put it in.

Converting older style Imake rules to New Imake Rules

If you see any function (simple or complex) that does not follow the conventions outlined in this document then update the function to follow the guidlines. This is more of a concern while merging as these changes propogate through the various branches. But if you are editing a source file which looks like it is adhering to these conventions, then assume it is adhering to these conventions and change it accordingly.

The Imake processor is the C Preprocessor:

The usual caveats to this applies, e.g.: If you need to do something like this:

#define cat(x,y)x##y and you want to use it like this: cat(cat(1,2),3) it will do the wrong thing and produce cat(1,2)3 . If you want to do it right, then you'd need an additional rule like this: #define xcat(x,y)cat(x,y) which would then produce the correct concatenation sequence. Remember that section A.12 in the K&R C book applies.

In the case of the HTCondor build system, this specific example is already implemented in the Concat*() rules written in Global.h .