fbuild 1.1.0__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.
Potentially problematic release.
This version of fbuild might be problematic. Click here for more details.
- fbuild/__init__.py +0 -0
- fbuild/assets/example.txt +1 -0
- fbuild/build/__init__.py +117 -0
- fbuild/build/archive_creator.py +186 -0
- fbuild/build/binary_generator.py +444 -0
- fbuild/build/build_component_factory.py +131 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +98 -0
- fbuild/build/compilation_executor.py +422 -0
- fbuild/build/compiler.py +165 -0
- fbuild/build/compiler_avr.py +574 -0
- fbuild/build/configurable_compiler.py +612 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +186 -0
- fbuild/build/library_dependency_processor.py +185 -0
- fbuild/build/linker.py +708 -0
- fbuild/build/orchestrator.py +67 -0
- fbuild/build/orchestrator_avr.py +656 -0
- fbuild/build/orchestrator_esp32.py +797 -0
- fbuild/build/orchestrator_teensy.py +543 -0
- fbuild/build/source_compilation_orchestrator.py +220 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +566 -0
- fbuild/cli_utils.py +312 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +457 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +209 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +34 -0
- fbuild/daemon/client.py +929 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/daemon.py +474 -0
- fbuild/daemon/daemon_context.py +196 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/lock_manager.py +270 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +301 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +12 -0
- fbuild/daemon/processors/build_processor.py +157 -0
- fbuild/daemon/processors/deploy_processor.py +327 -0
- fbuild/daemon/processors/monitor_processor.py +146 -0
- fbuild/daemon/request_processor.py +401 -0
- fbuild/daemon/status_manager.py +216 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +17 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +314 -0
- fbuild/deploy/monitor.py +495 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/packages/__init__.py +53 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +249 -0
- fbuild/packages/downloader.py +366 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_teensy.py +346 -0
- fbuild/packages/github_utils.py +96 -0
- fbuild/packages/header_trampoline_cache.py +394 -0
- fbuild/packages/library_compiler.py +203 -0
- fbuild/packages/library_manager.py +549 -0
- fbuild/packages/library_manager_esp32.py +413 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +325 -0
- fbuild/packages/sdk_utils.py +231 -0
- fbuild/packages/toolchain.py +436 -0
- fbuild/packages/toolchain_binaries.py +196 -0
- fbuild/packages/toolchain_esp32.py +484 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_teensy.py +404 -0
- fbuild/platform_configs/esp32.json +150 -0
- fbuild/platform_configs/esp32c2.json +144 -0
- fbuild/platform_configs/esp32c3.json +143 -0
- fbuild/platform_configs/esp32c5.json +151 -0
- fbuild/platform_configs/esp32c6.json +151 -0
- fbuild/platform_configs/esp32p4.json +149 -0
- fbuild/platform_configs/esp32s3.json +151 -0
- fbuild/platform_configs/imxrt1062.json +56 -0
- fbuild-1.1.0.dist-info/METADATA +447 -0
- fbuild-1.1.0.dist-info/RECORD +93 -0
- fbuild-1.1.0.dist-info/WHEEL +5 -0
- fbuild-1.1.0.dist-info/entry_points.txt +5 -0
- fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
- fbuild-1.1.0.dist-info/top_level.txt +2 -0
- fbuild_lint/__init__.py +0 -0
- fbuild_lint/ruff_plugins/__init__.py +0 -0
- fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""Compilation Executor.
|
|
2
|
+
|
|
3
|
+
This module handles executing compilation commands via subprocess with support
|
|
4
|
+
for response files and proper error handling.
|
|
5
|
+
|
|
6
|
+
Design:
|
|
7
|
+
- Wraps subprocess.run for compilation commands
|
|
8
|
+
- Generates response files for include paths (avoids command line length limits)
|
|
9
|
+
- Provides clear error messages for compilation failures
|
|
10
|
+
- Supports both C and C++ compilation
|
|
11
|
+
- Integrates sccache for compilation caching
|
|
12
|
+
- Uses header trampoline cache to avoid Windows command-line length limits
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import _thread
|
|
16
|
+
import subprocess
|
|
17
|
+
import shutil
|
|
18
|
+
import platform
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from ..packages.header_trampoline_cache import HeaderTrampolineCache
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from ..daemon.compilation_queue import CompilationJobQueue
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CompilationError(Exception):
|
|
30
|
+
"""Raised when compilation operations fail."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CompilationExecutor:
|
|
35
|
+
"""Executes compilation commands with response file support.
|
|
36
|
+
|
|
37
|
+
This class handles:
|
|
38
|
+
- Running compiler subprocess commands
|
|
39
|
+
- Generating response files for include paths
|
|
40
|
+
- Handling compilation errors with clear messages
|
|
41
|
+
- Supporting progress display
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, build_dir: Path, show_progress: bool = True, use_sccache: bool = True, use_trampolines: bool = True):
|
|
45
|
+
"""Initialize compilation executor.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
build_dir: Build directory for response files
|
|
49
|
+
show_progress: Whether to show compilation progress
|
|
50
|
+
use_sccache: Whether to use sccache for caching (default: True)
|
|
51
|
+
use_trampolines: Whether to use header trampolines on Windows (default: True)
|
|
52
|
+
"""
|
|
53
|
+
self.build_dir = build_dir
|
|
54
|
+
self.show_progress = show_progress
|
|
55
|
+
|
|
56
|
+
# Disable sccache on Windows due to file locking issues
|
|
57
|
+
# See: https://github.com/anthropics/claude-code/issues/...
|
|
58
|
+
if platform.system() == 'Windows':
|
|
59
|
+
if use_sccache and show_progress:
|
|
60
|
+
print("[sccache] Disabled on Windows due to file locking issues")
|
|
61
|
+
self.use_sccache = False
|
|
62
|
+
else:
|
|
63
|
+
self.use_sccache = use_sccache
|
|
64
|
+
|
|
65
|
+
self.use_trampolines = use_trampolines
|
|
66
|
+
self.sccache_path: Optional[Path] = None
|
|
67
|
+
self.trampoline_cache: Optional[HeaderTrampolineCache] = None
|
|
68
|
+
|
|
69
|
+
# Check if sccache is available
|
|
70
|
+
if self.use_sccache:
|
|
71
|
+
sccache_exe = shutil.which("sccache")
|
|
72
|
+
if sccache_exe:
|
|
73
|
+
self.sccache_path = Path(sccache_exe)
|
|
74
|
+
# Always print sccache status for visibility
|
|
75
|
+
print(f"[sccache] Enabled: {self.sccache_path}")
|
|
76
|
+
else:
|
|
77
|
+
# Try common Windows locations (Git Bash uses /c/ paths)
|
|
78
|
+
common_locations = [
|
|
79
|
+
Path("/c/tools/python13/Scripts/sccache.exe"),
|
|
80
|
+
Path("C:/tools/python13/Scripts/sccache.exe"),
|
|
81
|
+
Path.home() / ".cargo" / "bin" / "sccache.exe",
|
|
82
|
+
]
|
|
83
|
+
for loc in common_locations:
|
|
84
|
+
if loc.exists():
|
|
85
|
+
self.sccache_path = loc
|
|
86
|
+
print(f"[sccache] Enabled: {self.sccache_path}")
|
|
87
|
+
break
|
|
88
|
+
else:
|
|
89
|
+
# Always warn if sccache not found
|
|
90
|
+
print("[sccache] Warning: not found in PATH, proceeding without cache")
|
|
91
|
+
|
|
92
|
+
# Initialize trampoline cache if enabled and on Windows
|
|
93
|
+
if self.use_trampolines and platform.system() == 'Windows':
|
|
94
|
+
self.trampoline_cache = HeaderTrampolineCache(show_progress=show_progress)
|
|
95
|
+
|
|
96
|
+
def compile_source(
|
|
97
|
+
self,
|
|
98
|
+
compiler_path: Path,
|
|
99
|
+
source_path: Path,
|
|
100
|
+
output_path: Path,
|
|
101
|
+
compile_flags: List[str],
|
|
102
|
+
include_paths: List[Path]
|
|
103
|
+
) -> Path:
|
|
104
|
+
"""Compile a single source file.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
compiler_path: Path to compiler executable (gcc/g++)
|
|
108
|
+
source_path: Path to source file
|
|
109
|
+
output_path: Path for output object file
|
|
110
|
+
compile_flags: Compilation flags
|
|
111
|
+
include_paths: Include directory paths
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Path to generated object file
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
CompilationError: If compilation fails
|
|
118
|
+
"""
|
|
119
|
+
if not compiler_path.exists():
|
|
120
|
+
raise CompilationError(
|
|
121
|
+
f"Compiler not found: {compiler_path}. Ensure toolchain is installed."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if not source_path.exists():
|
|
125
|
+
raise CompilationError(f"Source file not found: {source_path}")
|
|
126
|
+
|
|
127
|
+
# Ensure output directory exists
|
|
128
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
|
|
130
|
+
# Apply header trampoline cache on Windows when enabled
|
|
131
|
+
# This resolves Windows CreateProcess 32K limit issues with sccache
|
|
132
|
+
effective_include_paths = include_paths
|
|
133
|
+
if self.trampoline_cache is not None and platform.system() == 'Windows':
|
|
134
|
+
# Use trampolines to shorten include paths
|
|
135
|
+
# Exclude ESP-IDF headers that use relative paths that break trampolines
|
|
136
|
+
try:
|
|
137
|
+
exclude_patterns = [
|
|
138
|
+
'newlib/platform_include', # Uses #include_next which breaks trampolines
|
|
139
|
+
'newlib\\platform_include', # Windows path variant
|
|
140
|
+
'/bt/', # Bluetooth SDK uses relative paths between bt/include and bt/controller
|
|
141
|
+
'\\bt\\' # Windows path variant
|
|
142
|
+
]
|
|
143
|
+
effective_include_paths = self.trampoline_cache.generate_trampolines(
|
|
144
|
+
include_paths,
|
|
145
|
+
exclude_patterns=exclude_patterns
|
|
146
|
+
)
|
|
147
|
+
except KeyboardInterrupt:
|
|
148
|
+
_thread.interrupt_main()
|
|
149
|
+
raise
|
|
150
|
+
except Exception as e:
|
|
151
|
+
if self.show_progress:
|
|
152
|
+
print(f"[trampolines] Warning: Failed to generate trampolines, using original paths: {e}")
|
|
153
|
+
effective_include_paths = include_paths
|
|
154
|
+
|
|
155
|
+
# Convert include paths to flags - ensure no quotes for sccache compatibility
|
|
156
|
+
# GCC response files with quotes cause sccache to treat @file literally
|
|
157
|
+
include_flags = [f"-I{str(inc).replace(chr(92), '/')}" for inc in effective_include_paths]
|
|
158
|
+
|
|
159
|
+
# Build compiler command
|
|
160
|
+
cmd = self._build_compile_command(
|
|
161
|
+
compiler_path, source_path, output_path, compile_flags, include_flags
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Execute compilation
|
|
165
|
+
if self.show_progress:
|
|
166
|
+
print(f"Compiling {source_path.name}...")
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
cmd,
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
timeout=60
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if result.returncode != 0:
|
|
177
|
+
error_msg = f"Compilation failed for {source_path.name}\n"
|
|
178
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
179
|
+
error_msg += f"stdout: {result.stdout}"
|
|
180
|
+
raise CompilationError(error_msg)
|
|
181
|
+
|
|
182
|
+
if self.show_progress and result.stderr:
|
|
183
|
+
print(result.stderr)
|
|
184
|
+
|
|
185
|
+
return output_path
|
|
186
|
+
|
|
187
|
+
except subprocess.TimeoutExpired as e:
|
|
188
|
+
raise CompilationError(f"Compilation timeout for {source_path.name}") from e
|
|
189
|
+
except KeyboardInterrupt as ke:
|
|
190
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
191
|
+
handle_keyboard_interrupt_properly(ke)
|
|
192
|
+
raise # Never reached, but satisfies type checker
|
|
193
|
+
except Exception as e:
|
|
194
|
+
if isinstance(e, CompilationError):
|
|
195
|
+
raise
|
|
196
|
+
raise CompilationError(f"Failed to compile {source_path.name}: {e}") from e
|
|
197
|
+
|
|
198
|
+
def _build_compile_command(
|
|
199
|
+
self,
|
|
200
|
+
compiler_path: Path,
|
|
201
|
+
source_path: Path,
|
|
202
|
+
output_path: Path,
|
|
203
|
+
compile_flags: List[str],
|
|
204
|
+
include_paths: List[str]
|
|
205
|
+
) -> List[str]:
|
|
206
|
+
"""Build compilation command with optional sccache wrapper.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
compiler_path: Path to compiler executable
|
|
210
|
+
source_path: Path to source file
|
|
211
|
+
output_path: Path for output object file
|
|
212
|
+
compile_flags: Compilation flags
|
|
213
|
+
include_paths: Include paths (or include flags if already converted)
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
List of command arguments
|
|
217
|
+
"""
|
|
218
|
+
# Include paths are already converted to flags (List[str])
|
|
219
|
+
include_flags = include_paths
|
|
220
|
+
|
|
221
|
+
# Write response file for includes
|
|
222
|
+
response_file = self._write_response_file(include_flags)
|
|
223
|
+
|
|
224
|
+
# Build compiler command with optional sccache wrapper
|
|
225
|
+
use_sccache = self.sccache_path is not None
|
|
226
|
+
|
|
227
|
+
cmd = []
|
|
228
|
+
if use_sccache:
|
|
229
|
+
cmd.append(str(self.sccache_path))
|
|
230
|
+
# Use absolute resolved path for sccache
|
|
231
|
+
# On Windows, sccache needs consistent path format (all backslashes)
|
|
232
|
+
resolved_compiler = compiler_path.resolve()
|
|
233
|
+
compiler_str = str(resolved_compiler)
|
|
234
|
+
# Normalize to Windows backslashes on Windows
|
|
235
|
+
if platform.system() == 'Windows':
|
|
236
|
+
compiler_str = compiler_str.replace('/', '\\')
|
|
237
|
+
cmd.append(compiler_str)
|
|
238
|
+
else:
|
|
239
|
+
cmd.append(str(compiler_path))
|
|
240
|
+
cmd.extend(compile_flags)
|
|
241
|
+
cmd.append(f"@{response_file}")
|
|
242
|
+
cmd.extend(['-c', str(source_path)])
|
|
243
|
+
cmd.extend(['-o', str(output_path)])
|
|
244
|
+
|
|
245
|
+
return cmd
|
|
246
|
+
|
|
247
|
+
def _write_response_file(self, include_flags: List[str]) -> Path:
|
|
248
|
+
"""Write include paths to response file.
|
|
249
|
+
|
|
250
|
+
Response files avoid command line length limits when there are
|
|
251
|
+
many include paths.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
include_flags: List of -I include flags
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Path to generated response file
|
|
258
|
+
"""
|
|
259
|
+
response_file = self.build_dir / "includes.rsp"
|
|
260
|
+
response_file.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
|
|
262
|
+
with open(response_file, 'w') as f:
|
|
263
|
+
f.write('\n'.join(include_flags))
|
|
264
|
+
|
|
265
|
+
return response_file
|
|
266
|
+
|
|
267
|
+
def preprocess_ino(
|
|
268
|
+
self,
|
|
269
|
+
ino_path: Path,
|
|
270
|
+
output_dir: Path
|
|
271
|
+
) -> Path:
|
|
272
|
+
"""Preprocess .ino file to .cpp file.
|
|
273
|
+
|
|
274
|
+
Simple preprocessing: adds Arduino.h include and renames to .cpp.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
ino_path: Path to .ino file
|
|
278
|
+
output_dir: Directory for generated .cpp file
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Path to generated .cpp file
|
|
282
|
+
|
|
283
|
+
Raises:
|
|
284
|
+
CompilationError: If preprocessing fails
|
|
285
|
+
"""
|
|
286
|
+
if not ino_path.exists():
|
|
287
|
+
raise CompilationError(f"Sketch file not found: {ino_path}")
|
|
288
|
+
|
|
289
|
+
# Read .ino content
|
|
290
|
+
try:
|
|
291
|
+
with open(ino_path, 'r', encoding='utf-8') as f:
|
|
292
|
+
ino_content = f.read()
|
|
293
|
+
except KeyboardInterrupt as ke:
|
|
294
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
295
|
+
handle_keyboard_interrupt_properly(ke)
|
|
296
|
+
raise # Never reached, but satisfies type checker
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise CompilationError(f"Failed to read {ino_path}: {e}") from e
|
|
299
|
+
|
|
300
|
+
# Generate .cpp file path
|
|
301
|
+
cpp_path = output_dir / "sketch" / f"{ino_path.stem}.ino.cpp"
|
|
302
|
+
cpp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
|
|
304
|
+
# Simple preprocessing: add Arduino.h and content
|
|
305
|
+
cpp_content = '#include <Arduino.h>\n\n' + ino_content
|
|
306
|
+
|
|
307
|
+
# Write .cpp file
|
|
308
|
+
try:
|
|
309
|
+
with open(cpp_path, 'w', encoding='utf-8') as f:
|
|
310
|
+
f.write(cpp_content)
|
|
311
|
+
except KeyboardInterrupt as ke:
|
|
312
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
313
|
+
handle_keyboard_interrupt_properly(ke)
|
|
314
|
+
raise # Never reached, but satisfies type checker
|
|
315
|
+
except Exception as e:
|
|
316
|
+
raise CompilationError(f"Failed to write {cpp_path}: {e}") from e
|
|
317
|
+
|
|
318
|
+
if self.show_progress:
|
|
319
|
+
print(f"Preprocessed {ino_path.name} -> {cpp_path.name}")
|
|
320
|
+
|
|
321
|
+
return cpp_path
|
|
322
|
+
|
|
323
|
+
def compile_source_async(
|
|
324
|
+
self,
|
|
325
|
+
compiler_path: Path,
|
|
326
|
+
source_path: Path,
|
|
327
|
+
output_path: Path,
|
|
328
|
+
compile_flags: List[str],
|
|
329
|
+
include_paths: List[Path],
|
|
330
|
+
job_queue: 'CompilationJobQueue'
|
|
331
|
+
) -> str:
|
|
332
|
+
"""Compile a single source file asynchronously via daemon queue.
|
|
333
|
+
|
|
334
|
+
This method submits a compilation job to the daemon's CompilationJobQueue
|
|
335
|
+
for parallel execution instead of blocking on subprocess.run().
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
compiler_path: Path to compiler executable (gcc/g++)
|
|
339
|
+
source_path: Path to source file
|
|
340
|
+
output_path: Path for output object file
|
|
341
|
+
compile_flags: Compilation flags
|
|
342
|
+
include_paths: Include directory paths
|
|
343
|
+
job_queue: CompilationJobQueue from daemon
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Job ID string for tracking the compilation job
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
CompilationError: If job submission fails
|
|
350
|
+
"""
|
|
351
|
+
from ..daemon.compilation_queue import CompilationJob
|
|
352
|
+
|
|
353
|
+
if not compiler_path.exists():
|
|
354
|
+
raise CompilationError(
|
|
355
|
+
f"Compiler not found: {compiler_path}. Ensure toolchain is installed."
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if not source_path.exists():
|
|
359
|
+
raise CompilationError(f"Source file not found: {source_path}")
|
|
360
|
+
|
|
361
|
+
# Ensure output directory exists
|
|
362
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
363
|
+
|
|
364
|
+
# Apply header trampoline cache on Windows when enabled
|
|
365
|
+
effective_include_paths = include_paths
|
|
366
|
+
if self.trampoline_cache is not None and platform.system() == 'Windows':
|
|
367
|
+
try:
|
|
368
|
+
exclude_patterns = [
|
|
369
|
+
'newlib/platform_include',
|
|
370
|
+
'newlib\\platform_include',
|
|
371
|
+
'/bt/',
|
|
372
|
+
'\\bt\\'
|
|
373
|
+
]
|
|
374
|
+
effective_include_paths = self.trampoline_cache.generate_trampolines(
|
|
375
|
+
include_paths,
|
|
376
|
+
exclude_patterns=exclude_patterns
|
|
377
|
+
)
|
|
378
|
+
except KeyboardInterrupt:
|
|
379
|
+
_thread.interrupt_main()
|
|
380
|
+
raise
|
|
381
|
+
except Exception as e:
|
|
382
|
+
if self.show_progress:
|
|
383
|
+
print(f"[trampolines] Warning: Failed to generate trampolines, using original paths: {e}")
|
|
384
|
+
effective_include_paths = include_paths
|
|
385
|
+
|
|
386
|
+
# Convert include paths to flags
|
|
387
|
+
include_flags = [f"-I{str(inc).replace(chr(92), '/')}" for inc in effective_include_paths]
|
|
388
|
+
response_file = self._write_response_file(include_flags)
|
|
389
|
+
|
|
390
|
+
# Build compiler command with optional sccache wrapper
|
|
391
|
+
use_sccache = self.sccache_path is not None
|
|
392
|
+
|
|
393
|
+
cmd = []
|
|
394
|
+
if use_sccache:
|
|
395
|
+
cmd.append(str(self.sccache_path))
|
|
396
|
+
resolved_compiler = compiler_path.resolve()
|
|
397
|
+
compiler_str = str(resolved_compiler)
|
|
398
|
+
if platform.system() == 'Windows':
|
|
399
|
+
compiler_str = compiler_str.replace('/', '\\')
|
|
400
|
+
cmd.append(compiler_str)
|
|
401
|
+
else:
|
|
402
|
+
cmd.append(str(compiler_path))
|
|
403
|
+
cmd.extend(compile_flags)
|
|
404
|
+
cmd.append(f"@{response_file}")
|
|
405
|
+
cmd.extend(['-c', str(source_path)])
|
|
406
|
+
cmd.extend(['-o', str(output_path)])
|
|
407
|
+
|
|
408
|
+
# Create and submit compilation job
|
|
409
|
+
job_id = f"compile_{source_path.stem}_{int(time.time()*1000000)}"
|
|
410
|
+
|
|
411
|
+
job = CompilationJob(
|
|
412
|
+
job_id=job_id,
|
|
413
|
+
source_path=source_path,
|
|
414
|
+
output_path=output_path,
|
|
415
|
+
compiler_cmd=cmd,
|
|
416
|
+
response_file=response_file
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Submit to queue
|
|
420
|
+
job_queue.submit_job(job)
|
|
421
|
+
|
|
422
|
+
return job_id
|
fbuild/build/compiler.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Abstract base classes for compilation components.
|
|
2
|
+
|
|
3
|
+
This module defines the interface for platform-specific compilers and linkers
|
|
4
|
+
to ensure consistent behavior across different platforms (AVR, ESP32, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Dict, Optional
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class CompileResult:
|
|
15
|
+
"""Result of a compilation operation."""
|
|
16
|
+
success: bool
|
|
17
|
+
object_file: Optional[Path]
|
|
18
|
+
stdout: str
|
|
19
|
+
stderr: str
|
|
20
|
+
returncode: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CompilerError(Exception):
|
|
24
|
+
"""Base exception for compilation errors."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ICompiler(ABC):
|
|
29
|
+
"""Interface for source code compilers.
|
|
30
|
+
|
|
31
|
+
This interface defines the common contract for all compiler implementations:
|
|
32
|
+
- AVR Compiler (avr-gcc/avr-g++)
|
|
33
|
+
- ESP32 Compiler (riscv32-esp-elf-gcc, xtensa-esp32-elf-gcc)
|
|
34
|
+
- Configurable Compiler (platform-agnostic)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def compile_source(
|
|
39
|
+
self,
|
|
40
|
+
source_path: Path,
|
|
41
|
+
output_path: Optional[Path] = None
|
|
42
|
+
) -> Path:
|
|
43
|
+
"""Compile a single source file to object file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
source_path: Path to .c or .cpp source file
|
|
47
|
+
output_path: Optional path for output .o file
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Path to generated .o file
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
CompilerError: If compilation fails
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def get_include_paths(self) -> List[Path]:
|
|
59
|
+
"""Get all include paths needed for compilation.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of include directory paths
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def get_compile_flags(self) -> Dict[str, List[str]]:
|
|
68
|
+
"""Get compilation flags.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dictionary with 'cflags', 'cxxflags', and 'common' keys
|
|
72
|
+
"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def needs_rebuild(self, source: Path, object_file: Path) -> bool:
|
|
77
|
+
"""Check if source file needs to be recompiled.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
source: Source file path
|
|
81
|
+
object_file: Object file path
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if source is newer than object file or object doesn't exist
|
|
85
|
+
"""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def compile(
|
|
90
|
+
self,
|
|
91
|
+
source: Path,
|
|
92
|
+
output: Path,
|
|
93
|
+
extra_flags: Optional[List[str]] = None
|
|
94
|
+
) -> CompileResult:
|
|
95
|
+
"""Compile source file (auto-detects C vs C++).
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
source: Path to source file
|
|
99
|
+
output: Path to output .o object file
|
|
100
|
+
extra_flags: Additional compiler flags
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
CompileResult with compilation status
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
CompilerError: If compilation fails
|
|
107
|
+
"""
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ILinker(ABC):
|
|
112
|
+
"""Interface for linkers.
|
|
113
|
+
|
|
114
|
+
This interface defines the common contract for all linker implementations:
|
|
115
|
+
- AVR Linker (avr-gcc linker)
|
|
116
|
+
- ESP32 Linker (riscv32/xtensa linker)
|
|
117
|
+
- Configurable Linker (platform-agnostic)
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def link(
|
|
122
|
+
self,
|
|
123
|
+
sketch_objects: List[Path],
|
|
124
|
+
core_archive: Path,
|
|
125
|
+
output_elf: Optional[Path] = None,
|
|
126
|
+
library_archives: Optional[List[Path]] = None
|
|
127
|
+
) -> Path:
|
|
128
|
+
"""Link object files into firmware ELF.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
sketch_objects: List of sketch object files
|
|
132
|
+
core_archive: Core archive file (core.a)
|
|
133
|
+
output_elf: Optional path for output .elf file
|
|
134
|
+
library_archives: Optional list of library archives
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Path to generated .elf file
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
LinkerError: If linking fails
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def generate_bin(self, elf_path: Path) -> Path:
|
|
146
|
+
"""Generate binary from ELF file.
|
|
147
|
+
|
|
148
|
+
For AVR: Generates .hex (Intel HEX format)
|
|
149
|
+
For ESP32: Generates .bin (raw binary)
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
elf_path: Path to firmware.elf file
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Path to generated binary file
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
LinkerError: If binary generation fails
|
|
159
|
+
"""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class LinkerError(Exception):
|
|
164
|
+
"""Base exception for linking errors."""
|
|
165
|
+
pass
|