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.

Files changed (93) hide show
  1. fbuild/__init__.py +0 -0
  2. fbuild/assets/example.txt +1 -0
  3. fbuild/build/__init__.py +117 -0
  4. fbuild/build/archive_creator.py +186 -0
  5. fbuild/build/binary_generator.py +444 -0
  6. fbuild/build/build_component_factory.py +131 -0
  7. fbuild/build/build_state.py +325 -0
  8. fbuild/build/build_utils.py +98 -0
  9. fbuild/build/compilation_executor.py +422 -0
  10. fbuild/build/compiler.py +165 -0
  11. fbuild/build/compiler_avr.py +574 -0
  12. fbuild/build/configurable_compiler.py +612 -0
  13. fbuild/build/configurable_linker.py +637 -0
  14. fbuild/build/flag_builder.py +186 -0
  15. fbuild/build/library_dependency_processor.py +185 -0
  16. fbuild/build/linker.py +708 -0
  17. fbuild/build/orchestrator.py +67 -0
  18. fbuild/build/orchestrator_avr.py +656 -0
  19. fbuild/build/orchestrator_esp32.py +797 -0
  20. fbuild/build/orchestrator_teensy.py +543 -0
  21. fbuild/build/source_compilation_orchestrator.py +220 -0
  22. fbuild/build/source_scanner.py +516 -0
  23. fbuild/cli.py +566 -0
  24. fbuild/cli_utils.py +312 -0
  25. fbuild/config/__init__.py +16 -0
  26. fbuild/config/board_config.py +457 -0
  27. fbuild/config/board_loader.py +92 -0
  28. fbuild/config/ini_parser.py +209 -0
  29. fbuild/config/mcu_specs.py +88 -0
  30. fbuild/daemon/__init__.py +34 -0
  31. fbuild/daemon/client.py +929 -0
  32. fbuild/daemon/compilation_queue.py +293 -0
  33. fbuild/daemon/daemon.py +474 -0
  34. fbuild/daemon/daemon_context.py +196 -0
  35. fbuild/daemon/error_collector.py +263 -0
  36. fbuild/daemon/file_cache.py +332 -0
  37. fbuild/daemon/lock_manager.py +270 -0
  38. fbuild/daemon/logging_utils.py +149 -0
  39. fbuild/daemon/messages.py +301 -0
  40. fbuild/daemon/operation_registry.py +288 -0
  41. fbuild/daemon/process_tracker.py +366 -0
  42. fbuild/daemon/processors/__init__.py +12 -0
  43. fbuild/daemon/processors/build_processor.py +157 -0
  44. fbuild/daemon/processors/deploy_processor.py +327 -0
  45. fbuild/daemon/processors/monitor_processor.py +146 -0
  46. fbuild/daemon/request_processor.py +401 -0
  47. fbuild/daemon/status_manager.py +216 -0
  48. fbuild/daemon/subprocess_manager.py +316 -0
  49. fbuild/deploy/__init__.py +17 -0
  50. fbuild/deploy/deployer.py +67 -0
  51. fbuild/deploy/deployer_esp32.py +314 -0
  52. fbuild/deploy/monitor.py +495 -0
  53. fbuild/interrupt_utils.py +34 -0
  54. fbuild/packages/__init__.py +53 -0
  55. fbuild/packages/archive_utils.py +1098 -0
  56. fbuild/packages/arduino_core.py +412 -0
  57. fbuild/packages/cache.py +249 -0
  58. fbuild/packages/downloader.py +366 -0
  59. fbuild/packages/framework_esp32.py +538 -0
  60. fbuild/packages/framework_teensy.py +346 -0
  61. fbuild/packages/github_utils.py +96 -0
  62. fbuild/packages/header_trampoline_cache.py +394 -0
  63. fbuild/packages/library_compiler.py +203 -0
  64. fbuild/packages/library_manager.py +549 -0
  65. fbuild/packages/library_manager_esp32.py +413 -0
  66. fbuild/packages/package.py +163 -0
  67. fbuild/packages/platform_esp32.py +383 -0
  68. fbuild/packages/platform_teensy.py +312 -0
  69. fbuild/packages/platform_utils.py +131 -0
  70. fbuild/packages/platformio_registry.py +325 -0
  71. fbuild/packages/sdk_utils.py +231 -0
  72. fbuild/packages/toolchain.py +436 -0
  73. fbuild/packages/toolchain_binaries.py +196 -0
  74. fbuild/packages/toolchain_esp32.py +484 -0
  75. fbuild/packages/toolchain_metadata.py +185 -0
  76. fbuild/packages/toolchain_teensy.py +404 -0
  77. fbuild/platform_configs/esp32.json +150 -0
  78. fbuild/platform_configs/esp32c2.json +144 -0
  79. fbuild/platform_configs/esp32c3.json +143 -0
  80. fbuild/platform_configs/esp32c5.json +151 -0
  81. fbuild/platform_configs/esp32c6.json +151 -0
  82. fbuild/platform_configs/esp32p4.json +149 -0
  83. fbuild/platform_configs/esp32s3.json +151 -0
  84. fbuild/platform_configs/imxrt1062.json +56 -0
  85. fbuild-1.1.0.dist-info/METADATA +447 -0
  86. fbuild-1.1.0.dist-info/RECORD +93 -0
  87. fbuild-1.1.0.dist-info/WHEEL +5 -0
  88. fbuild-1.1.0.dist-info/entry_points.txt +5 -0
  89. fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
  90. fbuild-1.1.0.dist-info/top_level.txt +2 -0
  91. fbuild_lint/__init__.py +0 -0
  92. fbuild_lint/ruff_plugins/__init__.py +0 -0
  93. fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
@@ -0,0 +1,346 @@
1
+ """Teensy Framework Management.
2
+
3
+ This module handles downloading, extracting, and managing the Teensy Arduino core
4
+ framework needed for Teensy 4.x builds.
5
+
6
+ Framework Download Process:
7
+ 1. Download Teensy cores from GitHub (PaulStoffregen/cores)
8
+ 2. Extract to cache directory
9
+ 3. Provide access to cores, linker scripts, and libraries
10
+
11
+ Framework Structure (after extraction):
12
+ cores/
13
+ ├── teensy/ # Teensy 2.0 (AVR)
14
+ ├── teensy3/ # Teensy 3.x (Cortex-M4)
15
+ └── teensy4/ # Teensy 4.x (Cortex-M7) <- TARGET
16
+ ├── Arduino.h
17
+ ├── main.cpp
18
+ ├── wiring.c
19
+ ├── core_pins.h
20
+ ├── imxrt.h
21
+ ├── imxrt1062_t41.ld # Linker script for Teensy 4.1
22
+ └── ...
23
+
24
+ Key Features:
25
+ - Full Arduino API compatibility
26
+ - USB device type configuration
27
+ - DMA-based peripherals
28
+ - Hardware timers and PWM
29
+ """
30
+
31
+ from pathlib import Path
32
+ from typing import Any, Dict, List, Optional
33
+
34
+ from .cache import Cache
35
+ from .downloader import DownloadError, ExtractionError, PackageDownloader
36
+ from .package import IFramework, PackageError
37
+
38
+
39
+ class FrameworkErrorTeensy(PackageError):
40
+ """Raised when Teensy framework operations fail."""
41
+
42
+ pass
43
+
44
+
45
+ class FrameworkTeensy(IFramework):
46
+ """Manages Teensy framework download, extraction, and access.
47
+
48
+ This class handles the Teensy cores framework which includes:
49
+ - Arduino core for Teensy 4.x (cores/teensy4/)
50
+ - Linker scripts for memory layout
51
+ - Core libraries and headers
52
+ """
53
+
54
+ # Teensy cores repository URL
55
+ CORES_REPO_URL = "https://github.com/PaulStoffregen/cores"
56
+ CORES_ARCHIVE_URL = "https://github.com/PaulStoffregen/cores/archive/refs/heads/master.zip"
57
+
58
+ def __init__(
59
+ self,
60
+ cache: Cache,
61
+ show_progress: bool = True,
62
+ ):
63
+ """Initialize Teensy framework manager.
64
+
65
+ Args:
66
+ cache: Cache manager instance
67
+ show_progress: Whether to show download/extraction progress
68
+ """
69
+ self.cache = cache
70
+ self.show_progress = show_progress
71
+ self.downloader = PackageDownloader()
72
+
73
+ # Use master branch as version
74
+ self.version = "master"
75
+ self.framework_url = self.CORES_ARCHIVE_URL
76
+
77
+ # Get framework path from cache
78
+ self.framework_path = cache.get_platform_path(self.framework_url, self.version)
79
+
80
+ def ensure_framework(self) -> Path:
81
+ """Ensure framework is downloaded and extracted.
82
+
83
+ Returns:
84
+ Path to the extracted framework directory
85
+
86
+ Raises:
87
+ FrameworkErrorTeensy: If download or extraction fails
88
+ """
89
+ if self.is_installed():
90
+ if self.show_progress:
91
+ print(f"Using cached Teensy cores {self.version}")
92
+ return self.framework_path
93
+
94
+ try:
95
+ if self.show_progress:
96
+ print(f"Downloading Teensy cores {self.version}...")
97
+
98
+ # Download and extract framework package
99
+ self.cache.ensure_directories()
100
+
101
+ # Use downloader to handle download and extraction
102
+ archive_name = "teensy-cores-master.zip"
103
+ archive_path = self.framework_path.parent / archive_name
104
+
105
+ # Download if not cached
106
+ if not archive_path.exists():
107
+ archive_path.parent.mkdir(parents=True, exist_ok=True)
108
+ self.downloader.download(self.framework_url, archive_path, show_progress=self.show_progress)
109
+ else:
110
+ if self.show_progress:
111
+ print("Using cached cores archive")
112
+
113
+ # Extract to framework directory
114
+ if self.show_progress:
115
+ print("Extracting Teensy cores...")
116
+
117
+ # Create temp extraction directory
118
+ temp_extract = self.framework_path.parent / "temp_extract"
119
+ temp_extract.mkdir(parents=True, exist_ok=True)
120
+
121
+ self.downloader.extract_archive(archive_path, temp_extract, show_progress=self.show_progress)
122
+
123
+ # Find the cores directory in the extracted content
124
+ # Usually it's a subdirectory like "cores-master/"
125
+ extracted_dirs = list(temp_extract.glob("cores-*"))
126
+ if not extracted_dirs:
127
+ # Maybe it extracted directly
128
+ extracted_dirs = [temp_extract]
129
+
130
+ source_dir = extracted_dirs[0]
131
+
132
+ # Move to final location
133
+ if self.framework_path.exists():
134
+ import shutil
135
+
136
+ shutil.rmtree(self.framework_path)
137
+
138
+ source_dir.rename(self.framework_path)
139
+
140
+ # Clean up temp directory
141
+ if temp_extract.exists() and temp_extract != self.framework_path:
142
+ import shutil
143
+
144
+ shutil.rmtree(temp_extract, ignore_errors=True)
145
+
146
+ if self.show_progress:
147
+ print(f"Teensy cores installed to {self.framework_path}")
148
+
149
+ return self.framework_path
150
+
151
+ except (DownloadError, ExtractionError) as e:
152
+ raise FrameworkErrorTeensy(f"Failed to install Teensy cores: {e}")
153
+ except KeyboardInterrupt as ke:
154
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
155
+
156
+ handle_keyboard_interrupt_properly(ke)
157
+ raise # Never reached, but satisfies type checker
158
+ except Exception as e:
159
+ raise FrameworkErrorTeensy(f"Unexpected error installing framework: {e}")
160
+
161
+ def is_installed(self) -> bool:
162
+ """Check if framework is already installed.
163
+
164
+ Returns:
165
+ True if framework directory exists with key files
166
+ """
167
+ if not self.framework_path.exists():
168
+ return False
169
+
170
+ # Verify teensy4 core directory exists
171
+ teensy4_path = self.framework_path / "teensy4"
172
+ if not teensy4_path.exists():
173
+ return False
174
+
175
+ # Verify essential files exist
176
+ required_files = [
177
+ teensy4_path / "Arduino.h",
178
+ teensy4_path / "main.cpp",
179
+ ]
180
+
181
+ return all(f.exists() for f in required_files)
182
+
183
+ def get_core_dir(self, core_name: str = "teensy4") -> Path:
184
+ """Get path to specific core directory.
185
+
186
+ Args:
187
+ core_name: Core name (default: "teensy4")
188
+
189
+ Returns:
190
+ Path to the core directory
191
+
192
+ Raises:
193
+ FrameworkErrorTeensy: If core directory doesn't exist
194
+ """
195
+ core_path = self.framework_path / core_name
196
+ if not core_path.exists():
197
+ raise FrameworkErrorTeensy(f"Core '{core_name}' not found at {core_path}")
198
+ return core_path
199
+
200
+ def get_core_sources(self, core_name: str = "teensy4") -> List[Path]:
201
+ """Get all source files in a core.
202
+
203
+ Args:
204
+ core_name: Core name (default: "teensy4")
205
+
206
+ Returns:
207
+ List of .c and .cpp source file paths
208
+ """
209
+ core_dir = self.get_core_dir(core_name)
210
+ sources: List[Path] = []
211
+
212
+ # Get all .c and .cpp files in the core directory
213
+ sources.extend(core_dir.glob("*.c"))
214
+ sources.extend(core_dir.glob("*.cpp"))
215
+
216
+ # Remove duplicates and sort
217
+ return sorted(set(sources))
218
+
219
+ def get_core_includes(self, core_name: str = "teensy4") -> List[Path]:
220
+ """Get include directories for a core.
221
+
222
+ Args:
223
+ core_name: Core name (default: "teensy4")
224
+
225
+ Returns:
226
+ List of include directory paths
227
+ """
228
+ core_dir = self.get_core_dir(core_name)
229
+ return [core_dir]
230
+
231
+ def get_linker_script(self, board: str = "teensy41") -> Optional[Path]:
232
+ """Get linker script for a specific board.
233
+
234
+ Args:
235
+ board: Board identifier (default: "teensy41")
236
+
237
+ Returns:
238
+ Path to linker script or None if not found
239
+ """
240
+ core_dir = self.get_core_dir("teensy4")
241
+
242
+ # Map board names to linker scripts
243
+ linker_scripts = {
244
+ "teensy41": "imxrt1062_t41.ld",
245
+ "teensy40": "imxrt1062.ld",
246
+ }
247
+
248
+ linker_script_name = linker_scripts.get(board)
249
+ if not linker_script_name:
250
+ return None
251
+
252
+ linker_script_path = core_dir / linker_script_name
253
+ return linker_script_path if linker_script_path.exists() else None
254
+
255
+ def list_cores(self) -> List[str]:
256
+ """List all available cores.
257
+
258
+ Returns:
259
+ List of core names
260
+ """
261
+ if not self.framework_path.exists():
262
+ return []
263
+
264
+ return [d.name for d in self.framework_path.iterdir() if d.is_dir() and d.name.startswith("teensy")]
265
+
266
+ def get_framework_info(self) -> Dict[str, Any]:
267
+ """Get information about the installed framework.
268
+
269
+ Returns:
270
+ Dictionary with framework information
271
+ """
272
+ info = {
273
+ "version": self.version,
274
+ "path": str(self.framework_path),
275
+ "url": self.framework_url,
276
+ "installed": self.is_installed(),
277
+ }
278
+
279
+ if self.is_installed():
280
+ info["available_cores"] = self.list_cores()
281
+ teensy4_dir = self.framework_path / "teensy4"
282
+ if teensy4_dir.exists():
283
+ info["teensy4_path"] = str(teensy4_dir)
284
+ info["teensy4_sources"] = len(self.get_core_sources("teensy4"))
285
+
286
+ return info
287
+
288
+ # Implement IFramework interface methods
289
+ def get_cores_dir(self) -> Path:
290
+ """Get path to cores directory.
291
+
292
+ Returns:
293
+ Path to cores directory containing Arduino core implementation
294
+
295
+ Raises:
296
+ FrameworkErrorTeensy: If cores directory doesn't exist
297
+ """
298
+ if not self.framework_path.exists():
299
+ raise FrameworkErrorTeensy(f"Framework not installed at {self.framework_path}")
300
+ return self.framework_path
301
+
302
+ def get_variants_dir(self) -> Path:
303
+ """Get path to variants directory.
304
+
305
+ For Teensy, variants are embedded in the core directories.
306
+ Returns the framework path as variants are core-specific.
307
+
308
+ Returns:
309
+ Path to framework directory (variants are in core dirs)
310
+ """
311
+ if not self.framework_path.exists():
312
+ raise FrameworkErrorTeensy(f"Framework not installed at {self.framework_path}")
313
+ return self.framework_path
314
+
315
+ def get_libraries_dir(self) -> Path:
316
+ """Get path to built-in libraries directory.
317
+
318
+ For Teensy, built-in libraries are typically part of Teensyduino.
319
+ Returns the framework path as a base.
320
+
321
+ Returns:
322
+ Path to framework directory (libraries location)
323
+ """
324
+ if not self.framework_path.exists():
325
+ raise FrameworkErrorTeensy(f"Framework not installed at {self.framework_path}")
326
+ return self.framework_path
327
+
328
+ # Implement IPackage interface
329
+ def ensure_package(self) -> Path:
330
+ """Ensure package is downloaded and extracted.
331
+
332
+ Returns:
333
+ Path to the extracted package directory
334
+
335
+ Raises:
336
+ PackageError: If download or extraction fails
337
+ """
338
+ return self.ensure_framework()
339
+
340
+ def get_package_info(self) -> Dict[str, Any]:
341
+ """Get information about the package.
342
+
343
+ Returns:
344
+ Dictionary with package metadata (version, path, etc.)
345
+ """
346
+ return self.get_framework_info()
@@ -0,0 +1,96 @@
1
+ """GitHub URL optimization utilities for fbuild.
2
+
3
+ This module provides utilities for working with GitHub repository URLs,
4
+ including converting them to optimized zip download URLs.
5
+ """
6
+
7
+ from urllib.parse import urlparse
8
+
9
+
10
+ class GitHubURLOptimizer:
11
+ """Optimizes GitHub URLs to use zip downloads instead of git clone."""
12
+
13
+ @staticmethod
14
+ def is_github_url(url: str) -> bool:
15
+ """Check if a URL is a GitHub repository URL.
16
+
17
+ Args:
18
+ url: The URL to check
19
+
20
+ Returns:
21
+ True if the URL is a GitHub repository
22
+ """
23
+ parsed = urlparse(url)
24
+ return parsed.netloc.lower() in ("github.com", "www.github.com")
25
+
26
+ @staticmethod
27
+ def detect_default_branch(url: str) -> str:
28
+ """Detect the default branch name for a GitHub repository.
29
+
30
+ Makes a HEAD request to determine if the repo uses 'main' or 'master'.
31
+
32
+ Args:
33
+ url: GitHub repository URL
34
+
35
+ Returns:
36
+ Default branch name ('main' or 'master')
37
+ """
38
+ try:
39
+ import requests
40
+
41
+ # Try main first (modern default)
42
+ test_url = f"{url}/archive/refs/heads/main.zip"
43
+ response = requests.head(test_url, timeout=5, allow_redirects=True)
44
+ if response.status_code == 200:
45
+ return "main"
46
+
47
+ # Fall back to master
48
+ test_url = f"{url}/archive/refs/heads/master.zip"
49
+ response = requests.head(test_url, timeout=5, allow_redirects=True)
50
+ if response.status_code == 200:
51
+ return "master"
52
+
53
+ # Default to main if both fail
54
+ return "main"
55
+
56
+ except KeyboardInterrupt as ke:
57
+ from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
58
+
59
+ handle_keyboard_interrupt_properly(ke)
60
+ raise # Never reached, but satisfies type checker
61
+ except Exception:
62
+ # If we can't detect, default to main
63
+ return "main"
64
+
65
+ @classmethod
66
+ def optimize_url(cls, url: str) -> str:
67
+ """Convert a GitHub URL to use zip download instead of git clone.
68
+
69
+ Transforms:
70
+ https://github.com/FastLED/FastLED
71
+ Into:
72
+ https://github.com/FastLED/FastLED/archive/refs/heads/main.zip
73
+
74
+ Args:
75
+ url: Original GitHub URL
76
+
77
+ Returns:
78
+ Optimized zip download URL
79
+ """
80
+ if not cls.is_github_url(url):
81
+ return url
82
+
83
+ # Remove trailing slashes and .git suffix
84
+ url = url.rstrip("/")
85
+ if url.endswith(".git"):
86
+ url = url[:-4]
87
+
88
+ # Check if already a zip URL
89
+ if "/archive/" in url:
90
+ return url
91
+
92
+ # Detect default branch
93
+ branch = cls.detect_default_branch(url)
94
+
95
+ # Build zip URL
96
+ return f"{url}/archive/refs/heads/{branch}.zip"