fbuild 1.2.8__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.
- fbuild/__init__.py +390 -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_info_generator.py +624 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +93 -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 +664 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +214 -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 +651 -0
- fbuild/build/orchestrator_esp32.py +878 -0
- fbuild/build/orchestrator_rp2040.py +719 -0
- fbuild/build/orchestrator_stm32.py +696 -0
- fbuild/build/orchestrator_teensy.py +580 -0
- fbuild/build/source_compilation_orchestrator.py +218 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +717 -0
- fbuild/cli_utils.py +314 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +542 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +369 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +42 -0
- fbuild/daemon/async_client.py +531 -0
- fbuild/daemon/client.py +1505 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/configuration_lock.py +865 -0
- fbuild/daemon/daemon.py +585 -0
- fbuild/daemon/daemon_context.py +293 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/firmware_ledger.py +546 -0
- fbuild/daemon/lock_manager.py +508 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +957 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/port_state_manager.py +249 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +18 -0
- fbuild/daemon/processors/build_processor.py +248 -0
- fbuild/daemon/processors/deploy_processor.py +664 -0
- fbuild/daemon/processors/install_deps_processor.py +431 -0
- fbuild/daemon/processors/locking_processor.py +777 -0
- fbuild/daemon/processors/monitor_processor.py +285 -0
- fbuild/daemon/request_processor.py +457 -0
- fbuild/daemon/shared_serial.py +819 -0
- fbuild/daemon/status_manager.py +238 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +21 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +310 -0
- fbuild/deploy/docker_utils.py +315 -0
- fbuild/deploy/monitor.py +519 -0
- fbuild/deploy/qemu_runner.py +603 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/ledger/__init__.py +52 -0
- fbuild/ledger/board_ledger.py +560 -0
- fbuild/output.py +352 -0
- fbuild/packages/__init__.py +66 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +256 -0
- fbuild/packages/concurrent_manager.py +510 -0
- fbuild/packages/downloader.py +518 -0
- fbuild/packages/fingerprint.py +423 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_rp2040.py +349 -0
- fbuild/packages/framework_stm32.py +459 -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 +725 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_rp2040.py +400 -0
- fbuild/packages/platform_stm32.py +581 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +369 -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 +489 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_rp2040.py +436 -0
- fbuild/packages/toolchain_stm32.py +417 -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/platform_configs/rp2040.json +70 -0
- fbuild/platform_configs/rp2350.json +76 -0
- fbuild/platform_configs/stm32f1.json +59 -0
- fbuild/platform_configs/stm32f4.json +63 -0
- fbuild/py.typed +0 -0
- fbuild-1.2.8.dist-info/METADATA +468 -0
- fbuild-1.2.8.dist-info/RECORD +121 -0
- fbuild-1.2.8.dist-info/WHEEL +5 -0
- fbuild-1.2.8.dist-info/entry_points.txt +5 -0
- fbuild-1.2.8.dist-info/licenses/LICENSE +21 -0
- fbuild-1.2.8.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,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"
|