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

Extending Makeit
OSE - Makeit User GuideExtending Makeit

Extending Makeit

1 Introduction

Eventually, you will want to do something which makeit does not support. This chapter discusses the target structure used by top level targets in makeit and how to extend them. A discussion of the main types of modules which exist, and through example, how you can create your own is also included.

2 Target Structure

The simplest extension to add to makeit, is the ability to perform additional actions for a specific target. For example, when the `mostlyclean' target is invoked, you may want to remove backup files created by editors such as emacs or textedit. To do this correctly you need to understand the target structure of makeit.

The four main top level targets to which you may wish to add additional actions are:

If you were using a standard make program, you would extend a top level target by listing the target name, followed by two colons, with the actions on the following lines indented by tabs. In makeit, additional actions should not be added directly to a top level target such as the four listed above. Instead, sub targets are provided, which are automatically triggered when top level targets are used. For example, in makeit, to extend the `mostlyclean' target to remove editor backup files, you would add actions to the `mostlyclean.always' target.

  mostlyclean.always ::
$(RM) *% *~
How these actions are triggered when the `mostlyclean' target is used, can be seen if you look more closely at the structure of the `mostlyclean' target.

  mostlyclean : mostlyclean.setup
mostlyclean : mostlyclean.subdirs
mostlyclean : mostlyclean.target
mostlyclean : mostlyclean.always

mostlyclean.setup ::

mostlyclean.subdirs :: mostlyclean.setup
ifneq "$(SUBDIRS)" ""
ifneq "$(filter navigate_all_variants,$(MAKEIT_OPTIONS))" ""
@$(foreach var,$(VARIANTS), \
$(foreach dir,$(SUBDIRS), \
$(MAKE) -C $(dir) VARIANT=$(var) mostlyclean \
|| $(FAILACTION);))
else
@$(foreach d,$(SUBDIRS), \
$(MAKE) -C $(d) mostlyclean || $(FAILACTION);)
endif
endif

mostlyclean.target :: mostlyclean.setup

mostlyclean.always :: mostlyclean.setup
Using the `mostlyclean' target results in four sub targets being triggered. The purpose of this structure is to provide control over when your actions will be run. The two primary times at which you would want your actions to be run are: before, or after makeit has visited any subdirectories. If your actions must be run after makeit has visited any subdirectories the `always' sub target for the top level target should be used. If your actions must be run before makeit has visited any subdirectories, the `setup' sub target for the top level target should be used.

The two sub targets of `setup' and `always', are the only sub targets you should need to extend. The `subdirs' sub target should never have to be extended. The purpose of the `target' sub target can be seen by looking at the structure of the `clean' target.

  clean : clean.setup
clean : clean.subdirs
clean : clean.target
clean : clean.always

clean.setup :: mostlyclean.setup

clean.subdirs :: clean.setup
ifneq "$(SUBDIRS)" ""
ifneq "$(filter navigate_all_variants,$(MAKEIT_OPTIONS))" ""
@$(foreach var,$(VARIANTS), \
$(foreach dir,$(SUBDIRS), \
$(MAKE) -C $(dir) VARIANT=$(var) clean \
|| $(FAILACTION);))
else
@$(foreach dir,$(SUBDIRS), \
$(MAKE) -C $(dir) clean || $(FAILACTION);)
endif
endif

clean.target :: clean.setup

clean.always :: clean.setup mostlyclean.always
From this it can be seen that when the `clean' target is used, sub targets will be triggered in the following order:

Note that the `mostlyclean.setup' and `mostlyclean.always' sub targets are triggered when the `clean' target is used. The `mostlyclean.target' sub target is not triggered when the `clean' target is used. The `target' sub target for a top level target is only triggered when its top level target is triggered directly.

In the case of the `clean' and `mostlyclean' targets, the `mostlyclean.target' target should be extended to add actions which are only to be run when the `mostlyclean' target is used, and not the `clean' target. As the `clean.always' target totally removes the makeit subdirectory you would not want to add to the `mostlyclean.always' target, actions which remove only parts of the makeit subdirectory. Instead, add actions which only remove portions of the makeit subdirectory to the `mostlyclean.target' target.

A further two levels of targets exist above the `clean' target. These targets are `distclean' and `realclean'. Neither of these targets define default actions. The `distclean' target structure can be added to, to define actions which will bring back the directory to a state ready for distribution. The `realclean' target structure can be added to, to define actions which will remove everything which can be regenerated.

In all the above targets, the FAILACTION macro is special and will evaluate to either `true' or `exit'. For a normal build it will take the value `exit'. This ensures that if makeit fails while in one directory, it will not continue to other directories. When the `-k' option is passed to makeit on the command line, FAILACTION takes the value `true', and makeit will attempt to traverse all subdirectories, even if errors occur.

Other top level targets such as `all', `install' and `check' have a similar structure and would be extended in a similar way to the set of `clean' targets described above.

3 Library Modules

A library module is the easiest type of module to create. The purpose of a library module is to define directory search paths for library include files, and to link in the set of libraries accessed through that module. The module may also define special preprocessor symbols, as appropriate.

A module to support the use of the InterViews graphics library is included below, as an example of a library module.

  ifeq "$(origin INTERVIEWS_HOME)" "undefined"
INTERVIEWS_HOME := /usr/local/InterViews
endif

ifeq "$(origin X11LIBDIR)" "undefined"
X11LIBDIR := /usr/lib/X11
endif

override CPPFLAGS += \
-I$(INTERVIEWS_HOME)/include -Dcplusplus_2_1

ifneq "$(filter 2.6,$(INTERVIEWS_OPTIONS))" ""
override CPPFLAGS += \
-I$(INTERVIEWS_HOME)/include/InterViews/2.6 \
-I$(INTERVIEWS_HOME)/include/IV-look/2.6 \
-Div2_6_compatible
endif

override LDFLAGS += $(LOPT)$(INTERVIEWS_HOME)/lib/$(CPU)
ifneq "$(X11LIBDIR)" ""
override LDFLAGS += $(LOPT)$(X11LIBDIR)
endif

ifneq "$(filter libUnidraw,$(INTERVIEWS_OPTIONS))" "
override LDLIBS += -lUnidraw
endif

override LDLIBS += -lIV -lXext -lX11 -lm
When defining additional options to be passed on to the preprocessor, compiler, or linker, you should always append to the existing value of the variable for those options. If you do not append to the variables, flags could be passed on, in the wrong order. For example, libraries could be linked in the wrong order, resulting in undefined symbols at link time. When you define variables, use the `override' directive. This will ensure that the variables will not be overridden by options passed to makeit on the command line, or because of `override' being used previously with the variable.

If you know that the module will need to be portable across multiple platforms, do not use the `-L' option for specifying the location of libraries. Instead of the `-L' option, use `$(LOPT)'. This will be expanded to either `-L' or `-Wl,-L', whichever is appropriate for the platform on which makeit is being run.

If optional libraries need to be linked, include directories searched, or preprocessor symbols defined, set up an options variable for the module in which you can list names, to enable the additional features. For example, in the InterViews module, the INTERVIEWS_OPTIONS variable can be set to enable the linking of the Unidraw library and to enable backwards compatibility support for InterViews version 2.6.

You should also allow for library packages to be installed in different locations, by providing a variable which can be set to specify the location. This will allow you to specify a different location in your personal or project makefile without having to modify the module file. A variable, giving the location of the library package will also make it easier to select different versions of the software.

4 Language Modules

Three types of language modules can be integrated with makeit. The first of these types, are modules that support code generators, tools, which take an input file and generate code for a language already supported by makeit. The second of these types are modules that provide support for languages, which compile directly into object files and programs. The third of these types are modules which support script languages.

4.1 Code Generators

A code generator is a tool which takes an input file and produces an output file in another language. Three such tools are Yacc, Lex and RPCGEN. Although supported by makeit, this section will describe how a module would be constructed for Lex. The example demonstrates the generation of output files for the C language only. In addition, the case of the object files produced by compiling the output files, being placed into a library, is not dealt with.

Each of the primary languages supported by makeit provide variables, which can be set by other modules, to indicate that code files have been generated for that language. For the `c' module, these variables are:

---------------------------------------------------------------------
Variable                  Purpose                                      
---------------------------------------------------------------------
_c_generated_SRC          The names of C code files that should be     
                          compiled into object files and placed into   
                          the library.                                 
_c_generated_PROGRAM_SRC  The names of C code files that should be     
                          compiled into programs.                      
_c_generated_NONLIB_SRC   The names of C code files that should be     
                          compiled into object files, but which        
                          should be placed into the library.           
---------------------------------------------------------------------
If a module adds files to these variables, the `c' module expects to find those files in the makeit subdirectory. When the files are listed in the variables, do not include the makeit subdirectory in the name of the file. For example, from the Lex input file `lexan.l', the file `lexan.c' will be generated. The name `lexan.c' would be listed in the variable _c_generated_NONLIB_SRC, however the `c' module will actually search for the file `$(MK)/lexan.c'.

The cut down version of the Lex module is given below.

  ## Search source directory as well as build directory
## if they are different.

vpath %.l $(SRCDIR)

## Calculate language src.

_lex_real_SRC := \
$(filter-out $(EXCLUDE),$(filter %.l,$(SRCFILES)))

## Generate stems for src.

_lex_real_SRC_STEMS := $(basename $(_lex_real_SRC))

## Let C module know about generated src files.

_lex_OUTPUT_SRC := $(addsuffix .c,$(_lex_real_SRC_STEMS))

_c_generated_NONLIB_SRC += $(_lex_OUTPUT_SRC)

## Rules for generated files.

%.l :

%.c : %.l

ifneq "$(_lex_real_SRC_STEMS)" ""
$(patsubst %,$(MK)/%.c,$(_lex_real_SRC_STEMS)) \
: $(MK)/%.c : %.l
$(LEX) $(LFLAGS) $<
mv lex.yy.c $@
endif

## Add lex library.

ifeq "$(origin LEXLIB)" "undefined"
LEXLIB := -ll
endif

override LDLIBS += $(LEXLIB)
When writing any language module, allow the user to exclude an input file from consideration, by listing the name of the file in the EXCLUDE variable. When you integrate a language module for a code generator into the `modules.mk' file, it must be included, before the module supporting the language that it generates code files for.

4.2 Compiled Languages

Compiled languages are languages for which code files can be compiled directly, into either object files, or programs. So makeit knows which object files need to be archived into the library, and which programs should be built, when the `programs' target is used, the names of object files and program binaries generated by the module must be passed to makeit via variables. These variables are:

---------------------------------------------------------------
Variable                Purpose                                  
---------------------------------------------------------------
_makeit_MK_LIB_OBJECTS  The names of object files which should   
                        be archived into the library.            
_makeit_MK_BINARIES     The names of program binaries genera     
                        ted.                                     
---------------------------------------------------------------
The names listed in these variables should include the name of the makeit subdirectory.

An example of a cut down module to support C code is given below. The example does not cater to C code file created by code generators. Neither does the example support generation of PIC objects for shared libraries.

  ## Search source directory as well as build directory
## if they are different.

vpath %.h $(SRCDIR)
vpath %.c $(SRCDIR)

## Define compilation commands.

PREPROCESS.c =
COMPILE.c =
LINK.c =

PREPROCESS.c += $(CC) -C -E $(CFLAGS) $(CPPFLAGS)
COMPILE.c += $(CC) $(CFLAGS) $(CPPFLAGS) -c
LINK.c += $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)

## Libraries and object files to be linked with programs.

LDLIBS1.c = $(filter-out $(LDLIBS),$(filter %.a %.o,$^))
LDLIBS2.c = $(LDLIBS)

## Wipe out standard target for creation of object file
## from src file and program from src file.

%.o : %.c
% : %.c

## Calculate all language src.

_c_real_SRC := \
$(filter-out $(EXCLUDE),$(filter %.c,$(SRCFILES)))

## Calculate program src.

_c_real_PROGRAM_SRC := \
$(filter-out $(EXCLUDE),$(filter \
$(addsuffix .c,$(PROGRAMS)),$(SRCFILES)))
## Calculate non library src.

_c_real_NONLIB_SRC := \
$(filter-out $(EXCLUDE),$(filter %.c,$(NONLIBSRC)))

## Calculate library src.

_c_real_LIB_SRC := \
$(filter-out \
$(_c_real_NONLIB_SRC) $(_c_real_PROGRAM_SRC), \
$(_c_real_SRC))

## Setup library dependencies for real src.

ifneq "$(_c_real_LIB_SRC)" ""

_c_real_LIB_OBJECTS := \
$(patsubst %.c,%.o,$(_c_real_LIB_SRC))

_c_real_MK_LIB_OBJECTS := \
$(addprefix $(MK)/,$(_c_real_LIB_OBJECTS))

$(_c_real_MK_LIB_OBJECTS) : $(MK)/%.o : %.c
$(COMPILE.c) $<
mv $(<F:.c=.o) $@

$(_c_real_LIB_OBJECTS) : %.o : $(MK)/%.o

_makeit_MK_LIB_OBJECTS += $(_c_real_MK_LIB_OBJECTS)

endif

## Setup dependencies for non library objects coming
## from real src.

ifneq "$(_c_real_NONLIB_SRC)" ""

_c_real_NONLIB_OBJECTS := \
$(patsubst %.c,%.o,$(_c_real_NONLIB_SRC))

_c_real_MK_NONLIB_OBJECTS := \
$(addprefix $(MK)/,$(_c_real_NONLIB_OBJECTS))

$(_c_real_MK_NONLIB_OBJECTS) : $(MK)/%.o : %.c
$(COMPILE.c) $<
mv $(<F:.c=.o) $@

$(_c_real_NONLIB_OBJECTS) : %.o : $(MK)/%.o

endif

## Setup dependencies for programs coming from real src.

ifneq "$(_c_real_PROGRAM_SRC)" ""

_c_real_PROGRAM_OBJECTS := \
$(patsubst %.c,%.o,$(_c_real_PROGRAM_SRC))

_c_real_MK_PROGRAM_OBJECTS := \
$(addprefix $(MK)/,$(_c_real_PROGRAM_OBJECTS))

_c_real_MK_PROGRAMS := \
$(addprefix $(MK)/,$(basename $(_c_real_PROGRAM_SRC)))

$(_c_real_MK_PROGRAMS) : $(MK)/% : $(MK)/%.o
$(LINK.c) $(LDLIBS1.c) $(LDLIBS2.c) -o $@~
mv $@~ $@

$(_c_real_PROGRAM_OBJECTS) : %.o : $(MK)/%.o

$(_c_real_MK_PROGRAM_OBJECTS) : $(MK)/%.o : %.c
$(COMPILE.c) $<
mv $(<F:.c=.o) $@

_makeit_MK_BINARIES += $(_c_real_MK_PROGRAMS)

endif
For compiled languages, you also need to set up rules to generate dependency information. To pass the location of the dependency files to makeit, which your rules generate, you need to set the _depend_SRC variable. Dependency files which your rules generate, should be placed into the makeit subdirectory. The name of the dependency file should be the same as the language file, except that the filename extension for that language would be replaced with `.d'.

For an object file, the dependency file should consist of a series of lines of the form:

  $(MK)/object.o $(MK)/object.d : file.h
Only one file on which the object file is dependent should be listed on each line. For a program, the dependency file should be of the form:

  $(MK)/program $(MK)/program.o $(MK)/object.d : file.h
Rules to generate dependency files for the cut down version of the C module are given below.

  ## Dependency files.

DEPFILTER.c = sed -n \
-e `s/\# [1-9][0-9]* "\(.*\)".*$$/TARGET : \1/p' \
-e `s/\#line [1-9][0-9]* "\(.*\)".*$$/TARGET : \1/p' \
| sort -u

ifneq "$(_c_real_LIB_SRC)" ""

_c_real_LIB_D := \
$(patsubst %.c,$(MK)/%.d,$(_c_real_LIB_SRC))

$(_c_real_LIB_D) : $(MK)/%.d : %.c
@echo makeit: generating dependencies for $<
@$(PREPROCESS.c) $< | $(DEPFILTER.c) | \
sed -e "s%^TARGET%TARGET \$$(MK)/$(*F).o%" \
-e "s%^TARGET%\$$(MK)/$(*F).d%" > $@

_depend_SRC += $(_c_real_LIB_D)

endif

ifneq "$(_c_real_NONLIB_SRC)" ""

_c_real_NONLIB_D := \
$(patsubst %.c,$(MK)/%.d,$(_c_real_NONLIB_SRC))

$(_c_real_NONLIB_D) : $(MK)/%.d : %.c
@echo makeit: generating dependencies for $<
@$(PREPROCESS.c) $< | $(DEPFILTER.c) | \
sed -e "s%^TARGET%TARGET \$$(MK)/$(*F).o%" \
-e "s%^TARGET%\$$(MK)/$(*F).d%" > $@

_depend_SRC += $(_c_real_NONLIB_D)

endif

ifneq "$(_c_real_PROGRAM_SRC)" ""

_c_real_PROGRAM_D := \
$(patsubst %.c,$(MK)/%.d,$(_c_real_PROGRAM_SRC))

$(_c_real_PROGRAM_D) : $(MK)/%.d : %.c
@echo makeit: generating dependencies for $<
@$(PREPROCESS.c) $< | $(DEPFILTER.c) | \
sed -e "s%^TARGET%TARGET \$$(MK)/$(*F)%" \
-e "s%^TARGET%TARGET \$$(MK)/$(*F).o%" \
-e "s%^TARGET%\$$(MK)/$(*F).d%" > $@

_depend_SRC += $(_c_real_PROGRAM_D)

endif

4.3 Script Languages

Script languages are languages which do not need to be compiled, but can be run directly. To let makeit know about executable scripts, you should set the _makeit_MK_SCRIPTS variable. The names of scripts listed in the variable should include the name of the makeit subdirectory. Makeit expects to find the scripts in the makeit subdirectory.

The module provided with makeit to handle shell scripts, excluding support for generated shell scripts, is given below.

  ## Search source directory as well as build directory
## if they are different.

vpath %.sh $(SRCDIR)

## Wipe out standard target for creation of object file
## from src file and program from src file.

% : %.sh

## Calculate language src.

_sh_real_SRC := \
$(filter-out $(EXCLUDE),$(filter %.sh),$(SRCFILES))

## Setup dependencies.

ifneq "$(_sh_real_SRC)" ""

_sh_real_SCRIPTS := $(basename $(_sh_real_SRC))

_sh_real_MK_SCRIPTS := \
$(addprefix $(MK)/,$(_sh_real_SCRIPTS))

$(_sh_real_MK_SCRIPTS) : $(MK)/% : %.sh
ifneq "$(SHFILTER)" ""
cat $< | $(SHFILTER) > $@
else
cp $< $@
endif
chmod 0775 $@

$(_sh_real_SCRIPTS) : % : $(MK)/%

_makeit_MK_SCRIPTS += $(_sh_real_MK_SCRIPTS)

endif
Modules for script languages should provide a means to process the script, as it is copied to the makeit subdirectory. For example, the `sh' module allows an arbitrary command to be supplied by defining the SHFILTER variable. If defined, the program is run with the script being passed to the standard input of the program. The output from the program is saved as the resultant program in the makeit subdirectory.