Dmake and OOo environment

From Apache OpenOffice Wiki
Jump to: navigation, search


Introduction


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

Wrong deps.png


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


Right deps.png

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.



Variable handling


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

echo second

second

echo first

first


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

subtarget first

subtarget third

second third

second third third



Centralized targets


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 form of

    ALLTAR: \
    ...
    $(RESLIB1TARGETN) \
    $(RESLIB2TARGETN) \
    $(RESLIB3TARGETN) \
    ...

    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:

  1. Pre settings – some data to identify the actual makefile and control initialization of the make process.

  2. Initialization – normally just including “settings.mk” but also module wide settings can be included here.

  3. Regular work – define all variables for central targets.

  4. Common targets – include “target.mk” to get involved with “ALLTAR” and all the central targets.

  5. 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
PRJNAME=prjsample

# path to module root
PRJ=..

# unique module wide
TARGET=shl_source


.INCLUDE : settings.mk


SLOFILES= \
$(SLO)$/shlobj.obj



.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.


Summary

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.


Links

Some links which might be helpful when dealing with makefiles in the OOo/SO environment:



Glossary

Not yet



Personal tools