Valid
	XHTML 1.1! Valid CSS!
Created 2007-02-16   Modified 2009-04-11
Chelton Evans

proj Makefile Build Tools home

Intro
Assumptions
How to Use the Tool
    Including and managing object libraries
    Calling make directly
    Configuring Compiler Commands
    Managing object compilation
    Corrupt Compilation
    How my coding has improved
Program crashes and gdb
Performance and Motivation
Why not use Makefiles to update themselves
Bash Integration
Issues

Intro

This is a building tool to make Makefiles for a simple C++ environment within Bash.

Features:

There are three main areas. Building the makefile. Compiling the makefile and unit testing/CI feedback.

There are two versions, the main one written in C++ called mkupdate written in C++ see proj/makefilebuildtool, and a bash version called mkupdatebash see proj/ide/MakefileUpdate.sh.

Similarly there is mkerrors0 largely deprecated and mkerrors the C++ implementation.

This is the main document for describing these tools.

Assumptions

How to Use the Tool

Call $ mkupdate in the directory containing the source files. Whenever a file or another header is included the makefile needs to be regenerated. The makefile and messages are output to the screen and the makefile is written to "Makefile".

For example in proj/visit directory.
# This makefile was generated using the build tool found at
# http://www.fluxionsdividebyzero.com/p1/misc/makefilebuildtool.html

CC=g++ -Wall
INC=-I../misclib/ -I../visit/ -I./
OBJ=visitprint.o visitdataC.o main.o
LIB=

main: $(OBJ)
    $(CC) $(INC) -o main $(OBJ) $(LIB)
visitprint.o: ../misclib/typedefs.h ../visit/visitbase.h ../visit/visitdataA.h ../visit/visitdataB.h ../visit/visitprint.h ../visit/visitprint.cpp
    $(CC) $(INC) -c ../visit/visitprint.cpp
visitdataC.o: ../visit/visitbase.h ../misclib/typedefs.h visitprint.o ../visit/visitdataC.h ../visit/visitdataC.cpp ../visit/visitdataC.h
    $(CC) $(INC) -c ../visit/visitdataC.cpp
main.o: ../visit/visitbase.h ../misclib/typedefs.h ../visit/visitdataA.h ../visit/visitdataB.h visitprint.o visitdataC.o ./main.cpp
    $(CC) $(INC) -c ./main.cpp

clean:
    rm *.o *.order *.out gmon.* main

# Re-compile specific parts of the program. e.g.
# $ make del targ=windowscale
targ=__nopattern__
del:
    rm *${targ}*.o; make

At the terminal information is printed about processed files and any unknown files found.

mkupdate
processed: visitdataB.h visitprint.h typedefs.h visitprint.cpp visitdataA.h main.cpp visitdataC.h main.h visitdataC.cpp visitbase.h
unknown:

To make this easy I have a function which captures the first few lines of errors, and inserts the appropriate options.

Compiling the module.
$ mkerrors

id=debug
command=make
libraries=
exitstatus=0
g++ -Wall -I../misclib/ -I../visit/ -I./ -c ../visit/visitprint.cpp
g++ -Wall -I../misclib/ -I../visit/ -I./ -c ../visit/visitdataC.cpp
g++ -Wall -I../misclib/ -I../visit/ -I./ -c ./main.cpp
g++ -Wall -I../misclib/ -I../visit/ -I./ -o main visitprint.o visitdataC.o main.o

The important information to look at the is exitstatus. If this is non-zero then there is a problem.

More detailed information is stored in projcompile.txt, and is used by the CI(Continuous Integration) html reports.

Including and managing object libraries

When compiling the two places of interest are commands at the start of the compiler call and commands at the end. Library linking are commands at the end of the compiler call.

I decided on a very simple model that looks for a file called libraries and appends its contents at the end of the compiler call.

For example to include OpenGL graphics the file "libraries" contains the following line of text.
-lGLU -lGL -lglut

This approach has a per directory compiler linking options approach. This is an issue of portability as different systems have different library linkage.

To address this projlibsave projlibload projlibclear can be used to save, load and clear proj/xxx/library files. [proj/ide/projlib.sh]

For example to set up the libraries on my Mac
$ cd ${projdirectory}/ide       [ $ proj ide ]
$ projlibload librariesMacOSX.txt

Lets say I made some minor configuration changes that I want to occasionally use. Put them in a text file and call to overwrite current library files (as specified). Save your original configuration before to revert changes.

The library configuration format is the module/project directories name, a "," and then the options.

Typical examples
zpr,-framework GLUT -framework OpenGL in MacOSX
zpr,-lGLU -lGL -lglut in Linux
zpr,-lopengl32 -lglu32 -lglut32 in Cygwin

Configuring Compiler Commands

Managing compiler configurations and linkage supported. Edit xml configuration file proj/mkerrorsconfig.txt.

<command> </command> file.cpp <libraries> </libraries>
$   g++ -Wall   file01.cpp   --framework GLUT -framework OpenGL

The start and end of commands can be overridden by the command and libraries tags respectively. Each compiler option has a id which uniquely associates the command and optionally the linkage.

The presence of library file overrides the configuration file and affects only the libraries tag. e.g. set all options to include this library irrespective of their different compile options.

Some reasonable defaults for Linux were given. These can be overridden.

mkerrors   - compiles with "g++ -Wall"
mkerrors clean   - removes *.o and main .
mkerrors gdb   - compiles with "g++ -g -Wall".
mkerrors release   - compiles with
"g++ -DNDEBUG -03 -Wall".

Calling make directly

Advantage: know exactly what is going on. Do this before automating.

Disadvantage: can not use id's/configuration file.

Managing object compilation

If changing strategies for example compiling release code where before you were compiling debug code then delete the object files.
$ make clean

For large projects deleting all object code is time consuming because of the cost in time when you recompile the project.

In big projects for code edits near the root of the dependency tree use Corrupt Compilation.

For leaf compilation - compilation at the leafs of the dependency tree, generally use
$ mkerrors.

Corrupt Compilation

mkupdate

Consider editing code A which depends on code B. Code C also depends on code B. Now if we edit code B then code A and C will be recompiled.

For large programs the results in unnecessary recompilation of areas of code which are not used.

To correct this support for limited(or corrupt) compilation is supported. Here only the target object and its dependencies are recompiled. As long as other parts of the application are not used everything is ok.

Example
$ mkupdate - to build the Makefile
$ make - to build the object code and binaries
$ mkupdate corrupt=randomtest.o - changes the makefile to
    main: randomtest.o
Edit random.h, now we can recompile the application without other object files which depend on random.h being recompiled.
$ make - compiles randomtest.cpp only.

The advantage of what I have called corrupt compilation is that as the size of the source code increases, the compilation time need not increase. This is of course in situations where this technique is useful.

The client then uses the tool as follows. First build the application.
mkupdate
make
This ensures that all the object files exist. Now rebuild the makefile by adding the target dependency as an argument to mkupdate.
mkupdate targ.o
Freely edit targ.o's source dependencies and compile in the usual way. When finished convert back to the correct makefile with mkupdate.

In the previous example there was over 3 times gain in compilation time, and this will increase as the application grows.

The technique of corrupt compilation results in significant compilation time savings. The project can grow (independently) without effecting the ability to compile low level source files.

Performance and Motivation

This makefile update tool is excellent value for several reasons. Firstly it automates one of the most unpleasant and error prone programming tasks - that of generating the makefile.

I first wrote the makefile generation utility in bash (mkupdatebash) and eventually in C++.

In practise updating the makefile is not done less frequently compared with compiling, but it is common so having the process as fast as possible.

Motivation comes from being dissatisfied with complex Makefile technology - it should be easy to debug a Makefile, by making everything explicit (what you see is what you get) and restricting the use of Makefile to a subset of its capabilities, dependencies can be tracked.

mkupdatebash

The bash version is still used to bootstrap mkupdate so that it can be compiled (built).

mkupdatebash:
The way dependency is calculated is recursive and does not re use dependency information.

How my coding has improved


The Corrupt Compilation can lift performance as it allows the programmer to take direct control of compilation dependency. You know when to use this tool when you see files that you are not using continually being recompiled.


Different environments lead to very different wait times for building the makefile.

For example making the Makefile in the proj/graphicslib/ module takes a few minutes under Windows in Cygwin on an old P2, but takes 20s on a Pentium D with Fedora 6 os.

Why not use Makefiles to update themselves

I was unhappy with the complexity of Makefiles. In my opinion a technology should be easy to use and seeing the errors is critical.

I had a problem with the GSL makefile and it was so complicated that I could not debug it. Similarly Qt's makefile generation tool is pretty complicated too. So for now I want a simple tool that generates makefiles which are easy to see what is going wrong.

To this effect my tool explicitly writes/realizes dependency paths - uses variables simply and only a few. Effectively using a simple subset of Makefile technology and throwing the rest away.

To conclude my aim was to generate a simple makefile so that if there is a problem it can easily be seen. For example if a source file was not processed it would not appear in the makefile so the makefile generated was wrong. Then back track by looking at the error messages, back track further to the logic of the program if necessary.

For an alternative solution with Makefiles which I do not recommend see "GNU Make" 3rd edition page 33 where dependency information is included in the Makefile, pages 149+.

Program crashes and gdb

When things go wrong gdb ddd is there. Delete the old object and executable code, then recompile with -g option. Then enter a gdb session.

$ make clean
$ mkerrors gdb
generated the new code.
Start gdb, kill annoying confirm messages, load and run the program for a back trace.
$ gdb
(gdb) set confirm off
(gdb) file ./main
(gdb) r prog=34
(gdb) bt
(gdb) kill

This is generally enough to solve most of my bugs as I am an intuitional programmer I put the software in my head.

Bash Integration

This is a command line IDE(Integrated Development Environment) in a Bash OS.

proj/makefilebuildtool is compiled in release and copied to mainrelease. .bashrc loads the integrated bash code by sourcing in proj/ide/ide.sh which defines mkupdate.

[bootstrapping mkupdate] To compile the makefile build tool the bash version is used to build ${projdirectory}/makefilebuildtool. See proj/ide/MakefileUpdate.sh.

Issues