8577 libstand: add stpcpy stpncpy
[unleashed.git] / tools / mkconfig / main.c
blobfbe21a443ad2383a5de2565b54cfeabe1f5d5a64
1 /*
2 * Copyright (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
23 #include <stdlib.h>
24 #include <stddef.h>
25 #include <libgen.h>
27 #include <jeffpc/jeffpc.h>
28 #include <jeffpc/error.h>
29 #include <jeffpc/io.h>
30 #include <jeffpc/sexpr.h>
32 #include "main.h"
35 * Config
36 * ======
38 * Each config item is represented by a list:
40 * (config
41 * NAME
42 * default-value)
44 * For example:
46 * (config
47 * LINUX_CORE_SUPPORT
48 * #t)
50 * This generates the following line when -H is specified:
52 * #define CONFIG_LINUX_CORE_SUPPORT
54 * and the following line when -M is specified:
56 * CONFIG_LINUX_CORE_SUPPORT=y
58 * Some configuration item do not should never be changed, however it is
59 * convenient to keep track of them using config files. To make it
60 * paintfully obvious that these should not be changed (and later on
61 * possibly give hints to an interactive configuration tool), one can use
62 * const statements instead. They have the same syntax as config
63 * statements:
65 * (const
66 * NAME
67 * value)
69 * For example:
71 * (const
72 * MACH
73 * 'amd64)
75 * The generated makefile and header lines are indistinguishable from config
76 * statement lines.
78 * Select
79 * ------
81 * In some cases, the domain of the values is narrower than the domain of
82 * all integers, booleans, strings, etc. For these cases, there is the
83 * select statement. For example, given the following statement:
85 * (select
86 * PLATFORM
87 * ('i86pc 'i86xpv))
89 * The only possible values for CONFIG_PLATFORM are i86pc and i86xpv. The
90 * default is the first value listed.
92 * Mapping of Default Values
93 * -------------------------
95 * bool true (#t):
97 * #define CONFIG_FOO
98 * CONFIG_FOO=y
100 * bool false (#f):
102 * #undef CONFIG_FOO
103 * CONFIG_FOO=n
105 * int (e.g., 5):
107 * #define CONFIG_FOO 5
108 * CONFIG_FOO=5
110 * string (e.g., "abc")
112 * #define CONFIG_FOO "abc"
113 * CONFIG_FOO="abc"
115 * quoted symbol (e.g., 'abc)
117 * #define CONFIG_FOO abc
118 * CONFIG_FOO=abc
120 * Unquoted symbols are subject to evaluation. For example, given the
121 * this config:
123 * ((config
124 * FOO
125 * 5)
127 * (config
128 * BAR
129 * FOO))
131 * We would end up with:
133 * #define CONFIG_FOO 5
134 * #define CONFIG_BAR 5
136 * and
138 * CONFIG_FOO=5
139 * CONFIG_BAR=5
141 * since when generating the CONFIG_BAR line, we evaluate the expression (FOO)
142 * and obtain 5.
144 * Evaluation
145 * ----------
147 * The values in config and const statements are subject to evaluation. As
148 * stated above, strings, integers, booleans and quoted symbols evaluate to
149 * themselves.
151 * Unquoted symbols and lists are evaluated using a handful of simple built-in
152 * functions. The resulting value is used in the generated output file. This
153 * allows for complex expressions without having to worry if every part of the
154 * build system supports them (e.g., bitshifts).
156 * The supported functions include:
158 * (or ...)
159 * (and ...)
160 * (+ ...)
161 * (* ...)
163 * TODO: expand
165 * Include
166 * =======
168 * It is possible to include files by using the include statement:
170 * (include "filename")
172 * This includes the contents of the specified file as if they were included
173 * in place of the include statement.
176 #define CONFIG_STMT "config"
177 #define CONST_STMT "const"
178 #define SELECT_STMT "select"
179 #define INCLUDE_STMT "include"
181 enum gen_what {
182 GEN_HEADER,
183 GEN_MAKEFILE,
184 GEN_MAKEFILE_DMAKE,
186 #define NGEN (GEN_MAKEFILE_DMAKE + 1)
188 static avl_tree_t mapping_tree;
189 static list_t mapping_list;
190 static const char *include_guard_name = "__CONFIG_H";
192 static int cmp(const void *va, const void *vb)
194 const struct config_item *a = va;
195 const struct config_item *b = vb;
196 int ret;
198 ret = strcmp(str_cstr(a->name), str_cstr(b->name));
199 if (ret < 0)
200 return -1;
201 else if (ret > 0)
202 return 1;
203 return 0;
206 static struct val *config_lookup(struct str *name, void *private)
208 struct config_item key = {
209 .name = name,
211 struct config_item *ci;
213 ci = avl_find(&mapping_tree, &key, NULL);
215 str_putref(name);
218 * We're supposed to return "code", so we wrap the value in a quote.
219 * If we returned a value instead (e.g., i386), sexpr_eval() would
220 * try to evaluate that - by looking up the symbol.
222 return VAL_ALLOC_CONS(VAL_ALLOC_SYM_CSTR("quote"),
223 VAL_ALLOC_CONS(val_getref(ci->value), NULL));
226 struct mapping {
227 void (*file_start)(FILE *);
228 void (*file_end)(FILE *);
229 void (*entry[VT_CONS + 1])(FILE *, const struct str *, struct val *);
232 static struct mapping mapping[NGEN]; /* forward decl */
234 static void __map_int(FILE *f, const char *pfx, const char *name,
235 const char *sep, const uint64_t val)
237 fprintf(f, "%sCONFIG_%s%s%"PRIu64"\n", pfx, name, sep, val);
240 static void map_int_header(FILE *f, const struct str *name, struct val *val)
242 __map_int(f, "#define\t", str_cstr(name), " ", val->i);
245 static void map_int_makefile(FILE *f, const struct str *name, struct val *val)
247 __map_int(f, "", str_cstr(name), "=", val->i);
250 static void __map_str(FILE *f, const char *pfx, const char *name,
251 const char *sep, const char *val, bool quoted)
253 const char *q = quoted ? "\"" : "";
255 fprintf(f, "%sCONFIG_%s%s%s%s%s\n", pfx, name, sep, q, val, q);
258 static void map_str_header(FILE *f, const struct str *name, struct val *val)
260 // XXX: return val with quotes escaped
261 __map_str(f, "#define\t", str_cstr(name), " ", str_cstr(val->str), true);
264 static void map_str_makefile(FILE *f, const struct str *name, struct val *val)
266 // XXX: return val with quotes escaped
267 __map_str(f, "", str_cstr(name), "=", str_cstr(val->str), true);
270 static void map_sym_header(FILE *f, const struct str *name, struct val *val)
272 /* produce the symbol as well as a stringified version */
273 __map_str(f, "#define\t", str_cstr(name), " ", str_cstr(val->str), false);
274 __map_str(f, "#define\t", str_cstr(name), "_STR ", str_cstr(val->str), true);
277 static void map_sym_makefile(FILE *f, const struct str *name, struct val *val)
279 /* produce the symbol as well as a stringified version */
280 __map_str(f, "", str_cstr(name), "=", str_cstr(val->str), false);
281 __map_str(f, "", str_cstr(name), "_STR=", str_cstr(val->str), true);
284 static void map_bool_header(FILE *f, const struct str *name,
285 struct val *val)
287 if (val->b)
288 fprintf(f, "#define\tCONFIG_%s\n", str_cstr(name));
289 else
290 fprintf(f, "#undef\tCONFIG_%s\n", str_cstr(name));
293 static void map_bool_makefile(FILE *f, const struct str *name,
294 struct val *val)
296 fprintf(f, "CONFIG_%s=%s\n", str_cstr(name), val->b ? "y" : "n");
299 static void map_bool_makefile_dmake(FILE *f, const struct str *name,
300 struct val *val)
302 fprintf(f, "CONFIG_%s=%s\n", str_cstr(name), val->b ? "" : "$(POUND_SIGN)");
305 static void map_cons(FILE *f, const struct str *name, struct val *val)
308 * All VT_CONS value should have been evaluated away into ints,
309 * bools, strings, and symbols.
311 panic("We should never be mapping a VT_CONS");
314 static void map_file_start_header(FILE *f)
316 fprintf(f, "/* GENERATED FILE - DO NOT EDIT */\n");
317 fprintf(f, "\n");
318 fprintf(f, "#ifndef\t%s\n", include_guard_name);
319 fprintf(f, "#define\t%s\n", include_guard_name);
320 fprintf(f, "\n");
323 static void map_file_end_header(FILE *f)
325 fprintf(f, "\n");
326 fprintf(f, "#endif\n");
329 static void map_file_start_makefile(FILE *f)
331 fprintf(f, "# GENERATED FILE - DO NOT EDIT\n");
332 fprintf(f, "\n");
335 static void map_file_start_makefile_dmake(FILE *f)
337 fprintf(f, "# GENERATED FILE - DO NOT EDIT\n");
338 fprintf(f, "\n");
339 fprintf(f, "PRE_POUND=pre\\#\n");
340 fprintf(f, "POUND_SIGN=$(PRE_POUND:pre\\%%=%%)\n");
341 fprintf(f, "\n");
344 static struct mapping mapping[NGEN] = {
345 [GEN_HEADER] = {
346 .file_start = map_file_start_header,
347 .file_end = map_file_end_header,
348 .entry = {
349 [VT_INT] = map_int_header,
350 [VT_STR] = map_str_header,
351 [VT_SYM] = map_sym_header,
352 [VT_BOOL] = map_bool_header,
353 [VT_CONS] = map_cons,
356 [GEN_MAKEFILE] = {
357 .file_start = map_file_start_makefile,
358 .entry = {
359 [VT_INT] = map_int_makefile,
360 [VT_STR] = map_str_makefile,
361 [VT_SYM] = map_sym_makefile,
362 [VT_BOOL] = map_bool_makefile,
363 [VT_CONS] = map_cons,
366 [GEN_MAKEFILE_DMAKE] = {
367 .file_start = map_file_start_makefile_dmake,
368 .entry = {
369 [VT_INT] = map_int_makefile,
370 [VT_STR] = map_str_makefile,
371 [VT_SYM] = map_sym_makefile,
372 [VT_BOOL] = map_bool_makefile_dmake,
373 [VT_CONS] = map_cons,
378 static char *prog;
380 static void __config(struct val *args, enum config_item_type type)
382 struct val *name = sexpr_nth(val_getref(args), 1);
383 struct val *value = sexpr_nth(val_getref(args), 2);
384 struct config_item *item;
386 if (name->type != VT_SYM)
387 die("name not a VT_SYM");
389 item = malloc(sizeof(struct config_item));
390 if (!item)
391 die("failed to allocate config item");
393 item->name = str_getref(name->str);
394 item->defvalue = val_getref(value);
395 item->type = type;
397 avl_add(&mapping_tree, item);
398 list_insert_tail(&mapping_list, item);
400 val_putref(name);
401 val_putref(value);
404 static void __select(struct val *args)
406 struct val *name = sexpr_nth(val_getref(args), 1);
407 struct val *value = sexpr_nth(val_getref(args), 2);
408 struct config_item *item;
410 if (name->type != VT_SYM)
411 die("name not a VT_SYM");
413 item = malloc(sizeof(struct config_item));
414 if (!item)
415 die("failed to allocate config item");
417 item->name = str_getref(name->str);
418 item->defvalue = val_getref(value);
419 item->type = CIT_SELECT;
421 avl_add(&mapping_tree, item);
422 list_insert_tail(&mapping_list, item);
424 val_putref(name);
425 val_putref(value);
428 static int process(int dirfd, const char *infile);
430 static void __include(int dirfd, struct val *args)
432 struct val *fname = sexpr_nth(val_getref(args), 1);
433 char *dname;
434 char *bname;
435 int dir;
437 if (fname->type != VT_STR)
438 die("fname not a VT_STR");
440 dname = strdup(str_cstr(fname->str));
441 bname = strdup(str_cstr(fname->str));
443 dir = xopenat(dirfd, dirname(dname), O_RDONLY, 0);
445 ASSERT0(process(dir, basename(bname)));
447 xclose(dir);
449 free(bname);
450 free(dname);
452 val_putref(fname);
455 static int process(int dirfd, const char *infile)
457 struct val *cur;
458 char *in;
460 in = read_file_at(dirfd, infile);
461 if (IS_ERR(in)) {
462 fprintf(stderr, "%s: cannot read: %s\n",
463 infile, xstrerror(PTR_ERR(in)));
464 die("failed");
467 cur = sexpr_parse(in, strlen(in));
469 free(in);
471 while (cur != NULL) {
472 struct val *item = sexpr_car(val_getref(cur));
473 struct val *stmt = sexpr_car(val_getref(item));
474 struct val *args = sexpr_cdr(val_getref(item));
475 struct val *next = sexpr_cdr(cur);
477 if (item->type != VT_CONS)
478 die("item not a VT_CONS");
479 if (stmt->type != VT_SYM)
480 die("statement not a VT_SYM");
482 if (strcmp(str_cstr(stmt->str), CONFIG_STMT) == 0) {
483 __config(args, CIT_CONFIG);
484 } else if (strcmp(str_cstr(stmt->str), CONST_STMT) == 0) {
485 __config(args, CIT_CONST);
486 } else if (strcmp(str_cstr(stmt->str), SELECT_STMT) == 0) {
487 __select(args);
488 } else if (strcmp(str_cstr(stmt->str), INCLUDE_STMT) == 0) {
489 __include(dirfd, args);
490 } else {
491 die("unknown statement");
494 val_putref(item);
495 val_putref(stmt);
496 val_putref(args);
497 cur = next;
500 VERIFY3P(cur, ==, NULL);
502 return 0;
505 static inline struct val *get_val_atom(struct config_item *ci)
507 return val_getref(ci->defvalue);
510 static inline struct val *get_val_sym(struct config_item *ci)
512 return sexpr_eval(val_getref(ci->defvalue), config_lookup, NULL);
515 static inline struct val *get_val_expr(struct config_item *ci)
517 return sexpr_eval(val_getref(ci->defvalue), config_lookup, NULL);
520 static inline struct val *get_val_select(struct config_item *ci)
522 /* grab the first option */
523 return sexpr_eval(sexpr_car(val_getref(ci->defvalue)), NULL, NULL);
526 /* figure out config items' values */
527 static void eval(void)
529 struct config_item *cur;
531 /* use defaults for all atoms */
532 for (cur = list_head(&mapping_list);
533 cur != NULL;
534 cur = list_next(&mapping_list, cur)) {
535 ASSERT3P(cur->value, ==, NULL);
537 switch (cur->defvalue->type) {
538 case VT_INT:
539 case VT_STR:
540 case VT_BOOL:
541 cur->value = get_val_atom(cur);
542 break;
543 case VT_SYM:
544 case VT_CONS:
545 /* skip these for now */
546 break;
550 /* fill in all the rest (i.e., those with VT_CONS & VT_SYM defaults) */
551 for (cur = list_head(&mapping_list);
552 cur != NULL;
553 cur = list_next(&mapping_list, cur)) {
554 if (cur->value)
555 continue; /* already set */
557 switch (cur->defvalue->type) {
558 case VT_INT:
559 case VT_STR:
560 case VT_BOOL:
561 panic("config item %p (%s) found empty", cur,
562 str_cstr(cur->name));
563 case VT_SYM:
564 cur->value = get_val_sym(cur);
565 break;
566 case VT_CONS:
567 if (cur->type != CIT_SELECT)
568 cur->value = get_val_expr(cur);
569 else
570 cur->value = get_val_select(cur);
571 break;
576 /* print out the config items */
577 static void map(const char *outfile, enum gen_what what)
579 struct mapping *m = &mapping[what];
580 struct config_item *cur;
581 FILE *f;
583 if (!outfile)
584 f = stdout;
585 else
586 f = fopen(outfile, "w");
588 if (m->file_start)
589 m->file_start(f);
591 for (cur = list_head(&mapping_list);
592 cur != NULL;
593 cur = list_next(&mapping_list, cur)) {
594 struct val *value;
596 value = val_getref(cur->value);
598 m->entry[value->type](f, cur->name, value);
600 val_putref(value);
603 if (m->file_end)
604 m->file_end(f);
606 if (outfile)
607 fclose(f);
610 static void usage(void)
612 fprintf(stderr, "Usage: %s {-H | -M | -m} [-I <guard name>] "
613 "[-o <outfile>] <infile>\n", prog);
614 exit(2);
617 int main(int argc, char **argv)
619 enum gen_what what = GEN_HEADER;
620 const char *outfile = NULL;
621 char opt;
623 prog = argv[0];
625 ASSERT0(putenv("UMEM_DEBUG=default,verbose"));
626 ASSERT0(putenv("BLAHG_DISABLE_SYSLOG=1"));
628 jeffpc_init(NULL);
630 avl_create(&mapping_tree, cmp, sizeof(struct config_item),
631 offsetof(struct config_item, tree));
632 list_create(&mapping_list, sizeof(struct config_item),
633 offsetof(struct config_item, list));
635 while ((opt = getopt(argc, argv, "HI:Mmo:")) != -1) {
636 switch (opt) {
637 case 'H':
638 /* header */
639 what = GEN_HEADER;
640 break;
641 case 'I':
642 include_guard_name = optarg;
643 break;
644 case 'M':
645 /* makefile (bmake) */
646 what = GEN_MAKEFILE;
647 break;
648 case 'm':
649 /* makefile (dmake) */
650 what = GEN_MAKEFILE_DMAKE;
651 break;
652 case 'o':
653 /* output file */
654 outfile = optarg;
655 break;
656 default:
657 usage();
658 break;
662 if (optind == argc)
663 usage();
666 * Start processing by faking a config which looks like:
668 * ((include "<the path user specified>"))
670 * This allows make relative path includes work properly. We could
671 * alternatively do the same path munging that __include() does
672 * here, but that would duplicate some ugly code.
674 __include(AT_FDCWD, VAL_ALLOC_CONS(VAL_ALLOC_CSTR(argv[argc - 1]), NULL));
676 eval();
678 map(outfile, what);
680 return 0;