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
fbuild/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Example assets that will be deployed with python code.
|
fbuild/build/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build system components for Fbuild.
|
|
3
|
+
|
|
4
|
+
This module provides the build system implementation including:
|
|
5
|
+
- Source file discovery and preprocessing
|
|
6
|
+
- Compilation (avr-gcc/avr-g++)
|
|
7
|
+
- Linking (avr-gcc linker, avr-objcopy)
|
|
8
|
+
- Build orchestration
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .source_scanner import SourceScanner, SourceCollection
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'SourceScanner',
|
|
15
|
+
'SourceCollection',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Import base classes
|
|
19
|
+
try:
|
|
20
|
+
from .orchestrator import ( # noqa: F401
|
|
21
|
+
IBuildOrchestrator,
|
|
22
|
+
BuildResult,
|
|
23
|
+
BuildOrchestratorError
|
|
24
|
+
)
|
|
25
|
+
__all__.extend(['IBuildOrchestrator', 'BuildResult', 'BuildOrchestratorError'])
|
|
26
|
+
except ImportError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from .compiler import ICompiler, CompilerError, ILinker, LinkerError # noqa: F401
|
|
31
|
+
__all__.extend(['ICompiler', 'CompilerError', 'ILinker', 'LinkerError'])
|
|
32
|
+
except ImportError:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# Import platform-specific implementations
|
|
36
|
+
try:
|
|
37
|
+
from .compiler_avr import CompilerAVR # noqa: F401
|
|
38
|
+
__all__.append('CompilerAVR')
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from .linker import LinkerAVR # noqa: F401
|
|
44
|
+
__all__.append('LinkerAVR')
|
|
45
|
+
except ImportError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from .orchestrator_avr import BuildOrchestratorAVR # noqa: F401
|
|
50
|
+
__all__.append('BuildOrchestratorAVR')
|
|
51
|
+
except ImportError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
from .orchestrator_esp32 import OrchestratorESP32 # noqa: F401
|
|
56
|
+
__all__.append('OrchestratorESP32')
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
from .binary_generator import BinaryGenerator # noqa: F401
|
|
62
|
+
__all__.append('BinaryGenerator')
|
|
63
|
+
except ImportError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
from .build_utils import SizeInfoPrinter # noqa: F401
|
|
68
|
+
__all__.append('SizeInfoPrinter')
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
from .flag_builder import FlagBuilder # noqa: F401
|
|
74
|
+
__all__.append('FlagBuilder')
|
|
75
|
+
except ImportError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
from .compilation_executor import CompilationExecutor # noqa: F401
|
|
80
|
+
__all__.append('CompilationExecutor')
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
from .archive_creator import ArchiveCreator # noqa: F401
|
|
86
|
+
__all__.append('ArchiveCreator')
|
|
87
|
+
except ImportError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
from .library_dependency_processor import ( # noqa: F401
|
|
92
|
+
LibraryDependencyProcessor,
|
|
93
|
+
LibraryProcessingResult
|
|
94
|
+
)
|
|
95
|
+
__all__.extend(['LibraryDependencyProcessor', 'LibraryProcessingResult'])
|
|
96
|
+
except ImportError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
from .source_compilation_orchestrator import ( # noqa: F401
|
|
101
|
+
SourceCompilationOrchestrator,
|
|
102
|
+
SourceCompilationOrchestratorError,
|
|
103
|
+
MultiGroupCompilationResult
|
|
104
|
+
)
|
|
105
|
+
__all__.extend([
|
|
106
|
+
'SourceCompilationOrchestrator',
|
|
107
|
+
'SourceCompilationOrchestratorError',
|
|
108
|
+
'MultiGroupCompilationResult'
|
|
109
|
+
])
|
|
110
|
+
except ImportError:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
from .build_component_factory import BuildComponentFactory # noqa: F401
|
|
115
|
+
__all__.append('BuildComponentFactory')
|
|
116
|
+
except ImportError:
|
|
117
|
+
pass
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Archive Creator.
|
|
2
|
+
|
|
3
|
+
This module handles creating static library archives (.a files) from compiled
|
|
4
|
+
object files using the archiver tool (ar).
|
|
5
|
+
|
|
6
|
+
Design:
|
|
7
|
+
- Wraps ar command execution
|
|
8
|
+
- Creates .a archives from object files
|
|
9
|
+
- Provides clear error messages
|
|
10
|
+
- Shows archive size information
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import gc
|
|
14
|
+
import platform
|
|
15
|
+
import subprocess
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ArchiveError(Exception):
|
|
22
|
+
"""Raised when archive creation operations fail."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ArchiveCreator:
|
|
27
|
+
"""Creates static library archives from object files.
|
|
28
|
+
|
|
29
|
+
This class handles:
|
|
30
|
+
- Running archiver (ar) commands
|
|
31
|
+
- Creating .a archives from object files
|
|
32
|
+
- Validating archive creation
|
|
33
|
+
- Showing size information
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, show_progress: bool = True):
|
|
37
|
+
"""Initialize archive creator.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
show_progress: Whether to show archive creation progress
|
|
41
|
+
"""
|
|
42
|
+
self.show_progress = show_progress
|
|
43
|
+
|
|
44
|
+
def create_archive(
|
|
45
|
+
self,
|
|
46
|
+
ar_path: Path,
|
|
47
|
+
archive_path: Path,
|
|
48
|
+
object_files: List[Path]
|
|
49
|
+
) -> Path:
|
|
50
|
+
"""Create static library archive from object files.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
ar_path: Path to archiver tool (ar)
|
|
54
|
+
archive_path: Path for output .a file
|
|
55
|
+
object_files: List of object file paths to archive
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Path to generated archive file
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ArchiveError: If archive creation fails
|
|
62
|
+
"""
|
|
63
|
+
if not object_files:
|
|
64
|
+
raise ArchiveError("No object files provided for archive")
|
|
65
|
+
|
|
66
|
+
if not ar_path.exists():
|
|
67
|
+
raise ArchiveError(
|
|
68
|
+
f"Archiver not found: {ar_path}. Ensure toolchain is installed."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Ensure archive directory exists
|
|
72
|
+
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
# Build archiver command
|
|
75
|
+
# 'rcs' flags: r=insert/replace, c=create, s=index (ranlib)
|
|
76
|
+
cmd = [str(ar_path), "rcs", str(archive_path)]
|
|
77
|
+
cmd.extend([str(obj) for obj in object_files])
|
|
78
|
+
|
|
79
|
+
# Execute archiver
|
|
80
|
+
if self.show_progress:
|
|
81
|
+
print(f"Creating {archive_path.name} archive from {len(object_files)} object files...")
|
|
82
|
+
|
|
83
|
+
# On Windows, add retry logic to handle file locking issues
|
|
84
|
+
# Object files may still have open handles from compiler/antivirus
|
|
85
|
+
is_windows = platform.system() == "Windows"
|
|
86
|
+
max_retries = 5 if is_windows else 1
|
|
87
|
+
delay = 0.1
|
|
88
|
+
last_error = None
|
|
89
|
+
|
|
90
|
+
for attempt in range(max_retries):
|
|
91
|
+
try:
|
|
92
|
+
# On Windows, force garbage collection and add delay before retry
|
|
93
|
+
if is_windows and attempt > 0:
|
|
94
|
+
gc.collect()
|
|
95
|
+
time.sleep(delay)
|
|
96
|
+
if self.show_progress:
|
|
97
|
+
print(f" Retrying archive creation (attempt {attempt + 1}/{max_retries})...")
|
|
98
|
+
|
|
99
|
+
result = subprocess.run(
|
|
100
|
+
cmd,
|
|
101
|
+
capture_output=True,
|
|
102
|
+
text=True,
|
|
103
|
+
timeout=60
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if result.returncode != 0:
|
|
107
|
+
# Check if error is due to file truncation/locking (Windows-specific)
|
|
108
|
+
# Windows file locking manifests as: "file truncated", "error reading", or "No such file"
|
|
109
|
+
stderr_lower = result.stderr.lower()
|
|
110
|
+
is_file_locking_error = (
|
|
111
|
+
"file truncated" in stderr_lower or
|
|
112
|
+
"error reading" in stderr_lower or
|
|
113
|
+
"no such file" in stderr_lower
|
|
114
|
+
)
|
|
115
|
+
if is_windows and is_file_locking_error:
|
|
116
|
+
last_error = result.stderr
|
|
117
|
+
if attempt < max_retries - 1:
|
|
118
|
+
if self.show_progress:
|
|
119
|
+
print(" [Windows] Detected file locking error, retrying...")
|
|
120
|
+
delay = min(delay * 2, 1.0) # Exponential backoff, max 1s
|
|
121
|
+
continue
|
|
122
|
+
else:
|
|
123
|
+
# Last attempt failed
|
|
124
|
+
error_msg = f"Archive creation failed after {max_retries} attempts (file locking)\n"
|
|
125
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
126
|
+
error_msg += f"stdout: {result.stdout}"
|
|
127
|
+
raise ArchiveError(error_msg)
|
|
128
|
+
|
|
129
|
+
error_msg = f"Archive creation failed for {archive_path.name}\n"
|
|
130
|
+
error_msg += f"stderr: {result.stderr}\n"
|
|
131
|
+
error_msg += f"stdout: {result.stdout}"
|
|
132
|
+
raise ArchiveError(error_msg)
|
|
133
|
+
|
|
134
|
+
if not archive_path.exists():
|
|
135
|
+
raise ArchiveError(f"Archive was not created: {archive_path}")
|
|
136
|
+
|
|
137
|
+
if self.show_progress:
|
|
138
|
+
size = archive_path.stat().st_size
|
|
139
|
+
print(f"✓ Created {archive_path.name}: {size:,} bytes ({size / 1024 / 1024:.2f} MB)")
|
|
140
|
+
|
|
141
|
+
return archive_path
|
|
142
|
+
|
|
143
|
+
except subprocess.TimeoutExpired as e:
|
|
144
|
+
raise ArchiveError(f"Archive creation timeout for {archive_path.name}") from e
|
|
145
|
+
except KeyboardInterrupt as ke:
|
|
146
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
147
|
+
handle_keyboard_interrupt_properly(ke)
|
|
148
|
+
raise # Never reached, but satisfies type checker
|
|
149
|
+
except Exception as e:
|
|
150
|
+
if isinstance(e, ArchiveError):
|
|
151
|
+
raise
|
|
152
|
+
# If Windows file locking error, retry
|
|
153
|
+
if is_windows and attempt < max_retries - 1:
|
|
154
|
+
last_error = str(e)
|
|
155
|
+
delay = min(delay * 2, 1.0)
|
|
156
|
+
continue
|
|
157
|
+
raise ArchiveError(f"Failed to create archive {archive_path.name}: {e}") from e
|
|
158
|
+
|
|
159
|
+
# If we exhausted retries, raise the last error
|
|
160
|
+
if last_error:
|
|
161
|
+
raise ArchiveError(f"Archive creation failed after {max_retries} attempts: {last_error}")
|
|
162
|
+
raise ArchiveError(f"Archive creation failed after {max_retries} attempts")
|
|
163
|
+
|
|
164
|
+
def create_core_archive(
|
|
165
|
+
self,
|
|
166
|
+
ar_path: Path,
|
|
167
|
+
build_dir: Path,
|
|
168
|
+
object_files: List[Path]
|
|
169
|
+
) -> Path:
|
|
170
|
+
"""Create core.a archive from core object files.
|
|
171
|
+
|
|
172
|
+
Convenience method for creating the standard core.a archive.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
ar_path: Path to archiver tool (ar)
|
|
176
|
+
build_dir: Build directory
|
|
177
|
+
object_files: List of core object file paths
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Path to generated core.a file
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ArchiveError: If archive creation fails
|
|
184
|
+
"""
|
|
185
|
+
archive_path = build_dir / "core.a"
|
|
186
|
+
return self.create_archive(ar_path, archive_path, object_files)
|