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/cli.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for fbuild.
|
|
3
|
+
|
|
4
|
+
This module provides the `fbuild` CLI tool for building embedded firmware.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from fbuild.cli_utils import (
|
|
14
|
+
EnvironmentDetector,
|
|
15
|
+
ErrorFormatter,
|
|
16
|
+
MonitorFlagParser,
|
|
17
|
+
PathValidator,
|
|
18
|
+
)
|
|
19
|
+
from fbuild.daemon import client as daemon_client
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class BuildArgs:
|
|
24
|
+
"""Arguments for the build command."""
|
|
25
|
+
|
|
26
|
+
project_dir: Path
|
|
27
|
+
environment: Optional[str] = None
|
|
28
|
+
clean: bool = False
|
|
29
|
+
verbose: bool = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DeployArgs:
|
|
34
|
+
"""Arguments for the deploy command."""
|
|
35
|
+
|
|
36
|
+
project_dir: Path
|
|
37
|
+
environment: Optional[str] = None
|
|
38
|
+
port: Optional[str] = None
|
|
39
|
+
clean: bool = False
|
|
40
|
+
monitor: Optional[str] = None
|
|
41
|
+
verbose: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class MonitorArgs:
|
|
46
|
+
"""Arguments for the monitor command."""
|
|
47
|
+
|
|
48
|
+
project_dir: Path
|
|
49
|
+
environment: Optional[str] = None
|
|
50
|
+
port: Optional[str] = None
|
|
51
|
+
baud: int = 115200
|
|
52
|
+
timeout: Optional[int] = None
|
|
53
|
+
halt_on_error: Optional[str] = None
|
|
54
|
+
halt_on_success: Optional[str] = None
|
|
55
|
+
expect: Optional[str] = None
|
|
56
|
+
verbose: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_command(args: BuildArgs) -> None:
|
|
60
|
+
"""Build firmware for embedded target.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
fbuild build # Build default environment
|
|
64
|
+
fbuild build tests/uno # Build specific project
|
|
65
|
+
fbuild build -e uno # Build 'uno' environment
|
|
66
|
+
fbuild build --clean # Clean build
|
|
67
|
+
fbuild build --verbose # Verbose output
|
|
68
|
+
"""
|
|
69
|
+
# Print header
|
|
70
|
+
print("fbuild Build System v0.1.0")
|
|
71
|
+
print()
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Determine environment name
|
|
75
|
+
env_name = EnvironmentDetector.detect_environment(args.project_dir, args.environment)
|
|
76
|
+
|
|
77
|
+
# Show build start message
|
|
78
|
+
if args.verbose:
|
|
79
|
+
print(f"Building project: {args.project_dir}")
|
|
80
|
+
print(f"Environment: {env_name}")
|
|
81
|
+
print()
|
|
82
|
+
else:
|
|
83
|
+
print(f"Building environment: {env_name}...")
|
|
84
|
+
|
|
85
|
+
# Route build through daemon for background processing
|
|
86
|
+
success = daemon_client.request_build(
|
|
87
|
+
project_dir=args.project_dir,
|
|
88
|
+
environment=env_name,
|
|
89
|
+
clean_build=args.clean,
|
|
90
|
+
verbose=args.verbose,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Exit with appropriate code
|
|
94
|
+
sys.exit(0 if success else 1)
|
|
95
|
+
|
|
96
|
+
except FileNotFoundError as e:
|
|
97
|
+
ErrorFormatter.handle_file_not_found(e)
|
|
98
|
+
except PermissionError as e:
|
|
99
|
+
ErrorFormatter.handle_permission_error(e)
|
|
100
|
+
except KeyboardInterrupt:
|
|
101
|
+
ErrorFormatter.handle_keyboard_interrupt()
|
|
102
|
+
except Exception as e:
|
|
103
|
+
ErrorFormatter.handle_unexpected_error(e, args.verbose)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def deploy_command(args: DeployArgs) -> None:
|
|
107
|
+
"""Deploy firmware to embedded target.
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
fbuild deploy # Deploy default environment
|
|
111
|
+
fbuild deploy tests/esp32c6 # Deploy specific project
|
|
112
|
+
fbuild deploy -e esp32c6 # Deploy 'esp32c6' environment
|
|
113
|
+
fbuild deploy -p COM3 # Deploy to specific port
|
|
114
|
+
fbuild deploy --clean # Clean build before deploy
|
|
115
|
+
fbuild deploy --monitor="--timeout 60 --halt-on-success \"TEST PASSED\"" # Deploy and monitor
|
|
116
|
+
"""
|
|
117
|
+
print("fbuild Deployment System v0.1.0")
|
|
118
|
+
print()
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
# Determine environment name
|
|
122
|
+
env_name = EnvironmentDetector.detect_environment(args.project_dir, args.environment)
|
|
123
|
+
|
|
124
|
+
# Parse monitor flags if provided
|
|
125
|
+
monitor_after = args.monitor is not None
|
|
126
|
+
monitor_timeout = None
|
|
127
|
+
monitor_halt_on_error = None
|
|
128
|
+
monitor_halt_on_success = None
|
|
129
|
+
monitor_expect = None
|
|
130
|
+
if monitor_after and args.monitor is not None:
|
|
131
|
+
flags = MonitorFlagParser.parse_monitor_flags(args.monitor)
|
|
132
|
+
monitor_timeout = flags.timeout
|
|
133
|
+
monitor_halt_on_error = flags.halt_on_error
|
|
134
|
+
monitor_halt_on_success = flags.halt_on_success
|
|
135
|
+
monitor_expect = flags.expect
|
|
136
|
+
|
|
137
|
+
# Use daemon for concurrent deploy management
|
|
138
|
+
success = daemon_client.request_deploy(
|
|
139
|
+
project_dir=args.project_dir,
|
|
140
|
+
environment=env_name,
|
|
141
|
+
port=args.port,
|
|
142
|
+
clean_build=args.clean,
|
|
143
|
+
monitor_after=monitor_after,
|
|
144
|
+
monitor_timeout=monitor_timeout,
|
|
145
|
+
monitor_halt_on_error=monitor_halt_on_error,
|
|
146
|
+
monitor_halt_on_success=monitor_halt_on_success,
|
|
147
|
+
monitor_expect=monitor_expect,
|
|
148
|
+
timeout=1800, # 30 minute timeout for deploy
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if success:
|
|
152
|
+
sys.exit(0)
|
|
153
|
+
else:
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
except FileNotFoundError as e:
|
|
157
|
+
ErrorFormatter.handle_file_not_found(e)
|
|
158
|
+
except KeyboardInterrupt as ke:
|
|
159
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
160
|
+
|
|
161
|
+
handle_keyboard_interrupt_properly(ke)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
ErrorFormatter.handle_unexpected_error(e, args.verbose)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def monitor_command(args: MonitorArgs) -> None:
|
|
167
|
+
"""Monitor serial output from embedded target.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
fbuild monitor # Monitor default environment
|
|
171
|
+
fbuild monitor -p COM3 # Monitor specific port
|
|
172
|
+
fbuild monitor --timeout 60 # Monitor with 60s timeout
|
|
173
|
+
fbuild monitor --halt-on-error "ERROR" # Exit on error
|
|
174
|
+
fbuild monitor --halt-on-success "TEST PASSED" # Exit on success
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# Determine environment name
|
|
178
|
+
env_name = EnvironmentDetector.detect_environment(args.project_dir, args.environment)
|
|
179
|
+
|
|
180
|
+
# Use daemon for concurrent monitor management
|
|
181
|
+
success = daemon_client.request_monitor(
|
|
182
|
+
project_dir=args.project_dir,
|
|
183
|
+
environment=env_name,
|
|
184
|
+
port=args.port,
|
|
185
|
+
baud_rate=args.baud,
|
|
186
|
+
halt_on_error=args.halt_on_error,
|
|
187
|
+
halt_on_success=args.halt_on_success,
|
|
188
|
+
expect=args.expect,
|
|
189
|
+
timeout=args.timeout,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if success:
|
|
193
|
+
sys.exit(0)
|
|
194
|
+
else:
|
|
195
|
+
sys.exit(1)
|
|
196
|
+
|
|
197
|
+
except FileNotFoundError as e:
|
|
198
|
+
ErrorFormatter.handle_file_not_found(e)
|
|
199
|
+
except KeyboardInterrupt as ke:
|
|
200
|
+
from fbuild.interrupt_utils import handle_keyboard_interrupt_properly
|
|
201
|
+
|
|
202
|
+
handle_keyboard_interrupt_properly(ke)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
ErrorFormatter.handle_unexpected_error(e, args.verbose)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def daemon_command(action: str) -> None:
|
|
208
|
+
"""Manage the fbuild daemon.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
fbuild daemon status # Show daemon status
|
|
212
|
+
fbuild daemon stop # Stop the daemon
|
|
213
|
+
fbuild daemon restart # Restart the daemon
|
|
214
|
+
"""
|
|
215
|
+
try:
|
|
216
|
+
if action == "status":
|
|
217
|
+
# Get daemon status
|
|
218
|
+
status = daemon_client.get_daemon_status()
|
|
219
|
+
|
|
220
|
+
if status["running"]:
|
|
221
|
+
print("✅ Daemon is running")
|
|
222
|
+
print(f" PID: {status.get('pid', 'unknown')}")
|
|
223
|
+
|
|
224
|
+
if "current_status" in status:
|
|
225
|
+
current = status["current_status"]
|
|
226
|
+
print(f" State: {current.get('state', 'unknown')}")
|
|
227
|
+
print(f" Message: {current.get('message', 'N/A')}")
|
|
228
|
+
|
|
229
|
+
if current.get("operation_in_progress"):
|
|
230
|
+
print(" 🔄 Operation in progress:")
|
|
231
|
+
print(f" Environment: {current.get('environment', 'N/A')}")
|
|
232
|
+
print(f" Project: {current.get('project_dir', 'N/A')}")
|
|
233
|
+
else:
|
|
234
|
+
print("❌ Daemon is not running")
|
|
235
|
+
|
|
236
|
+
elif action == "stop":
|
|
237
|
+
# Stop daemon
|
|
238
|
+
if daemon_client.stop_daemon():
|
|
239
|
+
sys.exit(0)
|
|
240
|
+
else:
|
|
241
|
+
ErrorFormatter.print_error("Failed to stop daemon", "")
|
|
242
|
+
sys.exit(1)
|
|
243
|
+
|
|
244
|
+
elif action == "restart":
|
|
245
|
+
# Restart daemon
|
|
246
|
+
print("Restarting daemon...")
|
|
247
|
+
if daemon_client.is_daemon_running():
|
|
248
|
+
if not daemon_client.stop_daemon():
|
|
249
|
+
ErrorFormatter.print_error("Failed to stop daemon", "")
|
|
250
|
+
sys.exit(1)
|
|
251
|
+
|
|
252
|
+
# Start fresh daemon
|
|
253
|
+
if daemon_client.ensure_daemon_running():
|
|
254
|
+
print("✅ Daemon restarted successfully")
|
|
255
|
+
sys.exit(0)
|
|
256
|
+
else:
|
|
257
|
+
ErrorFormatter.print_error("Failed to restart daemon", "")
|
|
258
|
+
sys.exit(1)
|
|
259
|
+
else:
|
|
260
|
+
ErrorFormatter.print_error(f"Unknown daemon action: {action}", "")
|
|
261
|
+
print("Valid actions: status, stop, restart")
|
|
262
|
+
sys.exit(1)
|
|
263
|
+
|
|
264
|
+
except KeyboardInterrupt:
|
|
265
|
+
ErrorFormatter.handle_keyboard_interrupt()
|
|
266
|
+
except Exception as e:
|
|
267
|
+
ErrorFormatter.handle_unexpected_error(e, verbose=False)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def parse_default_action_args(argv: list[str]) -> DeployArgs:
|
|
271
|
+
"""Parse arguments for the default action (fbuild <project_dir> [flags]).
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
argv: Command-line arguments (sys.argv)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
DeployArgs with parsed values
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
SystemExit: If project directory is invalid or required arguments are missing
|
|
281
|
+
"""
|
|
282
|
+
if len(argv) < 2:
|
|
283
|
+
ErrorFormatter.print_error("Missing project directory", "")
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
project_dir = Path(argv[1])
|
|
287
|
+
PathValidator.validate_project_dir(project_dir)
|
|
288
|
+
|
|
289
|
+
# Parse remaining arguments
|
|
290
|
+
monitor: Optional[str] = None
|
|
291
|
+
port: Optional[str] = None
|
|
292
|
+
environment: Optional[str] = None
|
|
293
|
+
clean = False
|
|
294
|
+
verbose = False
|
|
295
|
+
|
|
296
|
+
i = 2
|
|
297
|
+
while i < len(argv):
|
|
298
|
+
arg = argv[i]
|
|
299
|
+
|
|
300
|
+
# Handle --monitor flag
|
|
301
|
+
if arg.startswith("--monitor="):
|
|
302
|
+
monitor = arg.split("=", 1)[1]
|
|
303
|
+
i += 1
|
|
304
|
+
elif arg == "--monitor" and i + 1 < len(argv):
|
|
305
|
+
monitor = argv[i + 1]
|
|
306
|
+
i += 2
|
|
307
|
+
# Handle --port flag
|
|
308
|
+
elif arg.startswith("--port="):
|
|
309
|
+
port = arg.split("=", 1)[1]
|
|
310
|
+
i += 1
|
|
311
|
+
elif arg in ("-p", "--port") and i + 1 < len(argv):
|
|
312
|
+
port = argv[i + 1]
|
|
313
|
+
i += 2
|
|
314
|
+
# Handle --environment flag
|
|
315
|
+
elif arg.startswith("--environment="):
|
|
316
|
+
environment = arg.split("=", 1)[1]
|
|
317
|
+
i += 1
|
|
318
|
+
elif arg.startswith("-e="):
|
|
319
|
+
environment = arg.split("=", 1)[1]
|
|
320
|
+
i += 1
|
|
321
|
+
elif arg in ("-e", "--environment") and i + 1 < len(argv):
|
|
322
|
+
environment = argv[i + 1]
|
|
323
|
+
i += 2
|
|
324
|
+
# Handle --clean flag
|
|
325
|
+
elif arg in ("-c", "--clean"):
|
|
326
|
+
clean = True
|
|
327
|
+
i += 1
|
|
328
|
+
# Handle --verbose flag
|
|
329
|
+
elif arg in ("-v", "--verbose"):
|
|
330
|
+
verbose = True
|
|
331
|
+
i += 1
|
|
332
|
+
else:
|
|
333
|
+
# Unknown flag - warn and skip
|
|
334
|
+
ErrorFormatter.print_error(f"Unknown flag in default action: {arg}", "")
|
|
335
|
+
print("Hint: Use 'fbuild deploy --help' to see available flags")
|
|
336
|
+
sys.exit(1)
|
|
337
|
+
|
|
338
|
+
return DeployArgs(
|
|
339
|
+
project_dir=project_dir,
|
|
340
|
+
environment=environment,
|
|
341
|
+
port=port,
|
|
342
|
+
clean=clean,
|
|
343
|
+
monitor=monitor if monitor is not None else "", # Empty string means monitor with default settings
|
|
344
|
+
verbose=verbose,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def main() -> None:
|
|
349
|
+
"""fbuild - Modern embedded build system.
|
|
350
|
+
|
|
351
|
+
Replace PlatformIO with URL-based platform/toolchain management.
|
|
352
|
+
"""
|
|
353
|
+
# Handle default action: fbuild <project_dir> [flags] → deploy with monitor
|
|
354
|
+
# This check must happen before argparse to avoid conflicts
|
|
355
|
+
if len(sys.argv) >= 2 and not sys.argv[1].startswith("-") and sys.argv[1] not in ["build", "deploy", "monitor", "daemon"]:
|
|
356
|
+
# User provided a path without a subcommand - use default action
|
|
357
|
+
deploy_args = parse_default_action_args(sys.argv)
|
|
358
|
+
deploy_command(deploy_args)
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
parser = argparse.ArgumentParser(
|
|
362
|
+
prog="fbuild",
|
|
363
|
+
description="fbuild - Modern embedded build system",
|
|
364
|
+
)
|
|
365
|
+
parser.add_argument(
|
|
366
|
+
"--version",
|
|
367
|
+
action="version",
|
|
368
|
+
version="fbuild 0.1.0",
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
372
|
+
|
|
373
|
+
# Build command
|
|
374
|
+
build_parser = subparsers.add_parser(
|
|
375
|
+
"build",
|
|
376
|
+
help="Build firmware for embedded target",
|
|
377
|
+
)
|
|
378
|
+
build_parser.add_argument(
|
|
379
|
+
"project_dir",
|
|
380
|
+
nargs="?",
|
|
381
|
+
type=Path,
|
|
382
|
+
default=Path.cwd(),
|
|
383
|
+
help="Project directory (default: current directory)",
|
|
384
|
+
)
|
|
385
|
+
build_parser.add_argument(
|
|
386
|
+
"-e",
|
|
387
|
+
"--environment",
|
|
388
|
+
default=None,
|
|
389
|
+
help="Build environment (default: auto-detect from platformio.ini)",
|
|
390
|
+
)
|
|
391
|
+
build_parser.add_argument(
|
|
392
|
+
"-c",
|
|
393
|
+
"--clean",
|
|
394
|
+
action="store_true",
|
|
395
|
+
help="Clean build artifacts before building",
|
|
396
|
+
)
|
|
397
|
+
build_parser.add_argument(
|
|
398
|
+
"-v",
|
|
399
|
+
"--verbose",
|
|
400
|
+
action="store_true",
|
|
401
|
+
help="Show verbose build output",
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Deploy command
|
|
405
|
+
deploy_parser = subparsers.add_parser(
|
|
406
|
+
"deploy",
|
|
407
|
+
help="Deploy firmware to embedded target",
|
|
408
|
+
)
|
|
409
|
+
deploy_parser.add_argument(
|
|
410
|
+
"project_dir",
|
|
411
|
+
nargs="?",
|
|
412
|
+
type=Path,
|
|
413
|
+
default=Path.cwd(),
|
|
414
|
+
help="Project directory (default: current directory)",
|
|
415
|
+
)
|
|
416
|
+
deploy_parser.add_argument(
|
|
417
|
+
"-e",
|
|
418
|
+
"--environment",
|
|
419
|
+
default=None,
|
|
420
|
+
help="Build environment (default: auto-detect from platformio.ini)",
|
|
421
|
+
)
|
|
422
|
+
deploy_parser.add_argument(
|
|
423
|
+
"-p",
|
|
424
|
+
"--port",
|
|
425
|
+
default=None,
|
|
426
|
+
help="Serial port (default: auto-detect)",
|
|
427
|
+
)
|
|
428
|
+
deploy_parser.add_argument(
|
|
429
|
+
"-c",
|
|
430
|
+
"--clean",
|
|
431
|
+
action="store_true",
|
|
432
|
+
help="Clean build artifacts before building",
|
|
433
|
+
)
|
|
434
|
+
deploy_parser.add_argument(
|
|
435
|
+
"--monitor",
|
|
436
|
+
default=None,
|
|
437
|
+
help="Monitor flags to pass after deployment (e.g., '--timeout 60 --halt-on-success \"TEST PASSED\"')",
|
|
438
|
+
)
|
|
439
|
+
deploy_parser.add_argument(
|
|
440
|
+
"-v",
|
|
441
|
+
"--verbose",
|
|
442
|
+
action="store_true",
|
|
443
|
+
help="Show verbose output",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Monitor command
|
|
447
|
+
monitor_parser = subparsers.add_parser(
|
|
448
|
+
"monitor",
|
|
449
|
+
help="Monitor serial output from embedded target",
|
|
450
|
+
)
|
|
451
|
+
monitor_parser.add_argument(
|
|
452
|
+
"project_dir",
|
|
453
|
+
nargs="?",
|
|
454
|
+
type=Path,
|
|
455
|
+
default=Path.cwd(),
|
|
456
|
+
help="Project directory (default: current directory)",
|
|
457
|
+
)
|
|
458
|
+
monitor_parser.add_argument(
|
|
459
|
+
"-e",
|
|
460
|
+
"--environment",
|
|
461
|
+
default=None,
|
|
462
|
+
help="Build environment (default: auto-detect from platformio.ini)",
|
|
463
|
+
)
|
|
464
|
+
monitor_parser.add_argument(
|
|
465
|
+
"-p",
|
|
466
|
+
"--port",
|
|
467
|
+
default=None,
|
|
468
|
+
help="Serial port (default: auto-detect)",
|
|
469
|
+
)
|
|
470
|
+
monitor_parser.add_argument(
|
|
471
|
+
"-b",
|
|
472
|
+
"--baud",
|
|
473
|
+
default=115200,
|
|
474
|
+
type=int,
|
|
475
|
+
help="Baud rate (default: 115200)",
|
|
476
|
+
)
|
|
477
|
+
monitor_parser.add_argument(
|
|
478
|
+
"-t",
|
|
479
|
+
"--timeout",
|
|
480
|
+
default=None,
|
|
481
|
+
type=int,
|
|
482
|
+
help="Timeout in seconds (default: no timeout)",
|
|
483
|
+
)
|
|
484
|
+
monitor_parser.add_argument(
|
|
485
|
+
"--halt-on-error",
|
|
486
|
+
default=None,
|
|
487
|
+
help="Pattern that triggers error exit (regex)",
|
|
488
|
+
)
|
|
489
|
+
monitor_parser.add_argument(
|
|
490
|
+
"--halt-on-success",
|
|
491
|
+
default=None,
|
|
492
|
+
help="Pattern that triggers success exit (regex)",
|
|
493
|
+
)
|
|
494
|
+
monitor_parser.add_argument(
|
|
495
|
+
"--expect",
|
|
496
|
+
default=None,
|
|
497
|
+
help="Expected pattern - checked at timeout/success, exit 0 if found, 1 if not (regex)",
|
|
498
|
+
)
|
|
499
|
+
monitor_parser.add_argument(
|
|
500
|
+
"-v",
|
|
501
|
+
"--verbose",
|
|
502
|
+
action="store_true",
|
|
503
|
+
help="Show verbose output",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Daemon command
|
|
507
|
+
daemon_parser = subparsers.add_parser(
|
|
508
|
+
"daemon",
|
|
509
|
+
help="Manage the fbuild daemon",
|
|
510
|
+
)
|
|
511
|
+
daemon_parser.add_argument(
|
|
512
|
+
"action",
|
|
513
|
+
choices=["status", "stop", "restart"],
|
|
514
|
+
help="Daemon action to perform",
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# Parse arguments
|
|
518
|
+
parsed_args = parser.parse_args()
|
|
519
|
+
|
|
520
|
+
# If no command specified, show help
|
|
521
|
+
if not parsed_args.command:
|
|
522
|
+
parser.print_help()
|
|
523
|
+
sys.exit(0)
|
|
524
|
+
|
|
525
|
+
# Validate project directory exists
|
|
526
|
+
if hasattr(parsed_args, "project_dir"):
|
|
527
|
+
PathValidator.validate_project_dir(parsed_args.project_dir)
|
|
528
|
+
|
|
529
|
+
# Execute command
|
|
530
|
+
if parsed_args.command == "build":
|
|
531
|
+
build_args = BuildArgs(
|
|
532
|
+
project_dir=parsed_args.project_dir,
|
|
533
|
+
environment=parsed_args.environment,
|
|
534
|
+
clean=parsed_args.clean,
|
|
535
|
+
verbose=parsed_args.verbose,
|
|
536
|
+
)
|
|
537
|
+
build_command(build_args)
|
|
538
|
+
elif parsed_args.command == "deploy":
|
|
539
|
+
deploy_args = DeployArgs(
|
|
540
|
+
project_dir=parsed_args.project_dir,
|
|
541
|
+
environment=parsed_args.environment,
|
|
542
|
+
port=parsed_args.port,
|
|
543
|
+
clean=parsed_args.clean,
|
|
544
|
+
monitor=parsed_args.monitor,
|
|
545
|
+
verbose=parsed_args.verbose,
|
|
546
|
+
)
|
|
547
|
+
deploy_command(deploy_args)
|
|
548
|
+
elif parsed_args.command == "monitor":
|
|
549
|
+
monitor_args = MonitorArgs(
|
|
550
|
+
project_dir=parsed_args.project_dir,
|
|
551
|
+
environment=parsed_args.environment,
|
|
552
|
+
port=parsed_args.port,
|
|
553
|
+
baud=parsed_args.baud,
|
|
554
|
+
timeout=parsed_args.timeout,
|
|
555
|
+
halt_on_error=parsed_args.halt_on_error,
|
|
556
|
+
halt_on_success=parsed_args.halt_on_success,
|
|
557
|
+
expect=parsed_args.expect,
|
|
558
|
+
verbose=parsed_args.verbose,
|
|
559
|
+
)
|
|
560
|
+
monitor_command(monitor_args)
|
|
561
|
+
elif parsed_args.command == "daemon":
|
|
562
|
+
daemon_command(parsed_args.action)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
if __name__ == "__main__":
|
|
566
|
+
main()
|