Have you ever wanted a generic, simple, easy to configure, flexible makefile for everyday development? Don’t want to have to invoke automake/autoconf or construct new makefiles by hand for each program/project? This may be what you’re looking for:
edam’s general-purpose makefile!
- Suited to development, not distribution
- Easy configuration (documented below, reference in the makefile itself)
- Support for C, C++, assembly (nasm only) and D (gdc only) source files
- Build modes (release, debug and profile mode)
- Subdirectories/sub-projects (for multiple build targets and automatic recursion when building/cleaning)
- A sophisticated dependency file generation system
- Can live alongside another build system, such as a GNU autotools one.
This is only really meant to facilitate development and it is obviously still worth learning to use the GNU autotools. If you want a quick-start guild for autoconf and automake, you should read this great little guide instead. Also, this book, while a little uncoordinated IMO, is a much more thorough guide.
You can get it here…
Usage is simple. Create a file named “Makefile” alongside your source code. In the Makefile, specify your project’s settings (by defining some variables that describe it; documentation follows) and then include edam.mk at the end. It couldn’t be simpler.
Here is an example Makefile for a test program:
SOURCES = main.cc foo.cc
LIBRARIES = bar
As you can see from the include line at the end, I keep the general-purpose makefile at ~/src/edam.mk. This means I can easily replace it globally when bugs are fixed and improvements made. You can just as easily keep a separate copy of edam.mk with each project’s source code though, if that is preferred.
First, a couple of points:
Whenever a setting is “boolean”, you should set it to “1” to enable it and leave it unset (or set to an empty string) to disable it. Setting it to “0” will not unset it.
There is reference documentation at the top of edam.mk for all configuration variables to save you having to refer to this site.
Ok, on to the settings…
The TARGET variable specifies the name of the target file. By default an executable is built, so if you want to build a static or a shared library you will need to set the boolean MKSTATICLIB or MKSHAREDLIB variables.
For example, to build your shared libtim.so, you might go:
TARGET = libtim.so
You can only build one target per Makefile. If you need to build more, use the SUBPROJS variable (see below).
Also, you don’t need to specify the “.so” or “.a” suffixes in the TARGET as they are automatically appended. The same is true for the “lib” prefix for shared libraries. If you don’t want to have “lib” automatically prepended to the TARGET, you can set the boolean NOLIBPREFIX variable.
Specifying Build Input
Specify all source files you want to build and libraries you want to link against in the variables SOURCES and LIBRARIES. Both variables are space-separated lists.
LIBRARIES = gl glu
How a file is compiled depends on it’s extension. The following extension types are recognised:
|C source files||.c|
|C++ source files||.cc, .C, .cpp|
|assembly source files||.s, .S, .asm|
|D source files||.d|
While you can happily mix different source file types in the SOURCES variable, you can not have two files named the same but with a different extension (such as “foo.cc” and “foo.c“). When compiled, they would both produce files of the same name (such as the object file “foo.o“).
Note that, as with the -l linker option, you don’t use a “lib” prefix when specifying LIBRARIES.
When the target is built, you can also tell the linker that you want to statically link against any libraries specified in the LIBRARIES variable. This is usually not desired (it may make the target very large and it negates the benefits of using shared libraries) but can be achieved by setting the boolean LINKSTATIC variable.
Building sub-projects and subdirectories
You may also want to build sub-projects or subdirectories. These can be specified with the SUBPROJS and SUBDIRS variables. Sub-projects and subdirectories are always built before the target since the target may depend on them (and subdirectories before sub-projects for the same reason). Both the SUBPROJS and SUBDIRS variables are space-separated lists.
The purpose of sub-projects is to allow you to build more than one target in a directory. Since you can only specify one target in your Makefile, the way to build more than one it is to create a separate makefile for each target and specify these makefiles as sub-projects in the main Makefile. So, for example, you might create the makefiles “proj1.mk” and “proj2.mk” which each build their own target. Then, in the main Makefile, you can specify proj1 and proj2 as sub-projects (note that “.mk” is automatically appended to sub-projects in the SUBPROJS variable).
SUBDIRS = subdir1 subdir2
would cause make to be run four times: first in the subdirectories subdir1 and subdir2 and then with the makefiles proj1.mk and proj2.mk.
Additionally, when subdirectories are built, if a file named “emake.mk” exists in a subdirectory, then this will be used instead of the default Makefile (or other files) that make defaults to using. This is to allow the use of this general-purpose makefile alongside other build systems.
If you need more control over build flags, use the following variables:
|CPPFLAGS||passed to the C, C++ and D compiler|
|CFLAGS||passed to the C compiler only|
|CXXFLAGS||passed to the C++ compiler only|
|DFLAGS||passed to the D compiler only|
|ASFLAGS||passed to the assembler|
|LDFLAGS||passed to the linker before the list of object files|
|LDPOSTFLAGS||passed to the linker after the list of object files|
LDPOSTFLAGS = `pkg-config --libs gtkmm-2.4`
When running make, there are three build modes:
- release — this is the default. Output is optimised and symbol information stripped
- debug — for use in everyday development with gdb.
- profile — for profilling with gprof
Specifying the Build Mode
With no overriding options, the project will build in release-mode. There are several ways to initiate a build other than the default. This allows for some fairly flexible configurations. You can:
- Create the environment variables DEBUGMODE and PROFILEMODE and set them to the value “1“, like this:
Or, alternatively, unset them like this:$ export DEBUGMODE=1$ unset DEBUGMODE
- Specify a build mode on the make command line, like this:
or, like this:$ make DEBUGMODE=1This overrides any environment variables set (as above).$ make DEBUGMODE=
- Specify values for DEBUGMODE or PROFILEMODE in your Makefile to hard-code the build mode (although this is probably a less preferable option).
The arrangement I would suggest for the purposes of development is to add the line
to your ~/.bashrc so that you are always building in debug-mode by default. Then, when you want to build in release-mode, run make like this:
or to build in profile-mode, like this:
Build Mode Specific Files
When building your code, object files are generated. The release-, debug- and profile-mode versions of these files must be kept separate, as must the resulting executable or library.
To differentiate between build modes, debug-mode files have “_d” appended to them and profile-mode files have “_p” appended to them. So, for example, “test.cc” will compile into “test_d.o” in debug-mode. This, in turn, will compile into the executable “test_d“. Similarly, in profile-mode, “test.cc” will compile in to “test_p.o” which will become part of the executable “test_p“.
In addition to the .o files, .dep files are generated to track the build dependencies between files. Unlike the .o files, these are not generated separately for each build-mode. In fact, they are only built in debug-mode.
In a nutshell, dependency files are built as a by-product of compilation (rather than separately to it), do not include system headers, and include dummy targets to prevent make complaining when depended-upon files are deleted or renamed. For more information about the dependency file generation system, see this excellent article on advanced auto-dependency generation. The full system outlined in the article is implemented with the slight improvement that dummy targets are created at compile-time, not in post-processing by a rather unsightly sed invocation. That said, the sed command is still used for other, less-able compilers/assemblers.
Finally, dependency files are not (currently) yet generated for D.
If you’ve used make before, you will know that you can specify a “goal” when running make, like this:
The full list of goals for the general-purpose makefile are:
|all||the default; builds any subdirectories and sub-projects and then any target|
|subdirs||builds the subdirectories (but not any target)|
|subprojs||builds the sub-projects (but not any target)|
|target||builds the target (but not any subdirectories or sub-projects)|
|run||builds the target (but not any subdirectories or sub-projects) and, on success, executes it|
|clean||removes intermediate build files for the target only|
|clean_all||removes intermediate build files for the target and any subdirectories and sub-projects|
|<subdir>||you can specify a specific subdirectory to build (so long as it is defined in SUBDIRS)|
|<subproj>||you can specify a specific sub-project to build (so long as it is defined in SUBPROJS)|
|<file>||you can also specify any object files, or the target, as with most makefiles|
Co-existence with GNU autotools
If you are using this general-purpose makefile on a project that you want to release, it is likely that you will want to also use the GNU autotools along side it and package the familiar configure script. This, in turn, is likely to generate a Makefile of it’s own, which would obviously clash with the one you have created that uses this general-purpose makefile. Here is how I get them to play nicely together:
Firstly, I rename all the Makefiles that use this general-purpose makefile to emake.mk. If a subdirectory mentioned in the SUBDIRS variable contains a file named emake.mk, then it will be used instead of that subdirectory’s Makefile. This frees the Makefiles for use with GNU autotools, but creates another problem. Where before you would simply have needed to type
in any directory or subdirectory, you now have to type
So, secondly, I also created the bash alias emake that tells make to use the emake.mk makefile:
If you discover any bugs/issues, I would like to hear about them. Drop me an email with the details. I am also open to suggestions on how to improve this makefile.
You can obtain the latest development code from the bazaar repository at: