Basic Introduction
This document provides a basic introduction to makeit, the main features it provides and how to get started with it.
How Makeit Works
Where the power and ease of use comes from in makeit, derives from the fact that it operates by looking at the extensions used on files contained within the directory it is run. From the extension of a file, it is able to deduce what to do with that file. For example, if a file has a ".cc" extension it knows that the file is C++ code and thus should be compiled using the C++ compiler. That makeit automatically collects the names of the files in the directory and makes these deductions itself, means you do not have to define variables in your makefile giving long lists of names of code files or object files, as you would normally have to with a basic make program. The only time you need to list names of files in the makefile is when a file should be treated in a special way.
By default, makeit will compile all C/C++ code files into object files and put those object files into a library. The most common case therefore, where you will need to make reference to a code file in the makefile, is when a code file is to be compiled into a program. This simply consists of you listing in the "PROGRAMS" variable, the names of the program to be created. The name of a program being the name of the code file less any extension. Makeit will automatically determine the name of the actual code file by looking at the languages being used and applying the extension appropriate for that type of language file to the program name. For the case of a code file referred to in the "PROGRAMS" variable in this way, not only will the code file be compiled as a program, it will also be linked against the library created from any other code files in the directory.
When the operation of makeit is further discussed, those code files which you designate through listing the corresponding program name in the "PROGRAMS" variable, will be described as being "program code files". The files which are left over and which when compiled are placed into a library to be used by these programs, will be described as being "library code files". These are the two main file types and cover the majority of situations which will arise. This default model of behaviour therefore means that you will typically only need to change one variable to control how makeit deals with any code files present in the directory, everything else will automatically be taken care of for you. You will never have to write any rules in order to create or manage library archives, or to ensure that a program links against a library within the same directory.
Bundled Modules
Besides the "PROGRAMS" variable, which you may use to distinguish which of your code files are program code files and which are library code files, the only other variable which must be defined in your makefile is the "MODULES" variable. The "MODULES" variable defines what features provided by makeit you wish to use. The philosophy being that you only include what you require in the way of functionality, thus reducing the impact on the performance of makeit by features you do not want.
Each feature you might want to use is separated out into distinct modules. You select the functionality you wish to make use of by listing the name of the module in the "MODULES" variable. A small selection of basic modules is provided with makeit. When you become more experienced at using makeit, you can create your own modules and make use of them also.
Included below you will find the list of the standard modules which are provided with makeit.
Module |
Purpose |
c |
C code |
cxx |
C++ code |
sh |
shell scripts |
lex |
lexical analyser generator |
yacc |
parser generator |
rpcgen |
remote procedure call generator |
Note that by having you list the names of modules you want in the "MODULES" variable, as opposed to you explicitly including a separate file into the makefile, it avoids a problem which can exist in a complicated environment where multiple modules are used. This problem is ensuring that you include the modules in the correct order. When you list the names of the modules in the "MODULES" variable, you can list them in any order, yet internally to makeit they will be included in the order required, thus making your job easier, with even less chance of an error or unexpected behaviour arising.
The Makeit Program
In order to use the build environment to build your source code you need to run the "makeit" program. This program is a shell script which will in turn run the GNU make program. The purpose of the shell script wrapper is to set various environment variables and command line options so that GNU make knows where makeit is installed and thus where the top level makefiles and predefined modules used by makeit reside. This avoids you having to explicitly set this information in your own user environment. All you need to ensure is that you have the program directory into which the "makeit" program was installed, listed in your "PATH" environment variable.
Naming of Makefiles
As the "makeit" program is a shell script wrapper around GNU make, you can use any makefile name accepted by GNU make. The names you can thus use are "GNUmakefile", "makefile" and "Makefile". As tends to be the convention on UNIX platforms, it is recommended that the name "Makefile" be used. Whatever name you use, keep in mind that GNU make will check for the different files in the order listed above. That is, if you have both "GNUmakefile" and "Makefile" appearing in the same directory, the file "GNUmakefile" will be used in preference to "Makefile".
Makefile Structure
Although ordering is not important when you list modules in the "MODULES" variable, for makeit to work correctly your overall makefile must still be laid out in a particular way. This standard structure is necessary to ensure that actions performed by makeit are carried out in the correct order and to ensure that variables which makeit may interrogate are defined before the point they are needed. The standard structure which you must use for your makefile is given below.
# Initialisation. MODULES = cxx include makeit/setup.mk # Local Definitions. PROGRAMS += include makeit/modules.mk # Local Rules.
The standard structure for the makefile is divided into three sections. The sections are separated by the inclusion of two special top level files. These two files are the only files which need to be included explicitly in your makefile.
The first of the included makefiles is called "makeit/setup.mk" and is where makeit is initialised. Any variable definitions which affect the initialisation of makeit or of a module you have selected, should appear in the makefile before this file is included. The section before this point in the makefile is referred to as the "initialisation section" of the makefile.
The second of the included makefiles is called "makeit/modules.mk". The section of the makefile which preceeds the inclusion of this file is referred to as the "local definitions section". Any variable definitions not directly related to the initialisation of makeit or of a module you have selected, should be included here. The "makeit/modules.mk" include line results in the remainder of the makefiles necessary for makeit to run, including the modules you have selected, being included.
The section of the makefile following the inclusion of "makeit/modules.mk" is referred to as the "local rules section". If you ever need to add special dependency information, or wish to add your own rules to the makefile, they must be included in this final section. If you do add in extra rules and do not place them in this final section, makeit may not have carried out some action your rules rely on occurring, by the time your actions are triggered. Therefore, make sure you always put them in this last section and not elsewhere in your makefile.
Running Makeit
In order to get makeit to build something, you need to tell it what you want built. What you want built is abstractly referred to as the target. To build all the programs in a directory, the target you use is "programs". The target is supplied as an argument to the "makeit" program when you run it.
makeit programs
The result of using the "programs" target is that makeit will compile each library code file into an object file and archive the object files into a library. Makeit will then compile each program code file and link the object file produced with the library to create a program executable.
If you have more than one program, but only want to compile one of them, the target you supply to the "makeit" program should be the name of your program. If the name of your program code file was "app1.cc" and you had listed "app1" in the "PROGRAMS" variable as required, you would run the "makeit" program with the target "app1".
makeit app1
As the object file created from compiling the program code file must still be linked against the library, makeit will ensure that the library will be built first. If none of the library code files had previously been compiled, this would result in makeit compiling all library code files. If the library already existed and none of the library code files had been changed, the existing library will be used without makeit having to recompile any of the library code files.
You can build more than one program at a time by listing each of the program names in the target when running the "makeit" program. For example, if the directory contained two program code files, named "app1.cc" and "app2.cc", with the names "app1" and "app2" being listed in the "PROGRAMS" variable as required, the string "app1 app2" would be used as the target.
makeit app1 app2
If you want to check that an individual program code file will compile, but don't want to have to wait for any library code files to be compiled, the target should be the name of the object file which would be produced by compiling that program code file. Thus, if you want to check that the program code file "app1.cc" will compile, the target "app1.o" would be used.
makeit app1.o
This will only result in the object file for that program code file being produced. The object file will not be linked against the library to create an executable program.
As with program code files, makeit can be directed to compile individual library code files. This is achieved by listing the name of the object file which is produced when the code file is compiled.
makeit obj1.o
Multiple code files can be compiled by listing more than one object file as the target. You can even list object files for program code files and library code files together as targets.
makeit obj1.o obj2.o app1.o app2.o
If an individual library code file is compiled, the object file created is not placed in the library immediately. Creation of the library will only occur, when a program that needs to be linked with the library is being built. Alternatively, you can force the library to be created by running the "makeit" program with the target "lib".
makeit lib
File Dependencies
GNU make can only determine the direct dependencies that exist between an object file and a code file. If code files contain header files, and a header file is changed, the code file will not be recompiled. In order that GNU make can correctly determine if an object file or program needs to be recompiled when header files change, dependencies indicating that an object file depends on a header file, need to exist. This would usually require you to maintain a list of such dependencies. In makeit this process is automated, although, you will have to use the target "depend" to trigger the creation of this dependency information. You will need to do this whenever you change the set of include files used by a header file or code file such that dependencies for the object file were changed.
High Level Targets
The targets "lib", "programs" and "depend" are referred to as high level targets. For high level targets there is not necessarily a one to one correspondence between the target name and resultant product. Instead, high level targets can trigger a number of different actions. In addition to these targets, makeit accepts a number of other standard high level targets. A summary of the actions performed by makeit for the most common high level targets is given below.
Target |
Purpose |
lib |
Compiles library code files and creates a library for static linking. |
pic |
Compiles library code files as "position independent code" and creates either a shared library or dynamically loadable module. |
programs |
Compiles program code files and links them with the static library to produce executable programs. If the library doesn't exist, the library code files will be compiled and the library created prior to linking the programs. This target will also create executable scripts for languages where compilation of code files is not required. |
all |
Triggers the "lib", "pic" and "programs" targets. Running makeit without any target, is equivalent to using the target "all". |
clean |
Deletes files that can be recreated by running makeit. This includes the library, any object files and programs. |
mostlyclean |
Deletes intermediate files that were created by running makeit, such as library or program object files. Mainly intended to be a way of reclaiming space without deleting the desired end results of running makeit. |
distclean |
Deletes all files that were created when configuring the package being built or from subsequently running makeit. Intended that it should leave only the files that were provided with the original package. |
realclean |
Deletes everything that could be reconstructed in some way. |
depend |
Updates dependency information for all code files. |
install |
Triggers the "all" target. The library and programs are then copied into an area where they should reside in order to be used by others. Any auxiliary files required by the programs or library will also be copied to their appropriate destinations. |
Compilation Variants
As you write your application, you will go through various development phases. These include where you will be creating and debugging your application, where you will be testing it to see how well it performs and finally when you use an optimiser to get the best performance that you can prior to releasing the application. In each of these phases you will generally need to pass different options to the compiler you are using in order to get it to do different things for you. Having to record these different options in your makefile and changing them explicitly when required is error prone. Two of the problems which arise are, using the wrong options for the compiler in use and mixing object files compiled with different options. That you have to list the options explicitly is also a portability nightmare as different compilers and/or platforms may require different options to achieve the same result.
To assist you in moving between these different phases, makeit defines what are called compilation variants. Each compilation variant maps to the use of a specified set of compiler options. What the options are for a particular compilation variant is defined once when you install makeit. Having the options defined in one spot in this way means that you will always use the correct options for the compiler you are using. You will also not have to go modifying all your separate makefiles when you want to change what you are doing. Further, makeit will keep object files compiled in different compilation variants separate from each other, so there will never be a problem of mixing object files compiled with different options.
Makeit provides a choice of four compilation variants. These are "dbg", "opt", "prf" and "std". A particular compilation variant is selected by defining the "VARIANT" variable. The purposes of the four standard variants are given in the table below.
Variant |
Purpose |
dbg |
The "dbg" variant is generally the default compilation variant and is used during the implementation phase of development. If this variant is selected, flags will be provided to compilers to cause inclusion of support to allow the use of a debugging tool. |
opt |
The "opt" variant should be used when necessary to compile an application for release. When the "opt" variant is selected, flags will be supplied to compilers to enable optimisation. In addition, the preprocessor symbol "NDEBUG" will be defined. |
prf |
The "prf" variant is used when you want to improve the performance of an application through the use of execution profilers. The profiler for which support is included is "gprof". If "gprof" is not supported for a compiler then support for "prof" will be included. This variant is generally only usable on UNIX systems. |
std |
The "std" variant is equivalent to a standard compilation, ie., with no special flags provided to compilers. |
If you define the "VARIANT" variable, the definition must appear in the initialisation section of the makefile.
VARIANT = opt
If you do not define the "VARIANT" variable, that which was defined as the default variant when you installed makeit will be used. Normally, you would not define the "VARIANT" variable in a makefile, instead, you would always work in the default variant. When it is necessary to compile in a different compilation variant, "VARIANT" can be defined on the command line when the "makeit" program is run.
makeit VARIANT=opt programs
As the products resulting from working in one compilation variant have to be kept separate from those produced in another compilation variant, you will not find them sitting in the same directory as the source code after the "makeit" program has been run. What makeit will do is create a separate subdirectory for each compilation variant you work in, with the object files, library and program being placed into that directory. This means that when you go to run your application to see if it works, you will need to use a relative pathname and refer to it as it appears in the subdirectory.
powerpc-apple-darwin-gcc3.3-dbg/app1
The name of the subdirectory created to hold your object files, library and programs is actually dependent on the combination of the host identifier, compiler toolchain identifier and compilation variant. By also including the identifier for the host and compiler toolchain in the name of the subdirectory, not only will the results from working in different compilation variants be kept separate, the results from working on different platforms and with different compilers will be as well. This avoids the problem of accidentally using together object files from different platforms. You also will not have to continually clean your directories in order to be able to compile in a different variant or on a different platform.
GNU Make Options
As the "makeit" program is a shell script wrapper around the GNU make program, you can use any of the options supported by GNU make with the "makeit" program. A summary of the major options are given below. For a more comprehensive discussion of the command line options you should consult the documentation for GNU make.
Option |
Purpose |
-C DIR |
Change to directory "DIR" before reading the makefiles. If multiple "-C" options are specified, each is interpreted relative to the previous one: "-C / -C etc" is equivalent to "-C /etc". |
-d |
Print debugging information in addition to normal processing. The debugging information says which files are being considered for remaking, which file-times are being compared with what results, which files actually need to be remade, which implicit rules are considered and which are applied - everything interesting about how GNU make decides what to do. |
-f FILE |
Read the file named "FILE" as a makefile. |
-h |
Remind you of the options that GNU make understands, and then exit. |
-n |
Print the commands that would be executed, but do not execute them. |
-p |
Print the data base (rules and variable values) that results from reading the makefiles, then execute as usual, or as otherwise specified. |
