Makefiles For The Experienced

Hamburg (Germany), the 18th November 1997.
Written by Nils Pipenbrinck aka Submissive/Cubic & $eeN

Table Of Contents: Makefiles II

  1. Introduction
  2. Symbolic Targets
  3. Multiple Targets
  4. Makefile as a Dependency?
  5. Explicit Rules
  6. More on Macros
  7. WMAKE Special Commands
  8. Contact Me

Introduction

Ok, since I think there are still some things you should know about these useful MAKEFILES I decided to set up another web-page. This page may be viewed as Chapter II of the previous page. If you've missed it, or you don't know what I'm talking about, better check it out here. Again this tutorial will only deal with WMAKE (the WATCOM MAKE utility). Whenever I say MAKE instead of WMAKE I still mean the WATCOM MAKE. The compatibility issues of other MAKE utilities are discussed on my Makefile Compatibility Chart Page.

For my coding work I make an alias. Whenever I type make, WMAKE will automatically be invoked. (and when you've used it a lot you'll know why. Just try to type WMAKE very fast... it sucks! I usually end up with stuff like WMAEK or WMKE. Typing MAKE is much easier).

Power-Tip for 4DOS and NDOS users:
If you code a lot, you can put MAKE and other useful utilities on the unused f-keys. I added the following lines to my autoexec.bat. I'll tell you, I can't code without them anymore..

  alias @@F9=wmake                 make current project
  alias @@SHIFT-F9=wmake -a        build current project
  alias @@CTRL-F9=edit makefile    edit your makefile
  alias @@F2=edit                  edit last open sourcecode (my editor does this!)
  alias @@F6=ultrinit^mode co80    init screen and soundcard (if your program crashed)

Symbolic Targets

Maybe you've alredy found that it's currently only possible to declare a target that has to be created during the make process. However, sometimes it might be useful to create a target that's symbolic. That means the target might not have dependencies, and it might not exist after the make-progress. Ok, this is a little bit strange. Again an example will help:

clean:
  del *.obj
  del *.lst
  del *.map
  del *.exe
  del *.err

This target is very useful if you want to clean your directory of all the crap the compiler generated during the development. However, if you add this piece into your makefile and just type MAKE clean (yes, you can specify the target at the command line) MAKE will issue an error and tell you that it wasn't able to create the target clean. Right! how should it generate a target? This rule is symbolic! You only have to add .SYMBOLIC to your target-header, and it'll work:

clean : .SYMBOLIC
  del *.obj
  del *.lst
  del *.map
  del *.exe
  del *.err

Now the target is valid because MAKE doesn't expect the target to exist. Important: Put that Target at the bottom of your makefile. MAKE will always execute the first defined target of your makefile. The order of the declarations IS important. Wouldn't it be cool to write a Target to make a backup? Zip all the sourcefiles, rename the archive to the current Data/Time and copy it into your backup-directory? If you think so, then write one!

Multiple Targets

Let's go back to our game example. Say you've written a setup-program, and used some of the modules for game and setup (the mouse and video might be good canditates for this). If you now simply define two targets, one for game and one for setup, your makefile won't do what you expect:

coptions   = /5r /s /ox
obj_setup  = setup.obj
obj_game   = game.obj joystick.obj
obj_common = video.obj mouse.obj

.cpp.obj
  wpp386 $(coptions) $<

game.exe: $(obj_game) $(obj_common)
  %write temp.lnk NAME   $@
  %write temp.lnk SYSTEM DOS4G
  %write temp.lnk FILE   {$(obj_game)}
  %write temp.lnk FILE   {$(obj_common)}
  wlink  @temp.lnk

setup.exe: $(obj_setup) $(obj_common)
  %write temp.lnk NAME   $@
  %write temp.lnk SYSTEM DOS4G
  %write temp.lnk FILE   {$(obj_setup)}
  %write temp.lnk FILE   {$(obj_common)}
  wlink  @temp.lnk

If you type MAKE -a, or simply make, only GAME.EXE will be made (Remember... the first target is the default). But hell... If you've updated mouse.cpp you want all targets up to date, don't you? This is where the ALL target is for: just declare a target called ALL, and put it before your other targets. If you do so, everything will work as expected. The complete makefile will now look like this:

coptions   = /5r /s /ox
obj_setup  = setup.obj
obj_game   = game.obj joystick.obj
obj_common = video.obj mouse.obj

.cpp.obj
  wpp386 $(coptions) $<

all: game.exe setup.exe

game.exe: $(obj_game) $(obj_common)
  %write temp.lnk NAME   $@
  %write temp.lnk SYSTEM DOS4G
  %write temp.lnk FILE   {$(obj_game)}
  %write temp.lnk FILE   {$(obj_common)}
  wlink  @temp.lnk

setup.exe: $(obj_setup) $(obj_common)
  %write temp.lnk NAME   $@
  %write temp.lnk SYSTEM DOS4G
  %write temp.lnk FILE   {$(obj_setup)}
  %write temp.lnk FILE   {$(obj_common)}
  wlink  @temp.lnk

ALL is symbolic by default, so you don't have to add .SYMBOLIC to it (If you do so, make will exit with an error). Now you can either compile the whole project with a single MAKE, or you can choose one of your targets to update and type (for example) MAKE game.exe. MAKE -A will build everything from scratch... Perfect!

MAKEFILE as a Dependency?

Now it's time to add the makefile itself to the dependency list. Why should you? If you change the makefile (add a module for example) all dependencies are out of date... adding makefile to the dependency list of all targets that need a relink will do the trick (no example here... just try it yourself). If you do so you'll soon notice that if you changed the makefile and call MAKE, only the setup.exe and game.exe are not valid anymore. The object files are still up to date. This is a problem when you change the compiler options (and want a recompile of your sourcecode). How can we handle this situation? Either we call MAKE -A or we let MAKE do the work for you... All we have to do is to add a new symolic target to our makefile. It should look like this:

all: dummy game.exe setup.exe

dummy: makefile .symbolic
  del *.obj

This will cause MAKE to delete all objectfiles whenever you've changed the makefile. The targets game.exe and setup.exe become invalid, because the objectfiles don't exist anymore. MAKE will therefore recompile your modules and relink the applications. This might sound cool, but it has a drawback: If you haven't changed your compiler options but only added another module MAKE will still recompile everything. That's the reason why I prefer to type MAKE -A.

Explicit Rules

All the rules we've discussed so far are implicit rules (you know, the .cpp.obj stuff). They're very useful if all your sourcefiles should be compiled using the same options. In some rare cases however you might need other compiler options for a single module. Changing the extension of these modules might work, but it's better to tell make how to do it. A good example of this situation is when you work with the MIDAS sound-library. The MIDAS library needs a special compiler option whenever you write code that's executed from an interrupt. You can override the implicit rule with an explicit one:

.cpp.obj
  wpp386 $(coptions) $<

music.obj : music.cpp
  wpp386 $(coptions) -zu music.cpp

Now MAKE will call all .cpp dependencies except music.cpp using the implicit rule and use the explicit for music.cpp. (the -zu is a WATCOM switch which is nessesary when compiling code that's called from interrupt routines). Explicit rules also have another nice property: You can add dependencies to several modules. When you write C(++) code you usually deal a lot with header files. You can now add the headerfiles included by your module into the dependency list:

video.obj : video.cpp video.h vesa.h global.h
  wpp386 $(coptions) music.cpp

If you change the vesa.h headerfile video.obj gets invalid and will be rebuild. This is very nice to use, but it blows up your makefiles (adding an explicit rule for every module makes your makefile hard to maintain and is a place for evil and hard to find bugs (a hello to Crash/Polka Brother at this point. He favors this kind of dependencies)). I prefer to rebuild my whole project whenever I change headerfiles. This doesn't happen too often, so it doesn't hurt. If you have a cool idea which avoids these monster-makefiles then write me an email!

More on Macros

You can do more things with macros. When you use a macro, you can do a direct text replacement. for example, when you define a macro like this:
obj_common = video.obj mouse.obj
and use it with a colon you can replace parts of the text. For example, try this:
tasm $(obj_common:.obj=.asm)
This will replace all .obj strings in the macro with .asm. The expanded macro is "video.asm mouse.asm". I've never found a place where I could use it, but maybe you know a killer use for it.

If you want to use an enviroment-variable as a macro you can simply use it in the same way you would use a user-defined macro, but be aware that as soon as you define a macro with the same name you're not able to access the enviroment-variable anymore. Again an example:

tasmopts = /m4

.asm.obj
  tasm $(tasmopts) /i$(include) <&

This will add the watcom-include enviroment to the tasm include search-path.

WMAKE Special Commands

WMAKE is much more powerful and has a lot of stuff I won't describe here. Again, if you're interested look into the TOOLS helpfile and dig out that stuff yourself. For completeness I'll now list the most common WMAKE special commands.

CommandWhat it does / Where to use it.
.SYMBOLICMakes a target symbolic (explained)
Insert it after the Dependencies into the Header of the Target definition.
.AUTODEPENDMakes an implicit rule use autodependencies (I had problems with this)
Insert it at the end of the implicit rule header
.IGNOREIgnore return-Code of all compiler/utilities
Insert it at the start of a line
.SILENTSuppress all screen-output if make is successful
Insert it at the start of a line
.ERASEErase target, if make-process is not successful
Insert it at the start of a line
.HOLDOpposite of .ERASE
Insert it at the start of a line
.BEFOREInstructions to be invoked before MAKE does anything
Insert it at the start of a line
.AFTERInstructions to be invoked when MAKE has finished
it at the start of a line
#Comments out a line
Insert it at the first column of the line you want to comment out

Contact Me

Now I've written everything important on makefiles. I hope you learned something. If you have a good tip or found a bug in this page, then write me a mail.


© by submissive