public inbox for linux-doc@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] scripts: add kconfirm
@ 2026-04-27 17:44 Julian Braha
  2026-04-27 17:44 ` [RFC PATCH 1/2] " Julian Braha
  2026-04-27 17:44 ` [RFC PATCH 2/2] Documentation: dev-tools: " Julian Braha
  0 siblings, 2 replies; 15+ messages in thread
From: Julian Braha @ 2026-04-27 17:44 UTC (permalink / raw)
  To: akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild, Julian Braha

Hi all,

Following this discussion:
https://lore.kernel.org/all/20260405122749.4990dcb538d457769a3276e0@linux-foundation.org/
in which Andrew brought up the possibility of moving kconfirm in-tree,
I've prepared this RFC to do so. See also kconfirm's introduction to the
mailing list:
https://lore.kernel.org/all/6ec4df6d-1445-48ca-8f54-1d1a83c4716d@gmail.com/

kconfirm currently detects dead code (defaults, dependencies, selects, and
ranges), and includes an optional check for dead links in the help texts.

False Alarms:
kconfirm aims for zero false-positives, which is currently true for dead
code detection (as far as I'm aware - but there are hundreds to go 
through); this is not really possible for dead link checks, as this
depends on an internet connection, and we do not attempt to bypass bot
blocks. For this reason, dead link checking is disabled by default, but
I've provided an example of how to enable it. Additionally, you can view
my previous message to the mailing list with hand-verified dead links
here:
https://lore.kernel.org/all/6732bf08-41ee-40c4-83b2-4ae8bc0da7cf@gmail.com/

Current State of Alarms:
The last time I checked linux-next (next-20260427), there were 579
instances of dead code, and 81 unique dead links. The most critical check
is the dead default statements, which has surfaced a few misconfiguration
bugs (fortunately, just for kunit tests), see examples:
https://lore.kernel.org/all/20260323124118.1414913-1-julianbraha@gmail.com/
and:
https://lore.kernel.org/all/20260323123536.1413732-1-julianbraha@gmail.com/

But hopefully kconfirm can ease maintenance and we can prevent more of
these from making it into the tree in the future.

Use it:
You can test out kconfirm with this patch series by compiling and running
kconfirm like this:

`make kconfirm`

You can enable dead link checks in the help texts by
passing KCONFIRM_ARGS="--enable dead_links", like this:

`KCONFIRM_ARGS="--enable dead_links" make kconfirm`

Note that it is not architecture-specific; it runs tree-wide.
If you run it on linux-next, you should find 579 instances of dead code.
Originally this number was even higher, but many patches have since been
applied to linux-next, and included in linux 7.1-rc1. Thank you to the
maintainers and reviewers for their feedback and patience :)

You will need Rust with Cargo and an internet connection to download the
dependencies for compilation. Originally, I planned to vendor the
dependencies and submit the entirety of the code here, in-tree, but the
dependencies (and their dependencies...) are too large (somehow, a
whopping 264MB!), so instead I am proposing to add just the tool's own
code.

I've included the Rust for Linux team to discuss the build system changes,
as I'd like know if there is a better way to integrate this with `make`,
and if there's a better solution as far as the dependencies and Cargo go.

Thanks,
Julian Braha

Julian Braha (2):
  scripts: add kconfirm
  Documentation: dev-tools: add kconfirm

 Documentation/dev-tools/index.rst             |    1 +
 Documentation/dev-tools/kconfirm.rst          |  147 ++
 Makefile                                      |   12 +-
 scripts/Makefile                              |    2 +-
 scripts/kconfirm/Cargo.lock                   | 1710 +++++++++++++++++
 scripts/kconfirm/Cargo.toml                   |   21 +
 scripts/kconfirm/Makefile                     |   28 +
 scripts/kconfirm/kconfirm-lib/Cargo.toml      |   16 +
 scripts/kconfirm/kconfirm-lib/src/analyze.rs  |  593 ++++++
 scripts/kconfirm/kconfirm-lib/src/checks.rs   |  257 +++
 .../kconfirm/kconfirm-lib/src/dead_links.rs   |   63 +
 scripts/kconfirm/kconfirm-lib/src/lib.rs      |   55 +
 scripts/kconfirm/kconfirm-lib/src/output.rs   |   52 +
 .../kconfirm/kconfirm-lib/src/symbol_table.rs |  209 ++
 scripts/kconfirm/kconfirm-linux/Cargo.toml    |   14 +
 scripts/kconfirm/kconfirm-linux/src/lib.rs    |  129 ++
 scripts/kconfirm/kconfirm-linux/src/main.rs   |   74 +
 17 files changed, 3379 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/dev-tools/kconfirm.rst
 create mode 100644 scripts/kconfirm/Cargo.lock
 create mode 100644 scripts/kconfirm/Cargo.toml
 create mode 100644 scripts/kconfirm/Makefile
 create mode 100644 scripts/kconfirm/kconfirm-lib/Cargo.toml
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/analyze.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/checks.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/dead_links.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/lib.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/output.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/symbol_table.rs
 create mode 100644 scripts/kconfirm/kconfirm-linux/Cargo.toml
 create mode 100644 scripts/kconfirm/kconfirm-linux/src/lib.rs
 create mode 100644 scripts/kconfirm/kconfirm-linux/src/main.rs

-- 
2.53.0


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [RFC PATCH 1/2] scripts: add kconfirm
  2026-04-27 17:44 [RFC PATCH 0/2] scripts: add kconfirm Julian Braha
@ 2026-04-27 17:44 ` Julian Braha
  2026-04-27 20:48   ` Greg KH
  2026-04-28  7:01   ` Jonathan Corbet
  2026-04-27 17:44 ` [RFC PATCH 2/2] Documentation: dev-tools: " Julian Braha
  1 sibling, 2 replies; 15+ messages in thread
From: Julian Braha @ 2026-04-27 17:44 UTC (permalink / raw)
  To: akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild, Julian Braha

Add kconfirm into scripts/ and modify the root Makefile and
scripts/Makefile accordingly so that it can be compiled and run with:
`make kconfirm`
from the root of the tree.

The user is expected to have Rust with Cargo installed, and an internet
connection on the first run to download and compile the dependencies.

Signed-off-by: Julian Braha <julianbraha@gmail.com>
---
 Makefile                                      |   12 +-
 scripts/Makefile                              |    2 +-
 scripts/kconfirm/Cargo.lock                   | 1710 +++++++++++++++++
 scripts/kconfirm/Cargo.toml                   |   21 +
 scripts/kconfirm/Makefile                     |   28 +
 scripts/kconfirm/kconfirm-lib/Cargo.toml      |   16 +
 scripts/kconfirm/kconfirm-lib/src/analyze.rs  |  593 ++++++
 scripts/kconfirm/kconfirm-lib/src/checks.rs   |  257 +++
 .../kconfirm/kconfirm-lib/src/dead_links.rs   |   63 +
 scripts/kconfirm/kconfirm-lib/src/lib.rs      |   55 +
 scripts/kconfirm/kconfirm-lib/src/output.rs   |   52 +
 .../kconfirm/kconfirm-lib/src/symbol_table.rs |  209 ++
 scripts/kconfirm/kconfirm-linux/Cargo.toml    |   14 +
 scripts/kconfirm/kconfirm-linux/src/lib.rs    |  129 ++
 scripts/kconfirm/kconfirm-linux/src/main.rs   |   74 +
 15 files changed, 3231 insertions(+), 4 deletions(-)
 create mode 100644 scripts/kconfirm/Cargo.lock
 create mode 100644 scripts/kconfirm/Cargo.toml
 create mode 100644 scripts/kconfirm/Makefile
 create mode 100644 scripts/kconfirm/kconfirm-lib/Cargo.toml
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/analyze.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/checks.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/dead_links.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/lib.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/output.rs
 create mode 100644 scripts/kconfirm/kconfirm-lib/src/symbol_table.rs
 create mode 100644 scripts/kconfirm/kconfirm-linux/Cargo.toml
 create mode 100644 scripts/kconfirm/kconfirm-linux/src/lib.rs
 create mode 100644 scripts/kconfirm/kconfirm-linux/src/main.rs

diff --git a/Makefile b/Makefile
index 54e1ae602000..8fcee448111b 100644
--- a/Makefile
+++ b/Makefile
@@ -1707,13 +1707,16 @@ MRPROPER_FILES += include/config include/generated          \
 #
 clean: private rm-files := $(CLEAN_FILES)
 
-PHONY += archclean vmlinuxclean
+PHONY += archclean vmlinuxclean kconfirm_clean
 
 vmlinuxclean:
 	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean
 	$(Q)$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) clean)
 
-clean: archclean vmlinuxclean resolve_btfids_clean objtool_clean
+kconfirm_clean:
+	$(Q)$(MAKE) -C scripts/kconfirm clean
+
+clean: archclean vmlinuxclean resolve_btfids_clean objtool_clean kconfirm_clean
 
 # mrproper - Delete all generated files, including .config
 #
@@ -2223,7 +2226,7 @@ endif
 # Scripts to check various things for consistency
 # ---------------------------------------------------------------------------
 
-PHONY += includecheck versioncheck coccicheck
+PHONY += includecheck versioncheck coccicheck kconfirm
 
 includecheck:
 	find $(srctree)/* $(RCS_FIND_IGNORE) \
@@ -2238,6 +2241,9 @@ versioncheck:
 coccicheck:
 	$(Q)$(BASH) $(srctree)/scripts/$@
 
+kconfirm:
+	$(Q)$(MAKE) -C scripts/kconfirm
+
 PHONY += checkstack kernelrelease kernelversion image_name
 
 # UML needs a little special treatment here.  It wants to use the host
diff --git a/scripts/Makefile b/scripts/Makefile
index 55244ce95578..402950d9a008 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -57,7 +57,7 @@ HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED
 endif
 
 # The following programs are only built on demand
-hostprogs += unifdef gen_packed_field_checks
+hostprogs += unifdef gen_packed_field_checks kconfirm
 
 # The module linker script is preprocessed on demand
 targets += module.lds
diff --git a/scripts/kconfirm/Cargo.lock b/scripts/kconfirm/Cargo.lock
new file mode 100644
index 000000000000..c1e55ae2ca00
--- /dev/null
+++ b/scripts/kconfirm/Cargo.lock
@@ -0,0 +1,1710 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "aws-lc-rs"
+version = "1.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytecount"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "env_filter"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
+dependencies = [
+ "env_filter",
+ "log",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "iri-string"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jni"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys",
+ "log",
+ "simd_cesu8",
+ "thiserror",
+ "walkdir",
+ "windows-link",
+]
+
+[[package]]
+name = "jni-macros"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kconfirm-lib"
+version = "0.7.0"
+dependencies = [
+ "env_logger",
+ "log",
+ "nom-kconfig",
+ "regex",
+ "reqwest",
+]
+
+[[package]]
+name = "kconfirm-linux"
+version = "0.7.0"
+dependencies = [
+ "clap",
+ "env_logger",
+ "kconfirm-lib",
+ "log",
+ "nom-kconfig",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "mio"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "nom-kconfig"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c27ba674eb4a89c21e046dcaed8552f26c8332ba4951b167ec845d2abb8c95c4"
+dependencies = [
+ "glob",
+ "nom",
+ "nom_locate",
+ "regex",
+ "serde",
+ "tracing",
+]
+
+[[package]]
+name = "nom_locate"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
+dependencies = [
+ "bytecount",
+ "memchr",
+ "nom",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+dependencies = [
+ "aws-lc-rs",
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+
+[[package]]
+name = "reqwest"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e"
+dependencies = [
+ "aws-lc-rs",
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "aws-lc-rs",
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "security-framework"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simd_cesu8"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.52.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "yoke"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/scripts/kconfirm/Cargo.toml b/scripts/kconfirm/Cargo.toml
new file mode 100644
index 000000000000..afde3813a673
--- /dev/null
+++ b/scripts/kconfirm/Cargo.toml
@@ -0,0 +1,21 @@
+[workspace]
+members = ["kconfirm-lib", "kconfirm-linux"]
+resolver = "3"
+
+[workspace.package]
+license = "GPL-2.0-only"
+description = "A static analysis tool for Kconfig"
+keywords = ["kconfig", "static", "analysis", "detect"]
+categories = ["development-tools", "config", "command-line-utilities"]
+exclude = ["assets/"]
+version = "0.7.0"
+edition = "2024"
+rust-version = "1.85.0"
+
+[workspace.dependencies]
+nom-kconfig = "0.10"
+
+log = { version = "0.4", default-features = false }
+env_logger = { version = "0.11", default-features = false }
+
+clap = { version = "4.6", default-features = false, features = ["std", "derive"] }
diff --git a/scripts/kconfirm/Makefile b/scripts/kconfirm/Makefile
new file mode 100644
index 000000000000..6aa8dbe6f885
--- /dev/null
+++ b/scripts/kconfirm/Makefile
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+# kconfirm makefile
+
+TARGET := kconfirm
+
+HAS_CARGO := $(shell command -v cargo 2> /dev/null)
+
+# Extra arguments forwarded to kconfirm.
+# Example: make kconfirm KCONFIRM_ARGS="--enable dead_links"
+KCONFIRM_ARGS ?=
+
+PHONY += all
+all: $(TARGET)
+
+$(TARGET):
+ifdef HAS_CARGO
+	@cargo run --release -p kconfirm-linux -- --linux-path ../../ $(KCONFIRM_ARGS)
+else
+	@echo "Error: Cargo not found. Please install Rust and Cargo to build kconfirm."
+	@false
+endif
+
+clean:
+ifdef HAS_CARGO
+	@cargo clean > /dev/null 2>&1
+else
+	@:
+endif
diff --git a/scripts/kconfirm/kconfirm-lib/Cargo.toml b/scripts/kconfirm/kconfirm-lib/Cargo.toml
new file mode 100644
index 000000000000..4a5274e76665
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "kconfirm-lib"
+version = "0.7.0"
+edition = "2024"
+rust-version.workspace = true
+
+[dependencies]
+nom-kconfig = { workspace = true }
+reqwest = { version = "0.13", default-features = false,  features = ["blocking", "rustls"] }
+regex = { version = "1.12", default-features = false }
+log = { workspace = true }
+env_logger = { workspace = true }
+
+[features]
+default = []
+coreboot = ["nom-kconfig/coreboot"]
diff --git a/scripts/kconfirm/kconfirm-lib/src/analyze.rs b/scripts/kconfirm/kconfirm-lib/src/analyze.rs
new file mode 100644
index 000000000000..d647d1d5537b
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/analyze.rs
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+use crate::AnalysisArgs;
+use crate::Check;
+use crate::SymbolTable;
+use crate::dead_links::{self, LinkStatus, check_link};
+use crate::output::{Finding, Severity};
+use crate::symbol_table::ChoiceData;
+
+use log::{debug, warn};
+use nom_kconfig::attribute::DefaultAttribute;
+use nom_kconfig::attribute::Expression;
+use nom_kconfig::attribute::Select;
+use nom_kconfig::attribute::r#type::Type;
+use nom_kconfig::entry::Choice;
+use nom_kconfig::entry::Config;
+use nom_kconfig::entry::If;
+use nom_kconfig::entry::Menu;
+use nom_kconfig::entry::Source;
+use nom_kconfig::{
+    Attribute::*,
+    Entry::{self},
+};
+use std::collections::HashSet;
+use std::option::Option;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum FunctionalAttributes {
+    // only tracking the attributes that affect the semantics, e.g. not help texts
+    Dependencies,
+    Selects,
+    Implies,
+    Ranges,
+    Defaults,
+}
+
+struct AttributeGroupingChecker {
+    current_group: Option<FunctionalAttributes>,
+    finished_groups: HashSet<FunctionalAttributes>,
+}
+
+impl AttributeGroupingChecker {
+    fn new() -> Self {
+        Self {
+            current_group: None,
+            finished_groups: HashSet::new(),
+        }
+    }
+
+    // doesn't modify `findings` if the style check is disabled
+    fn check(
+        &mut self,
+        group: FunctionalAttributes,
+        args: &AnalysisArgs,
+        findings: &mut Vec<Finding>,
+        symbol: &str,
+        message: String,
+    ) {
+        if !args.is_enabled(Check::Style) {
+            return;
+        }
+
+        match self.current_group {
+            // still contiguous
+            Some(current) if current == group => {}
+
+            // start of group
+            None => {
+                self.current_group = Some(group);
+            }
+
+            Some(current) => {
+                // the previous group finished
+                self.finished_groups.insert(current);
+
+                // we've already finished this group, it's ungrouped
+                if self.finished_groups.contains(&group) {
+                    findings.push(Finding {
+                        severity: Severity::Style,
+                        check: "ungrouped_attribute",
+                        symbol: Some(symbol.to_string()),
+                        message,
+                    });
+                }
+
+                // switch to the new group
+                self.current_group = Some(group);
+            }
+        }
+    }
+}
+
+struct DeadLinkChecker {
+    visited_links: HashSet<String>,
+}
+
+impl DeadLinkChecker {
+    fn new() -> Self {
+        Self {
+            visited_links: HashSet::new(),
+        }
+    }
+
+    fn check_text(
+        &mut self,
+        text: &str,
+        args: &AnalysisArgs,
+        findings: &mut Vec<Finding>,
+        symbol: Option<&str>,
+        context: &str,
+    ) {
+        if !args.is_enabled(Check::DeadLinks) {
+            return;
+        }
+
+        let links = dead_links::find_links(text);
+
+        if links.is_empty() {
+            return;
+        }
+
+        debug!("{} links are: {:?}", context, links);
+
+        for link in links {
+            // avoid rechecking identical links
+            if !self.visited_links.insert(link.clone()) {
+                continue;
+            }
+
+            let status = check_link(&link);
+            if status != LinkStatus::Ok && status != LinkStatus::ProbablyBlocked {
+                findings.push(Finding {
+                    severity: Severity::Warning,
+                    check: "dead_link",
+                    symbol: symbol.map(|s| s.to_string()),
+                    message: format!(
+                        "{} contains link {} with status {:?}",
+                        context, link, status
+                    ),
+                });
+            }
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct Context {
+    pub arch: Option<String>,
+    pub definition_condition: Vec<Expression>,
+    pub visibility: Vec<Expression>,
+    pub dependencies: Vec<Expression>,
+    pub in_choice: bool,
+}
+
+impl Context {
+    fn with_arch(arch: Option<String>) -> Context {
+        Context {
+            arch,
+            definition_condition: vec![],
+            visibility: vec![],
+            dependencies: vec![],
+            in_choice: false,
+        }
+    }
+
+    fn child(&self) -> Self {
+        self.clone()
+    }
+
+    fn with_dep(mut self, dep: Expression) -> Self {
+        self.dependencies.push(dep);
+        self
+    }
+
+    fn with_visibility(mut self, cond: Expression) -> Self {
+        self.visibility.push(cond);
+        self
+    }
+
+    fn with_definition(mut self, cond: Expression) -> Self {
+        self.definition_condition.push(cond);
+        self
+    }
+
+    fn in_choice(mut self) -> Self {
+        self.in_choice = true;
+        self
+    }
+}
+
+fn recurse_entries(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entries: Vec<Entry>,
+    ctx: Context,
+    findings: &mut Vec<Finding>,
+) {
+    for entry in entries {
+        process_entry(args, symtab, entry, ctx.clone(), findings);
+    }
+}
+
+pub fn analyze(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    arch: Option<String>,
+    entries: Vec<Entry>,
+) -> Vec<Finding> {
+    let mut findings = Vec::new();
+
+    let ctx = Context::with_arch(arch);
+
+    recurse_entries(args, symtab, entries, ctx, &mut findings);
+
+    findings
+}
+
+fn handle_config(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: Config,
+    ctx: &Context,
+    findings: &mut Vec<Finding>,
+) {
+    let config_symbol = entry.symbol;
+    debug!(
+        "starting to process config option `config` type: {}",
+        config_symbol
+    );
+
+    let mut child_ctx = ctx.child();
+
+    let mut config_type = None;
+    let mut kconfig_dependencies = Vec::new();
+    let mut kconfig_selects: Vec<Select> = Vec::new();
+    let mut kconfig_ranges = Vec::new();
+    let mut kconfig_defaults = Vec::new();
+
+    debug!("attributes are: {:?}", &entry.attributes);
+    /*
+     * style check: ungrouped attributes
+     * - need to check that dependencies, selects, ranges, and defaults are each kept together.
+     */
+    let mut attribute_grouping_checker = AttributeGroupingChecker::new();
+    let mut dead_link_checker = DeadLinkChecker::new();
+    for attribute in entry.attributes {
+        match attribute {
+            Type(kconfig_type) => match kconfig_type.r#type.clone() {
+                // hybrid type definition and default
+                Type::DefBool(db) => {
+                    let default_attribute: DefaultAttribute = DefaultAttribute {
+                        expression: db.clone(),
+                        r#if: kconfig_type.clone().r#if,
+                    };
+
+                    kconfig_defaults.push(default_attribute);
+                    config_type = Some(kconfig_type);
+
+                    // NOTE: as a style, we prefer to keep the hybrid default-typedef with the standalone defaults
+                    attribute_grouping_checker.check(
+                        FunctionalAttributes::Defaults,
+                        args,
+                        findings,
+                        &config_symbol,
+                        format!("ungrouped default {}", db),
+                    );
+                }
+                Type::Bool(_b) => {
+                    config_type = Some(kconfig_type);
+                }
+
+                // hybrid type definition and default
+                Type::DefTristate(dt) => {
+                    // NOTE: as a style, we prefer to keep the hybrid default-typedef with the standalone defaults
+                    attribute_grouping_checker.check(
+                        FunctionalAttributes::Defaults,
+                        args,
+                        findings,
+                        &config_symbol,
+                        format!("ungrouped default {}", &dt),
+                    );
+
+                    let default_attribute: DefaultAttribute = DefaultAttribute {
+                        expression: dt,
+                        r#if: kconfig_type.clone().r#if,
+                    };
+
+                    kconfig_defaults.push(default_attribute);
+                    config_type = Some(kconfig_type);
+                }
+                Type::Tristate(_ts) => config_type = Some(kconfig_type.clone()),
+                Type::Hex(_h) => config_type = Some(kconfig_type),
+                Type::Int(_i) => config_type = Some(kconfig_type),
+                Type::String(_s) => config_type = Some(kconfig_type),
+            },
+            Default(default) => {
+                attribute_grouping_checker.check(
+                    FunctionalAttributes::Defaults,
+                    args,
+                    findings,
+                    &config_symbol,
+                    format!("ungrouped default {}", &default),
+                );
+
+                kconfig_defaults.push(default);
+            }
+
+            DependsOn(depends_on) => {
+                attribute_grouping_checker.check(
+                    FunctionalAttributes::Dependencies,
+                    args,
+                    findings,
+                    &config_symbol,
+                    format!("ungrouped dependency {}", &depends_on),
+                );
+
+                kconfig_dependencies.push(depends_on);
+            }
+            Select(select) => {
+                attribute_grouping_checker.check(
+                    FunctionalAttributes::Selects,
+                    args,
+                    findings,
+                    &config_symbol,
+                    format!("ungrouped select {}", &select),
+                );
+
+                kconfig_selects.push(select);
+            }
+            Imply(imply) => {
+                // doing nothing for imply in the symtab right now
+
+                attribute_grouping_checker.check(
+                    FunctionalAttributes::Implies,
+                    args,
+                    findings,
+                    &config_symbol,
+                    format!("ungrouped imply {}", imply),
+                );
+
+                // TODO: may be relevant for nonvisible config options when building an SMT model...
+            }
+            // NOTE: range bounds are inclusive
+            Range(r) => {
+                attribute_grouping_checker.check(
+                    FunctionalAttributes::Ranges,
+                    args,
+                    findings,
+                    &config_symbol,
+                    format!("ungrouped range {}", r),
+                );
+
+                kconfig_ranges.push(r);
+            }
+            Help(h) => {
+                // doing nothing for menu help right now
+
+                dead_link_checker.check_text(&h, args, findings, Some(&config_symbol), "help text");
+            }
+
+            Modules => {
+                // the modules attribute designates this config option as the one that determines if the `m` state is available for tristates options.
+
+                // just making a special note of this in the symtab for now...
+                symtab.modules_option = Some(config_symbol.clone());
+            }
+
+            // the prompt's option `if` determines "visibility"
+            Prompt(prompt) => {
+                if let Some(c) = prompt.r#if {
+                    child_ctx = child_ctx.with_visibility(c);
+                }
+            }
+            Transitional => {
+                // doing nothing for transitional right now
+            }
+            _defconfig_list => {
+                todo!(
+                    "Found a defconfig list for config option: {:?}, TODO: handle it!",
+                    &config_symbol
+                );
+            }
+        }
+    }
+
+    // there can be multiple entries that get merged. so we need to do the same for our symtab.
+    let kconfig_type = config_type.clone().map(|c| c.r#type);
+
+    // at the time of writing this, linux's kconfig only uses Bool inside Choice.
+    // however, the kconfig documentation doesn't specify whether or not this is guaranteed to be the case.
+    // we add this check to ensure that we don't cause undefined behavior in future linux versions if something changes...
+    if ctx.in_choice {
+        if let Some(kt) = &kconfig_type {
+            match kt {
+                Type::Bool(_) | Type::DefBool(_) => {
+                    // expected in a choice...
+                }
+
+                _ => {
+                    // TODO: old versions of linux (like 5.4.4) have tristates in the choice
+                    //       - u-boot also currently has hex options in the choice!
+                    warn!("found something unexpected in a choice-statement: {:?}", kt);
+                }
+            }
+        }
+    }
+
+    // at the end, add the file's cur_dependencies to this var's invididual dependencies.
+    kconfig_dependencies.extend(ctx.dependencies.clone());
+    symtab.merge_insert_new_solved(
+        config_symbol.clone(),
+        kconfig_type,
+        kconfig_dependencies,
+        //z3_dependency,
+        kconfig_ranges,
+        kconfig_defaults,
+        ctx.visibility.clone(),
+        ctx.arch.clone(),
+        ctx.definition_condition.clone(),
+        None,
+        kconfig_selects
+            .clone()
+            .into_iter()
+            .map(|sel| (sel.symbol, sel.r#if))
+            .collect(),
+    );
+
+    // need to add the select condition to the definedness condition if it exists
+    for select in kconfig_selects {
+        match select.r#if {
+            None => symtab.merge_insert_new_solved(
+                select.symbol,
+                None,
+                Vec::new(),
+                Vec::new(),
+                Vec::new(),
+                Vec::new(),
+                ctx.arch.clone(),
+                ctx.definition_condition.clone(),
+                Some((config_symbol.clone(), None)),
+                Vec::new(),
+            ),
+            Some(select_condition) => {
+                symtab.merge_insert_new_solved(
+                    select.symbol,
+                    None,
+                    Vec::new(),
+                    Vec::new(),
+                    Vec::new(),
+                    Vec::new(),
+                    ctx.arch.clone(),
+                    ctx.definition_condition.clone(),
+                    Some((config_symbol.clone(), Some(select_condition))),
+                    Vec::new(),
+                );
+            }
+        }
+    }
+}
+
+fn handle_menu(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: Menu,
+    ctx: &Context,
+    findings: &mut Vec<Finding>,
+) {
+    // menus can set the visibility of their menu items
+
+    let mut child_ctx = ctx.child();
+
+    if !entry.depends_on.is_empty() {
+        debug!(
+            "the menu {:?} dependencies are: {:?}",
+            entry, entry.depends_on
+        );
+    }
+
+    for dep in entry.depends_on {
+        child_ctx = child_ctx.with_dep(dep.clone());
+        child_ctx = child_ctx.with_visibility(dep); // not a typo, the config options inside of a menu are only visible if the menu's dependencies are satisfied
+    }
+
+    let nested_entries = entry.entries;
+
+    recurse_entries(args, symtab, nested_entries, child_ctx.clone(), findings);
+}
+
+fn handle_choice(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: Choice,
+    ctx: &Context,
+    findings: &mut Vec<Finding>,
+) {
+    debug!("the attributes of the choice are: {:?}", entry.options);
+    debug!("the entries of the choice are: {:?}", entry.entries);
+
+    let mut child_ctx = ctx.child();
+    child_ctx = child_ctx.in_choice();
+
+    // we are going to add the dependencies of the choice to the dependencies of the entries.
+    //   we start with the dependencies inherited from the file
+    let mut choice_visibility_condition = None;
+    let mut defaults = Vec::new();
+    for attribute in entry.options {
+        match attribute {
+            DependsOn(depends_on) => {
+                child_ctx = child_ctx.with_dep(depends_on);
+            }
+
+            Default(default) => {
+                defaults.push(default);
+            }
+
+            // the prompt's `if` determines visibility
+            Prompt(prompt) => {
+                choice_visibility_condition = prompt.r#if;
+                if let Some(i) = choice_visibility_condition.clone() {
+                    child_ctx = child_ctx.with_visibility(i);
+                }
+            }
+            _ => debug!("skipping attribute {:?} for choice", attribute),
+        }
+    }
+
+    // all of the variables in the choice menu
+    //let mut contained_vars = Vec::with_capacity(c.entries.len());
+    let nested_entries = entry.entries;
+
+    recurse_entries(args, symtab, nested_entries, child_ctx.clone(), findings);
+
+    let choice_data = ChoiceData {
+        //inner_vars: contained_vars,
+        arch: child_ctx.arch.clone(),
+        visibility: choice_visibility_condition,
+        dependencies: child_ctx.dependencies,
+        defaults: defaults,
+    };
+    symtab.choices.push(choice_data);
+}
+
+fn handle_if(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: If,
+    ctx: &Context,
+    findings: &mut Vec<Finding>,
+) {
+    let mut child_ctx = ctx.child();
+    child_ctx = child_ctx.with_definition(entry.condition.clone());
+    child_ctx = child_ctx.with_dep(entry.condition);
+    let nested_entries = entry.entries;
+
+    recurse_entries(args, symtab, nested_entries, child_ctx, findings);
+}
+
+fn handle_source(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: Source,
+    ctx: &Context,
+    findings: &mut Vec<Finding>,
+) {
+    let sourced_kconfig = entry.entries;
+
+    for sourced_kconfig in sourced_kconfig {
+        recurse_entries(args, symtab, sourced_kconfig.entries, ctx.clone(), findings);
+    }
+}
+
+pub fn process_entry(
+    args: &AnalysisArgs,
+    symtab: &mut SymbolTable,
+    entry: Entry,
+    ctx: Context,
+    findings: &mut Vec<Finding>,
+) {
+    // NOTE: in general, each handler should update the context as it encounters that construct.
+    //       e.g. Context.in_choice() should be called at the start of handle_choice(), not right before call to process_entry() when a choice is found and process_entry is called
+    match entry {
+        Entry::Config(c) | Entry::MenuConfig(c) => {
+            handle_config(args, symtab, c, &ctx, findings);
+        }
+        Entry::Menu(m) => handle_menu(args, symtab, m, &ctx, findings),
+        Entry::Choice(c) => handle_choice(args, symtab, c, &ctx, findings),
+        Entry::If(i) => handle_if(args, symtab, i, &ctx, findings),
+        Entry::Source(s) => handle_source(args, symtab, s, &ctx, findings),
+        Entry::Comment(_) => {}
+        Entry::MainMenu(_) => {}
+        _ => {}
+    }
+}
diff --git a/scripts/kconfirm/kconfirm-lib/src/checks.rs b/scripts/kconfirm/kconfirm-lib/src/checks.rs
new file mode 100644
index 000000000000..96b2cf15a6d9
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/checks.rs
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+use crate::{
+    output::{Finding, Severity},
+    symbol_table::AttributeDef,
+};
+use std::collections::HashSet;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Check {
+    Style,     // check for duplicate default values, and ungrouped attributes
+    DeadLinks, // check for dead links in the help texts
+    DuplicateDependency,
+    DuplicateRange,
+    DuplicateSelect,
+    DeadDefault,
+    DuplicateDefault,
+}
+
+impl Check {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Check::Style => "style",
+            Check::DeadLinks => "dead_links",
+            Check::DuplicateDependency => "duplicate_dependency",
+            Check::DuplicateRange => "duplicate_range",
+            Check::DuplicateSelect => "duplicate_select",
+            Check::DeadDefault => "dead_default",
+            Check::DuplicateDefault => "duplicate_default",
+        }
+    }
+}
+
+pub fn parse_check(name: &str) -> Option<Check> {
+    match name {
+        "style" => Some(Check::Style),
+        "dead_links" => Some(Check::DeadLinks),
+        "duplicate_dependency" => Some(Check::DuplicateDependency),
+        "duplicate_range" => Some(Check::DuplicateRange),
+        "duplicate_select" => Some(Check::DuplicateSelect),
+        "dead_default" => Some(Check::DeadDefault),
+        "duplicate_default" => Some(Check::DuplicateDefault),
+        _ => None,
+    }
+}
+
+#[derive(Clone)]
+pub struct AnalysisArgs {
+    // check for duplicate default values
+    pub enabled_checks: HashSet<Check>,
+}
+
+impl AnalysisArgs {
+    pub fn is_enabled(&self, check: Check) -> bool {
+        self.enabled_checks.contains(&check)
+    }
+}
+
+pub fn check_variable_info(
+    args: &AnalysisArgs,
+    var_symbol: &str,
+    arch_specific: &Option<String>,
+    info: &AttributeDef,
+) -> Vec<Finding> {
+    let mut findings = Vec::new();
+
+    if args.is_enabled(Check::DuplicateDependency) {
+        findings.extend(check_duplicate_dependencies(
+            var_symbol,
+            info,
+            arch_specific,
+        ));
+    }
+
+    if args.is_enabled(Check::DuplicateRange) {
+        findings.extend(check_duplicate_ranges(var_symbol, info));
+    }
+
+    if args.is_enabled(Check::DuplicateSelect) {
+        findings.extend(check_duplicate_selects(var_symbol, info));
+    }
+
+    if args.is_enabled(Check::DeadDefault)
+        || args.is_enabled(Check::DuplicateDefault)
+        || args.is_enabled(Check::Style)
+    {
+        findings.extend(check_defaults(
+            var_symbol,
+            info,
+            args.is_enabled(Check::Style),
+        ));
+    }
+
+    findings
+}
+
+fn is_duplicate<T: Eq + std::hash::Hash>(set: &mut HashSet<T>, key: T) -> bool {
+    !set.insert(key)
+}
+
+fn check_duplicate_dependencies(
+    var_symbol: &str,
+    info: &AttributeDef,
+    arch_specific: &Option<String>,
+) -> Vec<Finding> {
+    let mut findings = Vec::new();
+    let mut seen = HashSet::new();
+
+    for dep in &info.kconfig_dependencies {
+        if is_duplicate(&mut seen, dep.to_string()) {
+            let message = match arch_specific {
+                Some(arch) => format!(
+                    "duplicate dependency on {:?} for architecture {:?}",
+                    dep.to_string(),
+                    arch
+                ),
+                None => format!("duplicate dependency on {:?}", dep.to_string()),
+            };
+            findings.push(Finding {
+                severity: Severity::Warning,
+                check: Check::DuplicateDependency.as_str(),
+                symbol: Some(var_symbol.to_owned()),
+                message,
+            });
+        }
+    }
+
+    findings
+}
+
+fn check_duplicate_ranges(var_symbol: &str, info: &AttributeDef) -> Vec<Finding> {
+    let mut findings = Vec::new();
+    let mut seen_conditions = HashSet::new();
+    let mut already_unconditional = false;
+
+    for range in &info.kconfig_ranges {
+        if already_unconditional {
+            findings.push(Finding {
+                severity: Severity::Warning,
+                check: Check::DuplicateRange.as_str(),
+                symbol: Some(var_symbol.to_owned()),
+                message: format!("dead range of {:?}", range),
+            });
+            continue;
+        }
+
+        if let Some(cond) = range.r#if.clone() {
+            if is_duplicate(&mut seen_conditions, cond.to_string()) {
+                findings.push(Finding {
+                    severity: Severity::Warning,
+                    check: Check::DuplicateRange.as_str(),
+                    symbol: Some(var_symbol.to_owned()),
+                    message: format!("dead range of {:?}", range),
+                });
+            }
+        } else {
+            already_unconditional = true;
+        }
+    }
+
+    findings
+}
+
+fn check_duplicate_selects(var_symbol: &str, info: &AttributeDef) -> Vec<Finding> {
+    let mut findings = Vec::new();
+    let mut seen: HashSet<(String, String)> = HashSet::new();
+
+    for select in &info.selects {
+        let select_var = select.0.clone();
+
+        match &select.1 {
+            Some(cond) => {
+                // A conditional select is dead if the same var is already selected unconditionally.
+                if seen.contains(&(select_var.clone(), String::new())) {
+                    findings.push(Finding {
+                        severity: Severity::Warning,
+                        check: "dead_select",
+                        symbol: Some(var_symbol.to_owned()),
+                        message: format!("dead select of {:?}", select),
+                    });
+                }
+
+                if is_duplicate(&mut seen, (select_var, cond.to_string())) {
+                    findings.push(Finding {
+                        severity: Severity::Warning,
+                        check: Check::DuplicateSelect.as_str(),
+                        symbol: Some(var_symbol.to_owned()),
+                        message: format!("duplicate select of {:?}", select),
+                    });
+                }
+            }
+            None => {
+                if is_duplicate(&mut seen, (select_var, String::new())) {
+                    findings.push(Finding {
+                        severity: Severity::Warning,
+                        check: Check::DuplicateSelect.as_str(),
+                        symbol: Some(var_symbol.to_owned()),
+                        message: format!("duplicate select of {:?}", select),
+                    });
+                }
+            }
+        }
+    }
+
+    findings
+}
+
+fn check_defaults(var_symbol: &str, info: &AttributeDef, style_enabled: bool) -> Vec<Finding> {
+    let mut findings = Vec::new();
+    let mut seen_conditions = HashSet::new();
+    let mut seen_values = HashSet::new();
+    let mut already_unconditional = false;
+
+    for default in &info.kconfig_defaults {
+        let val_str = default.expression.to_string();
+
+        if already_unconditional {
+            findings.push(Finding {
+                severity: Severity::Warning,
+                check: Check::DeadDefault.as_str(),
+                symbol: Some(var_symbol.to_owned()),
+                message: format!("dead default of {}", default.expression),
+            });
+        }
+
+        if style_enabled {
+            if default.r#if.is_some() && is_duplicate(&mut seen_values, val_str.clone()) {
+                findings.push(Finding {
+                    severity: Severity::Style,
+                    check: "duplicate_default_value",
+                    symbol: Some(var_symbol.to_owned()),
+                    message: format!(
+                        "duplicate default value of {:?}; consider combining the conditions with a logical-or: ||",
+                        val_str
+                    ),
+                });
+            }
+        }
+
+        match &default.r#if {
+            Some(cond) => {
+                if is_duplicate(&mut seen_conditions, cond.to_string()) {
+                    findings.push(Finding {
+                        severity: Severity::Warning,
+                        check: Check::DuplicateDefault.as_str(),
+                        symbol: Some(var_symbol.to_owned()),
+                        message: format!("duplicate default condition of {:?}", cond),
+                    });
+                }
+            }
+            None => {
+                already_unconditional = true;
+            }
+        }
+    }
+
+    findings
+}
diff --git a/scripts/kconfirm/kconfirm-lib/src/dead_links.rs b/scripts/kconfirm/kconfirm-lib/src/dead_links.rs
new file mode 100644
index 000000000000..74224b888695
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/dead_links.rs
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+use regex::Regex;
+use reqwest::blocking::Client;
+use std::time::Duration;
+
+/*
+ * during testing, "Unreachable" and "ServerError" seem to be a 50/50
+ * as to whether or not they're actually dead links
+ */
+#[derive(PartialEq, Debug)]
+pub enum LinkStatus {
+    Ok,                        // 2xx, definitely alive
+    ProbablyBlocked,           // 403, 429, or cloudflare-style response
+    Redirected(String),        // 301/302, redirection, consider updating the link
+    NotFound,                  // 404, probably dead
+    ServerError,               // 5xx, might be temporary
+    Unreachable(String),       // connection failed, timeout, DNS error etc.
+    UnsupportedScheme(String), // e.g. ftp, git
+}
+
+pub fn check_link(url: &str) -> LinkStatus {
+    if let Some(scheme) = url.split("://").next() {
+        match scheme {
+            "http" | "https" => return check_http(url),
+            "git" | "ftp" | _ => return LinkStatus::UnsupportedScheme(scheme.into()),
+        }
+    }
+
+    LinkStatus::Unreachable("invalid URL".into())
+}
+
+fn check_http(url: &str) -> LinkStatus {
+    let client = Client::builder()
+        .timeout(Duration::from_secs(10))
+        .build()
+        .unwrap();
+
+    match client.head(url).send() {
+        Ok(response) => match response.status().as_u16() {
+            200..=299 => LinkStatus::Ok,
+            301 | 302 => {
+                let location = response
+                    .headers()
+                    .get("location")
+                    .and_then(|v| v.to_str().ok())
+                    .unwrap_or("unknown")
+                    .to_string();
+                LinkStatus::Redirected(location)
+            }
+            403 | 429 => LinkStatus::ProbablyBlocked,
+            404 => LinkStatus::NotFound,
+            500..=599 => LinkStatus::ServerError,
+            _ => LinkStatus::ProbablyBlocked,
+        },
+        Err(e) => LinkStatus::Unreachable(e.to_string()),
+    }
+}
+
+pub fn find_links(text: &str) -> Vec<String> {
+    let re = Regex::new(r#"[a-zA-Z][a-zA-Z0-9+\-.]*://[^\s\)\]\}\"'<>]+"#).unwrap();
+
+    re.find_iter(text).map(|m| m.as_str().to_string()).collect()
+}
diff --git a/scripts/kconfirm/kconfirm-lib/src/lib.rs b/scripts/kconfirm/kconfirm-lib/src/lib.rs
new file mode 100644
index 000000000000..a5c6d6f0b6f2
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/lib.rs
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+pub mod output;
+use output::*;
+
+pub mod symbol_table;
+use symbol_table::*;
+
+mod dead_links;
+
+mod checks;
+pub use checks::{AnalysisArgs, Check, check_variable_info, parse_check};
+
+mod analyze;
+use analyze::*;
+
+use log::error;
+use nom_kconfig::Entry;
+
+use nom_kconfig::{KconfigInput, parse_kconfig};
+
+pub fn check_kconfig(
+    args: AnalysisArgs,
+    kconfig_files: Vec<(Option<String>, KconfigInput)>,
+) -> Vec<Finding> {
+    let mut findings = Vec::new();
+    let mut symbol_table = SymbolTable::new();
+
+    for (arch_config_option, kconfig_file) in kconfig_files {
+        match parse_kconfig(kconfig_file) {
+            Ok(parsed) => {
+                let entries: Vec<Entry> = parsed.1.entries;
+                findings.extend(analyze(
+                    &args,
+                    &mut symbol_table,
+                    arch_config_option,
+                    entries,
+                ));
+            }
+            Err(e) => {
+                error!("FATAL: failed to parse kconfig, error is {:?}", e);
+                panic!();
+            }
+        }
+    }
+
+    for (var_symbol, type_info) in &symbol_table.raw {
+        for (arch_specific, redefinitions) in &type_info.attribute_defs {
+            for (_definition_condition, info) in redefinitions {
+                findings.extend(check_variable_info(&args, var_symbol, arch_specific, info));
+            }
+        }
+    }
+
+    findings
+}
diff --git a/scripts/kconfirm/kconfirm-lib/src/output.rs b/scripts/kconfirm/kconfirm-lib/src/output.rs
new file mode 100644
index 000000000000..2686bcd13e33
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/output.rs
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+use std::fmt;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Severity {
+    Fatal,
+    Error, // will be used for known bugs, e.g. unmet dependencies
+    Warning,
+    Style,
+}
+
+impl fmt::Display for Severity {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Severity::Fatal => write!(f, "FATAL  "),
+            Severity::Error => write!(f, "ERROR  "),
+            Severity::Warning => write!(f, "WARNING"),
+            Severity::Style => write!(f, "STYLE   "),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct Finding {
+    pub severity: Severity,
+    pub check: &'static str,
+    pub symbol: Option<String>,
+    pub message: String,
+}
+
+impl fmt::Display for Finding {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.symbol {
+            Some(s) => write!(
+                f,
+                "{} [{}] config {}: {}",
+                self.severity, self.check, s, self.message
+            ),
+            None => write!(f, "{} [{}] {}", self.severity, self.check, self.message),
+        }
+    }
+}
+
+pub fn print_findings(mut findings: Vec<Finding>) {
+    findings.sort_by(|a, b| {
+        (&a.severity, &a.check, &a.symbol).cmp(&(&b.severity, &b.check, &b.symbol))
+    });
+
+    for f in &findings {
+        println!("{}", f);
+    }
+}
diff --git a/scripts/kconfirm/kconfirm-lib/src/symbol_table.rs b/scripts/kconfirm/kconfirm-lib/src/symbol_table.rs
new file mode 100644
index 000000000000..5d368b0f8fac
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-lib/src/symbol_table.rs
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+use log::debug;
+use nom_kconfig::attribute::DefaultAttribute;
+use nom_kconfig::attribute::Expression;
+use nom_kconfig::attribute::OrExpression;
+use nom_kconfig::attribute::Range;
+use nom_kconfig::attribute::r#type::Type;
+use std::collections::HashMap;
+use std::collections::hash_map;
+
+type KconfigSymbol = String;
+type Arch = Option<String>;
+type Cond = Option<Expression>;
+
+// NOTE: we cannot add these elements to the solver until we've processed all variables,
+// because we need to know all of the selectors.
+#[derive(Debug, Clone)]
+pub struct TypeInfo {
+    pub kconfig_type: Option<Type>, // 'None' when we don't know the type (e.g. if it's a dangling reference)
+
+    // maps the selector to an (ARCH, select_cond)
+    // - if the ARCH is None, then it's not arch-specific
+    // if the select_cond is None, then it's unconditional
+    pub selected_by: HashMap<KconfigSymbol, Vec<(Arch, Cond)>>, // .0 only selects it when .1 is true.
+
+    // there is one of these per entry (each entry expected to have a different definedness condition)
+    // maps architecture option name (or none if not arch-specific) to:
+    // [([condition], config definition)]
+    // - NOTE: there can be multiple partial definitions under the same condition, or mutually-exclusive conditions, or a subset condition.
+    pub attribute_defs: HashMap<Arch, Vec<(Vec<Expression>, AttributeDef)>>, // the innermost `Vec<Expression>` represents each nested condition that was reached (we will eventually need to AND them all)
+}
+
+// the dependencies are a vector because we may encounter multiple over time,
+//   so we won't know until the end what the condition is.
+#[derive(Debug, Clone)]
+pub struct AttributeDef {
+    pub kconfig_dependencies: Vec<OrExpression>,
+    pub kconfig_ranges: Vec<Range>,
+    pub kconfig_defaults: Vec<DefaultAttribute>,
+    pub visibility: Vec<OrExpression>,
+    pub selects: Vec<(KconfigSymbol, Cond)>,
+}
+
+impl TypeInfo {
+    fn new_empty() -> Self {
+        Self {
+            kconfig_type: None,
+            selected_by: HashMap::new(),
+            attribute_defs: HashMap::new(),
+        }
+    }
+
+    fn insert(
+        &mut self,
+        kconfig_type: Option<Type>,
+        raw_constraints: Vec<OrExpression>,
+        kconfig_ranges: Vec<Range>,
+        kconfig_defaults: Vec<DefaultAttribute>,
+        visibility: Vec<OrExpression>,
+        arch: Option<String>,
+        definition_condition: Vec<OrExpression>,
+        selected_by: Option<(KconfigSymbol, Cond)>,
+        selects: Vec<(KconfigSymbol, Cond)>,
+    ) {
+        // type merge
+        match (&self.kconfig_type, &kconfig_type) {
+            (None, Some(_)) => self.kconfig_type = kconfig_type,
+            (Some(_), Some(new)) if Some(new) != self.kconfig_type.as_ref() => {
+                // TODO: not doing anything with redefined types yet.
+                //       later, we will want to consider e.g. bool/def_bool the same type (and possibly int/hex?) but not bool/tristate, so we need to build out typechecking.
+                debug!(
+                    "NOTE: different type {:?} (existing {:?})",
+                    kconfig_type, self.kconfig_type
+                );
+            }
+            _ => {}
+        }
+
+        // selected_by merge
+        if let Some(sb) = selected_by {
+            merge_selected_by(&mut self.selected_by, arch.clone(), sb);
+        }
+
+        // variable_info merge
+        insert_variable_info(
+            &mut self.attribute_defs,
+            arch,
+            definition_condition,
+            AttributeDef {
+                kconfig_dependencies: raw_constraints,
+                kconfig_ranges,
+                kconfig_defaults,
+                visibility,
+                selects,
+            },
+        );
+    }
+}
+
+// the visibility and the dependencies will each need to be AND'd (separately)
+// the defaults should each be handled separately.
+pub struct ChoiceData {
+    //pub inner_vars: Vec<String>,
+    pub arch: Arch,
+    pub visibility: Cond,
+    pub dependencies: Vec<OrExpression>, // this is the menu's dependencies (and inherited dependencies from the file)
+    pub defaults: Vec<DefaultAttribute>, // these are each of the conditional defaults for the choice
+}
+
+// NOTE: it might be better if TypeInfo is an enum with a single value,
+//       e.g. Unsolved(kconfig_raw) and Solved(z3_ast)
+pub struct SymbolTable {
+    pub raw: HashMap<KconfigSymbol, TypeInfo>,
+    pub choices: Vec<ChoiceData>,
+    pub modules_option: Option<KconfigSymbol>, // None until we find the modules attribute in exactly 1 config option
+}
+
+impl SymbolTable {
+    pub fn new() -> Self {
+        SymbolTable {
+            raw: HashMap::new(),
+            choices: Vec::new(),
+            modules_option: None,
+        }
+    }
+
+    pub fn from_parts(
+        raw: HashMap<KconfigSymbol, TypeInfo>,
+        choices: Vec<ChoiceData>,
+        modules_option: Option<KconfigSymbol>,
+    ) -> Self {
+        SymbolTable {
+            raw,
+            choices,
+            modules_option,
+        }
+    }
+
+    pub fn merge_insert_new_solved(
+        &mut self,
+        var: KconfigSymbol,
+        kconfig_type: Option<Type>,
+        raw_constraints: Vec<OrExpression>,
+        kconfig_ranges: Vec<Range>,
+        kconfig_defaults: Vec<DefaultAttribute>,
+        visibility: Vec<OrExpression>,
+        arch: Arch,
+        definition_condition: Vec<OrExpression>,
+        selected_by: Option<(KconfigSymbol, Cond)>,
+        selects: Vec<(KconfigSymbol, Cond)>,
+    ) {
+        let entry = self.raw.entry(var.clone());
+
+        match entry {
+            hash_map::Entry::Vacant(v) => {
+                let mut t = TypeInfo::new_empty();
+                t.insert(
+                    kconfig_type,
+                    raw_constraints,
+                    kconfig_ranges,
+                    kconfig_defaults,
+                    visibility,
+                    arch,
+                    definition_condition,
+                    selected_by,
+                    selects,
+                );
+                v.insert(t);
+            }
+
+            hash_map::Entry::Occupied(mut o) => {
+                let t = o.get_mut();
+
+                t.insert(
+                    kconfig_type,
+                    raw_constraints,
+                    kconfig_ranges,
+                    kconfig_defaults,
+                    visibility,
+                    arch,
+                    definition_condition,
+                    selected_by,
+                    selects,
+                );
+            }
+        }
+    }
+}
+
+fn merge_selected_by(
+    map: &mut HashMap<String, Vec<(Arch, Cond)>>,
+    arch: Arch,
+    selected_by: (KconfigSymbol, Cond),
+) {
+    map.entry(selected_by.0)
+        .or_insert_with(Vec::new)
+        .push((arch, selected_by.1));
+}
+
+fn insert_variable_info(
+    map: &mut HashMap<Arch, Vec<(Vec<Expression>, AttributeDef)>>,
+    arch: Arch,
+    definition_condition: Vec<Expression>,
+    info: AttributeDef,
+) {
+    map.entry(arch)
+        .or_insert_with(Vec::new)
+        .push((definition_condition, info));
+}
diff --git a/scripts/kconfirm/kconfirm-linux/Cargo.toml b/scripts/kconfirm/kconfirm-linux/Cargo.toml
new file mode 100644
index 000000000000..3568e198410f
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-linux/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "kconfirm-linux"
+version = "0.7.0"
+edition = "2024"
+rust-version.workspace = true
+
+
+[dependencies]
+kconfirm-lib = { path = "../kconfirm-lib" }
+nom-kconfig = { workspace = true }
+
+log = { workspace = true }
+env_logger = { workspace = true }
+clap = { workspace = true }
diff --git a/scripts/kconfirm/kconfirm-linux/src/lib.rs b/scripts/kconfirm/kconfirm-linux/src/lib.rs
new file mode 100644
index 000000000000..e90d571ecfb8
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-linux/src/lib.rs
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+use log::warn;
+use std::io;
+use std::path::PathBuf;
+
+use nom_kconfig::KconfigFile;
+
+// each architecture has its own directory, and config option.
+// most are the same, but powerpc / ppc and um / uml are not.
+// this maps the directory to the config option.
+pub fn arch_dir_to_config(arch_dir: &str) -> String {
+    match arch_dir {
+        "arm" => String::from("ARM"),
+        "arm64" => String::from("ARM64"),
+        "x86" => String::from("X86"),
+        "riscv" => String::from("RISCV"),
+        "mips" => String::from("MIPS"),
+        "xtensa" => String::from("XTENSA"),
+        "sparc" => String::from("SPARC"),
+        "alpha" => String::from("ALPHA"),
+        "arc" => String::from("ARC"),
+        "csky" => String::from("CSKY"),
+        "hexagon" => String::from("HEXAGON"),
+        "loongarch" => String::from("LOONGARCH"),
+        "m68k" => String::from("M68K"),
+        "microblaze" => String::from("MICROBLAZE"),
+        "nios2" => String::from("NIOS2"),
+        "openrisc" => String::from("OPENRISC"),
+        "parisc" => String::from("PARISC"),
+        "powerpc" => String::from("PPC"),
+        "s390" => String::from("S390"),
+        "sh" => String::from("SH"),
+        "um" => String::from("UML"),
+
+        _ => {
+            warn!(
+                "unexpected directory in /arch/ was a new architecture added: {} ?
+                Assuming the config option is the same as the directory name...",
+                arch_dir
+            );
+            String::from(arch_dir).to_uppercase()
+        }
+    }
+}
+
+pub struct LinuxKconfig {
+    pub arch_config_option: Option<String>, // not used for the root kconfig
+    pub kconfig_file: KconfigFile,
+    pub file_contents: String,
+}
+
+// returns a 2-tuple of the arch config option and its root kconfig file
+pub fn get_arch_kconfig_files(
+    linux_root: PathBuf,
+    arch_dir_path: PathBuf,
+) -> std::io::Result<Vec<LinuxKconfig>> {
+    let mut arch_kconfigs = Vec::new();
+
+    // collect all file paths up to 2 levels deep under arch/.
+    // depth 1 is files directly in arch/ (e.g. arch/Kconfig), depth 2 is
+    // files inside each arch subdirectory (e.g. arch/x86/Kconfig.cpu).
+    let mut paths: Vec<PathBuf> = Vec::new();
+    for entry in std::fs::read_dir(&arch_dir_path)? {
+        let entry = entry?;
+        let file_type = entry.file_type()?;
+        if file_type.is_file() {
+            paths.push(entry.path());
+        } else if file_type.is_dir() {
+            for sub_entry in std::fs::read_dir(entry.path())? {
+                let sub_entry = sub_entry?;
+                if sub_entry.file_type()?.is_file() {
+                    paths.push(sub_entry.path());
+                }
+            }
+        }
+    }
+
+    for path in paths {
+        // filter for "Kconfig" prefix
+        if !path
+            .file_name()
+            .and_then(|s| s.to_str())
+            .map_or(false, |n| n.starts_with("Kconfig"))
+        {
+            continue;
+        }
+
+        // get the arch from the path (e.g. x86 in /arch/x86/)
+        let relative_path = path.strip_prefix(&linux_root).unwrap();
+        let arch_dir = match relative_path.components().nth(1) {
+            Some(std::path::Component::Normal(n)) => n.to_string_lossy(),
+            _ => continue,
+        };
+
+        if linux_root.join("arch").join(&*arch_dir).is_dir() {
+            let kconfig_file = KconfigFile::new(linux_root.clone(), relative_path.to_path_buf());
+            arch_kconfigs.push(LinuxKconfig {
+                arch_config_option: Some(arch_dir_to_config(&arch_dir)),
+                file_contents: kconfig_file.read_to_string()?,
+                kconfig_file,
+            });
+        }
+    }
+
+    Ok(arch_kconfigs)
+}
+
+// collects the root kconfig file, and all of the arch-specific kconfig files
+pub fn collect_kconfig_root_files(linux_source: PathBuf) -> io::Result<Vec<LinuxKconfig>> {
+    let mut all_root_kconfig_files = Vec::new();
+
+    // add the root kconfig file
+    let root_kconfig_path = PathBuf::from("Kconfig"); // doesn't include the arch: arch/x86/Kconfig
+    let root_kconfig_file = KconfigFile::new(linux_source.clone(), root_kconfig_path.clone());
+    let root_kconfig = LinuxKconfig {
+        arch_config_option: None,
+        file_contents: root_kconfig_file.read_to_string()?,
+        kconfig_file: root_kconfig_file,
+    };
+    all_root_kconfig_files.push(root_kconfig);
+
+    // add the arch kconfig files
+    let arch_dir_path = linux_source.join("arch");
+    let arch_kconfig_files = get_arch_kconfig_files(linux_source, arch_dir_path)?;
+    all_root_kconfig_files.extend(arch_kconfig_files);
+
+    Ok(all_root_kconfig_files)
+}
diff --git a/scripts/kconfirm/kconfirm-linux/src/main.rs b/scripts/kconfirm/kconfirm-linux/src/main.rs
new file mode 100644
index 000000000000..d8db25d87ee8
--- /dev/null
+++ b/scripts/kconfirm/kconfirm-linux/src/main.rs
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-only
+use clap::Parser;
+use std::collections::HashSet;
+use std::io::{self};
+use std::path::PathBuf;
+
+use nom_kconfig::KconfigInput;
+
+use kconfirm_lib::check_kconfig;
+use kconfirm_lib::output::print_findings;
+use kconfirm_lib::parse_check;
+use kconfirm_lib::{AnalysisArgs, Check};
+use kconfirm_linux::collect_kconfig_root_files;
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct Args {
+    #[arg(long, required = true)]
+    linux_path: PathBuf,
+
+    // enable specific checks (repeatable or comma-separated)
+    #[arg(long, value_delimiter = ',', num_args = 1..)]
+    enable: Vec<String>,
+
+    // disable specific checks
+    #[arg(long, value_delimiter = ',', num_args = 1..)]
+    disable: Vec<String>,
+}
+
+fn main() -> io::Result<()> {
+    env_logger::init();
+    let cli_args = Args::parse();
+    let mut enabled_checks: HashSet<Check> = [
+        Check::DuplicateDependency,
+        Check::DuplicateRange,
+        Check::DuplicateSelect,
+        Check::DeadDefault,
+        Check::DuplicateDefault,
+    ]
+    .into_iter()
+    .collect();
+
+    // apply --enable
+    for name in &cli_args.enable {
+        if let Some(c) = parse_check(name) {
+            enabled_checks.insert(c);
+        }
+    }
+
+    // apply --disable
+    for name in &cli_args.disable {
+        if let Some(c) = parse_check(name) {
+            enabled_checks.remove(&c);
+        }
+    }
+
+    let analysis_args = AnalysisArgs { enabled_checks };
+
+    let kconfig_files = collect_kconfig_root_files(cli_args.linux_path)?;
+    let kconfig_inputs = kconfig_files
+        .iter()
+        .map(|kconfig| {
+            let kconfig_input =
+                KconfigInput::new_extra(&kconfig.file_contents, kconfig.kconfig_file.clone());
+
+            (kconfig.arch_config_option.clone(), kconfig_input)
+        })
+        .collect();
+    let findings = check_kconfig(analysis_args, kconfig_inputs);
+
+    print_findings(findings);
+
+    Ok(())
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 17:44 [RFC PATCH 0/2] scripts: add kconfirm Julian Braha
  2026-04-27 17:44 ` [RFC PATCH 1/2] " Julian Braha
@ 2026-04-27 17:44 ` Julian Braha
  2026-04-27 18:17   ` Miguel Ojeda
  2026-04-28  8:23   ` Jani Nikula
  1 sibling, 2 replies; 15+ messages in thread
From: Julian Braha @ 2026-04-27 17:44 UTC (permalink / raw)
  To: akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild, Julian Braha

Add usage documentation and a brief description for kconfirm to
Documentation/dev-tools/

Signed-off-by: Julian Braha <julianbraha@gmail.com>
---
 Documentation/dev-tools/index.rst    |   1 +
 Documentation/dev-tools/kconfirm.rst | 147 +++++++++++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 Documentation/dev-tools/kconfirm.rst

diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 59cbb77b33ff..130ebc0d7282 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -40,3 +40,4 @@ Documentation/process/debugging/index.rst
    autofdo
    propeller
    container
+   kconfirm
diff --git a/Documentation/dev-tools/kconfirm.rst b/Documentation/dev-tools/kconfirm.rst
new file mode 100644
index 000000000000..6ad02de15031
--- /dev/null
+++ b/Documentation/dev-tools/kconfirm.rst
@@ -0,0 +1,147 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+.. Copyright (C) 2026 Julian Braha <julianbraha@gmail.com>
+
+========
+kconfirm
+========
+
+kconfirm is a static analysis tool for the kernel's Kconfig system.  It
+checks the entire tree-wide Kconfig, and reports misusage like
+dead code.  In the case of dead default statements, these can be a
+significant code smell.
+
+kconfirm has an optional check for dead links in the Kconfig help texts.
+Since this has a high potential for false positives (due to websites
+blocking bots) and slows down runtime signficantly, it is disabled by
+default.  However, an example of how to enable it is included below.
+
+kconfirm is written in Rust and lives in ``scripts/kconfirm``.  Other
+than the dead link checks, kconfirm aims for zero false positives.
+
+**NOTE**: kconfirm does not modify or compile the source tree; it is
+strictly a static checker.
+
+
+Getting Started
+===============
+
+
+kconfirm's Minimum Supported Rust Version (MSRV) is v1.85.0, because
+it uses Rust edition 2024, and this is the earliest supported version.
+
+kconfirm also requires the Cargo package manager and an internet
+connection for compilation of its dependencies.
+
+If Cargo is available, kconfirm can be built and run from the top of the
+kernel source tree::
+
+    make kconfirm
+
+The compiled ``kconfirm-linux`` binary will be available in
+``scripts/kconfirm/target/release/``.
+
+The default checks currently cover dead code analysis.  ``dead_links``
+must be turned on explicitly with ``--enable``; conversely, any default
+check can be turned off with ``--disable``.  Both options accept
+either a comma-separated list or repeated flags, so the following
+two invocations are equivalent::
+
+  kconfirm-linux --linux-path . --enable dead_defaults,dead_links
+  kconfirm-linux --linux-path . --enable dead_defaults --enable dead_links
+
+
+
+Options
+=======
+
+**NOTE**: kconfirm's arguments must be provided in the ``KCONFIRM_ARGS``
+environment variable if running with ``make``. See `Examples`_.
+
+Available options:
+
+``--linux-path PATH``
+    The path to the linux source tree to analyze. ``make`` uses this
+    option to pass the current linux tree, but this option can be used
+    when running the tool directly with another source tree.
+    See `Examples`_.
+
+``--enable CHECK[,CHECK...]``
+
+    Enable one or more checks in addition to the default set.  May be
+    given multiple times, or as a single comma-separated list.  See
+    `Available checks`_ below for valid names.
+
+``--disable CHECK[,CHECK...]``
+
+    Disable one or more checks from the default set.  May be given
+    multiple times, or as a single comma-separated list.
+
+``-h, --help``
+
+    Show the help message and exit.
+
+``-V, --version``
+
+    Show version information and exit.
+
+
+Available checks
+================
+
+Each check has a string name that is accepted by ``--enable`` and
+``--disable``.  Checks marked *(default)* are enabled unless turned off
+explicitly.
+
+``duplicate_dependency`` *(default)*
+
+    Reports duplicated ``depends on`` entries on a single Kconfig symbol.
+
+``duplicate_range`` *(default)*
+
+    Reports duplicated ``range`` entries on a single Kconfig symbol.
+
+``duplicate_select`` *(default)*
+
+    Reports duplicated ``select`` entries on a single Kconfig symbol.
+
+``duplicate_default`` *(default)*
+
+    Reports duplicated ``default`` entries on a single Kconfig symbol.
+
+``dead_default`` *(default)*
+
+    Reports ``default`` entries that can never be selected, for example
+    because their condition is unsatisfiable.
+
+``dead_links``
+
+    Reports broken URLs found in Kconfig help text.  Because this
+    performs network requests it can be quite slow, and is disabled by
+    default. May also have false positives.
+
+``style``
+
+    Reports opinionated style issues in Kconfig files.  Disabled by
+    default.
+
+
+Examples
+========
+
+Compile (as needed) and run on the current tree::
+
+    make kconfirm
+
+To additionally enable dead-link checking::
+
+    make kconfirm KCONFIRM_ARGS="--enable dead_links"
+
+To disable a check (here, ``duplicate_dependency``) while keeping the
+rest of the default set::
+
+    make kconfirm KCONFIRM_ARGS="--disable duplicate_dependency"
+
+To run the default checks against a kernel tree separate from the
+current directory, such as ``~/repos/linux``::
+
+    scripts/kconfirm/target/release/kconfirm-linux --linux-path ~/repos/linux
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 17:44 ` [RFC PATCH 2/2] Documentation: dev-tools: " Julian Braha
@ 2026-04-27 18:17   ` Miguel Ojeda
  2026-04-27 19:33     ` Arnd Bergmann
  2026-04-28  8:23   ` Jani Nikula
  1 sibling, 1 reply; 15+ messages in thread
From: Miguel Ojeda @ 2026-04-27 18:17 UTC (permalink / raw)
  To: Julian Braha
  Cc: akpm, ljs, arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

On Mon, Apr 27, 2026 at 7:44 PM Julian Braha <julianbraha@gmail.com> wrote:
>
> +kconfirm also requires the Cargo package manager and an internet
> +connection for compilation of its dependencies.

This will be quite surprising -- I think any `make` call should avoid
touching the network and should allow working while offline as long as
one has set up things beforehand.

i.e. I think tools and dependencies in general should be fetched
and/or installed beforehand, and then yes, `make` may use them,
without touching the network (e.g. passing `--offline` or `--frozen`).

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 18:17   ` Miguel Ojeda
@ 2026-04-27 19:33     ` Arnd Bergmann
  2026-04-28 11:51       ` Miguel Ojeda
  2026-04-28 17:01       ` Julian Braha
  0 siblings, 2 replies; 15+ messages in thread
From: Arnd Bergmann @ 2026-04-27 19:33 UTC (permalink / raw)
  To: Miguel Ojeda, Julian Braha
  Cc: Andrew Morton, ljs, Greg Kroah-Hartman, Masahiro Yamada,
	Nathan Chancellor, Nicolas Schier, Miguel Ojeda, Jonathan Corbet,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

On Mon, Apr 27, 2026, at 20:17, Miguel Ojeda wrote:
> On Mon, Apr 27, 2026 at 7:44 PM Julian Braha <julianbraha@gmail.com> wrote:
>>
>> +kconfirm also requires the Cargo package manager and an internet
>> +connection for compilation of its dependencies.
>
> This will be quite surprising -- I think any `make` call should avoid
> touching the network and should allow working while offline as long as
> one has set up things beforehand.
>
> i.e. I think tools and dependencies in general should be fetched
> and/or installed beforehand, and then yes, `make` may use them,
> without touching the network (e.g. passing `--offline` or `--frozen`).

It would also be helpful to reduce the number of hard dependencies
and ideally only rely on packages that are already shipping in common
distros.

From the dependency list, it appears that the majority of dependencies
here are only indirectly pulled in by 'reqwest'. I guess is only
required for dead-link checking, so maybe that part could be
made optional?

     Arnd

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 1/2] scripts: add kconfirm
  2026-04-27 17:44 ` [RFC PATCH 1/2] " Julian Braha
@ 2026-04-27 20:48   ` Greg KH
  2026-04-28  7:01   ` Jonathan Corbet
  1 sibling, 0 replies; 15+ messages in thread
From: Greg KH @ 2026-04-27 20:48 UTC (permalink / raw)
  To: Julian Braha
  Cc: akpm, ljs, arnd, masahiroy, nathan, nsc, ojeda, corbet,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

On Mon, Apr 27, 2026 at 06:44:28PM +0100, Julian Braha wrote:
> Add kconfirm into scripts/ and modify the root Makefile and
> scripts/Makefile accordingly so that it can be compiled and run with:
> `make kconfirm`
> from the root of the tree.

You don't say what this fancy new tool actually does anywhere that I can
see :(

> 
> The user is expected to have Rust with Cargo installed, and an internet
> connection on the first run to download and compile the dependencies.
> 
> Signed-off-by: Julian Braha <julianbraha@gmail.com>
> ---
>  Makefile                                      |   12 +-
>  scripts/Makefile                              |    2 +-
>  scripts/kconfirm/Cargo.lock                   | 1710 +++++++++++++++++

Did you mean to check this file in?  That's not going to work well...

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 1/2] scripts: add kconfirm
  2026-04-27 17:44 ` [RFC PATCH 1/2] " Julian Braha
  2026-04-27 20:48   ` Greg KH
@ 2026-04-28  7:01   ` Jonathan Corbet
  2026-04-28 18:31     ` Nathan Chancellor
  1 sibling, 1 reply; 15+ messages in thread
From: Jonathan Corbet @ 2026-04-28  7:01 UTC (permalink / raw)
  To: Julian Braha, akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild, Julian Braha

Julian Braha <julianbraha@gmail.com> writes:

> Add kconfirm into scripts/ and modify the root Makefile and
> scripts/Makefile accordingly so that it can be compiled and run with:
> `make kconfirm`
> from the root of the tree.

As Greg noted, it would be helpful if you said what this tool does in
the changelog.

Also, a nit, but I would really suggest putting it under tools/ rather
than in the scripts/ dumping ground.

Thanks,

jon

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 17:44 ` [RFC PATCH 2/2] Documentation: dev-tools: " Julian Braha
  2026-04-27 18:17   ` Miguel Ojeda
@ 2026-04-28  8:23   ` Jani Nikula
  2026-04-28 18:06     ` Julian Braha
  1 sibling, 1 reply; 15+ messages in thread
From: Jani Nikula @ 2026-04-28  8:23 UTC (permalink / raw)
  To: Julian Braha, akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild, Julian Braha

On Mon, 27 Apr 2026, Julian Braha <julianbraha@gmail.com> wrote:
> Add usage documentation and a brief description for kconfirm to
> Documentation/dev-tools/
>
> Signed-off-by: Julian Braha <julianbraha@gmail.com>
> ---
>  Documentation/dev-tools/index.rst    |   1 +
>  Documentation/dev-tools/kconfirm.rst | 147 +++++++++++++++++++++++++++
>  2 files changed, 148 insertions(+)
>  create mode 100644 Documentation/dev-tools/kconfirm.rst
>
> diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
> index 59cbb77b33ff..130ebc0d7282 100644
> --- a/Documentation/dev-tools/index.rst
> +++ b/Documentation/dev-tools/index.rst
> @@ -40,3 +40,4 @@ Documentation/process/debugging/index.rst
>     autofdo
>     propeller
>     container
> +   kconfirm
> diff --git a/Documentation/dev-tools/kconfirm.rst b/Documentation/dev-tools/kconfirm.rst
> new file mode 100644
> index 000000000000..6ad02de15031
> --- /dev/null
> +++ b/Documentation/dev-tools/kconfirm.rst
> @@ -0,0 +1,147 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +.. Copyright (C) 2026 Julian Braha <julianbraha@gmail.com>
> +
> +========
> +kconfirm
> +========
> +
> +kconfirm is a static analysis tool for the kernel's Kconfig system.  It
> +checks the entire tree-wide Kconfig, and reports misusage like
> +dead code.  In the case of dead default statements, these can be a
> +significant code smell.
> +
> +kconfirm has an optional check for dead links in the Kconfig help texts.
> +Since this has a high potential for false positives (due to websites
> +blocking bots) and slows down runtime signficantly, it is disabled by
> +default.  However, an example of how to enable it is included below.
> +
> +kconfirm is written in Rust and lives in ``scripts/kconfirm``.  Other
> +than the dead link checks, kconfirm aims for zero false positives.
> +
> +**NOTE**: kconfirm does not modify or compile the source tree; it is
> +strictly a static checker.
> +
> +
> +Getting Started
> +===============
> +
> +
> +kconfirm's Minimum Supported Rust Version (MSRV) is v1.85.0, because
> +it uses Rust edition 2024, and this is the earliest supported version.
> +
> +kconfirm also requires the Cargo package manager and an internet
> +connection for compilation of its dependencies.
> +
> +If Cargo is available, kconfirm can be built and run from the top of the
> +kernel source tree::
> +
> +    make kconfirm
> +
> +The compiled ``kconfirm-linux`` binary will be available in
> +``scripts/kconfirm/target/release/``.
> +
> +The default checks currently cover dead code analysis.  ``dead_links``
> +must be turned on explicitly with ``--enable``; conversely, any default
> +check can be turned off with ``--disable``.  Both options accept
> +either a comma-separated list or repeated flags, so the following
> +two invocations are equivalent::
> +
> +  kconfirm-linux --linux-path . --enable dead_defaults,dead_links
> +  kconfirm-linux --linux-path . --enable dead_defaults --enable dead_links
> +
> +
> +
> +Options
> +=======
> +
> +**NOTE**: kconfirm's arguments must be provided in the ``KCONFIRM_ARGS``
> +environment variable if running with ``make``. See `Examples`_.
> +
> +Available options:
> +
> +``--linux-path PATH``
> +    The path to the linux source tree to analyze. ``make`` uses this
> +    option to pass the current linux tree, but this option can be used
> +    when running the tool directly with another source tree.
> +    See `Examples`_.
> +
> +``--enable CHECK[,CHECK...]``
> +
> +    Enable one or more checks in addition to the default set.  May be
> +    given multiple times, or as a single comma-separated list.  See
> +    `Available checks`_ below for valid names.
> +
> +``--disable CHECK[,CHECK...]``
> +
> +    Disable one or more checks from the default set.  May be given
> +    multiple times, or as a single comma-separated list.
> +
> +``-h, --help``
> +
> +    Show the help message and exit.
> +
> +``-V, --version``
> +
> +    Show version information and exit.
> +
> +
> +Available checks
> +================
> +
> +Each check has a string name that is accepted by ``--enable`` and
> +``--disable``.  Checks marked *(default)* are enabled unless turned off
> +explicitly.
> +
> +``duplicate_dependency`` *(default)*
> +
> +    Reports duplicated ``depends on`` entries on a single Kconfig symbol.
> +
> +``duplicate_range`` *(default)*
> +
> +    Reports duplicated ``range`` entries on a single Kconfig symbol.
> +
> +``duplicate_select`` *(default)*
> +
> +    Reports duplicated ``select`` entries on a single Kconfig symbol.
> +
> +``duplicate_default`` *(default)*
> +
> +    Reports duplicated ``default`` entries on a single Kconfig symbol.
> +
> +``dead_default`` *(default)*
> +
> +    Reports ``default`` entries that can never be selected, for example
> +    because their condition is unsatisfiable.
> +
> +``dead_links``
> +
> +    Reports broken URLs found in Kconfig help text.  Because this
> +    performs network requests it can be quite slow, and is disabled by
> +    default. May also have false positives.
> +
> +``style``
> +
> +    Reports opinionated style issues in Kconfig files.  Disabled by
> +    default.

Oh, I'd really like a check on this part from kconfig-language.rst:

  Note:
	select should be used with care. select will force
	a symbol to a value without visiting the dependencies.
	By abusing select you are able to select a symbol FOO even
	if FOO depends on BAR that is not set.
	In general use select only for non-visible symbols
	(no prompts anywhere) and for symbols with no dependencies.
	That will limit the usefulness but on the other hand avoid
	the illegal configurations all over.

i.e. warn on selecting visible symbols or symbols with
dependencies.

Yes, it's going to produce tons of warnings. But currently we don't even
know how bad it really is.

BR,
Jani.


> +
> +
> +Examples
> +========
> +
> +Compile (as needed) and run on the current tree::
> +
> +    make kconfirm
> +
> +To additionally enable dead-link checking::
> +
> +    make kconfirm KCONFIRM_ARGS="--enable dead_links"
> +
> +To disable a check (here, ``duplicate_dependency``) while keeping the
> +rest of the default set::
> +
> +    make kconfirm KCONFIRM_ARGS="--disable duplicate_dependency"
> +
> +To run the default checks against a kernel tree separate from the
> +current directory, such as ``~/repos/linux``::
> +
> +    scripts/kconfirm/target/release/kconfirm-linux --linux-path ~/repos/linux

-- 
Jani Nikula, Intel

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 19:33     ` Arnd Bergmann
@ 2026-04-28 11:51       ` Miguel Ojeda
  2026-04-28 12:58         ` Gary Guo
  2026-04-28 18:45         ` Nathan Chancellor
  2026-04-28 17:01       ` Julian Braha
  1 sibling, 2 replies; 15+ messages in thread
From: Miguel Ojeda @ 2026-04-28 11:51 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Julian Braha, Andrew Morton, ljs, Greg Kroah-Hartman,
	Masahiro Yamada, Nathan Chancellor, Nicolas Schier, Miguel Ojeda,
	Jonathan Corbet, linux-kernel, rust-for-linux, linux-doc,
	linux-kbuild

On Mon, Apr 27, 2026 at 9:34 PM Arnd Bergmann <arnd@arndb.de> wrote:
>
> It would also be helpful to reduce the number of hard dependencies
> and ideally only rely on packages that are already shipping in common
> distros.
>
> From the dependency list, it appears that the majority of dependencies
> here are only indirectly pulled in by 'reqwest'. I guess is only
> required for dead-link checking, so maybe that part could be
> made optional?

+1, I had the same thoughts -- even if the fetching is done outside
`make`, it would be still be nice to reduce the dependencies. Even if
it just means calling into `curl` or similar.

Then I thought if the same would apply to `clap` etc., but then again,
we may want to write more tools like this in Rust in the future (we
already felt the pain in the past the pain of not having a e.g. JSON
parser), and whether we could have a more general solution for this,
including perhaps even a kernel.org registry (either as primary or
not) etc.

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-28 11:51       ` Miguel Ojeda
@ 2026-04-28 12:58         ` Gary Guo
  2026-04-28 18:45         ` Nathan Chancellor
  1 sibling, 0 replies; 15+ messages in thread
From: Gary Guo @ 2026-04-28 12:58 UTC (permalink / raw)
  To: Miguel Ojeda, Arnd Bergmann
  Cc: Julian Braha, Andrew Morton, ljs, Greg Kroah-Hartman,
	Masahiro Yamada, Nathan Chancellor, Nicolas Schier, Miguel Ojeda,
	Jonathan Corbet, linux-kernel, rust-for-linux, linux-doc,
	linux-kbuild

On Tue Apr 28, 2026 at 12:51 PM BST, Miguel Ojeda wrote:
>
> Then I thought if the same would apply to `clap` etc., but then again,
> we may want to write more tools like this in Rust in the future (we
> already felt the pain in the past the pain of not having a e.g. JSON
> parser), and whether we could have a more general solution for this,
> including perhaps even a kernel.org registry (either as primary or
> not) etc.

Personally, I think if Internet access is needed, downloading from whatever
registry doesn't really matter as long as we can guarantee that it doesn't
download random packages. Cryptographic hashes would be sufficient for that.

For the part of "not downloading random packages" but only those that are
audited by kernel community -- it could be achieved by using cargo-vet and we
can maintain a shared list of vetted dependencies.

Best,
Gary

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-27 19:33     ` Arnd Bergmann
  2026-04-28 11:51       ` Miguel Ojeda
@ 2026-04-28 17:01       ` Julian Braha
  1 sibling, 0 replies; 15+ messages in thread
From: Julian Braha @ 2026-04-28 17:01 UTC (permalink / raw)
  To: Arnd Bergmann, Miguel Ojeda
  Cc: Andrew Morton, ljs, Greg Kroah-Hartman, Masahiro Yamada,
	Nathan Chancellor, Nicolas Schier, Miguel Ojeda, Jonathan Corbet,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

On Mon, Apr 27, 2026 at 9:34 PM Arnd Bergmann <arnd@arndb.de> wrote:
> It would also be helpful to reduce the number of hard dependencies
> and ideally only rely on packages that are already shipping in common
> distros.
>
> From the dependency list, it appears that the majority of dependencies
> here are only indirectly pulled in by 'reqwest'. I guess is only
> required for dead-link checking, so maybe that part could be
> made optional?

Thanks Arnd, I've done a deep dive on this, and found a number of ways
to reduce the size of the vendored dependencies:

1. replace the reqwest dependency with the smaller ureq,
2. remove rustls as a dependency, and instead expect the user's system
to have openssl,
3. filter out unneeded artifacts (e.g. none of their tests or
docs),
4. filter out non-linux platform support

With all of this, the size of the entire tool source with dependencies
is under 50mb (about the same as the perf tool).

And then an internet connection can be avoided when using Make.

- Julian Braha

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-28  8:23   ` Jani Nikula
@ 2026-04-28 18:06     ` Julian Braha
  0 siblings, 0 replies; 15+ messages in thread
From: Julian Braha @ 2026-04-28 18:06 UTC (permalink / raw)
  To: Jani Nikula, akpm, ljs
  Cc: arnd, gregkh, masahiroy, nathan, nsc, ojeda, corbet, linux-kernel,
	rust-for-linux, linux-doc, linux-kbuild

On Tue, 28 Apr 2026, Jani Nikula <jani.nikula@linux.intel.com> wrote:
> Oh, I'd really like a check on this part from kconfig-language.rst:
>
> i.e. warn on selecting visible symbols or symbols with
> dependencies.

Hi Jani,

This is very doable in the general case of unconditional
visibility/non-visibility. Handling the more complex case of conditional
prompts will require finishing the SMT model, coming later.

I'll add this check, hopefully for kconfirm v0.8 / RFC 2 :)

Thanks for the inspiration, and let me know if you have any other ideas,
too.

- Julian Braha

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 1/2] scripts: add kconfirm
  2026-04-28  7:01   ` Jonathan Corbet
@ 2026-04-28 18:31     ` Nathan Chancellor
  2026-04-28 19:08       ` Jonathan Corbet
  0 siblings, 1 reply; 15+ messages in thread
From: Nathan Chancellor @ 2026-04-28 18:31 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: Julian Braha, akpm, ljs, arnd, gregkh, masahiroy, nsc, ojeda,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

On Tue, Apr 28, 2026 at 01:01:29AM -0600, Jonathan Corbet wrote:
> Also, a nit, but I would really suggest putting it under tools/ rather
> than in the scripts/ dumping ground.

As if tools/ isn't its own dumping ground? :)

While I can understand the desire to avoid adding more random stuff to
scripts/, it sets a confusing precedent because tools/ is not a part of
Kbuild, so I would not expect tools that would run within Kbuild to live
there (which this one appears to do). While there are obvious exceptions
such as objtool and resolve_btfids, I would like to avoid adding new
ones, which aligns with the comment added by Masahiro's commit
6e6ef2da3a28 ("Makefile: add comment to discourage tools/* addition for
kernel builds"). Maybe this could be mitigated with a tools/kbuild/
directory or something but not sure. Just some additional input.

Cheers,
Nathan

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 2/2] Documentation: dev-tools: add kconfirm
  2026-04-28 11:51       ` Miguel Ojeda
  2026-04-28 12:58         ` Gary Guo
@ 2026-04-28 18:45         ` Nathan Chancellor
  1 sibling, 0 replies; 15+ messages in thread
From: Nathan Chancellor @ 2026-04-28 18:45 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Arnd Bergmann, Julian Braha, Andrew Morton, ljs,
	Greg Kroah-Hartman, Masahiro Yamada, Nicolas Schier, Miguel Ojeda,
	Jonathan Corbet, linux-kernel, rust-for-linux, linux-doc,
	linux-kbuild

On Tue, Apr 28, 2026 at 01:51:09PM +0200, Miguel Ojeda wrote:
> Then I thought if the same would apply to `clap` etc., but then again,
> we may want to write more tools like this in Rust in the future (we
> already felt the pain in the past the pain of not having a e.g. JSON
> parser), and whether we could have a more general solution for this,
> including perhaps even a kernel.org registry (either as primary or
> not) etc.

Yeah, I seem to recall a previous proposal for a host program that was
written in Rust, I think it was the vdsocheck tool?

  https://lore.kernel.org/20250812-vdso-absolute-reloc-v4-11-61a8b615e5ec@linutronix.de/

I agree with the sentiment that more host tools may want to be written
in Rust. The Rust build in the kernel does not use cargo altogether, do
we want the same restriction for host programs? Would these tools be
able to share the Rust build rules so that we could have a simple
hostprogs syntax for tools integrating with Kbuild to use? Obviously, it
might be harder for some programs not to use some of the nice third
party libraries available but it seems like it would eliminate the build
system considerations from the discussion at that point.

Cheers,
Nathan

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH 1/2] scripts: add kconfirm
  2026-04-28 18:31     ` Nathan Chancellor
@ 2026-04-28 19:08       ` Jonathan Corbet
  0 siblings, 0 replies; 15+ messages in thread
From: Jonathan Corbet @ 2026-04-28 19:08 UTC (permalink / raw)
  To: Nathan Chancellor
  Cc: Julian Braha, akpm, ljs, arnd, gregkh, masahiroy, nsc, ojeda,
	linux-kernel, rust-for-linux, linux-doc, linux-kbuild

Nathan Chancellor <nathan@kernel.org> writes:

> On Tue, Apr 28, 2026 at 01:01:29AM -0600, Jonathan Corbet wrote:
>> Also, a nit, but I would really suggest putting it under tools/ rather
>> than in the scripts/ dumping ground.
>
> As if tools/ isn't its own dumping ground? :)

It is more structured and more amenable to useful MAINTAINERS entries.

> While I can understand the desire to avoid adding more random stuff to
> scripts/, it sets a confusing precedent because tools/ is not a part of
> Kbuild, so I would not expect tools that would run within Kbuild to live
> there (which this one appears to do). While there are obvious exceptions
> such as objtool and resolve_btfids,

...and the docs build system...

> I would like to avoid adding new
> ones, which aligns with the comment added by Masahiro's commit
> 6e6ef2da3a28 ("Makefile: add comment to discourage tools/* addition for
> kernel builds"). Maybe this could be mitigated with a tools/kbuild/
> directory or something but not sure. Just some additional input.

I don't understand that reticence.  As we build up more tools, why not
organize them in a directory tree, perhaps called "tools", where we can
track who's responsible for the various subtrees?

Thanks,

jon

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-04-28 19:08 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-27 17:44 [RFC PATCH 0/2] scripts: add kconfirm Julian Braha
2026-04-27 17:44 ` [RFC PATCH 1/2] " Julian Braha
2026-04-27 20:48   ` Greg KH
2026-04-28  7:01   ` Jonathan Corbet
2026-04-28 18:31     ` Nathan Chancellor
2026-04-28 19:08       ` Jonathan Corbet
2026-04-27 17:44 ` [RFC PATCH 2/2] Documentation: dev-tools: " Julian Braha
2026-04-27 18:17   ` Miguel Ojeda
2026-04-27 19:33     ` Arnd Bergmann
2026-04-28 11:51       ` Miguel Ojeda
2026-04-28 12:58         ` Gary Guo
2026-04-28 18:45         ` Nathan Chancellor
2026-04-28 17:01       ` Julian Braha
2026-04-28  8:23   ` Jani Nikula
2026-04-28 18:06     ` Julian Braha

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox