kconfig-preprocessor-parser 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 s4nsec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: kconfig-preprocessor-parser
3
+ Version: 0.1.0
4
+ Summary: Extract C preprocessor conditionals and resolve each branch to its effective CONFIG_* condition
5
+ Keywords: c,preprocessor,tree-sitter,kconfig,linux-kernel,ifdef
6
+ Author: s4nsec
7
+ Author-email: s4nsec <s4nsec@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: C
19
+ Classifier: Topic :: Software Development :: Compilers
20
+ Classifier: Topic :: Software Development :: Pre-processors
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: tree-sitter>=0.23
23
+ Requires-Dist: tree-sitter-c>=0.23
24
+ Requires-Python: >=3.10
25
+ Project-URL: Homepage, https://github.com/appleseedlab/kconfig-preprocessor-parser
26
+ Project-URL: Repository, https://github.com/appleseedlab/kconfig-preprocessor-parser
27
+ Project-URL: Issues, https://github.com/appleseedlab/kconfig-preprocessor-parser/issues
28
+ Description-Content-Type: text/markdown
29
+
30
+ # kconfig-preprocessor-parser
31
+
32
+ Extract C preprocessor conditional blocks (`#ifdef` / `#if` / `#elif` / `#else`)
33
+ from source files using [tree-sitter](https://tree-sitter.github.io/), and
34
+ resolve each branch to the **effective condition** that must hold for its lines
35
+ to compile.
36
+
37
+ Built for Linux kernel configuration analysis, where knowing *which* `CONFIG_*`
38
+ symbols guard a given line matters. Scope is deliberately narrow: only
39
+ `CONFIG_*` symbols are resolved (see [Limitations](#limitations)).
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ uv add kconfig-preprocessor-parser
45
+ ```
46
+
47
+ Requires Python 3.10+.
48
+
49
+ ## Quick start
50
+
51
+ Given `s.c`:
52
+
53
+ ```c
54
+ int fn(void) {
55
+ #ifdef CONFIG_A
56
+ a();
57
+ #elif defined(CONFIG_B)
58
+ b();
59
+ #else
60
+ c();
61
+ #endif
62
+ return 0;
63
+ }
64
+ ```
65
+
66
+ Each branch resolves to the full condition, including the negated preceding
67
+ branches:
68
+
69
+ ```python
70
+ from pathlib import Path
71
+ from kconfig_preprocessor_parser import extract_preproc_branch_blocks_from_file
72
+
73
+ for block, cond in extract_preproc_branch_blocks_from_file(
74
+ kernel_src=Path("."), c_file_path=Path("s.c")
75
+ ):
76
+ print(block.type, block.body_lines, "->", cond.raw_expression)
77
+ ```
78
+
79
+ ```
80
+ preproc_ifdef [3] -> CONFIG_A
81
+ preproc_elif [5] -> not CONFIG_A and CONFIG_B
82
+ preproc_else [7] -> not CONFIG_A and not CONFIG_B
83
+ ```
84
+
85
+ Note `raw_expression` for the `#elif` and `#else` branches: the parser carries
86
+ the negation of every earlier branch in the chain, so the expression is the
87
+ complete guard for those lines, not just the local directive.
88
+
89
+ Nested blocks compose the same way — a `#ifdef CONFIG_B` inside a
90
+ `#ifdef CONFIG_A` yields `CONFIG_A and CONFIG_B`.
91
+
92
+ Boolean operators are supported: `defined(A) && defined(B)` resolves to
93
+ `((A) and (B))`, and `||` resolves to `or`.
94
+
95
+ ## Limitations
96
+
97
+ **Value comparisons are not resolved.** Conditions are reduced in terms of
98
+ whether a symbol is defined, so any comparison against a value fails:
99
+
100
+ ```
101
+ #if LINUX_VERSION_CODE > 100 -> parseable=False, "unsupported_condition_expression"
102
+ #if CONFIG_NR_CPUS > 4 -> parseable=False, "unsupported_condition_expression"
103
+ ```
104
+
105
+ Unresolved conditions are never silently dropped. They come back with
106
+ `parseable=False`, a `failure_reason`, and the original text in
107
+ `raw_expression`, so you can filter or handle them yourself.
108
+
109
+ **Only `CONFIG_*` symbols are resolved.** Any other identifier is reported with
110
+ `parseable=False` and `failure_reason="non_config_symbol"`:
111
+
112
+ ```
113
+ #ifdef CONFIG_A -> parseable=True
114
+ #ifdef __KERNEL__ -> parseable=False, "non_config_symbol"
115
+ #ifdef DEBUG -> parseable=False, "non_config_symbol"
116
+ ```
117
+
118
+ **Conditions are read syntactically from one file.** Macros are not expanded and
119
+ `#include` is not followed, so `#define MY_FLAG CONFIG_A` followed by
120
+ `#ifdef MY_FLAG` reports `MY_FLAG`, not `CONFIG_A`. Symbols are reported as
121
+ guards regardless of whether they are actually defined — resolve them against a
122
+ real config with `parse_enabled_configs`.
123
+
124
+ **C only.** Backed by `tree-sitter-c`; C++ is not supported.
125
+
126
+ ## Coverage reports
127
+
128
+ `get_all_lines_in_preproc` and
129
+ `get_all_parseable_preproc_lines_in_covered_functions` read syzkaller coverage
130
+ JSON. Reports record absolute paths from the machine the kernel was built on,
131
+ which rarely match the tree you are analyzing, so paths are mapped onto
132
+ `kernel_src` by longest matching suffix:
133
+
134
+ ```python
135
+ get_all_parseable_preproc_lines_in_covered_functions(
136
+ coverage_file=Path("coverage.json"),
137
+ kernel_src=Path("/src/linux"),
138
+ )
139
+ ```
140
+
141
+ Pass `strip_prefix` to map them explicitly instead:
142
+
143
+ ```python
144
+ get_all_parseable_preproc_lines_in_covered_functions(
145
+ coverage_file=Path("coverage.json"),
146
+ kernel_src=Path("/src/linux"),
147
+ strip_prefix="/build/ci/kernel-6.1",
148
+ )
149
+ ```
150
+
151
+ Files that cannot be mapped onto `kernel_src` are skipped, not fatal.
152
+
153
+ ## License
154
+
155
+ MIT
@@ -0,0 +1,126 @@
1
+ # kconfig-preprocessor-parser
2
+
3
+ Extract C preprocessor conditional blocks (`#ifdef` / `#if` / `#elif` / `#else`)
4
+ from source files using [tree-sitter](https://tree-sitter.github.io/), and
5
+ resolve each branch to the **effective condition** that must hold for its lines
6
+ to compile.
7
+
8
+ Built for Linux kernel configuration analysis, where knowing *which* `CONFIG_*`
9
+ symbols guard a given line matters. Scope is deliberately narrow: only
10
+ `CONFIG_*` symbols are resolved (see [Limitations](#limitations)).
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ uv add kconfig-preprocessor-parser
16
+ ```
17
+
18
+ Requires Python 3.10+.
19
+
20
+ ## Quick start
21
+
22
+ Given `s.c`:
23
+
24
+ ```c
25
+ int fn(void) {
26
+ #ifdef CONFIG_A
27
+ a();
28
+ #elif defined(CONFIG_B)
29
+ b();
30
+ #else
31
+ c();
32
+ #endif
33
+ return 0;
34
+ }
35
+ ```
36
+
37
+ Each branch resolves to the full condition, including the negated preceding
38
+ branches:
39
+
40
+ ```python
41
+ from pathlib import Path
42
+ from kconfig_preprocessor_parser import extract_preproc_branch_blocks_from_file
43
+
44
+ for block, cond in extract_preproc_branch_blocks_from_file(
45
+ kernel_src=Path("."), c_file_path=Path("s.c")
46
+ ):
47
+ print(block.type, block.body_lines, "->", cond.raw_expression)
48
+ ```
49
+
50
+ ```
51
+ preproc_ifdef [3] -> CONFIG_A
52
+ preproc_elif [5] -> not CONFIG_A and CONFIG_B
53
+ preproc_else [7] -> not CONFIG_A and not CONFIG_B
54
+ ```
55
+
56
+ Note `raw_expression` for the `#elif` and `#else` branches: the parser carries
57
+ the negation of every earlier branch in the chain, so the expression is the
58
+ complete guard for those lines, not just the local directive.
59
+
60
+ Nested blocks compose the same way — a `#ifdef CONFIG_B` inside a
61
+ `#ifdef CONFIG_A` yields `CONFIG_A and CONFIG_B`.
62
+
63
+ Boolean operators are supported: `defined(A) && defined(B)` resolves to
64
+ `((A) and (B))`, and `||` resolves to `or`.
65
+
66
+ ## Limitations
67
+
68
+ **Value comparisons are not resolved.** Conditions are reduced in terms of
69
+ whether a symbol is defined, so any comparison against a value fails:
70
+
71
+ ```
72
+ #if LINUX_VERSION_CODE > 100 -> parseable=False, "unsupported_condition_expression"
73
+ #if CONFIG_NR_CPUS > 4 -> parseable=False, "unsupported_condition_expression"
74
+ ```
75
+
76
+ Unresolved conditions are never silently dropped. They come back with
77
+ `parseable=False`, a `failure_reason`, and the original text in
78
+ `raw_expression`, so you can filter or handle them yourself.
79
+
80
+ **Only `CONFIG_*` symbols are resolved.** Any other identifier is reported with
81
+ `parseable=False` and `failure_reason="non_config_symbol"`:
82
+
83
+ ```
84
+ #ifdef CONFIG_A -> parseable=True
85
+ #ifdef __KERNEL__ -> parseable=False, "non_config_symbol"
86
+ #ifdef DEBUG -> parseable=False, "non_config_symbol"
87
+ ```
88
+
89
+ **Conditions are read syntactically from one file.** Macros are not expanded and
90
+ `#include` is not followed, so `#define MY_FLAG CONFIG_A` followed by
91
+ `#ifdef MY_FLAG` reports `MY_FLAG`, not `CONFIG_A`. Symbols are reported as
92
+ guards regardless of whether they are actually defined — resolve them against a
93
+ real config with `parse_enabled_configs`.
94
+
95
+ **C only.** Backed by `tree-sitter-c`; C++ is not supported.
96
+
97
+ ## Coverage reports
98
+
99
+ `get_all_lines_in_preproc` and
100
+ `get_all_parseable_preproc_lines_in_covered_functions` read syzkaller coverage
101
+ JSON. Reports record absolute paths from the machine the kernel was built on,
102
+ which rarely match the tree you are analyzing, so paths are mapped onto
103
+ `kernel_src` by longest matching suffix:
104
+
105
+ ```python
106
+ get_all_parseable_preproc_lines_in_covered_functions(
107
+ coverage_file=Path("coverage.json"),
108
+ kernel_src=Path("/src/linux"),
109
+ )
110
+ ```
111
+
112
+ Pass `strip_prefix` to map them explicitly instead:
113
+
114
+ ```python
115
+ get_all_parseable_preproc_lines_in_covered_functions(
116
+ coverage_file=Path("coverage.json"),
117
+ kernel_src=Path("/src/linux"),
118
+ strip_prefix="/build/ci/kernel-6.1",
119
+ )
120
+ ```
121
+
122
+ Files that cannot be mapped onto `kernel_src` are skipped, not fatal.
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "kconfig-preprocessor-parser"
3
+ version = "0.1.0"
4
+ description = "Extract C preprocessor conditionals and resolve each branch to its effective CONFIG_* condition"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "tree-sitter>=0.23",
11
+ "tree-sitter-c>=0.23",
12
+ ]
13
+ keywords = [
14
+ "c",
15
+ "preprocessor",
16
+ "tree-sitter",
17
+ "kconfig",
18
+ "linux-kernel",
19
+ "ifdef",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Developers",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: C",
31
+ "Topic :: Software Development :: Compilers",
32
+ "Topic :: Software Development :: Pre-processors",
33
+ "Typing :: Typed",
34
+ ]
35
+
36
+ [[project.authors]]
37
+ name = "s4nsec"
38
+ email = "s4nsec@gmail.com"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/appleseedlab/kconfig-preprocessor-parser"
42
+ Repository = "https://github.com/appleseedlab/kconfig-preprocessor-parser"
43
+ Issues = "https://github.com/appleseedlab/kconfig-preprocessor-parser/issues"
44
+
45
+ [dependency-groups]
46
+ dev = ["pytest>=8"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
50
+
51
+ [tool.uv.build-backend]
52
+ source-include = ["tests/**"]
53
+
54
+ [[tool.uv.index]]
55
+ name = "testpypi"
56
+ url = "https://test.pypi.org/simple/"
57
+ publish-url = "https://test.pypi.org/legacy/"
58
+ explicit = true
59
+
60
+ [build-system]
61
+ requires = ["uv_build>=0.12.7,<0.13"]
62
+ build-backend = "uv_build"
@@ -0,0 +1,54 @@
1
+ [project]
2
+ name = "kconfig-preprocessor-parser"
3
+ version = "0.1.0"
4
+ description = "Extract C preprocessor conditionals and resolve each branch to its effective CONFIG_* condition"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "s4nsec", email = "s4nsec@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "tree-sitter>=0.23",
14
+ "tree-sitter-c>=0.23",
15
+ ]
16
+ keywords = ["c", "preprocessor", "tree-sitter", "kconfig", "linux-kernel", "ifdef"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: C",
27
+ "Topic :: Software Development :: Compilers",
28
+ "Topic :: Software Development :: Pre-processors",
29
+ "Typing :: Typed",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/appleseedlab/kconfig-preprocessor-parser"
34
+ Repository = "https://github.com/appleseedlab/kconfig-preprocessor-parser"
35
+ Issues = "https://github.com/appleseedlab/kconfig-preprocessor-parser/issues"
36
+
37
+ [dependency-groups]
38
+ dev = ["pytest>=8"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+
43
+ [tool.uv.build-backend]
44
+ source-include = ["tests/**"]
45
+
46
+ [[tool.uv.index]]
47
+ name = "testpypi"
48
+ url = "https://test.pypi.org/simple/"
49
+ publish-url = "https://test.pypi.org/legacy/"
50
+ explicit = true
51
+
52
+ [build-system]
53
+ requires = ["uv_build>=0.12.7,<0.13"]
54
+ build-backend = "uv_build"
@@ -0,0 +1,33 @@
1
+ from kconfig_preprocessor_parser.parser import (
2
+ ConditionInfo,
3
+ EffectiveConditionInfo,
4
+ PreprocBlock,
5
+ extract_all_tracked_lines,
6
+ extract_preproc_branch_blocks_from_file,
7
+ extract_preproc_blocks_from_file,
8
+ extract_preproc_blocks_from_covered_functions,
9
+ find_function_for_line,
10
+ get_all_parseable_preproc_lines_in_covered_functions,
11
+ get_all_lines_in_preproc,
12
+ get_function_name,
13
+ parse_enabled_configs,
14
+ parse_function_spans,
15
+ write_effective_conditions_for_requested_lines,
16
+ )
17
+
18
+ __all__ = [
19
+ "ConditionInfo",
20
+ "EffectiveConditionInfo",
21
+ "PreprocBlock",
22
+ "extract_all_tracked_lines",
23
+ "extract_preproc_branch_blocks_from_file",
24
+ "extract_preproc_blocks_from_file",
25
+ "extract_preproc_blocks_from_covered_functions",
26
+ "find_function_for_line",
27
+ "get_all_parseable_preproc_lines_in_covered_functions",
28
+ "get_all_lines_in_preproc",
29
+ "get_function_name",
30
+ "parse_enabled_configs",
31
+ "parse_function_spans",
32
+ "write_effective_conditions_for_requested_lines",
33
+ ]