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)