Recently, in a fit of insanity, I offered to "write an article or few" on C programming issues. The areas I'd like to concentrate on are tips that will help take those without much experience a step further. My first article is about writing makefiles by hand.
First, I'd better clear up what's what in terminology. A makefile is a textual script describing rules to build programs from source, C being the most common. It contains information about which source and library files are needed to create a program, and can work out exactly how much to recompile when one of the files is updated.
On Unix platforms, makefiles are written in a text editor and run with a command-line tool called make, but in Acorn's C packages, the tool equivalent to make is called amu (probably standing for something like "Acorn Make Utility"), and they provide !Make to automate the building of makefiles. This article applies to Acorn's amu, but the free alternative(s) should use the same syntax, as should Easy C, unless it uses something completely proprietary instead of makefiles.
If you create a template makefile, you can use it as a starting point for all your projects by copying it and typing in a few filenames. This is hardly any slower than drag & drop, and it will save time in the long run when you're familiar with the syntax and can easily edit more complicated makefiles as a whole instead of !Make hiding it from you.
To change the run action of the Makefile filetype (&FE1) comment out the Alias$@RunType lines in the !Boot and !Run files of !Make. Or just delete the whole thing altogether if you're feeling bold, but you should copy the appropriate bits to load the sprite and set the File$Type variable somewhere else where they'll get booted. !SetPaths is the ideal place if you have Acorn C/C++, otherwise probably somewhere in your boot sequence.
Create a TaskObey file containing the following, and save it as !Make in !SetPaths or wherever. You don't have to call it "!Make" - it's just the name I use. It could also be Obey instead of TaskObey, but I like to have compilations running in a TaskWindow.
RMEnsure DDEUtils 0.00 RMLoad System:Modules.DDEUtils Set Alias$@RunType_FE1 <Obey$Dir>.!Make %%*0 amu -desktop -f %0 %*1
Also, add the second line of that to !SetPath's !Boot and !Run files. Now double-click !SetPaths and everything should be set up.
The first line loads the DDEUtils module which is needed for Throwback and to deal with long command lines. The second resets the filetype run action to run this file, because DDEUtils seems to change it back to !Make when it initialises. The third line runs the makefile in a TaskWindow. I'm not sure why I wrote %0 %*1 instead of %*0; it was either absent-mindedness or for a good reason. Just in case it was the latter, I'll leave it like that!
amu [options] [target1 {target2...}] [macrodefn1 {macrodefn2...}]
Options are:
-f filename use filename as Makefile -i ignore return codes -k continue after errors -n don't execute -o filename write commands to filename -s silent -t stamp
If you don't use the -f option, amu will look for a file called "Makefile" in the current directory. Usually, when one of the commands executed by amu generates an error, amu halts at that point. The -i causes it to continue as if there were no error, and -k causes it to continue with rules that don't depend on the failed one. The -n makes it just print the commands it would execute without executing them, the -o makes it print the commands into a file, and the -s stops it from printing command; usually, it prints each one just before executing it. Finally, the -t makes it stamp all the targets and source files, so the project can be seen to be up to date.
If you omit targets, amu uses the first one in the makefile.
Macro definitions take the form VARIABLE=value and have the same effect as if they were set in the makefile, which I'll explain below. If you want spaces in a macro defined on the command line, enclose the definition in quotes.
When amu is running a makefile, the current working directory is set to the directory the makefile is in, and it is usual for the makefile to refer to all files relative to this.
A makefile consists of a series of text lines which can be macro definitions, rule prerequisites and commands, or comments. Blank lines are ignored. Anything after a # character, anywhere on a line, is a comment. Long lines can be split with the \ character i.e. if the last character on a line is a \, make treats the following line as a continuation of the first, swallowing the \. This feature is usable in comments.
MACRO_NAME = macro value
To be on the safe side, apply the same rules to characters in macro names as you do to C variable names. Macro names are usually upper case, but some people advise using lower case for those that are only for internal use in the makefile.
Leading and trailing spaces in the macro value are ignored, but any in the middle are included.
Macros defined in the makefile can be overridden by defining them on the command line to Unix versions of make, but this doesn't seem to work in amu, where versions defined in the makefile less usefully take priority.
To dereference a macro (i.e. substitute its value), use $(MACRO_NAME) or ${MACRO_NAME}. You can use dereferenced macros as part of the definitions for other macros. If a macro doesn't exist, dereferencing it produces a blank without generating an error.
Targets: Prerequisites
Commands
Targets and prerequisites are files, although a common trick is to use a target name which never exists as a file. As long as it's a valid filename, it doesn't have to exist. A target name must have no leading spaces. A prerequisite is a file, such as a source file, which is used to build the target. It is quite common to have multiple prerequisites in one rule.
In this article, I'll only consider rules with one target. When amu builds a target, it finds all the rules that refer to it. Apart from double-colon rules, which are beyond the scope of this article, only one rule per target can have commands. It checks all the prerequisites in the rules, and if any of them are newer than the target, or the target doesn't exist, it uses the commands to build the target. Prerequisites can be targets themselves, so if rules exist for them, amu satisfies those rules before executing any commands of the rule they're prerequisites of.
It follows that you can have rules with no commands, in which case the prerequisites' rules are checked without building the target, or rules without prerequisites, in which case the commands are always executed. Rules with neither commands nor prerequisites are meaningless.
You can have as many commands in a rule as you like, executed sequentially, but each one must begin with a tab. Acorn amu allows spaces instead of tabs, but this is incompatible with Unix. SrcEdit can't handle tabs properly, but you shouldn't be using that!
Note that the Acorn Desktop Tools manual claims you can't use a macro to specify the first target, but I've often broken this rule with no errors being generated.
# Dynamic dependencies:
is used by amu as a marker, so make sure you include it at the bottom of any makefiles you write.
$@ Current target $* Current target with suffix stripped $< Current prerequisites $? Current prerequisites which are newer than target
You'll probably only be commonly using $@ and $<.
To use default rules, you must first use the pseudo-rule .SUFFIXES to indicate which suffixes you're using. This will look something like:
.SUFFIXES: .o .c .c++ .s
Then you need a rule for each source suffix. The target for the rule is the source (prerequisite) suffix followed by the target suffix, e.g.:
.c.o: # No specific prerequisites cc -o $@ $< # Simplest possible compiler command
In particular, notice the extensive use of macros to separate out different concepts, making it easier to find and alter various parameters.
OBJECTS = # Insert your object files here. It looks neatest if you use one per # line with a backslash at the end of each to make one logical line. LIBS = C:o.Stubs # Insert any further libraries you need eg tboxlibs, DeskLib at this point. INCLUDE = C: # Include path for C. TARGET = !RunImage # Name of master target. ASMFLAGS = $(ASMEXTRA) -Stamp -NoCache -CloseExec \ -Quit -throwback CCFLAGS = $(CCEXTRA) -fahi -depend !Depend -throwback \ -I$(INCLUDE) CPPFLAGS = $(CPPEXTRA) -depend !Depend -throwback \ -I$(INCLUDE) LINKFLAGS = $(LINKEXTRA) SQUEEZEFLAGS = $(SQUEEZEEXTRA) # Flags for the various tools. These can be overriden on amu's # command-line, or you can use the *EXTRA variables to add to them. # You should look up the meanings of the flags in the manuals. ASM = objasm $(ASMFLAGS) CC = cc -c $(CCFLAGS) CPP = c++ -c $(CPPFLAGS) LINK = Link -aif $(LINKFLAGS) SQUEEZE = Squeeze $(SQUEEZEFLAGS) # Skeleton commands all: $(TARGET) # Using this as the default rule works around the (apparently wrongly) # documented restriction of not being able to use a macro as the first # target. "all" is a very common target name. $(TARGET): $(OBJECTS) $(LIBS) $(LINK) -o $@ $(OBJECTS) $(LIBS) $(SQUEEZE) $@ # This is what we actually want to build, hence we list .o files # instead of source files above. .SUFFIXES:.o .s .c .c++ .s.o: $(ASM) -from $< -to $@ .c.o: $(CC) -o $@ $< .c++.o: $(CPP) -o $@ $< # Dynamic dependencies:
In another series of articles I wrote for Archive years ago, I used FormText. So I'm using it again with a makefile constructed from the above template, so you can see for yourself how a complete makefile typically looks. Have a look at the Makefile in Example 1 on the monthly disc. To compile it yourself, you may need to change your C$Path (C:) or the INCLUDE macro in the makefile. The latter is the better way to sort out any problem, but this may leave the dynamic dependencies wrong for your setup; so you should delete them all and let them be rebuilt. Please ignore any compilation warnings; I wrote that code before I knew how to write good quality C! Improve the code as an exercise if you like. Do try running the Makefile, then see how it adds the dynamic dependencies.
At this point, you should probably go back and read this article again and/or the Makefile syntax appendix in Acorn's Desktop Tools manual.
Source: | Archive Magazine - 12.10 |
Publication: | Archive Magazine |
Contributor: | Tony Houghton |