OSE - Makeit User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Larger Projects
OSE - Makeit User GuideLarger Projects

Larger Projects

1 Introduction

Although the core of makeit is targeted at supporting work within a single directory, this does not restrict makeit being used on projects spanning multiple directories. This chapter describes some of the support in makeit for dealing with larger projects.

2 Using Subdirectories

With a large project, different parts of the project will be scattered across multiple directories. The ways in which a project can be organised are many and varied. No matter what way a project is organised, it is important that information about the order in which parts of the project are built, and how the different parts of the project relate to each other, is recorded. With makeit, the order in which parts of a project are built can be controlled through the SUBDIRS variable.

As an example, consider a project which consists of two parts, a set of programs, and a library which is used by those programs. The directory structure of this project may look something like that shown below.

  src ----- lib
|
--- bin ----- program1
|
--- program2
|
--- program3
As the programs require the library to be built first, you need to ensure that makeit is run in the `lib' directory, before it is run in each of the directories where the program source code is located. To do this you will need to add a makefile into the `src' directory, and also into the `bin' directory. These makefiles are in addition to the makefiles you would already have in the `lib' directory and each of the program directories.

The contents of the makefile you place in the `src' directory should be:

  include makeit/init.mk

SUBDIRS := lib bin

include makeit/modules.mk
The contents of the makefile you place in the `bin' directory should be:

  include makeit/init.mk

SUBDIRS := program1 program2 program3

include makeit/modules.mk
You should also remember that you will need to add,

  CPPFLAGS := -I../../lib
LDLIBS := ../../lib/$(MK)/lib.a
to each of the makefiles in the program directories. This will allow the compiler to find the header files for the library, and the library itself. Once you have added these changes, you will be able to run the command `makeit all' from the `src' directory. When executed with the `all' target, makeit will visit in turn, each of the directories listed in the SUBDIRS variable, and execute the `all' target in the directory. This will result in the library being built, then each of the programs.

In addition to the `all' target, there are a number of other targets, which when used, will result in makeit visiting directories listed in the SUBDIRS variable. Of these targets, those which are applicable to this example, are the `clean', `mostlyclean' and `depend' targets. The first two targets will allow you to totally, or partially remove the products of a build from all directories in the hierarchy, by executing makeit from the `src' directory only. The last target will allow you to update dependency files in both the library directory and program directories.

If you list `install' in the MODULES variable in each of your makefiles, you will also be able to use the `install' target with makeit when executing it in the `src' directory. If you have configured the makefiles in your program directories appropriately, this will allow you to run `makeit install' from the `src' directory and have everything built and installed in one operation.

An additional module which introduces a target which will navigate into subdirectories is the `check' module. By listing `check' in the MODULES variable, it becomes possible to run `makeit check' from a top level directory, resulting in any tests which exist, being run.

2.1 Project Builds and Variants

When makeit visits each of the directories in a project, it will use the value of the VARIANT variable, which is defined in the makefile contained in the directory, to determine which variant should be used in the directory. If a makefile does not define the VARIANT variable, makeit will use the default variant in that directory. This means that if you define the VARIANT variable in the top level makefile, its value will not be inherited by subdirectories when makeit visits them.

To override this behaviour, and build the complete project under a particular variant, you need to define the VARIANT variable on the command line when you run makeit. For example:

  makeit VARIANT=prf install
When a variable is defined in this way, the definition will be inherited by subdirectories when makeit visits them. If you need to force a directory to always be built under a particular variant, even if the VARIANT variable is overridden on the command line, you should define the VARIANT variable in the makefile of that directory, in the following way.

  override VARIANT := opt
The use of the `override' directive will ensure that the value of VARIANT variable defined on the command line will not override that defined in the makefile.

If you want the target you supply to makeit to be applied under all variants, then you can set an option in the MAKEIT_OPTIONS variable. The name of the option which should be listed in the variable is `navigate_all_variants'. For example:

  MAKEIT_OPTIONS := navigate_all_variants
You should only use this option in a top level directory, in which there are no source code files.

2.2 Makeit Subdirectory Creation

Even though nothing is generated in the `src' and `bin' directories, makeit still creates a subdirectory into which it would have placed products of the build if something had been generated. To disable the creation of these empty directories you can add the following definition in the initialisation section of your makefile.

  NOMK := YES

3 Library Combination

If a library is large, it is often necessary to split it over multiple directories so that it is more manageable. When makeit is used, this will result in separate libraries in each directory. To recombine each of these separate libraries into a single library, the `combine' module can be used.

The directory hierarchy you would use in this case is,

  src ----- lib ----- lib1
|
--- lib2
|
--- lib3
where the directories `lib1', `lib2' and `lib3', are the directories containing each part of the library. You should not place any code files or header files directly into the `lib' directory. However, you will need to add a makefile to the `lib' directory. The contents of the makefile should be:

  MODULES := combine

include makeit/init.mk

SUBDIRS := lib1 lib2 lib3

include makeit/modules.mk
Executing makeit in the `lib' directory with the `combine' target, will result in makeit visiting in turn, each subdirectory listed in the SUBDIRS variable and building the library in that directory. Makeit will then combine each of the libraries in the subdirectories into a single library, and place the final library into the makeit subdirectory of the `lib' directory.

As the SUBDIRS variable is used to define the subdirectories containing the libraries to be combined, the `all' target can also be used with makeit in the `lib' directory. When makeit is executed with the `all' target in the `lib' directory, makeit will visit each subdirectory as before, but will build any programs in that directory, as well as the library. After visiting all the subdirectories listed in SUBDIRS, it will combine the libraries from the subdirectories into one library. As the combined library is only created after visiting all directories, and building all programs, the programs in the subdirectories cannot link with the combined library.

If not all libraries in subdirectories listed in the SUBDIRS variable should be combined, the NONLIBDIRS variable can be defined. An example of where this may be required, is the case where you have a directory which contains test programs only, and does not contain a library. In this case you should list the name of that directory in the NONLIBDIRS variable. For instance, if the name of the directory was `test1', you would have the following in your makefile.

  SUBDIRS := lib1 lib2 lib3 test1
NONLIBDIRS := test1

3.1 Restrictions Imposed on Naming

Due to limitations with library archives on the majority of UNIX systems, makeit imposes restrictions on the names which you can give your code files, when using the `combine' module. The restriction is that all object filenames should have a maximum length of 14 characters. If this is not adhered to, object files from libraries in a subdirectory will not be included in the combined library. This occurs as the names of object files with greater than 14 characters are truncated when placed into a library. Makeit does not cater for names which have been truncated when placed into the library, so the maximum length of 14 characters applies.

A further restriction, is that object files must have a unique name within the final combined library. This prevents you from using the same file name in two different library subdirectories. If this restriction is not adhered to, all but one of the object files generated from the similarly named files will be left out of the combined library.

3.2 Combining Shared Libraries

If you have enabled the generation of shared libraries in each of the library subdirectories, you can also enable the generation of a combined, shared library. To do this, you would add the following definition to the initialisation section of the makefile in the `lib' directory.

  MAKEIT_OPTIONS := shlib
With `shlib' listed in the MAKEIT_OPTIONS variable, a combined version of both the normal library and the shared library will be created, when the `combine' target is used. By default, a standard archive of the position independent object files will not be created when creating a combined shared library. If a second level of library combination is done, it is necessary to list the option `export_shared_objects' in the CLOSURE_OPTIONS variable.

  CLOSURE_OPTIONS := export_shared_objects
This tells makeit to create the archive of position independent object files, thus allowing a second level of library combination to be performed for a shared library.

3.3 Template Closure with Cfront

If you are using a cfront based C++ compiler, makeit can perform template closure on the combined library. What this does is to force the expansion of any templates which are required by the library, and then incorporates the expanded templates into the library. Rather than you having to incur the overhead of expanding the templates every time you use the library, template closure means that it only has to be done when the library is created.

Although this would reduce your compilation times, when you come to develop applications, template closure should not be used on all libraries. You should only form template closure on libraries that you will always use. Consider the following set of libraries and their relationships.

  OTCLIB ----- OUXLIB ----- LIB1 ----- LIB2
|
--- OPT1 ----- OPT2
|
--- OPT3
In this example, the chain of libraries ending in `LIB2' represent those libraries which you always link into your application. The others are optional libraries. When you form template closure on a library, any libraries which must be used with that library, must also be used in the closure. When you form template closure on `LIB2', you must therefore tell makeit about the libraries `OTCLIB', `OUXLIB' and `LIB1'. If you do not do this, templates will be expanded into `LIB2' which are already expanded into the other libraries. When you come to use the libraries, this can result in the linker complaining about multiply defined symbols and stopping.

If you also formed template closure on the optional libraries, because they do not use `LIB1' and `LIB2', and are not included in the closure, you can have templates being expanded into the optional libraries which are already expanded into your main libraries. You would only detect that this has happened at some later time, when you came to use your optional libraries in conjunction with your main libraries. Therefore, you should only form template closure on your main stream libraries with which you always link your applications.

If you have decided that you should be forming template closure on a library, you should add the following definition to your makefile.

  COMBINE_OPTIONS := closure
In addition, you will need to add definitions to your makefile to let the compiler know where to search for include files, and also which libraries should be used, when forming closure. When specifying the location of the libraries, you should use the LDLIBS variable and absolute or relative pathnames. Do not use the `-l' option as versions of some C++ compilers do not check libraries defined using the `-l' option for expanded templates.

3.3.1 Undefined Symbols

When closure is peformed on a library, the compiler attempts to resolve all symbols. If there are undefined symbols related to template classes, it will attempt to expand those template classes. If undefined symbols exist which the compiler cannot create by expanding templates, the compiler will stop prematurely. This can occur when there are still template classes which need to be expanded, resulting in complete closure not occuring. Failure of the compilation due to undefined symbols will also cause the build to fail as makeit will stop at that point.

To prevent makeit stopping when there are unresolved symbols at the time of closure, list the option `ignore_undefined_symbols' in the CLOSURE_OPTIONS variable. Alternatively, you can provide a code file containing function stubs or variable definitions, for those symbols which are undefined. The name of this file should be listed in the variable PTDUMMYS. The file will be compiled as C++ code prior to closure and the resulting object file used in the closure. The file does not need to have an extension appropriate for C++ code, as a copy of the file is made, renaming it in the process.

3.3.2 Object File Prefixes

When the C++ compiler expands templates, it can generate object files with names greater than 14 characters. Because the names would be truncated if placed directly into the library, the object files are renamed before being placed into the final combined library. New names for the files are created by numbering the files from `1' onwards and adding back on, the `.o' extension. For example, if there were originally three object files, they would subsequently be named, `1.o', `2.o' and `3.o'.

If the library is ever unpacked into a directory along with other libraries also containing expanded templates, some files will be overwritten. To avoid this, you should define the variable CLOSURE_PREFIX in your makefile to a symbol which uniquely identifies your library. For example,

  CLOSURE_PREFIX := mylib_
With this defined, the object files would be named `mylib_1.o', `mylib_2.o' and `mylib_3.o'. As long as different libraries use different prefixes, no object files would ever be lost.

3.3.3 Template Map Files

When the C++ compiler expands templates, it needs to know which files contain the definitions for the templates, and also the types being used as arguments to the templates. This information is collected by the C++ compiler, when it is compiling each file in a library, and is placed into a file contained in the repository directory where the compiler is being run.

So that the C++ compiler can find this information, makeit will give the location of the repository directories in each of the subdirectories listed in the SUBDIRS variable, to the compiler, when it is forming template closure on the library. This however, will only work correctly if you have not compiled any programs in the subdirectories, which resulted in expansion of templates into the subdirectory repositories. The reason this will not work, is that the C++ compiler will see that the template has already been expanded into one of the subdirectory repositories, and will not expand it into the repository directory where closure is being carried out. Since makeit only looks in the current repository for object files, and no others, the expanded templates in the subdirectory repositories will not be included into the combined library.

The obvious way to avoid this problem is to clean out all the subdirectories, by running `makeit clean', before running `makeit combine'. This works, as the `combine' target does not result in any programs being built, so no templates would be expanded into the subdirectory repository directory. Unfortunately though, this still will not work in all cases, due to bugs in some C++ compilers, which result in the compilers giving internal fatal errors when the template map files contain duplicate information.

The most reliable way of letting the compiler know where to find the files it needs, is to stop it from looking at the repositories in the subdirectories, and provide your own template map file giving the locations of the files. To prevent the compiler from looking in the repository directories of the library subdirectories, you should add `ignore_repositories' to your definition of the MAKEIT_OPTIONS variable. For example:

  MAKEIT_OPTIONS := ignore_repositories
You should now construct a template map file listing all types in the library, and the location of the files containing the definitions of those types. An example of a template map file is included below. You should, however, consult your compiler documentation to determine what you should be placing in this file.

  @dec MyClass
<myclass.hh>
Finally, you need to tell makeit where your template map file is. If you have called your template map file `PTMAP', you will need to add the following definition to your makefile.

  PTMAPS := PTMAP
If other libraries being used, when doing the closure also have template map files, you should add the pathnames of those files to the PTMAPS variable also.

3.3.4 Static Members in Templates

If you have enabled the creation of a combined shared library, as well as the normal library, closure will be carried out twice. The second time it is carried out, the compiler will be told to generate PIC objects suitable for inclusion in the shared library. On some platforms, this may not result in a useable library. This will occur if you have static member variables in your template classes, which require the data to be initialised to non zero values, or which require a constructor to be run on the data.

The problem here is that it is not possible, when expanding templates, to split out the static member initialisers into a separate file, which can then be included into the static portion of the shared library. The best recommendation is to avoid the use of static members in templates, where the data has to be initialised to anything but zero. If you want your code to be portable to all C++ compilers, you should actually avoid static members in templates altogether, as not all C++ compilers support them.

3.3.5 Minimal Expansion

When the C++ compiler forms template closure on the library, it will expand each template class into a separate object file. In addition it will expand all member functions of the template, even if they are not required by the library. This is the preferred approach; however if you do not want member functions, which are not used by the library, to be expanded, you can define the `minimal_expansion' option in the CLOSURE_OPTIONS variable. This will result in only those member functions used, being expanded; however it may take the compiler longer to form closure, as it may have to recompile the same template a number of times, as it works out that additional member functions are required. If the user of the library uses any member functions of the class not expanded into the library, the overhead of having to generate them will now be incurred.

By default, the `-pta' option is passed to the C++ compiler by makeit when forming template closure. The `minimal_expansion' option prevents the `-pta' option being passed to the compiler.

3.3.6 Separating Functions

A further option, is to expand each of the member functions of a template into separate object files. This is enabled by listing `separate_functions' in the CLOSURE_OPTIONS variable. Again, this will result in closure taking longer. It will also result in the library being larger, as each object file will have some overhead associated with it.

The `separate_functions' option is equivalent to supplying the `-pts' option directly to the compiler. Most C++ compilers do not support the `-pta' and `-pts' options being passed to the compiler at the same time. If your compiler behaves in this way you will need to define the `separate_functions' option in conjunction with the `minimal_expansion' option.

3.4 Closure with the Template Preprocessor

Closure can be simulated when using the template preprocessor. To do this you need to know in advance, which templates are used internally to your library and need to be expanded. Once you know which templates need to be expanded, you need to create a special header file to be included into the code file of any program which will link with your library. The easiest way to manage this is to include the special header file into each of you own header files. By including the special header file into your own header files, users of your library will not have to do it themselves.

The contents of the special header file will be a list of macros giving the names of each of the templates which will be expanded into the library. The purpose of the macros is to let the template preprocessor know that it should not try and expand these templates a second time. The name of the macro is OSE_MARK_COMPILED and the list may look something like:

  #ifdef __OSE_TEMPLATES__
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED vector<double>;
OSE_MARK_COMPILED vector<char>;
#endif
If you are doing all your work in a single directory, you should now create a file into which the templates will be expanded. This file must include the header files for any template classes which are to be expanded, and the header files for any types which are supplied as arguments to the templates. Prior to the header files being included, you should define the symbol EXPAND_TEMPLATES. Following the inclusion of the header files you should list macros to expand each of the template classes you wish to expand. The complete code file may look like the following:

  #define EXPAND_TEMPLATES 1

#include <vector.h>

#ifdef __OSE_TEMPLATES__
OSE_PRECOMPILE vector<int>;
OSE_PRECOMPILE vector<double>;
OSE_PRECOMPILE vector<char>;
#endif
This example shows more than one template being expanded into a file. If you wanted to, you could expand each template into a separate file.

If the template you are expanding derives from another template class, or uses other templates, you will need to expand each of the template classes. For example, consider that you have a template class myvector which derives from the template class vector, and you need to expand it with the argument of type int. In this case, your special header file would need to list:

  #ifdef __OSE_TEMPLATES__
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED myvector<int>;
#endif
and your code file would contain:

  #define EXPAND_TEMPLATES 1

#include <myvector.h>

#ifdef __OSE_TEMPLATES__
OSE_PRECOMPILE vector<int>;
OSE_PRECOMPILE myvector<int>;
#endif
In your makefile, if you need to know if the template preprocessor is being used, and that this method of template closure will work, you can check for the presence of the __OSE_TEMPLATES__ variable. For example, if the name of the file into which you were expanding templates is called `compile.cc' you could include in your makefile, the following:

  ifndef __OSE_TEMPLATES__
EXCLUDE := compile.cc
endif
The effect of this is that when a C++ compiler is being used which does not use the template preprocessor, the file into which you are expanding templates would be ignored.

If you have split your library over multiple directories, and are using the `combine' module to create one library, you could create a new directory in which to expand the templates. If you have programs in any of the library subdirectories, you will need to hide the list of OSE_MARK_COMPILED macros. This needs to be done, otherwise the template preprocessor will think they have been expanded when at that stage of the build process they would not have. A way of hiding the macros, is to surround them with a `#ifndef'. For example:

  #ifndef MYLIB_BUILD
#ifdef __OSE_TEMPLATES__
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED myvector<int>;
#endif
#endif
When compiling in any of the library subdirectories you would define the preprocessor symbol MYLIB_BUILD. This would be done by setting the CPPFLAGS variable.

  CPPFLAGS := -DMYLIB_BUILD
The name of the symbol you use to hide the macros should be uniquely identified with your library, and not a symbol which others are likely to use.

4 Cfront Template Repositories

An alternative to expanding templates into a library is to create a template repository, when developing your application. This involves creating a directory, into which all object files from libraries being used will be extracted. The C++ compiler is then used to form closure on the object files, resulting in expansion of any templates required into the compiler repository directory. In the directory, in which your application is being developed, you add a definition to your makefile, to tell the compiler to use the repository directory which has been created.

To set up a template repository, you should create a new directory and add into it the makefile:

  MODULES := repository

include makeit/init.mk

include makeit/modules.mk
You should add to this makefile, exactly the same compiler definitions and flags you are using in your application directory. If you are including any library modules, you should also list those. If `-I' preprocessor flags are not included in the same order as in the application directory, the C++ compiler will ignore this repository, when you go to compile your application.

When you list the libraries with which your application links, you must use the form `lib.a' and not `-llib'. This is necessary, as makeit will only extract object files from libraries listed in the first form. If you are using a library module, you can usually force the library name in the first form to be used, by listing `use_static_libraries' in the options variable for that module. For example, if you are using the `ose' module you would need to include in your makefile:

  OSE_OPTIONS := use_static_libraries
When forming closure, since the C++ compiler has not actually seen any of the library code files, it will not know which header files to include, to get a specific class. As a result, it will fall back on its default method of finding the appropriate header file. If you do not give your header files the same name as your class, or you locate header files in a subdirectory, the default search mechanism of the compiler will fail. To avoid this, each library which you list, should provide a template map file, that lists the types declared in the library, and the header file which contains the definition of each type.

If you are using a library module, they should automatically define the location of the template map file. If the library you are using is your own, or the library provider did not provide a template map file, you will need to create one. Once you have created this file, you should list the names of the template map files in the PTMAPS variable. For example:

  PTMAPS := PTMAP
Once the makefile has been configured, running `makeit all' will cause the repository to be built. The name of the repository directory created is defined by the variable MKPTR. By default, this will be the expansion of `$(MK).ptr'. As a result the variant will be encoded into the name of the repository directory. You therefore may wish to run `makeit all' for each variant which you are using in your application directory.

Having created the repository directory, you now need to set up your application directory to use it. If the new directory you created was called `repository' which is adjacent to your application directory, you would need to include the following in the makefile in the application directory:

  PTRDIRS := ../repository/$(MKPTR)
This will tell the compiler to use the repository as a secondary repository. That is, it will use it as a source of instantiated templates, but will not instantiate new templates in to it. Any extra templates which need to be instantiated will still be instantiated in to the local repository for that directory.

Instead of using the repository as a shared secondary repository, it can be used as a shared local repository. To enable this, instead of defining PTRDIRS, defined the variable PTRDIR. For example:

  PTRDIR := ../repository/$(MKPTR)
Using a shared local repository amongst a number of application directories can cut down on the amount of disk space consumed, and the time taken to perform compilations. At times your compilation can be stalled though, as locking is performed on the repository, meaning that if another application is writing to the repository you will be prevented from doing so.

5 Separate Build Directory

In general, makeit would be run from the directory containing the code you wish to build. However, if desired, makeit can be made to perform the build from a directory different to that where your code is located.

To build your code in a separate directory, you first need to create and prepare the separate directory so makeit can find the code it has to build. To prepare the directory, you should run the following command in the directory you have created for the build.

  makeit SRCDIR=somepath -f somepath/makefile workspace
When you run this, `somepath' should be replaced with the pathname of the directory containing your code. This pathname may be either an absolute or relative pathname. Also, `makefile' should be replaced with the name you have used for the makefile in that directory.

The actions of the `workspace' target will result in a parallel directory hierarchy to that rooted at the source directory you define, to be created in the build directory. In each of the directories created, a makefile with the same name as that appearing in the original directory will be created. For the top level makefile, this will take the form:

  override SRCDIR := somepath

include $(SRCDIR)/makefile
The makefiles contained in the directories below the root directory, will have the value of SRCDIR adjusted to point at its corresponding source directory.

Makeit works out how the directory hierarchy should look, by using the SUBDIRS variable. If the value of SUBDIRS can change depending on what platform you are working on, or what configuration of your system you are building, you should also define the ALLDIRS variable. This should be defined to the full set of subdirectories which exist and which at some time may be included in the SUBDIRS variable.

For a build of your code from a separate directory to work, you may need to change some things in your makefiles. You may also need to change any modules you have written which extend the functionality of makeit.

The main change, is that you can no longer use the GNU make `wildcard' command to determine the set of files a directory may contain, which match a defined pattern. Instead, you should use the GNU make `filter' command to obtain the names of the files from the SRCFILES variable. For example, instead of:

  SECTION1 := $(wildcard *.1)
you must use:

  SECTION1 := $(filter %.1,$(SRCFILES))
The SRCFILES variable is a special variable, which is set to the combined list of files found in the build directory and the source directory. The names of the files will have the directory component of their names removed.

Through SRCFILES including the files in the build directory, it is possible to add code files to the build directory which will also be compiled when you run makeit. If a code file is added to the build directory which already exists in your source directory, it will be used instead of the file in the source directory. This allows you to try out changes to your code without modifying your original code files. If you are happy with your changes you could merge them back into your original code files.

By interrogating the SRCFILES variable, the feature whereby files in the build directory will be used in addition to, or override those in the source directory, will carry through to your makefiles. If you do not want this behaviour, you can still use the `wildcard' command, however, you must prefix the wildcard pattern with the location of the source directory. You may also have to remove the directory component of the names of the files. For example:

  SECTION1 := $(notdir $(wildcard $(SRCDIR)/*.1))
If you use files with uncommon extensions, you may need to indicate to makeit that it should search for files with that extension, in the source directory as well as the build directory. For example, if you were installing, using the `install' module, configuration files using a `.cf' extension, as well as defining:

  AUXILIARIES := $(filter %.cf,$(SRCFILES))
you must define:

  vpath %.cf $(SRCDIR)
The only other major change which may be required relates to the use of relative pathnames. For example, if your makefile includes another makefile using a relative pathname, you should append the location of the source directory in front of the relative pathname. For example, if you have:

  SRCROOT := ..

include $(SRCROOT)/project/init.mk
you should change it to:

  ifeq "$(SRCDIR)" ""
SRCDIR := .
endif

SRCROOT := $(SRCDIR)/..

include $(SRCROOT)/project/init.mk
It is necessary to set SRCDIR to `.' if not defined, as this will be the case when makeit is run in the source directory. If SRCDIR is not set when the `init.mk' file provided with makeit is included, makeit will set it to `.'.

Note that it is not necessary to adjust relative pathnames used in include directory search paths. This is the case as makeit will automatically add additional search paths relative to the location of the source directory. Relative pathnames used to link in libraries also should not be changed, provided that is that they refer to directories within your source directory. There is no need to change these as the library will now be residing in the build directory and not in the source directory.

6 Project Customisation

Often, in a large project, you will need to provide definitions which apply to all directories. Instead of placing these definitions into every makefile, it is better to create a layer on top of makeit. What this involves is the creation of `init.mk' and `modules.mk' files, which are tailored to your project. In your `init.mk' file you can include any special definitions which you may require. The definitions may include variables to define the version of makeit or C++ compiler, which should be used for that project. For example, your `init.mk' might look like the following:

  OSE_VERSION := 3.0
C++COMPILER := SUN3.0.1

EXTRA_CPPFLAGS := -I/usr/local/projects/include

include makeit/init.mk
If you want to provide definitions for any of the variables, CPPFLAGS, CFLAGS, C++FLAGS, LDFLAGS or LDLIBS, you must use an alternate variable name. The alternate name is the same as that you wish to set, but is prefixed with `EXTRA_'. For example:

  EXTRA_CPPFLAGS := -I/usr/local/projects/include
The value of these variables will be appended to the true variables in the modules file. If you do not use the alternate names, your variable definitions will be overriden in the `init.mk' file provided by makeit.

If you do not have any modules of your own which need to be integrated into makeit, your `modules.mk' file would contain only the line:

  include makeit/modules.mk
If you do have modules of your own which need to be integrated into makeit, your `modules.mk' file must list the names of the modules, and the names of the directories containing the module files. When you list the additional modules, you must incorporate the names of the default modules available with makeit into the list. The names of the modules should be defined so as to satisfy any constraints with respect to the order in which modules need to be included. A listed module, if required, will be included before any modules which may appear later in the list.

The name of the variable used to specify the list of modules is ALLMODULES. If you do not provide a definition for the ALLMODULES variable, it will default to:

  ALLMODULES := ose yacc lex rpcgen cc cpp cxx C c-cc \
c sh check combine repository install ocenter
If for example, you had created a module called `release', the ALLMODULES variable would be defined in your `modules.mk' file as:

  ALLMODULES := ose yacc lex rpcgen cc cpp cxx C c-cc \
c sh check combine repository install ocenter release
The name of module file for this module must be the module name suffixed with a `.mk' extension. The directory containing the module file, must be listed in the MODULEPATH variable. Your complete `modules.mk' file may thus look something like:

  ALLMODULES := ose yacc lex rpcgen cc cpp cxx C c-cc \
c sh check combine repository install ocenter release

MODULEPATH := $(PROJECT_MAKEIT)

include makeit/modules.mk
If you have used the character `/' in the name of your module, for example:

  xwindows/interviews
there will need to exist a directory below that listed in the MODULEPATH variable, corresponding to the directory component of the module name. In this example, this would require a directory `xwindows' to to exist under the directory defined by the PROJECT_MAKEIT variable. In this subdirectory would need to be a file `interviews.mk' for that module.

If more than one directory needs to be searched for module files, the names of each directory should be listed in the MODULEPATH variable, separated by spaces.

Your versions of the `init.mk' and `modules.mk' file should be placed into a directory within your project area. Each of the makefiles in the directories containing your code should include your version of the files, instead of the versions provided by makeit. For example, if you placed these files into a subdirectory of the root directory of your project called `project', your top level makefile would have the form:

  ifeq "$(SRCDIR)" ""
SRCDIR := .
endif

BLDROOT := .
SRCROOT := $(SRCDIR)/$(BLDROOT)

PROJECT_MAKEIT := $(SRCROOT)/project

MODULES := ...

include $(PROJECT_MAKEIT)/init.mk

SUBDIRS := ...

include $(PROJECT_MAKEIT)/modules.mk
The makefile contained in a subdirectory of the root directory would have the form:

  ifeq "$(SRCDIR)" ""
SRCDIR := .
endif

BLDROOT := ..
SRCROOT := $(SRCDIR)/$(BLDROOT)

PROJECT_MAKEIT := $(SRCROOT)/project

MODULES := ...

include $(PROJECT_MAKEIT)/init.mk

...

include $(PROJECT_MAKEIT)/modules.mk
In both of the above, the use of the BLDROOT and SRCROOT variables, is to ensure that the makefile will still function correctly when the source is built from a workspace.

7 Site Customisation

An alternative to customising the top level interface of makeit on a per project basis, is to customise it on a site wide, or work group basis. This approach does not require changes to be made to your makefiles. As a result it is not obvious how extra functionality is being included. This may cause confusion if problems do occur, thus care should be exercised.

Inclusion of site wide versions of the `init.mk' and `modules.mk' files is controlled through the OSE_LOCAL_MAKEIT environment variable. This variable should define a directory containing your copies of these files. When you specify:

  include makeit/init.mk
and

  include makeit/modules.mk
in your makefile, the site wide versions of the files will be included from the directory defined in the environment variable, instead of the default versions provided by makeit.

Construction of the site wide versions of the `init.mk' and `modules.mk' file should follow the guidelines given above and in the next chapter. One important difference for this approach though, is that your `init.mk' and `modules.mk' files should always use the variable OSE_MAKEIT to include predefined makefiles supplied with makeit. For example, the `init.mk' file described in the previous chapter would be rewritten to use:

  include $(OSE_MAKEIT)/init.mk
instead of:

  include makeit/init.mk
If this is not done, a loop will occur when including the `init.mk' file.

In addition to the OSE_MAKEIT variable, the variable OSE_VERSION will also be defined when the site wide `init.mk' file is included. If OSE_VERSION had not been specified in your local makefile, the variable will be defined to the version of OSE corresponding to the `makeit' program your ran. The OSE_VERSION variable can be used to include different functionality based on which version of OSE is being used. For example:

  include $(OSE_LOCAL_MAKEIT)/$(OSE_VERSION)/mymodule.mk
If necessary, local project specific versions of `init.mk' and `modules.mk' may still be layered on top of your site wide versions.