aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: e6a9c0e886797b99d97a5d6e333c5393c1aa7a4a (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
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
# 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

— Simple and easy to understand API
— Easy command building and execution
— Capturing of command output
— PkgConfig support
— Thread pool support
— Preprocessor-friendly variadic macros*

*See the examples below


## 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>

#include "cbs.h"

#define CC     "cc"
#define CFLAGS "-Wall", "-Wextra", "-Werror", "-O3"
#define TARGET "my-file"

int
main(int argc, char **argv)
{
	int ec;
	cmd_t cmd = {0};
	struct strv v = {0};

	cbsinit(argc, argv);
	rebuild();

	if (!foutdated(TARGET, TARGET ".c"))
		return EXIT_SUCCESS;

	cmdadd(&cmd, CC);
	if (pcquery(&v, "liblux", PKGC_LIBS | PKGC_CFLAGS))
		cmdaddv(&cmd, v.buf, v.len);
	else
		cmdadd(&cmd, "-llux");
	cmdadd(&cmd, CFLAGS, "-o", TARGET, TARGET ".c");

	cmdput(cmd);
	if ((ec = cmdexec(cmd)) != EXIT_SUCCESS)
		diex("%s failed with exit-code %d", *cmd._argv, ec);

	return EXIT_SUCCESS;
}
```


## Example With Environment Variables

In many cases a user may want to use environment variables to alter the
build process.  They may want to specify their own compiler via `CC`,
their own compiler flags via `CFLAGS`, or the installation directory via
`DESTDIR` and `PREFIX`.  These are made easy to configure via
`env_or_default()`.

```c
#include "cbs.h"

#define CC     "cc"
#define CFLAGS "-Wall", "-Wextra", "-O3"

int
main(int argc, char **argv)
{
    cmd_t c = {0};
    struct strv sv = {0};

    cbsinit(argc, argv);
    rebuild();

    /* Append the expanded values of ‘CC’ and ‘CFLAGS’ to ‘sv’ if they’re set,
       using the defaults specified by the macros otherwise. */
    env_or_default(&sv, "CC", CC);
    env_or_default(&sv, "CFLAGS", CFLAGS);

    cmdaddv(&c, sv.buf, sv.len);
    cmdput(c);
    (void)cmdexec(c);

    strvfree(&sv);
}
```


## Example With Threads

This is like the previous example, but you should compile with -lpthread.
This is not the most efficient way to build a multi-file project, but
this is simply for demonstration purposes.

```c
#include <errno.h>
#include <string.h>

#define CBS_PTHREAD
#include "cbs.h"

#define CC     "cc"
#define CFLAGS "-Wall", "-Wextra", "-Werror", "-O3"
#define TARGET "my-file"

static const char *sources[] = {"foo.c", "bar.c", "baz.c"};
static const char *objects[] = {"foo.o", "bar.o", "baz.o"};

static void build(void *);

int
main(int argc, char **argv)
{
	int ec, cpus;
	cmd_t cmd = {0};
	tpool_t tp;

	cbsinit(argc, argv);
	rebuild();

	if (!foutdatedv(TARGET, sources, lengthof(sources)))
		return EXIT_SUCCESS;

	if ((cpus = nproc()) == -1) {
		if (errno)
			die("nproc");
		/* System not properly supported; default to 8 threads */
		cpus = 8;
	}
	tpinit(&tp, cpus);

	for (size_t i = 0; i < lengthof(sources); i++)
		tpenq(&tp, build, sources[i], NULL);
	tpwait(&tp);
	tpfree(&tp);

	cmdadd(&cmd, CC, "-o", TARGET);
	cmdaddv(&cmd, objects, lengthof(objects));
	cmdput(cmd);
	if ((ec = cmdexec(cmd)) != EXIT_SUCCESS)
		diex("%s failed with exit-code %d", *cmd._argv, ec);

	return EXIT_SUCCESS;
}

void
build(void *arg)
{
	int ec;
	char *dst, *src = arg;
	cmd_t cmd = {0};

	for (size_t i = 0; i < lengthof(sources); i++) {
		/* All the sources and objects have 3 letter names + an extension */
		if (strncmp(src, objects[i], 3) == 0) {
			dst = objects[i];
			break;
		}
	}

	cmdadd(&cmd, CC, CFLAGS, "-o", dst, "-c", src);
	cmdput(cmd);
	if ((ec = cmdexec(cmd)) != EXIT_SUCCESS)
		diex("%s failed with exit-code %d", *cmd._argv, ec);
	free(cmd._argv);
}
```