[Home]
[Search]
[CTG]
[RTL]
[IDDE]
Last update Jul 1, 2004
Managing Code with SMAKE
Digital Mars
SMAKE is a command processor for maintaining and updating
project files. SMAKE is compatible with Microsoft NMAKE. This chapter
describes how SMAKE builds a new version of a program by compiling
only the files that have changed.
For information on Digital Mars C++'s build system and source control
system, see the
User's Guide and Reference.
This chapter discusses:
- Overview of SMAKE and how it works.
- Components of makefiles.
- Command line syntax and command options.
- Rules for how SMAKE chooses dependency files.
- Using operating system commands in makefiles.
- Using macros in makefiles.
- Inference rules.
- Preprocessing directives.
- Using the MAKEDEP utility
- Using the TOUCH utility
- Using tools.ini
- Using response files
- Using Error messages
What is SMAKE?
SMAKE automates program maintenance, instead of typing in
commands to compile and link your program. A makefile
describes how all the files in a project depend on each
other and how to update a file (such as an object file) when the files
it depends on (such as source files) change.
SMAKE is particularly useful for programs that have many source
files, since it recompiles only those source files that have changed. It
does this by looking at the date and time stamp of each relevant file
before performing any tasks.
Note: When using SMAKE, always be sure your PC
displays the correct date and time. Be especially
careful when logging on to networks that change
the time on the local machine, in case the network
time is incorrect.
SMAKE differs from a batch file in that it performs only the
commands required to rebuild your program, while a batch file
executes all tasks, whether or not they are necessary.
Writing Makefiles
A makefile is a text file that tells SMAKE how to build your program.
By default, SMAKE expects the makefile to be named makefile,
with no extension. Any extension can be used, though .mak
is commonly used.
The makefile contains one or more dependence lines, each followed
by one or more command lines. Each dependence line specifies a
target (upon which the associated command lines act), followed by a
colon and a (possibly empty) list of dependent files (files needed to
create the target). For example, this dependence line:
app.exe : app.obj app.re app.def
tells SMAKE to perform the commands that follow if any of
app.obj, app.res, or app.def have a more recent date and time
than app.exe.
Command lines tell SMAKE how to create the target. Command lines
must begin with a tab or space character. For example:
app.exe : app.obj app.res app.def
link app,,NULL,lib,app
rcc app.res app.exe
tells SMAKE to run the linker and the resource compiler.
A makefile can also contain comments, macros, preprocessing
directives, operating system commands, and inference rules.
Example of a simple makefile
This makefile creates a program that has only one source file.
hello.exe: hello.obj # Dependence line 1
link hello.obj # Command line 1
hello.obj: hello.cpp # Dependence line 2
sc -c hello.cpp # Command line 2
Note:
A makefile
comment begins with the # character and continues
until the end of the line.
The first dependence line indicates that hello.exe depends on
hello.obj. The first command line specifies how to make
hello.exe by executing link hello.obj.
The second dependence line indicates that hello.obj depends on
hello.cpp. The second command line specifies how to make
hello.obj by executing sc -c hello.cpp.
Suppose you just finished editing hello.cpp and typed smake to
create a new version of your program. SMAKE reads the dependence
lines from the makefile. It determines that hello.cpp is newer than
hello.obj, so it re-creates hello.obj by executing
sc -c hello.cpp. Then it determines that hello.obj is now newer
than hello.exe. It re-creates hello.exe by executing
link hello.obj.
Makefile syntax
The syntax for a basic makefile is:
target-file : dependent-file1 dependent-file2 ...
command 1
command 2
...
SMAKE assumes that if a target file is older than any of the files
needed to create it, then it must re-create the target file by executing
the command lines that follow the dependence line.
Target files are typically executable files or libraries, but they can
also be labels for sets of commands (like all:); these are often
called pseudo-targets. Similarly, a dependent file is usually a C or
C++ source file, but can be a pseudo-target. For more information
see "Specifying targets" below.
Note:
SMAKE reads all the dependence lines in a makefile
before executing any of the associated commands.
It then compares the date and time of the
dependents to the target and executes the
commands for any target that is out of date. It also
checks to see if that target has other dependents
that might be more recent.
Dependence line syntax
The name of the target file must be the first thing on the dependence
line, with no white space preceding it. The target and subsequent
dependents are separated by a single colon, which can optionally be
preceded by tabs or spaces. A dependence line can span multiple
lines; all lines to be continued must end with a backslash (\)
character.
Specifying targets
The dependence line can contain one or more target names
separated by one or more spaces. Each target can be a fully qualified
path and file name, or just a label. However, if the last target has a
single character name (with no extension), at least one space is
required before the colon to distinguish it from a drive letter.
To build a complicated target that requires more than one set of
dependence lines and commands, use double colons (::) to separate
it from the dependents in each case. For example:
app.exe :: app.obj app.def
link app,,NULL,lib,app
app.exe :: app.res
rc app.res app.exe
If the same target, followed by a single colon, appears on two
consecutive lines, and commands follow only one of those instances,
SMAKE interprets this as a single, cumulatively combined
dependence line. For example:
target.exe : file_1.obj
target.exe : file_2.obj
# is interpreted as
# target.exe : file_1.obj file_2.obj
If the same target, followed by a single colon, appears on two
different lines in various places in the makefile, and commands
follow only one of those instances, SMAKE interprets this as if the
dependence lines were adjacent. This means that SMAKE does not
apply an inference rule (see "Inference Rules" below) to the instance
without commands. Use double colons (::) after the target to avoid
this side effect.
If a target is not specified on the SMAKE command line, SMAKE uses
the first target it encounters in the makefile. SMAKE always interprets
a label used as a target name as being out of date; this is useful to
avoid having to pass several targets on the command line. For
example:
all: app.exe app2.exe app3.exe
Specifying dependent files
The names of zero or more dependents can follow the target on the
dependence line. As with targets, a dependent can be a fully
qualified path name or a label, although it is usually a file that is
used to build the target. Multiple dependents must be separated by
one or more spaces or tabs.
A search path can precede a dependent file; enclose the path in
curly braces ({}) and separate the directories with semicolons (;).
SMAKE will search the specified directories in order for the
dependent. (The search path applies only to the dependent it
proceeds.) For example:
app.exe : app.obj app.res \
{c:\project; c:\default} app.def
Special characters in makefiles
SMAKE accepts the special characters listed below in makefiles.
These characters have special meaning depending on their context.
For any of these characters to be interpreted literally in a makefile,
you must precede it with a caret (^) character. If ^ appears in front
of a character that does not have special meaning to SMAKE, it is
interpreted literally.
Note:
A ^ at the end of a line includes the
carriage return/line feed sequence in a macro or
string.
Table 13-1 Special characters in makefiles
- :
- Separates target from rest of dependence line.
- #
- All characters following a # on a line are
interpreted as a comment and ignored.
- ()
- Encloses the value string of a macro to be expanded.
- $
- Precedes an instance of a macro to be expanded.
- ^
- The next character is interpreted as a literal character.
- \
- Continues a line on the next line.
- {}
- On a dependence line, encloses a path name
associated with a dependant.
- !
- As the first character on a line, recognized as the start
of a preprocessing directive. On a dependence line,
acts as a command modifier.
- @
- On a dependence line, acts as a command modifier.
- -
- On a dependence line, acts as a command modifier.
Using % and $ in commands
SMAKE interprets a percent sign (%) as the beginning of a file name
specifier. To place a percent sign in a command, use two percent
signs (%%). Similarly, to use a dollar ($) sign in a command, specify
two consecutive dollar signs ($$). You can also specify multiple
literal % and $ characters this way. For example, SMAKE interprets
$$$$ in a command as two consecutive dollar signs.
Note:
SMAKE interprets the $ and @ symbols as special
characters, do not use
these symbols in file or directory names that will be
processed by SMAKE.
Comments in makefiles
SMAKE interprets all characters following a # on a line as comment
characters, and ignores them. Comments need not start at the
beginning of a line; they can appear almost anywhere, including at
the ends of lines defining macros, dependence lines, and inference
rules. Comment are also permitted in command blocks, but cannot
be on the same line as the command itself, even if the command
spans multiple lines.
Running SMAKE
When you run SMAKE (from an operating system command
prompt), it prints your commands as it executes them. If you run
SMAKE and it doesn't need to update your files, it lets you know like
this:
Target is up to date
To redirect the output of SMAKE to a file, use the greater-than sign:
smake > log
Note:
When SMAKE calls another program, it redirects
that program's output to the log file only if that
program writes to stdout. The tools that come
with Digital Mars C++, such as SC and OPTLINK, write
to stdout.
SMAKE command line syntax
The syntax for the SMAKE command is:
smake {options} {macros} {targets}
where:
- options
- Zero or more command line options used to
control the current SMAKE session. If no options
are specified, SMAKE uses the options specified in
the tools. ini file. If a required option is not
specified, SMAKE uses the default value for that
option specified in tools.ini.
- macros
- One or more macros of the form macro=text.
A macro defined on the command line overrides any
corresponding macro definition in the makefile.
- targets
- The targets to build.
Command line options
SMAKE options are preceded by either a dash (-) or slash (/). Case is
irrelevant when specifying options. If a required option is not
specified, SMAKE uses the default value for that option specified in
tools.ini
. The following options are valid:
- /A
- Rebuilds all targets, including targets that are not out of
date with respect to their dependents. Unrelated targets
will not be rebuilt.
See also /B, below.
- /B
- Rebuilds a target if its date and time are identical to the
date and time of the dependent. Because most
operating systems assign file date and time stamps to a
resolution of two seconds, commands that execute very
quickly might not result in a different date and time
between a target and its dependent. Therefore, SMAKE
might conclude that a target is current when it is not.
Though this option can result in unnecessary build
steps, it is recommended for use on very fast systems.
See also /A, above.
- /C
- Suppresses the default SMAKE output, which includes
any nonfatal error messages, warning messages, date
and time information, as well as the SMAKE copyright
message. If both the /C and the /K options are
specified, /C suppresses the warnings issued by the /K
option.
See also /K.
- /D
- Displays extra information during the SMAKE session.
SMAKE displays the date and time of each target and its
dependent when they are evaluated during the build,
and outputs a message when a target does not yet exist.
The names of the dependents for a target precede the
target itself, and are indented. The /D, /N, and /P
options are useful for debugging makefiles.
See also /N, /P.
- /E
- Causes the settings for environment variables to
override the macro definitions within the makefile.
- /F filename
- Specifies filename as the name of the makefile to be
used. White space consisting of zero or more spaces or
tabs can precede the filename. Using a dash (-) instead
of a filename causes SMAKE to get the makefile input
from the standard input device. Keyboard input is then
ended by typing either F6 or Ctrl-Z. SMAKE can accept
more than one makefile; use a separate /F specifier for
each makefile. If /F is omitted, SMAKE obtains its input
from the file MAKEFILE.
- /HELP
- /?
- Displays a brief summary of the SMAKE command line
syntax.
- /I
- Ignores exit codes from all commands processed by
SMAKE. To ignore errors for unrelated parts of the
build, use the /K option; /I overrides /K if both are
specified.
See also .IGNORE, /K.
- /K
- Continues building unrelated parts of the dependencies,
even if a command terminates with an error. SMAKE
normally stops processing if any command returns a
non-zero exit code. /K allows SMAKE to continue
processing any unrelated targets (those which do not
depend on the results of the current command). If an
error is encountered, SMAKE returns an error code of 1,
unlike the /I option which ignores all exit codes. /I
overrides /K if both options are specified. Also, /C
suppresses the warnings issued by /K.
See also /C, /I.
- /N
- Displays but does not execute the commands that
SMAKE would have executed. However, any
preprocessing commands (. directive, !directive) are
executed. Use /N to determine which targets are out of
date. The /N, /D, and /P options are useful in
debugging makefiles. Note that /N does not
automatically recurse to subsequent calls to SMAKE.
See also /D, /P.
- /NOLOGO
- Suppresses the SMAKE copyright message.
- /P
- Displays SMAKE information to the standard output
device before processing begins. This includes all
macro definitions, inference rules, and target
descriptions, as well as the .SUFFIXES list. Specifying /P
without a makefile or command line target causes
SMAKE to display its information without issuing an
error. The /P, /D and /N options are useful for
debugging makefiles.
See also /D, /N.
- /Q
- Checks the dates and times of targets that SMAKE
would haveupdated, but does not update any files; only
the preprocessing commands (.directive, !directive) are
executed. A non-zero exit code is returned if any target
is not up to date. Note that /Q does not automatically
recurse to subsequent calls to SMAKE.
- /R
- Clears the .SUFFIXES list and ignores the default
tools.ini
settings.
- /S
- Suppresses the display of all commands executed by
SMAKE.
See also .SILENT, @.
- /T
- Changes the dates and times of the command line
targets to the current date and time and executes the
preprocessing commands, but does not process the
normal target commands. If a command line target is
not passed, SMAKE changes the date and time of the
first target in the makefile. The contents of the target
files are not changed, only the date and time stamp is
updated.
- /X filename
- Sends all SMAKE error output to filename. This can be
either a file or a device. White space consisting of zero
or more spaces or tabs can precede filename. Using a
dash (-) instead of a filename causes SMAKE to send
the error messages to the standard output device.
SMAKE normally sends any error messages to the
standard error device. This option only affects the
SMAKE output, and not the output of any command
executed by SMAKE.
- @command
- A command file can be used to pass command line
options to SMAKE. SMAKE first attempts to interpret
command as an environment variable. If it does not
exist, SMAKE attempts to open a linker response file
with that name.
The command file name must be preceded by the @
character, with no white space separating the @
character from the file name. Use of a command file
also permits a command sequence longer than the 128
character limit imposed by DOS. The command file
options can be placed on a single line separated by
white space, or on multiple lines. If multiple lines are
used, carriage return/ line feed sequences are replaced
by spaces when the lines are concatenated into a full
command. Macro definitions can be continued on
multiple lines by putting a backspace (\) character at
the end of the lines that are to be continued.
Note:
Any command line option except
/F, /HELP, /?, or /NOLOGO can be changed from within a makefile
with the !CMDSWITCHES directive.
Specifying what targets to build
Usually SMAKE creates the first target that it finds in your makefile.
However, if you specify a target on the command line, SMAKE
creates that target instead.
For example, this makefile creates several utility programs:
all: count.exe write.exe read.exe
count.exe: count.c
sc count.c
write.exe: write.c
sc write.c
read.exe: read.c
sc read.c
To create all the targets, don't specify a target on the
command line. In the example above, SMAKE encounters the target
all first. Since the dependency list for all contains the executables
for all the utilities, SMAKE creates all the utilities.
To create just some targets, specify them on the
command line. For example, this command creates the programs
write.exe and read.exe:
smake write.exe read.exe makefile
To build a target only when you specify the
target on the command line, make sure that target has no
dependencies and is not the first target in the makefile. Such a target
can delete files you no longer need or install software you just
created. For example, suppose the makefile above ends with this
target:
clean:
del *.obj
If you type only SMAKE, SMAKE does not perform this target's
command. But if you type SMAKE clean, SMAKE performs only
this target's command and exits.
Choosing Dependency Files
Since you use dependency files to create the target file, the names of
the dependency files usually appear in the command lines that
follow the dependence line. But if an object file depends on a source
file and that source file includes a header file, the object file depends
on that header file. If that header file changes, you must recompile
the source file to create a new object file. It can be useful to include
these kinds of files in dependency lists.
Suppose that hello.cpp includes the file hello.h. The
dependence line for hello.obj looks like this:
hello.obj : hello.cpp hello.h
If a header file isn't likely to change, omit it from the dependency
list. For example, system header files are unlikely to change. System
header files include header files for standard libraries, such as
stdio.h, and header files for operating system functions, such as
windows.h.
Dependencies can be automatically generated with MAKEDEP.
Including files
To textually include files in makefiles:
!include filespec
The !include statement lets one makefile support several
configurations of a program. Use SET commands to set
environment variables for executed programs in the included file.
Defining Macros
SMAKE lets you define macros - identifiers that are replaced with
text you specify. By combining the macros in a makefile with macros
in tools. ini (see "Customizing SMAKE Sessions with tools.ini"
below), you can use a single makefile for multiple projects. Macros
are useful for specifying compiler options, paths for targets,
dependents, and inference rules.
Define a macro by putting a line with the following syntax in
the makefile, in tools.ini, or on the SMAKE command line:
macro=value_string
macro is a case sensitive combination of up to 1024 letters, digits,
and underscore (_) characters. If a macro is to be used as a
command, it cannot be null or undefined.
The value_string can be any sequence of characters, including no
characters, or white space (interpreted as a null string). It includes all
the text between the equals sign and the end of the line. Note that a
macro defined as a null string is not equivalent to an undefined
macro; it is still defined in relation to directives like !IFDEF and
!IFNDEF.
To specify an instance of a macro that is to be expanded, enclose the
macro name in parentheses and put a dollar sign in front of it, like
this:
$(macro)
If a macro name is only a single character, the parentheses are not
required.
Examples of when to use macros
Macros are especially useful for defining frequently repeated text.
For example, if you need to make sure that all your source files are
compiled with the same compiler options, define a macro that
contains those options, like this:
FLAGS=-c -g -DDEBUG=1 -ml
file1.obj: file1.cpp file2.h
sc $(FLAGS) file1.cpp
file2.obj: file2.cpp
sc $(FLAGS) file2.cpp
If you need to refer frequently to the same directory path, put the
path in a macro, like this:
MYLIB=c:\dev\lib
prog.exe: prog.obj $(MYLIB)\mylib.lib
sc prog.obj $(MYLIB)\mylib.lib
Comments in macros
A hash character (#) on a line that defines a macro starts a comment.
To use a # as part of a macro name, precede it with a caret (^). To
continue a macro definition on another line, end the first line with a
backslash (\). To specify a literal backslash at the end of a line, as
for a directory name, precede the \ with a caret. To use a
carriage return as part of a macro name, precede the carriage
return with a ^; this also continues the macro on the next line.
Specifying text to be substituted
Specify text to be substituted when an instance of a macro
is expanded by including a colon (:) followed by the string to be
substituted for, an equal sign (=), and the string to substitute. For
example:
MODEL=L # defines macro
$(MODEL) # instance to be expanded
$(MODEL:L=S) # expands 'L' to 'S'
The substitution applies only to the current instance, not to the
original macro definition. Do not put white space characters before
the colon; white space after the colon is interpreted as part of the
string to be substituted. The text to be substituted is case sensitive.
Overriding macros on the command line
Override the macros defined in a makefile or tools.ini
by specifying a new definition on the SMAKE command line. For
example, to override the definition of MYLIB in the example above,
enter a command like this:
smake MYLIB=c:\dev\test\lib
Refer to macros defined in the environment with
the operating system command SET. For example, define
temp with this command:
set temp=c:\dos
and refer to it like this:
test.obj: test.cpp
sc -cod$(temp)\test.cod test.cpp
If you define a macro in more than one place, SMAKE chooses
definitions in this order of priority:
- Definitions on the command line
- Definitions in the makefile
- Definitions in the environment (specified using the SET
command)
Note:
If a macro is to be passed on the command line,
double quotation marks (") must surround any part
of its definition that contain spaces. This is true not
only for spaces that are part of the macro's value
string, or even for spaces that appear on either side
of the equal sign (=) that separates the macro's
name from its value string.
Recursively defined macros
Ordinarily, macros are defined only for the current SMAKE session or
iteration. The only macros that are defined recursively in calls to
SMAKE from within a makefile are:
- Macros specified on the command line
- Macros predefined by environment variables
- One of the macros MAKE, MAKEDIR, or MAKEFLAG
Predefined macros
SMAKE supports the following predefined macros, which you can
use in your makefile:
Macro Description
- MAKE
- The name of the executable specified on the
SMAKE command line. The default is SMAKE.
- MAKEDIR
- The current directory when SMAKE was called.
- MAKEFLAG
- The current SMAKE options (you can change
these with the !CMDSWITCHES directive).
- $?
- Lists dependencies that are newer than the target.
- $**
- Full list of dependents of the current target.
- $*
- Current target's name and path, without the extension.
- $<
- The dependent file that is newer than the target. This macro
is only valid within commands in inference rules.
- $@
- The current target's fully qualified path name.
- $$@
- The current target's fully qualified path name. This macro is
valid only for specifying a dependent on a dependence line.
- $$
- Expands to $.
Modifiers for predefined macros
Use the following filename modifiers in combination with
the predefined macros listed above:
Table 13-5 Filename modifiers for use with predefined macros
Modifier Description
B Base file name
D Drive and directory
F Base name and extension
R Drive, directory, and base name
For example, for the file c:\project\app.exe:
$(@D) refers to c:\project
$(@F) refers to app.exe
$(@B) refers to app
$(@R) refers to c:\project\app
Predefined command and option macros
tools.ini predefines macros that correspond to commands and
command options. The option macros are undefined by default.
Define these macros to expand to the commands and options
to pass to the compiler and tools. (See tools.ini for a
list of definitions.)
Table 13-6 Predefined command and option macros
Macro Description
AS Command to run the Microsoft Macro Assembler
CC Command to run the Digital Mars C Compiler
CPP Command to run the Digital Mars C++ Compiler
CXX Command to run the Digital Mars C++ Compiler
RC Command to run the Digital Mars Resource Compiler
AFLAGS Options for the Microsoft Macro Assembler
CFLAGS Options for the Digital Mars C Compiler
CPPFLAGS Options for the Digital Mars C++ Compiler
CXXFLAGS Options for the Digital Mars C++ Compiler
RFLAGS Options for the Digital Mars Resource Compiler
Macros for predefined environment variables
In addition to the predefined macros listed above, every
environment variable that is defined when SMAKE starts up is
equivalent to a predefined macro. This can cause unexpected results
if the value of an environment variable used as a macro contains a $
character, because SMAKE interprets $ as the beginning of a macro
invocation.
Macro precedence
The order of precedence for macros in SMAKE sessions is:
- Macros defined on the command line
- Macros defined in a makefile or include file
- Macros that correspond to predefined environment
variables
- Macros defined in tools.ini
- Macros that correspond to commands or command
options, like CPP or CPPFLAGS
Using Operating System Commands
Command lines that follow dependence lines, as well as inference
rules, can contain any command that is valid on the command line.
SMAKE runs these commands when a target is out of date.
Specify multiple commands by putting each command on its own
line. If there are no commands following a dependence line, the
dependency is checked against the inference rules.
A command line must begin with one or more spaces or tabs, and
must immediately follow the dependence line. No blank lines can
separate them; however, a line containing only white space can be
used to specify a null command. Blank lines can appear within a list
of commands. Continue commands by ending the line to be
continued with a backslash (\). If any characters, including spaces or
tabs, follow the backslash, SMAKE interprets them literally.
Place single commands at the end of a dependence line by
separating the dependency from the command with a semicolon (;).
To let SMAKE know how to execute a command line, precede a
command with any of the following modifiers. More than one
modifier can be used with a command:
Table 13-7 Modifiers for SMAKE commands
Modifier Description
- Tells SMAKE to ignore the exit status from a command;
processing continues no matter what value the command
returns. Otherwise, SMAKE stops executing if a command
returns an error.
-N Tells SMAKE to halt execution only if the exit status a
command returns is greater than the number N (even if
.IGNORE or /I are not being used). Otherwise SMAKE
halts when a command returns a non-zero exit code.
@ Tells SMAKE not to display a command when it is
executed, even if /S or .SILENT are not specified.
Otherwise, SMAKE displays commands as they are
executed.
! Executes a command for each of the dependents in the
dependency list. The command must use one of the file
name macros $** or $?, or the ! prefix is ignored. $**
causes the command to be executed for all of the
dependent files. $? causes the command to be executed
only for those dependent files which are more recent
than the target.
* Allows SMAKE to accept long command lines. Causes all
arguments to be assigned to the temporary variable
$MAKE$, and all arguments to be replaced with
@$MAKE$.
<<file Specified after a command. If file is specified it must
appear immediately after the angle brackets (<<), with no
white space. If file is not specified, SMAKE uses a unique
filename.
Tells SMAKE to use inline files when processing a
command. The file is created from the literal text
beginning on the line immediately following the angle
brackets. End the inline file by beginning a line with
<<. Optionally, you can write one of the modifiers KEEP
or NOKEEP immediately after the closing <<. NOKEEP
(the default) tells SMAKE to delete file at the end of the
session. KEEP tells SMAKE not to delete file at the end of
the session (though file will be overwritten each time it is
used).
For example, in the makefile fragment below, SMAKE continues
even if the RM command returns an error, and SMAKE executes the
PROG command with command. com so you can use I/O redirection
(with the greater than symbol > in this case):
install:
-rm \bin\prog
SMAKE always executes the commands in the table below by calling
the operating system. The + modifier is not necessary with them.
break cd chdir cls copy
ctty date del dir echo
erase exit if md mkdir
pause rd rem rmdir ren
rename time type verify vol
Note:
SMAKE handles del commands itself, unless they
are followed by a file specification containing
wildcard characters. This allows you to specify a
command like:
del $(OBJS)
where OBJS is a macro that expands to a list of file
names.
Preprocessing Directives
Preprocessing directives are commands for SMAKE. SMAKE
processes these directives before it processes dependency lines and
commands. Preprocessing directives go in makefiles or
in tools.ini.
There are two types of preprocessing directives:
- Directives that begin with an exclamation point (!) work
like C preprocessing directives. The ! must be the first
character the line; it can be followed by white space. !
directives are not case sensitive.
- Directives that begin with a period (.) and end with a
colon (:) cannot appear among dependency lines and
commands. They must begin the line on which they
appear, and are case sensitive.
SMAKE supports these ! preprocessing directives:
Table 13-8 ! preprocessing directives
Directive Description
!CMDSWITCHES{+/-} opt
Turns on or off one or more command line
options with the exception of /F, /HELP,
/NOLOGO, /X, or /?. In the makefile, only the
/D, /I, /N, and /S options are valid, though
tools.ini can contain the others.
!ELSE If the preceding !IF, !IFDEF or !IFNDEF
evaluated to zero, the statements between the
!ELSE and the next !ENDIF are processed. An
additional IF, IFDEF or IFNDEF can be
combined on the same line.
!ELSEIF Equivalent to the !ELSE IF directives.
!ELSEIFDEF Equivalent to the !ELSE IFDEF directives.
!ELSEIFNDEF Equivalent to the !ELSE IFNDEF directives.
!ENDIF Marks the end of a block beginning with an !IF,
!IFDEF, or !IFNDEF directive.
!ERROR text
Stops the SMAKE session with a fatal error,
followed by the text. This directive will stop the
session even if other options, directives or
command modifiers, such as
/K, /I, .IGNORE, or -, are being used.
!IF expression
If the expression evaluates to other than zero, the
statements between the !IF and the next !ELSE
or !ENDIF are processed. See "Expressions in
preprocessing directives" below for information.
!IFDEF macro
If the macro is defined, even with a null value,
the statements between the !IFDEF and the next
!ELSE or !ENDIF are processed.
!IFNDEF macro
If the macro is not defined, the statements
between the !IFDEF and the next !ELSE or
!ENDIF are processed.
!INCLUDE <filename>
Reads and evaluates the filename and continues
the SMAKE session. If angle brackets (<>) are
used, the directories in the INCLUDE macro is
searched to locate the filename. If angle brackets
are not used, the current directory or the
specified path is searched to locate the filename.
For compatibility with NMAKE and Digital Mars
MAKE, you can specify this directive without the
! prefix.
!MESSAGE text
Writes the text to the standard output and
continues the SMAKE session.
!UNDEF macro
Removes the macro from the SMAKE symbol
table.
SMAKE supports these . preprocessing directives:
Table 13-9 . preprocessing directives
Directives Description
.IGNORE Directs SMAKE to ignore exit codes from all the
commands it processes from this point to the
end of the file. You can also use
!CMDSWITCHES+/I.
See also /I, -, -number.
.LONGCOMMANDLINE: tools
Permits longer command lines. tools is a list of
one or more executable program names, with no
path or extension. Multiple occurrences of
.LONGCOMMANDLINE are cumulative. If tools is
not specified, the current list is cleared. Any tool
on the tools list whose name appears in the
makefile has its command line arguments passed
in a temporary environment variable. For
example:
.LONGCOMMANDLINE: sc link lib
.PRECIOUS : targets
Tells SMAKE not to delete the target if the
command to make it is interrupted. If a
command is interrupted with a CTRL+C or
CTRL+BREAK, SMAKE deletes the target by
default. Making a target .PRECIOUS: will make
the target immune to deletion across the entire
makefile, not just a portion of it.
.SILENT Disables the display of command lines before
they are executed. You can also use
!CMDSWITCHES/S.
See also /S, @.
.SUFFIXES : lis
Provides a list of file name suffixes for SMAKE to
use when applying inference rules. The
predefined list (as defined in tools.ini)
contains: .exe .obj .asm .c .cpp .cxx .res
.rc.
Additional suffixes can be added by using
.SUFFIX; separate each suffix in the list with
white space, one or more spaces, or tabs. To
clear the suffix list, specify .SUFFIX without any
suffixes.
See also /P.
Expressions in preprocessing directives
The !IF and !ELSE IF directives use the result of an expression, which
is evaluated when these directives are encountered. This expression
can consist of any combination of string constants, integer constants,
or the names of external programs to be run.
Group subexpressions by enclosing them in parentheses.
Any constant string in an expression must be enclosed in double
quotation marks (""), even if it is a macro. Quoted strings can be
compared using the equality (==) or inequality (!=) operators.
Numeric values are treated as signed long integers. Numbers are
assumed to be decimal values. Octal values must start with 0;
hexadecimal values must start with 0x. Constant expressions can use
any binary operators (see below); the integer constants can also use
the unary operators.
An expression can also consist of the name of an external program,
enclosed in square brackets ([]). The program will be executed
during the preprocessing phase of makefile processing, and that
portion of the expression will be replaced by an integer value equal
to the error level returned by the executed program.
Operators
Use the following operators in expressions:
Table 13-10 Operators used in SMAKE expressions
Operator Description
DEFINED(macro)
Unary operator that evaluates to TRUE if the macro
is defined. The expression !IF DEFINED( macro) is
equivalent to the expression !IFDEF macro.
EXIST(path)
Unary operator that evaluates to TRUE if the path
exists. Some operating systems allow spaces within
the path name. If a space is used, the path must be
surrounded by double quotation marks (").
+ Addition
- Subtraction
* Multiplication
/ Division
== Equality
!= Inequality
> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
&& Logical AND
|| Logical OR
<< Left shift
>> Right shift
% Modulus
Running programs with preprocessing directives
When you run a program with a SMAKE preprocessing directive, you
typically obtain and test its exit code and execute other commands
based on the result. For example:
!if [c:\mydir\myprog] == 0
# continue processing
!else
# do something else
!endif
Command line macros are expanded before SMAKE executes the makefile.
Inference Rules
Inference rules tell SMAKE how to automatically create
certain types of files. For example, to define rules that tell
SMAKE how to create an object file (.obj) from a C++ source file
(.cpp) and how to create an executable file (.exe) from an object
file (.obj). Inference rules reduce the number of actions needing
to be typed in.
SMAKE interprets inference rules as templates for creating a target
from dependent files, based on the extensions of the files involved.
Use predefined inference rules or write your own; they can
be specified in the makefile or in tools.ini.
To determine priorities for applying inference rules, SMAKE uses the
list associated with the .SUFFIXES directive.
When SMAKE applies inference rules
SMAKE applies inference rules when:
- A dependence line does not contain any dependents or is
not followed by any commands
- A target is specified that does not exist in the makefile (or
the makefile does not exist).
- A dependent file does not exist and is not itself a target.
Inference rule syntax
The dependence line for an inference rule contains the source file
extension, the destination file extension, and a colon. Inference rules
have the following syntax:
{dependent_path}.dependent_ext{target_path}target_ext:
commands
where dependent_path is the path for the dependent file and
dependent_ext its extension, and target_path is the path for the
target and target_ext its extension. If no paths are specified, SMAKE
looks for the files in the current directory. commands are the steps
SMAKE will take if the dependent file is out of date.
Use macros for paths and extensions. Use the macros in
Table 13-4 to specify the arguments. Do not use white space in an
inference rule.
For example, this rule builds an object file from a C++ file:
.cpp.obj :
sc -c $* # $* contains the name of
# the target file without
# an extension
A simple makefile can use inference rules to create a program from
two source files, such as:
.cpp.obj
sc -c $*
.obj.exe
sc $**
test.exe: test.obj util.obj
test.obj: test.cpp util.h
util.obj: util.cpp
Levels of inference rules
SMAKE can interpret no more than one level of inference rules. For
example, this makefile does not produce an executable:
.c.obj: ;sc -c $
.obj.exe: ;sc $
hello.exe: hello.c # ERROR
How inference rules work
Inference rules work on targets and dependents with the same file
name and different extensions; they do not match multiple files. For
example, you can specify an inference rule to build myprog.obj
from myprog.cpp, but not yourprog.obj from myprog.cpp.
For example, given this inference rule:
.cpp.obj: $(CPP) $(CPPFLAGS) $<
SMAKE will apply this rule to any pair of target/dependency files in
the current directory (or on the specified path) that have the same
name, where one has the extension .cpp and the other .obj.
SMAKE will expand the predefined macros and run the resulting
commands on those dependents that are newer than their
corresponding targets (as specified by $<).
If the target specified has a .exe extension, SMAKE searches for a
file with the same base name and an extension that is in the
.SUFFIXES list to find which inference rule to use.
If the dependence line is followed by commands but does not list
dependents, SMAKE will use the inference rules and the .SUFFIXES
list to determine the dependent file and then apply the commands
specified in the makefile for the target.
Order of precedence for inference rules
SMAKE determines the precedence of inference rules as follows:
Order Inference Rule
1 Defined in a makefile; the latest defined
inference rule applies.
2 Defined in the tools.ini file.
3 Predefined inference rule.
Predefined inference rules
The following inference rules are predefined in tools.ini:
Table 13-11 Predefined inference rules
Inference rule Command
.asm.exe $(AS) $(AFLAGS) $*.asm
.asm.obj $(AS) $(AFLAGS) /c $*.asm
.c.exe $(CC) $(CFLAGS) $*.c
.c.obj $(CC) $(CFLAGS) /c $*.c
.cpp.exe $(CPP) $(CPPFLAGS) $*.cpp
.cpp.obj $(CPP) $(CPPFLAGS) /c $*.cpp
.cxx.exe $(CXX) $(CXXFLAGS) $*.cxx
.cxx.obj $(CXX) $(CXXFLAGS) /c $*.cxx
.rc.res $(RC) $(RCFLAGS) /r $*
Customizing SMAKE Sessions with tools.ini
Customize SMAKE sessions by placing default values for
command line options in the initialization file tools.ini. Any
settings specified in tools.ini will be used for every SMAKE
session, unless /R is specified on the command line. The following is
a sample of SMAKE information in tools.ini:
[SMAKE]
# macro redefinitions
CCFLAGS=-A -wx -mn
DEBUGFLAGS=-gh
# inference rule
.c.obj:
$(CC) $(CCFLAGS) $<
SMAKE information must follow one of the tags [NMAKE] or
[SMAKE]. Case is not significant. The format for comments is the
same as in makefiles. tools.ini is read before the makefile; you
can override macro definitions and inference rules specified in
tools.ini in the makefile.
When it searches for tools.ini, SMAKE searches the directory for
executable files first, then the current directory, and finally in the
directory specified by the INIT environment variable.
Creating Response Files
SMAKE can automatically creates a response file on disk, which
contains text defined by the makefile. Specify
the response file by name as input to another program, link
OPTLINK or LIB.
To write a line to a response file, use the command ECHO and
redirect its output with one and two greater-than signs (>>). Write
the first line with one greater-than sign so that ECHO creates a new
file. Write the rest of the lines with two greater-than signs so that
ECHO does not overwrite the file you just created.
For example, this makefile creates the program main.exe from the
source files main.cpp and funcs.cpp. It not only creates a linker
response file, but also writes the linker's output to cmp.err:
main.exe: main.obj funcs.obj
echo main.obj+funcs.obj > linker.rsp
echo main.exe,,, >> linker.rsp
+link @linker.rsp > lnk.err
main.obj: main.cpp
+sc -c main.cpp > cmp.err
funcs.obj: funcs.cpp
+sc -c funcs.cpp >> cmp.err
SMAKE Error Messages
This appendix lists and describes error messages produced by the SMAKE
utility.
List of SMAKE Error Messages
When SMAKE encounters an error, it prints a message to the screen
describing the error and then returns to the operating system.
SMAKE error messages begin with the text "SMAKE fatal error:"
followed in most cases by the name of the makefile and the current
line number in the makefile, followed by one of the messages
described below.
access denied
DOS could not perform the action specified in a command block.
can't create response file filename
SMAKE could not find or open response file filename as specified on
the command line (using the @ option).
can't create temporary file filename
SMAKE could not create the temporary file filename.
can't execute command
In a command block, the specified (DOS) command could not be
executed.
can't nest response file
SMAKE was passed a response file (using the @ option) that
included another @ command.
can't open error file filename
SMAKE could not open the error file filename.
can't open include file filename
SMAKE could not open the #include file filename.
can't open makefile filename
SMAKE could not open the makefile filename.
can't reopen filename for input
SMAKE could not reopen the makefile filename after closing a
#included file.
command expression retured with error code number
In a command block, the command expression failed; it returned
error code (ERRORLEVEL) number.
directory not found
In a command block, the specified (DOS) directory was not found.
don't know how to make filename
SMAKE did not have enough information to make target filename
based on the makefile.
error in macro substitution syntax: identifier
The macro identifier uses internal substitution (for example,
$(XYZ: a=b) incorrectly.
exceeded max nesting level for conditionals
Conditional directives (for example !IF) within the makefile were
nested greater than 32 levels deep.
extension ext too long in rule
In a rule, SMAKE encountered a file extension ext that was too long.
file not found
In a command block, the external (DOS) command was not found.
illegal operator and/or operand in expression
A conditional expression was not written properly.
illegal string expression
On the specified line, a string expression within a conditional
statement used invalid syntax.
invalid option number in parameter number
The number th option in the number th parameter on the SMAKE
command line was invalid.
invalid parameter number
The number th parameter on the SMAKE command line was invalid.
not enough memory for command
In a command block, there was not enough memory to execute the
specified (DOS) command.
out of memory
SMAKE could not allocate enough memory to continue.
parameter number requires a filename
The number th parameter on the SMAKE command line requires a
file name argument.
special macro expression is undefined in this context
The predefined macro expression was used incorrectly.
syntax error: expression
On the specified line, expression contained a syntax error.
text found after !ELSE
SMAKE encountered text on a line following a !ELSE directive.
too many rules or blocks for target filename
Target filename was associated with too many rules or blocks.
unexpected directive
SMAKE encountered an invalid conditional directive (one beginning
with a ! character).
unknown error
An unexpected error occurred.
unknown error number executing command
In a command block, a command returned an error code number
that SMAKE could not interpret.
unmatched quotes in command identifier filename
SMAKE was passed a response file (specified using @) that contained
unmatched double quotation marks (").
Copyright © 1995-2004 Digital Mars. All Rights Reserved.