Creating a Makefile and Using the bmake Utility

The MDL development environment includes a utility called bmake that produces executable images from makefiles. These makefiles contain the commands and directives needed to build your application. bmake was developed by and for Bentley Systems specifically for MicroStation development.

In programming environments such as MDL, changes to one file often causes other files to become obsolete. bmake recognizes when files have changed and detects and recreates any obsolete files based on the relationships between the file creation times.

For example, suppose you make a change to one of your MDL source files. For your change to be reflected in the application's executable, you must first run the MDL compiler on your source (.mc) file to generate or make its object (.mo) file. Next, you must run the MDL linker on your object file(s) to create the application (.ma) file.

In your makefile, you would first list your object file as a target file and tell bmake that it depends on the source file. When the target file is older than any of its dependents, the build commands that recreate the target (in this case, the MDL compiler) are executed. Next you list your application file as a target file that depends on the object file. bmake always processes makefiles linearly from beginning to end, so that both the object and the application files are rebuilt (assuming there are no errors).

Using file extensions to differentiate types of files is good programming practice. bmake exploits this by allowing you to define rules that describe how to create a file with one extension from a file of another extension. After you define these rules, you do not have to explicitly tell bmake how to recreate every target file. It infers the build commands from the file extension of the target and its dependencies and your rules.

In many instances, you will collect your rules and definitions into a single make include (.mki) file like the MicroStation mdl.mki file which contains the default macros and build rules for MicroStation MDL applications. This file is then included by your makefile to provide the needed macro and rule definitions.

You should use bmake instead of similar programs you may have used in other development environments for the following reasons:

Makefile format

The makefile format is similar to the format for input files to other make programs. Each makefile must contain a series of records, each of which must be one of the following types:

A `\' (backslash) as the last character on a line signals that the line continues. (To end a line with the backslash character, use two consecutive backslashes). No spaces can follow the backslash if it is used to signal the line continues.

The `#' character denotes a comment. bmake ignores the remainder of the line following this character. Comment lines cannot be continued over multiple lines.

Each makefile record type is described below.

Example

A listing of the makefile for basic.ma can be found in "CompleteExample".

Macros

There are three locations where a bmake macro may be defined:

  1. In the body of a makefile (or in BMAKE_OPT).
  2. As an environment variable, outside of bmake.
  3. On the command line using the -d command line option.

bmake looks for definitions in the above order, so macros defined in makefiles take precedence over those defined elsewhere.

Macros defined in a makefile are not case sensitive.

To define a macro in the body of a makefile, use one of the following formats:

format description
macro = definition Standard assignment. This is the most commonly used method.
macro =% definition Standard but value being assigned is expanded before assignment.
macro + definition Definition is appended to existing macro value.
macro +% definition Definition is appended to existing macro value, and is expanded before being appended.

bmake performs macro substitution when it reads $_macro_ in the input makefile, where the `_' characters signify the type of expansion as shown in the table below.

macro description
$(name) Expand by iterative substitution.
${name} Expand by iterative substitution removing the last character if it is a path separator character.
$[name] Expand by value; expand the macro to its literal string value without doing any further substitution.

These macros are reserved and expand as follows:

macro expansion
$@ the current target file
$? all dependency files that are newer than the target file
$= the newest dependency file
$< the current dependency file
$* the base filename of the target file
$% the directory of the first dependency

bmake also predefines macros for each operating system platform. The list of macros can be obtained by typing:

bmake -p

Example

This is a simple macro from basic.mke:

%if defined (_MakeFilePath)
baseDir = $(_MakeFilePath)
%else
baseDir = $(MS)/mdl/examples/basic/
%endif

Advanced Example

Suppose you have some modules that you want to compile with the default .c.o rule, except you want -I included on the compiler command line of the PC. Here is one way to accomplish this:

%if pm386
savecopt =% $[copt]
copt + -I${appDir}
%endif
myfile1$(oext) : myfile1.c
myfile2$(oext) : myfile2.c
%if pm386
copt =% $[savecopt]
%endif

Rules

A rule in a makefile defines how to build targets with a given extension from a dependency with a given extension. The general format of a rule is as follows:

.[dir1;dir2;...;dirN]depExt.targetExt:
buildCommands

If the directories section [dir1;dir2;...] is missing, the rule applies to files in any directory. If a target file depends on more than one file, the extension of the first one in the list is used to find the rule. If more than one rule applies to a target, the last rule in the makefile has precedence. For this reason, you should put rules that are directory-specific after any non-directory-specific rules in the makefile.

Example (from mdl.mki)

.mt.r:
$(msg)
> $(o)temp.cmd
-o$@
%if privateInc
-i$(privateInc)
%endif<BR>
$(rscCompIncs)
$(altIncs)
-i$(publishInc)
-i$(publishIdsInc)
-i$(stdlibInc)
$%$*.mt$(RTypeCmd) @$(o)temp.cmd
<
$(RTypeCmd) @$(o)temp.cmd
~time

Dependencies

A dependency in a makefile specifies that one or more target files depends on one or more dependent files. The general format of a dependency is as follows:

<P>target1 target2 ... targetN: dep1 dep2 ... depN
buildCommands</P>

If any dependent files are newer than any target files, the build commands are executed.

Dependencies are always processed in their makefile order, so you should organize your makefiles accordingly. (That is, if one target depends on another target, its dependency should appear later in the makefile.)

Build commands are a sequence of lines, executed one after the other, to build the target file. If a build command returns an error, bmake terminates. If there are no build commands, bmake attempts to use a predefined rule.

Example

$(o)basic.mo : $(baseDir)basic.mc $(baseDir)basic.h

(where (o) has been defined as the object directory.)

The first character in a build command can have special significance:

character meaning
- Ignore the status returned from this command. Do not terminate if the command returns an error.
"@" Do not echo, even when bmake is not operating in silent mode.
| Echo only this line, even in silent mode. This is useful for informational messages to the user.
~ Use a built-in bmake command (see below).
>filename Write all lines in the makefile until a < to the specified file. This is typically used to write the dependencies to a file to generate a link file.

The following are built-in bmake commands:

command description
~CURRENT Print the current time.
~TIME Set the target file's modification date to the date of the newest dependency file. This should generally be the last build command for a target file.

Conditional

A conditional includes or excludes sections of the makefile, depending on whether macros are present. The following types of conditionals are supported:

directive description
%ifdef If the macro exists, include subsequent lines.
%if If the expression yields a non-zero result, include the subsequent lines. The conditional if defined is functionally equivalent to ifdef.
%ifndef If the macro does not exist, include subsequent lines.
%else Use with ifdef and ifndef.
%elif Use with ifdef and ifndef.
%endif Close an ifdef or if block.
%undef Undefines a macro.
%iffile If the named file exists, include subsequent lines.
%ifnofile If the named file does not exist, include subsequent lines.

The always: directive precedes one or more commands that are executed regardless of condition.

Example

%if defined (_MakeFilePath)

Expressions

Expressions can be written using the operators in the table below to act on constant values, exit codes from commands, strings, chars, macros, and file-system paths.

operator associative integer constant string or char constant
+ binary, unary addition string or char concatenation
- binary, unary subtraction
* binary multiplication
/ binary division
% binary modulus
& binary bitwise AND
^ binary bitwise XOR
&& binary logical AND
|| binary logical OR
<< binary left shift
>> binary rifht shift
== binary equality
!= binary in equality
< binary less than
> binary greater than
<= binary less than or equal to
>= binary greater than or equal to

Example

An example of using expressions is shown below.

PERSON = "This person is "
%if PERSON + "Ben Franklin" == "This person is Ben Franklin"
do something
%elif PERSON + "Winston Churchill" == "This person is Winston Churchill"
do something else
%endif

Include Files

As mentioned earlier, bmake allows you to include other files (.mki files) into the current makefile using the include preprocessor directive.

The files included must be written using the same syntax as bmake makefiles. That is they must be other makefiles or files containing default macro and rule definitions. Examples of this can be found in all distributed MDL example applications.

Example

%include mdl.mki
...
%include $(baseDir)basicrsc.mki

As shown above, basic.mke includes three additional files to assist in, and complete, the application build process:

file description
mdl.mki This is the main MicroStation MDL make include file. It contains the default rules and macro definitions that should always be used when building MDL applications, Dynamic Link Libraries (DLL), and external programs.
basicrsc.mki This include file is actually another makefile which contains the steps necessary to complete the building of the basic application. The steps defined in this file cause the language-specific portions of the application to be compiled and combined with the generic portions to create the final application (.ma) file. It is recommended that the language-specific portions of an application be separated from the rest of the application if the application needs to be delivered in different languages. This makefile is listed in the "Complete Example" chapter with basic.mke.

Starting the bmake utility

After installation, the bmake utility is in the "\$(MS)\\mdl\\bin" directory.

The command line syntax for starting the bmake utility is as follows:

BMAKE [OPTIONS] MAKEFILE

Normally, [options] are not specified on the command line, but in the BMAKE_OPT environment variable so that the user does not have to specify them every time bmake is used. The MicroStation Developer Shell defines BMAKE_OPT.

bmake supports multiple include paths. They may be specified via either the command line or the environment variable BMAKE_OPT. The standard BMAKE_OPT definitions are as follows:

set BMAKE_OPT=-I$(MS)\mdl\include

Therefore, the command to make basic.ma without BMAKE_OPT set would be:

BMAKE -I BASIC

But with BMAKE_OPT set, which is the default, it would be shortened to:

BMAKE BASIC

Command line options are as follows:

option description
-a Always build. For every dependency, execute the build command regardless of whether dependent files are newer than the target files. This option causes bmake to remake all files.
-d Define a macro. For example, including -ddebug=1 on the command line is the same as specifying debug=1 in the makefile.
-f[ext] Filter targets (only build files with [ext] file extensions.
-i Ignore errors. Ordinarily, bmake terminates when a build command returns a non-zero exit status. With this option, bmake ignores errors and continues.
-l List targets. bmake prints the target file before executing build commands (helpful for debugging).
-m Missing files are acceptable. Ordinarily, bmake terminates if any dependency files are missing. With this option, bmake continues.
-n No execution. List, but do not execute the build commands.
-p Print macros as defined (helpful for debugging makefiles).
-q Quiet mode (no salutations).
-s Enter silent mode. Do not print build commands as they are executed.
-t Touch target files. Set the target file date to the date of the latest dependency file rather than executing the build commands.
-v Print macros in verbose mode.
-x[exe] Run [exe] on error.
-C Print conditional level.
-I Specify the include (*.mki) file path.
-L Log file path
-P Always process this makefile.
-X Generate _MakeFilePath using DOS 8.3 Format
-aD Delete target files. When specified on the command line, bmake will define a macro BMAKE_DELETE_ALL_TARGETS.

Copyright © 2017 Bentley Systems, Incorporated. All rights reserved.