A small library that allows you to write build recipes in C.
API might change often because right now its not good enough.
This library is licensed with LGPL-3.0-or-later. see LICENSE and
COPYING.LESSER for full text of the licenses.
TLDR: full example at the bottom. also see
build.c for example usage. also see examples/ directory for some
more examples. (those are more up to date then readme because they
actually get compiled to see if they still work)
First, you need to compile this library as a static lib. Clone the
repository and run make, this creates a libcbuild.a file in
project root. Either copy it to /usr/local/lib, and copy the include
dir to /usr/local/ (recursively), OR do whatever. its a normal
library. You can hardcode the absolute path, use env variables, add
this as a submodule in your git...
Then you need to use -Lpath/to/lib, -lcbuild and
-Ipath/to/include in your compile command (example command later)
To create an object file, you can use cbuild_obj_create(source, cflags..., NULL). it takes a path to c file and const char *
varargs to be used as cflags. varargs in all of the library functions
need to end in NULL.
When a function in this lib fails, it prints the error and exit()s. This allows you to write shorter and prettier code, at the price of... no price?
#include <cbuild/cbuild.h>
cbuild_obj_t *foo_obj = cbuild_obj_create("foo.c", "-std=gnu23", "-Wall", "-O2", NULL);If after the fact you want to add more cflags, you can use
cbuild_obj_append_cflags. notice how all obj related functions begin
with cbuild_obj..., for easy autocompletion. All
cbuild_..._append_... functions take an array of items.
const char *wextra = "-Wextra";
cbuild_obj_append_cflags(foo_obj, &wextra, 1);
char *someflags[] = {"-std=c99", "-pedantic"};
cbuild_obj_append_cflags(foo_obj, someflags, 2);You can also create an executable using cbuild_target_create. it
takes the type of the target, output filename and a vararg of
cbuild_target_t *'s.
There's multiple target types, but they all share the same base:
cbuild_target_t. It should be always safe to cast the specific types
to cbuild_target_t (you need to cast it sometimes).
Type of the target is one of enum cbuild_target_type, there's also 3
macros to create each type: cbuild_create_executable,
cbuild_create_sharedlib,cbuild_create_staticlib
cbuild_executable_t *foo = cbuild_create_executable("foo", foo_obj, NULL);Since you can't mix types for varargs (or you can, inconveniently),
you can only append ldflags via
cbuild_executable_append_ldflags. (its an alias for
cbuild_link_target_append_ldflags, same alias exists for other 2
types of link target)
const char *ldlibs[] = {"-lm"};
cbuild_executable_append_ldflags(foo, ldlibs, sizeof(ldlibs) / sizeof(*ldlibs));Now, to compile, use cbuild_target_compile. its going to compare the
timestamps of all the required files (if foo.c > foo.o, compile foo.o,
then if foo.o > foo, compile foo). All commands are printed to stdout.
It takes compile flags, described in header cbuild/compile.h. 0
disables them all.
It returns child process' pid, which you should wait for. (otherwise
the parent exists too early and kills its children). It may also
return 0 if the target is up to date.
// void* cast is simply shorter than cbuild_target_t*
pid_t cpid = cbuild_target_compile((void *)foo, 0);
if (cpid > 0)
waitpid(cpid, NULL, 0);Now compile your build driver. You can, for example, add this git repo
as a submodule, inside of lib/cbuild, run make install in it, and
link against it:
gcc build.c -Llib/cbuild -lcbuild -Ilib/cbuild/include -o buildNow, if you run build, it should work.
It can also recompile itself, when its source file is newer than
executable. To do so, you need to call cbuild_recompile_myself on
the top of your main(...). It also takes cflags and ldflags combined
as varargs, and compile flags. It needs, at minimum, flags to link against cbuild
library (just like in the command above). You can pass any other
flags, they should work.
cbuild_recompile_myself(__FILE__, argv, 0, "-Llib/cbuild", "-lcbuild", "-Ilib/cbuild/include", NULL);if it decides to recompile itself, its going to print the commands
like usual, and its going to exec the new executable (that command
is also printed). If you want to force rebuild your compile driver,
you can update mtime of build.c and run ./build
All compiler's commands, stdout and stderr are printed like usual, emacs' parsing doesn't break.
You can use the bootstrap.sh script in the root of the project to,
well, bootstrap your build program. Instead of long and painful gcc
command the script compiles your build program for you, and generates
a header with a macro CBUILD_SELFCOMPILE_FLAGS, which you should
pass to cbuild_recompile_myself like this:
cbuild_recompile_myself(__FILE__, argv, 0, "-Wall", CBUILD_SELFCOMPILE_FLAGS, NULL);Final program example:
#include "build.h"
#include <cbuild/cbuild.h>
#include <wait.h>
int main(int argc, char **argv) {
cbuild_recompile_myself(__FILE__,
argv,
0,
CBUILD_SELFCOMPILE_FLAGS,
NULL);
cbuild_obj_t *foo_obj = cbuild_obj_create("foo.c", "-std=gnu23", "-Wall", "-Wextra", NULL);
cbuild_executable_t *foo = cbuild_executable_create("foo", foo_obj, NULL);
pid_t cpid = cbuild_target_compile((void *)foo, 0);
if (cpid > 0)
waitpid(cpid, NULL, 0);
}