Dmake and OOo environment
This document is mainly targeting developers who are working in a OOo/SO environment but also gives some general background on using makefiles.
No scripting or how make works
One of the most common mistakes is to regard a makefile as a script which is executed in a sequential order. This may lead to some surprises as the order of the lines in the makefile does not correspond with the order of processing targets.
Take the following example:
all : my_app_object my_app my_lib_object my_library my_lib_object: compile source my_app_object : compile source my_library : link library from my_lib_object my_app : my_library link my application from “my_app_object” and “my_library”
although “my_lib_object” appears before “my_library” in the main target “all” and in the target description, this makefile will fail when linking “my_library”. The reason is that there is no stated dependency from “my_library” to “my_lib_object”.
What make does is parsing the complete makefile and keeping the first target in mind. From this target a dependency tree is generated which controls all further action. In this case this tree will look like
Starting from this first target, make descends through the tree, building all targets (knots) with satisfied dependencies in a quite unpredictable order. This order depends on the implementation details of each make among other things (e.g. parallel processes).
To get a clean build, the created tree has to look like this
where the red arrow are marking the missing dependencies which caused errors while the dependencies marked in blue are simply not needed in this simple example (there are circumstances where adding redundant dependencies may be needed for standardization).
To complete this first example, here is the makefile, cleaned up a bit:
all : my_app my_lib_object : compile source my_app_object : compile source my_library : my_lib_object link library from my_lib_object my_app : my_library my_app_object link my application from “my_app_object” and “my_library”
Even the “all” target could have been removed when pushing “my_app” to first position but it's quite useful to keep a standard “first target”, especially when things get more complicated.
An other remarkable point is the handling of variables. A variable can change it's content as long it's only referenced but not used. The following (note the colon when defining VAR2) example tries to illustrate this:
VAR1=first VAR2:=$(VAR1) mytarget : +echo $(VAR1) +echo $(VAR2) VAR1=second
The output when running dmake is:
dmake: makefile.mk: line 9: Warning -- Macro `VAR1' redefined after use
The warning already gives a hint what is going on. When parsing the makefile as described above, the content of “VAR1” may change several times. What it contains depends on the current value when using it. Defining “VAR2” with forced expansion ( this is the colon) gets the value “first” as this is the current content of “VAR1” when parsing. the command line in “mytarget” gets the value “second” as it is the last redefinition of “VAR1”. The content of “VAR1” when parsing the target description (called the recipe) doesn't matter. Be aware that it does when the variable appears in either the right or left side of the colon when stating a dependency. Here's some more, just for the record:
VAR1=first VAR2=$(VAR1) third ttt1 : $(VAR2) @+echo $(VAR1) >@+echo $(VAR2) VAR1=second VAR1:=$(VAR2) first second third : @+echo subtarget $@
The output is:
dmake: makefile.mk: line 9: Warning -- Macro `VAR1' redefined after use
dmake: makefile.mk: line 10: Warning -- Macro `VAR1' redefined after use
second third third
In SO/OOo build environment some measures have been taken to simplify multi-platform building. One approach was to create a set of makefiles which wrap all platform specific handling of common targets. This also has the advantage that changes to a special target or behavior can be done for all modules in a single place.
Since there is an increasing number of requests to do things different than all others, the number of variables modifying those centralized targets are growing but the concept of these targets still exists and is illustrated here.
From the developer view a “centralized target” is just a couple of variables to to fill with their according values. To build e.g. a resource library for OOo/SO the makefile snippet may look like
RESLIB1NAME=myres RESLIB1SRSFILES= \ $(SRS)$/mydlg1.srs \ $(SRS)$/mytabbar.srs
To make this result in a resource library, several things have to be handled by the central makefiles:
- First of all a huge amount of variables is initialized to match the requirements of used platform, set default output directories, locations, switches etc. For example the used “$(SRS)” variable is defined now.
- Second a filename is computed from the variables supplied in the makefile. This happens depending on naming, versioning, platform or other conventions. It also contains a directory portion which points somewhere in the output tree. In this case it is stored in the variable RESLIB1TARGETN and can act as a “real” target as it represents a real file.
- Now a
centralized target hierarchy has to be
established. Normally this is done in the
ALLTAR: \Note that ALLTAR doesn't represent a file. It is also meant to be the first target the make process encounters and takes care of inserting all other targets in the global dependency tree.
- Last but not least, the target itself is defined. Normally this is the most complex part of the game. Here the dependencies, partially taken from the developer makefile, are handled. Also remaining differences between platforms are taken care of. And in some cases the whole processing takes place in different flavors, depending on boundary conditions.
Only the target itself is about 100 lines of makefile code, not counting the code setting the required variables. That's another reason for using a lot of common targets this way.
Going for examples
To get some examples for using the most common targets I've prepared a module for demonstration purpose only (no license header!). it contains a couple of makefiles which will be the base of this part.
It's a good idea to imagine a makefile.mk to be divided into five sections:
Pre settings – some data to identify the actual makefile and control initialization of the make process.
Initialization – normally just including “settings.mk” but also module wide settings can be included here.
Regular work – define all variables for central targets.
Common targets – include “target.mk” to get involved with “ALLTAR” and all the central targets.
Freestyle – define your own targets and do things nobody else ever dreamed of...
First of all a simple makefile.mk to compile some C sources. It will also help to cover some basics which are common for all makefiles used in SO/OOo environment. I'll call them “makefile.mk” to ease my work.
# case sensitive modulename
# path to module root
# unique module wide
.INCLUDE : settings.mk
.INCLUDE : target.mk
There are three variables at the beginning of this file which are obligatory for each makefile.mk:
PRJNAME is the case sensitive module name. If the module name changes for any reason, this variable needs to be changed too.
PRJ indicates the relative path to the module root. Normally it contains just an assembly of “..” and “$/” (the platform independent directory delimiter).
TARGET is a user defined string that has to full fill two requirements: it may only contain the characters “0-9”, “a-z” and “_” and it must not be used as TARGET in an other makefile.mk of the same module.
This can easily be identified as section I..
Section III. Defines “SLOFILES” to be just one object file in the directory pointed to by the variable “SLO”. This triggers all required actions, from checking dependencies to included header files and looking for the source file up to selecting the compiler command line and compiling. It also causes the build process to collect those objects (just one in this case) in one file with the extension “.lib” in the output tree. The basename is taken from the “TARGET” variable as it is guaranteed to be unique. This is done by default to ease the handling of lots of objects later on.
As this is a quite simple makefile.mk, it doesn't make much sense to talk a lot about the other sections. They are either empty (V.) or contain their defaults (II. And IV.).
The next example is linking a shared library from the results of the previous makefile.mk. It's also a good example how the idea of platform independent centralized targets got softened and how this pollutes makefiles.
PRJNAME=prjsample PRJ=.. TARGET=shl #win32 USE_DEFFILE=TRUE .INCLUDE : settings.mk SHL1TARGET=myshl SHL1LIBS=$(SLB)$/shl_source.lib # win32 # needed to link against SHL1IMPLIB=i$(SHL1TARGET) SHL1VERSIONMAP=myshl.map # win32 DEF1NAME=$(SHL1TARGET) .INCLUDE : target.mk
Beside the common lines we have already seen in the first example, the interesting lines are
SHL1TARGET=myshl SHL1LIBS=$(SLB)$/shl_source.lib SHL1VERSIONMAP=myshl.map
Ideally this is all that's needed to
create a shared library. The basename, the source files and a map to
define the exports. Unfortunately some more lines are required to do
the same thing for windows but this difference will vanish.
For completion an other example linking a share library using the old win32 way of defining exports.
PRJNAME=prjsample PRJ=.. TARGET=shl_cxx .INCLUDE : settings.mk # win32 # must collect all archives to have one file to dump # symbols from LIB1TARGET=$(SLB)$/myshl_cxx.lib LIB1FILES=$(SLB)$/shl_source_cxx.lib SHL1TARGET=myshl_cxx SHL1LIBS=$(LIB1TARGET) # win32 # needed to link against SHL1IMPLIB=i$(SHL1TARGET) # win32 DEF1NAME=$(SHL1TARGET) DEF1LIBNAME=myshl_cxx DEF1DEPN=$(MISC)$/$(SHL1TARGET).flt .INCLUDE : target.mk # win32 $(MISC)$/$(SHL1TARGET).flt : myshl.flt makefile.mk +-$(RM) $@ $(TYPE) myshl.flt > $@
It strikes that, beside other disadvantages, there's even more platform specific bloat. It also illustrates that when section V. comes into play, things are getting a bit more cryptic.
The last but one example builds an application or two. It takes some more lines in this example because one has to cope with some handling/defaults not applicable for this example.
PRJNAME=prjsample PRJ=.. TARGET=test_app # avoid some defaults NO_DEFAULT_STL=TRUE LIBTARGET=NO .INCLUDE : settings.mk OBJFILES= \ $(OBJ)$/app_source.obj \ $(OBJ)$/app_source_cxx.obj # normay defined in "solenv/inc/libs.mk" .IF "$(GUI)"=="UNX" MYSHLLIB=-lmyshl MYSHLLIB_CXX=-lmyshl_cxx .ELSE # "$(GUI)"=="UNX" MYSHLLIB=imyshl.lib MYSHLLIB_CXX=imyshl_cxx.lib .ENDIF # "$(GUI)"=="UNX" # avoid some more defaults APP1LIBSALCPPRT= APP1NOSAL=TRUE APP1TARGET=myapp APP1STDLIBS=$(MYSHLLIB) APP1OBJS=$(OBJ)$/app_source.obj # avoid some more defaults APP2LIBSALCPPRT= APP2NOSAL=TRUE APP2TARGET=myapp_cxx APP2STDLIBS=$(MYSHLLIB_CXX) APP2OBJS=$(OBJ)$/app_source_cxx.obj .INCLUDE : target.mk
Beside this “example specific” stuff, the requirements for linking an application are quite simple. Just fill three variables to define input and output. No need to care about fancy linker switches or similar.
Note also that the linked object files appear twice. Once in OBJFILES to make them a regular target and twice in APPnOBJS to link them to the application. Both occurrences are required if the objects are not built elsewhere.
Getting to the end of those examples, some Java stuff.
PRJNAME=prjsample PRJ=..$/..$/..$/.. TARGET=cssu PACKAGE=com$/sun$/star$/unsupported .INCLUDE : settings.mk JAVACLASSFILES= \ $(CLASSDIR)$/$(PACKAGE)$/something.class JARTARGET=something.jar .INCLUDE : target.mk ALLTAR : runjar runjar : $(CLASSDIR)$/$(JARTARGET) java -classpath $(CLASSDIR)$/$(JARTARGET) com.sun.star.unsupported.something
Simple as this is creating a jar file containing compiled Java files located in the current directory. For supporting the Java package structures, section I. contains one more variable which contains the package defined in the files located in this directory.
As a final goodie an example how to include an own target and make sure it is handled according to it's dependencies.
ALLTAR : runjar
This simply inserts the target in the global dependency tree. As “runjar” doesn't correspond with a filename, it's regarded “out of date” in each dmake run.
runjar : $(CLASSDIR)$/$(JARTARGET) java -classpath $(CLASSDIR)$/$(JARTARGET) com.sun.star.unsupported.something
As “runjar” requires the jar file to exist, it's lined up as a dependency of this target. This makes sure that the command line isn't executed before the jarfile exists.
An often seen construct
all: \ ALLTAR \ runjar
is by no means a useful alternative!
(still remember the first part?)
Recalling the content of the previous topics of this document and looking at the samples given, it should be possible to get an idea of how to write a makefile.mk and what to avoid. Nevertheless the next section of this document tries to summarize and also give some additional rules of thumb.
With centralized targets as available in SO/OOo environment, writing a makefile.mk normally is done in two steps:
First make sure to create a skeleton which contains the required variables matching your module/directory and the standard includes.
Second fill up section III. with the variables needed to define your targets. It may be quite convenient to recycle an existing makfile.mk but just copying all isn't that useful (see “Rules of thumb” later on).
When writing own targets (section V.) there some more things to check:
Is it required to write an own target?
Is my target stating it's dependencies?
Are there other targets that depend on mine? At least ALLTAR should or your target will never be touched.
Is all output protected against colliding with an other platform building this target at the same time?
Is there any time consuming action done more than once? This primary addresses file system operations like zip, unzip and copying.
And last but not least, some general rules which may help writing makefiles that do not cause unexpected behavior some day later on:
A sane makefile.mk shouldn't build anything on second dmake call (there are some very rare exceptions otherwise it would have been a must).
A sane makefile.mk must rebuild the according targets when any source file gets touched.
Do not write targets that require manual intervention when rebuilding after encountering an error.
Use real files as targets to avoid unnecessary rebuilds.
And my private favorite:
remove all lines you do not understand!
You'll either not need them (most cases) or you should learn why they are required and what they're doing.
Some links which might be helpful when dealing with makefiles in the OOo/SO environment:
The dmake documentation itself:
http://tools.openoffice.org/source/browse/*checkout*/tools/dmake/man/dmake.nc (plain text)
And the main entry point for building OOo: