aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 4ebb334a8c618c56a06b2c4f5346049d7be36c06 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# CBS — The C Build System

CBS is a single-header library for writing build scripts in C.  The
philosophy behind this project is that the only tool you should ever need
to build your C projects is a C compiler.  Not Make, not Cmake, not
Autoconf — just a C compiler.

Using C for your build system also has numerous advantages.  C is
portable to almost any platform, C is a turing-complete language that
makes performing very specific build steps easy, and anyone working on
your C project already knows C.

CBS does not aim to be the most powerful and ultimate high-level API.  It
simply aims to be a set of potentially useful functions and macros to
make writing a build script in C a bit easier.  If there is functionality
you are missing, then add it.  You’re a programmer aren’t you?

All functions and macros are documented in cbs.h.

CBS is very much inspired by Tsoding’s ‘Nob’.


## Features

— C99 and POSIX compliant
— Capturing of command output
— Easy command building and execution
— PkgConfig support
— Simple and easy to understand API
— Thread pool support


## Important

This library works with the assumption that your compiled build script
*and* your build script source code are located in the **same
directory**.  The general project structure of your project is intended
to look like so:

```
.
├── cbs.h   # This library
├── make    # The compiled build script
├── make.c  # The build script source
└── …       # Your own files
```


## Example

Assuming you have a source file `my-file.c` — you can compile this build
script (called `build.c` for example) with `cc build.c` to bootstrap —
and then run `./a.out` to build the `my-file` binary linked against the
liblux library.

If you make any modifications to the build script *or* to the cbs.h
header, there is no need to manually recompile — the script will rebuild
itself.

```c
#include <stdlib.h>

#define CBS_NO_THREADS
#include "cbs.h"

static char *cflags[] = {"-Wall", "-Wextra", "-Werror", "-O3"};

int
main(int argc, char **argv)
{
    /* Initialize the library, and rebuild this script if needed */
    cbsinit(argc, argv);
    rebuild();

    /* If the compiled binary isn’t outdated, do nothing */
    if (!foutdatedl("my-file", "my-file.c"))
        return EXIT_SUCCESS;

    /* Append ‘cc’ and our cflags to the command, but allow the user to use the
       $CC and $CFLAGS environment variables to override them */
    struct strs cmd = {0};
    strspushenvl(&cmd, "CC", "cc");
    strspushenv(&cmd, "CFLAGS", cflags, lengthof(cflags));

    /* Call pkg-config with the --libs and --cflags options for the library
      ‘liblux’, appending the result to our command.  If it fails then we
      fallback to using -llux */
    if (!pcquery(&cmd, "liblux", PC_LIBS | PC_CFLAGS))
        strspushl(&cmd, "-llux");

    /* Push the final arguments to our command */
    strspushl(&cmd, "-o", "my-file", "-c", "my-file.c");

    /* Print our command to stdout, and execute it */
    cmdput(cmd);
    return cmdexec(cmd);
}
```


## Example With Threads

This is like the previous example, but you should compile with -lpthread.

```c
#include <stdlib.h>

#include "cbs.h"

static char *cflags[] = {"-Wall", "-Wextra", "-Werror", "-O3"};
static char *sources[] = {"foo.c", "bar.c", "baz.c"};

static void build(void *);

int
main(int argc, char **argv)
{
	cbsinit(argc, argv);
	rebuild();

	if (!foutdated("my-file", sources, lengthof(sources)))
		return EXIT_SUCCESS;

    /* Get the number of CPUs available.  If this fails we fallback to 8. */
    int cpus = nproc();
	if (cpus == -1)
		cpus = 8;

    /* Create a thread pool, with one thread per CPU */
    tpool tp;
	tpinit(&tp, cpus);

    /* For each of our source files, add a task to the thread pool to build
       the file ‘sources[i]’ with the function ‘build’ */
	for (size_t i = 0; i < lengthof(sources); i++)
		tpenq(&tp, build, sources[i], NULL);

    /* Wait for all the tasks to complete and free the thread pool */
	tpwait(&tp);
	tpfree(&tp);

    struct strs cmd = {0};
    strspushenvl(&cmd, "CC", "cc");
    strspushl(&cmd, "-o", "my-file");

    for (size_t i = 0; i < lengthof(sources); i++)
        strspushl(&cmd, swpext(sources[i], "o"));

	cmdput(cmd);
    return cmdexec(cmd);
}

void
build(void *arg)
{
    /* This function will be called by the thread pool with ‘arg’ set to a
       filename such as ‘foo.c’ */

    struct strs cmd = {0};

    strspushenvl(&cmd, "CC", "cc");
    strspushenv(&cmd, "CFLAGS", cflags, lengthof(cflags));

    /* Allocate a copy of the string ‘arg’, with the file extension replaced.
       This will for example return ‘foo.o’ when given ‘foo.c’ */
    char *object = swpext(arg, "o");

    strspushl(&cmd, "-o", object, "-c", arg);

    cmdput(cmd);
    if (cmdexec(cmd) != EXIT_SUCCESS)
        exit(EXIT_FAILURE);
    free(object);
    strsfree(&cmd);
}
```


## Documentation

Coming soon!