Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 NEW_OPTION
bool "New option"
default OLD_OPTION

config OLD_OPTION
bool
transitional
help
Transitional config for OLD_OPTION migration.

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.

Added positive and negative testing via "testconfig" make target.

Co-developed-by: Vegard Nossum <vegard.nossum@oracle.com>
Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
Tested-by: Nathan Chancellor <nathan@kernel.org>
Link: https://lore.kernel.org/r/20250923213422.1105654-2-kees@kernel.org
Signed-off-by: Kees Cook <kees@kernel.org>

Kees Cook f9afce4f 64f4ea20

+306 -1
+32
Documentation/kbuild/kconfig-language.rst
··· 232 232 enables the third modular state for all config symbols. 233 233 At most one symbol may have the "modules" option set. 234 234 235 + - transitional attribute: "transitional" 236 + This declares the symbol as transitional, meaning it should be processed 237 + during configuration but omitted from newly written .config files. 238 + Transitional symbols are useful for backward compatibility during config 239 + option migrations - they allow olddefconfig to process existing .config 240 + files while ensuring the old option doesn't appear in new configurations. 241 + 242 + A transitional symbol: 243 + - Has no prompt (is not visible to users in menus) 244 + - Is processed normally during configuration (values are read and used) 245 + - Can be referenced in default expressions of other symbols 246 + - Is not written to new .config files 247 + - Cannot have any other properties (it is a pass-through option) 248 + 249 + Example migration from OLD_NAME to NEW_NAME:: 250 + 251 + config NEW_NAME 252 + bool "New option name" 253 + default OLD_NAME 254 + help 255 + This replaces the old CONFIG_OLD_NAME option. 256 + 257 + config OLD_NAME 258 + bool 259 + transitional 260 + help 261 + Transitional config for OLD_NAME to NEW_NAME migration. 262 + 263 + With this setup, existing .config files with "CONFIG_OLD_NAME=y" will 264 + result in "CONFIG_NEW_NAME=y" being set, while CONFIG_OLD_NAME will be 265 + omitted from newly written .config files. 266 + 235 267 Menu dependencies 236 268 ----------------- 237 269
+1
scripts/kconfig/expr.h
··· 145 145 #define SYMBOL_CONST 0x0001 /* symbol is const */ 146 146 #define SYMBOL_CHECK 0x0008 /* used during dependency checking */ 147 147 #define SYMBOL_VALID 0x0080 /* set when symbol.curr is calculated */ 148 + #define SYMBOL_TRANS 0x0100 /* symbol is transitional only (not visible)*/ 148 149 #define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */ 149 150 #define SYMBOL_WRITTEN 0x0800 /* track info to avoid double-write to .config */ 150 151 #define SYMBOL_CHECKED 0x2000 /* used during dependency checking */
+1
scripts/kconfig/lexer.l
··· 126 126 "select" return T_SELECT; 127 127 "source" return T_SOURCE; 128 128 "string" return T_STRING; 129 + "transitional" return T_TRANSITIONAL; 129 130 "tristate" return T_TRISTATE; 130 131 "visible" return T_VISIBLE; 131 132 "||" return T_OR;
+47
scripts/kconfig/parser.y
··· 75 75 %token T_SELECT 76 76 %token T_SOURCE 77 77 %token T_STRING 78 + %token T_TRANSITIONAL 78 79 %token T_TRISTATE 79 80 %token T_VISIBLE 80 81 %token T_EOL ··· 204 203 { 205 204 menu_add_prompt(P_PROMPT, $2, $3); 206 205 printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); 206 + }; 207 + 208 + config_option: T_TRANSITIONAL T_EOL 209 + { 210 + current_entry->sym->flags |= SYMBOL_TRANS; 211 + printd(DEBUG_PARSE, "%s:%d:transitional\n", cur_filename, cur_lineno); 207 212 }; 208 213 209 214 config_option: default expr if_expr T_EOL ··· 490 483 %% 491 484 492 485 /** 486 + * transitional_check_sanity - check transitional symbols have no other 487 + * properties 488 + * 489 + * @menu: menu of the potentially transitional symbol 490 + * 491 + * Return: -1 if an error is found, 0 otherwise. 492 + */ 493 + static int transitional_check_sanity(const struct menu *menu) 494 + { 495 + struct property *prop; 496 + 497 + if (!menu->sym || !(menu->sym->flags & SYMBOL_TRANS)) 498 + return 0; 499 + 500 + /* Check for depends and visible conditions. */ 501 + if ((menu->dep && !expr_is_yes(menu->dep)) || 502 + (menu->visibility && !expr_is_yes(menu->visibility))) { 503 + fprintf(stderr, "%s:%d: error: %s", 504 + menu->filename, menu->lineno, 505 + "transitional symbols can only have help sections\n"); 506 + return -1; 507 + } 508 + 509 + /* Check for any property other than "help". */ 510 + for (prop = menu->sym->prop; prop; prop = prop->next) { 511 + if (prop->type != P_COMMENT) { 512 + fprintf(stderr, "%s:%d: error: %s", 513 + prop->filename, prop->lineno, 514 + "transitional symbols can only have help sections\n"); 515 + return -1; 516 + } 517 + } 518 + 519 + return 0; 520 + } 521 + 522 + /** 493 523 * choice_check_sanity - check sanity of a choice member 494 524 * 495 525 * @menu: menu of the choice member ··· 600 556 struct menu *child; 601 557 602 558 if (menu->sym && sym_check_deps(menu->sym)) 559 + yynerrs++; 560 + 561 + if (transitional_check_sanity(menu)) 603 562 yynerrs++; 604 563 605 564 if (menu->sym && sym_is_choice(menu->sym)) {
+6 -1
scripts/kconfig/symbol.c
··· 214 214 struct property *prop; 215 215 tristate tri; 216 216 217 + if (sym->flags & SYMBOL_TRANS) { 218 + sym->visible = yes; 219 + return; 220 + } 221 + 217 222 /* any prompt visible? */ 218 223 tri = no; 219 224 for_all_prompts(sym, prop) { ··· 531 526 } 532 527 } 533 528 534 - if (sym_is_choice(sym)) 529 + if (sym_is_choice(sym) || sym->flags & SYMBOL_TRANS) 535 530 sym->flags &= ~SYMBOL_WRITE; 536 531 } 537 532
+52
scripts/kconfig/tests/err_transitional/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # Test that transitional symbols cannot have properties other than help 3 + 4 + config BAD_DEFAULT 5 + bool 6 + transitional 7 + default y 8 + help 9 + This transitional symbol illegally has a default property. 10 + 11 + config BAD_PROMPT 12 + bool 13 + transitional 14 + prompt "Bad prompt" 15 + help 16 + This transitional symbol illegally has a prompt. 17 + 18 + config BAD_SELECT 19 + bool 20 + transitional 21 + select OTHER_SYMBOL 22 + help 23 + This transitional symbol illegally has a select. 24 + 25 + config BAD_IMPLY 26 + bool 27 + transitional 28 + imply OTHER_SYMBOL 29 + help 30 + This transitional symbol illegally has an imply. 31 + 32 + config BAD_DEPENDS 33 + bool 34 + transitional 35 + depends on OTHER_SYMBOL 36 + help 37 + This transitional symbol illegally has a depends. 38 + 39 + config BAD_RANGE 40 + int 41 + transitional 42 + range 1 10 43 + help 44 + This transitional symbol illegally has a range. 45 + 46 + config BAD_NO_TYPE 47 + transitional 48 + help 49 + This transitional symbol illegally has no type specified. 50 + 51 + config OTHER_SYMBOL 52 + bool
+14
scripts/kconfig/tests/err_transitional/__init__.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + """ 3 + Test that transitional symbols with invalid properties are rejected. 4 + 5 + Transitional symbols can only have help sections. Any other properties 6 + (default, select, depends, etc.) should cause a parser error. 7 + """ 8 + 9 + def test(conf): 10 + # This should fail with exit code 1 due to invalid transitional symbol 11 + assert conf.olddefconfig() == 1 12 + 13 + # Check that the error message is about transitional symbols 14 + assert conf.stderr_contains('expected_stderr')
+7
scripts/kconfig/tests/err_transitional/expected_stderr
··· 1 + Kconfig:46:warning: config symbol defined without type 2 + Kconfig:7: error: transitional symbols can only have help sections 3 + Kconfig:14: error: transitional symbols can only have help sections 4 + Kconfig:21: error: transitional symbols can only have help sections 5 + Kconfig:28: error: transitional symbols can only have help sections 6 + Kconfig:32: error: transitional symbols can only have help sections 7 + Kconfig:42: error: transitional symbols can only have help sections
+100
scripts/kconfig/tests/transitional/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # Test transitional symbols for config migration with all Kconfig types 3 + 4 + # Enable module support for tristate testing 5 + config MODULES 6 + bool "Enable loadable module support" 7 + modules 8 + default y 9 + 10 + # Basic migration tests for all types 11 + config NEW_BOOL 12 + bool "New bool option" 13 + default OLD_BOOL 14 + 15 + config OLD_BOOL 16 + bool 17 + transitional 18 + 19 + config NEW_TRISTATE 20 + tristate "New tristate option" 21 + default OLD_TRISTATE 22 + 23 + config OLD_TRISTATE 24 + tristate 25 + transitional 26 + 27 + config NEW_STRING 28 + string "New string option" 29 + default OLD_STRING 30 + 31 + config OLD_STRING 32 + string 33 + transitional 34 + 35 + config NEW_HEX 36 + hex "New hex option" 37 + default OLD_HEX 38 + 39 + config OLD_HEX 40 + hex 41 + transitional 42 + 43 + config NEW_INT 44 + int "New int option" 45 + default OLD_INT 46 + 47 + config OLD_INT 48 + int 49 + transitional 50 + 51 + # Precedence tests for all types 52 + config NEW_BOOL_PRECEDENCE 53 + bool "New bool option with precedence" 54 + default OLD_BOOL_PRECEDENCE 55 + 56 + config OLD_BOOL_PRECEDENCE 57 + bool 58 + transitional 59 + 60 + config NEW_STRING_PRECEDENCE 61 + string "New string option with precedence" 62 + default OLD_STRING_PRECEDENCE 63 + 64 + config OLD_STRING_PRECEDENCE 65 + string 66 + transitional 67 + 68 + config NEW_TRISTATE_PRECEDENCE 69 + tristate "New tristate option with precedence" 70 + default OLD_TRISTATE_PRECEDENCE 71 + 72 + config OLD_TRISTATE_PRECEDENCE 73 + tristate 74 + transitional 75 + 76 + config NEW_HEX_PRECEDENCE 77 + hex "New hex option with precedence" 78 + default OLD_HEX_PRECEDENCE 79 + 80 + config OLD_HEX_PRECEDENCE 81 + hex 82 + transitional 83 + 84 + config NEW_INT_PRECEDENCE 85 + int "New int option with precedence" 86 + default OLD_INT_PRECEDENCE 87 + 88 + config OLD_INT_PRECEDENCE 89 + int 90 + transitional 91 + 92 + # Test that help sections are allowed for transitional symbols 93 + config OLD_WITH_HELP 94 + bool 95 + transitional 96 + help 97 + This transitional symbol has a help section to validate that help is allowed. 98 + 99 + config REGULAR_OPTION 100 + bool "Regular option"
+18
scripts/kconfig/tests/transitional/__init__.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + """ 3 + Test transitional symbol migration functionality for all Kconfig types. 4 + 5 + This tests that: 6 + - OLD_* options in existing .config cause NEW_* options to be set 7 + - OLD_* options are not written to the new .config file 8 + - NEW_* options appear in the new .config file with correct values 9 + - All Kconfig types work correctly: bool, tristate, string, hex, int 10 + - User-set NEW values take precedence over conflicting OLD transitional values 11 + """ 12 + 13 + def test(conf): 14 + # Run olddefconfig to process the migration with the initial config 15 + assert conf.olddefconfig(dot_config='initial_config') == 0 16 + 17 + # Check that the configuration matches expected output 18 + assert conf.config_contains('expected_config')
+12
scripts/kconfig/tests/transitional/expected_config
··· 1 + CONFIG_MODULES=y 2 + CONFIG_NEW_BOOL=y 3 + CONFIG_NEW_TRISTATE=m 4 + CONFIG_NEW_STRING="test string" 5 + CONFIG_NEW_HEX=0x1234 6 + CONFIG_NEW_INT=42 7 + # CONFIG_NEW_BOOL_PRECEDENCE is not set 8 + CONFIG_NEW_STRING_PRECEDENCE="user value" 9 + CONFIG_NEW_TRISTATE_PRECEDENCE=y 10 + CONFIG_NEW_HEX_PRECEDENCE=0xABCD 11 + CONFIG_NEW_INT_PRECEDENCE=100 12 + # CONFIG_REGULAR_OPTION is not set
+16
scripts/kconfig/tests/transitional/initial_config
··· 1 + CONFIG_MODULES=y 2 + CONFIG_OLD_BOOL=y 3 + CONFIG_OLD_TRISTATE=m 4 + CONFIG_OLD_STRING="test string" 5 + CONFIG_OLD_HEX=0x1234 6 + CONFIG_OLD_INT=42 7 + # CONFIG_NEW_BOOL_PRECEDENCE is not set 8 + CONFIG_OLD_BOOL_PRECEDENCE=y 9 + CONFIG_NEW_STRING_PRECEDENCE="user value" 10 + CONFIG_OLD_STRING_PRECEDENCE="old value" 11 + CONFIG_NEW_TRISTATE_PRECEDENCE=y 12 + CONFIG_OLD_TRISTATE_PRECEDENCE=m 13 + CONFIG_NEW_HEX_PRECEDENCE=0xABCD 14 + CONFIG_OLD_HEX_PRECEDENCE=0x5678 15 + CONFIG_NEW_INT_PRECEDENCE=100 16 + CONFIG_OLD_INT_PRECEDENCE=200