Received: by 10.223.185.116 with SMTP id b49csp1926882wrg; Sat, 17 Feb 2018 08:17:54 -0800 (PST) X-Google-Smtp-Source: AH8x2270wf9aJumfugNx/nwsNC/9mAFazFGkaUsem6mvkNFxV65+vTKij2E6lnyHweZ/iYBIfsGM X-Received: by 10.101.68.202 with SMTP id g10mr8297813pgs.210.1518884274013; Sat, 17 Feb 2018 08:17:54 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1518884273; cv=none; d=google.com; s=arc-20160816; b=vtmSSA2JX00AMu5AhX2PaWZQY2T++1FxgapecwEvTtdGwr+LLKiBWkGQTpldoFPEnl qaPfOgvQtEVeV6SDb+EvMTEeBKHdKEhCxHKldXc+LTusnM9KSuB1NDVw58OYZx6Jgceb /SmLhl8f3rdJ8NlzJJhes3lVJtYu5bF0D/1lQkZVs33uzXbRp7vdsw52qlhqrtjMIvb6 KkDT3F3LQ/txTNt7bGAxQy5FtyO5M0f6iDl6B2TPlwY4d8kCaPR+UiFzVAXMtcgsH9xt tHPzPpvtJhY9h2Jk+L2JSDBLs27iiTRO29Ob3a2Y7VXWH/sHPOV4m7tZie8CC7GBqWLT XxaQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-disposition:mime-version:references:message-id:subject:cc :to:from:date:dkim-signature:arc-authentication-results; bh=ljB6uV29yXESl2uRR0HdFXq9aSntUXNqM3UM2aFAco0=; b=M049+RoO3CxGGmuUGuEsIckYmWt/7K4Lj3xDHcGLNjXdjotdmsv/XVMsMJok4jt3UD Pbdrv35UulgwwhDiTP1vbtAEgLKK6PSdfLsvzZBBiKJP6PrpQXd6ayCy57oiDR+2E5mH AJjSV2XNTdnrHwiVZNk0gg7cZH5oEkN8cZJY2W38l45dol/2hi0lOCwTk9hckpd+udSM BNUuVbOJWL4gmZjAmsSHH5Gt9WmpGCSnjVPOXlKVsgB1yzWQXBSUkoxL8V/c8RH0+fcN TB5/FJTxcr3LDo9KY4uhzIKVVN2/uh0BFMivHG+kTGvEPh/E4Aydne9a/lVHFuAc5PZg HyWw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=DIWQGuqD; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 43-v6si1637953plb.548.2018.02.17.08.17.39; Sat, 17 Feb 2018 08:17:53 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=DIWQGuqD; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751131AbeBQQQ6 (ORCPT + 99 others); Sat, 17 Feb 2018 11:16:58 -0500 Received: from mail-lf0-f67.google.com ([209.85.215.67]:40066 "EHLO mail-lf0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751049AbeBQQQ4 (ORCPT ); Sat, 17 Feb 2018 11:16:56 -0500 Received: by mail-lf0-f67.google.com with SMTP id 37so7852162lfs.7; Sat, 17 Feb 2018 08:16:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=ljB6uV29yXESl2uRR0HdFXq9aSntUXNqM3UM2aFAco0=; b=DIWQGuqDRymlymM3hOP5LHnJBlLoa80Sx1UVgQ9NAjTcDfbpoFrk5DkKYiydWp6qT7 6UOsxZt9qyMeesrcE8soI4K1MiI3qlMeMkvetVaTC3fHMxcnKKjTFrn+v+4Sfvnw9Dby YybLOabOSS3N0Vc/+Gc16vueaOevRfDxOHQgHOVPNNBQCBms+w47c0KSwj43Nd+mkvGK iifVQNkVvOF0UYOYaiXDXR6Tq5RZCeASj7neQlY9sgS5PZhYCWGHju8fmaBVttR9Wj0B XIMwfHXJO4aLCmLS+LCLhUZ1k4QhAVwNpiTK45vgps72XDz03MR6p1FbzAEDUWylkDQ0 k8KQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=ljB6uV29yXESl2uRR0HdFXq9aSntUXNqM3UM2aFAco0=; b=iwj1HiuOYM0XGZuiBc0gvdwzE0bjCC1siEtd1X5dSUStCX1auQALmOdipBYTWnbdeu u20uBaC2EM61JjNGs0AfdTgFR7TCN+IzJHgitnRLXmMSJ6hkCWwOSHiY6PT11SvxYINJ UlPbSTTw6yw8J/C+lIfcWlJDBCojojn7nGwywo3wZLyKdSqzpuqOSygE94zPdWG9umXr VwwyRtNezInrAuoiXDLaz4S6rXihNL9DohdE4xStOJKr+XPL/Qa8wX7RTAajjkS7gkCq drHXvHy1eNhTpSRwncKk4iSVoubst89WhrUk8T1Q7J8bRhuC39ZxbW9NbztutPNZBoc+ UGqA== X-Gm-Message-State: APf1xPA6I179sRx5RvQWZN729Z+E7TIbXM6iz9p0vDn7423/vWRt737O vVZsF+SxW/i+hl/k3gllIeY= X-Received: by 10.25.143.27 with SMTP id r27mr7008585lfd.129.1518884214955; Sat, 17 Feb 2018 08:16:54 -0800 (PST) Received: from huvuddator (ua-213-113-106-221.cust.bredbandsbolaget.se. [213.113.106.221]) by smtp.gmail.com with ESMTPSA id f70sm1857041lfi.4.2018.02.17.08.16.53 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sat, 17 Feb 2018 08:16:54 -0800 (PST) Date: Sat, 17 Feb 2018 17:16:36 +0100 From: Ulf Magnusson To: Masahiro Yamada Cc: linux-kbuild@vger.kernel.org, Linus Torvalds , Greg Kroah-Hartman , Arnd Bergmann , Kees Cook , Randy Dunlap , Sam Ravnborg , Michal Marek , linux-kernel@vger.kernel.org Subject: Re: [PATCH 07/23] kconfig: add function support and implement 'shell' function Message-ID: <20180217161636.h6miw4xhu2mfjzvd@huvuddator> References: <1518806331-7101-1-git-send-email-yamada.masahiro@socionext.com> <1518806331-7101-8-git-send-email-yamada.masahiro@socionext.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline In-Reply-To: <1518806331-7101-8-git-send-email-yamada.masahiro@socionext.com> User-Agent: NeoMutt/20170609 (1.8.3) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Sat, Feb 17, 2018 at 03:38:35AM +0900, Masahiro Yamada wrote: > This commit adds a new concept 'function' to Kconfig. A function call > resembles a variable reference with arguments, and looks like this: > > $(function arg1, arg2, arg3, ...) > > (Actually, this syntax was inspired by Makefile.) > > Real examples will look like this: > > $(shell true) > $(cc-option -fstackprotector) > > This commit adds the basic infrastructure to add, delete, evaluate > functions. > > Also, add the first built-in function $(shell ...). This evaluates > to 'y' if the given command exits with 0, 'n' otherwise. > > I am also planning to support user-defined functions (a.k.a 'macro') > for cases where hard-coding is not preferred. > > If you want to try this feature, the hello-world code is someting below. > > Example code: > > config CC_IS_GCC > bool > default $(shell $CC --version | grep -q gcc) > > config CC_IS_CLANG > bool > default $(shell $CC --version | grep -q clang) > > config CC_HAS_OZ > bool > default $(shell $CC -Werror -Oz -c -x c /dev/null -o /dev/null) > > Result: > > $ make -s alldefconfig && tail -n 3 .config > CONFIG_CC_IS_GCC=y > # CONFIG_CC_IS_CLANG is not set > # CONFIG_CC_HAS_OZ is not set > > $ make CC=clang -s alldefconfig && tail -n 3 .config > # CONFIG_CC_IS_GCC is not set > CONFIG_CC_IS_CLANG=y > CONFIG_CC_HAS_OZ=y > > A function call can appear anywhere a symbol reference can appear. > So, the following code is possible. > > Example code: > > config CC_NAME > string > default "gcc" if $(shell $CC --version | grep -q gcc) > default "clang" if $(shell $CC --version | grep -q clang) > default "unknown compiler" > > Result: > > $ make -s alldefconfig && tail -n 1 .config > CONFIG_CC_NAME="gcc" > > $ make CC=clang -s alldefconfig && tail -n 1 .config > CONFIG_CC_NAME="clang" > > Signed-off-by: Masahiro Yamada > --- > > Reminder for myself: > Update Documentation/kbuild/kconfig-language.txt > > > scripts/kconfig/function.c | 149 ++++++++++++++++++++++++++++++++++++++++++++ > scripts/kconfig/lkc_proto.h | 5 ++ > scripts/kconfig/util.c | 46 +++++++++++--- > scripts/kconfig/zconf.l | 38 ++++++++++- > scripts/kconfig/zconf.y | 9 +++ > 5 files changed, 238 insertions(+), 9 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..60e59be > --- /dev/null > +++ b/scripts/kconfig/function.c > @@ -0,0 +1,149 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// > +// Copyright (C) 2018 Masahiro Yamada > + > +#include > +#include > +#include > + > +#include "list.h" > + > +#define FUNCTION_MAX_ARGS 10 > + > +static LIST_HEAD(function_list); > + > +struct function { > + const char *name; > + char *(*func)(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)(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 = name; > + f->func = func; > + > + list_add_tail(&f->node, &function_list); > +} > + > +static void func_del(struct function *f) > +{ > + list_del(&f->node); > + 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(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; > + str = NULL; > + delim = ","; > + } > + > + res = func_call(argc, argv); > + > + free(expanded); > + > + return res ?: xstrdup(""); > +} > + > +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(int argc, char *argv[]) > +{ > + static const char *pre = "("; > + static const char *post = ") >/dev/null 2>&1"; > + char *cmd; > + int ret; > + > + if (argc != 2) > + return NULL; > + > + /* > + * Surround the command with ( ) in case it is piped commands. > + * Also, redirect stdout and stderr to /dev/null. > + */ > + cmd = xmalloc(strlen(pre) + strlen(argv[1]) + strlen(post) + 1); > + strcpy(cmd, pre); > + strcat(cmd, argv[1]); > + strcat(cmd, post); > + > + ret = system(cmd); > + > + free(cmd); > + > + return xstrdup(ret == 0 ? "y" : "n"); > +} > + > +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 dddf85b..ed89fb9 100644 > --- a/scripts/kconfig/util.c > +++ b/scripts/kconfig/util.c > @@ -93,9 +93,10 @@ static char *env_expand_n(const char *name, size_t n) > } > > /* > - * 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) > { > @@ -113,11 +114,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++; > + } > + } 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.l b/scripts/kconfig/zconf.l > index 0d89ea6..f433ab0 100644 > --- a/scripts/kconfig/zconf.l > +++ b/scripts/kconfig/zconf.l > @@ -1,7 +1,7 @@ > %option nostdinit noyywrap never-interactive full ecs > %option 8bit nodefault perf-report perf-report > %option noinput > -%x COMMAND HELP STRING PARAM > +%x COMMAND HELP STRING PARAM FUNCTION > %{ > /* > * Copyright (C) 2002 Roman Zippel > @@ -25,6 +25,7 @@ static struct { > > static char *text; > static int text_size, text_asize; > +static int function_nest; > > struct buffer { > struct buffer *parent; > @@ -138,6 +139,12 @@ n [$A-Za-z0-9_-] > new_string(); > BEGIN(STRING); > } > + "$(" { > + new_string(); > + append_string(yytext, yyleng); > + function_nest = 0; > + BEGIN(FUNCTION); > + } > \n BEGIN(INITIAL); current_file->lineno++; return T_EOL; > ({n}|[/.])+ { > const struct kconf_id *id = kconf_id_lookup(yytext, yyleng); > @@ -196,6 +203,35 @@ n [$A-Za-z0-9_-] > } > } > > +{ > + [^()\n]* { > + append_string(yytext, yyleng); > + } > + "(" { > + append_string(yytext, yyleng); > + function_nest++; > + } > + ")" { > + append_string(yytext, yyleng); > + if (function_nest-- == 0) { > + BEGIN(PARAM); > + yylval.string = text; > + return T_WORD; T_WORD_QUOTE (which would turn into a constant symbol in most contexts) would be better here, IMO. That would turn $(foo) into just an alias for "$(foo)". See below. > + } > + } > + \n { > + fprintf(stderr, > + "%s:%d:warning: multi-line function not supported\n", > + zconf_curname(), zconf_lineno()); > + current_file->lineno++; > + BEGIN(INITIAL); > + return T_EOL; > + } > + <> { > + BEGIN(INITIAL); > + } > +} > + > { > [ \t]+ { > ts = 0; > diff --git a/scripts/kconfig/zconf.y b/scripts/kconfig/zconf.y > index 784083d..d9977de 100644 > --- a/scripts/kconfig/zconf.y > +++ b/scripts/kconfig/zconf.y > @@ -534,11 +534,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) > @@ -778,4 +786,5 @@ void zconfdump(FILE *out) > #include "confdata.c" > #include "expr.c" > #include "symbol.c" > +#include "function.c" > #include "menu.c" > -- > 2.7.4 > Here's a simplification idea I'm throwing out there: What about only allowing $ENV and $() within quotes, and just having them always do simple text substitution (so that they'd indirectly always generate T_WORD_QUOTE tokens)? Pros: - Zero new syntax outside of strings (until the macro stuff). - Makes the behavior and limitations of the syntax obvious: You can't have $(foo) expand to a full expression, only to (possibly part of) a value. This is a good limitation, IMO, and it's already there. - Super simple and straightforward Kconfig implementation. All the new magic would happen in expand_string_value(). Neutral: - Just as general where it matters. Take something like default "$(foo)" If $(foo) happens to expand to "y", then that will do its usual thing for a bool/tristate symbol. Same for string symbols, etc. It'd just be a thin preprocessing step on constant symbol values. - The only loss in generality would be that you can no longer have a function expand to the name of non-constant symbol. For example, take the following: default $(foo) If $(foo) expands to MY_SYMBOL, then that would work as default MY_SYMBOL (I.e., it would default to the value of the symbol MY_SYMBOL.) With the quotes, it would instead work as default "MY_SYMBOL" IMO, the second version is less confusing, and deliberately using the first version seems like a bad idea (there's likely to be cleaner ways to do the indirection in plain Kconfig). This is also why I think T_WORD_QUOTE is better in the code above. It would make $(foo) work more like a shorthand for "$(foo)". Cons: - Bit more typing to add the quotes - Maybe it isn't widely known that "n", "m", "y" work just like n, m, y for bool and tristate symbols (the latter automatically get converted to the former), though you see them quite a lot in Kconfig files. ("n", "foo bar", etc., are all just constant symbols. Kconfig keeps track of them separately from non-constant symbols. The constant symbols "n", "m", and "y" are predefined.) If we go with obligatory quotes, I volunteer to explain things in kconfig-language.txt at least, to make it clear why you'd see quotes in bool/tristate symbols using $(shell). I suspect it wouldn't be that tricky to figure out anyway. What do you think? Cheers, Ulf