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 ----- libAs 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.
|
--- bin ----- program1
|
--- program2
|
--- program3
The contents of the makefile you place in the `src' directory should be:
include makeit/init.mkThe contents of the makefile you place in the `bin' directory should be:
SUBDIRS := lib bin
include makeit/modules.mk
include makeit/init.mkYou should also remember that you will need to add,
SUBDIRS := program1 program2 program3
include makeit/modules.mk
CPPFLAGS := -I../../libto 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.
LDLIBS := ../../lib/$(MK)/lib.a
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.
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 installWhen 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 := optThe 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_variantsYou should only use this option in a top level directory, in which there are no source code files.
NOMK := YES
The directory hierarchy you would use in this case is,
src ----- lib ----- lib1where 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:
|
--- lib2
|
--- lib3
MODULES := combineExecuting 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.
include makeit/init.mk
SUBDIRS := lib1 lib2 lib3
include makeit/modules.mk
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
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.
MAKEIT_OPTIONS := shlibWith `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_objectsThis 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.
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 ----- LIB2In 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.
|
--- OPT1 ----- OPT2
|
--- OPT3
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 := closureIn 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.
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.
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.
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_repositoriesYou 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 MyClassFinally, 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.
<myclass.hh>
PTMAPS := PTMAPIf 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.
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.
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.
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.
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__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:
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED vector<double>;
OSE_MARK_COMPILED vector<char>;
#endif
#define EXPAND_TEMPLATES 1This example shows more than one template being expanded into a file. If you wanted to, you could expand each template into a separate file.
#include <vector.h>
#ifdef __OSE_TEMPLATES__
OSE_PRECOMPILE vector<int>;
OSE_PRECOMPILE vector<double>;
OSE_PRECOMPILE vector<char>;
#endif
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__and your code file would contain:
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED myvector<int>;
#endif
#define EXPAND_TEMPLATES 1In 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:
#include <myvector.h>
#ifdef __OSE_TEMPLATES__
OSE_PRECOMPILE vector<int>;
OSE_PRECOMPILE myvector<int>;
#endif
ifndef __OSE_TEMPLATES__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.
EXCLUDE := compile.cc
endif
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_BUILDWhen compiling in any of the library subdirectories you would define the preprocessor symbol MYLIB_BUILD. This would be done by setting the CPPFLAGS variable.
#ifdef __OSE_TEMPLATES__
OSE_MARK_COMPILED vector<int>;
OSE_MARK_COMPILED myvector<int>;
#endif
#endif
CPPFLAGS := -DMYLIB_BUILDThe 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.
To set up a template repository, you should create a new directory and add into it the makefile:
MODULES := repositoryYou 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.
include makeit/init.mk
include makeit/modules.mk
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_librariesWhen 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 := PTMAPOnce 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.
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 workspaceWhen 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 := somepathThe makefiles contained in the directories below the root directory, will have the value of SRCDIR adjusted to point at its corresponding source directory.
include $(SRCDIR)/makefile
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 := ..you should change it to:
include $(SRCROOT)/project/init.mk
ifeq "$(SRCDIR)" ""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 `.'.
SRCDIR := .
endif
SRCROOT := $(SRCDIR)/..
include $(SRCROOT)/project/init.mk
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.
OSE_VERSION := 3.0If 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:
C++COMPILER := SUN3.0.1
EXTRA_CPPFLAGS := -I/usr/local/projects/include
include makeit/init.mk
EXTRA_CPPFLAGS := -I/usr/local/projects/includeThe 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.mkIf 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 \If for example, you had created a module called `release', the ALLMODULES variable would be defined in your `modules.mk' file as:
c sh check combine repository install ocenter
ALLMODULES := ose yacc lex rpcgen cc cpp cxx C c-cc \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:
c sh check combine repository install ocenter release
ALLMODULES := ose yacc lex rpcgen cc cpp cxx C c-cc \If you have used the character `/' in the name of your module, for example:
c sh check combine repository install ocenter release
MODULEPATH := $(PROJECT_MAKEIT)
include makeit/modules.mk
xwindows/interviewsthere 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)" ""The makefile contained in a subdirectory of the root directory would have the form:
SRCDIR := .
endif
BLDROOT := .
SRCROOT := $(SRCDIR)/$(BLDROOT)
PROJECT_MAKEIT := $(SRCROOT)/project
MODULES := ...
include $(PROJECT_MAKEIT)/init.mk
SUBDIRS := ...
include $(PROJECT_MAKEIT)/modules.mk
ifeq "$(SRCDIR)" ""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.
SRCDIR := .
endif
BLDROOT := ..
SRCROOT := $(SRCDIR)/$(BLDROOT)
PROJECT_MAKEIT := $(SRCROOT)/project
MODULES := ...
include $(PROJECT_MAKEIT)/init.mk
...
include $(PROJECT_MAKEIT)/modules.mk
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.mkand
include makeit/modules.mkin 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.mkinstead of:
include makeit/init.mkIf 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.mkIf necessary, local project specific versions of `init.mk' and `modules.mk' may still be layered on top of your site wide versions.