Manpage logo

mk - build stuff


MK(1) General Commands Manual MK(1)

NAME

mk — build stuff

SYNOPSIS

mk [−hkpsSv] [−C dir] [−f makefile] [−o objdir] [−V var] [target ...]

DESCRIPTION

mk is designed to help you build stuff. It is specifically designed for larger projects, for which make(1) is not suitable enough.

The following options are available:

−h

Print a help page for the current directory.

−hv

Similar to −h, but do so recursively for all subdirectories.

−p

Dump the current directory’s makefile.

−pv

Same as −p, but do so recursively for all subdirectories.

−s

Do not echo commands, as they are executed. Equivalent to specifying ’@’ before each command in the makefile.

−S

stop processing when an erorr is encountered. This is the default behavior and is the opposite of −k.

−k

Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creating caused the error. This options is the opposite of the −S option.

−v

Cause mk to print more verbose output. This option can be specified multiple times, to increase the verbosity. This option is negated by the −s option.

−C dir

Change the current working directory to dir.

−f mk

Read file mk instead of the default makefile. Contrary to other implementations, only the last occurence of this option is honored.

−o obj

Put generated artifacts into the obj directory. mk will also search for artifacts in this directory. This is very useful for out-of-tree builds.

−V var

Print the expanded value of var to the standard output and exit.

DEPENDENCY LINES

Dependency lines consist of one or more targets, and zero or more prerequisites:

target ...:[prerequisite ...]

This creates a relationship, where the targets “depend” on the prerequisites, and are built from them.

Targets must consist of only characters in “[a-zA-Z._-]”. Dependencies can additionally contain “/” characters, but the resulting paths must be relative, and cannot cross directories, that are outside the build.

SHELL COMMANDS

Each target may have associated with it a series of shell commands, normally used to build the target. While several dependency lines may name the same target, only one of these dependency lines may be followed by shell commands, and thus define a complete target rule.

If a command line begins with one of the characters, ‘@’ or ‘’, the command is treated specially:

@

causes the command not to be echoed before it is executed.

causes any non-zero exit status of the command line to be ignored.

Commands are executed using /bin/sh in "set -e" mode, unless ‘’ is specified, or the shell is broken (in case of Minix-vmd).

INFERENCE RULES

TODO

VARIABLE ASSIGNMENTS

Variables in mk are much like variables in the shell:

name = value

They are also sometimes refered to as ‘macros’. Variable names can only consist of “[a-zA-Z0-9._-]” and may not start with a digit or hyphen. By tradition names consist of all upper-case letters.

The following operators can be used to define variables:

=

Assign the value to the variable. Any previous value is overriden.

:=

Like “=”, but expand the value, before assigning it.

::=

Currently equivalent to “:=”.

+=

Append the value to the current value of the variable.

?=

Assign the value, if it is not already defined.

??=

Similar to “?=”, but take environment variables into account.

!=

Perform variable expansion and pass the result to the shell for execution. The output of the shell command will be assigned to the variable.

Any whitespace before or after the assigned value will be removed; if the value is being appended, a single space is inserted between the previous contents of the variable and the appended value.

Variables are expanded by surrounding the variable name with curly braces (‘{}’) and preceding it with a doller sign (‘$’). If the variable name consists of only a single letter, the surrounding braces can be omitted.

Variables can be made visible into subdirectories using the “.EXPORT:” directive.

SPECIAL VARIABLES

The following special variables can be used:

$$

The literal $ character.

$.

Relative path to the top-level directory. This is the shortform of ${.TOPDIR}.

$@

The name of the target currently being built. This is the shortform of ${.TARGET}.

$<

The name of the prerequisite from which this target is to be built, if a valid inference rule (suffix rule) is in scope. This is the shortform of ${.IMPSRC}.

All source dependencies. This is the shortform of ${.ALLSRC}.

$*

This is the shortform of ${.IMPSRC:T}.

${.SUBDIRS}

A list of subdirectories specified by the .SUBDIRS: directive.

${.EXPORTS}

A list of variables specified by the .EXPORTS: directive. This is useful for invoking other build systems in foreign subdirectories.

${.OBJDIR}

The object directory specified by the −o objdir option, or the current directory.

${.MAKEFILES}

A list of Mkfiles from this directory upwards.

${SHELL}

The shell used to interpret the command lines.

${MAKE}

The name of the mk command. Alias for ${.MAKE}.

${MAKEFLAGS}

A list of options given to mk. Alias for ${.MAKEFLAGS}.

MODIFIERS

Modifiers can be applied to a macro expansion using the following syntax:

${NAME:modfiers...}

The following modifiers can be applied to variable expansions:

:U

Make all characters in string uppercase.

:L

Make all characters in string lowercase.

:F

Search for files in either the source directory, or in ${.OBJDIR}.

:E

Replace each word by it’s suffix. “suffix” refers to the file extension.

:R

Replace each word by everything but it’s suffix.

:H

Replace each word by it’s dirname(1) equivalent.

:T

Replace each word by it’s basename(1) equivalent.

:Mpattern

Only retain words that match pattern.

:Npattern

The opposite of the :M modifier.

:Jseparator

Concatenate each word and separate the resulting string by separator.

:name=value

Replace every occurence of name with value. This has to be the last modifier.

These are a few examples for using macro modifiers:

SOURCE = main.c lex.l parse.y gen.S

# Only retain words that match the glob *.c
CFILES = ${SOURCE:M*.c}

# Replace all the file extension .c with .o
COBJS = ${CFILES:.c=.o}

clean:

# Use :F to search for the object files in ${.OBJDIR}

rm -f ${COBJS:F}

print:

# Multiple modifiers can be specified:

@echo ${SOURCE:T:U}

INCLUDE STATEMENTS

Using the include statement it is possible to instruct mk to read another file.

Examples:

# Include a file called "templates.mk"
include
templates.mk

# Try including a file called "config.mk", if it exists
-include
config.mk

SUBDIRECTORIES

Subdirectories can be declared using the .SUFFIXES: directive.

The handling of subdirectories is done lazily, such that the Mkfile of the subdirectory is only parsed, once a target in that subdirectory tree is needed.

Examples:

# Declare subdirectories foo and bar
.SUBDIRS:
foo bar

# Depend on targets from other directories
foobar: foo/libfoo.a bar/libbar.so ../config.h

# $ˆ refers to the list of prerequisites

build -o foobar

# Print a ", " separated list of subdirectories
list:

@echo "${.SUBDIRS:J, }"

# Depend on the clean target of every subdirectories:
clean: ${.SUBDIRS:=/clean}

# Prefix with ${.OBJDIR}/ for compatibility with the -o option.

rm -f ${.OBJDIR}/foobar

FOREIGN SUBDIRECTORIES

TODO

Foreign subdirectories allow integration with other build systems.

This is an example of integrating an example Autotools dependency:

# Declare hello as a "foreign directory"
.FOREIGN
: hello

HELLODIR = hello

## External make used for building libhello
EMAKE
?= make

## C Compiler
CC
?= cc

## C Compiler Flags
CFLAGS
?=

## Installation Prefix
PREFIX
?= /usr/local

## Installation destination directory
DESTDIR
?=

.EXPORTS: CC CFLAGS DESTDIR

## Build hello/hello and xhello
all: hello/hello xhello

## Delete build artifacts
clean: hello/clean

rm -f xhello

## Delete all non-source files
distclean: clean

rm -rf ${HELLODIR:F}

rm -f ${.OBJDIR}/{hello.tgz,.hello-configure,.hello-extract}

## Install hello and xhello into ${DESTDIR}${PREFIX}
install: hello/install xhello

mkdir -p ${DESTDIR}${PREFIX}/bin

cp -f ${.OBJDIR}/xhello ${DESTDIR}${PREFIX}/bin/

## Download hello source
hello.tgz:

ftp -o $@ https://got.stuerz.xyz/download/hello-2.0.tgz

.hello-extract: hello.tgz

tar -xzf $< -C ${.OBJDIR}

touch $@

.hello-configure: .hello-extract

(cd ${HELLODIR:F} && ./configure --prefix=${PREFIX})

touch $@

# Check if $< is fresh in hello
hello?:

test -e ${HELLODIR:F}/Makefile

${EMAKE} -q -C ${HELLODIR:F} ${.EXPORTS} "$$(echo "$<" | sed ’s#./##’)"

# Build $< in hello
hello!: .hello-configure

${EMAKE} -C ${HELLODIR:F} ${.EXPORTS} "$$(echo "$<" | sed ’s#./##’)"

## Build the xhello binary
xhello: xhello.c hello/libhello.a

${CC} -o $@ xhello.c -I${HELLODIR:F} -L${HELLODIR:F} -lhello ${CFLAGS}

CONDITIONALS

TODO

OTHER DIRECTIVES

TODO

COMMENTS

Comments begin with a single hash (‘#’) character, anywhere but in a shell command line, and continue to the end of the line. A (#) character within a shell command line will be interpreted as a comment by the shell.

DOCUMENTATION COMMENTS

Documentation comments are a special variant of comments, which can only appear before rule and macro definitions. To define a doc-comment, two hash (‘#’) characters must be used.

Example:

# This is a doc-comment for macro NAME
## Define the name of the project
NAME = hello

# Macros in doc-comments will be expanded:
## Build ${NAME}
${NAME}: ${NAME}.c

${CC} -o $@ $<

SPECIAL TARGETS

TODO

ENVIRONMENT

TODO

FILES
Mkfile

default makefile

EXAMPLES

The following is a list of real-world projects built with mk:

desktop: https://got.stuerz.xyz/?action=summary&path=desktop.git

286bsd: https://got.stuerz.xyz/?action=summary&path=286bsd.git

EXIT STATUS

The mk utility exits 0 on success, and >0 if an error occurs.

SEE ALSO

make(1), sh(1)

STANDARDS

This implementation of make(1) does not strictly follow any POSIX standard, but still most simple Makefiles will work fine.

HISTORY

Over the the long history of make, there have been many (competing) implementations of make(1):

Original make from PWB/UNIX 1.0

GNU Make

Various versions of BSD Make

Plan 9’s mk(1)

AUTHORS

Benjamin Stürz <[email protected]>

CAVEATS

All paths used as targets/dependencies must be relative.

The handling of the ‘?=‘ and ‘??=‘ is different from POSIX.

The way environment and commandline variables are treated is different from POSIX.

BUGS

There exists another project called mk(1).

Defining recursive dependencies can lead to mk crashing.

Specifying multiple targets at once is broken.

This manual is unfinished.

TODO

Run commands of the same rule in the same shell, instead of creating a new shell for every command line.

Create a detailed list of all features.

Create a second implementation in Rust, which will support parallel execution of targets.

Create a comparison to other makes.

See TODO.md file.

Optional prerequisites (continue even if the prerequisite failed).

Numbered prerequisite macros.

Strip “./” prefix for $< and other variables. GNU January 4, 2025 MK(1)


Updated 2026-06-01 - jenkler.se | uex.se