Managing Code with smake
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
- Error messages
What is smake?
smake automates program maintenance, instead of typing in commands to compile and link a 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 dmc -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 dmc -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 dmc -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 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.
Character | Description |
---|---|
: | 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 dmc 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 intools.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 dmc count.c write.exe: write.c dmc write.c read.exe: read.c dmc 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.hIf 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=text
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 text 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 dmc $(FLAGS) file1.cpp file2.obj: file2.cpp dmc $(FLAGS) file2.cpp
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 dmc 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 dmc -cod$(temp)\test.cod test.cpp
To 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:
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.)
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:
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:
Directive | Description |
---|---|
!CMDSWITCHES{+/-} option | 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:
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: dmc 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:
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 : dmc -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 dmc -c $* .obj.exe dmc $** 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: ;dmc -c $ .obj.exe: ;dmc $ 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: $<
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:
Inference rule | Command |
---|---|
.asm.exe | $*.asm |
.asm.obj | /c $*.asm |
.c.exe | $*.c |
.c.obj | /c $*.c |
.cpp.exe | $*.cpp |
.cpp.obj | /c $*.cpp |
.cxx.exe | $*.cxx |
.cxx.obj | /c $*.cxx |
.rc.res | /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: $<
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 +dmc -c main.cpp > cmp.err funcs.obj: funcs.cpp +dmc -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, ) 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 (").