![]() |
Languages »
C / C++ Language »
Howto
Beginner
License: The Code Project Open License (CPOL)
Make Quick LookBy metcarobA quick look at a make file and how to create more advanced make files |
C++, Windows, Win32, Dev
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
In my other articles I have used manually created make files. Make is a program that runs a set of steps to produce an output. In most cases the set of steps involve calling compilers to compile source code. Make automatically checks if the source files have been updated before it takes the steps to create the output files. This means that only changed parts of a program are recompiled rather than the entire program.
I have previously stuck to simple make files but with the make files I have created so far there have been a couple of problems/missing features.
In this article I will explain the make structure I currently have and show how I extend it to include the above features.
Some people have told me that they believe hand coding make is a waste of time. People have suggested using compiler tools as well as automake etc. My thinking behind using make is as follows:
I have created a Codeproject article that completely shows how I set up the tool chain I use for compiling C++ programs. It is at opensource_tool.aspx
I will go through changing a make file I have used in another article. You view it at starting_sqlite_in_cpp.aspx. You should follow this before following this article to get the right files. If you want to skip this I have included a zip file with this article. You should unzip all the files to C:\code\sqlite_hello_world
A make file has a 'top bit' and then any number of sections. The top bit is generally used to setup variables etc. and each section is used to supply instructions telling make how to create a file. Follow these steps to setup out project directory and create a simple little make file. You can use Shift + Ins in MINGW to paste each command to avoid typing errors.
$(warning Starting Makefile)
CXX=g++
fin.txt:
$(warning Making fin.txt)
echo Messing about with Make > fin.txt
This is a simple make file with the 'top bit' and one section. The first line contains a warning statement which is just a way of printing a message to the screen during build. The second line sets up a variable. The Line fin.txt: denotes the start of the first section. The layout of the section is:
<Output file>:<prerequisites ...> command 1 command 2 ...
Output file is the name of the file to output, prerequisites is the files that must be present in order to create the output file. Command 1, 2 etc. are the shell commands used to create the file. Each command line starts with a tab character and there is a blank line separating each section.
In out example the file we are creating is fin.txt and we don't need any files to make it. There are two commands to make it, the first just tells us what make is doing and the second is just an echo command piped into a file called fin.txt. To run this launch MINGW and follow these steps:
You can see that the file is produced
As you can see fin.txt now exists.
You can now see the message `fin.txt' is up to date which shows us that make is doing it's job. It hasn't processed the command to make the file that is already there. Now lets enhance the makefile a bit:
$(warning Starting Makefile)
CXX=g++
.PHONY: clean
fin.txt:
$(warning Making fin.txt)
echo Messing about with Make > fin.txt
clean:
$(warning Deleting all txt files)
rm *.txt
The first thing to note is that the file now has two sections. How does make know which one to process? Well when it is run with no parameters make will always run the first section. In this case fin.txt is processed. If you want to make a different output file you can specify it as the first parameter, e.g. make fin.txt or make clean, etc.
Another thing to note is that the clean section is not a real section. The third line declares it as phoney. This tells make that there is no file called clean created but the commands are run when requested. Clean is a standard section put into a lot of make files which deletes all the files that make produced. This is useful to force a rebuild or if you are working on changes to the makefile. You can run make clean before backing up your code to save space. Experiment with this file a bit and note how the txt file appears and disappears and how make will not rebuild it if it is already there.
Now alter the makefile so it shows the following:
$(warning Starting Makefile) CXX=g++ .PHONY: clean fin1.txt: fin2.txt $(warning Making fin.txt) cat fin2.txt > fin1.txt echo done with 2 >> fin1.txt fin2.txt: fin3.txt $(warning Making fin.txt) cat fin3.txt > fin2.txt echo done with 3 >> fin2.txt fin3.txt: fin4.txt $(warning Making fin.txt) cat fin4.txt > fin3.txt echo done with 4 >> fin3.txt fin4.txt: $(warning Making fin.txt) echo We are still messing about with Make > fin4.txt clean: $(warning Deleting all txt files) rm *.txt
You can see that this makefile has five sections. The clean section we talked about earlier. You can see that fin1.txt depends on fin2 which depends on fin3 which depends on fin4. You can use make to make the whole lot and make will work out which files needs to be recreated. Make also looks at the date stamps of the files, so once you have made all the files, if you change fin3.txt you will note that make will see this and recreate fin2.txt and fin1.txt. This is useful in programming. You make objects dependant on their headers and make will rebuild objects that need to. This saves time as objects that haven’t changed aren’t rebuilt.
You can also add a section to make a final file which depends on them all:
fin.txt:fin1.txt fin2.txt fin3.txt fin4.txt $(warning putting it all together) cat fin1.txt >> fin.txt cat fin2.txt >> fin.txt cat fin3.txt >> fin.txt cat fin4.txt >> fin.txt
Remember make by default always builds the first section it sees so place this at the top of your make file. As this file depends on all the other files make will create all the other files first. Also all of this has lots of warnings which tell us what make is doing here but I wouldn't put in a real makefile.
For a laugh we can try and confuse make with something like the following:
aa.txt:bb.txt echo aa > aa.txt bb.txt:aa.txt echo bb > bb.txt
This can't be built and make warns us and tries its best.
Have a look at the following make file:
$(warning Starting Makefile) CXX=g++ .PHONY: clean fin.txt: echo Make variable test > fin.txt echo CXX=${CXX} >> fin.txt echo CFG=${CFG} >> fin.txt clean: rm *.txt
This file uses a built in variable and an external variable. You can run it by using make, or you can pass a variable to it by running a command like: make -e CFG=Test_other_var As you can see this will allow us change what we do based on command line parameters. We can also add some code to give the external variables default values, and check if it is the correct value:
$(warning Starting Makefile) CXX=g++ .PHONY: clean ifndef CFG CFG=DEBUG $(warning Defaulting to $(CFG)) endif ifneq ($(CFG),DEBUG) ifneq ($(CFG),RELEASE) $(error error is CFG must be DEBUG or RELEASE) endif endif fin.txt: echo Make variable test > fin.txt echo CXX=${CXX} >> fin.txt echo CFG=${CFG} >> fin.txt clean: rm *.txt
The Error command is different to the Warning command because Error will exit the make process. You can see here that the variable CFG is defaulted to DEBUG but the user can enter a value. If the value is not DEBUG or RELEASE the build process errors. You can test this make file with the commands:
make make -e CFG=ddd make -e CFG=RELEASENote: Remember to use make clean in between tests.
Ok, now we need to use this to allow us to make debug and release builds and store the created files somewhere other than our code directory. I will do this using the sqlite example project I created. Follow these instructions:
$(warning Starting Makefile)
CXX=g++
ifndef CFG
CFG=DEBUG
$(warning Defaulting to $(CFG))
endif
ifneq ($(CFG),DEBUG)
ifneq ($(CFG),RELEASE)
$(error error is CFG must be DEBUG or RELEASE)
endif
endif
.PHONY: clean
${CFG}/main.exe: ${CFG}/dir_exists.obj ${CFG}/sqlite_demo.lib main.cpp
$(CXX) -s main.cpp -o ${CFG}/main.exe -Wl,${CFG}/sqlite_demo.lib
${CFG}/sqlite_demo.lib: ${CFG}/dir_exists.obj ${CFG}/sqlite3.obj ${CFG}/RJM_SQLite_Resultset.obj ${CFG}/RJMFTime.obj
ar cq $@ ${CFG}/RJMFTime.obj
ar cq $@ ${CFG}/RJM_SQLite_Resultset.obj
ar cq $@ ${CFG}/sqlite3.obj
${CFG}/sqlite3.obj: ${CFG}/dir_exists.obj sqlite3.c
gcc -c sqlite3.c -o ${CFG}/sqlite3.obj -DTHREADSAFE=1
${CFG}/RJM_SQLite_Resultset.obj: ${CFG}/dir_exists.obj RJM_SQLite_Resultset.cpp RJM_SQLite_Resultset.h Glob_Defs.h RJMFTime.h sqlite3.h
$(CXX) -c RJM_SQLite_Resultset.cpp -o ${CFG}/RJM_SQLite_Resultset.obj
${CFG}/RJMFTime.obj: ${CFG}/dir_exists.obj RJMFTime.cpp RJMFTime.h
$(CXX) -c RJMFTime.cpp -o ${CFG}/RJMFTime.obj
${CFG}/dir_exists.obj:
-mkdir ${CFG}
-echo dir exists > ${CFG}/dir_exists.obj
clean:
-rm DEBUG/*.*
-rm RELEASE/*.*
-rmdir DEBUG
-rmdir RELEASE
Basically what I have done is added ${CFG} in front of the object files. This means that the files are created in the relevant directories. I also made all the files dependant on ${CFG}/dir_exists.obj. This means that the directory will be created if it doesn’t exist. You can also see an enhanced clean section to deal with the sub directories.
When I set up the tool chain I added menu items into notepad++ menu. This automatically launched the program. This won't now work as the program is in a different place. To fix this follow these instructions:
:##BATCH to run fron notepad++ c: cd\ cd %1 make pause cd DEBUG main.exe pause
This default menu option will automatically run the DEBUG version of the code. You should note that the data files used in this example are also stored in the DEBUG directory and are always recreated when the program is rebuilt. This behaviour is program specific and if you wish data files to be kept you will need to locate them elsewhere.
Finally I create a new menu to produce the release build
:##BATCH to run fron notepad++ c: cd\ cd %1 make -e CFG=RELEASE pause cd RELEASE main.exe pause
Clean cleans both RELEASE and DEBUG so there is no reason to add it here.
We now have a much simpler and cleaner way to build our programs separating the source files from the created files. You now should be able to create your own projects that follow this system. Things you need to remember when building the makefile are:
If you follow these guides you can easily create your own projects following this system.
Make is very powerful and using just the features I have described you can produce complex build processes. One thing I have done before is to use make to build a program, then run it. The program I wrote produced source code which I then used make to build into my main project. This way I achieved auto-generated code being directly included in the build process.
Some people say make is complicated and time sucking. I do not think what I have explained here is too complicated especially since people here will be programmers anyway and variables etc. should be second nature. I personally like the idea of having control over the build process and manually setting up the dependencies, rather than allowing the IDE to do it. It is also useful as it exposes me to common compiler directives used in libraries I use and I can see what is going on. Of course there are other methods available and you might decide something else is better. Even if you move on to something else you might be glad that you spent this time studying make and the build process at a detailed level.
You can view the make manual at http://www.gnu.org/software/make/manual/.
I have just signed up to twitter and I am hoping make contacts with other developers who work with C++ to help expand what I know. My username is @metcarob. I usually post when I update articles, and other development stuff I do.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 5 Jul 2009 Editor: |
Copyright 2009 by metcarob Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |