mincbuild


Published: 2023-09-29

Latest revision: 2024-03-15

Author: tirimid

What is mincbuild?

Mincbuild, which stands for (Min)imalist (C) (Build)system, is a buildsystem designed to make project build easier for other minimalist C(++) software. Now, you may be worried that this is just like all the other buildsystems, and it has some shitty DSL that was designed by a group of crackheads rolling dice to decide on fundamental features, but no! The only piece of necessary configuration is a single conf file that describes the basics of how your project should be built.

Of course, with no buildsystem DSL, mincbuild is meaningfully more restrictive than other buildsystems. For example, you cannot build one set of files using one command, and another set of files using another command without setting up multiple configurations, but given that the target usage for mincbuild is other minimalist software which shouldn’t really be doing this anyway, that’s not really an issue.

As of writing this, mincbuild is only 1073 lines of C code, and the resulting binary from the build is 44KB in size.

If your software is so minimalist (or designed in such a way) that the entire source implementation fits in one file, you are better off using something like Make, because mincbuild (while it will also work for this use-case) is meant specifically for minimalist software implemented across multiple source files, allowing for incremental compilation.

Installation and setup

First, clone the repository from the GitHub using:

$ git clone https://github.com/tirimid/mincbuild
$ cd mincbuild

Then, build the program. On Linux, this is a very simple process - mincbuild is designed for it. I have also had success with building mincbuild on Windows through MinGW, but it can be a trivial bit more involved.

To build on Linux, run:

$ ./bootstrap.sh
$ ./mincbuild

To build on MinGW:

  1. Open bootstrap.sh in an editor (probably GNU nano)
  2. Remove -lpthread from LIBS (MinGW may not support pthread)
  3. Add -DPRUNE_SINGLE_THREAD -DCOMPILE_SINGLE_THREAD to CFLAGS
  4. Exit your text editor
  5. Run ./bootstrap.sh
  6. If you get errors about how “uint16 is undefined” or something, you can do an evil - evil - (but working) hack where you just go into the file causing the error and insert a typedef unsigned short uint16;. You can also add an #include <sys/types.h> higher up if needed, but it probably shouldn’t be.
  7. Run CFLAGS="-std=c99 -pedantic -D_POSIX_C_SOURCE=200809 -D_GNU_SOURCE -DPRUNE_SINGLE_THREAD -DCOMPILE_SINGLE_THREAD" ./mincbuild.exe

You will notice upon the mincbuild binary that it prints build output with an extremely simple progress bar. This is because I’ve personally always hated it when buildsystems or build processes in general don’t give you even a vague indication of how long is left until completion.

As another note - something worth knowing is that the whole “PRUNE_SINGLE_THREAD COMPILE_SINGLE_THREAD” thing you do on MinGW will obviously neuter multithreaded functionality. This sucks for general usage, but is actually quite nice if you have specific compatibility requirements (I suppose MinGW actually counts as one?).

Anyway - now, you can install mincbuild to your system. To do this, run:

# ./install.sh

And if you ever want to remove mincbuild from your system, run:

# ./uninstall.sh

Using mincbuild

First, to use mincbuild for a project, your project must follow a set, simple directory structure:

project/
    source-files/
        file.c
        file.c
        ...
    header-files/
        file.h
        file.h
        ...
    mincbuild.conf

Obviously, the names of the files / directories involved can be changed, they have just been named descriptively here for ease of understanding. The main thing here is that this directory structure is set in stone. Technically you can just use a single source directory for everything and mincbuild will allow it, but by design, mincbuild is meant for the structure outlined above. The rationale behind this is that it enforces a consistent project organization which is common and easy to understand. Anyway, that part is fairly obvious and requires no further elaboration - the interesting thing is the mincbuild.conf.

The mincbuild conf takes the form of key = value configuration pairs (notice the significant whitespace between the key, =, and value). Comments begin with a #, as with most basic conf files - and only lines which begin with a # (not including leading whitespace) will be treated as a comment. If a # comment appears at the end of an otherwise non-comment line, it will not be treated as a comment.

Simple compilation

To begin with, there are a few keys that are absolutely essential to the compilation phase of the build, and mincbuild will output an error if any of these are missing. See below:

A basic mincbuild.conf which contains all of these keys would look something like this:

# toolchain.
cc = /usr/bin/gcc
cflags = -std=c99 -pedantic

# project.
src_dir = src
inc_dir = include
lib_dir = lib
produce_output = false
src_exts = c
hdr_exts = h

# dependencies.
incs = NONE

# toolchain information.
cc_inc_fmt = -I%i
cc_cmd_fmt = %c %f -o %o -c %s %i
cc_success_rc = 0

… which will define a C project with a src source file directory, an include header file directory, and a lib object file output directory. Unlike src and include, lib does not need to exist before the build process - and will be automatically created if it is missing. When mincbuild is invoked here, all source files in src will be compiled to corresponding object files in lib, but the build will stop at that. No linking will occur and you will be left with just compiled object files - which is useful in the situation that you want to link them individually into other projects.

Oh, and, by the way, in case you haven’t noticed - the dummy name NONE is used whenever a key should have an empty value, rather than just leaving it blank. Another thing to keep in mind is that trailing significant whitespace applies to the values of the configuration file: i.e. a value “hello” is different to another value “hello “ (notice the trailing space) but it is not different to “ hello” (notice the leading space). This is important to note because if you meant to pass “NONE” as one of the values, but accidentally passed “NONE ” (with a trailing space), the behavior will not be the same in both cases.

Linking the project

However, for most projects it won’t be enough to simply compile all the sources - you want to link them all together. In order to do this, you will need to set produce_output to true and define the following keys, without which the mincbuild linking phase cannot occur and an error will be outputted:

A basic mincbuild.conf which implements compilation and linking would look something like:

# toolchain.
cc = /usr/bin/gcc
ld = /usr/bin/gcc
cflags = -std=c99 -pedantic
ldflags = NONE

# project.
src_dir = src
inc_dir = include
lib_dir = lib
produce_output = true
output = demo
src_exts = c
hdr_exts = h

# dependencies.
incs = NONE
libs = NONE

# toolchain information.
cc_inc_fmt = -I%i
cc_cmd_fmt = %c %f -o %o -c %s %i
ld_lib_fmt = -l%l
ld_obj_fmt = %o
ld_cmd_fmt = %c %f -o %b %o %l
cc_success_rc = 0
ld_success_rc = 0

… which will define a C project with all the same properties as before, except mincbuild will now also link the compiled object files into an output binary called demo.

Using the binary

Finally, that’s basically all you need to understand about the mincbuild conf file and I can move on to the mincbuild command line tool you built during the previous part of this guide. Using the mincbuild command line tool is extremely simple. Just navigate to the project directory which contains the mincbuild.conf, and run:

$ mincbuild

Nice. Very simple. However, I said earlier that “the names of the files / directories [in the project] can be changed”, and this includes the mincbuild conf. This is true, you can rename it to anything. If you rename mincbuild.conf to something else, say, buildinfo.conf or something, you will need to run the following command instead:

$ mincbuild buildinfo.conf

You may also pass some command line flags to mincbuild in the standard getopt()-compatible format in order to change certain parts of the build process in such a way that a successful build won’t differ in any way than a clean mincbuild invocation, but still provide some kind of useful functionality. The possible flags are:

As well as the basic -h flag that everyone already understands.

Temporary overrides

The fact that the compiler, linker, compiler flags, linker flags, etc. are hardcoded into the mincbuild conf is fine for most cases - especially if you’ve only tested your project on a single toolchain and cannot guarantee it building on anything else.

However, let’s say you have a program with a conditional compilation path which enables UTF-8 support for text handling, and to enable this conditional compilation path, you must compile with -DENABLE_UTF_8_SUPPORT. Well, it’s fine to make an edit to the conf file if you are an end user, but if you are a programmer and need to frequently test your program both with and without UTF-8, it certainly gets annoying to constantly make changes to a build file.

For this reason, mincbuild allows you to override specifically those variables. You can do this by setting the following environment variables:

So, in our above example, you might do something like:

$ CFLAGS="-DENABLE_UTF_8_SUPPORT" mincbuild

… to build with UTF-8 support.

If the environment variable is not set, mincbuild will simply use the value specified in the conf file.

As a hint, try running with -v to see that the command has actually changed after an override, and make sure it is correct. You should also remember that compiling different parts of the same project with different flags is often ill-advised.

In fact, to be completely safe, you might want to run this instead:

$ CFLAGS="-DENABLE_UTF_8_SUPPORT" mincbuild -vr

You now understand everything there is to know about the basics of using mincbuild.