SBS is a light weight build system for C/C++ projects using gnu-make and either gnu compilers (gcc/g++) or LLVM compilers (clang/clang++). It is intended for users who want an easy to install and configure build system that doesn't require the user to write complex makefiles, has minimal dependencies and doesn't add any steps to the build process. SBS strives for simplicity and ease of use, making most of the build settings a matter of simple configuration, sparing the coder the need to write Makefile rules and for the most part, even understand how Makefiles work.
Partial list of SBS features:
- Simple compilation and linking settings
- Build flavors (Debug/Release)
- Automatic setup of source files dependencies
- Multi folders project support, with full control over build order
- Source and build output separation.
- Target systems
- Dependencies
- Installation
- Quick start example
- SBS basics
- Build configuration
- Sub-modules
- Invocation and Makefiles naming
- SBS debugging
- Misc
This system was built with and for Linux. It is the only platform SBS was verified for. Not officially supported, it can probably be adapted to any system with gmake and gcc-like compiler.
Naturally, SBS assume Gnu-make and either GCC or Clang suite are installed. It relies on bash
, but otherwise, no additional packages or tools are required.
Using SBS requires copying a single file - module.inc.mk
. From that point it's a matter of configuring the build by writing simple Makefiles.
In a folder named project
, create a file named main.c
. Here's a small example for you to paste:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("Simple-Build-System hello world!\n");
return EXIT_SUCCESS;
}
Place the module.inc.mk
file in the project
folder.
Now it's time to tell SBS what to build and how. In order to so, add a Makefile
to the same directory. No worries, it's going to be much smaller and much simpler than your usual Makefile. No rules are required, just very basic settings so SBS will know what to do. Only 3 lines are needed:
- The name of the binary we want to build
- List of source files to compile and link, in our example, a single source file -
main.c
- Inclusion of the
module.inc.mk
file placed in our directory in the installation phase
Here's how such a Makefile will look:
MODULE_NAME := my_prog
MODULE_SRCS := main.c
include module.inc.mk
And that's it. We're done with setting up the build, ready to build the project and run the created executable (we could have gotten away without the 1st line, but it's probably clearer this way).
First, lets build the project by running make. It there are no compilation errors, the output should look something like this:
project $ make
Building 'my_prog'
CC main.c
LD my_prog
project $
Now it's a good time to examine what happened in the project directory. Listing its content will reveal some changes:
project $ tree
.
├── main.c
├── Makefile
├── module.inc.mk
└── obj
└── dbg
├── main.c.d
├── main.c.o
└── my_prog
2 directories, 6 files
A new directory named obj
was created in the folder. All binary outputs are placed in that folder, including my_prog
, the build executable. The additional dbg
folder indicates a debug flavor was used. This is the default SBS behavior, and can be configured. The *.d
files in the build output are dependency files, to force source files compilations in case a header dependency has changed. Don't worry if you are unfamiliar with dependency files.
To run your program, invoke obj/dbg/my_prog
from the project directory:
project $ obj/dbg/my_prog
Simple-Build-System hello world!
project $
And that concludes the very basic usage of the build system. Browse the SBS repository test folder for more advanced examples.
In SBS terms, a module is a group of source files built by the same Makefile and (possibly) linked to a single binary referred to as an artifact. At its most simple and common form, a module contains source files residing in a single folder with a SBS Makefile. More complicated setups can place source files in multiple folders. Each module can have sub-modules to be build before, during and after the module build, as well as pre/post build steps.
A build produces object files, dependency files and binaries. Usually, the user is mainly concerned about the latter. SBS refers to binary outputs as artifacts
.
For a basic to intermediate use of SBS, no Makefile rules are needed and almost all SBS settings are defined using a gnu-make <SETTING> := <VALUE>
syntax, so it is better to think of SBS Makefiles as configuration files rather than traditional Makefiles.
There are quite a few settings, allowing the user to control many aspects of the build. Guidelines and most common configuration settings are covered below. For a full list of possible settings and their documentation, see the Makefile.skel file.
Following the module's setting, the Makefile must source (using include
) the module.inc.mk
file. It is important the inclusion directive is after the modules' settings.
A minimal SBS Makefile will look like this:
include module.inc.mk
Though such a Makefile will do absolutely nothing. A somewhat more advanced SBS Makefile might look like this:
include ../config.mk
MODULE_NAME := mylibrary
MODULE_BIN_TYPE := shared
MODULE_SRCS := file1.c file2.c file3.c file4.c
MODULE_CDEFS += MY_DEF=10
MODULE_POST_SUB_MODULES := mylibrary_tester
MODULE_VERBOSE := 1
MODULE_ARTIFACT_DIR := /path/to/another/folder
include ../module.inc.mk
Most of these settings are optional and used for specific needs. In the Makefile above example, the line MODULE_CDEFS += MY_DEF=10
adds a pre-processor definition of MY_DEF=10
to the build through a -DMY_DEF=10
flag. The MODULE_POST_SUB_MODULES := dynlib_tester
line defines that once this Makefile (module) is built, a make -C dynlib_tester
is invoked.
Internally, SBS defines variables and make targets are prefixed with sbs
and SBS
. When writing SBS makefiles, these prefixes should be avoided, as they might get overridden.
SBS defines many variables upon the inclusion of module.inc.mk
, all can be used after the include directive. For example, SBS_OBJS
lists all the module's object files. There are many such variables, which might be useful in some cases. To see all the internal variables, use make sbs_dump_internals
over a module. See the debugging section for more details.
NOTE
Some configuration settings details might look intimidating for the less experienced users. Yet for most of the configuration tasks, just getting them to work should be easy enough, even without understanding exactly what happens under the hood, so don't fret.
The module's name serves as the base name for the module linked output name. It is configured with the MODULE_NAME
setting. The module's name is used for output. With no name set, SBS uses the directory base-name as the module name. So a nameless module that reside in /dir0/dir1/dir2
will have the name dir2
.
A module type defines the artifact type the build produces. SBS module supports 4 kind of types: executables, dynamic libraries, static libraries and none. The build type is controlled with MODULE_BIN_TYPE
.
For an exec
MODULE_BIN_TYPE
value, SBS builds an executable. This is the default value if MODULE_BIN_TYPE
is not set. exec
binary type doesn't modify any build flags.
For a shared
MODULE_BIN_TYPE
value, SBS builds a shared library, where:
- Artifact file name is set to
lib<MODULE_NAME>.so
- Compilation flags is appended with
-fPIC
- Link flag is appended with
-shared
Refer to the selected build suite documentation for further details.
For a static
MODULE_BIN_TYPE
value, SBS builds a static library, where:
- Artifact file name is set to
lib<MODULE_NAME>.a
- Link command is
ar
based -ar -rsc
.
Refer to ar
documentation for further details.
For a none
MODULE_BIN_TYPE
value, SBS doesn't build any artifact. It compiles all the source files in the MODULE_SRCS
list, but does not link them. This mopdule type setting doesn't affect any build flags.
A module's source files are defined with MODULE_SRCS
. It's a space separated list of files to be compiled. All source files must have a valid C or C++ suffix (e.g. c
, C
, cpp
, cxx
, etc.). See the source file suffixes section for more details about source files suffixes.
Source files can reside anywhere. Their corresponding object files are always created inside the module's own obj
directory, so a source file can be compiled multiple times by different modules, possibly with different compilation flags, and different objects will be created on each module.
Due to gnu-Make limitations, SBS doesn't support source file with names or paths that contain white spaces.
Flavors are used to distinguish between different builds and their corresponding output and artifacts. Flavors are defined with MODULE_FLAV
. With the exception of SBS internal flavors, flavors only determine the module output directory. For a MODULE_FLAV := myflav
entry, SBS will use obj/myflav
, relative to the module directory, as the output directory. It is up to the user to put meaning into the flavor using different build options, and distinguishing it from other flavors.
All SBS modules must have a flavor. If no flavor is set, SBS defaults to the dbg
flavor (see below).
NOTE
The idea of flavors, or 'build configurations', as they are called in other build systems, isn't unique to SBS and it is beyond the scope of this guide to explain how to manage them. Common approaches can be based on different project config files for each flavor controlled by renames or soft links, or a common config file branching internally according to an environment variable. To get things started, SBS default internal flavors should be sufficient for most cases.
SBS provides two internal flavors for debug and release. If the user sets the flavor type to either dbg
or rel
SBS will slightly modify the compilation flags with popular relevant settings:
- Debug (
dbg
) --g -O0 -DDEBUG=1 -D__DEBUG__=1
- Release (
rel
) --O3
If you are unfamiliar with these compile options and their meaning, refer the compiler's documentation.
To prevent SBS from adding any flags to the build options (e.g. If the -O3
is too aggressive), set MODULE_USE_DEF_FLAV
to 0
.
A module build outputs up to 3 types of files
- Object files
- The target artifact (the executable or library)
- Dependency files (See Header dependencies)
By default, all output files reside in <MODULE_PATH>/obj/<FLAVOR>
, where flavor is determined by MODULE_FLAV
. The artifact location can be set to any location by setting MODULE_ARTIFACT_DIR
. MODULE_ARTIFACT_DIR
is treated 'as is', and is relative to the current working directory. If it is an absolute path, the absolute path is the artifact directory.
However, if MODULE_ARTIFACT_DIR_REL
is set to 1
, SBS will treat artifact directory set by MODULE_ARTIFACT_DIR
, as a path relative to the project root directory (i.e. Where module.inc.mk
resides in). This can be useful for settting a multi module project where binaries are to be placed in single known location, like a project level lib
directory where other binaries can link with.
SBS supports GCC and Clang as compilers suites. By default, SBS uses GCC (gcc
) as its compiler suite. To change this behavior, set the MODULE_CSUITE
setting value to clang
.
SBS compiles a given source file as a C or C++ file according to its file extension:
- Default C suffixes are
c
andC
. Modifiable with theMODULE_C_SUFFIXES
setting. - Default C++ suffixes are
cpp
,cxx
,cc
,CC
,CXX
andCPP
. Modifiable with theMODULE_CXX_SUFFIXES
setting.
Setting either MODULE_C_SUFFIXES
or MODULE_CXX_SUFFIXES
overrides the default C or C++ source suffixes, so the user should make sure that changed, all the module's possible C or C++ source files suffixes are included.
By default, linkage tool is deduced by SBS according to the list of source files: If a C++ file is found among the list of source files, SBS will default to C++ linker, otherwise the C linker is used. Most of the time, allowing SBS to choose the linker will generate the desired behavior, but SBS allows the user to explicitly choose a linker with the MODULE_LD
setting.
SBS provides a MODULE_CFLAGS
setting to add any compilation flags. All compilation flags can be controlled with this setting alone. The content of MODULE_CFLAGS
is appended to any of the module's object compilation command.
SBS also provides dedicated settings for the more common compilation configuration options - header search directories, pre-processor definition, compiler warnings and a few other. These are usually easier to write and maintain than putting all the compilation settings inside MODULE_CFLAGS
and should generally be preferred over the general MODULE_CFLAGS
, but it is up to the user to decide how to configure the build. See below for possible dedicated compilation settings.
Search path for headers can be appended to the module's compilation commands by setting MODULE_INCLUDE_DIRS
. It's space separated lists of directories, where each entry <DIR>
yields a -I<DIR>
flag in the compilation command.
The following example yields a -Isome_dir/include -Iother_dir/include
in the module's objects compile command:
MODULE_INCLUDE_DIRS := some_dir/include other_dir/include
Preprocessor definitions can be added to the module's compilation with the MODULE_CDEFS
setting. It's space separated lists of definitions, where each entry <DEF>
in the setting yields a -D<DEF>
flag in the compilation command.
The following example yields a -D_GNU_SOURCE -DUSE_LOG=0
in the module's objects compile command:
MODULE_CDEFS := _GNU_SOURCE USE_LOG=0
Compiler warnings are controlled with the MODULE_CWARNS
setting. It's space separated lists of warnings, where each entry <WARN>
in the setting yields a -W<WARN>
flag in the compilation command. So a setting of
The following setting yields a -Wno-import -Wformat
in the module's object compile command:
MODULE_CWARNS := no-import format
GCC and clang provide a specific pthread support using a -pthread
flag on both compile and link commands. SBS provides a convenience wrapper around this setting with the MODULE_USE_PTHREAD
setting. Setting it to 1, will add the -pthread
to the compile and link commands.
By default, SBS adds compilation flags for header files dependencies generation (.d
, files) for better build behavior on headers modification. Unchanged, compilation flags are appended with -MMD -MP
. This behavior can be turned off by setting MODULE_DEP_FLAGS
value to 0
.
For more details about headers dependencies generation, consult the compiler's documentation.
SBS provides a MODULE_CFLAGS_OVERRIDE
setting to completely override all settings of the compilation, either defined by the user or appended by SBS itself. If you find yourself using this setting extensively, you are probably either misusing SBS or should look for a better suited build system.
As with compilation, SBS provides a MODULE_LDFLAGS
setting to add any flag to the linker. The content of MODULE_LDFLAGS
is appended to any of the module's link command.
SBS also provides a couple of link flags settings for the more common link configuration options - libraries to link against and library search directories. These are usually easier to write and maintain than putting all the link flags inside MODULE_LDFLAGS
, and should generally be preferred over the general MODULE_LDFLAGS
, but it is up to the user to decide how to configure the build. See below for possible dedicated link settings.
Libraries to link against can be set with MODULE_LIBS
. It's space separated lists of libraries, where each entry <LIB>
yields a -l<LIB>
flag in the link command.
The following setting yields a -lm -luuid
in the module's artifact link command:
MODULE_LIBS := m uuid
Libraries search path can be set with MODULE_LIB_DIRS
. It's space separated lists of directories, where each entry <DIR>
yields a -L<DIR>
flag in the link command.
The following setting yields a -L/opt/external_project0 -L/opt/external_project1
in the module's artifact link command:
MODULE_LIB_DIRS := /opt/external_project0 /opt/external_project1
A MODULE_LDFLAGS_OVERRIDE
setting can be used to completely override all compilation flags, either defined by the user or those appended by SBS itself. If you find yourself using this setting extensively, you are probably either misusing SBS or should look for a better suited build system.
Each module may define sub-modules to build. A sub module is a directory with a valid Makefile in it. It doesn't have to be a SBS module by itself. A sub module can be built before, during or after the parent module. Sub-modules allow complex project folders and build hierarchies. SBS modules might serve as pseudo modules, with no sources to compile but with sub modules. Under the hood, sub modules are built by invoking make -C
. SBS provides settings to define which and when sub modules are built.
MODULE_SUB_MODULES
- List of folders to be build along the current module.
MODULE_PRE_SUB_MODULES
- List of folders to be built before the current module.
MODULE_POST_SUB_MODULES
- List of folders to be after before the current module.
In case a none-parallel build is to executed, the build order is straight forward. Sub-modules defined by MODULE_PRE_SUB_MODULES
are built first in the order they are set, followed by the current module, followed by the sub-modules defined in the MODULE_SUB_MODULES
and finally, sub-modules defined in MODULE_POST_SUB_MODULES
.
When executing a parallel build (i.e. With make -j
), the major difference is with the parent module and its sub modules defined in MODULE_SUB_MODULES
, as they are all built in parallel. Pre and post sub-modules are still built one by one according to the order they are defined (Each sub-module is still built in parallel internally).
To control the order of sub-modules within MODULE_SUB_MODULES
, dependencies rules between the sub modules should be explicitly set. For example, if sub modules sm0 sm1 sm2
are defined but sm1
must be built before sm0
, a dependency rule is due:
MODULE_SUB_MODULES := sm0 sm1 sm2
sm0: sm1
Given the following example:
MODULE_SRCS := main.cpp
MODULE_NAME := my_prog
MODULE_PRE_SUB_MODULES := pre_order
MODULE_SUB_MODULES := unordered0 unordered1
MODULE_POST_SUB_MODULES := post_order
include module.inc.mk
- Running make without parallel build, will build modules in this order:
pre_order
my_prog
unordered0
unordered0
post_order
Running make over this Makefile with parallel build will build build the current and regular sub-modules in parallel:
pre_order
my_prog
(current module),unordered0
andunordered1
are built in parallelpost_order
As a general rule, SBS Makefiles should be simply named Makefile
, where one Makefile exists in every module directory. Like other gnu-based systems, to run SBS the user should do either of the following:
- Enter the module's directory and run
make
- Run
make -C <MODULE_DIR>
.
Occasionally, one might want to have explicit Makefile names in a given directory, where build are invoked using make -f <FILENAME>
. SBS offers some support for this type of build command. A SBS Makefile can be named whatever and executed with a make -f
command, but there are a few notes to consider:
- Sub-modules are always built using a default Makefile.
- While true for Makefile in general, it's still worth mentioning that more than one Makefile in the same directory (i.e.
Makefile.A
andMakefile.B
) are hard to maintain and should be carefully written so they won't overwrite one another output. It is usually best avoided.
In the rare and unheard of case of a buggy build, SBS provides a couple of debugging mechanics:
SBS auto defines a special sbs_dump_internals
target that shows SBS internal variables generated after the module.inc.mk
inclusion. This can be used to get some sight into the build process internals.
All the variables shown by the sbs_dump_internals
are available to use (if needed) after the inclusion of the module.inc.mk
file.
SBS also auto defines few targets to summarize the input received from the user (i.e. Defined by the user using MODULE_<SETTIMG>
):
sbs_dump_module_vars
- Summary of the module's basic variablessbs_dump_module_submodules
- Summary of the module's sub-modules variablessbs_dump_module_build_steps
- Summary of the module's build steps (i.e. Pre and post build) variables.
These targets variants are helpful when working projects where modules makefiles are generated or include other makefiles (as often happens when managing flavors). In the simple case of a self contained module, the target will just echo the module's own variables.
By default, SBS only dumps none-empty variables. To show all variables regardless of there content, set SBS_FORCE_DBG
to 1
on invoke. For example on sbs_dump_module_vars
: make SBS_FORCE_DBG=1 sbs_dump_module_vars
.
A target named sbs_dump_module
combines dump of all module's dump target
A target named sbs_dump_all
combines dump of all module's dump target and the SBS internal target.
Setting MODULE_VERBOSE
to 1
will generate the full build and link commands instead of the easier on the eyes CC <FILE>
and LD <FILE>
messages. After setting MODULE_VERBOSE
to 1
in the quick start example, the output might look like this:
project $ make clean; make
Cleaning 'my_prog'
rm -f /home/user/project/obj/dbg/main.c.o /home/user/project/obj/dbg/main.c.d /home/user/project/obj/dbg/my_prog
Building 'my_prog'
gcc -MMD -MP -g -O0 -DDEBUG=1 -D__DEBUG__=1 -c /home/user/project/main.c -o /home/user/project/obj/dbg/main.c.o
gcc -o /home/user/project/obj/dbg/my_prog /home/user/project/obj/dbg/main.c.o
SBS defines MODULE_PRE_BUILD
and MODULE_POST_BUILD
for actions to be taken before and after a module's build. These settings should refer to valid Make targets. Thus, it requires writing Makefile rules.
The following example adds a pre-build target that displays a message to the screen at the beginning of the build:
MODULE_PRE_BUILD := display_message
display_message:
@echo "This is displayed at the beginning of the module's build"
SBS can't determine the location of the module.inc.mk
relatively to the current module location. It is for the user to set, and the module.inc.mk
inclusion path from each SBS Makefile of can be different.
It's possible to mitigate this problem with external tools. For example, with git, it is a good idea to place the module.inc.mk
next to the .git
directory, and use git to determine this location, so the following snippet can be used throughout all the modules within a given project:
PROJ_ROOT=$(shell git rev-parse --show-toplevel)
include $(PROJ_ROOT)/module.inc.mk
Other tools and environments might offer other possible solutions.