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,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request Processor - Template method pattern for daemon request handling.
|
|
3
|
+
|
|
4
|
+
This module provides the RequestProcessor abstract base class which implements
|
|
5
|
+
the Template Method pattern to eliminate code duplication across build, deploy,
|
|
6
|
+
and monitor request handlers. It handles all common concerns (lock management,
|
|
7
|
+
status updates, error handling) while allowing subclasses to implement only
|
|
8
|
+
the operation-specific business logic.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from contextlib import ExitStack
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from fbuild.daemon.lock_manager import LockAcquisitionError
|
|
18
|
+
from fbuild.daemon.messages import DaemonState, OperationType
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from fbuild.daemon.daemon_context import DaemonContext
|
|
22
|
+
from fbuild.daemon.messages import BuildRequest, DeployRequest, MonitorRequest
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequestProcessor(ABC):
|
|
26
|
+
"""Abstract base class for processing daemon requests.
|
|
27
|
+
|
|
28
|
+
This class implements the Template Method pattern to handle all common
|
|
29
|
+
concerns of request processing:
|
|
30
|
+
- Request validation
|
|
31
|
+
- Lock acquisition (port and/or project locks)
|
|
32
|
+
- Status updates (started, in-progress, completed, failed)
|
|
33
|
+
- Error handling and cleanup
|
|
34
|
+
- Operation tracking
|
|
35
|
+
|
|
36
|
+
Subclasses only need to implement:
|
|
37
|
+
- get_operation_type(): Return the OperationType
|
|
38
|
+
- get_required_locks(): Specify which locks are needed
|
|
39
|
+
- execute_operation(): Implement the actual business logic
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> class BuildRequestProcessor(RequestProcessor):
|
|
43
|
+
... def get_operation_type(self) -> OperationType:
|
|
44
|
+
... return OperationType.BUILD
|
|
45
|
+
...
|
|
46
|
+
... def get_required_locks(self, request, context):
|
|
47
|
+
... return {"project": request.project_dir}
|
|
48
|
+
...
|
|
49
|
+
... def execute_operation(self, request, context):
|
|
50
|
+
... # Actual build logic here
|
|
51
|
+
... result = build_project(request.project_dir)
|
|
52
|
+
... return result.success
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def process_request(
|
|
56
|
+
self,
|
|
57
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
58
|
+
context: "DaemonContext",
|
|
59
|
+
) -> bool:
|
|
60
|
+
"""Process a request using the template method pattern.
|
|
61
|
+
|
|
62
|
+
This is the main entry point that coordinates the entire request
|
|
63
|
+
processing lifecycle. It handles all boilerplate while calling
|
|
64
|
+
abstract methods for operation-specific logic.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
request: The request to process (BuildRequest, DeployRequest, or MonitorRequest)
|
|
68
|
+
context: The daemon context containing all subsystems
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if operation succeeded, False otherwise
|
|
72
|
+
|
|
73
|
+
Lifecycle:
|
|
74
|
+
1. Validate request
|
|
75
|
+
2. Acquire required locks (project and/or port)
|
|
76
|
+
3. Mark operation as in progress
|
|
77
|
+
4. Update status to starting state
|
|
78
|
+
5. Execute operation (abstract method)
|
|
79
|
+
6. Update status based on result
|
|
80
|
+
7. Release locks and cleanup
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> processor = BuildRequestProcessor()
|
|
84
|
+
>>> success = processor.process_request(build_request, daemon_context)
|
|
85
|
+
"""
|
|
86
|
+
logging.info(f"Processing {self.get_operation_type().value} request {request.request_id}: " + f"env={request.environment}, project={request.project_dir}")
|
|
87
|
+
|
|
88
|
+
# Validate request
|
|
89
|
+
if not self.validate_request(request, context):
|
|
90
|
+
self._update_status(
|
|
91
|
+
context,
|
|
92
|
+
DaemonState.FAILED,
|
|
93
|
+
"Request validation failed",
|
|
94
|
+
request=request,
|
|
95
|
+
exit_code=1,
|
|
96
|
+
)
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# Use ExitStack to manage multiple locks as context managers
|
|
100
|
+
# We store the result to return after lock release and status update
|
|
101
|
+
result: bool = False
|
|
102
|
+
exception_to_reraise: BaseException | None = None
|
|
103
|
+
|
|
104
|
+
with ExitStack() as lock_stack:
|
|
105
|
+
# Acquire required locks
|
|
106
|
+
if not self._acquire_locks(request, context, lock_stack):
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Mark operation in progress
|
|
111
|
+
with context.operation_lock:
|
|
112
|
+
context.operation_in_progress = True
|
|
113
|
+
|
|
114
|
+
# Update status to starting state
|
|
115
|
+
self._update_status(
|
|
116
|
+
context,
|
|
117
|
+
self.get_starting_state(),
|
|
118
|
+
self.get_starting_message(request),
|
|
119
|
+
request=request,
|
|
120
|
+
request_started_at=time.time(),
|
|
121
|
+
operation_type=self.get_operation_type(),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Execute the operation (implemented by subclass)
|
|
125
|
+
success = self.execute_operation(request, context)
|
|
126
|
+
|
|
127
|
+
# Update final status
|
|
128
|
+
if success:
|
|
129
|
+
self._update_status(
|
|
130
|
+
context,
|
|
131
|
+
DaemonState.COMPLETED,
|
|
132
|
+
self.get_success_message(request),
|
|
133
|
+
request=request,
|
|
134
|
+
exit_code=0,
|
|
135
|
+
operation_in_progress=False,
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
self._update_status(
|
|
139
|
+
context,
|
|
140
|
+
DaemonState.FAILED,
|
|
141
|
+
self.get_failure_message(request),
|
|
142
|
+
request=request,
|
|
143
|
+
exit_code=1,
|
|
144
|
+
operation_in_progress=False,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
result = success
|
|
148
|
+
|
|
149
|
+
except KeyboardInterrupt as ki:
|
|
150
|
+
import _thread
|
|
151
|
+
|
|
152
|
+
_thread.interrupt_main()
|
|
153
|
+
exception_to_reraise = ki
|
|
154
|
+
except Exception as e:
|
|
155
|
+
import traceback
|
|
156
|
+
|
|
157
|
+
logging.error(f"{self.get_operation_type().value} exception: {e}")
|
|
158
|
+
logging.error(f"Traceback:\n{traceback.format_exc()}")
|
|
159
|
+
self._update_status(
|
|
160
|
+
context,
|
|
161
|
+
DaemonState.FAILED,
|
|
162
|
+
f"{self.get_operation_type().value} exception: {e}",
|
|
163
|
+
request=request,
|
|
164
|
+
exit_code=1,
|
|
165
|
+
operation_in_progress=False,
|
|
166
|
+
)
|
|
167
|
+
result = False
|
|
168
|
+
finally:
|
|
169
|
+
# Mark operation complete
|
|
170
|
+
with context.operation_lock:
|
|
171
|
+
context.operation_in_progress = False
|
|
172
|
+
|
|
173
|
+
# After locks are released (ExitStack has exited), update status to reflect
|
|
174
|
+
# the new lock state. This ensures the status file shows locks as released.
|
|
175
|
+
# We read the current status and re-write it to capture the updated lock state.
|
|
176
|
+
try:
|
|
177
|
+
current_status = context.status_manager.read_status()
|
|
178
|
+
context.status_manager.update_status(
|
|
179
|
+
state=current_status.state,
|
|
180
|
+
message=current_status.message,
|
|
181
|
+
environment=getattr(current_status, "environment", request.environment),
|
|
182
|
+
project_dir=getattr(current_status, "project_dir", request.project_dir),
|
|
183
|
+
request_id=getattr(current_status, "request_id", request.request_id),
|
|
184
|
+
caller_pid=getattr(current_status, "caller_pid", request.caller_pid),
|
|
185
|
+
caller_cwd=getattr(current_status, "caller_cwd", request.caller_cwd),
|
|
186
|
+
exit_code=getattr(current_status, "exit_code", None),
|
|
187
|
+
)
|
|
188
|
+
except KeyboardInterrupt as ke:
|
|
189
|
+
import _thread
|
|
190
|
+
|
|
191
|
+
_thread.interrupt_main()
|
|
192
|
+
raise ke
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logging.warning(f"Failed to update status after lock release: {e}")
|
|
195
|
+
|
|
196
|
+
# Re-raise KeyboardInterrupt if it was caught
|
|
197
|
+
if exception_to_reraise is not None:
|
|
198
|
+
import _thread
|
|
199
|
+
|
|
200
|
+
_thread.interrupt_main()
|
|
201
|
+
raise exception_to_reraise
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
@abstractmethod
|
|
206
|
+
def get_operation_type(self) -> OperationType:
|
|
207
|
+
"""Get the operation type for this processor.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
OperationType enum value (BUILD, DEPLOY, MONITOR, etc.)
|
|
211
|
+
"""
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def get_required_locks(
|
|
216
|
+
self,
|
|
217
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
218
|
+
context: "DaemonContext",
|
|
219
|
+
) -> dict[str, str]:
|
|
220
|
+
"""Specify which locks are required for this operation.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dictionary with lock types as keys and resource identifiers as values.
|
|
224
|
+
Valid keys: "project" (for project_dir), "port" (for serial port)
|
|
225
|
+
|
|
226
|
+
Examples:
|
|
227
|
+
Build only needs project lock:
|
|
228
|
+
return {"project": request.project_dir}
|
|
229
|
+
|
|
230
|
+
Deploy needs both project and port locks:
|
|
231
|
+
return {"project": request.project_dir, "port": request.port}
|
|
232
|
+
|
|
233
|
+
Monitor only needs port lock:
|
|
234
|
+
return {"port": request.port}
|
|
235
|
+
"""
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def execute_operation(
|
|
240
|
+
self,
|
|
241
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
242
|
+
context: "DaemonContext",
|
|
243
|
+
) -> bool:
|
|
244
|
+
"""Execute the actual operation logic.
|
|
245
|
+
|
|
246
|
+
This is the core business logic that subclasses must implement.
|
|
247
|
+
All boilerplate (locks, status updates, error handling) is handled
|
|
248
|
+
by the base class.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
request: The request being processed
|
|
252
|
+
context: The daemon context with all subsystems
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if operation succeeded, False otherwise
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
>>> def execute_operation(self, request, context):
|
|
259
|
+
... # Build the project
|
|
260
|
+
... orchestrator = BuildOrchestratorAVR(verbose=request.verbose)
|
|
261
|
+
... result = orchestrator.build(
|
|
262
|
+
... project_dir=Path(request.project_dir),
|
|
263
|
+
... env_name=request.environment,
|
|
264
|
+
... clean=request.clean_build,
|
|
265
|
+
... )
|
|
266
|
+
... return result.success
|
|
267
|
+
"""
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
def validate_request(
|
|
271
|
+
self,
|
|
272
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
273
|
+
context: "DaemonContext",
|
|
274
|
+
) -> bool:
|
|
275
|
+
"""Validate the request before processing.
|
|
276
|
+
|
|
277
|
+
Default implementation always returns True. Override to add validation.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
request: The request to validate
|
|
281
|
+
context: The daemon context
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if request is valid, False otherwise
|
|
285
|
+
"""
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
def get_starting_state(self) -> DaemonState:
|
|
289
|
+
"""Get the daemon state when operation starts.
|
|
290
|
+
|
|
291
|
+
Default implementation uses BUILDING. Override for different operations.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
DaemonState enum value for operation start
|
|
295
|
+
"""
|
|
296
|
+
operation_type = self.get_operation_type()
|
|
297
|
+
if operation_type == OperationType.BUILD:
|
|
298
|
+
return DaemonState.BUILDING
|
|
299
|
+
elif operation_type == OperationType.DEPLOY or operation_type == OperationType.BUILD_AND_DEPLOY:
|
|
300
|
+
return DaemonState.DEPLOYING
|
|
301
|
+
elif operation_type == OperationType.MONITOR:
|
|
302
|
+
return DaemonState.MONITORING
|
|
303
|
+
else:
|
|
304
|
+
return DaemonState.BUILDING
|
|
305
|
+
|
|
306
|
+
def get_starting_message(self, request: "BuildRequest | DeployRequest | MonitorRequest") -> str:
|
|
307
|
+
"""Get the status message when operation starts.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
request: The request being processed
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Human-readable status message
|
|
314
|
+
"""
|
|
315
|
+
operation_type = self.get_operation_type()
|
|
316
|
+
if operation_type == OperationType.BUILD:
|
|
317
|
+
return f"Building {request.environment}"
|
|
318
|
+
elif operation_type == OperationType.DEPLOY or operation_type == OperationType.BUILD_AND_DEPLOY:
|
|
319
|
+
return f"Deploying {request.environment}"
|
|
320
|
+
elif operation_type == OperationType.MONITOR:
|
|
321
|
+
return f"Monitoring {request.environment}"
|
|
322
|
+
else:
|
|
323
|
+
return f"Processing {request.environment}"
|
|
324
|
+
|
|
325
|
+
def get_success_message(self, request: "BuildRequest | DeployRequest | MonitorRequest") -> str:
|
|
326
|
+
"""Get the status message on success.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
request: The request that was processed
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Human-readable success message
|
|
333
|
+
"""
|
|
334
|
+
operation_type = self.get_operation_type()
|
|
335
|
+
if operation_type == OperationType.BUILD:
|
|
336
|
+
return "Build successful"
|
|
337
|
+
elif operation_type == OperationType.DEPLOY or operation_type == OperationType.BUILD_AND_DEPLOY:
|
|
338
|
+
return "Deploy successful"
|
|
339
|
+
elif operation_type == OperationType.MONITOR:
|
|
340
|
+
return "Monitor completed"
|
|
341
|
+
else:
|
|
342
|
+
return "Operation successful"
|
|
343
|
+
|
|
344
|
+
def get_failure_message(self, request: "BuildRequest | DeployRequest | MonitorRequest") -> str:
|
|
345
|
+
"""Get the status message on failure.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
request: The request that failed
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Human-readable failure message
|
|
352
|
+
"""
|
|
353
|
+
operation_type = self.get_operation_type()
|
|
354
|
+
if operation_type == OperationType.BUILD:
|
|
355
|
+
return "Build failed"
|
|
356
|
+
elif operation_type == OperationType.DEPLOY or operation_type == OperationType.BUILD_AND_DEPLOY:
|
|
357
|
+
return "Deploy failed"
|
|
358
|
+
elif operation_type == OperationType.MONITOR:
|
|
359
|
+
return "Monitor failed"
|
|
360
|
+
else:
|
|
361
|
+
return "Operation failed"
|
|
362
|
+
|
|
363
|
+
def _acquire_locks(
|
|
364
|
+
self,
|
|
365
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
366
|
+
context: "DaemonContext",
|
|
367
|
+
lock_stack: ExitStack,
|
|
368
|
+
) -> bool:
|
|
369
|
+
"""Acquire all required locks for the operation.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
request: The request being processed
|
|
373
|
+
context: The daemon context
|
|
374
|
+
lock_stack: ExitStack to manage lock lifetimes
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
True if all locks acquired, False if any lock is unavailable
|
|
378
|
+
"""
|
|
379
|
+
required_locks = self.get_required_locks(request, context)
|
|
380
|
+
operation_type = self.get_operation_type()
|
|
381
|
+
operation_desc = f"{operation_type.value} for {request.environment}"
|
|
382
|
+
|
|
383
|
+
# Acquire project lock if needed
|
|
384
|
+
if "project" in required_locks:
|
|
385
|
+
project_dir = required_locks["project"]
|
|
386
|
+
try:
|
|
387
|
+
lock_stack.enter_context(
|
|
388
|
+
context.lock_manager.acquire_project_lock(
|
|
389
|
+
project_dir,
|
|
390
|
+
blocking=False,
|
|
391
|
+
operation_id=request.request_id,
|
|
392
|
+
description=operation_desc,
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
except LockAcquisitionError as e:
|
|
396
|
+
logging.warning(f"Project lock unavailable: {e}")
|
|
397
|
+
self._update_status(
|
|
398
|
+
context,
|
|
399
|
+
DaemonState.FAILED,
|
|
400
|
+
str(e),
|
|
401
|
+
request=request,
|
|
402
|
+
)
|
|
403
|
+
return False
|
|
404
|
+
|
|
405
|
+
# Acquire port lock if needed
|
|
406
|
+
if "port" in required_locks:
|
|
407
|
+
port = required_locks["port"]
|
|
408
|
+
if port: # Only acquire if port is not None/empty
|
|
409
|
+
try:
|
|
410
|
+
lock_stack.enter_context(
|
|
411
|
+
context.lock_manager.acquire_port_lock(
|
|
412
|
+
port,
|
|
413
|
+
blocking=False,
|
|
414
|
+
operation_id=request.request_id,
|
|
415
|
+
description=operation_desc,
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
except LockAcquisitionError as e:
|
|
419
|
+
logging.warning(f"Port lock unavailable: {e}")
|
|
420
|
+
self._update_status(
|
|
421
|
+
context,
|
|
422
|
+
DaemonState.FAILED,
|
|
423
|
+
str(e),
|
|
424
|
+
request=request,
|
|
425
|
+
)
|
|
426
|
+
return False
|
|
427
|
+
|
|
428
|
+
return True
|
|
429
|
+
|
|
430
|
+
def _update_status(
|
|
431
|
+
self,
|
|
432
|
+
context: "DaemonContext",
|
|
433
|
+
state: DaemonState,
|
|
434
|
+
message: str,
|
|
435
|
+
request: "BuildRequest | DeployRequest | MonitorRequest",
|
|
436
|
+
**kwargs: Any,
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Update daemon status file.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
context: The daemon context
|
|
442
|
+
state: New daemon state
|
|
443
|
+
message: Status message
|
|
444
|
+
request: The request being processed
|
|
445
|
+
**kwargs: Additional fields for status update
|
|
446
|
+
"""
|
|
447
|
+
# Use the status manager from context
|
|
448
|
+
context.status_manager.update_status(
|
|
449
|
+
state=state,
|
|
450
|
+
message=message,
|
|
451
|
+
environment=request.environment,
|
|
452
|
+
project_dir=request.project_dir,
|
|
453
|
+
request_id=request.request_id,
|
|
454
|
+
caller_pid=request.caller_pid,
|
|
455
|
+
caller_cwd=request.caller_cwd,
|
|
456
|
+
**kwargs,
|
|
457
|
+
)
|