Skip to contentSkip to conversation

kconfig: Add transitional symbol attribute for migration support

PATCH Superseded Kees Cook 4 participants 18 messages Aug 30, 2025 – Sep 23, 2025

Subsystems (3)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Kees Cook

author
#0 lore raw

PATCH v2 1/1 kconfig: Add transitional symbol attribute for migration support

During kernel option migrations (e.g. CONFIG_CFI_CLANG to CONFIG_CFI), existing .config files need to maintain backward compatibility while preventing deprecated options from appearing in newly generated configurations. This is challenging with existing Kconfig mechanisms because:

1. Simply removing old options breaks existing .config files.
2. Manually listing an option as "deprecated" leaves it needlessly visible and still writes them to new .config files.
3. Using any method to remove visibility (.e.g no 'prompt', 'if n', etc) prevents the option from being processed at all.

Add a "transitional" attribute that creates symbols which are:
- Processed during configuration (can influence other symbols' defaults)
- Hidden from user menus (no prompts appear)
- Omitted from newly written .config files (gets migrated)
- Restricted to only having help sections (no defaults, selects, etc) making it truly just a "prior value pass-through" option.

The transitional syntax requires a type argument and prevents type redefinition:

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

config NEW_OPTION
bool "New option"
default OLD_OPTION

This allows seamless migration: olddefconfig processes existing CONFIG_OLD_OPTION=y settings to enable CONFIG_NEW_OPTION=y, while CONFIG_OLD_OPTION is omitted from newly generated .config files.

Implementation details:
- Parser validates transitional symbols can only have help sections
- Symbol visibility logic updated: usable = (visible != no || transitional)
- Transitional symbols preserve user values during configuration
- Type safety enforced to prevent redefinition after transitional declaration
- Used distinct struct members instead of new flags for readability
- Documentation added to show the usage

---
With help from Claude Code to show me how to navigate the kconfig parser.

v2: fixed human-introduced errors
v1: https://lore.kernel.org/all/[email protected]/

Cc Nathan Chancellor <[email protected]>
Cc Nicolas Schier <[email protected]>
Cc Jonathan Corbet <[email protected]>
Cc Masahiro Yamada <[email protected]>
Cc Randy Dunlap <[email protected]>
Cc Arnd Bergmann <[email protected]>
Cc Krzysztof Kozlowski <[email protected]>
---
 scripts/kconfig/expr.h                    | 15 +++++++
 scripts/kconfig/lexer.l                   |  1 +
 scripts/kconfig/parser.y                  | 51 +++++++++++++++++++++++
 scripts/kconfig/symbol.c                  | 11 +++--
 Documentation/kbuild/kconfig-language.rst | 31 ++++++++++++++
 5 files changed, 106 insertions(+), 3 deletions(-)
scripts/kconfig/expr.h +15 -0
index fe2231e0e6a4..be51574d6c77 100644
@@ -127,6 +127,21 @@ struct symbol {
 	/* SYMBOL_* flags */
 	int flags;
 
+	/*
+	 * Transitional symbol - processed during configuration but hidden from
+	 * user in menus and omitted from newly written .config files. Used for
+	 * backward compatibility during config option migrations (e.g.,
+	 * CFI_CLANG → CFI). Transitional symbols can still influence default
+	 * expressions of other symbols.
+	 */
+	bool transitional:1;
+
+	/*
+	 * Symbol usability - calculated as (visible != no || transitional).
+	 * Determines if symbol can be used in expressions.
+	 */
+	bool usable:1;
+
 	/* List of properties. See prop_type. */
 	struct property *prop;
 
scripts/kconfig/lexer.l +1 -0
index 9c2cdfc33c6f..6d2c92c6095d 100644
@@ -126,6 +126,7 @@ n	[A-Za-z0-9_-]
 "select"		return T_SELECT;
 "source"		return T_SOURCE;
 "string"		return T_STRING;
+"transitional"		return T_TRANSITIONAL;
 "tristate"		return T_TRISTATE;
 "visible"		return T_VISIBLE;
 "||"			return T_OR;
scripts/kconfig/parser.y +51 -0

75 lines not on this page · raw message

scripts/kconfig/symbol.c +8 -3
index 26ab10c0fd76..b822c0c897e5 100644
@@ -447,6 +447,9 @@ void sym_calc_value(struct symbol *sym)
 	if (sym->visible != no)
 		sym->flags |= SYMBOL_WRITE;
 
+	/* Calculate usable flag */
+	sym->usable = (sym->visible != no || sym->transitional);
+
 	/* set default if recursively called */
 	sym->curr = newval;
 
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
 			sym_calc_choice(choice_menu);
 			newval.tri = sym->curr.tri;
 		} else {
-			if (sym->visible != no) {
+			if (sym->usable) {
 				/* if the symbol is visible use the user value
 				 * if available, otherwise try the default value
 				 */
 				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
 					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);
 					goto calc_newval;
 				}
 			}
@@ -497,7 +502,7 @@ void sym_calc_value(struct symbol *sym)
 	case S_STRING:
 	case S_HEX:
 	case S_INT:
-		if (sym->visible != no && sym_has_value(sym)) {
+		if (sym->usable && sym_has_value(sym)) {
 			newval.val = sym->def[S_DEF_USER].val;
 			break;
 		}
Documentation/kbuild/kconfig-language.rst +31 -0
index a91abb8f6840..345c334ce680 100644
@@ -232,6 +232,37 @@ applicable everywhere (see syntax).
   enables the third modular state for all config symbols.
   At most one symbol may have the "modules" option set.
 
+- transitional attribute: "transitional"
+  This declares the symbol as transitional, meaning it should be processed
+  during configuration but omitted from newly written .config files.
+  Transitional symbols are useful for backward compatibility during config
+  option migrations - they allow olddefconfig to process existing .config
+  files while ensuring the old option doesn't appear in new configurations.
+
+  A transitional symbol:
+  - Has no prompt (is not visible to users in menus)
+  - Is processed normally during configuration (values are read and used)
+  - Can be referenced in default expressions of other symbols
+  - Is not written to new .config files
+  - Cannot have any other properties (it is a pass-through option)
+
+  Example migration from OLD_NAME to NEW_NAME::
+
+    config NEW_NAME
+	bool "New option name"
+	default OLD_NAME
+	help
+	  This replaces the old CONFIG_OLD_NAME option.
+
+    config OLD_NAME
+	transitional bool
+	help
+	  Transitional config for OLD_NAME to NEW_NAME migration.
+
+  With this setup, existing .config files with "CONFIG_OLD_NAME=y" will
+  result in "CONFIG_NEW_NAME=y" being set, while CONFIG_OLD_NAME will be
+  omitted from newly written .config files.
+
 Menu dependencies
 -----------------
 
Signature · 1 line
2.34.1
#1 lore raw

Hi,

Drive-by review... consider it more as "here's some stuff that could be worth looking at" rather than blocking in any way.

Quoted 22 lines · Kees Cook

During kernel option migrations (e.g. CONFIG_CFI_CLANG to CONFIG_CFI), existing .config files need to maintain backward compatibility while preventing deprecated options from appearing in newly generated configurations. This is challenging with existing Kconfig mechanisms because:

1. Simply removing old options breaks existing .config files.
2. Manually listing an option as "deprecated" leaves it needlessly visible and still writes them to new .config files.
3. Using any method to remove visibility (.e.g no 'prompt', 'if n', etc) prevents the option from being processed at all.

Add a "transitional" attribute that creates symbols which are:
- Processed during configuration (can influence other symbols' defaults)
- Hidden from user menus (no prompts appear)
- Omitted from newly written .config files (gets migrated)
- Restricted to only having help sections (no defaults, selects, etc) making it truly just a "prior value pass-through" option.

The transitional syntax requires a type argument and prevents type redefinition:

Quoted 8 lines

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

config NEW_OPTION
bool "New option"
default OLD_OPTION

Can you add this to scripts/kconfig/tests/ + both positive and negative tests? Tests are run with 'make testconfig' but (AFAICT) doesn't actually recompile config/mconf/etc. before running the tests, so small gotcha there.

Quoted 49 lines

This allows seamless migration: olddefconfig processes existing CONFIG_OLD_OPTION=y settings to enable CONFIG_NEW_OPTION=y, while CONFIG_OLD_OPTION is omitted from newly generated .config files.

Implementation details:
- Parser validates transitional symbols can only have help sections
- Symbol visibility logic updated: usable = (visible != no || transitional)
- Transitional symbols preserve user values during configuration
- Type safety enforced to prevent redefinition after transitional declaration
- Used distinct struct members instead of new flags for readability
- Documentation added to show the usage

---
With help from Claude Code to show me how to navigate the kconfig parser.

v2: fixed human-introduced errors
v1: https://lore.kernel.org/all/[email protected]/

Cc Nathan Chancellor <[email protected]>
Cc Nicolas Schier <[email protected]>
Cc Jonathan Corbet <[email protected]>
Cc Masahiro Yamada <[email protected]>
Cc Randy Dunlap <[email protected]>
Cc Arnd Bergmann <[email protected]>
Cc Krzysztof Kozlowski <[email protected]>
---
  scripts/kconfig/expr.h                    | 15 +++++++
  scripts/kconfig/lexer.l                   |  1 +
  scripts/kconfig/parser.y                  | 51 +++++++++++++++++++++++
  scripts/kconfig/symbol.c                  | 11 +++--
  Documentation/kbuild/kconfig-language.rst | 31 ++++++++++++++
  5 files changed, 106 insertions(+), 3 deletions(-)
scripts/kconfig/expr.h +5 -0
index fe2231e0e6a4..be51574d6c77 100644
@@ -127,6 +127,21 @@ struct symbol {
  	/* SYMBOL_* flags */
  	int flags;
 
+	/*
+	 * Transitional symbol - processed during configuration but hidden from
+	 * user in menus and omitted from newly written .config files. Used for
+	 * backward compatibility during config option migrations (e.g.,
+	 * CFI_CLANG → CFI). Transitional symbols can still influence default

scripts/kconfig/expr.h struct symbol { · line 144

Quoted 10 lines
scripts/kconfig/expr.h +10 -0
+	 * expressions of other symbols.
+	 */
+	bool transitional:1;
+
+	/*
+	 * Symbol usability - calculated as (visible != no || transitional).
+	 * Determines if symbol can be used in expressions.
+	 */
+	bool usable:1;
+

It's a bit of a "red flag" to see bitfield bools just after an "int flags;" member... should these be SYMBOL_ flags?

Speaking of SYMBOL_ flags, there's apparently one that controls whether a given symbol should be written out to .config:

scripts/kconfig/expr.h:#define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */

This seems like something you'd like to use somehow -- maybe simply clear it in sym_calc_value() if it's transitional? Similar to how it's done for choice values:

        if (sym_is_choice(sym))
                sym->flags &= ~SYMBOL_WRITE;
Quoted 30 lines
scripts/kconfig/expr.h +0 -0
  	/* List of properties. See prop_type. */
  	struct property *prop;
 
scripts/kconfig/lexer.l +1 -0
index 9c2cdfc33c6f..6d2c92c6095d 100644
@@ -126,6 +126,7 @@ n	[A-Za-z0-9_-]
  "select"		return T_SELECT;
  "source"		return T_SOURCE;
  "string"		return T_STRING;
+"transitional"		return T_TRANSITIONAL;
  "tristate"		return T_TRISTATE;
  "visible"		return T_VISIBLE;
  "||"			return T_OR;
scripts/kconfig/parser.y +1 -0
index e9c3c664e925..01d2d0f720ce 100644
@@ -75,6 +75,7 @@ struct menu *current_menu, *current_entry, *current_choice;
  %token T_SELECT
  %token T_SOURCE
  %token T_STRING
+%token T_TRANSITIONAL
  %token T_TRISTATE
  %token T_VISIBLE
  %token T_EOL
@@ -205,6 +206,16 @@ config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL
  	printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno);
  };

scripts/kconfig/parser.y config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL · line 217

Quoted 10 lines
scripts/kconfig/parser.y +9 -0
 
+config_option: T_TRANSITIONAL type T_EOL
+{
+	if (current_entry->sym->type != S_UNKNOWN)
+		yyerror("transitional type cannot be set after symbol type is already defined");
+	menu_set_type($2);
+	current_entry->sym->transitional = true;
+	printd(DEBUG_PARSE, "%s:%d:transitional(%u)\n", cur_filename, cur_lineno,
+		$2);
+};

You could also consider making this an attribute similar to the "modules" flags and simplify:

config_option: T_TRANSITIONAL T_EOL
{
current_entry->sym->transitional = true; printd(DEBUG_PARSE, "%s:%d:transitional\n", cur_filename,
cur_lineno);
};

...it would mean the config options look this way:

config OLD_OPTION
bool
transitional

(If not, menu_set_type() does already contain a check for whether the type has already been set.)

Quoted 63 lines
scripts/kconfig/parser.y +41 -0
+
  config_option: default expr if_expr T_EOL
  {
  	menu_add_expr(P_DEFAULT, $2, $3);
@@ -482,6 +493,43 @@ assign_val:
 
  %%
 
+/**
+ * transitional_check_sanity - check transitional symbols have no other
+ *			       properties
+ *
+ * @menu: menu of the potentially transitional symbol
+ *
+ * Return: -1 if an error is found, 0 otherwise.
+ */
+static int transitional_check_sanity(const struct menu *menu)
+{
+	struct property *prop;
+
+	if (!menu->sym || !menu->sym->transitional)
+		return 0;
+
+	/* Check for depends and visible conditions. */
+	if ((menu->dep && !expr_is_yes(menu->dep)) ||
+	    (menu->visibility && !expr_is_yes(menu->visibility))) {
+		fprintf(stderr, "%s:%d: error: %s",
+			menu->filename, menu->lineno,
+			"transitional symbols can only have help sections\n");
+		return -1;
+	}
+
+	/* Check for any property other than "help". */
+	for (prop = menu->sym->prop; prop; prop = prop->next) {
+		if (prop->type != P_COMMENT) {
+			fprintf(stderr, "%s:%d: error: %s",
+				prop->filename, prop->lineno,
+				"transitional symbols can only have help sections\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
  /**
   * choice_check_sanity - check sanity of a choice member
   *
@@ -558,6 +606,9 @@ void conf_parse(const char *name)
  		if (menu->sym && sym_check_deps(menu->sym))
  			yynerrs++;
 
+		if (transitional_check_sanity(menu))
+			yynerrs++;
+
  		if (menu->sym && sym_is_choice(menu->sym)) {
  			menu_for_each_sub_entry(child, menu)
  				if (child->sym && choice_check_sanity(child))
scripts/kconfig/symbol.c +0 -0
index 26ab10c0fd76..b822c0c897e5 100644

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 452

Quoted 6 lines
scripts/kconfig/symbol.c +3 -0
@@ -447,6 +447,9 @@ void sym_calc_value(struct symbol *sym)
  	if (sym->visible != no)
  		sym->flags |= SYMBOL_WRITE;
 
+	/* Calculate usable flag */
+	sym->usable = (sym->visible != no || sym->transitional);
+

Is this actually ever used outside of this function? (IOW could this just be a local variable instead of a sym-> flag/member?) Or do we need to set it here because sym_calc_value() calls itself recursively? To me it looks like we only ever access sym->usable for the "sym" that was passed as an argument to the function.

Quoted 8 lines
scripts/kconfig/symbol.c +0 -1
  	/* set default if recursively called */
  	sym->curr = newval;
 
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
  			sym_calc_choice(choice_menu);
  			newval.tri = sym->curr.tri;
  		} else {
-			if (sym->visible != no) {

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 473

Quoted 10 lines
scripts/kconfig/symbol.c +4 -1
+			if (sym->usable) {
  				/* if the symbol is visible use the user value
  				 * if available, otherwise try the default value
  				 */
  				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
  					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);

This looks a bit odd to me. Just thinking out loud: your new logic is there to be able to use a value even though it's not visible. In the case where it's transitional you use the .config value instead of the condition that makes it visible.

Could you simply change sym_calc_visibility() instead to always return 'yes' when the symbol is transitional? Wouldn't that simplify everything in sym_calc_value()?

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 508

Quoted 12 lines
scripts/kconfig/symbol.c +1 -1
  					goto calc_newval;
  				}
  			}
@@ -497,7 +502,7 @@ void sym_calc_value(struct symbol *sym)
  	case S_STRING:
  	case S_HEX:
  	case S_INT:
-		if (sym->visible != no && sym_has_value(sym)) {
+		if (sym->usable && sym_has_value(sym)) {
  			newval.val = sym->def[S_DEF_USER].val;
  			break;
  		}

Ok.

Quoted 32 lines
Documentation/kbuild/kconfig-language.rst +24 -0
index a91abb8f6840..345c334ce680 100644
@@ -232,6 +232,37 @@ applicable everywhere (see syntax).
    enables the third modular state for all config symbols.
    At most one symbol may have the "modules" option set.
 
+- transitional attribute: "transitional"
+  This declares the symbol as transitional, meaning it should be processed
+  during configuration but omitted from newly written .config files.
+  Transitional symbols are useful for backward compatibility during config
+  option migrations - they allow olddefconfig to process existing .config
+  files while ensuring the old option doesn't appear in new configurations.
+
+  A transitional symbol:
+  - Has no prompt (is not visible to users in menus)
+  - Is processed normally during configuration (values are read and used)
+  - Can be referenced in default expressions of other symbols
+  - Is not written to new .config files
+  - Cannot have any other properties (it is a pass-through option)
+
+  Example migration from OLD_NAME to NEW_NAME::
+
+    config NEW_NAME
+	bool "New option name"
+	default OLD_NAME
+	help
+	  This replaces the old CONFIG_OLD_NAME option.
+
+    config OLD_NAME
+	transitional bool

Documentation/kbuild/kconfig-language.rst applicable everywhere (see syntax). · line 268

Quoted 10 lines
Documentation/kbuild/kconfig-language.rst +7 -0
+	help
+	  Transitional config for OLD_NAME to NEW_NAME migration.
+
+  With this setup, existing .config files with "CONFIG_OLD_NAME=y" will
+  result in "CONFIG_NEW_NAME=y" being set, while CONFIG_OLD_NAME will be
+  omitted from newly written .config files.
+
  Menu dependencies
  -----------------
 

Vegard

author
#2 lore raw
Quoted 2 lines · Vegard Nossum

Drive-by review... consider it more as "here's some stuff that could be worth looking at" rather than blocking in any way.

Thanks for looking at it!

Quoted 29 lines · Kees Cook
Quoted 27 lines

During kernel option migrations (e.g. CONFIG_CFI_CLANG to CONFIG_CFI), existing .config files need to maintain backward compatibility while preventing deprecated options from appearing in newly generated configurations. This is challenging with existing Kconfig mechanisms because:

1. Simply removing old options breaks existing .config files.
2. Manually listing an option as "deprecated" leaves it needlessly visible and still writes them to new .config files.
3. Using any method to remove visibility (.e.g no 'prompt', 'if n', etc) prevents the option from being processed at all.

Add a "transitional" attribute that creates symbols which are:
- Processed during configuration (can influence other symbols' defaults)
- Hidden from user menus (no prompts appear)
- Omitted from newly written .config files (gets migrated)
- Restricted to only having help sections (no defaults, selects, etc) making it truly just a "prior value pass-through" option.

The transitional syntax requires a type argument and prevents type redefinition:

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

Quoted 8 lines
Quoted 3 lines

config NEW_OPTION
bool "New option"
default OLD_OPTION

Can you add this to scripts/kconfig/tests/ + both positive and negative tests? Tests are run with 'make testconfig' but (AFAICT) doesn't actually recompile config/mconf/etc. before running the tests, so small gotcha there.

Yes, I will get that added if people are generally happy with this feature idea. :)

Quoted 26 lines
Quoted 12 lines
scripts/kconfig/expr.h +5 -0
index fe2231e0e6a4..be51574d6c77 100644
@@ -127,6 +127,21 @@ struct symbol {
  	/* SYMBOL_* flags */
  	int flags;
+	/*
+	 * Transitional symbol - processed during configuration but hidden from
+	 * user in menus and omitted from newly written .config files. Used for
+	 * backward compatibility during config option migrations (e.g.,
+	 * CFI_CLANG → CFI). Transitional symbols can still influence default

scripts/kconfig/expr.h struct symbol { · line 143

Quoted 10 lines
scripts/kconfig/expr.h +10 -0
+	 * expressions of other symbols.
+	 */
+	bool transitional:1;
+
+	/*
+	 * Symbol usability - calculated as (visible != no || transitional).
+	 * Determines if symbol can be used in expressions.
+	 */
+	bool usable:1;
+

It's a bit of a "red flag" to see bitfield bools just after an "int flags;" member... should these be SYMBOL_ flags?

Quoted 2 lines

Speaking of SYMBOL_ flags, there's apparently one that controls whether a given symbol should be written out to .config:

Yeah, I mentioned this in the commit log, and maybe I just have to make this not as easily readable? But you have a point about "usable" below...

Quoted 9 lines

scripts/kconfig/expr.h:#define SYMBOL_WRITE 0x0200 /* write symbol to
file (KCONFIG_CONFIG) */

This seems like something you'd like to use somehow -- maybe simply clear it in sym_calc_value() if it's transitional? Similar to how it's done for choice values:

        if (sym_is_choice(sym))
                sym->flags &= ~SYMBOL_WRITE;

This is actually handled naturally as part of this logic:

Quoted 2 lines

scripts/kconfig/expr.h struct symbol { · line 145

Quoted 2 lines
scripts/kconfig/expr.h +0 -0
  	if (sym->visible != no)
  		sym->flags |= SYMBOL_WRITE;

i.e. "usable" doesn't change SYMBOL_WRITE getting set.

Quoted 23 lines

scripts/kconfig/expr.h config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL · line 216

Quoted 12 lines
scripts/kconfig/expr.h +9 -0
@@ -205,6 +206,16 @@ config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL
  	printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno);
  };
+config_option: T_TRANSITIONAL type T_EOL
+{
+	if (current_entry->sym->type != S_UNKNOWN)
+		yyerror("transitional type cannot be set after symbol type is already defined");
+	menu_set_type($2);
+	current_entry->sym->transitional = true;
+	printd(DEBUG_PARSE, "%s:%d:transitional(%u)\n", cur_filename, cur_lineno,
+		$2);
+};

You could also consider making this an attribute similar to the "modules" flags and simplify:

config_option: T_TRANSITIONAL T_EOL
{
current_entry->sym->transitional = true; printd(DEBUG_PARSE, "%s:%d:transitional\n", cur_filename,
cur_lineno);
};

Quoted 8 lines

...it would mean the config options look this way:

config OLD_OPTION
bool
transitional

(If not, menu_set_type() does already contain a check for whether the type has already been set.)

I went back and forth on how I wanted it to look and ultimately decided it was awkward to say "use transitional but only with a type that doesn't have a prompt". Instead it seemed better to have the type explicitly set.

menu_set_type() does check already, but it's a warning only.

Quoted 17 lines
Quoted 17 lines
scripts/kconfig/expr.h +3 -0
@@ -558,6 +606,9 @@ void conf_parse(const char *name)
  		if (menu->sym && sym_check_deps(menu->sym))
  			yynerrs++;
+		if (transitional_check_sanity(menu))
+			yynerrs++;
+
  		if (menu->sym && sym_is_choice(menu->sym)) {
  			menu_for_each_sub_entry(child, menu)
  				if (child->sym && choice_check_sanity(child))
scripts/kconfig/symbol.c +1 -0
index 26ab10c0fd76..b822c0c897e5 100644
@@ -447,6 +447,9 @@ void sym_calc_value(struct symbol *sym)
  	if (sym->visible != no)
  		sym->flags |= SYMBOL_WRITE;
+	/* Calculate usable flag */
Quoted 8 lines

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 452

Quoted 2 lines
scripts/kconfig/symbol.c +2 -0
+	sym->usable = (sym->visible != no || sym->transitional);
+

Is this actually ever used outside of this function? (IOW could this just be a local variable instead of a sym-> flag/member?) Or do we need to set it here because sym_calc_value() calls itself recursively? To me it looks like we only ever access sym->usable for the "sym" that was passed as an argument to the function.

Ah! It's not any more, no. I had an earlier version where I was examining it elsewhere, but yeah, this is only needed here.

Quoted 24 lines
Quoted 8 lines
scripts/kconfig/symbol.c +0 -1
  	/* set default if recursively called */
  	sym->curr = newval;
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
  			sym_calc_choice(choice_menu);
  			newval.tri = sym->curr.tri;
  		} else {
-			if (sym->visible != no) {

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 473

Quoted 10 lines
scripts/kconfig/symbol.c +4 -1
+			if (sym->usable) {
  				/* if the symbol is visible use the user value
  				 * if available, otherwise try the default value
  				 */
  				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
  					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);

This looks a bit odd to me. Just thinking out loud: your new logic is there to be able to use a value even though it's not visible. In the case where it's transitional you use the .config value instead of the condition that makes it visible.

Quoted 3 lines

Could you simply change sym_calc_visibility() instead to always return 'yes' when the symbol is transitional? Wouldn't that simplify everything in sym_calc_value()?

It's a tristate, so "m" is also possible besides "y". (sym->visible is also a tristate. :)

I will send a v3 with better bit fields.

-Kees

Signature · 1 line
Kees Cook
#3 lore raw
Quoted 20 lines · Kees Cook
Quoted 20 lines
Quoted 5 lines
scripts/kconfig/symbol.c +0 -1
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
   			sym_calc_choice(choice_menu);
   			newval.tri = sym->curr.tri;
   		} else {
-			if (sym->visible != no) {

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 473

Quoted 10 lines
scripts/kconfig/symbol.c +4 -1
+			if (sym->usable) {
   				/* if the symbol is visible use the user value
   				 * if available, otherwise try the default value
   				 */
   				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
   					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);

This looks a bit odd to me. Just thinking out loud: your new logic is there to be able to use a value even though it's not visible. In the case where it's transitional you use the .config value instead of the condition that makes it visible.

Quoted 5 lines
Quoted 3 lines

Could you simply change sym_calc_visibility() instead to always return 'yes' when the symbol is transitional? Wouldn't that simplify everything in sym_calc_value()?

It's a tristate, so "m" is also possible besides "y". (sym->visible is also a tristate. 🙂

That would be fine, right?

We'd pass the if (sym->visible != no) check... we'd do the

newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

EXPR_AND() is basically min() (with n=0, m=1, y=2), so effectively it would end up doing

newval.tri = min(sym->def[S_DEF_USER].tri, 2);

which is the same as

newval.tri = sym->def[S_DEF_USER].tri;

That's what your code is currently doing too, but in a much more roundabout way.

Vegard

author
#4 lore raw
Quoted 37 lines · Vegard Nossum
Quoted 20 lines · Kees Cook
Quoted 20 lines
Quoted 5 lines
scripts/kconfig/symbol.c +0 -1
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
   			sym_calc_choice(choice_menu);
   			newval.tri = sym->curr.tri;
   		} else {
-			if (sym->visible != no) {

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 473

Quoted 10 lines
scripts/kconfig/symbol.c +4 -1
+			if (sym->usable) {
   				/* if the symbol is visible use the user value
   				 * if available, otherwise try the default value
   				 */
   				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
   					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);

This looks a bit odd to me. Just thinking out loud: your new logic is there to be able to use a value even though it's not visible. In the case where it's transitional you use the .config value instead of the condition that makes it visible.

Quoted 5 lines
Quoted 3 lines

Could you simply change sym_calc_visibility() instead to always return 'yes' when the symbol is transitional? Wouldn't that simplify everything in sym_calc_value()?

It's a tristate, so "m" is also possible besides "y". (sym->visible is also a tristate. 🙂

That would be fine, right?

We'd pass the if (sym->visible != no) check... we'd do the

newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

EXPR_AND() is basically min() (with n=0, m=1, y=2), so effectively it would end up doing

Quoted 8 lines

newval.tri = min(sym->def[S_DEF_USER].tri, 2);

which is the same as

newval.tri = sym->def[S_DEF_USER].tri;

That's what your code is currently doing too, but in a much more roundabout way.

Right, it was this:

    newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

But I made it effectively:

  if (sym->transitional)
    newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->def[S_DEF_USER].tri);
  else
    newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

That first "if" is kind of pointless. I just sent the v3 before I saw this email. :P

I was trying to avoid yet more indentation, but I could change it to:

		if (sym->transitional)
			newval.tri = sym->def[S_DEF_USER].tri;
		else
			newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
					      sym->visible);

?

Signature · 1 line
Kees Cook
#5 lore raw
Quoted 63 lines · Kees Cook
Quoted 37 lines · Vegard Nossum
Quoted 20 lines · Kees Cook
Quoted 20 lines
Quoted 5 lines
scripts/kconfig/symbol.c +0 -1
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
    			sym_calc_choice(choice_menu);
    			newval.tri = sym->curr.tri;
    		} else {
-			if (sym->visible != no) {

scripts/kconfig/symbol.c void sym_calc_value(struct symbol *sym) · line 473

Quoted 10 lines
scripts/kconfig/symbol.c +4 -1
+			if (sym->usable) {
    				/* if the symbol is visible use the user value
    				 * if available, otherwise try the default value
    				 */
    				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
    					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);

This looks a bit odd to me. Just thinking out loud: your new logic is there to be able to use a value even though it's not visible. In the case where it's transitional you use the .config value instead of the condition that makes it visible.

Quoted 5 lines
Quoted 3 lines

Could you simply change sym_calc_visibility() instead to always return 'yes' when the symbol is transitional? Wouldn't that simplify everything in sym_calc_value()?

It's a tristate, so "m" is also possible besides "y". (sym->visible is also a tristate. 🙂

That would be fine, right?

We'd pass the if (sym->visible != no) check... we'd do the

newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

EXPR_AND() is basically min() (with n=0, m=1, y=2), so effectively it would end up doing

Quoted 8 lines

newval.tri = min(sym->def[S_DEF_USER].tri, 2);

which is the same as

newval.tri = sym->def[S_DEF_USER].tri;

That's what your code is currently doing too, but in a much more roundabout way.

Right, it was this:

     newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

But I made it effectively:

   if (sym->transitional)
     newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->def[S_DEF_USER].tri);
   else
     newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, sym->visible);

That first "if" is kind of pointless. I just sent the v3 before I saw this email. :P

I was trying to avoid yet more indentation, but I could change it to:

Quoted 8 lines
		if (sym->transitional)
			newval.tri = sym->def[S_DEF_USER].tri;
		else
			newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
					      sym->visible);

?

If you change sym_calc_visibility() to always return 'yes' for transitional values then I don't think you need to touch sym_calc_value() at all.

Vegard

author
#6 lore raw
Quoted 3 lines · Vegard Nossum

If you change sym_calc_visibility() to always return 'yes' for transitional values then I don't think you need to touch sym_calc_value() at all.

Hm, it looks like sym_calc_visibility() doesn't strictly just look at visibility. And visibility seems to "last"? And I think the "tri" still can't just be "yes", don't we need the other stuff handled?

Do you see a way to do it how you're suggesting? And now I wrote the regression tests so we can test any alternatives! ;)

Signature · 1 line
Kees Cook
#7 lore raw
Quoted 11 lines · Kees Cook
Quoted 3 lines · Vegard Nossum

If you change sym_calc_visibility() to always return 'yes' for transitional values then I don't think you need to touch sym_calc_value() at all.

Hm, it looks like sym_calc_visibility() doesn't strictly just look at visibility. And visibility seems to "last"? And I think the "tri" still can't just be "yes", don't we need the other stuff handled?

Do you see a way to do it how you're suggesting? And now I wrote the regression tests so we can test any alternatives! ;)

Here's what I had in mind (on top of your kcfi patchset), see the attachment.

It basically undoes all your additions to sym_calc_value() in favour of two straightforward additions:

@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)

        struct property *prop;
        tristate tri;

+ if (sym->flags & SYMBOL_HIDDEN) {
+ sym->visible = yes;
+ return;
+ }
+

and

@@ -536,7 +531,7 @@ void sym_calc_value(struct symbol *sym)

                }
        }

- if (sym_is_choice(sym))
+ if (sym_is_choice(sym) || sym->flags & SYMBOL_HIDDEN)

                sym->flags &= ~SYMBOL_WRITE;

}

Let me know how that works for you (the new test passes here).

Vegard

scripts/kconfig/symbol.c +9 -14
index b2686dba05ece..37958ff57f707 100644
@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)
 	struct property *prop;
 	tristate tri;
 
+	if (sym->flags & SYMBOL_HIDDEN) {
+		sym->visible = yes;
+		return;
+	}
+
 	/* any prompt visible? */
 	tri = no;
 	for_all_prompts(sym, prop) {
@@ -408,7 +413,6 @@ void sym_calc_value(struct symbol *sym)
 	struct symbol_value newval, oldval;
 	struct property *prop;
 	struct menu *choice_menu;
-	bool usable;
 
 	if (!sym)
 		return;
@@ -448,13 +452,6 @@ void sym_calc_value(struct symbol *sym)
 	if (sym->visible != no)
 		sym->flags |= SYMBOL_WRITE;
 
-	/*
-	 * For a symbol to be processed during configuration it needs to
-	 * be either visible or a transitional symbol that is hidden from
-	 * menus and omitted from newly written .config files.
-	 */
-	usable = (sym->visible != no || (sym->flags & SYMBOL_HIDDEN));
-
 	/* set default if recursively called */
 	sym->curr = newval;
 
@@ -467,15 +464,13 @@ void sym_calc_value(struct symbol *sym)
 			sym_calc_choice(choice_menu);
 			newval.tri = sym->curr.tri;
 		} else {
-			if (usable) {
+			if (sym->visible != no) {
 				/* if the symbol is visible use the user value
 				 * if available, otherwise try the default value
 				 */
 				if (sym_has_value(sym)) {
-					tristate value = (sym->flags & SYMBOL_HIDDEN) ?
-						sym->def[S_DEF_USER].tri : sym->visible;
 					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      value);
+							      sym->visible);
 					goto calc_newval;
 				}
 			}
@@ -507,7 +502,7 @@ void sym_calc_value(struct symbol *sym)
 	case S_STRING:
 	case S_HEX:
 	case S_INT:
-		if (usable && sym_has_value(sym)) {
+		if (sym->visible != no && sym_has_value(sym)) {
 			newval.val = sym->def[S_DEF_USER].val;
 			break;
 		}
@@ -536,7 +531,7 @@ void sym_calc_value(struct symbol *sym)
 		}
 	}
 
-	if (sym_is_choice(sym))
+	if (sym_is_choice(sym) || sym->flags & SYMBOL_HIDDEN)
 		sym->flags &= ~SYMBOL_WRITE;
 }
 
#8 lore raw

scripts/kconfig/symbol.c static void sym_calc_visibility(struct symbol *sym) · line 218

Quoted 6 lines · Vegard Nossum
scripts/kconfig/symbol.c +2 -0
@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)
         struct property *prop;
         tristate tri;
 
+       if (sym->flags & SYMBOL_HIDDEN) {
+               sym->visible = yes;

...I just saw the irony here after having already pressed "Send".

Let me explain:

SYMBOL_HIDDEN is your new flag that indicates that somebody used "transitional" on the config entry.

sym->visible is tristate value that gives you the condition for whether a symbol can take on a value -- y/m means the option is visible to the user (hence the name) and thus eligible to have a value assigned to it. In this case you've explicitly hidden the symbol from the user but you _want_ it to be able to take on a value from the .config that was read in.

If we want to be perfectly clear, we'd rename SYMBOL_HIDDEN to SYMBOL_TRANSITIONAL and rename sym->visible to sym->assignable or something like that. Then the logic would be:

if (sym->flags & SYMBOL_TRANSITIONAL)

    sym->assignable = yes;

which is much more intuitive.

Vegard

#9 lore raw
Quoted 10 lines · Vegard Nossum

scripts/kconfig/symbol.c static void sym_calc_visibility(struct symbol *sym) · line 218

Quoted 6 lines · Vegard Nossum
scripts/kconfig/symbol.c +2 -0
@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)
          struct property *prop;
          tristate tri;
 
+       if (sym->flags & SYMBOL_HIDDEN) {
+               sym->visible = yes;

...I just saw the irony here after having already pressed "Send".

Quoted 8 lines

Let me explain:

SYMBOL_HIDDEN is your new flag that indicates that somebody used "transitional" on the config entry.

sym->visible is tristate value that gives you the condition for whether a symbol can take on a value -- y/m means the option is visible to the user (hence the name) and thus eligible to have a value assigned to it.

Another small clarification: Replace "is visible to the user" by "can be set by .config".

Actual user visibility is controlled by menu_is_visible(), not sym->visible, so my patch still doesn't show transitional symbols to the user in menuconfig. AFAICT, menu_is_visible() is completely independent of sym->visible.

I tested menuconfig/mconf and oldconfig/conf --oldconfig with scripts/ kconfig/tests/transitional/Kconfig and my patch and it looks correct (only the new options are displayed).

Vegard

author
#10 lore raw
Quoted 21 lines · Vegard Nossum
Quoted 10 lines · Vegard Nossum

scripts/kconfig/symbol.c static void sym_calc_visibility(struct symbol *sym) · line 218

Quoted 6 lines · Vegard Nossum
scripts/kconfig/symbol.c +2 -0
@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)
          struct property *prop;
          tristate tri;
 
+       if (sym->flags & SYMBOL_HIDDEN) {
+               sym->visible = yes;

...I just saw the irony here after having already pressed "Send".

Quoted 8 lines

Let me explain:

SYMBOL_HIDDEN is your new flag that indicates that somebody used "transitional" on the config entry.

sym->visible is tristate value that gives you the condition for whether a symbol can take on a value -- y/m means the option is visible to the user (hence the name) and thus eligible to have a value assigned to it.

Quoted 7 lines

Another small clarification: Replace "is visible to the user" by "can be set by .config".

Actual user visibility is controlled by menu_is_visible(), not sym->visible, so my patch still doesn't show transitional symbols to the user in menuconfig. AFAICT, menu_is_visible() is completely independent of sym->visible.

Yeah, and I think this is another very good reason to rename stuff.

Quoted 3 lines

I tested menuconfig/mconf and oldconfig/conf --oldconfig with scripts/ kconfig/tests/transitional/Kconfig and my patch and it looks correct (only the new options are displayed).

Great! Thank you for looking at this. :)

Signature · 1 line
Kees Cook
author
#11 lore raw
In reply to Vegard Nossum
Quoted 21 lines · Vegard Nossum
Quoted 11 lines · Kees Cook
Quoted 3 lines · Vegard Nossum

If you change sym_calc_visibility() to always return 'yes' for transitional values then I don't think you need to touch sym_calc_value() at all.

Hm, it looks like sym_calc_visibility() doesn't strictly just look at visibility. And visibility seems to "last"? And I think the "tri" still can't just be "yes", don't we need the other stuff handled?

Do you see a way to do it how you're suggesting? And now I wrote the regression tests so we can test any alternatives! ;)

Here's what I had in mind (on top of your kcfi patchset), see the attachment.

It basically undoes all your additions to sym_calc_value() in favour of two straightforward additions:

scripts/kconfig/symbol.c static void sym_calc_visibility(struct symbol *sym) · line 221

Quoted 8 lines
scripts/kconfig/symbol.c +5 -0
@@ -214,6 +214,11 @@ static void sym_calc_visibility(struct symbol *sym)
        struct property *prop;
        tristate tri;
 
+       if (sym->flags & SYMBOL_HIDDEN) {
+               sym->visible = yes;
+               return;
+       }
+

If this doesn't break "m" choices and the config doesn't show up in menuconfig, then yeah, I can do this.

I'll give it a spin!

Signature · 1 line
Kees Cook
author
#12 lore raw
Quoted 1 line · Kees Cook

I'll give it a spin!

*one month later*

I've v3 now with your implementation and lots of selftests. :)

-Kees

Signature · 1 line
Kees Cook
#13 lore raw
In reply to Kees Cook
Quoted 19 lines · Kees Cook

During kernel option migrations (e.g. CONFIG_CFI_CLANG to CONFIG_CFI), existing .config files need to maintain backward compatibility while preventing deprecated options from appearing in newly generated configurations. This is challenging with existing Kconfig mechanisms because:

1. Simply removing old options breaks existing .config files.
2. Manually listing an option as "deprecated" leaves it needlessly visible and still writes them to new .config files.
3. Using any method to remove visibility (.e.g no 'prompt', 'if n', etc) prevents the option from being processed at all.

Add a "transitional" attribute that creates symbols which are:
- Processed during configuration (can influence other symbols' defaults)
- Hidden from user menus (no prompts appear)
- Omitted from newly written .config files (gets migrated)
- Restricted to only having help sections (no defaults, selects, etc) making it truly just a "prior value pass-through" option.

Quoted 7 lines

The transitional syntax requires a type argument and prevents type redefinition:

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

How long do you think we'll need to keep the transitional config options around? Forever?

BR,
Jani.

Quoted 243 lines

config NEW_OPTION
bool "New option"
default OLD_OPTION

This allows seamless migration: olddefconfig processes existing CONFIG_OLD_OPTION=y settings to enable CONFIG_NEW_OPTION=y, while CONFIG_OLD_OPTION is omitted from newly generated .config files.

Implementation details:
- Parser validates transitional symbols can only have help sections
- Symbol visibility logic updated: usable = (visible != no || transitional)
- Transitional symbols preserve user values during configuration
- Type safety enforced to prevent redefinition after transitional declaration
- Used distinct struct members instead of new flags for readability
- Documentation added to show the usage

---
With help from Claude Code to show me how to navigate the kconfig parser.

v2: fixed human-introduced errors
v1: https://lore.kernel.org/all/[email protected]/

Cc Nathan Chancellor <[email protected]>
Cc Nicolas Schier <[email protected]>
Cc Jonathan Corbet <[email protected]>
Cc Masahiro Yamada <[email protected]>
Cc Randy Dunlap <[email protected]>
Cc Arnd Bergmann <[email protected]>
Cc Krzysztof Kozlowski <[email protected]>
---
 scripts/kconfig/expr.h                    | 15 +++++++
 scripts/kconfig/lexer.l                   |  1 +
 scripts/kconfig/parser.y                  | 51 +++++++++++++++++++++++
 scripts/kconfig/symbol.c                  | 11 +++--
 Documentation/kbuild/kconfig-language.rst | 31 ++++++++++++++
 5 files changed, 106 insertions(+), 3 deletions(-)
scripts/kconfig/expr.h +15 -0
index fe2231e0e6a4..be51574d6c77 100644
@@ -127,6 +127,21 @@ struct symbol {
 	/* SYMBOL_* flags */
 	int flags;
 
+	/*
+	 * Transitional symbol - processed during configuration but hidden from
+	 * user in menus and omitted from newly written .config files. Used for
+	 * backward compatibility during config option migrations (e.g.,
+	 * CFI_CLANG → CFI). Transitional symbols can still influence default
+	 * expressions of other symbols.
+	 */
+	bool transitional:1;
+
+	/*
+	 * Symbol usability - calculated as (visible != no || transitional).
+	 * Determines if symbol can be used in expressions.
+	 */
+	bool usable:1;
+
 	/* List of properties. See prop_type. */
 	struct property *prop;
 
scripts/kconfig/lexer.l +1 -0
index 9c2cdfc33c6f..6d2c92c6095d 100644
@@ -126,6 +126,7 @@ n	[A-Za-z0-9_-]
 "select"		return T_SELECT;
 "source"		return T_SOURCE;
 "string"		return T_STRING;
+"transitional"		return T_TRANSITIONAL;
 "tristate"		return T_TRISTATE;
 "visible"		return T_VISIBLE;
 "||"			return T_OR;
scripts/kconfig/parser.y +51 -0
index e9c3c664e925..01d2d0f720ce 100644
@@ -75,6 +75,7 @@ struct menu *current_menu, *current_entry, *current_choice;
 %token T_SELECT
 %token T_SOURCE
 %token T_STRING
+%token T_TRANSITIONAL
 %token T_TRISTATE
 %token T_VISIBLE
 %token T_EOL
@@ -205,6 +206,16 @@ config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL
 	printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno);
 };
 
+config_option: T_TRANSITIONAL type T_EOL
+{
+	if (current_entry->sym->type != S_UNKNOWN)
+		yyerror("transitional type cannot be set after symbol type is already defined");
+	menu_set_type($2);
+	current_entry->sym->transitional = true;
+	printd(DEBUG_PARSE, "%s:%d:transitional(%u)\n", cur_filename, cur_lineno,
+		$2);
+};
+
 config_option: default expr if_expr T_EOL
 {
 	menu_add_expr(P_DEFAULT, $2, $3);
@@ -482,6 +493,43 @@ assign_val:
 
 %%
 
+/**
+ * transitional_check_sanity - check transitional symbols have no other
+ *			       properties
+ *
+ * @menu: menu of the potentially transitional symbol
+ *
+ * Return: -1 if an error is found, 0 otherwise.
+ */
+static int transitional_check_sanity(const struct menu *menu)
+{
+	struct property *prop;
+
+	if (!menu->sym || !menu->sym->transitional)
+		return 0;
+
+	/* Check for depends and visible conditions. */
+	if ((menu->dep && !expr_is_yes(menu->dep)) ||
+	    (menu->visibility && !expr_is_yes(menu->visibility))) {
+		fprintf(stderr, "%s:%d: error: %s",
+			menu->filename, menu->lineno,
+			"transitional symbols can only have help sections\n");
+		return -1;
+	}
+
+	/* Check for any property other than "help". */
+	for (prop = menu->sym->prop; prop; prop = prop->next) {
+		if (prop->type != P_COMMENT) {
+			fprintf(stderr, "%s:%d: error: %s",
+				prop->filename, prop->lineno,
+				"transitional symbols can only have help sections\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
 /**
  * choice_check_sanity - check sanity of a choice member
  *
@@ -558,6 +606,9 @@ void conf_parse(const char *name)
 		if (menu->sym && sym_check_deps(menu->sym))
 			yynerrs++;
 
+		if (transitional_check_sanity(menu))
+			yynerrs++;
+
 		if (menu->sym && sym_is_choice(menu->sym)) {
 			menu_for_each_sub_entry(child, menu)
 				if (child->sym && choice_check_sanity(child))
scripts/kconfig/symbol.c +8 -3
index 26ab10c0fd76..b822c0c897e5 100644
@@ -447,6 +447,9 @@ void sym_calc_value(struct symbol *sym)
 	if (sym->visible != no)
 		sym->flags |= SYMBOL_WRITE;
 
+	/* Calculate usable flag */
+	sym->usable = (sym->visible != no || sym->transitional);
+
 	/* set default if recursively called */
 	sym->curr = newval;
 
@@ -459,13 +462,15 @@ void sym_calc_value(struct symbol *sym)
 			sym_calc_choice(choice_menu);
 			newval.tri = sym->curr.tri;
 		} else {
-			if (sym->visible != no) {
+			if (sym->usable) {
 				/* if the symbol is visible use the user value
 				 * if available, otherwise try the default value
 				 */
 				if (sym_has_value(sym)) {
+					tristate value = sym->transitional ?
+						sym->def[S_DEF_USER].tri : sym->visible;
 					newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
-							      sym->visible);
+							      value);
 					goto calc_newval;
 				}
 			}
@@ -497,7 +502,7 @@ void sym_calc_value(struct symbol *sym)
 	case S_STRING:
 	case S_HEX:
 	case S_INT:
-		if (sym->visible != no && sym_has_value(sym)) {
+		if (sym->usable && sym_has_value(sym)) {
 			newval.val = sym->def[S_DEF_USER].val;
 			break;
 		}
Documentation/kbuild/kconfig-language.rst +31 -0
index a91abb8f6840..345c334ce680 100644
@@ -232,6 +232,37 @@ applicable everywhere (see syntax).
   enables the third modular state for all config symbols.
   At most one symbol may have the "modules" option set.
 
+- transitional attribute: "transitional"
+  This declares the symbol as transitional, meaning it should be processed
+  during configuration but omitted from newly written .config files.
+  Transitional symbols are useful for backward compatibility during config
+  option migrations - they allow olddefconfig to process existing .config
+  files while ensuring the old option doesn't appear in new configurations.
+
+  A transitional symbol:
+  - Has no prompt (is not visible to users in menus)
+  - Is processed normally during configuration (values are read and used)
+  - Can be referenced in default expressions of other symbols
+  - Is not written to new .config files
+  - Cannot have any other properties (it is a pass-through option)
+
+  Example migration from OLD_NAME to NEW_NAME::
+
+    config NEW_NAME
+	bool "New option name"
+	default OLD_NAME
+	help
+	  This replaces the old CONFIG_OLD_NAME option.
+
+    config OLD_NAME
+	transitional bool
+	help
+	  Transitional config for OLD_NAME to NEW_NAME migration.
+
+  With this setup, existing .config files with "CONFIG_OLD_NAME=y" will
+  result in "CONFIG_NEW_NAME=y" being set, while CONFIG_OLD_NAME will be
+  omitted from newly written .config files.
+
 Menu dependencies
 -----------------
Signature · 1 line
Jani Nikula, Intel
author
#14 lore raw
Quoted 11 lines · Jani Nikula
Quoted 7 lines · Kees Cook

The transitional syntax requires a type argument and prevents type redefinition:

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

How long do you think we'll need to keep the transitional config options around? Forever?

As with the "manual" transitions, it'd probably be until the next LTS is released.

Signature · 1 line
Kees Cook
reviewer
#15 lore raw
In reply to Kees Cook

Hi Kees,

Quoted 43 lines · Kees Cook

During kernel option migrations (e.g. CONFIG_CFI_CLANG to CONFIG_CFI), existing .config files need to maintain backward compatibility while preventing deprecated options from appearing in newly generated configurations. This is challenging with existing Kconfig mechanisms because:

1. Simply removing old options breaks existing .config files.
2. Manually listing an option as "deprecated" leaves it needlessly visible and still writes them to new .config files.
3. Using any method to remove visibility (.e.g no 'prompt', 'if n', etc) prevents the option from being processed at all.

Add a "transitional" attribute that creates symbols which are:
- Processed during configuration (can influence other symbols' defaults)
- Hidden from user menus (no prompts appear)
- Omitted from newly written .config files (gets migrated)
- Restricted to only having help sections (no defaults, selects, etc) making it truly just a "prior value pass-through" option.

The transitional syntax requires a type argument and prevents type redefinition:

config OLD_OPTION
transitional bool
help
Transitional config for OLD_OPTION migration.

config NEW_OPTION
bool "New option"
default OLD_OPTION

This allows seamless migration: olddefconfig processes existing CONFIG_OLD_OPTION=y settings to enable CONFIG_NEW_OPTION=y, while CONFIG_OLD_OPTION is omitted from newly generated .config files.

Implementation details:
- Parser validates transitional symbols can only have help sections
- Symbol visibility logic updated: usable = (visible != no || transitional)
- Transitional symbols preserve user values during configuration
- Type safety enforced to prevent redefinition after transitional declaration
- Used distinct struct members instead of new flags for readability
- Documentation added to show the usage

Quoted 3 lines

---
With help from Claude Code to show me how to navigate the kconfig parser.

Are you (implicitly?) saying that all previous attempts at transitional kconfig symbols have failed? If so, I just wasn't aware of that.

Or is there some new prime directive that requires this?

Thanks.

Signature · 1 line
~Randy
author
#16 lore raw
Quoted 2 lines · Randy Dunlap

Are you (implicitly?) saying that all previous attempts at transitional kconfig symbols have failed? If so, I just wasn't aware of that.

I haven't found any way to do a "proper" CONFIG transition. I looked through past transitions and they all left stuff visible. Is there a way to actually do this with existing kconfig?

Signature · 1 line
Kees Cook
reviewer
#17 lore raw
Quoted 8 lines · Kees Cook
Quoted 2 lines · Randy Dunlap

Are you (implicitly?) saying that all previous attempts at transitional kconfig symbols have failed? If so, I just wasn't aware of that.

I haven't found any way to do a "proper" CONFIG transition. I looked through past transitions and they all left stuff visible. Is there a way to actually do this with existing kconfig?

I don't know. I haven't looked as hard at it as you have. I just didn't realize that there had been significant failures that required kconfig code changes.

I'm just trying to understand. Carry on.

Signature · 1 line
~Randy