Makefile

From Wikiid
Revision as of 16:56, 30 July 2010 by SteveBaker (Talk | contribs) (Making C++)

Jump to: navigation, search

The UNIX 'make' tool is one of the oldest ways to make things (executable programs mostly) under UNIX, Linux and even CygWin/Windows. The GNU version of the tool is free OpenSourced - and can be found almost everywhere.

Make is told how to make things using a 'Makefile'. Makefiles can have any name or extension - but overwhelmingly often they have just one name: 'Makefile' (with a capital 'M'). When you have to call them something else, the '.mk' extension is the most common choice.

Running 'make'

Running 'make' is easy:

   make

...providing you called your Makefile by the usual name. Otherwise (if you really must), use:

   make -f mymakefile.mk

What's in a Makefile?

Rules

At it's most basic, a Makefile contains some number of 'rules':

   whatYouWant : whatItsMadeFrom
          how you make it

This says that if the file 'whatYouWant' either doesn't exist or is older than 'whatItsMadeFrom' then run the command 'how you make it'.

The singular most ugly thing about Makefiles is that there must be no white-space before 'whatYouWant' - and the white-space before 'how you make it' must be a 'tab' character. This is horrible and very un-modern - but we're essentially stuck with it.

If there is more than one rule in your Makefile, 'make' will initially only try to obey the first one. You can override that on the command line - but usually, you don't.

Examples

As a practical example, you might write:

   myProgram : myProgram.cpp
          g++ -o myProgram  myProgram.cpp

...this will look to see whether myProgram exists - and if it does, whether it's been written to more recently than myProgram.cpp. If that's true then myProgram is up to date and 'make' does nothing. But if myProgram is missing or older than myProgram.cpp (eg because you just edited myProgram.cpp) - then it'll run the 'g++' command to compile myProgram from the source code in myProgram.cpp.

Now it's important to realize that 'make' is pretty dumb. It doesn't know anything about compilers or what's inside files or even whether this is C++ source code. This could just as easily be:

   myMusic.ogg : myMusic.mid
           timidity -Ov -o myMusic.ogg myMusic.mid

...which converts a MIDI file into OGG sound samples.

How the commands are run

Make's job is to take the rules for making something - and to flawlessly follow those rules. However, it's also lazy - so it does the least work it can to get the job done.

Now, there is another aspect to this. Let's go back to our simplest version:

   whatYouWant : whatItsMadeFrom
          how you make it

Suppose 'whatItsMadeFrom' doesn't exist? 'make' knows that this means that it's pointless to run the 'how you make it' command because it will probably fail. So what it does is to look through the other rules in the file to see if any of them tell it how to make 'whatItsMadeFrom' - so in a C++ situation, you might compile a '.cpp' file to make an intermediate '.o' file - then link that to make the final program:

   myProgram : myProgram.o
          g++ -o myProgram  myProgram.o
   myProgram.o : myProgram.cpp
          g++ -o myProgram.o -c myProgram.cpp

As I explained, 'make' only starts off by trying to make the thing on the left side of the first rule in the Makefile. So it wants to make 'myProgram' - but before it can do that, it must make sure that 'myProgram.o' is up to date. So it looks for a rule to make that file - and uses it to make sure that's true before it goes on to build 'myProgram'. In this case, that rule exists - and it says that 'myProgram.o' depends on 'myProgram.cpp' (it is common to 'pronounce' the colon in the rule as "depends on").

So if you edit 'myProgram.cpp', make will notice that it's more recent than myProgram.o - so it'll fix that by running the command. Assuming the command doesn't fail (ie if you don't have any compiler errors) - then it'll notice that myProgram.o is now more recent than 'myProgram' - so now it'll run the first command and now myProgram is finally up to date and 'make' can finish.

Multiple dependencies

You can also say:

 myComplexProgram : myProgram.o myOtherStuff.o myYetOtherStuff.o myYetOtherStuffStill.o
          g++ -o myComplexProgram  myProgram.o myOtherStuff.o myYetOtherStuff.o myYetOtherStuffStill.o

...and 'make' will make sure that ALL of the files to the right of the colon are up to date before it tries to run the command.

Saving typing

Technically, this is all you need to know to use 'make' - but there are a couple of other things that are amazingly useful. One is variable substitution:

 MYVARIABLE = some stuff

This sets a make variable to the string "some stuff" - you can use that like this:

 MP = myProgram
 ${MP} : ${MP}.cpp
      g++ -o ${MP} ${MP}.cpp

...or you could say:

 OBJFILES = myProgram.o myOtherStuff.o myYetOtherStuff.o myYetOtherStuffStill.o
 myComplexProgram : ${OBJFILES}
          g++ -o myComplexProgram ${OBJFILES}

...or you might want:

 TIMIDITY = timidity -Ov -s 20 -T 120 --module 1
 myMusic.ogg : myMusic.mid
      ${TIMIDITY} -o myMusic.ogg myMusic.mid

...which makes the Makefile much easier to read.

Generic rules

It is very common to want to say "all files that end with '.ogg' can be made from '.mid' files with the same name using this command...". You do that like this:

   %.ogg : %.mid
      ${TIMIDITY} -o $@ $<

...the '$@' sign is shorthand for "the file on the left of the colon" and '$<' means "the file on the right of the colon".

Now we can say things like this:

   myfile : OhSuzanna2.ogg Tom_And_Jerry2.ogg Black_Mountain_Rag2.ogg
       ...some command or other...

'make' will notice whether all of these ogg files exist - and for those that don't, it'll look for a rule for generating them. But if there isn't one - then it'll look for a generic rule for making '.ogg' files - and find the rule from above that makes it from a '.mid' file. If the '.mid' file exists - then 'make' will use the generic rule to make any of the OGGs that are missing or outdated and then move on to constructing 'myfile'.

Non-existant files and empty rules

Suppose you write this rule:

   myfile.abc : myfile.xyz
        echo "Hello World!"

...that will cause the following two things to happen:

  1. Make will make myfile.xyz (by whatever means).
  2. If/when it's made, it'll run the 'echo' command.

...but notice that 'myfile.abc' was never made. That's fine by 'make' - it ran the command you asked it to - and it's your own silly fault that it didn't produce the file you said it needed to. Also, it's OK to make a rule that has no 'how to' command:

    myfile.abc : myfile.xyz

...which forces 'make' to make myfile.xyz - and then do nothing whatever to even attempt to make 'myfile.abc'.

Now, recall that 'make' only tries to make the thing on the left of the FIRST rule in the file. That's a pain if you want to make several unrelated things in the same Makefile. In that case, you might say:

   all : A B C D

...which (since there is no file called 'all') will just cause 'make' to run the rules to make A, B, C and D. This is really handy:

  MYMUSIC = Beaumont2.ogg            OhSuzanna2.ogg        Tom_And_Jerry2.ogg      \
            Black_Mountain_Rag2.ogg  PollyWally2.ogg       Wabash_Cannonball2.ogg  \
            GoldenSlippers2.ogg      SheBeCominRound2.ogg                          \
            Mountain_Dew2.ogg        SkipToMyLou2.ogg
  all : ${MYMUSIC}

Other syntax

  MYMUSIC = Beaumont2.ogg            OhSuzanna2.ogg        Tom_And_Jerry2.ogg      \
            Black_Mountain_Rag2.ogg  PollyWally2.ogg       Wabash_Cannonball2.ogg  \
            GoldenSlippers2.ogg      SheBeCominRound2.ogg                          \
            Mountain_Dew2.ogg        SkipToMyLou2.ogg

The '\' character means "ignore the next character in the file - which (in this case) is a 'return' character - which results in all of those filenames becoming a part of the 'MYMUSIC' variable.

The '#' character introduces a comment:

  # This is a comment.

A complete example: Making a C++ program

So now, we can put this all together:

  # Object files:
  OBJ = main.o trig.o geom.o utils.o
  # Libraries that we need to link to:
  LIBS = -lGL -lX11 -lm
  # One rule to invoke what we need:
  all : myprog
  # C++ 'compile' command:
  CPP = g++ -c -o $@ $<
  LINK= g++ -o $@ $< ${LIBS}
  # Generic rules
  %.o : %.cpp
       ${CPP}
  # The final step:
  myprog : ${OBJ}
       ${LINK}

So when you type 'make', the following steps happen:

  1. Make tries to make a file called 'all' (which had better not exist!)
  2. 'all' depends on 'myprog' existing and being up to date.
  3. myprog depends on the contents of the ${OBJ} variable - which is a list of a bunch of '.o' files.
  4. There is no specific rule to make any of those files.
  5. But there is a generic rule to make '.o' files from '.cpp' files.
  6. So, presuming the '.cpp' files exist...
    1. Use the generic rule which has '${CPP}' as it's action.
    2. The CPP variable contains a 'g++' command to compile $< to produce $@ - which compiles your '.cpp' into a '.o'.
  7. Now that all of the '.o' files exist and are up to date, the 'myprog:${OBJ}' rule is able to run...
    1. The LINK variable contains a 'g++' command to link a bunch of .o files and produce the 'myprog' executable.
  8. When myprog is built, the 'all' rule completes and finally, 'make' is done.

If you immediately type 'make' again, it'll follow the same logic - but discover that all of the '.o' files exist and are more recent than the '.cpp' files - so it'll skip all of that work - then it'll see that since none of the '.o' files were changed, that 'myfile' is in fact up to date.

Hence, 'make' exits without doing any work other than checking a few time-stamps.

If you go and edit one of those '.cpp' files then make realises that just the corresponding '.o' is out of data, runs just that one command to recompile it - then realises that because one of the files that 'myprog' depends on has changed, that 'myprog' has to be re-linked. Then we're all up to date again.

Final stuff

Normally, 'make' starts with just the first rule in the file - but you can pass the name of a file you want to build (even a fake one like 'all') and it'll just try to make that one file. So you might say:

   all : midi ogg mp3
   midi : ${ALL_MIDI_FILES}
   ogg : ${ALL_OGG_FILES}
   mp3 : ${ALL_MP3_FILES} 

Then you can just run 'make' to build all of your music files - or 'make midi' to make just the midi files.

Remember that commands in the Makefile are just shell commands (or scripts or whatever). Hence, you can do things like:

   all : myprog
   upload : all
      scp myprog admin@sjbaker.org:~/temp/myprog

...and type 'make upload' to compile 'myprog' and upload it to the right place on your remote file server.

You can have multi-line commands for a single rule:

   this : that
      do something
      do something else
      other stuff to do

...but remember that all of those command lines must all start with a TAB character.