These Makefiles are used for building systems software projects. They support compilation of assembly, C, C++ and C* code as well as a variety of data/asset formats into object code. The Slick Makefiles are a living example showing how meta-build systems like CMake and SCons are unnecessary to target many platforms. They are a vastly simpler answer to the problem of building software, and they make no sacrifices in ability to output.
There are two files comprising the distribution:
targets.mk. In the project root where the
Makefile resides, a folder
etc/ is created to contain these two files. In the
base.mk is included at the top, before anything else;
targets.mk is included last, after the declaration of sources, flags and such.
There are a few kinds of variables provided by the Makefiles: environment identifiers, programs, program flags, and suffixes. Each of these categories has different properties of modifiability and configuration.
As variables are listed, they will have a few attribute markers displayed next to them, showing whether the variables are mutable (i.e. modifiable) using a Greek delta Δ, whether they are cascading (change other variable defaults) using a Greek xi Ξ, and whether they are “shelled”, i.e. modifiable from the shell command line or shell environment using a Greek sigma Σ.
There are two special environment variables the Slick Makefiles will recognise for altering behaviour:
SLICK_OVERRIDE. The former will enable the printing of build variable values for diagnostics, and the latter will cause the definitions of flag variables to fully override the default values instead of appending to them, the default behaviour.
Environment identifiers, or envidents for short, are variables provided to application developers to dictate the desired toolchain and the machine being targeted. This is more robust than having developers modify the other kinds of variables directly, as it is meant to automatically figure out desired defaults.
$TPis set to this variable.
$TCis discerned from this variable.
include/subdirectories for use in portable variable definitions in the user’s Makefile.
These are pretty self-explanatory. Compilers, assemblers, transmogrifiers, linkers, script interpreters, and any other utilities with canonical names are on this list.
llvm, as LLVM’s assembler is for IR. When using LLVM with projects depending on assembly, it is recommended to set this variable to the appropriate assembler in the
Linux, setting this to a path where
tcccauses the Makefiles to adjust for using the Tiny C Compiler. This is used as the default
$LD, unless C++ sources are present.
These are options, in the form of space-separated array-strings, passed to many of the above programs. When these are set in the main Makefile or in the shell, the contents of those definitions will be appended to the default values; this behaviour can be undone by setting the
SLICK_OVERRIDE variable on invocation. This category also includes file parameters for various programs, including the commonly named
$DEFINESthis does not propagate to the assembler, as the default lacks a flag for undefining macro constants.
#defines are providedto the
$CPPand the assembler to allow code to be made more amicable to the specifics of different machines. They are always prefixed with
CFG_. The glossary of such definitions is provided in the appendix.
This group of variables serve as glue for portability of files, as these Makefiles support cross-compilation as the only strategy for many targets. By design, these are not modifiable directly; if changing them is necessary, change the target platform instead.
.exe, or GBA where it is
This section covers how to write the actual project Makefile, using the Slick scaffolding detailed here.
base.mk. If the reference packages are in use, it can be pathed relative to the
$AQ environment variable:
The project needs to be named with an identifier. This can be anything, but it is best to pick something concise and alphanumeric only, as it is used to construct the output file name(s).
PROJECT := mycommand
Next, define the desired targets. For instance:
# put a ‘1’ for the desired target types to compile EXEFILE := 1 SOFILE := AFILE :=
Define system and local
#includes, as well as any libraries and the folders to find them in. Usually, working defaults will be set up automatically for these, but values can be set here to get appended to the ultimate values used.
# space-separated path list for #includes # <system> includes INCLUDES := # "local" includes INCLUDEL := src # space-separated library name list LIBS := LIBDIRS :=
Now define all of the sources. These are each space-separated lists of relative file paths, and the conventional way to organise them is using backslashes to have one file per line, like so:
# sources SFILES := CFILES := \ src/errors.c \ src/extra.c \ src/main.c CPPFILES := CSTFILES := PUBHFILES := \ include/extra.h PRVHFILES := \ src/errors.h \ src/extra.h # test suite sources TES_SFILES := TES_CFILES := TES_CPPFILES := TES_CSTFILES := TES_PUBHFILES := TES_PRVHFILES :=
In order for them to be picked up by the auto-formatter, header files may be listed as well. At present there is no other use for the variables. There are also a second set of sources for test batteries.
targets.mk to make the thing work:
There are several techniques that can be used to modify the behaviour of the Slick Makefiles to work differently. This section goes over these one-by-one with explanations and rationales.
Slick provides distinct variables
$INCLUDEL for public and private include folders, respectively. This makes it easy to separate private header files that are only consumed internally from the public API provided to users in library code. This also combines with ADP 1’s provision of a distinct
include/ folder for public headers, so it is clear that headers in
src/ are private. Since
$INCLUDES translates to angle-bracket includes and
$INCLUDEL translates to quoted includes, it becomes obvious and consistent in source code what files are coming from where, and what duties are imposed upon them by the project’s constraints.
Normally, the linker is automatically set as needed based on the composition of a project’s sources. However, it may be necessary to override the linker anyway in order for a project to build. One situation where this is needed is with a project using only C sources that statically links into a C++ library; the linker will not auto-detect to use C++ because there are no
$CPPFILES, but there are C++ ofiles inside the static library which will fail to link otherwise. This can be done with a line like so, written just before
targets.mk is included:
LD := $(CXX)
The Slick Makefiles provide six (6) different targets to choose from. The default is
debug, but there are also
ubsan as well. Here are the differences:
debug: full symbols for use with GDB, LLDB and Valgrind. No optimisations.
release: all symbols stripped. Maximum optimisations.
check: turns on all pedantic warnings and flags. Meant as a “sanity check” build option.
cov: short for “code coverage”, this is the default test suite build when using TES. Also reports on code coverage statistics.
asan: test suite build with instrumentation for “address sanitisation”, or A-San for short.
ubsan: test suite build with instrumentation for “undefined behaviour sanitisation”, or UB-San for short.
There are many features provided by the Slick Makefiles that provide for advanced use cases.
The Makefiles provide a novel dot-infix notation for specifying sources only intended to be compiled for a specific target platform. This is especially needed in projects that use assembly code, as it is a non-portable source code form, but it is provided for all types of sources as well as test batteries. Simply affix the desired
$TP identifier in all uppercase to the sources with a dot, like so:
SFILES.GBA := \ src/gbabios.s CFILES.DARWIN := \ src/macstuff.c \ src/swifty.c
The Slick Makefiles only run on Unix-like operating systems. Resultantly, the use of sysroots for dependency management is highly encouraged, even for all kinds of cross-compilation endeavours. However, it can often prove wiser to install dependencies in-tree, into a dedicated subdirectory of the project’s repository. The Slick Makefiles have a system to support this as well, using a couple of variables and some additional logic in the backend.
The two variables that need to be set for in-tree dependency management are
$3PLIBDIR is a directory path, relative to the repository root, in which subfolders for each dependency are kept. ADP 1 recommends that this be set to
3rdparty, as the spec makes a special allowance for that name, but it can be set to point anywhere, including absolute paths and symbolic links.
$3PLIBS is a space-separated list of names of each library that is an in-tree dependency, without the
project_namelibwhile their compiled binaries are placed into their own repository roots.
Slick supports targeting the Actually Portable Executable polyglot using its accompanying Cosmopolitan libc. To do this, five (5) file paths to the amalgamated sources of Cosmopolitan must be specified before the inclusion of
APE_LDSCR: path to
APE_AFILE: path to
APE_HFILE: path to
APE_CRTO: path to
APE_APEO: path to
Usually, it suffices to have these extracted into a subfolder of
etc/ in compliance with ADP 1. Many Aquefir projects use an automated
getcosmo.sh script to fetch and verify the amalgamated sources, along with
.gitignore usage to block out the downloads from the source tree. It is also possible to exclusively target APE by explicitly setting
TP := APE before including
Slick takes care of applying the appropriate flags where they’re needed, as well as fixing the ordering of the various options. It should “just work” if all five variables are provided and present.