USING MAKEFILES TO SIMPLIFY PROGRAM DEVELOPMENT


Many people new to QNX have come from an integrated environment which maintained information on projects and compiler options are selected by clicking them in a dialog. While this is especially easy to use, it may lack some flexibility for more advanced projects. QNX is identical to the UNIX environment, in which a Makefile is used to intelligently decide which source modules need to be compiled and linked and running the tools to accomplish that.

This document is a brief description of how to use make and provides some more advanced examples which illustrate it's flexibility.

Avoid directly running the Watcom tools (wcc386 etc.). Many options are interdependent and may change with different releases. cc is always used to run the compiler and the linker and it will pass the appropriate options as necessary.

In general, a Makefile includes two things. Lists of which files depend on which other files, so make will know what needs to be updated, and lists of commands to run in order to do so. Since many things are repeated throughout, variables may be used. There are also a number of built-in rules, which are used whenever the lists do not provide direct instructions.

Make by default, looks in the current directory for a file called Makefile or makefile, so you can run Make just by typing make.

The structure of a Makefile is:

result_file: dependency_file1 dependency_file2 dependency_file3
<tab>  command
<tab>  command
<tab>  command

dependency_file2: dependency_file4
<tab>  command

If an instruction line is too long, use \ as the last character and continue on the next line. Lines beginning with # are comments.

Unless you specify otherwise on the command line, make will locate the first result_file and try to create it. First it will parse the entire Makefile. It will examine the timestamp of each dependency file, plus those on which they in turn depend. Make will rebuild files as necessary to create the result_file, in the necessary order, by executing the commands listed. Command lines are identified by the first character on the line being a tab (not a space). Processing will abort if a command returns an error, unless the command is preceeded with a -:

<tab>  -command

Normally Make echoes the line to the screen prior to executing it. An @ can be used to hide the echo.

<tab>  @command

The following is a simple Makefile, which you can use to begin your project. It creates two executables: x10drv, which is a driver and x10 which a user runs to control it. The first result_file listed is all, so just typing make will create both. There are no commands to run for all, so make just needs to update x10 and x10drv.

# Makefile for X10

OPTIMIZE = -Oamrtx
#OPTIMIZE = -Od -g2 -M -D DEBUG

CFLAGS = -5 -w9 -Wc,-we -Wc,-e6 $(OPTIMIZE)
ISR = -Wc,-zu -Wc,-s
LDFLAGS=

all: x10 x10drv

x10: x10.h x10.c
    $(CC) $(CFLAGS) $(LDFLAGS) -o x10 x10.c
    usemsg x10 x10.c
    chmod 755 x10

x10drv: var.o drv.o int.o
    $(CC) $(CFLAGS) $(OPTIMIZE) $(LDFLAGS) -T1 -o x10drv var.o drv.o int.o
    usemsg x10drv drv.c
    chmod 4755 x10drv

var.o: var.c
    $(CC) $(CFLAGS) $(OPTIMIZE) -c var.c -o var.o

drv.o: var.c drv.c
    $(CC) $(CFLAGS) $(OPTIMIZE) -c drv.c -o drv.o

int.o: var.c int.c
    $(CC) $(CFLAGS) $(OPTIMIZE) $(ISR) -c int.c -o int.o

x10 is quite simple, depending on only a header and a single C source file. It is compiled and linked in a single command. So both the compiler flags CFLAGS and the linker flags LDFLAGS variables are used. Since the names of these variables are used in the default rules, do not change them. I define my own variable OPTIMIZE, to make it easy to switch between full blown optimization and debug options. I specify a number of options to be included in CFLAGS, which defines Pentium optimization, full warnings, treat warnings as errors and abort after 6 errors. Note the method of passing parameters directly to the compiler and linker: -Wc,### and -Wl,### where ### represents the options to be passed. Typically few options are passed directly, since cc is aware of almost all of them.

x10drv is a little more complicated, since it includes an Interrupt Service Routine that reads and writes I/O Ports. An interrupt routine has special compilation requirements. It is running with Proc's stack, so stack checking must be off. For convenience, I define a variable ISR to include these options. A special flag -T1 must be used to compile a program which will access hardware. I used it on the link line, but did not include it in LDFLAGS, since it is not required for other executables. I set the permissions to include the setuid bit, so non-root users can run it.

The following is a far more complex Makefile, using principles described above.

# Makefile for Dev.arnet with Watcom 9.5/10.6 for QNX 4.2
#
RELEASE = m
VERSION = 100

# DEV16 Version
#SIZE     = 16
#MODEL    = -2

# DEV32 Version
SIZE      = 32
MODEL     = -5

NAME     = Dev${SIZE}.arnet
COMPILER = 10.6 -ltermlib
#COMPILER = 9.52
#OPTIMIZE = -Oamrtx
OPTIMIZE = -Od -g2 -D DEBUG -M
CFLAGS = $(MODEL) -ms -w9 -Wc,-we -Wc,-e6 -ze -v$(COMPILER) -D
DEV$(SIZE) -D NAME=\"$(NAME)\" -D VERSION=$(VERSION) -D RELEASE=\'$(RELEASE)\' $(OPTIMIZE)
LDFLAGS = -T1 -N 2048 "-Wl,op H=1024"
ISR = -Wc,-zu -Wc,-s
VER_REL  = $(VERSION)$(RELEASE)
ARCHIVE  = //2/backups/arnet/arn$(VER_REL).pax.F
HEADERS  = arndef.h arnfep.h arnbox.h arnstruct.h arnproto.h

target: $(NAME)

clean:
    -rm *.x *.o *.map Dev*.arnet archive Ship/*

install:
    /etc/install -u $(ARCHIVE)

################################################################################
# INSTALLATION

# PRODUCTION DISKETTES
disk: disk144

#disk144: $(ARCHIVE)
disk144:
    echo Insert 1.44M diskette in drive 0
    format 0 1.44
    cat $(ARCHIVE) | vol -vws2 /dev/fd0
    vol -rs2 /dev/fd0 | melt | pax -v

disk720: archive
    echo Insert 720K diskette in drive 0
    format 0 720
    cat $(ARCHIVE) | vol -vws2 /dev/fd0
    vol -rs2 /dev/fd0 | melt | pax -v

# DOWNLOAD ARCHIVE
archive: $(ARCHIVE)
    date >archive

$(ARCHIVE): \
        Ship/Dev16.arnet.$(VER_REL) Ship/Dev32.arnet.$(VER_REL) \
        Ship/arncpsload \
        Ship/arnquery Ship/arnquery.h Ship/arnquery.c Ship/Makefile \
        Ship/arnethelp Ship/arnet.help \
        Ship/~setup Ship/0install_msg
    cp bin/spfep.bin Ship
    cp bin/csbios.bin Ship
    cp bin/csfep.bin Ship
    cp bin/cpbios.bin Ship
    cp bin/cpfep.bin Ship
    cp bin/cpcon*.bin Ship
    cp bin/ppbios.bin Ship
    cp bin/ppfep.bin Ship
    chmod 600 Ship/*.bin
    chmod 700 Ship/Dev*.arnet*
    chmod 700 Ship/arncpsload
    chmod 700 Ship/arnquery
    chmod 600 Ship/arnquery.c
    chmod 600 Ship/arnquery.h
    chmod 600 Ship/Makefile
    chmod 711 Ship/arnethelp
    chmod 644 Ship/arnet.help
    chmod 700 Ship/~setup
    chmod 600 Ship/0install_msg
    ls -p Ship >/ram/arnet.files
    @echo Archive: $(ARCHIVE)
    cat /ram/arnet.files | pax -wv -s,Ship/0install_msg,install_msg, \
    -s,Ship/~setup,setup, -s,Ship,/usr/lib/arnet, | freeze > $(ARCHIVE)

Ship/Dev16.arnet.$(VER_REL): Dev16.arnet
    cp -c Dev16.arnet Ship/Dev16.arnet.$(VER_REL)

Ship/Dev32.arnet.$(VER_REL): Dev32.arnet
    cp -c Dev32.arnet Ship/Dev32.arnet.$(VER_REL)

Ship/~setup: ~setup
    cp ~setup Ship/~setup

Ship/0install_msg: 0install_msg
    cp 0install_msg Ship/0install_msg

Ship/arncpsload: arncpsload
    cp arncpsload Ship/arncpsload

Ship/arnethelp: arnethelp
    cp arnethelp Ship/arnethelp

Ship/arnet.help: arnet.help
    ln -f arnet.help Ship/arnet.help

Ship/Makefile: Makefile.query
    cp Makefile.query Ship/Makefile

Ship/arnquery.h: arnquery.h
    cp arnquery.h Ship

################################################################################
# THE DRIVER
$(NAME): arnvar$(SIZE).o arnmain$(SIZE).o arnini$(SIZE).o arnprc$(SIZE).o \
        arndev$(SIZE).o arnirq$(SIZE).o arnsub$(SIZE).o obj/devdrive$(SIZE).o \
        Usage
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(NAME) \
    arnvar$(SIZE).o arnmain$(SIZE).o arnini$(SIZE).o arnprc$(SIZE).o

arndev$(SIZE).o arnirq$(SIZE).o arnsub$(SIZE).o obj/devdrive$(SIZE).o
    usemsg $(NAME) Usage

arnvar.x: arnvar.c arnquery.h $(HEADERS)
    makextrn arnvar.c arnvar.x

arnvar$(SIZE).o: arnvar.c arnquery.h $(HEADERS)
    $(CC) $(CFLAGS) -c arnvar.c -o arnvar$(SIZE).o
    
arnmain$(SIZE).o: arnmain.c arnvar.x
    $(CC) $(CFLAGS) -c arnmain.c -o arnmain$(SIZE).o
    
arnini$(SIZE).o: arnini.c arnvar.x
    $(CC) $(CFLAGS) -c arnini.c -o arnini$(SIZE).o
    
arnprc$(SIZE).o: arnprc.c arnvar.x
    $(CC) $(CFLAGS) -c arnprc.c -o arnprc$(SIZE).o
    
arnsub$(SIZE).o: arnsub.c arnvar.x
    $(CC) $(CFLAGS) -c arnsub.c -o arnsub$(SIZE).o
    
arndev$(SIZE).o: arndev.c arnvar.x
    $(CC) $(CFLAGS) $(ISR) -c arndev.c -o arndev$(SIZE).o
    
arnirq$(SIZE).o: arnirq.c arnvar.x
    $(CC) $(CFLAGS) $(ISR) -c arnirq.c -o arnirq$(SIZE).o

################################################################################
# SUPPLIMENTAL UTILITIES

# HELP UTILITY
arnethelp: arnethelp.c
    $(CC) $(CFLAGS) -2 -o arnethelp arnethelp.c
    usemsg arnethelp arnethelp.c

# CLUSTERPORT CONCENTRATOR BINARY IMAGE MANAGER
arncpsload: arncpsload.c arndef.h arnconc.h
    $(CC) $(CFLAGS) -2 -o arncpsload arncpsload.c
    usemsg arncpsload arncpsload.c

# QUERY MESSAGE EXAMPLE PROGRAM
arnquery: arnquery.c arnquery.h
    $(CC) $(CFLAGS) -2 -o arnquery arnquery.c
    usemsg arnquery arnquery.c



Note: These instructions are for typical situations. Individual configuration may differ. If you have any questions, please contact us for assistance.

Copyright © 1998 Qenesis Inc. All rights reserved.