Back to TOC Features


Creating Makefiles with the Korn Shell

Vladimir Batov

By obeying a few common conventions, you an get your makefiles constructed for you.


Introduction

Raise your hands, everyone who loves writing and maintaining makefiles during development, please. I cannot see many. I cannot see, actually, any hands raised. Well, do you create a separate directory for each sub-project (a library or executable that requires a separate makefile)? If your answer is "Yes," you can take advantage of the makefiles built for you automatically, with a Korn shell script called mkmk [1] . All it takes is a little discipline, preparatory configuration, and help from the Korn shell. Of course, you still have to start the Korn shell script to generate the makefile. It's okay if your answer to the above question is "No"; you still have time to join the happy "Yes" team.

Preliminaries

Before starting another development project, it pays to get a little organized. Suppose your project directory is named project. The project will probably consist of sub-projects — quite a few interrelated libraries and probably a few executables. It is quite logical to allocate a project/lib subdirectory for .a and .so library binaries, project/include for header files, project/bin for executables, and project/src for sub-project source files. This layout is not compulsory. I just use this project hierarchy as an example to demonstrate configuration steps. This organization is intuitive and reflects the common Unix hierarchy. Furthermore, this layout reflects a structural approach to projects and aids orderly development.

The only compulsory part of my scheme is that each sub-project must have its own separate directory. For example, using the hierarchy above, you would have to create a project/src/mama directory to build a libmama.so library.

A Sample Project

Imagine a project that consists of three sub-projects. There are two libraries: a shared libmama.so, and a static libpapa.a. There is also an executable exec_me. Following the agreed-upon project layout, you distribute the .c implementation files for the sub-projects as follows:

sub-project  .c files repository
-----------  -------------------
libmama.so   project/src/mama
libpapa.a    project/src/papa
exec_me      project/src/child

Again it is not compulsory but very handy to put .h declaration files in a subdirectory project/src/mam/include and to create a project/include/mama symbolic link to the project/src/mama/include. Then if some other part of the project, say, 'papa' needs to access declarations of the 'mama' library, you can do it as elegantly as:

#inlude "mama/header1.h"
#inlude "mama/header2.h"
#inlude "mama/header3.h"

Now you can build the project by typing the following commands:

cd project/src/mama
mkmk -shared;  make
cd project/src/papa
mkmk -static;  make
cd project/src/child
mkmk -exec exec_me;  make

(Don't think you are forced into the above layout. You are absolutely free to use your own layout. mkmk will handle it.)

Every sub-project supplies enough information to have makefiles created automatically. The first and second lines create makefiles for the libmama.so and the libpapa.a libraries respectively. Since the library names are not provided explicitly, mkmk uses the library directory names — mama and papa — to construct the libmama.so and libpapa.a library names.

The last line had to explicitly specify the exec_me executable name because the child directory name does not match the name of the executable to be created. Such is the price for being inconsistent.

How it Works

If you provide no -exec, -shared, or -static build directive, mkmk will try to analyze the source files in the current directory, looking for the main keyword. Depending on whether a main was found or not, mkmk applies either the -exec or

-shared directive respectively. If you don't explicitly specify a target name, mkmk uses the current directory name as the default.

Every time you add or delete a .c file, change the #include set within the files, or create a completely new sub-projects, all you have to do is run mkmk.

A Look at the Makefile

Running mkmk produces a makefile similar to the one shown in Listing 1. mkmk sets the TOOLS_DIR variable to the name of the directory the script resides in. mkmk expects to find the mkmk.inc, libraries, and includes support files in the directory.

If the PROJECT_DIR environment variable is set while executing the mkmk command, mkmk uses its value as a base project directory. Alternatively, if you are in a project/src/mama subdirectory, mkmk uses the 'project' part as the project directory. Otherwise your $(HOME) directory will be used as the default project directory.

Conclusion

I believe I have demonstrated that an accurate and consistent "object-oriented" approach to project layout can greatly help by automating routine tasks. o

Acknowledgement

Thanks to Jeff Johnson a script guru for the original idea.

[1] The mkmk script is available on the CUJ ftp site. See p. 3 for downloading instructions.

Vladimir Batov is a software developer living in Australia. You can reach him at vladimir@adacel.com.au.