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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
|
# 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 this file.
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 (fmdnewer("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", "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
### Macros
```c
#define CBS_NO_THREADS
```
If this macro is defined before including `cbs.h`, then support for
thread pools won’t be included meaning you don’t need to link with
`-lpthread` when bootstrapping the build script.
---
```c
#define lengthof(xs) /* … */
```
Return the number of elements in the static array `xs`.
### Startup Functions
These two functions should be called at the very beginning of your
`main()` function in the order in which they are documented here for
everything to work properly.
---
```c
void cbsinit(int argc, char **argv)
```
Should be the first function called in `main()` and passed the same
parameters received from `main()`. It initializes some internal data,
but it also changes the current working directory so that the running
process is in the same directory as the location of the process. For
example if your build script is called `make` and you call it as
`./build/make`, this function will change your working directory to
`./build`.
---
```c
#define rebuild() /* … */
```
Should be called right after `cbsinit()`. This function-like macro
checks to see if the build script is outdated compared to its source
file. If it finds that the build script is outdated it rebuilds it
before executing the new build script.
### String Array Types and Functions
The following types and functions all work on dynamically-allocated
arrays of string, which make gradually composing a complete command that
can be executed very simple.
---
```c
struct strs {
char **buf;
size_t len, cap;
};
```
A type representing a dynamic array of strings. The `len` and `cap`
fields hold the length and capacity of the string array respectively, and
the `buf` field is the actual array itself. Despite being a sized array,
`buf` is also guaranteed by all the functions that act on this structure
to always be null-terminated.
There is no initialization function for the `strs` structure. To
initialize the structure simply zero-initialize it:
```c
int
main(int argc, char **argv)
{
/* … */
struct strs cmd = {0};
strspush(&cmd, "cc");
/* … */
}
```
---
```c
void strsfree(struct strs *xs)
```
Deallocates all memory associated with the string array `xs`. Note that
this does **not** deallocate memory associated with the individual
elements in the string array — that must still be done manually.
This function also zeros `xs` after freeing memory, so that the same
structure can be safely reused afterwards.
---
```c
void strszero(struct strs *xs)
```
Zeros the string array `xs` **without** deallocating any memory used by
the string array. This allows you to reuse the same structure for a
different purpose without needing to reallocate a fresh new array,
instead reusing the old one.
---
```c
void strspush(struct strs *xs, char **ys, size_t n)
```
Append `n` strings from the string array `ys` to the end of `xs`.
---
```c
#define strspushl(xs, ...) /* … */
```
Append the strings specified by the provided variable-arguments to the
end of `xs`.
---
```c
void strspushenv(struct strs *xs, const char *ev, char **ys, size_t n)
```
Append the value of the environment variable `ev` to the end of `xs`. If
the provided environment variable doesn’t exist or has the value of the
empty string, then fallback to appending `n` strings from `ys` to the end
of `xs`.
---
```c
#define strspushenvl(xs, ev, ...) /* … */
```
Append the value of the environment variable `ev` to the end of `xs`. If
the provided environment variable doesn’t exist or has the value of the
empty string, then fallback to appending the strings specified by the
provided variable-arguments to the end of `xs`.
### File Information Functions
The following functions are useful for performing common checks on files.
---
```c
bool fexists(const char *s);
```
Returns `true` if the file `s` exists, and `false` otherwise. If you
want to check if a certain binary is present on the host system, you
should use `binexists()` instead.
---
```c
int fmdcmp(const char *x, const char *y);
```
Returns a value greater than 0 if the file `x` was modified more recently
than the file `y`, a value lower than 0 if the file `y` was modified more
recently than the file `x`, and 0 if the two files were modified at the
exact same time.
---
```c
bool fmdnewer(const char *x, const char *y);
bool fmdolder(const char *x, const char *y);
```
The `fmdnewer()` and `fmdolder()` functions return `true` if the file `x`
was modified more- or less recently than the file `y` respectively, and
`false` otherwise.
---
```c
bool foutdated(const char *x, char **xs, size_t n);
```
Returns `true` if any of the `n` files in the array `xs` were modified
more recently than the file `x`.
---
```c
#define foutdatedl(x, ...) /* … */
```
Returns `true` if any of the files specified by the variable-arguments
were modified more recently than the file `x`.
### Command Execution Functions
The following functions are used to execute commands. It is a common
task that you may want to interface with `pkg-config` or change the
extension of a file while building up a command. Functions to perform
these tasks are not listed in this section, but instead listed later on
in this document.
---
```c
int cmdexec(struct strs cmd);
```
Execute the command composed by the command-line arguments specified in
`cmd`, wait for the command to complete execution, and return its exit
status.
---
```c
int cmdexec_read(struct strs cmd, char **buf, size_t *bufsz);
```
Execute the command composed by the command-line arguments specified in
`cmd`, wait for the command to complete execution, and return its exit
status. Additionally, the standard output of the command is captured and
stored in the buffer pointed to by `buf`, with the length of the buffer
being stored in `bufsz`.
Note that `buf` will not be null-terminated, and must be freed by a call
to `free()` after use.
---
```c
pid_t cmdexec_async(struct strs cmd);
```
Execute the command composed by the command-line arguments specified in
`cmd` asynchronously and return its process ID.
---
```c
int cmdwait(pid_t pid);
```
Wait for the process specified by `pid` to terminate and return its exit
status.
---
```c
void cmdput(struct strs cmd);
void fcmdput(FILE *stream, struct strs cmd);
```
Print a representation of the command composed by the command-line
arguments specified in `cmd` to the standard output, with shell
metacharacters properly quoted.
This function is useful for implementing `make(1)`-like command-echoing
behaviour.
The `fcmdput()` function is identical to `cmdput()` except the output is
written to `stream` as opposed to `stdout`.
### Thread Pool Types and Functions
The following types and functions are used for implementing thread pools.
This will be very helpful for speeding up build times. If you intend to
use thread pools you may want to see the documentation below for the
`nproc()` function.
---
```c
typedef void tjob(void *arg);
```
A type representing a function that takes a void pointer argument and
performs some action.
---
```c
typedef void tjob_free(void *arg);
```
A type representing a function that takes a void pointer argument a and
frees it and its associated memory.
---
```c
typedef /* ... */ tpool;
```
An opaque structure representing a thread pool. A variable of this type
needs to be passed to all the thread pool functions.
---
```c
void tpinit(tpool *tp, size_t cnt);
```
Initialize the thread pool `tp` with `cnt` threads. To use the number of
threads available on the system you should query the `nproc()` function.
---
```c
void tpfree(tpool *tp);
```
Free the resources used by the thread pool `tp`, and join all remaining
threads.
---
```c
void tpwait(tpool *tp);
```
Block until all tasks in the thread pool `tp` have finished execution.
---
```c
void tpenq(tpool *tp, tjob *job, void *arg, tjob_free *free);
```
Enqueue a new job `job` to the thread pool `tp` for execution. `job`
will be called with the argument `arg`. If `free` is non-NULL, it will
be called with the argument `arg` after the job was completed.
### Miscellaneous Functions
The following functions are all useful, but don’t quite fall into any of
the specific function categories and namespaces documented above.
---
```c
bool binexists(const char *s);
```
Return `true` if a binary of the name `s` is located anywhere in the
users `$PATH`, and `false` otherwise.
---
```c
int nproc(void);
```
Return the number of available CPUs, or `-1` on error.
---
```c
char *swpext(const char *file, const char *ext);
```
Return a copy of the string `file` with the file extension set to the
string `ext`. The file extension is defined to be the contents following
the last occurance of a period in `file`.
The returned string is allocated via `malloc()` and should be freed by a
call to `free()` after use.
---
```c
enum pkg_config_flags {
PC_CFLAGS = /* --cflags */,
PC_LIBS = /* --libs */,
PC_SHARED = /* --shared */,
PC_STATIC = /* --static */,
};
bool pcquery(struct strs *cmd, const char *lib, int flags);
```
Query `pkg-config` for the library `lib` and append the output to the
command specified by `cmd`, returning `true` if successful and `false` if
`pkg-config` exited with a failing exit code.
`flags` is a bitwise-ORd set of values in the `pkg_config_flags`
enumeration which control the flags passed to `pkg-config`. The above
synopsis documents which enumeration values map to which command-line
flag.
It may be useful to append a default value to `cmd` if `pkg-config` fails
for whatever reason. As an example you may do the following when linking
to liburiparser:
```c
struct strs cmd = {0};
strspushl(&cmd, "cc");
if (!pcquery(&cmd, "uriparser", PC_CFLAGS | PC_LIBS))
strspushl(&cmd, "-luriparser"); /* fallback */
strspushl(&cmd, "-o", "main", "main.c");
```
|