RISCOS.com

www.riscos.com Technical Support:
DDE (Desktop Development Environment)

 


Makefile syntax


This appendix covers the syntax of Makefiles understood by amu, and the way they are arranged by Make. If all you need to do is construct and use simple Makefiles with Make, you do not need to study this information. It is included for those wishing to study, modify or construct Makefiles manually.

Make and AMU

Makefiles may be constructed by hand, using a text editor such as SrcEdit, or semi-automatically using Make. For more details of operating Make, see the chapter entitled Make. Makefiles may be used to run a make job using either Make or AMU. In both cases, make jobs operate by the command line tool amu interpreting the Makefile text and issuing command lines to other tools. The command line tool amu is installed in your library directory.

Command execution

Amu executes commands by calling the C library function system, once for each command to be executed. In turn, system issues an OS_CLI SWI to execute the command. Before calling OS_CLI, system copies its caller to the top end of application workspace and sets the workspace limit just below the copied program. Any command executed by amu therefore has less memory to execute in than amu had initially (the difference being the size of amu plus the size of amu's working space).

When the command returns, amu will be copied back to its original location and will continue, unless, of course, the command set a bad (non-0) value in the environment variable Sys$ReturnCode (the C library automatically sets Sys$ReturnCode to the value returned by main() or passed to exit()). If you have limited memory on your computer, or you are trying to run amu in a limited Wimpslot under the desktop, and a program (such as the C compiler) to be run by amu needs more memory than is left, you can instruct amu not to execute commands directly, but to write them to an output window to be saved and executed later (see the Don't execute option of Make and AMU). Of course, in this case, execution is not terminated or modified by a non-0 return code from a command.

Finally, note that there is a RISC OS command length limit of 255 characters. The desktop tools such as the linker and C compiler cooperate with the DDEUtils module to allow much longer command lines, but care must be taken to avoid generating long command lines for other operations, such as wipe, etc.

Makefile basics

In its simplest form, a Makefile consists of a sequence of entries which describe

  • what each component of a system depends on;
  • what commands to execute to make an up-to-date version of that component.

Everything else that you can express in a Makefile is designed to make the job of description easier for you.

Amu performs two functions for you. Firstly, it expands your description into the simple form just described: a sequence of explicit rules about how to make each component of a system. Then it decides which rules need to be applied to make a completely up-to-date, consistent system. This it does by deciding which components are older than any of the files they depend on. It then executes the commands associated with those entries, in an appropriate order.

An example will make all this clear, so let's look at part of the Makefile for amu itself:

amu:    o.amu $.301.clx.o.clxlib
        link -o amu o.amu $.CLib.o.Stubs
        squeeze amu

o.amu:  c.amu $.301.clx.o.clxlib
        cc -I$.301.clx c.amu

install:
        copy amu %.amu ~cfq
        remove amu
        remove o.amu

Each entry consists of

  • a target, followed by a colon character, followed by
  • a list of files on which the target depends, followed by
  • a list of commands to execute to make the target up to date.

Each command line begins with some white space (if you want your Makefile to be portable to UNIX systems you should begin these lines with a Tab character). For example, amu itself is made from o.amu, the compiled amu program, and a proprietary library called $.301.clx.o.clxlib. If either of these files is newer than amu, or if amu does not yet exist, then the commands link -o amu ... followed by squeeze amu, should be executed.

But what if o.amu doesn't yet exist or is not itself up to date? Amu will check this for you and will not use o.amu without first making it up to date. To do this it will execute the command(s) associated with the o.amu entry.

Thus amu might well execute for you:

cc -I$.301.clx c.amu
link -o amu o.amu $.CLib.o.Stubs
squeeze amu

As you can see, if you do this more than once - for example, because you are developing the program being managed by amu - it will save you many keystrokes. Now suppose you don't have $.301.clx.o.clxlib. What then? Well, the Makefile doesn't instruct amu how to make this so it can do no more than tell you so. Either you must modify the Makefile to say how to make it or, more likely, obtain a copy ready-made.

File name truncation

Machines that have file name truncation configured off can result in error messages being displayed where a Makefile contains a rule where a (non-file) target name has more than 10 characters.

For example, in the following Makefile extract:

install_rom: ${TARGET}
             ${CP} ${TARGET} ${DESTINATION}.${TARGET} ${CPFLAGS}
             @echo install_rom complete

typing in:

*amu install_rom

would result in the following error message:

AMU: failed to read time stamp for 'install_rom'

If you are going to use long target names you must ensure that file name truncation is configured on.

Macros as targets

The first target in a Makefile cannot be a macro. If you need to use a macro in this way then you should insert an 'extra' target.

For example:

all: ${PROG}

${PROG}: myprog.o
         @echo ${PROG} rebuilt

Makefile structure

Makefiles contain normal ASCII text, and are of type 0xFE1 (Makefile). For backwards compatibility they may also be used with text (0xFFF) file type, though these cannot be adjusted automatically by Make.

A Makefile consists of a sequence of logical lines. A logical line may be continued over several physical lines provided each but the last line ends with a \. For example:

# This is a comment line \
  continued on the next physical line \
  and on the next, but not thereafter.

A comment is introduced by a hash character # and runs to the end of the logical line. The active comment line:

# Dynamic dependencies:

is interpreted by amu as a marker for the start of dependencies to be kept up to date during a make job (see Makefiles constructed by Make). All other comment lines are ignored by amu.

Otherwise there are four kinds of non-empty logical lines in a Makefile:

  • dependency lines
  • command lines
  • macro definition lines
  • rule and other special lines.

Dependency lines have the form:

space-separated-list-of-targets COLON space-separated-list-of-prerequisites

For example:

amu : o.amu $.301.clx.o.clxlib
o.d35 o.d36 o.d37: h.util

A dependency line cannot begin with white space. Spaces before the colon are optional, but some white space must follow to distinguish a colon separating targets and prerequisites from a colon as part of a RISC OS filename.

For example:

adfs::4.$.library.amu: o.amu ...

(Although a space after the colon is not required by UNIX's make utility, omission of it is rare in UNIX Makefiles).

A line with multiple targets is shorthand for several lines, each with one target and the same right-hand side (and the same associated commands, if any). Multiple dependency lines referring to the same target accumulate, though only one such line may have commands associated with it (amu would not know in what order to execute the commands otherwise). For example:

amu: o.amu
amu: $.301.clx.o.clxlib

is exactly equivalent to the single line form given earlier. In general, the single line form is easier for you to write whereas the multi-line form is more readily generated by a program (for example, Make will generate a list of lines of the form o.foo: h.thing, one for each #include thing.h in c.foo). Command lines immediately follow a dependency line and begin with white space.

For maximum compatibility with UNIX Makefiles ensure that the first character of every command line is a Tab. Otherwise one or more spaces will do. A semi-colon may be used instead of a new line to introduce commands. This is often used when there are no prerequisites and only a single command associated with a target. For example:

clean:; wipe o.* ~cfq

Note that, in this case, no white space need follow the colon.

Macro definition lines are lines of the form:

macro-name = some text to the end of the logical line

For example:

CC = ncc
CFLAGS = -fah -c -I$.clib
LD = link
LIB = $.CLib.o.clxlib $.CLib.o.Stubs
CLX = $.301.clx

The = can be surrounded with white space, or not, to taste. Thereafter, wherever ${name} or $(name) is encountered, if name is the name of a macro then the whole of ${name} is replaced by its definition. A reference to an undefined macro simply vanishes. An example which uses the above macro definitions, and which is taken from the Makefile for amu itself, is:

amu:    amu.o $(CLX).o.clxlib
        $(LD) -o amu ${LFLAGS} o.amu ${LIB}

which expands to

amu:    amu.o $.301.clx.o.clxlib
        link -o amu o.amu $.CLib.o.clxlib $.CLib.o.Stubs

Note that ${LFLAGS} expands to nothing.

By using macros intelligently, you can minimise the effort needed to move Makefiles from computer to computer; for example, dealing with varying locations for prerequisites, or centralising what would otherwise be distributed through many lines of text. It is obviously much easier to add -g to a CFLAGS= line to make a debuggable version of the compiler than it is to add -g to 28 separate cc commands. Similarly, using $(CC) and CC=cc, rather than just cc, makes it very easy to use a different version of cc; just change the definition of the macro. Whilst this may not seem very useful in a small Makefile, it is common practice when describing larger systems such as the C compiler. Macros are used extensively in Makefiles constructed by Make.

Advanced features

File naming

To help you move MS-DOS and UNIX Makefiles to RISC OS, or to develop Makefiles under RISC OS for export to MS-DOS or UNIX, both amu and the C compiler accept three styles of file naming:

RISC OS native: $.301.cfe.c.pp ^.include.h.defs
UNIX-like: /301/cfe/pp.c ../include/defs.h
MS-DOS-like: \301\cfe\pp.c ..\include\defs.h

(All three of these examples refer to the same two RISC OS files.)

The linker offers more limited support; in essence, it recognises thing.o and o.thing as referring to the same RISC OS file (o.thing). In practice, object files almost always live locally (that's the only place the RISC OS and UNIX C compilers will put one) so this support is fairly complete.

Amu will even accept a mixture of naming styles, though this practice should be discouraged.

The mapping between different naming styles cannot be complete (consider the UNIX analogue of adfs::0.$.Library or net#1.251:src.amu). However, it is usually sufficient to take much of the hard work out of moving reasonably portable Makefiles.

VPATH

Usually, amu looks for files relative to the work directory or in places implicit in the filename. The example given earlier contains the line:

amu: amu.o $.301.clx.o.clxlib

which refers to:

@.o.amu (in @.o) and $.301.clx.o.clxlib (in $.301.clx.o)

Sometimes, particularly when dealing with multiple versions of large systems, it is convenient to have a complete set of object files locally, a few sources locally, but most sources in a central place shared between versions. For example, we can build different versions of the C compiler this way. If the macro VPATH is defined, then amu will look in the list of places defined in it for any files it can't find in the places implicit in their names. For example, we might have compiler sources in somewhere.arm, somewhere.mip, somewhere.cfe and put the compiler Makefile in somewhere.ccriscos. It might contain the following VPATH definition:

VPATH=^.arm ^.mip ^.cfe # note that UNIX VPATHs
                        # separate path elements
                        # with colons, not spaces

and then dependency lines like:

o.pp: c.pp # ^.cfe.c.pp, via VPATH
      cc $(ccflags) -o o.pp $?

o.cg: c.cg # ^.mip.c.cg, via VPATH
      cc $(ccflags) -o o.cg $?

Rule patterns, .SUFFIXES, $@, $*, $< and $?

All the examples given so far have been written out longhand, with explicit rules for making targets. In fact, amu can make inferences if you supply the appropriate rule patterns. These are specified using special target names consisting of the concatenation of two suffixes from the pseudo-dependency .SUFFIXES. This sounds very complicated, but is actually quite simple. For example:

.SUFFIXES: .o .c
amu:       o.amu ...
.c.o:;     $(CC) $(CFLAGS) -o $@ c.$*

(Note the order here: .c.o makes a .o-like thing from a .c-like thing).

The rule pattern .c.o describes how to make .o-like things from .c-like things. If, as in the above fragment, there is no explicit entry describing how to make a .o-like thing (o.amu, in the above example) amu will apply the first rule it has for making .o-like things. Here, order is determined by order in the .SUFFIXES pseudo-dependency. For example, suppose .SUFFIXES were defined as .o .c .f and that there were two rules, .c.o:... and .f.o:... Then amu would choose the .c.o rule because .c precedes .f in the .SUFFIXES dependency. In applying the .c.o rule, amu infers a dependence on the corresponding .c-like thing - here c.amu. So, in effect, it infers:

o.amu:  c.amu
        $(CC) $(CFLAGS) -o o.amu c.amu

Note that, in the commands, $@ is replaced by the name of the target and $* by the name of the target with the 'extension' deleted from it. In a similar fashion, $< refers to the list of inferred prerequisites. So the above example could be rewritten using the rule:

.c.o:;  $(CC) $(CFLAGS) -o $@ $<

However, if a VPATH were being used, this second form is obligatory. Consider, for example, the fragment:

VPATH=^.arm ^.mip ^.cfe
cc:     .... o.pp ....
.c.o:;  $(CC) $(CFLAGS) -o $@ $<

There is no explicit rule for making o.pp, so amu will apply the rule pattern .c.o:?. This might expand to:

o.pp:   ^.cfe.c.pp
        $(CC) $(CFLAGS) -o o.pp ^.cfe.c.pp

which has a much more useful effect than:

        $(CC) $(CFLAGS) -o o.pp c.pp

Finally, $? can be used in any command to stand for the list of prerequisites with respect to which the target is out of date (which may be only some of the prerequisites).

Use of ::

If you use :: to separate targets from prerequisites, rather than :, the right-hand sides of dependencies which refer to the same targets are not merged. Furthermore, each such dependency can have separate commands associated with it. Consider, for example:

o.t1::  c.t1 h.t1
        cc -g -c c.t1   # executed if o.t1 is out of
                        # date wrt c.t1 or h.t1
o.t1::  c.t1 h.t2
        cc -c c.t1      # executed if o.t1 is out of
                        # date wrt c.t1 or h.t2

These features are used extensively by Make in the construction of Makefiles.

Prefix$Dir

The DDEUtils module provides an environment variable Prefix$Dir set to the work directory. This is provided to allow you to execute binaries placed in the work directory.

Makefiles constructed by Make

A Makefile constructed by Make, i.e. used to maintain a project, is a file of type 0xFE1 (Makefile). This text is arranged into a number of sections which are separated by active comments.

When maintaining a project the meta-symbol @ is used to stand for the pathname of the work directory. This overcomes the problem of a current directory not being appropriate under the RISC OS desktop. If the absolute filename of a Makefile is:

adfs::4.$.any.thing.makefile

then all filenames for the project can use @ to replace adfs::4.$.any.thing.

For example:

adfs::4.$.any.thing.c.foo

becomes denoted by

@.c.foo

Amu is invoked with the -desktop flag to indicate that @ should be expanded.

Tools like cc and objasm which must produce dependency information are invoked with a flag -depend !Depend.

Below, we describe each of the Makefile sections, beginning with their corresponding active comments:

# Project: project_name This gives a name to be used for the project in the Open submenu.
# Toolflags: This section has a set of default flags for each of the tools which have registered themselves with !Make, for automatic inclusion in a Makefile. Each rule would be of the type:

toolFLAGS = ....

# Final targets: This section contains the rules for making the final targets of the project. For example:

!RunImage: link $(linkflags) -o !RunImage -via objects

# User-editable dependencies: This section is left untouched by !Make, and can freely be edited by the user using a text editor.

# Static dependencies: This section contains rules for making an object file from its corresponding source. It does not refer to include files and the like (described below in the section Dynamic dependencies).
# Dynamic dependencies: This section contains the rules which are created by !Make by running the relevant tool on a source file to ascertain its dependencies (e.g. cc -depend).

Miscellaneous features

The special pseudo-target .SILENT tells amu not to echo commands to be executed to your screen. Its effect is as if you used the Make or AMU option Silent.

The special pseudo-target .IGNORE tells amu to ignore the return code from the commands it executes. Its effect is as if you used the Make or AMU option Ignore return codes.

A command line in a Makefile, the first non-white-space character of which is @, is locally silent; just that command is not echoed. This is only rarely useful.

A command line, the first non-white-space character of which is - has its return code ignored when it is executed. This is extremely useful in Makefiles which use commands such as diff which cannot set the return code conventionally.

The special macro MFLAGS is given the value of the command line arguments passed to amu. This is most useful when a Makefile itself contains amu commands (for example, when a system consists of a collection of subsystems, each described by its own Makefile). MFLAGS allows the same command line arguments to be passed to every invocation of amu, even the recursive ones. For example, you might invoke amu like this:

* amu -k LIB=$.experiment.new.lib.grafix

and the Makefile might contain entries like:

subsys_1: $(COMMON) $(HDRS1) ...
          dir subsys1
          amu $(MFLAGS)
          back

This edition Copyright © 3QD Developments Ltd 2015
Last Edit: Tue,03 Nov 2015