time-assurance-code-scanner 0.2.0rc2__py3-none-any.whl

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.
Files changed (94) hide show
  1. config_detector/__init__.py +11 -0
  2. config_detector/analyzers/__init__.py +9 -0
  3. config_detector/analyzers/architecture.py +188 -0
  4. config_detector/analyzers/time_t_config.py +183 -0
  5. config_detector/build_parsers/__init__.py +10 -0
  6. config_detector/build_parsers/base.py +82 -0
  7. config_detector/build_parsers/cmake.py +113 -0
  8. config_detector/build_parsers/makefile.py +112 -0
  9. config_detector/cache.py +109 -0
  10. config_detector/cli.py +403 -0
  11. config_detector/detector.py +106 -0
  12. config_detector/hybrid_detector.py +384 -0
  13. config_detector/likelihoods.py +166 -0
  14. config_detector/llm_analyzer.py +368 -0
  15. config_detector/output/__init__.py +8 -0
  16. config_detector/output/likelihood.py +314 -0
  17. config_detector/validator.py +147 -0
  18. envui/__init__.py +4 -0
  19. envui/cli/__init__.py +4 -0
  20. envui/cli/derive_fields.py +147 -0
  21. envui/cli/env_wizard.py +381 -0
  22. envui/cli/io_utils.py +139 -0
  23. envui/cli/validate_env.py +156 -0
  24. envui/examples/env_config.sample.json +15 -0
  25. envui/schemas/environment.schema.json +151 -0
  26. report_renderer/__init__.py +8 -0
  27. report_renderer/__main__.py +8 -0
  28. report_renderer/cli.py +165 -0
  29. report_renderer/core.py +535 -0
  30. report_renderer/fixtures/malformed.json +20 -0
  31. report_renderer/fixtures/missing-fields.json +22 -0
  32. report_renderer/fixtures/typical.json +38 -0
  33. report_renderer/renderers.py +743 -0
  34. tacs/__init__.py +6 -0
  35. tacs/batch_render_reports.py +434 -0
  36. tacs/batch_repo_scan.py +390 -0
  37. tacs/batch_scan_repos.py +1556 -0
  38. tacs/cli.py +157 -0
  39. tacs/core/__init__.py +4 -0
  40. tacs/core/alias_scope.py +256 -0
  41. tacs/core/arithmetic_scanner.py +212 -0
  42. tacs/core/candidate_evidence.py +220 -0
  43. tacs/core/candidate_utils.py +238 -0
  44. tacs/core/config_validator.py +216 -0
  45. tacs/core/define_scanner.py +325 -0
  46. tacs/core/discovery_manager.py +389 -0
  47. tacs/core/env_capabilities.py +95 -0
  48. tacs/core/file_limits.py +68 -0
  49. tacs/core/function_analysis_response.py +438 -0
  50. tacs/core/function_analyzer.py +458 -0
  51. tacs/core/function_llm_client.py +1100 -0
  52. tacs/core/function_scanner.py +485 -0
  53. tacs/core/function_schemas.py +105 -0
  54. tacs/core/include_patterns.py +27 -0
  55. tacs/core/io_boundary_analyzer.py +706 -0
  56. tacs/core/io_format_parser.py +176 -0
  57. tacs/core/ir_adapter.py +465 -0
  58. tacs/core/ir_match.py +410 -0
  59. tacs/core/llm_client.py +2093 -0
  60. tacs/core/llm_errors.py +360 -0
  61. tacs/core/llm_logger.py +42 -0
  62. tacs/core/llm_prompt.py +45 -0
  63. tacs/core/llm_response.py +34 -0
  64. tacs/core/logging_config.py +128 -0
  65. tacs/core/metrics.py +92 -0
  66. tacs/core/migration_analyzer.py +373 -0
  67. tacs/core/model_assessment.py +368 -0
  68. tacs/core/path_utils.py +190 -0
  69. tacs/core/pipeline.py +3363 -0
  70. tacs/core/rule_catalog.py +206 -0
  71. tacs/core/run_ids.py +34 -0
  72. tacs/core/scan_session.py +761 -0
  73. tacs/core/schema.py +484 -0
  74. tacs/core/source_files.py +149 -0
  75. tacs/core/status_logger.py +97 -0
  76. tacs/core/struct_filter.py +108 -0
  77. tacs/core/typedef_scanner.py +246 -0
  78. tacs/fixtures/repo_lists/test_ilp32_repos.jsonl +7 -0
  79. tacs/fixtures/repo_lists/test_repos.jsonl +10 -0
  80. tacs/fixtures/repo_lists/test_time_t_size_repos.jsonl +7 -0
  81. tacs/llm/__init__.py +43 -0
  82. tacs/llm/env.py +166 -0
  83. tacs/llm/factory.py +29 -0
  84. tacs/llm/openai_models.py +187 -0
  85. tacs/python/y2038scan_fast_json_group.py +833 -0
  86. tacs/rules/retired_rule_ids.json +1 -0
  87. tacs/rules/y2038_sample_rules.json +6134 -0
  88. tacs/scan_command.py +288 -0
  89. time_assurance_code_scanner-0.2.0rc2.dist-info/METADATA +184 -0
  90. time_assurance_code_scanner-0.2.0rc2.dist-info/RECORD +94 -0
  91. time_assurance_code_scanner-0.2.0rc2.dist-info/WHEEL +5 -0
  92. time_assurance_code_scanner-0.2.0rc2.dist-info/entry_points.txt +2 -0
  93. time_assurance_code_scanner-0.2.0rc2.dist-info/licenses/LICENSE +203 -0
  94. time_assurance_code_scanner-0.2.0rc2.dist-info/top_level.txt +4 -0
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """
5
+ Auto-Detection Configuration Utility
6
+
7
+ A separate Python utility that analyzes build systems and outputs likelihood scores
8
+ for all eight possible environment configurations.
9
+ """
10
+
11
+ __version__ = "1.0.0"
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Analyzers for detecting architecture, time_t configuration, and C library."""
5
+
6
+ from .architecture import ArchitectureDetector
7
+ from .time_t_config import TimeTConfigDetector
8
+
9
+ __all__ = ['ArchitectureDetector', 'TimeTConfigDetector']
@@ -0,0 +1,188 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Architecture detection analyzer (ILP32 vs LP64)."""
5
+
6
+ import re
7
+ from typing import Dict, Tuple, Optional, List
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class ArchitectureEvidence:
13
+ """Evidence for architecture detection."""
14
+ source: str # "makefile", "cmake", "toolchain", etc.
15
+ indicator: str # What was detected
16
+ architecture: str # "ILP32" or "LP64"
17
+ confidence: float # 0.0 to 1.0
18
+
19
+
20
+ class ArchitectureDetector:
21
+ """Detects hardware architecture (ILP32 vs LP64) from build system information."""
22
+
23
+ def __init__(self):
24
+ """Initialize the architecture detector."""
25
+ pass
26
+
27
+ def detect(self, extracted_info: Dict) -> Tuple[Optional[str], float, List[ArchitectureEvidence]]:
28
+ """
29
+ Detect architecture from extracted build system information.
30
+
31
+ Args:
32
+ extracted_info: Dictionary from build parser with 'cflags', 'defines',
33
+ 'variables', 'toolchain'
34
+
35
+ Returns:
36
+ Tuple of (architecture, confidence, evidence_list)
37
+ - architecture: "ILP32", "LP64", or None if uncertain
38
+ - confidence: 0.0 to 1.0
39
+ - evidence_list: List of ArchitectureEvidence objects
40
+ """
41
+ evidence: List[ArchitectureEvidence] = []
42
+
43
+ cflags = extracted_info.get('cflags', [])
44
+ variables = extracted_info.get('variables', {})
45
+ toolchain = extracted_info.get('toolchain', '')
46
+
47
+ # Check explicit architecture flags (highest confidence)
48
+ for flag in cflags:
49
+ if flag == '-m32':
50
+ evidence.append(ArchitectureEvidence(
51
+ source='compiler_flag',
52
+ indicator='-m32',
53
+ architecture='ILP32',
54
+ confidence=0.95
55
+ ))
56
+ elif flag == '-m64':
57
+ evidence.append(ArchitectureEvidence(
58
+ source='compiler_flag',
59
+ indicator='-m64',
60
+ architecture='LP64',
61
+ confidence=0.95
62
+ ))
63
+ elif flag.startswith('-march='):
64
+ arch = self._parse_march(flag)
65
+ if arch:
66
+ evidence.append(ArchitectureEvidence(
67
+ source='compiler_flag',
68
+ indicator=flag,
69
+ architecture=arch[0],
70
+ confidence=arch[1]
71
+ ))
72
+
73
+ # Check toolchain (medium-high confidence)
74
+ if toolchain:
75
+ arch = self._parse_toolchain(toolchain)
76
+ if arch:
77
+ evidence.append(ArchitectureEvidence(
78
+ source='toolchain',
79
+ indicator=toolchain,
80
+ architecture=arch[0],
81
+ confidence=arch[1]
82
+ ))
83
+
84
+ # Check CMAKE_SYSTEM_PROCESSOR (medium confidence)
85
+ if 'CMAKE_SYSTEM_PROCESSOR' in variables:
86
+ processor = variables['CMAKE_SYSTEM_PROCESSOR'].lower()
87
+ arch = self._parse_processor(processor)
88
+ if arch:
89
+ evidence.append(ArchitectureEvidence(
90
+ source='cmake',
91
+ indicator=f'CMAKE_SYSTEM_PROCESSOR={processor}',
92
+ architecture=arch[0],
93
+ confidence=arch[1]
94
+ ))
95
+
96
+ # Determine final architecture and confidence
97
+ if not evidence:
98
+ return None, 0.0, []
99
+
100
+ # Count evidence for each architecture
101
+ ilp32_evidence = [e for e in evidence if e.architecture == 'ILP32']
102
+ lp64_evidence = [e for e in evidence if e.architecture == 'LP64']
103
+
104
+ # If conflicting evidence, use highest confidence
105
+ if ilp32_evidence and lp64_evidence:
106
+ ilp32_max = max(e.confidence for e in ilp32_evidence)
107
+ lp64_max = max(e.confidence for e in lp64_evidence)
108
+
109
+ if ilp32_max > lp64_max:
110
+ return 'ILP32', ilp32_max * 0.9, evidence # Slightly reduce due to conflict
111
+ else:
112
+ return 'LP64', lp64_max * 0.9, evidence
113
+ elif ilp32_evidence:
114
+ max_conf = max(e.confidence for e in ilp32_evidence)
115
+ return 'ILP32', max_conf, evidence
116
+ elif lp64_evidence:
117
+ max_conf = max(e.confidence for e in lp64_evidence)
118
+ return 'LP64', max_conf, evidence
119
+
120
+ return None, 0.0, evidence
121
+
122
+ def _parse_march(self, flag: str) -> Optional[Tuple[str, float]]:
123
+ """Parse -march= flag to determine architecture."""
124
+ match = re.search(r'-march=([\w-]+)', flag)
125
+ if not match:
126
+ return None
127
+
128
+ arch_str = match.group(1).lower()
129
+
130
+ # ILP32 indicators
131
+ if any(x in arch_str for x in ['armv7', 'armv6', 'armv5', 'armv4']):
132
+ return ('ILP32', 0.85)
133
+ if arch_str in ['i386', 'i686']:
134
+ return ('ILP32', 0.85)
135
+
136
+ # LP64 indicators
137
+ if arch_str in ['x86_64', 'amd64']:
138
+ return ('LP64', 0.95)
139
+ if 'armv8' in arch_str or 'aarch64' in arch_str:
140
+ return ('LP64', 0.90)
141
+ if arch_str.startswith('mips64'):
142
+ return ('LP64', 0.85)
143
+
144
+ return None
145
+
146
+ def _parse_toolchain(self, toolchain: str) -> Optional[Tuple[str, float]]:
147
+ """Parse toolchain name to determine architecture."""
148
+ toolchain_lower = toolchain.lower()
149
+
150
+ # ILP32 toolchains
151
+ if 'arm-none-eabi' in toolchain_lower or 'arm-eabi' in toolchain_lower:
152
+ return ('ILP32', 0.85)
153
+ if toolchain_lower.startswith('arm-'):
154
+ return ('ILP32', 0.80)
155
+ if 'mips32' in toolchain_lower:
156
+ return ('ILP32', 0.80)
157
+
158
+ # LP64 toolchains
159
+ if 'aarch64' in toolchain_lower:
160
+ return ('LP64', 0.90)
161
+ if 'x86_64' in toolchain_lower or 'amd64' in toolchain_lower:
162
+ return ('LP64', 0.90)
163
+ if 'mips64' in toolchain_lower:
164
+ return ('LP64', 0.85)
165
+
166
+ return None
167
+
168
+ def _parse_processor(self, processor: str) -> Optional[Tuple[str, float]]:
169
+ """Parse CMAKE_SYSTEM_PROCESSOR to determine architecture."""
170
+ processor_lower = processor.lower()
171
+
172
+ # ILP32 processors
173
+ if processor_lower in ['arm', 'armv7', 'armv6', 'armv5']:
174
+ return ('ILP32', 0.80)
175
+ if processor_lower in ['i386', 'i686']:
176
+ return ('ILP32', 0.80)
177
+ if 'mips32' in processor_lower:
178
+ return ('ILP32', 0.75)
179
+
180
+ # LP64 processors
181
+ if processor_lower in ['aarch64', 'arm64', 'armv8']:
182
+ return ('LP64', 0.85)
183
+ if processor_lower in ['x86_64', 'amd64']:
184
+ return ('LP64', 0.90)
185
+ if 'mips64' in processor_lower:
186
+ return ('LP64', 0.80)
187
+
188
+ return None
@@ -0,0 +1,183 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """time_t configuration detection analyzer."""
5
+
6
+ import re
7
+ from typing import Dict, Tuple, Optional, List
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class TimeTEvidence:
13
+ """Evidence for time_t configuration detection."""
14
+ source: str # "makefile", "cmake", etc.
15
+ indicator: str # What was detected
16
+ time_t_size: Optional[int] = None # 32 or 64, or None
17
+ time_t_signed: Optional[str] = None # "signed" or "unsigned" or None
18
+ confidence: float = 0.0 # 0.0 to 1.0
19
+
20
+
21
+ class TimeTConfigDetector:
22
+ """Detects time_t size and signedness configuration from build system information."""
23
+
24
+ def __init__(self):
25
+ """Initialize the time_t config detector."""
26
+ pass
27
+
28
+ def detect(self, extracted_info: Dict, architecture: Optional[str] = None) -> Tuple[Optional[int], float, Optional[str], float, List[TimeTEvidence]]:
29
+ """
30
+ Detect time_t size and signedness from extracted build system information.
31
+
32
+ Args:
33
+ extracted_info: Dictionary from build parser
34
+ architecture: Detected architecture (ILP32 or LP64) for inference
35
+
36
+ Returns:
37
+ Tuple of (time_t_size, time_t_size_confidence, time_t_signed, time_t_signed_confidence, evidence_list)
38
+ - time_t_size: 32, 64, or None if uncertain
39
+ - time_t_size_confidence: 0.0 to 1.0
40
+ - time_t_signed: "signed" or "unsigned" or None if uncertain
41
+ - time_t_signed_confidence: 0.0 to 1.0
42
+ - evidence_list: List of TimeTEvidence objects
43
+ """
44
+ evidence: List[TimeTEvidence] = []
45
+
46
+ defines = extracted_info.get('defines', [])
47
+ cflags = extracted_info.get('cflags', [])
48
+
49
+ # Check for explicit _TIME_BITS define (highest confidence)
50
+ time_bits_value = None
51
+ for define in defines:
52
+ if define.startswith('TIME_BITS='):
53
+ time_bits_value = define.split('=')[1]
54
+ elif define == 'TIME_BITS':
55
+ # Check cflags for -D_TIME_BITS=64
56
+ for flag in cflags:
57
+ match = re.search(r'-D_TIME_BITS=(\d+)', flag)
58
+ if match:
59
+ time_bits_value = match.group(1)
60
+ break
61
+
62
+ # Also check cflags directly
63
+ for flag in cflags:
64
+ match = re.search(r'-D_TIME_BITS=(\d+)', flag)
65
+ if match:
66
+ time_bits_value = match.group(1)
67
+ evidence.append(TimeTEvidence(
68
+ source='compiler_flag',
69
+ indicator=f'-D_TIME_BITS={time_bits_value}',
70
+ time_t_size=int(time_bits_value),
71
+ confidence=0.95
72
+ ))
73
+ break
74
+
75
+ # Detect time_t signedness
76
+ time_t_signed = None
77
+ time_t_signed_confidence = 0.0
78
+
79
+ # Check for explicit unsigned time_t defines
80
+ for define in defines:
81
+ if 'TIME_T_UNSIGNED' in define.upper() or 'CONFIG_TIME_T_UNSIGNED' in define.upper():
82
+ time_t_signed = 'unsigned'
83
+ time_t_signed_confidence = 0.85
84
+ evidence.append(TimeTEvidence(
85
+ source='define',
86
+ indicator=define,
87
+ time_t_signed='unsigned',
88
+ confidence=0.85
89
+ ))
90
+ break
91
+
92
+ # Check cflags for unsigned time_t
93
+ for flag in cflags:
94
+ if '-D_TIME_T_UNSIGNED' in flag or '-DCONFIG_TIME_T_UNSIGNED' in flag:
95
+ time_t_signed = 'unsigned'
96
+ time_t_signed_confidence = 0.85
97
+ evidence.append(TimeTEvidence(
98
+ source='compiler_flag',
99
+ indicator=flag,
100
+ time_t_signed='unsigned',
101
+ confidence=0.85
102
+ ))
103
+ break
104
+
105
+ # Infer signedness from library (if we can detect it)
106
+ # Most standard libraries (glibc, musl) use signed by default
107
+ # Some embedded libraries (Zephyr, picolibc) may use unsigned
108
+ # For now, default to signed if not explicitly detected
109
+ if time_t_signed is None:
110
+ # Default to signed (most common), but with low confidence
111
+ time_t_signed = 'signed'
112
+ time_t_signed_confidence = 0.5 # Low confidence default
113
+
114
+ if time_bits_value:
115
+ # Add signedness to evidence if not already added
116
+ if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
117
+ evidence.append(TimeTEvidence(
118
+ source='default',
119
+ indicator=f'Default {time_t_signed} (not explicitly detected)',
120
+ time_t_signed=time_t_signed,
121
+ confidence=time_t_signed_confidence
122
+ ))
123
+ return int(time_bits_value), 0.95, time_t_signed, time_t_signed_confidence, evidence
124
+
125
+ # Check for _FILE_OFFSET_BITS=64 (hint for 64-bit time_t)
126
+ for flag in cflags:
127
+ if '-D_FILE_OFFSET_BITS=64' in flag:
128
+ evidence.append(TimeTEvidence(
129
+ source='compiler_flag',
130
+ indicator='-D_FILE_OFFSET_BITS=64',
131
+ time_t_size=64,
132
+ time_t_signed=time_t_signed,
133
+ confidence=0.60 # Lower confidence, just a hint
134
+ ))
135
+
136
+ # Infer from architecture if no explicit flags
137
+ if architecture:
138
+ if architecture == 'LP64':
139
+ # LP64 typically uses 64-bit time_t by default
140
+ # Add signedness to evidence if not already added
141
+ if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
142
+ evidence.append(TimeTEvidence(
143
+ source='default',
144
+ indicator=f'Default {time_t_signed} (not explicitly detected)',
145
+ time_t_signed=time_t_signed,
146
+ confidence=time_t_signed_confidence
147
+ ))
148
+ evidence.append(TimeTEvidence(
149
+ source='architecture_inference',
150
+ indicator=f'LP64 architecture (default 64-bit time_t)',
151
+ time_t_size=64,
152
+ time_t_signed=time_t_signed,
153
+ confidence=0.70
154
+ ))
155
+ return 64, 0.70, time_t_signed, time_t_signed_confidence, evidence
156
+ elif architecture == 'ILP32':
157
+ # ILP32 typically uses 32-bit time_t by default (unless explicitly set)
158
+ # Add signedness to evidence if not already added
159
+ if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
160
+ evidence.append(TimeTEvidence(
161
+ source='default',
162
+ indicator=f'Default {time_t_signed} (not explicitly detected)',
163
+ time_t_signed=time_t_signed,
164
+ confidence=time_t_signed_confidence
165
+ ))
166
+ evidence.append(TimeTEvidence(
167
+ source='architecture_inference',
168
+ indicator=f'ILP32 architecture (default 32-bit time_t)',
169
+ time_t_size=32,
170
+ time_t_signed=time_t_signed,
171
+ confidence=0.65
172
+ ))
173
+ return 32, 0.65, time_t_signed, time_t_signed_confidence, evidence
174
+
175
+ # No evidence found - still return signedness defaults
176
+ if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
177
+ evidence.append(TimeTEvidence(
178
+ source='default',
179
+ indicator=f'Default {time_t_signed} (not explicitly detected)',
180
+ time_t_signed=time_t_signed,
181
+ confidence=time_t_signed_confidence
182
+ ))
183
+ return None, 0.0, time_t_signed, time_t_signed_confidence, evidence
@@ -0,0 +1,10 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Build system parsers for extracting compiler flags and configuration."""
5
+
6
+ from .base import BaseParser
7
+ from .makefile import MakefileParser
8
+ from .cmake import CMakeParser
9
+
10
+ __all__ = ['BaseParser', 'MakefileParser', 'CMakeParser']
@@ -0,0 +1,82 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Base parser interface for build system files."""
5
+
6
+ from abc import ABC, abstractmethod
7
+ from pathlib import Path
8
+ from typing import List, Dict, Set
9
+
10
+
11
+ class BaseParser(ABC):
12
+ """Base class for build system parsers."""
13
+
14
+ def __init__(self, root_path: Path):
15
+ """
16
+ Initialize parser.
17
+
18
+ Args:
19
+ root_path: Root directory of the project
20
+ """
21
+ self.root_path = root_path
22
+
23
+ @abstractmethod
24
+ def find_build_files(self) -> List[Path]:
25
+ """
26
+ Find relevant build system files.
27
+
28
+ Returns:
29
+ List of paths to build files
30
+ """
31
+ pass
32
+
33
+ @abstractmethod
34
+ def extract_flags(self, build_file: Path) -> Dict[str, any]:
35
+ """
36
+ Extract compiler flags and configuration from a build file.
37
+
38
+ Args:
39
+ build_file: Path to the build file
40
+
41
+ Returns:
42
+ Dictionary with extracted information:
43
+ - 'cflags': List of C compiler flags
44
+ - 'defines': List of -D defines
45
+ - 'variables': Dictionary of variable assignments
46
+ - 'toolchain': Toolchain information if found
47
+ """
48
+ pass
49
+
50
+ def extract_all(self) -> Dict[str, any]:
51
+ """
52
+ Extract all information from all build files.
53
+
54
+ Returns:
55
+ Combined dictionary with all extracted information
56
+ """
57
+ build_files = self.find_build_files()
58
+
59
+ all_cflags: Set[str] = set()
60
+ all_defines: Set[str] = set()
61
+ all_variables: Dict[str, str] = {}
62
+ toolchain: str = ""
63
+
64
+ for build_file in build_files:
65
+ try:
66
+ extracted = self.extract_flags(build_file)
67
+ all_cflags.update(extracted.get('cflags', []))
68
+ all_defines.update(extracted.get('defines', []))
69
+ all_variables.update(extracted.get('variables', {}))
70
+ if extracted.get('toolchain') and not toolchain:
71
+ toolchain = extracted.get('toolchain')
72
+ except Exception as e:
73
+ # Continue if one file fails
74
+ continue
75
+
76
+ return {
77
+ 'cflags': list(all_cflags),
78
+ 'defines': list(all_defines),
79
+ 'variables': all_variables,
80
+ 'toolchain': toolchain,
81
+ 'build_files_found': len(build_files)
82
+ }
@@ -0,0 +1,113 @@
1
+ # Copyright (c) 2026 Y2038.com LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """CMake parser for extracting compiler flags and configuration."""
5
+
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Dict, List, Set
9
+ from .base import BaseParser
10
+
11
+
12
+ class CMakeParser(BaseParser):
13
+ """Parser for CMake build systems."""
14
+
15
+ def find_build_files(self) -> List[Path]:
16
+ """Find CMakeLists.txt files in the project."""
17
+ build_files = []
18
+ seen_files = set()
19
+
20
+ # Check root directory first
21
+ cmake_file = self.root_path / 'CMakeLists.txt'
22
+ if cmake_file.exists():
23
+ build_files.append(cmake_file)
24
+ seen_files.add(cmake_file)
25
+
26
+ # Search for CMakeLists.txt in subdirectories (limit depth)
27
+ for cmake_file in self.root_path.rglob('CMakeLists.txt'):
28
+ if cmake_file.is_file() and cmake_file not in seen_files:
29
+ # Limit to reasonable depth and common locations
30
+ rel_path = cmake_file.relative_to(self.root_path)
31
+ if len(rel_path.parts) <= 4: # Max 4 levels deep
32
+ build_files.append(cmake_file)
33
+ seen_files.add(cmake_file)
34
+
35
+ return build_files[:10] # Limit to first 10
36
+
37
+ def extract_flags(self, build_file: Path) -> Dict[str, any]:
38
+ """
39
+ Extract compiler flags from a CMakeLists.txt file.
40
+
41
+ Looks for:
42
+ - add_definitions(), add_compile_definitions()
43
+ - set(CMAKE_C_FLAGS ...)
44
+ - set(CMAKE_SYSTEM_PROCESSOR ...)
45
+ - find_package() calls
46
+ """
47
+ try:
48
+ content = build_file.read_text(encoding='utf-8', errors='ignore')
49
+ except Exception:
50
+ return {'cflags': [], 'defines': [], 'variables': {}, 'toolchain': ''}
51
+
52
+ cflags: Set[str] = set()
53
+ defines: Set[str] = set()
54
+ variables: Dict[str, str] = {}
55
+ toolchain = ""
56
+
57
+ # Pattern for set() commands: set(VAR value) or set(VAR "value")
58
+ set_pattern = re.compile(r'set\s*\(\s*(\w+)\s+(.+?)\s*\)', re.IGNORECASE | re.DOTALL)
59
+
60
+ # Pattern for add_definitions: add_definitions(-DFOO -DBAR=value)
61
+ def_pattern = re.compile(r'add_(?:compile_)?definitions\s*\((.+?)\)', re.IGNORECASE | re.DOTALL)
62
+
63
+ # Pattern for CMAKE_SYSTEM_PROCESSOR
64
+ processor_pattern = re.compile(r'CMAKE_SYSTEM_PROCESSOR', re.IGNORECASE)
65
+
66
+ # Extract set() variables
67
+ for match in set_pattern.finditer(content):
68
+ var_name = match.group(1).upper()
69
+ var_value = match.group(2).strip()
70
+
71
+ # Remove quotes
72
+ var_value = re.sub(r'^["\']|["\']$', '', var_value)
73
+ # Remove comments
74
+ var_value = re.sub(r'#.*$', '', var_value).strip()
75
+
76
+ variables[var_name] = var_value
77
+
78
+ # Extract flags from CMAKE_C_FLAGS, etc.
79
+ if 'CMAKE_C_FLAGS' in var_name or 'CMAKE_CXX_FLAGS' in var_name:
80
+ flags = var_value.split()
81
+ for flag in flags:
82
+ flag = flag.strip()
83
+ if flag:
84
+ cflags.add(flag)
85
+
86
+ # Extract CMAKE_SYSTEM_PROCESSOR
87
+ if var_name == 'CMAKE_SYSTEM_PROCESSOR':
88
+ variables['CMAKE_SYSTEM_PROCESSOR'] = var_value
89
+
90
+ # Extract toolchain from CMAKE_C_COMPILER
91
+ if var_name == 'CMAKE_C_COMPILER' and not toolchain:
92
+ toolchain_match = re.match(r'^([\w-]+)-gcc', var_value)
93
+ if toolchain_match:
94
+ toolchain = toolchain_match.group(1)
95
+
96
+ # Extract add_definitions
97
+ for match in def_pattern.finditer(content):
98
+ defs_str = match.group(1)
99
+ # Extract -D defines
100
+ define_matches = re.finditer(r'-D(\w+(?:=\w+)?)', defs_str)
101
+ for def_match in define_matches:
102
+ defines.add(def_match.group(1))
103
+
104
+ # Look for find_package(Zephyr) or similar
105
+ if re.search(r'find_package\s*\(\s*Zephyr', content, re.IGNORECASE):
106
+ variables['ZEPHYR'] = 'true'
107
+
108
+ return {
109
+ 'cflags': list(cflags),
110
+ 'defines': list(defines),
111
+ 'variables': variables,
112
+ 'toolchain': toolchain
113
+ }