summaryrefslogtreecommitdiff
path: root/scripts/kconfig
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/kconfig')
-rw-r--r--scripts/kconfig/conf.c23
-rw-r--r--scripts/kconfig/confdata.c106
-rw-r--r--scripts/kconfig/expr.c33
-rw-r--r--scripts/kconfig/expr.h5
-rwxr-xr-xscripts/kconfig/kconfig-sym-check.pl132
-rw-r--r--scripts/kconfig/lexer.l4
-rw-r--r--scripts/kconfig/lkc.h3
-rw-r--r--scripts/kconfig/menu.c10
-rwxr-xr-xscripts/kconfig/merge_config.sh26
-rw-r--r--scripts/kconfig/parser.y15
-rw-r--r--scripts/kconfig/tests/conftest.py8
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/Kconfig3
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/Kconfig.inc14
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/Kconfig.inc23
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/Kconfig.inc31
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/__init__.py10
-rw-r--r--scripts/kconfig/tests/err_repeated_inc/expected_stderr2
-rw-r--r--scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py2
-rw-r--r--scripts/kconfig/tests/warn_changed_input/Kconfig40
-rw-r--r--scripts/kconfig/tests/warn_changed_input/__init__.py33
-rw-r--r--scripts/kconfig/tests/warn_changed_input/config3
-rw-r--r--scripts/kconfig/tests/warn_changed_input/expected_config6
-rw-r--r--scripts/kconfig/tests/warn_changed_input/expected_defconfig1
-rw-r--r--scripts/kconfig/tests/warn_changed_input/expected_stderr4
-rw-r--r--scripts/kconfig/util.c31
25 files changed, 463 insertions, 45 deletions
diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c
index a7b44cd8ae14..fe8ba09b0039 100644
--- a/scripts/kconfig/conf.c
+++ b/scripts/kconfig/conf.c
@@ -297,9 +297,7 @@ static int conf_askvalue(struct symbol *sym, const char *def)
line[1] = 0;
if (!sym_is_changeable(sym)) {
- printf("%s\n", def);
- line[0] = '\n';
- line[1] = 0;
+ printf("%s\n", def ?: "");
return 0;
}
@@ -307,7 +305,7 @@ static int conf_askvalue(struct symbol *sym, const char *def)
case oldconfig:
case syncconfig:
if (sym_has_value(sym)) {
- printf("%s\n", def);
+ printf("%s\n", def ?: "");
return 0;
}
/* fall through */
@@ -350,6 +348,23 @@ static int conf_string(struct menu *menu)
}
if (def && sym_set_string_value(sym, def))
return 0;
+
+ /*
+ * A new int or hex symbol whose default fails validation
+ * cannot be set from an empty answer. When standard input is
+ * exhausted, as it is for a non-interactive oldconfig or
+ * syncconfig, re-asking would loop forever and grow the output
+ * until it exhausts memory. Stop with an error that names the
+ * symbol instead. String symbols accept any text, and bool and
+ * tristate symbols (conf_sym()) and choices (conf_choice())
+ * accept the default on an empty line, so they are unaffected.
+ */
+ if (feof(stdin)) {
+ fprintf(stderr,
+ "\nerror: no value for new symbol '%s' at end of input\n",
+ sym->name);
+ exit(1);
+ }
}
}
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
index 9599a0408862..4234a51d16fd 100644
--- a/scripts/kconfig/confdata.c
+++ b/scripts/kconfig/confdata.c
@@ -206,6 +206,78 @@ static void conf_message(const char *fmt, ...)
va_end(ap);
}
+static void conf_changed_input_warning(const char *s)
+{
+ fputs(s, stderr);
+}
+
+static bool conf_warn_changed_input_enabled(void)
+{
+ const char *env = getenv("KCONFIG_WARN_CHANGED_INPUT");
+
+ return env && *env;
+}
+
+static const char *sym_get_user_value_string(struct symbol *sym)
+{
+ switch (sym->type) {
+ case S_BOOLEAN:
+ case S_TRISTATE:
+ switch (sym->def[S_DEF_USER].tri) {
+ case yes:
+ return "y";
+ case mod:
+ return "m";
+ default:
+ return "n";
+ }
+ default:
+ return sym->def[S_DEF_USER].val ?: "";
+ }
+}
+
+static bool sym_user_value_changed(struct symbol *sym)
+{
+ if (!sym_has_value(sym) || sym->type == S_UNKNOWN)
+ return false;
+
+ switch (sym->type) {
+ case S_BOOLEAN:
+ case S_TRISTATE:
+ return sym->def[S_DEF_USER].tri != sym_get_tristate_value(sym);
+ default:
+ return strcmp(sym_get_user_value_string(sym),
+ sym_get_string_value(sym));
+ }
+}
+
+static void conf_clear_written_flags(void)
+{
+ struct symbol *sym;
+
+ for_all_symbols(sym)
+ sym->flags &= ~SYMBOL_WRITTEN;
+}
+
+static void conf_append_changed_input_warning(struct gstr *gs,
+ struct symbol *sym,
+ bool *changed_input_found)
+{
+ if (!sym_user_value_changed(sym))
+ return;
+
+ if (!*changed_input_found) {
+ str_printf(gs,
+ "warning: user-provided values changed by Kconfig:\n");
+ *changed_input_found = true;
+ }
+
+ str_printf(gs, " %s%s: %s -> %s\n",
+ CONFIG_, sym->name,
+ sym_get_user_value_string(sym),
+ sym_get_string_value(sym));
+}
+
const char *conf_get_configname(void)
{
char *name = getenv("KCONFIG_CONFIG");
@@ -759,11 +831,15 @@ int conf_write_defconfig(const char *filename)
{
struct symbol *sym;
struct menu *menu;
+ struct gstr gs;
FILE *out;
+ bool warn_changed_input = conf_warn_changed_input_enabled();
+ bool changed_input_found = false;
out = fopen(filename, "w");
if (!out)
return 1;
+ gs = str_new();
sym_clear_all_valid();
@@ -772,10 +848,14 @@ int conf_write_defconfig(const char *filename)
sym = menu->sym;
- if (!sym || sym_is_choice(sym))
+ if (!sym || sym_is_choice(sym) || sym->flags & SYMBOL_WRITTEN)
continue;
sym_calc_value(sym);
+ if (warn_changed_input)
+ conf_append_changed_input_warning(&gs, sym,
+ &changed_input_found);
+ sym->flags |= SYMBOL_WRITTEN;
if (!(sym->flags & SYMBOL_WRITE))
continue;
sym->flags &= ~SYMBOL_WRITE;
@@ -798,6 +878,13 @@ int conf_write_defconfig(const char *filename)
print_symbol_for_dotconfig(out, sym);
}
fclose(out);
+
+ conf_clear_written_flags();
+
+ if (changed_input_found)
+ conf_changed_input_warning(str_get(&gs));
+
+ str_free(&gs);
return 0;
}
@@ -809,7 +896,10 @@ int conf_write(const char *name)
const char *str;
char tmpname[PATH_MAX + 1], oldname[PATH_MAX + 1];
char *env;
+ struct gstr gs;
bool need_newline = false;
+ bool warn_changed_input = conf_warn_changed_input_enabled();
+ bool changed_input_found = false;
if (!name)
name = conf_get_configname();
@@ -838,6 +928,7 @@ int conf_write(const char *name)
}
if (!out)
return 1;
+ gs = str_new();
conf_write_heading(out, &comment_style_pound);
@@ -859,13 +950,16 @@ int conf_write(const char *name)
} else if (!sym_is_choice(sym) &&
!(sym->flags & SYMBOL_WRITTEN)) {
sym_calc_value(sym);
+ if (warn_changed_input)
+ conf_append_changed_input_warning(&gs, sym,
+ &changed_input_found);
+ sym->flags |= SYMBOL_WRITTEN;
if (!(sym->flags & SYMBOL_WRITE))
goto next;
if (need_newline) {
fprintf(out, "\n");
need_newline = false;
}
- sym->flags |= SYMBOL_WRITTEN;
print_symbol_for_dotconfig(out, sym);
}
@@ -892,8 +986,12 @@ end_check:
}
fclose(out);
- for_all_symbols(sym)
- sym->flags &= ~SYMBOL_WRITTEN;
+ conf_clear_written_flags();
+
+ if (changed_input_found)
+ conf_changed_input_warning(str_get(&gs));
+
+ str_free(&gs);
if (*tmpname) {
if (is_same(name, tmpname)) {
diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c
index 16f92c4a775a..2b91d16bf14f 100644
--- a/scripts/kconfig/expr.c
+++ b/scripts/kconfig/expr.c
@@ -738,6 +738,39 @@ bool expr_contains_symbol(struct expr *dep, struct symbol *sym)
return false;
}
+/*
+ * Check if the expression references 'sym' in a way that is satisfiable
+ * with 'sym' disabled, e.g.'sym!=y'.
+ *
+ * Expects that expr_transform() was already called on 'expr'.
+ */
+bool expr_contains_symbol_negated(struct expr *dep, struct symbol *sym)
+{
+ if (!dep)
+ return false;
+
+ switch (dep->type) {
+ case E_AND:
+ case E_OR:
+ return expr_contains_symbol_negated(dep->left.expr, sym) ||
+ expr_contains_symbol_negated(dep->right.expr, sym);
+ case E_NOT:
+ return dep->left.expr->type == E_SYMBOL &&
+ dep->left.expr->left.sym == sym;
+ case E_EQUAL:
+ /* sym=n */
+ return dep->left.sym == sym && dep->right.sym == &symbol_no;
+ case E_UNEQUAL:
+ /* sym!=y, sym!=m */
+ return dep->left.sym == sym &&
+ (dep->right.sym == &symbol_yes ||
+ dep->right.sym == &symbol_mod);
+ default:
+ break;
+ }
+ return false;
+}
+
bool expr_depends_symbol(struct expr *dep, struct symbol *sym)
{
if (!dep)
diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h
index 5f900d18dae0..b580f9fa0f29 100644
--- a/scripts/kconfig/expr.h
+++ b/scripts/kconfig/expr.h
@@ -38,7 +38,7 @@ union expr_data {
* struct expr - expression
*
* @node: link node for the hash table
- * @type: expressoin type
+ * @type: expression type
* @val: calculated tristate value
* @val_is_valid: indicate whether the value is valid
* @left: left node
@@ -160,7 +160,7 @@ struct symbol {
#define SYMBOL_MAXLENGTH 256
-/* A property represent the config options that can be associated
+/* A property represents the config options that can be associated
* with a config "symbol".
* Sample:
* config FOO
@@ -307,6 +307,7 @@ tristate expr_calc_value(struct expr *e);
struct expr *expr_eliminate_dups(struct expr *e);
struct expr *expr_transform(struct expr *e);
bool expr_contains_symbol(struct expr *dep, struct symbol *sym);
+bool expr_contains_symbol_negated(struct expr *dep, struct symbol *sym);
bool expr_depends_symbol(struct expr *dep, struct symbol *sym);
struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym);
diff --git a/scripts/kconfig/kconfig-sym-check.pl b/scripts/kconfig/kconfig-sym-check.pl
new file mode 100755
index 000000000000..daa5285fdefc
--- /dev/null
+++ b/scripts/kconfig/kconfig-sym-check.pl
@@ -0,0 +1,132 @@
+#!/usr/bin/env perl
+# SPDX-License-Identifier: GPL-2.0
+
+use warnings;
+use strict;
+
+my $srctree = shift @ARGV;
+unless (defined $srctree) {
+ $srctree = `git rev-parse --show-toplevel 2>/dev/null`;
+ chomp $srctree;
+ my $msg = "Usage: $0 <srctree> [excludes file]\n";
+ $msg .= "Please provide <srctree>.";
+ $msg .= " Is it '$srctree'?" if $srctree;
+ $msg .= "\n";
+ die $msg;
+}
+my $kconfig_sym_check_excludes = defined $ARGV[0] ? $ARGV[0] : undef;
+
+sub indent_depth {
+ my ($ws) = @_;
+ my $col = 0;
+ for my $c (split //, $ws) {
+ $col = $c eq "\t" ? int($col / 8) * 8 + 8 : $col + 1;
+ }
+ return $col;
+}
+
+my @files = `git -C \Q$srctree\E ls-files '*Kconfig*' 2>/dev/null`;
+if (@files) {
+ chomp @files;
+ @files = map { "$srctree/$_" } @files;
+} else {
+ @files = `find \Q$srctree\E -name '*Kconfig*'`;
+ chomp @files;
+}
+
+@files = grep { !m{/scripts/kconfig/tests/} } @files;
+
+my %configs = ();
+my %refs = ();
+
+foreach my $file (@files) {
+ open F, $file or die "Cannot open $file: $!";
+
+ my $help = 0;
+ my $help_level;
+ my $level;
+
+ while (<F>) {
+ chomp;
+
+ while (/\\\s*$/) {
+ s/\\\s*$/ /;
+ my $cont = <F> // last;
+ chomp $cont;
+ $_ .= $cont;
+ }
+
+ next if /^\s*$/;
+ next if /^\s*#/;
+
+ /^(\s*)/;
+ $level = indent_depth($1);
+
+ if ($help && $level < $help_level) {
+ $help = 0;
+ }
+
+ next if ($help);
+
+ if (/^\s*(help|\-\-\-help\-\-\-)$/) {
+ $help = 1;
+ my $next;
+ while (defined($next = <F>)) {
+ last unless $next =~ /^\s*(?:#.*)?$/;
+ }
+ last unless defined $next;
+ $next =~ /^(\s*)/;
+ if (indent_depth($1) >= $level) {
+ $help_level = indent_depth($1);
+ } else {
+ $help = 0;
+ }
+ $_ = $next;
+ redo;
+ }
+
+ if (/^\s*(config|menuconfig)\s+([a-zA-Z0-9_]+)\s*(#.*)?$/) {
+ $configs{$2}++;
+ next;
+ }
+
+ if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) {
+ my $s = $2;
+ $s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g;
+ $s =~ s/#.*//;
+ $s =~ s/\$\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\)//g;
+ $s =~ s/%%[^%]*%%//g;
+ my @syms = split /[^a-zA-Z0-9_]+/, $s;
+ map {
+ $refs{$_}++ if (/[a-zA-Z]/ && $_ ne "if" && $_ ne "y" && $_ ne "n" && $_ ne "m" && !/^0[xX][0-9a-fA-F]+$/);
+ } @syms
+ }
+ }
+
+ close F;
+}
+
+my %known_syms = ();
+if (defined $kconfig_sym_check_excludes) {
+ my $file = $kconfig_sym_check_excludes;
+ open(F, "<", $file) or die "Cannot open $file: $!";
+ while (<F>) {
+ chomp;
+ next if /^\s*$/;
+ next if /^\s*#/;
+ $known_syms{$1}++ if (/^\s*([a-zA-Z0-9_]+)\s*(#.*)?$/);
+ }
+}
+
+my $ret = 0;
+foreach my $k (sort keys %refs) {
+ next if (exists $configs{$k} || exists $known_syms{$k});
+
+ print "$k";
+ print " - warning: '$k' is probably not what you want; Kconfig tristate literals are always lowercase ('n', 'y', 'm')" if ($k eq "N" || $k eq "Y" || $k eq "M");
+ print "\n";
+
+ $ret = 1;
+}
+
+exit $ret;
diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l
index 6d2c92c6095d..a6155422b4a6 100644
--- a/scripts/kconfig/lexer.l
+++ b/scripts/kconfig/lexer.l
@@ -402,7 +402,7 @@ void zconf_initscan(const char *name)
exit(1);
}
- cur_filename = file_lookup(name);
+ cur_filename = file_lookup(name, NULL, 0);
yylineno = 1;
}
@@ -443,7 +443,7 @@ void zconf_nextfile(const char *name)
}
yylineno = 1;
- cur_filename = file_lookup(name);
+ cur_filename = file_lookup(name, cur_filename, cur_lineno);
}
static void zconf_endfile(void)
diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h
index 798985961215..7e6f6ca299cf 100644
--- a/scripts/kconfig/lkc.h
+++ b/scripts/kconfig/lkc.h
@@ -51,7 +51,8 @@ static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out)
}
/* util.c */
-const char *file_lookup(const char *name);
+const char *file_lookup(const char *name,
+ const char *parent_name, int parent_lineno);
/* lexer.l */
int yylex(void);
diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c
index b2d8d4e11e07..9c079e92a9ed 100644
--- a/scripts/kconfig/menu.c
+++ b/scripts/kconfig/menu.c
@@ -428,9 +428,19 @@ static void _menu_finalize(struct menu *parent, bool inside_choice)
if (!expr_contains_symbol(dep, sym))
/* No dependency, quit */
break;
+ /*
+ * Note that it's actually possible to depend on both
+ * 'SYM!=y' and 'SYM=y', so we need to first check if
+ * it's a positive dependency before checking if it's
+ * a negative dependency. See example:
+ * 'SFC && MTD && !(SFC=y && MTD=m)'
+ */
if (expr_depends_symbol(dep, sym))
/* Absolute dependency, put in submenu */
goto next;
+ if (expr_contains_symbol_negated(dep, sym))
+ /* Negative dependency, quit */
+ break;
/*
* Also consider it a dependency on sym if our
diff --git a/scripts/kconfig/merge_config.sh b/scripts/kconfig/merge_config.sh
index 735e1de450c6..ec242e03f509 100755
--- a/scripts/kconfig/merge_config.sh
+++ b/scripts/kconfig/merge_config.sh
@@ -122,7 +122,7 @@ fi
MERGE_LIST=$*
-TMP_FILE=$(mktemp ./.tmp.config.XXXXXXXXXX)
+TMP_FILE=$(mktemp --tmpdir="$OUTPUT" .tmp.config.XXXXXXXXXX)
echo "Using $INITFILE as base"
@@ -151,6 +151,7 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do
if ! "$AWK" -v prefix="$CONFIG_PREFIX" \
-v warnoverride="$WARNOVERRIDE" \
-v strict="$STRICT" \
+ -v outfile="$TMP_FILE.new" \
-v builtin="$BUILTIN" \
-v warnredun="$WARNREDUN" '
BEGIN {
@@ -195,7 +196,7 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do
# First pass: read merge file, store all lines and index
FILENAME == ARGV[1] {
- mergefile = FILENAME
+ mergefile = FILENAME
merge_lines[FNR] = $0
merge_total = FNR
cfg = get_cfg($0)
@@ -212,17 +213,17 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do
# Not a config or not in merge file - keep it
if (cfg == "" || !(cfg in merge_cfg)) {
- print $0 >> ARGV[3]
+ print $0 >> outfile
next
}
- prev_val = $0
+ prev_val = $0
new_val = merge_cfg[cfg]
# BUILTIN: do not demote y to m
if (builtin == "true" && new_val ~ /=m$/ && prev_val ~ /=y$/) {
warn_builtin(cfg, prev_val, new_val)
- print $0 >> ARGV[3]
+ print $0 >> outfile
skip_merge[merge_cfg_line[cfg]] = 1
next
}
@@ -235,7 +236,7 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do
# "=n" is the same as "is not set"
if (prev_val ~ /=n$/ && new_val ~ / is not set$/) {
- print $0 >> ARGV[3]
+ print $0 >> outfile
next
}
@@ -246,25 +247,20 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do
}
}
- # output file, skip all lines
- FILENAME == ARGV[3] {
- nextfile
- }
-
END {
# Newline in case base file lacks trailing newline
- print "" >> ARGV[3]
+ print "" >> outfile
# Append merge file, skipping lines marked for builtin preservation
for (i = 1; i <= merge_total; i++) {
if (!(i in skip_merge)) {
- print merge_lines[i] >> ARGV[3]
+ print merge_lines[i] >> outfile
}
}
if (strict_violated) {
exit 1
}
}' \
- "$ORIG_MERGE_FILE" "$TMP_FILE" "$TMP_FILE.new"; then
+ "$ORIG_MERGE_FILE" "$TMP_FILE"; then
# awk exited non-zero, strict mode was violated
STRICT_MODE_VIOLATED=true
fi
@@ -381,7 +377,7 @@ END {
STRICT_MODE_VIOLATED=true
fi
-if [ "$STRICT" == "true" ] && [ "$STRICT_MODE_VIOLATED" == "true" ]; then
+if [ "$STRICT" = "true" ] && [ "$STRICT_MODE_VIOLATED" = "true" ]; then
echo "Requested and effective config differ"
exit 1
fi
diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y
index 6d1bbee38f5d..5fb6f07b6ad2 100644
--- a/scripts/kconfig/parser.y
+++ b/scripts/kconfig/parser.y
@@ -159,14 +159,8 @@ config_stmt: config_entry_start config_option_list
yynerrs++;
}
- /*
- * If the same symbol appears twice in a choice block, the list
- * node would be added twice, leading to a broken linked list.
- * list_empty() ensures that this symbol has not yet added.
- */
- if (list_empty(&current_entry->sym->choice_link))
- list_add_tail(&current_entry->sym->choice_link,
- &current_choice->choice_members);
+ list_add_tail(&current_entry->sym->choice_link,
+ &current_choice->choice_members);
}
printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno);
@@ -546,11 +540,10 @@ static int choice_check_sanity(const struct menu *menu)
ret = -1;
}
- if (prop->menu != menu && prop->type == P_PROMPT &&
- prop->menu->parent != menu->parent) {
+ if (prop->menu != menu && prop->type == P_PROMPT) {
fprintf(stderr, "%s:%d: error: %s",
prop->filename, prop->lineno,
- "choice value has a prompt outside its choice group\n");
+ "choice value must not have a prompt in another entry\n");
ret = -1;
}
}
diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py
index d94b79e012c0..66f95e4ed58c 100644
--- a/scripts/kconfig/tests/conftest.py
+++ b/scripts/kconfig/tests/conftest.py
@@ -37,7 +37,8 @@ class Conf:
# runners
def _run_conf(self, mode, dot_config=None, out_file='.config',
- interactive=False, in_keys=None, extra_env={}):
+ interactive=False, in_keys=None, extra_env={},
+ silent=False):
"""Run text-based Kconfig executable and save the result.
mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)
@@ -48,7 +49,10 @@ class Conf:
extra_env: additional environments
returncode: exit status of the Kconfig executable
"""
- command = [CONF_PATH, mode, 'Kconfig']
+ command = [CONF_PATH]
+ if silent:
+ command.append('-s')
+ command += [mode, 'Kconfig']
# Override 'srctree' environment to make the test as the top directory
extra_env['srctree'] = self._test_dir
diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig b/scripts/kconfig/tests/err_repeated_inc/Kconfig
new file mode 100644
index 000000000000..09a88fd29cb5
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+source "Kconfig.inc1"
diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1
new file mode 100644
index 000000000000..495dc38314a1
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+source "Kconfig.inc2"
+source "Kconfig.inc3"
diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2
new file mode 100644
index 000000000000..2b630eec2e99
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+source "Kconfig.inc3"
diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3
new file mode 100644
index 000000000000..a4e40e534e6a
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0-only
diff --git a/scripts/kconfig/tests/err_repeated_inc/__init__.py b/scripts/kconfig/tests/err_repeated_inc/__init__.py
new file mode 100644
index 000000000000..129d740a874b
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/__init__.py
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Detect repeated inclusion error.
+
+If repeated inclusion is detected, it should fail with error message.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() != 0
+ assert conf.stderr_contains('expected_stderr')
diff --git a/scripts/kconfig/tests/err_repeated_inc/expected_stderr b/scripts/kconfig/tests/err_repeated_inc/expected_stderr
new file mode 100644
index 000000000000..53071430ea7d
--- /dev/null
+++ b/scripts/kconfig/tests/err_repeated_inc/expected_stderr
@@ -0,0 +1,2 @@
+Kconfig.inc1:4: error: repeated inclusion of Kconfig.inc3
+Kconfig.inc2:3: note: location of first inclusion of Kconfig.inc3
diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
index ffd469d1f226..791ed659c76b 100644
--- a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
+++ b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
@@ -8,7 +8,7 @@ for symbols with unmet dependency.
This was not working correctly for choice values because choice needs
a bit different symbol computation.
-This checks that no unneeded "# COFIG_... is not set" is contained in
+This checks that no unneeded "# CONFIG_... is not set" is contained in
the .config file.
Related Linux commit: cb67ab2cd2b8abd9650292c986c79901e3073a59
diff --git a/scripts/kconfig/tests/warn_changed_input/Kconfig b/scripts/kconfig/tests/warn_changed_input/Kconfig
new file mode 100644
index 000000000000..69845e2f3fb3
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/Kconfig
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DEP
+ bool "DEP"
+ help
+ Test dependency symbol for Kconfig warning coverage.
+ This is used by the warn_changed_input selftest.
+ It intentionally stays unset in the input fragment.
+ The test checks how dependent user input is adjusted.
+
+config A
+ bool "A"
+ depends on DEP
+ help
+ Test bool symbol for changed-input diagnostics.
+ The input fragment requests this symbol as built-in.
+ The unmet dependency on DEP forces the final value to n.
+ The warning should report that downgrade.
+
+config NUM
+ int "NUM"
+ range 10 20
+ help
+ Test integer symbol for changed-input diagnostics.
+ The input fragment requests a value outside the allowed range.
+ Kconfig resolves it to the constrained in-range value.
+ The warning should report that adjustment.
+
+config DUP
+ bool "DUP"
+ depends on DEP
+ help
+ Test duplicate-definition handling for changed-input diagnostics.
+ The input fragment requests this symbol as built-in.
+ The duplicate definition below must not produce a duplicate warning.
+ This keeps the warning output stable for repeated menu entries.
+
+config DUP
+ bool
+ depends on DEP
diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py
new file mode 100644
index 000000000000..4c3bca6af846
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/__init__.py
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Test optional warnings for user-provided values changed by Kconfig.
+
+Warnings should stay disabled by default, and should only appear when
+KCONFIG_WARN_CHANGED_INPUT is enabled.
+"""
+
+
+def test(conf):
+ assert conf.olddefconfig('config') == 0
+ assert 'user-provided values changed by Kconfig' not in conf.stderr
+
+ assert conf._run_conf('--olddefconfig', dot_config='config',
+ extra_env={
+ 'KCONFIG_WARN_CHANGED_INPUT': '1',
+ }) == 0
+ assert conf.stderr_contains('expected_stderr')
+ assert conf.config_matches('expected_config')
+
+ assert conf._run_conf('--olddefconfig', dot_config='config',
+ extra_env={
+ 'KCONFIG_WARN_CHANGED_INPUT': '1',
+ }, silent=True) == 0
+ assert conf.stderr_contains('expected_stderr')
+
+ assert conf._run_conf('--savedefconfig=defconfig', dot_config='config',
+ out_file='defconfig',
+ extra_env={
+ 'KCONFIG_WARN_CHANGED_INPUT': '1',
+ }) == 0
+ assert conf.stderr_contains('expected_stderr')
+ assert conf.config_matches('expected_defconfig')
diff --git a/scripts/kconfig/tests/warn_changed_input/config b/scripts/kconfig/tests/warn_changed_input/config
new file mode 100644
index 000000000000..dbe93ff26408
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/config
@@ -0,0 +1,3 @@
+CONFIG_A=y
+CONFIG_NUM=30
+CONFIG_DUP=y
diff --git a/scripts/kconfig/tests/warn_changed_input/expected_config b/scripts/kconfig/tests/warn_changed_input/expected_config
new file mode 100644
index 000000000000..fe8bbec66c53
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/expected_config
@@ -0,0 +1,6 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Main menu
+#
+# CONFIG_DEP is not set
+CONFIG_NUM=20
diff --git a/scripts/kconfig/tests/warn_changed_input/expected_defconfig b/scripts/kconfig/tests/warn_changed_input/expected_defconfig
new file mode 100644
index 000000000000..af9e34851d2a
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/expected_defconfig
@@ -0,0 +1 @@
+CONFIG_NUM=20
diff --git a/scripts/kconfig/tests/warn_changed_input/expected_stderr b/scripts/kconfig/tests/warn_changed_input/expected_stderr
new file mode 100644
index 000000000000..9ec8446b4ac2
--- /dev/null
+++ b/scripts/kconfig/tests/warn_changed_input/expected_stderr
@@ -0,0 +1,4 @@
+warning: user-provided values changed by Kconfig:
+ CONFIG_A: y -> n
+ CONFIG_NUM: 30 -> 20
+ CONFIG_DUP: y -> n
diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c
index 5cdcee144b58..0809aa061b6a 100644
--- a/scripts/kconfig/util.c
+++ b/scripts/kconfig/util.c
@@ -18,25 +18,50 @@ static HASHTABLE_DEFINE(file_hashtable, 1U << 11);
struct file {
struct hlist_node node;
+ struct {
+ const char *name;
+ int lineno;
+ } parent;
char name[];
};
+static void die_duplicated_include(struct file *file,
+ const char *parent, int lineno)
+{
+ fprintf(stderr,
+ "%s:%d: error: repeated inclusion of %s\n"
+ "%s:%d: note: location of first inclusion of %s\n",
+ parent, lineno, file->name,
+ file->parent.name, file->parent.lineno, file->name);
+ exit(1);
+}
+
/* file already present in list? If not add it */
-const char *file_lookup(const char *name)
+const char *file_lookup(const char *name,
+ const char *parent_name, int parent_lineno)
{
+ const char *parent = NULL;
struct file *file;
size_t len;
int hash = hash_str(name);
+ if (parent_name)
+ parent = file_lookup(parent_name, NULL, 0);
+
hash_for_each_possible(file_hashtable, file, node, hash)
- if (!strcmp(name, file->name))
- return file->name;
+ if (!strcmp(name, file->name)) {
+ if (!parent_name)
+ return file->name;
+ die_duplicated_include(file, parent, parent_lineno);
+ }
len = strlen(name);
file = xmalloc(sizeof(*file) + len + 1);
memset(file, 0, sizeof(*file));
memcpy(file->name, name, len);
file->name[len] = '\0';
+ file->parent.name = parent;
+ file->parent.lineno = parent_lineno;
hash_add(file_hashtable, &file->node, hash);