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
27 #include <jeffpc/jeffpc.h>
28 #include <jeffpc/error.h>
29 #include <jeffpc/io.h>
30 #include <jeffpc/sexpr.h>
38 * Each config item is represented by a list:
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
75 * The generated makefile and header lines are indistinguishable from config
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:
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 * -------------------------
107 * #define CONFIG_FOO 5
110 * string (e.g., "abc")
112 * #define CONFIG_FOO "abc"
115 * quoted symbol (e.g., 'abc)
117 * #define CONFIG_FOO abc
120 * Unquoted symbols are subject to evaluation. For example, given the
131 * We would end up with:
133 * #define CONFIG_FOO 5
134 * #define CONFIG_BAR 5
141 * since when generating the CONFIG_BAR line, we evaluate the expression (FOO)
147 * The values in config and const statements are subject to evaluation. As
148 * stated above, strings, integers, booleans and quoted symbols evaluate to
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:
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"
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
;
198 ret
= strcmp(str_cstr(a
->name
), str_cstr(b
->name
));
206 static struct val
*config_lookup(struct str
*name
, void *private)
208 struct config_item key
= {
211 struct config_item
*ci
;
213 ci
= avl_find(&mapping_tree
, &key
, NULL
);
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
));
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
,
288 fprintf(f
, "#define\tCONFIG_%s\n", str_cstr(name
));
290 fprintf(f
, "#undef\tCONFIG_%s\n", str_cstr(name
));
293 static void map_bool_makefile(FILE *f
, const struct str
*name
,
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
,
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");
318 fprintf(f
, "#ifndef\t%s\n", include_guard_name
);
319 fprintf(f
, "#define\t%s\n", include_guard_name
);
323 static void map_file_end_header(FILE *f
)
326 fprintf(f
, "#endif\n");
329 static void map_file_start_makefile(FILE *f
)
331 fprintf(f
, "# GENERATED FILE - DO NOT EDIT\n");
335 static void map_file_start_makefile_dmake(FILE *f
)
337 fprintf(f
, "# GENERATED FILE - DO NOT EDIT\n");
339 fprintf(f
, "PRE_POUND=pre\\#\n");
340 fprintf(f
, "POUND_SIGN=$(PRE_POUND:pre\\%%=%%)\n");
344 static struct mapping mapping
[NGEN
] = {
346 .file_start
= map_file_start_header
,
347 .file_end
= map_file_end_header
,
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
,
357 .file_start
= map_file_start_makefile
,
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
,
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
,
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
));
391 die("failed to allocate config item");
393 item
->name
= str_getref(name
->str
);
394 item
->defvalue
= val_getref(value
);
397 avl_add(&mapping_tree
, item
);
398 list_insert_tail(&mapping_list
, item
);
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
));
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
);
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);
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
)));
455 static int process(int dirfd
, const char *infile
)
460 in
= read_file_at(dirfd
, infile
);
462 fprintf(stderr
, "%s: cannot read: %s\n",
463 infile
, xstrerror(PTR_ERR(in
)));
467 cur
= sexpr_parse(in
, strlen(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) {
488 } else if (strcmp(str_cstr(stmt
->str
), INCLUDE_STMT
) == 0) {
489 __include(dirfd
, args
);
491 die("unknown statement");
500 VERIFY3P(cur
, ==, NULL
);
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
);
534 cur
= list_next(&mapping_list
, cur
)) {
535 ASSERT3P(cur
->value
, ==, NULL
);
537 switch (cur
->defvalue
->type
) {
541 cur
->value
= get_val_atom(cur
);
545 /* skip these for now */
550 /* fill in all the rest (i.e., those with VT_CONS & VT_SYM defaults) */
551 for (cur
= list_head(&mapping_list
);
553 cur
= list_next(&mapping_list
, cur
)) {
555 continue; /* already set */
557 switch (cur
->defvalue
->type
) {
561 panic("config item %p (%s) found empty", cur
,
562 str_cstr(cur
->name
));
564 cur
->value
= get_val_sym(cur
);
567 if (cur
->type
!= CIT_SELECT
)
568 cur
->value
= get_val_expr(cur
);
570 cur
->value
= get_val_select(cur
);
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
;
586 f
= fopen(outfile
, "w");
591 for (cur
= list_head(&mapping_list
);
593 cur
= list_next(&mapping_list
, cur
)) {
596 value
= val_getref(cur
->value
);
598 m
->entry
[value
->type
](f
, cur
->name
, value
);
610 static void usage(void)
612 fprintf(stderr
, "Usage: %s {-H | -M | -m} [-I <guard name>] "
613 "[-o <outfile>] <infile>\n", prog
);
617 int main(int argc
, char **argv
)
619 enum gen_what what
= GEN_HEADER
;
620 const char *outfile
= NULL
;
625 ASSERT0(putenv("UMEM_DEBUG=default,verbose"));
626 ASSERT0(putenv("BLAHG_DISABLE_SYSLOG=1"));
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) {
642 include_guard_name
= optarg
;
645 /* makefile (bmake) */
649 /* makefile (dmake) */
650 what
= GEN_MAKEFILE_DMAKE
;
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
));