Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!sun-barr!decwrl!infopiz!lupine!rfg From: rfg@NCD.COM (Ron Guilmette) Newsgroups: comp.lang.c++ Subject: A GNU Makefile (was: ... header files **and** inline management) Message-ID: <3787@lupine.NCD.COM> Date: 8 Feb 91 05:59:52 GMT References: <15917@reed.UUCP> <3699@lupine.NCD.COM> <1991Feb5.180503.24515@mathcs.sjsu.edu> Organization: Network Computing Devices, Inc., Mt. View, CA Lines: 213 In article <1991Feb5.180503.24515@mathcs.sjsu.edu> horstman@mathcs.sjsu.edu (Cay Horstmann) writes: > >I agree 100% with Ron that HEADER FILES SHOULD BE AUTOMATICALLY GENERATED. >I always heard people moaning about header file management and wondered >what I was doing wrong because I never ran into those problems. Well, I >do something pretty similar to Ron's approach, except that I tag each >individual item (class, struct, global data, function) with either EXPORT >or PRIVATE (null-defined strings) and use an AWK script to extract the >.h file. Cay's approach is OK but (of course) I like mine a little bit better. Mine was patterened after the idea of "public" and "private" parts of packages in Ada. Now I know that just mentioning the "A word" in this group is normally the kiss of death for any ideas discussed in the same message, but you will be missing out if you don't at least consider some approach for automating the maintenance of your .h files. I promised some people that I would post my GNU Makefile, so here it comes. ------------------------------------------------------ The following Makefile implements the approach to automatic maintenance of include files (including the inline or non-inline version builds) that I described in a previous posting. Somebody asked me for a copy of this and I decided that, rather than having to respond to too many requests, I would just post it. Some comments are appropriate here. First, as I said in my previous posting, this Makefile makes use of several extensions to plain "make" which may be available *only* in GNU make. Second, I use the suffix .H for all of my include files which contain C++ code. This fact is reflected in the following Makefile. Third, the .H files which get automatically generated by this makefile all comform to the general pattern: #ifndef FILENAME_H #define FILENAME_H #pragma once ... interface stuff ... #endif Since FILENAME is just the upshifted version of the base part of the actual .C filename, you should not use any special characters in your filenames (except maybe underscores). This makefile assumes that each of your .C files has two parts as follows: ... interface part ... //######################################################## ... implementation part ... In this scheme, you may have #include directives either in the interface part or in the implementation part, but it will usually be pretty important to get each of your #include directives into the proper part. Most often, your #include directives should go into the *implementation* part, unless (for some reason) you absolutely *must* have them in the upper interface part. Having #includes in the upper (interface) part can lead to circularities in your dependencies. Note that all declarations of variables and functions in C++ (and in C) can be either explicitly "extern" or explicitly "static" or neither. Using the following Makefile (and this whole scheme for maintaining header files automatically) you should *never* place any non-extern declarations of variables or functions into the interface part. The interface part should contain only the definitions of types (e.g. class, struct, union, and enum type as well as typedefs) and extern declarations of functions and objects. In actuality, I usually maintain my .C files in *three* parts as follows: ... interface part ... //######################################################## ... inline function definitions part ... //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ... implementation part ... If I want to build a small and easily debuggable version of my program, I just use the following Makefile as-is. When I want to generate a fast version (e.g. all inlining enabled) I just comment out the Makefile line: DEFINES= -Dinline= and then I edit the line that says: @sed '/############/,$$d' $< >> $*.H+ So that all of the #'s are made into %'s instead. Then I just do a `make realclean' and then a `make all'. It's that simple. Using this approach to software development requires a lot of disipline (which most programmers seem to lack). If you are not prepared to expend some effort in making this scheme work then don't even bother trying it. This scheme requires that you maintain your .C files in a certain form and that you be very careful about the placement of various declarations and definitions. If you can live with that, then using this scheme can be very helpful, especially on large projects. Note that this scheme is very similar to Ada's approach to the problem of separate compilation. In Ada, you have package `specifications' (i.e. interface parts) and package `bodies' (i.e. implementation parts). You also have "with" clauses, which are somwhat similar to #include directives in this scheme. Unlike Ada however, the C/C++ programmer has no "hidden agent" to trigger recompilations automatically on an "as-needed" basis. The following Makefile attempts to duplicate that functionality using GNU make to act as the "library maintainer" agent. The following Makefile was developed by me on my own home system. It has nothing whatsoever to do with my work for NCD or for anybody else. Up until the time I posted it to the net, it was my private property. Now, it is in the public domain. Cut here ----------------------------------------------------------------------------- SHELL = /bin/sh DEFINES= -Dinline= GPLUSPLUS=g++ -Wall CFRONT = CC +p +w CPLUSPLUS= $(GPLUSPLUS) #CPLUSPLUS= $(CFRONT) CPLUSPLUS_FLAGS = $(DEFINES) -g # -O COMPILE=$(CPLUSPLUS) LINK=$(COMPILE) OBJECTS= \ three.o \ two.o \ one.o \ main.o SOURCES= $(patsubst %.o,%.C,$(OBJECTS)) INCLUDES= $(patsubst %.o,%.H,$(OBJECTS)) TIMESTAMPS= $(patsubst %.o,.%.timestamp,$(OBJECTS)) EXECUTABLE = myprog .PRECIOUS: .%.timestamp %.H %.o: %.C %.H $(COMPILE) $(CPLUSPLUS_FLAGS) -c $< .%.timestamp: %.C @echo Checking $*.H @rm -f $*.H+ @HNAME=`echo $* | tr '[a-z]' '[A-Z]'`_H; \ echo '#ifndef' $$HNAME >> $*.H+; \ echo '#define' $$HNAME >> $*.H+ @echo '#pragma once' >> $*.H+ @sed '/############/,$$d' $< >> $*.H+ @echo '#endif' >> $*.H+ @if cmp -s $*.H+ $*.H; then \ rm -f $*.H+; \ else \ echo Replacing $*.H; \ rm -f $*.H; \ mv $*.H+ $*.H; \ fi @touch $@ %.H: .%.timestamp @true all: $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(LINK) $(LDFLAGS) -o $(EXECUTABLE) $(OBJECTS) clean: rm -f $(OBJECTS) core clobber: rm -f $(EXECUTABLE) core realclean: clean clobber rm -f $(INCLUDES) $(TIMESTAMPS) install: cp $(EXECUTABLE) /usr/local/bin/$(EXECUTABLE) depend: $(INCLUDES) sed '/^# DO NOT TOUCH THIS LINE/,$$d' Makefile > Makefile+ echo '# DO NOT TOUCH THIS LINE' >> Makefile+ -rm -f dependencies for base_file in $(SOURCES); do \ $(GPLUSPLUS) -MM $$base_file \ | tr -d '\012' | tr '\\' ' ' | tr -s ' ' ' ' >> Makefile+; \ echo ' ' >> Makefile+; \ done -rm -f Makefile- mv Makefile Makefile- mv Makefile+ Makefile rm -f Makefile- .PHONY: all clean clobber realclean install depend # DO NOT TOUCH THIS LINE three.o : three.C two.o : two.C three.H one.o : one.C two.H three.H main.o : main.C one.H two.H three.H -- // Ron Guilmette - C++ Entomologist // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // Motto: If it sticks, force it. If it breaks, it needed replacing anyway.