I studied a bit on how to make makefiles in order to make the process of compiling my projects easier. This is what i found out. Make is a tool you can use to organize your project in such a way that when you're compiling it wouldn't be so much pain in the ass. Basically, you'll need the make program that is usually built in with most Linux and Unix operating systems. If you are using Windows, Microsoft has a great nmake.exe which is a great make implementation. For more info on what make really is, you can visit Wikipedia.
Warning
If you have not tried compiling using gcc, g++, c++ or any other command line interface compilers before, I strongly suggest you study them now and actually compile things using them. It will be very difficult for someone to understand the principles behind make without prior understanding of how compilers work.
Example
Let me go straight with explaining to you the makefile that i made.
makefile.mak
# Compiler to use
CC=c++
# Directory variables
BOOSTPATH = /cygdrive/d/data/boost_1_45_0/unix
# Include directories
CFINCLUDE = -I $(BOOSTPATH)
# Compiler options
CFLAGS = -g $(CFINCLUDE)
# Enumerate library files here
CFLIBS = \
$(BOOSTPATH)/stage/lib/libboost_date_time-gcc34-mt-1_45.a \
$(BOOSTPATH)/stage/lib/libboost_thread-gcc34-mt-d-1_45.a
# Code file (.c, .h, .c++)
CODEFILES = \
threading.cpp
# Output file
OUTPUT = -o \
threading.exe
all: main
main: threading.cpp
$(CC) $(CFLAGS) $(CODEFILES) $(OUTPUT) $(CFLIBS)
First of all you might notice that it's named makefile.mak. That is the best name you can give a make file. Second, it's best practice to put all your code in one folder or directory. Third, in this example I have only one code, the threading.c++ but I'm using compiled Boost libraries and Boost headers. By making a makefile, I can generally reduce the amount of time tinkering my compile command which looked like this before.
c++ -g \
-I $UBOOST \
threading.cpp \
-o threading.exe \
$UBOOSTLIB/libboost_date_time-gcc34-mt-1_45.a \
$UBOOSTLIB/libboost_thread-gcc34-mt-d-1_45.a
By the way, $UBOOSTLIB is an environment variable I setup in my Cygwin. Anyway, as you can see, it can bee a bit pain in the ass if I were going to edit that command using the terminal. And generally, it would be very difficult if I were using many more libraries and a lot more code files. With that make file in hand, I can simply issue this command at the terminal on the directory of my project.
make -f makefile.mak
Now let me explain what the codes in my makefile mean. Basically they're already commented (# comment
) but let me explain once again. For my project I'm using c++
as the compiler (not gcc
, g++
or others, it's c++
, the name of the compiler), hence I set the CC to c++. In the # Directory variables
region, I added an include directory for my project, which is basically the one from Boost Library. If I am going to add another directory it will be very simple and may look like this.
# Directory variables
BOOSTPATH = /cygdrive/d/data/boost_1_45_0/unix
ANOTHER_INCLUDE_DIR = /cygdrive/d/another_include_directory_3
# Include directories
CFINCLUDE = -I \
$(BOOSTPATH) \
/cygdrive/d/another_include_directory \
/cygdrive/d/another_include_directory_2 \
$(ANOTHER_INCLUDE_DIR)
It should be obvious by now that you can actually set variables by simply equating them to something. In addition in order to use them you use $(VARNAME)
. And finally, the back slash at the end of some lines means we don't want to end the line yet and continue on the next line. Observe I did not put a backslash on the last line of the previous code. That's because nothing follows that line. Make file will simply read from CFINCLUDE = -I to $(ANOTHER_INCLUDE_DIR) as one line.
But we haven't set the compiler options yet. So the following code will be used to set them.
# Compiler options
CFLAGS = -g $(CFINCLUDE)
You can see we inserted $(CFINCLUDE)
to CFLAGS
, that's because $(CFINCLUDE)
is also a compiler option, along with -g which means debug mode. The command $(CFINCLUDE)
when read by make will look like -I directory1 directory2 directory3
. The following lines will be about the compiled libraries used by my project.
# Enumerate library files here
CFLIBS = \
$(BOOSTPATH)/stage/lib/libboost_date_time-gcc34-mt-1_45.a \
$(BOOSTPATH)/stage/lib/libboost_thread-gcc34-mt-d-1_45.a
They work just like the included directories but this time, there are not -I option, they're just plain appended on the end of the command really. You can know this if you're using the c++ compiler and issue command, man c++.
The next lines should be quite understandable.
# Code file (.c, .h, .c++)
CODEFILES = \
threading.cpp
# Output file
OUTPUT = -o \
threading.exe
The main issue here is the next lines.
all: main
main: threading.cpp
$(CC) $(CFLAGS) $(CODEFILES) $(OUTPUT) $(CFLIBS)
The cod all: main just pretty much calls the main function first. The main function being named threadin.cpp in this case, where the main() part of your program is to be located. On the other hand, it has sub code which basically means concatenate the following variables of string. It's simply concatenating this string in order to form the final command. The final command will actually look like this in the end.
c++ -g -I /cygdrive/d/data/boost_1_45_0/unix threading.cpp -o threading.exe /cygdrive/d/data/boost_1_45_0/unix/stage/lib/libboost_date_time-gcc34-mt-1_45.a /cygdrive/d/data/boost_1_45_0/unix/stage/lib/libboost_thread-gcc34-mt-d-1_45.a
Conclusion
By making sure the compilation code is properly documented I can scale my project in to very large proportions and I can also easily use another compiler and chance settings. According to my understanding the make is a useful tool for concatenating very complex commands in a manner where it is easier to maintain. Hence making a makefile is a really great idea to both keep track and make the compilation process of your project much easier.