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,574 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AVR compiler wrapper for building Arduino sketches.
|
|
3
|
+
|
|
4
|
+
This module provides a wrapper around avr-gcc and avr-g++ for compiling
|
|
5
|
+
C and C++ source files to object files with sccache support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Dict, Optional, TYPE_CHECKING
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from .compiler import ICompiler, CompilerError
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ..daemon.compilation_queue import CompilationJobQueue
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class CompileResult:
|
|
22
|
+
"""Result of a compilation operation."""
|
|
23
|
+
success: bool
|
|
24
|
+
object_file: Optional[Path]
|
|
25
|
+
stdout: str
|
|
26
|
+
stderr: str
|
|
27
|
+
returncode: int
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CompilerAVR(ICompiler):
|
|
31
|
+
"""
|
|
32
|
+
Wrapper for AVR-GCC compiler.
|
|
33
|
+
|
|
34
|
+
Compiles C and C++ source files to object files using avr-gcc and avr-g++
|
|
35
|
+
with appropriate flags for Arduino builds.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
avr_gcc: Path,
|
|
41
|
+
avr_gpp: Path,
|
|
42
|
+
mcu: str,
|
|
43
|
+
f_cpu: str,
|
|
44
|
+
includes: List[Path],
|
|
45
|
+
defines: Dict[str, str],
|
|
46
|
+
use_sccache: bool = True,
|
|
47
|
+
compilation_queue: Optional['CompilationJobQueue'] = None
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize compiler.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
avr_gcc: Path to avr-gcc executable
|
|
54
|
+
avr_gpp: Path to avr-g++ executable
|
|
55
|
+
mcu: MCU type (e.g., atmega328p)
|
|
56
|
+
f_cpu: CPU frequency (e.g., 16000000L)
|
|
57
|
+
includes: List of include directories
|
|
58
|
+
defines: Dictionary of preprocessor defines
|
|
59
|
+
use_sccache: Whether to use sccache for caching (default: True)
|
|
60
|
+
compilation_queue: Optional compilation queue for async/parallel compilation
|
|
61
|
+
"""
|
|
62
|
+
self.avr_gcc = Path(avr_gcc)
|
|
63
|
+
self.avr_gpp = Path(avr_gpp)
|
|
64
|
+
self.mcu = mcu
|
|
65
|
+
self.f_cpu = f_cpu
|
|
66
|
+
self.includes = [Path(p) for p in includes]
|
|
67
|
+
self.defines = defines
|
|
68
|
+
self.use_sccache = use_sccache
|
|
69
|
+
self.sccache_path: Optional[Path] = None
|
|
70
|
+
self.compilation_queue = compilation_queue
|
|
71
|
+
self.pending_jobs: List[str] = [] # Track async job IDs
|
|
72
|
+
|
|
73
|
+
# Check if sccache is available
|
|
74
|
+
if self.use_sccache:
|
|
75
|
+
sccache_exe = shutil.which("sccache")
|
|
76
|
+
if sccache_exe:
|
|
77
|
+
self.sccache_path = Path(sccache_exe)
|
|
78
|
+
print(f"[sccache] Enabled for AVR compiler: {self.sccache_path}")
|
|
79
|
+
else:
|
|
80
|
+
print("[sccache] Warning: not found in PATH, proceeding without cache")
|
|
81
|
+
|
|
82
|
+
# Verify tools exist
|
|
83
|
+
if not self.avr_gcc.exists():
|
|
84
|
+
raise CompilerError(f"avr-gcc not found: {self.avr_gcc}")
|
|
85
|
+
if not self.avr_gpp.exists():
|
|
86
|
+
raise CompilerError(f"avr-g++ not found: {self.avr_gpp}")
|
|
87
|
+
|
|
88
|
+
def compile_c(
|
|
89
|
+
self,
|
|
90
|
+
source: Path,
|
|
91
|
+
output: Path,
|
|
92
|
+
extra_flags: Optional[List[str]] = None
|
|
93
|
+
) -> CompileResult:
|
|
94
|
+
"""
|
|
95
|
+
Compile C source file.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
source: Path to .c 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
|
+
cmd = self._build_c_command(source, output, extra_flags or [])
|
|
106
|
+
return self._execute_compiler(cmd, output)
|
|
107
|
+
|
|
108
|
+
def compile_cpp(
|
|
109
|
+
self,
|
|
110
|
+
source: Path,
|
|
111
|
+
output: Path,
|
|
112
|
+
extra_flags: Optional[List[str]] = None
|
|
113
|
+
) -> CompileResult:
|
|
114
|
+
"""
|
|
115
|
+
Compile C++ source file.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
source: Path to .cpp source file
|
|
119
|
+
output: Path to output .o object file
|
|
120
|
+
extra_flags: Additional compiler flags
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
CompileResult with compilation status
|
|
124
|
+
"""
|
|
125
|
+
cmd = self._build_cpp_command(source, output, extra_flags or [])
|
|
126
|
+
return self._execute_compiler(cmd, output)
|
|
127
|
+
|
|
128
|
+
def compile(
|
|
129
|
+
self,
|
|
130
|
+
source: Path,
|
|
131
|
+
output: Path,
|
|
132
|
+
extra_flags: Optional[List[str]] = None
|
|
133
|
+
) -> CompileResult:
|
|
134
|
+
"""
|
|
135
|
+
Compile source file (auto-detects C vs C++).
|
|
136
|
+
|
|
137
|
+
Supports dual-mode operation:
|
|
138
|
+
- If compilation_queue is set: submits job asynchronously and returns deferred result
|
|
139
|
+
- If compilation_queue is None: executes synchronously (legacy mode)
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
source: Path to source file
|
|
143
|
+
output: Path to output .o object file
|
|
144
|
+
extra_flags: Additional compiler flags
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
CompileResult with compilation status
|
|
148
|
+
When async mode: returns success=True with deferred=True, actual result via wait_all_jobs()
|
|
149
|
+
When sync mode: returns actual compilation result immediately
|
|
150
|
+
"""
|
|
151
|
+
source = Path(source)
|
|
152
|
+
|
|
153
|
+
# Async mode: submit to queue and defer result
|
|
154
|
+
if self.compilation_queue is not None:
|
|
155
|
+
# Build the command based on file type
|
|
156
|
+
if source.suffix == '.c':
|
|
157
|
+
cmd = self._build_c_command(source, output, extra_flags or [])
|
|
158
|
+
elif source.suffix in ['.cpp', '.cxx', '.cc']:
|
|
159
|
+
cmd = self._build_cpp_command(source, output, extra_flags or [])
|
|
160
|
+
else:
|
|
161
|
+
raise CompilerError(f"Unknown source file type: {source.suffix}")
|
|
162
|
+
|
|
163
|
+
# Submit to async compilation queue
|
|
164
|
+
job_id = self._submit_async_compilation(source, output, cmd)
|
|
165
|
+
self.pending_jobs.append(job_id)
|
|
166
|
+
|
|
167
|
+
# Return deferred result (actual result via wait_all_jobs())
|
|
168
|
+
return CompileResult(
|
|
169
|
+
success=True,
|
|
170
|
+
object_file=output, # Optimistic - will be validated in wait_all_jobs()
|
|
171
|
+
stdout="",
|
|
172
|
+
stderr="",
|
|
173
|
+
returncode=0
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Sync mode: execute synchronously (legacy behavior)
|
|
177
|
+
if source.suffix == '.c':
|
|
178
|
+
return self.compile_c(source, output, extra_flags)
|
|
179
|
+
elif source.suffix in ['.cpp', '.cxx', '.cc']:
|
|
180
|
+
return self.compile_cpp(source, output, extra_flags)
|
|
181
|
+
else:
|
|
182
|
+
raise CompilerError(f"Unknown source file type: {source.suffix}")
|
|
183
|
+
|
|
184
|
+
def compile_sources(
|
|
185
|
+
self,
|
|
186
|
+
sources: List[Path],
|
|
187
|
+
output_dir: Path,
|
|
188
|
+
extra_flags: Optional[List[str]] = None
|
|
189
|
+
) -> List[Path]:
|
|
190
|
+
"""
|
|
191
|
+
Compile multiple source files.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
sources: List of source files
|
|
195
|
+
output_dir: Output directory for object files
|
|
196
|
+
extra_flags: Additional compiler flags
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of compiled object file paths
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
CompilerError: If any compilation fails
|
|
203
|
+
"""
|
|
204
|
+
output_dir = Path(output_dir)
|
|
205
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
|
|
207
|
+
object_files = []
|
|
208
|
+
|
|
209
|
+
for source in sources:
|
|
210
|
+
source = Path(source)
|
|
211
|
+
# Generate object file name
|
|
212
|
+
obj_name = source.stem + '.o'
|
|
213
|
+
obj_path = output_dir / obj_name
|
|
214
|
+
|
|
215
|
+
# Compile
|
|
216
|
+
result = self.compile(source, obj_path, extra_flags)
|
|
217
|
+
|
|
218
|
+
if not result.success:
|
|
219
|
+
raise CompilerError(
|
|
220
|
+
f"Failed to compile {source}:\n{result.stderr}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
object_files.append(obj_path)
|
|
224
|
+
|
|
225
|
+
return object_files
|
|
226
|
+
|
|
227
|
+
def needs_rebuild(self, source: Path, object_file: Path) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Check if source file needs to be recompiled.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
source: Source file path
|
|
233
|
+
object_file: Object file path
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
True if source is newer than object file
|
|
237
|
+
"""
|
|
238
|
+
if not object_file.exists():
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
source_mtime = source.stat().st_mtime
|
|
242
|
+
obj_mtime = object_file.stat().st_mtime
|
|
243
|
+
|
|
244
|
+
return source_mtime > obj_mtime
|
|
245
|
+
|
|
246
|
+
def _build_c_command(
|
|
247
|
+
self,
|
|
248
|
+
source: Path,
|
|
249
|
+
output: Path,
|
|
250
|
+
extra_flags: List[str]
|
|
251
|
+
) -> List[str]:
|
|
252
|
+
"""Build avr-gcc command for C compilation."""
|
|
253
|
+
cmd = []
|
|
254
|
+
# Prepend sccache if available
|
|
255
|
+
if self.sccache_path:
|
|
256
|
+
cmd.append(str(self.sccache_path))
|
|
257
|
+
cmd.extend([
|
|
258
|
+
str(self.avr_gcc),
|
|
259
|
+
'-c', # Compile only, don't link
|
|
260
|
+
'-g', # Include debug symbols
|
|
261
|
+
'-Os', # Optimize for size
|
|
262
|
+
'-w', # Suppress warnings (matches Arduino)
|
|
263
|
+
'-std=gnu11', # C11 with GNU extensions
|
|
264
|
+
'-ffunction-sections', # Function sections for linker GC
|
|
265
|
+
'-fdata-sections', # Data sections for linker GC
|
|
266
|
+
'-flto', # Link-time optimization
|
|
267
|
+
'-fno-fat-lto-objects', # LTO bytecode only
|
|
268
|
+
f'-mmcu={self.mcu}', # Target MCU
|
|
269
|
+
])
|
|
270
|
+
|
|
271
|
+
# Add defines
|
|
272
|
+
for key, value in self.defines.items():
|
|
273
|
+
if value:
|
|
274
|
+
cmd.append(f'-D{key}={value}')
|
|
275
|
+
else:
|
|
276
|
+
cmd.append(f'-D{key}')
|
|
277
|
+
|
|
278
|
+
# Add F_CPU explicitly
|
|
279
|
+
if 'F_CPU' not in self.defines:
|
|
280
|
+
cmd.append(f'-DF_CPU={self.f_cpu}')
|
|
281
|
+
|
|
282
|
+
# Add include paths
|
|
283
|
+
for include in self.includes:
|
|
284
|
+
cmd.append(f'-I{include}')
|
|
285
|
+
|
|
286
|
+
# Add extra flags
|
|
287
|
+
cmd.extend(extra_flags)
|
|
288
|
+
|
|
289
|
+
# Add source and output
|
|
290
|
+
cmd.extend([str(source), '-o', str(output)])
|
|
291
|
+
|
|
292
|
+
return cmd
|
|
293
|
+
|
|
294
|
+
def _build_cpp_command(
|
|
295
|
+
self,
|
|
296
|
+
source: Path,
|
|
297
|
+
output: Path,
|
|
298
|
+
extra_flags: List[str]
|
|
299
|
+
) -> List[str]:
|
|
300
|
+
"""Build avr-g++ command for C++ compilation."""
|
|
301
|
+
cmd = []
|
|
302
|
+
# Prepend sccache if available
|
|
303
|
+
if self.sccache_path:
|
|
304
|
+
cmd.append(str(self.sccache_path))
|
|
305
|
+
cmd.extend([
|
|
306
|
+
str(self.avr_gpp),
|
|
307
|
+
'-c', # Compile only, don't link
|
|
308
|
+
'-g', # Include debug symbols
|
|
309
|
+
'-Os', # Optimize for size
|
|
310
|
+
'-w', # Suppress warnings (matches Arduino)
|
|
311
|
+
'-std=gnu++11', # C++11 with GNU extensions
|
|
312
|
+
'-fpermissive', # Allow some non-standard code
|
|
313
|
+
'-fno-exceptions', # Disable exceptions (no room on AVR)
|
|
314
|
+
'-ffunction-sections', # Function sections
|
|
315
|
+
'-fdata-sections', # Data sections
|
|
316
|
+
'-fno-threadsafe-statics', # No thread safety needed
|
|
317
|
+
'-flto', # Link-time optimization
|
|
318
|
+
'-fno-fat-lto-objects', # LTO bytecode only
|
|
319
|
+
f'-mmcu={self.mcu}', # Target MCU
|
|
320
|
+
])
|
|
321
|
+
|
|
322
|
+
# Add defines
|
|
323
|
+
for key, value in self.defines.items():
|
|
324
|
+
if value:
|
|
325
|
+
cmd.append(f'-D{key}={value}')
|
|
326
|
+
else:
|
|
327
|
+
cmd.append(f'-D{key}')
|
|
328
|
+
|
|
329
|
+
# Add F_CPU explicitly
|
|
330
|
+
if 'F_CPU' not in self.defines:
|
|
331
|
+
cmd.append(f'-DF_CPU={self.f_cpu}')
|
|
332
|
+
|
|
333
|
+
# Add include paths
|
|
334
|
+
for include in self.includes:
|
|
335
|
+
cmd.append(f'-I{include}')
|
|
336
|
+
|
|
337
|
+
# Add extra flags
|
|
338
|
+
cmd.extend(extra_flags)
|
|
339
|
+
|
|
340
|
+
# Add source and output
|
|
341
|
+
cmd.extend([str(source), '-o', str(output)])
|
|
342
|
+
|
|
343
|
+
return cmd
|
|
344
|
+
|
|
345
|
+
def _execute_compiler(
|
|
346
|
+
self,
|
|
347
|
+
cmd: List[str],
|
|
348
|
+
output: Path
|
|
349
|
+
) -> CompileResult:
|
|
350
|
+
"""Execute compiler command."""
|
|
351
|
+
try:
|
|
352
|
+
result = subprocess.run(
|
|
353
|
+
cmd,
|
|
354
|
+
capture_output=True,
|
|
355
|
+
text=True,
|
|
356
|
+
check=False
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
success = result.returncode == 0
|
|
360
|
+
obj_file = output if success and output.exists() else None
|
|
361
|
+
|
|
362
|
+
return CompileResult(
|
|
363
|
+
success=success,
|
|
364
|
+
object_file=obj_file,
|
|
365
|
+
stdout=result.stdout,
|
|
366
|
+
stderr=result.stderr,
|
|
367
|
+
returncode=result.returncode
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
except KeyboardInterrupt as ke:
|
|
371
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
372
|
+
handle_keyboard_interrupt_properly(ke)
|
|
373
|
+
raise # Never reached, but satisfies type checker
|
|
374
|
+
except Exception as e:
|
|
375
|
+
return CompileResult(
|
|
376
|
+
success=False,
|
|
377
|
+
object_file=None,
|
|
378
|
+
stdout='',
|
|
379
|
+
stderr=str(e),
|
|
380
|
+
returncode=-1
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def _submit_async_compilation(
|
|
384
|
+
self,
|
|
385
|
+
source: Path,
|
|
386
|
+
output: Path,
|
|
387
|
+
cmd: List[str]
|
|
388
|
+
) -> str:
|
|
389
|
+
"""
|
|
390
|
+
Submit compilation job to async queue.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
source: Source file path
|
|
394
|
+
output: Output object file path
|
|
395
|
+
cmd: Full compiler command
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Job ID for tracking
|
|
399
|
+
"""
|
|
400
|
+
import time
|
|
401
|
+
from ..daemon.compilation_queue import CompilationJob
|
|
402
|
+
|
|
403
|
+
job_id = f"compile_{source.stem}_{int(time.time() * 1000000)}"
|
|
404
|
+
|
|
405
|
+
job = CompilationJob(
|
|
406
|
+
job_id=job_id,
|
|
407
|
+
source_path=source,
|
|
408
|
+
output_path=output,
|
|
409
|
+
compiler_cmd=cmd,
|
|
410
|
+
response_file=None # AVR doesn't use response files for includes
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if self.compilation_queue is None:
|
|
414
|
+
raise CompilerError("Compilation queue not initialized")
|
|
415
|
+
self.compilation_queue.submit_job(job)
|
|
416
|
+
return job_id
|
|
417
|
+
|
|
418
|
+
def wait_all_jobs(self) -> List[CompileResult]:
|
|
419
|
+
"""
|
|
420
|
+
Wait for all pending async compilation jobs to complete.
|
|
421
|
+
|
|
422
|
+
This method must be called after using async compilation mode
|
|
423
|
+
to wait for all submitted jobs and collect their results.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of CompileResult for all pending jobs
|
|
427
|
+
|
|
428
|
+
Raises:
|
|
429
|
+
CompilerError: If any compilation fails
|
|
430
|
+
"""
|
|
431
|
+
if not self.compilation_queue:
|
|
432
|
+
return []
|
|
433
|
+
|
|
434
|
+
if not self.pending_jobs:
|
|
435
|
+
return []
|
|
436
|
+
|
|
437
|
+
# Wait for all jobs to complete
|
|
438
|
+
self.compilation_queue.wait_for_completion(self.pending_jobs)
|
|
439
|
+
|
|
440
|
+
# Collect results
|
|
441
|
+
results = []
|
|
442
|
+
failed_jobs = []
|
|
443
|
+
|
|
444
|
+
for job_id in self.pending_jobs:
|
|
445
|
+
job = self.compilation_queue.get_job_status(job_id)
|
|
446
|
+
|
|
447
|
+
if job is None:
|
|
448
|
+
# This shouldn't happen
|
|
449
|
+
failed_jobs.append(f"Job {job_id} not found")
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
result = CompileResult(
|
|
453
|
+
success=(job.state.value == "completed"),
|
|
454
|
+
object_file=job.output_path if job.state.value == "completed" else None,
|
|
455
|
+
stdout=job.stdout,
|
|
456
|
+
stderr=job.stderr,
|
|
457
|
+
returncode=job.result_code or -1
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
results.append(result)
|
|
461
|
+
|
|
462
|
+
if not result.success:
|
|
463
|
+
failed_jobs.append(f"{job.source_path.name}: {job.stderr[:200]}")
|
|
464
|
+
|
|
465
|
+
# Clear pending jobs
|
|
466
|
+
self.pending_jobs.clear()
|
|
467
|
+
|
|
468
|
+
# Raise error if any jobs failed
|
|
469
|
+
if failed_jobs:
|
|
470
|
+
error_msg = f"Compilation failed for {len(failed_jobs)} file(s):\n"
|
|
471
|
+
error_msg += "\n".join(f" - {err}" for err in failed_jobs[:5])
|
|
472
|
+
if len(failed_jobs) > 5:
|
|
473
|
+
error_msg += f"\n ... and {len(failed_jobs) - 5} more"
|
|
474
|
+
raise CompilerError(error_msg)
|
|
475
|
+
|
|
476
|
+
return results
|
|
477
|
+
|
|
478
|
+
def get_statistics(self) -> Dict[str, int]:
|
|
479
|
+
"""
|
|
480
|
+
Get compilation statistics from the queue.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Dictionary with compilation statistics
|
|
484
|
+
"""
|
|
485
|
+
if not self.compilation_queue:
|
|
486
|
+
return {
|
|
487
|
+
"total_jobs": 0,
|
|
488
|
+
"pending": 0,
|
|
489
|
+
"running": 0,
|
|
490
|
+
"completed": 0,
|
|
491
|
+
"failed": 0
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return self.compilation_queue.get_statistics()
|
|
495
|
+
|
|
496
|
+
# BaseCompiler interface implementation
|
|
497
|
+
def compile_source(
|
|
498
|
+
self,
|
|
499
|
+
source_path: Path,
|
|
500
|
+
output_path: Optional[Path] = None
|
|
501
|
+
) -> Path:
|
|
502
|
+
"""Compile a single source file to object file.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
source_path: Path to .c or .cpp source file
|
|
506
|
+
output_path: Optional path for output .o file
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Path to generated .o file
|
|
510
|
+
|
|
511
|
+
Raises:
|
|
512
|
+
CompilerError: If compilation fails
|
|
513
|
+
"""
|
|
514
|
+
source_path = Path(source_path)
|
|
515
|
+
|
|
516
|
+
# Generate output path if not provided
|
|
517
|
+
if output_path is None:
|
|
518
|
+
output_path = source_path.parent / f"{source_path.stem}.o"
|
|
519
|
+
|
|
520
|
+
# Compile the source
|
|
521
|
+
result = self.compile(source_path, output_path)
|
|
522
|
+
|
|
523
|
+
if not result.success:
|
|
524
|
+
raise CompilerError(
|
|
525
|
+
f"Failed to compile {source_path}:\n{result.stderr}"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
return output_path
|
|
529
|
+
|
|
530
|
+
def get_include_paths(self) -> List[Path]:
|
|
531
|
+
"""Get all include paths needed for compilation.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
List of include directory paths
|
|
535
|
+
"""
|
|
536
|
+
return self.includes
|
|
537
|
+
|
|
538
|
+
def get_compile_flags(self) -> Dict[str, List[str]]:
|
|
539
|
+
"""Get compilation flags.
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
Dictionary with 'cflags', 'cxxflags', and 'common' keys
|
|
543
|
+
"""
|
|
544
|
+
# Common flags for both C and C++
|
|
545
|
+
common = [
|
|
546
|
+
'-c',
|
|
547
|
+
'-g',
|
|
548
|
+
'-Os',
|
|
549
|
+
'-w',
|
|
550
|
+
'-ffunction-sections',
|
|
551
|
+
'-fdata-sections',
|
|
552
|
+
'-flto',
|
|
553
|
+
'-fno-fat-lto-objects',
|
|
554
|
+
f'-mmcu={self.mcu}',
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
# C-specific flags
|
|
558
|
+
cflags = [
|
|
559
|
+
'-std=gnu11',
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
# C++-specific flags
|
|
563
|
+
cxxflags = [
|
|
564
|
+
'-std=gnu++11',
|
|
565
|
+
'-fpermissive',
|
|
566
|
+
'-fno-exceptions',
|
|
567
|
+
'-fno-threadsafe-statics',
|
|
568
|
+
]
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
'common': common,
|
|
572
|
+
'cflags': cflags,
|
|
573
|
+
'cxxflags': cxxflags,
|
|
574
|
+
}
|