I found myself repeatedly missing the ability to create a minimal
defconfig file, as one can in linux and U-Boot with 'make
savedefconfig'.

Having such a minimal defconfig file is for example useful when using
the "base config + fragments" approach for the configuration (often
using the merge_config.sh helper script). Currently, if one has a base
config file in the form of a full .config file, and one then enables
some applet FOO with a fragment that just says "CONFIG_FOO=y", one
doesn't automatically get any of the options gated on FOO,
e.g. CONFIG_FEATURE_FOO_LONG_OPTS, because the base config file has an
explicit "# CONFIG_FEATURE_FOO_LONG_OPTS is not set" - even if those
options are "default y".

Conversely, if the base config file merely contains "# CONFIG_FOO is
not set", and merge_config.sh changes that to "CONFIG_FOO=y", a
subsequent "yes '' | make oldconfig" will fill in all the dependent
options with their defaults.

The implementation is mostly done by copying commit
7cf3d73b436 ("kconfig: add savedefconfig") from the linux kernel,
adapting that commit to the relatively few differences that had grown
between linux and busybox kbuild at that time.

In order to reduce the risk of this introducing any regressions, I
deliberately opted not to do any refactorizations of existing
code. For example, in linux, one of the preparatory
patches (c252147de68c) added sym_choice_default() and rewrote
sym_calc_choice() using that; I left busybox' sym_calc_choice() alone
and just added sym_choice_default() itself. That strategy means that,
apart from the obviously necessary wiring up in main(), this is pure
code addition, so I do believe this should be quite safe.

As for correctnes, I've done 100 tests where I do

  make randconfig
  make savedefconfig
  mv defconfig .config
  yes '' | make oldconfig

and checked that the new resulting .config is identical to the one
from the randconfig run. I have also done that round-trip test for a
few "real-life" .config files.

Signed-off-by: Rasmus Villemoes <r...@prevas.dk>
---
 Makefile.help               |   1 +
 scripts/kconfig/Makefile    |   3 +
 scripts/kconfig/conf.c      |  19 ++++-
 scripts/kconfig/confdata.c  | 134 ++++++++++++++++++++++++++++++++++++
 scripts/kconfig/expr.h      |   3 +
 scripts/kconfig/lkc.h       |   2 +
 scripts/kconfig/lkc_proto.h |   1 +
 scripts/kconfig/symbol.c    | 106 ++++++++++++++++++++++++++++
 8 files changed, 268 insertions(+), 1 deletion(-)

diff --git a/Makefile.help b/Makefile.help
index 6a23e2a80..7fbf16a2b 100644
--- a/Makefile.help
+++ b/Makefile.help
@@ -21,6 +21,7 @@ help:
        @echo '  defconfig              - set .config to largest generic 
configuration'
        @echo '  menuconfig             - interactive curses-based configurator'
        @echo '  oldconfig              - resolve any unresolved symbols in 
.config'
+       @echo '  savedefconfig          - Save current config as ./defconfig 
(minimal config)'
        @$(if $(boards), \
                $(foreach b, $(boards), \
                printf "  %-21s - Build for %s\\n" $(b) $(subst 
_defconfig,,$(b));) \
diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile
index 38bae809a..4b38dd69f 100644
--- a/scripts/kconfig/Makefile
+++ b/scripts/kconfig/Makefile
@@ -77,6 +77,9 @@ allmodconfig: $(obj)/conf
        $< -m Config.in
        $(MTIME_IS_COARSE) && sleep 1
 
+savedefconfig: $(obj)/conf
+       $< -S Config.in
+
 defconfig: $(obj)/conf
 ifeq ($(KBUILD_DEFCONFIG),)
        $< -d Config.in
diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c
index 39ec1cdb6..794f39b8b 100644
--- a/scripts/kconfig/conf.c
+++ b/scripts/kconfig/conf.c
@@ -27,7 +27,8 @@ enum {
        set_yes,
        set_mod,
        set_no,
-       set_random
+       set_random,
+       save_def,
 } input_mode = ask_all;
 char *defconfig_file;
 
@@ -415,6 +416,9 @@ static int conf_choice(struct menu *menu)
                        cnt = def;
                        printf("%d\n", cnt);
                        break;
+               case save_def:
+                       /* Doesn't happen. */
+                       break;
                }
 
        conf_childs:
@@ -553,6 +557,10 @@ int main(int ac, char **av)
                                exit(1);
                        }
                        break;
+               case 'S':
+                       input_mode = save_def;
+                       defconfig_file = "defconfig";
+                       break;
                case 'n':
                        input_mode = set_no;
                        break;
@@ -601,6 +609,7 @@ int main(int ac, char **av)
                }
        case ask_all:
        case ask_new:
+       case save_def:
                conf_read(NULL);
                break;
        case set_no:
@@ -628,6 +637,14 @@ int main(int ac, char **av)
                break;
        }
 
+       if (input_mode == save_def) {
+               if (conf_write_defconfig(defconfig_file)) {
+                       fprintf(stderr, _("n*** Error while saving defconfig 
to: %s\n\n"),
+                               defconfig_file);
+                       return 1;
+               }
+               return 0;
+       }
        if (input_mode != ask_silent) {
                rootEntry = &rootmenu;
                conf(&rootmenu);
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
index 249a3195e..047ebf917 100644
--- a/scripts/kconfig/confdata.c
+++ b/scripts/kconfig/confdata.c
@@ -600,3 +600,137 @@ int conf_write(const char *name)
 
        return 0;
 }
+
+/* Write a S_STRING */
+static void conf_write_string(bool headerfile, const char *name,
+                              const char *str, FILE *out)
+{
+       int l;
+       if (headerfile)
+               fprintf(out, "#define CONFIG_%s \"", name);
+       else
+               fprintf(out, "CONFIG_%s=\"", name);
+
+       while (1) {
+               l = strcspn(str, "\"\\");
+               if (l) {
+                       fwrite(str, l, 1, out);
+                       str += l;
+               }
+               if (!*str)
+                       break;
+               fprintf(out, "\\%c", *str++);
+       }
+       fputs("\"\n", out);
+}
+
+static void conf_write_symbol(struct symbol *sym, enum symbol_type type,
+                              FILE *out, bool write_no)
+{
+       const char *str;
+
+       switch (type) {
+       case S_BOOLEAN:
+       case S_TRISTATE:
+               switch (sym_get_tristate_value(sym)) {
+               case no:
+                       if (write_no)
+                               fprintf(out, "# CONFIG_%s is not set\n", 
sym->name);
+                       break;
+               case mod:
+                       fprintf(out, "CONFIG_%s=m\n", sym->name);
+                       break;
+               case yes:
+                       fprintf(out, "CONFIG_%s=y\n", sym->name);
+                       break;
+               }
+               break;
+       case S_STRING:
+               conf_write_string(false, sym->name, sym_get_string_value(sym), 
out);
+               break;
+       case S_HEX:
+       case S_INT:
+               str = sym_get_string_value(sym);
+               fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
+               break;
+       case S_OTHER:
+       case S_UNKNOWN:
+               break;
+       }
+}
+
+/*
+ * Write out a minimal config.
+ * All values that has default values are skipped as this is redundant.
+ */
+int conf_write_defconfig(const char *filename)
+{
+       struct symbol *sym;
+       struct menu *menu;
+       FILE *out;
+
+       out = fopen(filename, "w");
+       if (!out)
+               return 1;
+
+       sym_clear_all_valid();
+
+       /* Traverse all menus to find all relevant symbols */
+       menu = rootmenu.list;
+
+       while (menu != NULL)
+       {
+               sym = menu->sym;
+               if (sym == NULL) {
+                       if (!menu_is_visible(menu))
+                               goto next_menu;
+               } else if (!sym_is_choice(sym)) {
+                       sym_calc_value(sym);
+                       if (!(sym->flags & SYMBOL_WRITE))
+                               goto next_menu;
+                       sym->flags &= ~SYMBOL_WRITE;
+                       /* If we cannot change the symbol - skip */
+                       if (!sym_is_changable(sym))
+                               goto next_menu;
+                       /* If symbol equals to default value - skip */
+                       if (strcmp(sym_get_string_value(sym), 
sym_get_string_default(sym)) == 0)
+                               goto next_menu;
+
+                       /*
+                        * If symbol is a choice value and equals to the
+                        * default for a choice - skip.
+                        * But only if value equal to "y".
+                        */
+                       if (sym_is_choice_value(sym)) {
+                               struct symbol *cs;
+                               struct symbol *ds;
+
+                               cs = prop_get_symbol(sym_get_choice_prop(sym));
+                               ds = sym_choice_default(cs);
+                               if (sym == ds) {
+                                       if ((sym->type == S_BOOLEAN ||
+                                            sym->type == S_TRISTATE) &&
+                                           sym_get_tristate_value(sym) == yes)
+                                               goto next_menu;
+                               }
+                       }
+                       conf_write_symbol(sym, sym->type, out, true);
+               }
+       next_menu:
+               if (menu->list != NULL) {
+                       menu = menu->list;
+               }
+               else if (menu->next != NULL) {
+                       menu = menu->next;
+               } else {
+                       while ((menu = menu->parent)) {
+                               if (menu->next != NULL) {
+                                       menu = menu->next;
+                                       break;
+                               }
+                       }
+               }
+       }
+       fclose(out);
+       return 0;
+}
diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h
index 1b36ef18c..ae97ee0a7 100644
--- a/scripts/kconfig/expr.h
+++ b/scripts/kconfig/expr.h
@@ -49,6 +49,9 @@ struct expr {
 #define E_AND(dep1, dep2)      (((dep1)<(dep2))?(dep1):(dep2))
 #define E_NOT(dep)             (2-(dep))
 
+#define expr_list_for_each_sym(l, e, s) \
+       for (e = (l); e && (s = e->right.sym); e = e->left.expr)
+
 struct expr_value {
        struct expr *expr;
        tristate tri;
diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h
index 527f60c99..03aca8534 100644
--- a/scripts/kconfig/lkc.h
+++ b/scripts/kconfig/lkc.h
@@ -100,6 +100,8 @@ const char *str_get(struct gstr *gs);
 void sym_init(void);
 void sym_clear_all_valid(void);
 void sym_set_changed(struct symbol *sym);
+struct symbol *sym_choice_default(struct symbol *sym);
+const char *sym_get_string_default(struct symbol *sym);
 struct symbol *sym_check_deps(struct symbol *sym);
 struct property *prop_alloc(enum prop_type type, struct symbol *sym);
 struct symbol *prop_get_symbol(struct property *prop);
diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h
index b6a389c5f..c6b0e4c92 100644
--- a/scripts/kconfig/lkc_proto.h
+++ b/scripts/kconfig/lkc_proto.h
@@ -3,6 +3,7 @@
 P(conf_parse,void,(const char *name));
 P(conf_read,int,(const char *name));
 P(conf_read_simple,int,(const char *name));
+P(conf_write_defconfig,int,(const char *name));
 P(conf_write,int,(const char *name));
 
 /* menu.c */
diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c
index 3d7877afc..a349b721c 100644
--- a/scripts/kconfig/symbol.c
+++ b/scripts/kconfig/symbol.c
@@ -220,6 +220,38 @@ static void sym_calc_visibility(struct symbol *sym)
        }
 }
 
+/*
+ * Find the default symbol for a choice.
+ * First try the default values for the choice symbol
+ * Next locate the first visible choice value
+ * Return NULL if none was found
+ */
+struct symbol *sym_choice_default(struct symbol *sym)
+{
+       struct symbol *def_sym;
+       struct property *prop;
+       struct expr *e;
+
+        /* any of the defaults visible? */
+       for_all_defaults(sym, prop) {
+               prop->visible.tri = expr_calc_value(prop->visible.expr);
+               if (prop->visible.tri == no)
+                       continue;
+               def_sym = prop_get_symbol(prop);
+               if (def_sym->visible != no)
+                       return def_sym;
+       }
+
+       /* just get the first visible value */
+       prop = sym_get_choice_prop(sym);
+       expr_list_for_each_sym(prop->expr, e, def_sym)
+               if (def_sym->visible != no)
+                       return def_sym;
+       /* failed to locate any defaults */
+       return NULL;
+}
+
+
 static struct symbol *sym_calc_choice(struct symbol *sym)
 {
        struct symbol *def_sym;
@@ -615,6 +647,80 @@ bool sym_set_string_value(struct symbol *sym, const char 
*newval)
        return true;
 }
 
+/*
+ * Find the default value associated to a symbol.
+ * For tristate symbol handle the modules=n case
+ * in which case "m" becomes "y".
+ * If the symbol does not have any default then fallback
+ * to the fixed default values.
+ */
+const char *sym_get_string_default(struct symbol *sym)
+{
+       struct property *prop;
+       struct symbol *ds;
+       const char *str;
+       tristate val;
+
+       sym_calc_visibility(sym);
+       sym_calc_value(modules_sym);
+       val = symbol_no.curr.tri;
+       str = symbol_empty.curr.val;
+
+       /* If symbol has a default value look it up */
+       prop = sym_get_default_prop(sym);
+       if (prop != NULL) {
+               switch (sym->type) {
+               case S_BOOLEAN:
+               case S_TRISTATE:
+                       /* The visibility imay limit the value from yes => mod 
*/
+                       val = E_AND(expr_calc_value(prop->expr), 
prop->visible.tri);
+                       break;
+               default:
+                       /*
+                        * The following fails to handle the situation
+                        * where a default value is further limited by
+                        * the valid range.
+                        */
+                       ds = prop_get_symbol(prop);
+                       if (ds != NULL) {
+                               sym_calc_value(ds);
+                               str = (const char *)ds->curr.val;
+                       }
+               }
+       }
+
+       /* Handle select statements */
+       val = E_OR(val, sym->rev_dep.tri);
+
+       /* transpose mod to yes if modules are not enabled */
+       if (val == mod)
+               if (!sym_is_choice_value(sym) && modules_sym->curr.tri == no)
+                       val = yes;
+
+       /* transpose mod to yes if type is bool */
+       if (sym->type == S_BOOLEAN && val == mod)
+               val = yes;
+
+       switch (sym->type) {
+       case S_BOOLEAN:
+       case S_TRISTATE:
+               switch (val) {
+               case no: return "n";
+               case mod: return "m";
+               case yes: return "y";
+               }
+       case S_INT:
+       case S_HEX:
+               return str;
+       case S_STRING:
+               return str;
+       case S_OTHER:
+       case S_UNKNOWN:
+               break;
+       }
+       return "";
+}
+
 const char *sym_get_string_value(struct symbol *sym)
 {
        tristate val;
-- 
2.50.1

_______________________________________________
busybox mailing list
busybox@busybox.net
https://lists.busybox.net/mailman/listinfo/busybox

Reply via email to