Re: [PATCH v2 07/21] kconfig: add function support and implement 'shell' function
From: Ulf Magnusson
Date: Sun Apr 01 2018 - 00:19:42 EST
On Tue, Mar 27, 2018 at 7:29 AM, Masahiro Yamada
<yamada.masahiro@xxxxxxxxxxxxx> wrote:
> This commit adds a new concept 'function' to do more text processing
> in Kconfig.
>
> A function call looks like this:
>
> $(function arg1, arg2, arg3, ...)
>
> (Actually, this syntax was inspired by make.)
>
> Real examples will look like this:
>
> $(shell echo hello world)
> $(cc-option -fstackprotector)
>
> This commit adds the basic infrastructure to add, delete, evaluate
> functions, and also the first built-in function $(shell ...). This
> accepts a single command to execute. It returns the standard output
> from it.
>
> [Example code]
>
> config HELLO
> string
> default "$(shell echo hello world)"
>
> config Y
> def_bool $(shell echo y)
>
> [Result]
>
> $ make -s alldefconfig && tail -n 2 .config
> CONFIG_HELLO="hello world"
> CONFIG_Y=y
>
> Caveat:
> Like environments, functions are expanded in the lexer. You cannot
> pass symbols to function arguments. This is a limitation to simplify
> the implementation. I want to avoid the dynamic function evaluation,
> which would introduce much more complexity.
>
> Signed-off-by: Masahiro Yamada <yamada.masahiro@xxxxxxxxxxxxx>
> ---
>
> Reminder for myself:
> Update Documentation/kbuild/kconfig-language.txt
>
>
> Changes in v2:
> - Use 'shell' for getting stdout from the comment.
> It was 'shell-stdout' in the previous version.
> - Symplify the implementation since the expansion has been moved to
> lexer.
>
> scripts/kconfig/function.c | 170 ++++++++++++++++++++++++++++++++++++++++++++
> scripts/kconfig/lkc_proto.h | 5 ++
> scripts/kconfig/util.c | 46 +++++++++---
> scripts/kconfig/zconf.y | 9 +++
> 4 files changed, 222 insertions(+), 8 deletions(-)
> create mode 100644 scripts/kconfig/function.c
>
> diff --git a/scripts/kconfig/function.c b/scripts/kconfig/function.c
> new file mode 100644
> index 0000000..913685f
> --- /dev/null
> +++ b/scripts/kconfig/function.c
> @@ -0,0 +1,170 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@xxxxxxxxxxxxx>
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "list.h"
> +
> +#define FUNCTION_MAX_ARGS 10
> +
> +static LIST_HEAD(function_list);
> +
> +struct function {
> + char *name;
> + char *(*func)(struct function *f, int argc, char *argv[]);
> + struct list_head node;
> +};
> +
> +static struct function *func_lookup(const char *name)
> +{
> + struct function *f;
> +
> + list_for_each_entry(f, &function_list, node) {
> + if (!strcmp(name, f->name))
> + return f;
> + }
> +
> + return NULL;
> +}
> +
> +static void func_add(const char *name,
> + char *(*func)(struct function *f, int argc, char *argv[]))
> +{
> + struct function *f;
> +
> + f = func_lookup(name);
> + if (f) {
> + fprintf(stderr, "%s: function already exists. ignored.\n", name);
> + return;
> + }
> +
> + f = xmalloc(sizeof(*f));
> + f->name = xstrdup(name);
> + f->func = func;
> +
> + list_add_tail(&f->node, &function_list);
> +}
> +
> +static void func_del(struct function *f)
> +{
> + list_del(&f->node);
> + free(f->name);
> + free(f);
> +}
> +
> +static char *func_call(int argc, char *argv[])
> +{
> + struct function *f;
> +
> + f = func_lookup(argv[0]);
> + if (!f) {
> + fprintf(stderr, "%s: function not found\n", argv[0]);
> + return NULL;
> + }
> +
> + return f->func(f, argc, argv);
> +}
> +
> +static char *func_eval(const char *func)
> +{
> + char *expanded, *saveptr, *str, *token, *res;
> + const char *delim;
> + int argc = 0;
> + char *argv[FUNCTION_MAX_ARGS];
> +
> + expanded = expand_string_value(func);
> +
> + str = expanded;
> + delim = " ";
> +
> + while ((token = strtok_r(str, delim, &saveptr))) {
> + argv[argc++] = token;
Would be nice to error out if the array is overstepped.
> + str = NULL;
> + delim = ",";
> + }
> +
> + res = func_call(argc, argv);
> +
> + free(expanded);
> +
> + return res ?: xstrdup("");
> +}
Since only 'macro' will take multiple parameters, I wonder if it might
be better to implement the argument parsing there, and simply pass the
string (minus the function name) as-is to functions.
You would then be able to have ',' in shell commands, which might be
required -- think gcc -Wl,option and the like.
> +
> +char *func_eval_n(const char *func, size_t n)
> +{
> + char *tmp, *res;
> +
> + tmp = xmalloc(n + 1);
> + memcpy(tmp, func, n);
> + *(tmp + n) = '\0';
> +
> + res = func_eval(tmp);
> +
> + free(tmp);
> +
> + return res;
> +}
> +
> +/* built-in functions */
> +static char *do_shell(struct function *f, int argc, char *argv[])
> +{
> + static const char *pre = "(";
> + static const char *post = ") 2>/dev/null";
Arrays seem neater, since the pointers aren't needed.
> + FILE *p;
> + char buf[256];
> + char *cmd;
> + int ret;
Could get rid of 'ret' and just do
if (pclose(p) == -1)
perror(cmd);
> +
> + if (argc != 2)
> + return NULL;
> +
> + /*
> + * Surround the command with ( ) in case it is piped commands.
> + * Also, redirect stderr to /dev/null.
> + */
> + cmd = xmalloc(strlen(pre) + strlen(argv[1]) + strlen(post) + 1);
> + strcpy(cmd, pre);
> + strcat(cmd, argv[1]);
> + strcat(cmd, post);
> +
> + p = popen(cmd, "r");
> + if (!p) {
> + perror(cmd);
> + goto free;
> + }
> + if (fgets(buf, sizeof(buf), p)) {
> + size_t len = strlen(buf);
> +
> + if (buf[len - 1] == '\n')
> + buf[len - 1] = '\0';
> + } else {
> + buf[0] = '\0';
> + }
> +
> + ret = pclose(p);
> + if (ret == -1)
> + perror(cmd);
> +
> +free:
> + free(cmd);
> +
> + return xstrdup(buf);
> +}
> +
> +void func_init(void)
> +{
> + /* register built-in functions */
> + func_add("shell", do_shell);
> +}
> +
> +void func_exit(void)
> +{
> + struct function *f, *tmp;
> +
> + /* unregister all functions */
> + list_for_each_entry_safe(f, tmp, &function_list, node)
> + func_del(f);
> +}
> diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h
> index 9884adc..09a4f53 100644
> --- a/scripts/kconfig/lkc_proto.h
> +++ b/scripts/kconfig/lkc_proto.h
> @@ -48,5 +48,10 @@ const char * sym_get_string_value(struct symbol *sym);
>
> const char * prop_get_type_name(enum prop_type type);
>
> +/* function.c */
> +char *func_eval_n(const char *func, size_t n);
> +void func_init(void);
> +void func_exit(void);
> +
> /* expr.c */
> void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken);
> diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c
> index 3d27c49..218b051 100644
> --- a/scripts/kconfig/util.c
> +++ b/scripts/kconfig/util.c
> @@ -13,9 +13,10 @@
> #include "lkc.h"
>
> /*
> - * Expand environments embedded in the string given in argument. Environments
> - * to be expanded shall be prefixed by a '$'. Unknown environment expands to
> - * the empty string.
> + * Expand environments and functions embedded in the string given in argument.
> + * Environments to be expanded shall be prefixed by a '$'. Functions to be
> + * evaluated shall be surrounded by $(). Unknown environment/function expands
> + * to the empty string.
> */
> char *expand_string_value(const char *in)
> {
> @@ -33,11 +34,40 @@ char *expand_string_value(const char *in)
> while ((p = strchr(in, '$'))) {
> char *new;
>
> - q = p + 1;
> - while (isalnum(*q) || *q == '_')
> - q++;
> -
> - new = env_expand_n(p + 1, q - p - 1);
> + /*
> + * If the next character is '(', it is a function.
> + * Otherwise, environment.
> + */
> + if (*(p + 1) == '(') {
> + int nest = 0;
> +
> + q = p + 2;
> + while (1) {
> + if (*q == '\0') {
> + fprintf(stderr,
> + "unterminated function: %s\n",
> + p);
> + new = xstrdup("");
> + break;
> + } else if (*q == '(') {
> + nest++;
> + } else if (*q == ')') {
> + if (nest-- == 0) {
> + new = func_eval_n(p + 2,
> + q - p - 2);
> + q++;
> + break;
> + }
> + }
> + q++;
> + }
A loop like this might work too:
q = p + 1;
do {
if (*q == '\0') {
*error*
val = ...
goto error;
}
if (*q == '(')
nest++;
if (*q == ')')
nest--;
q++;
} while (nest > 0);
val = func_eval_n(...)
error:
> + } else {
> + q = p + 1;
> + while (isalnum(*q) || *q == '_')
> + q++;
> +
> + new = env_expand_n(p + 1, q - p - 1);
> + }
>
> reslen = strlen(res) + (p - in) + strlen(new) + 1;
> res = xrealloc(res, reslen);
> diff --git a/scripts/kconfig/zconf.y b/scripts/kconfig/zconf.y
> index d8120c7..feaea18 100644
> --- a/scripts/kconfig/zconf.y
> +++ b/scripts/kconfig/zconf.y
> @@ -520,11 +520,19 @@ void conf_parse(const char *name)
>
> zconf_initscan(name);
>
> + func_init();
> _menu_init();
>
> if (getenv("ZCONF_DEBUG"))
> yydebug = 1;
> yyparse();
> +
> + /*
> + * Currently, functions are evaluated only when Kconfig files are
> + * parsed. We can free functions here.
> + */
> + func_exit();
> +
> if (yynerrs)
> exit(1);
> if (!modules_sym)
> @@ -765,4 +773,5 @@ void zconfdump(FILE *out)
> #include "confdata.c"
> #include "expr.c"
> #include "symbol.c"
> +#include "function.c"
> #include "menu.c"
> --
> 2.7.4
>
Cheers,
Ulf