lager-cli 0.46.0__tar.gz → 0.46.2__tar.gz
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.
- {lager_cli-0.46.0/lager_cli.egg-info → lager_cli-0.46.2}/PKG-INFO +1 -1
- {lager_cli-0.46.0 → lager_cli-0.46.2}/__init__.py +1 -1
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/nets.py +95 -3
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/exec_.py +92 -1
- {lager_cli-0.46.0 → lager_cli-0.46.2/lager_cli.egg-info}/PKG-INFO +1 -1
- {lager_cli-0.46.0 → lager_cli-0.46.2}/LICENSE +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/MANIFEST.in +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/README.md +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/__main__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/address_utils.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/battery/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/battery/battery_tui.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/battery/websocket_client.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/box_storage.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_device_identity.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_host_ops.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_mount_prep.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_pip_validation.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_shim_verbs.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/_ssh.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/box_group.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/boxes.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/config.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/diagnose.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/dut.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/hello.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/instruments.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/labjack_pins.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/lock.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/net_tui.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/ssh.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/box/ssh_setup.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/ble.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/blufi.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/i2c.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/router.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/spi.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/uart.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/usb.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/websocket_client.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/communication/wifi.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/arm.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/commands.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/net_cache.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/rtt_websocket_client.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/service_client.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/service_helper.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/debug/tunnel.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/devenv.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/development/python.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/adc.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/dac.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/energy.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/gpi.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/gpo.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/logic.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/scope.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/thermocouple.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/measurement/watt.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/battery.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/battery_model_csv.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/eload.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/solar.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/power/supply.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/_host_cli.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/binaries.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/defaults.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/install.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/install_wheel.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/login.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/logs.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/uninstall.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/update.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/commands/utility/webcam.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/config.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/ci_detection.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/constants.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/core.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/error_handlers.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/context/session.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/group_usage.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/matchers.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/net_group.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/net_helpers.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/net_storage.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/param_types.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/ssh_utils.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/utils.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/version_skew.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/core/ws_diagnose.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/configure_docker_dns.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/configure_docker_dns.sh +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/setup_and_deploy_box.sh +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/scripts/setup_ssh_key.sh +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/security/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/deployment/security/secure_box_firewall.sh +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/errors.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/exceptions.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/gateway_auth.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/box_config.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/measurement/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/measurement/scope.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/measurement/scope_stream.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/power/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/impl/power/enable_disable.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/lager_cli.egg-info/SOURCES.txt +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/lager_cli.egg-info/dependency_links.txt +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/lager_cli.egg-info/entry_points.txt +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/lager_cli.egg-info/requires.txt +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/lager_cli.egg-info/top_level.txt +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/main.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/pyproject.toml +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/safe_unpickle.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/setup.cfg +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/setup.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/simple_hdlc.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/sort_utils.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/status.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/supply/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/supply/supply_tui.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/supply/websocket_client.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/core/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/core/executor.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/main.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/completer.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/display.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/logo.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/repl.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/terminal/ui/themes.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_box_lager_imports.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_box_storage.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_gateway_auth.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_gateway_callsites.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_host_cli.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_io_imports.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/tests/test_update_gate.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/update_check.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRC16.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRC16DNP.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRC16Kermit.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRC16SICK.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRC32.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/CRCCCITT.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/PyCRC/__init__.py +0 -0
- {lager_cli-0.46.0 → lager_cli-0.46.2}/vendor/__init__.py +0 -0
|
@@ -422,6 +422,33 @@ def canonical_instrument(instrument):
|
|
|
422
422
|
return _INSTRUMENT_ALIASES.get(instrument, instrument)
|
|
423
423
|
|
|
424
424
|
|
|
425
|
+
# Pins whose rejection has a better answer than "pick another channel". On a
|
|
426
|
+
# U3 these four are the fixed high-voltage analog inputs, so a user reaching
|
|
427
|
+
# for gpio there wants an adc net on the SAME physical pins, under the name
|
|
428
|
+
# they carry in analog mode. The scanner stopped advertising them as gpio
|
|
429
|
+
# (box/lager/http_handlers/usb_scanner.py), and this is what turns the
|
|
430
|
+
# resulting rejection into a next step rather than a dead end. Raw DIO
|
|
431
|
+
# numbers are included because the U3 drivers accept either spelling.
|
|
432
|
+
_U3_HV_PINS = {"FIO0", "FIO1", "FIO2", "FIO3", "0", "1", "2", "3"}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _channel_rejection_hint(instrument, role, channel):
|
|
436
|
+
"""An extra line for channel rejections that have a specific remedy.
|
|
437
|
+
|
|
438
|
+
Returns None when the generic "here are the valid channels" line is the
|
|
439
|
+
whole story, which is the case for every instrument but the U3.
|
|
440
|
+
"""
|
|
441
|
+
if (canonical_instrument(instrument) == "LabJack_U3"
|
|
442
|
+
and role == "gpio"
|
|
443
|
+
and str(channel).upper() in _U3_HV_PINS):
|
|
444
|
+
return (
|
|
445
|
+
"FIO0-FIO3 on a LabJack U3 are fixed high-voltage analog inputs "
|
|
446
|
+
"and are never digital I/O. Read them with an adc net on "
|
|
447
|
+
"AIN0-AIN3, the same physical pins."
|
|
448
|
+
)
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
|
|
425
452
|
# Chips that can run in exactly one mode at a time, across ALL roles. The
|
|
426
453
|
# canonical case is the FT232H: one physical channel, hardware-multiplexed
|
|
427
454
|
# between MPSSE (spi/i2c/gpio/debug) and async-serial (uart). Once the user
|
|
@@ -1325,6 +1352,18 @@ def add_cmd(ctx, name, role, channel, address, box, jlink_script, openocd_config
|
|
|
1325
1352
|
f"The channel '{channel}' is not valid for role '{role}' on the instrument at {address}.",
|
|
1326
1353
|
fg="red",
|
|
1327
1354
|
)
|
|
1355
|
+
# Naming what IS valid, the way the role rejection below
|
|
1356
|
+
# does. Both lines stay on stdout with the one above them:
|
|
1357
|
+
# cli/setup.py pins only click>=8.1.2, and 8.2's CliRunner
|
|
1358
|
+
# stopped folding stderr into result.output, so moving them
|
|
1359
|
+
# would change what every existing CLI test can see.
|
|
1360
|
+
click.secho(
|
|
1361
|
+
f"Valid {role} channels for {instrument}: "
|
|
1362
|
+
f"{', '.join(str(ch) for ch in role_chans)}"
|
|
1363
|
+
)
|
|
1364
|
+
hint = _channel_rejection_hint(instrument, role, channel)
|
|
1365
|
+
if hint:
|
|
1366
|
+
click.secho(hint, fg="yellow")
|
|
1328
1367
|
ctx.exit(1)
|
|
1329
1368
|
|
|
1330
1369
|
# ─────────── unique net name (regardless of type) ────────────────
|
|
@@ -2040,7 +2079,13 @@ def create_batch_cmd(ctx: click.Context, json_file, box: str | None) -> None:
|
|
|
2040
2079
|
# net never need the scan.
|
|
2041
2080
|
_instrument_cache: list | None = None
|
|
2042
2081
|
|
|
2043
|
-
def
|
|
2082
|
+
def _get_scanned_device(address: str) -> dict | None:
|
|
2083
|
+
"""The scanned record for *address*, or None when it is not present.
|
|
2084
|
+
|
|
2085
|
+
Shares the one lazy scan with _get_instrument_from_address. A batch
|
|
2086
|
+
run against a box that is down, or naming hardware that is unplugged,
|
|
2087
|
+
gets None here and keeps its pre-0.46.1 behavior.
|
|
2088
|
+
"""
|
|
2044
2089
|
nonlocal _instrument_cache
|
|
2045
2090
|
if _instrument_cache is None:
|
|
2046
2091
|
try:
|
|
@@ -2049,11 +2094,19 @@ def create_batch_cmd(ctx: click.Context, json_file, box: str | None) -> None:
|
|
|
2049
2094
|
_instrument_cache = []
|
|
2050
2095
|
for inst in _instrument_cache:
|
|
2051
2096
|
if inst.get("address") == address:
|
|
2052
|
-
return inst
|
|
2053
|
-
return
|
|
2097
|
+
return inst
|
|
2098
|
+
return None
|
|
2099
|
+
|
|
2100
|
+
def _get_instrument_from_address(address: str, fallback_instrument: str = "Unknown") -> str:
|
|
2101
|
+
inst = _get_scanned_device(address)
|
|
2102
|
+
return inst.get("name", "Unknown") if inst else fallback_instrument
|
|
2054
2103
|
|
|
2055
2104
|
# Validate and normalize each net in the batch
|
|
2056
2105
|
normalized_nets = []
|
|
2106
|
+
# Channel rejections are collected, not raised in the loop: _save_nets_batch
|
|
2107
|
+
# is all-or-nothing, so telling the user about record 3 and hiding record 7
|
|
2108
|
+
# would cost them a second run to find the next one.
|
|
2109
|
+
channel_errors: list[str] = []
|
|
2057
2110
|
|
|
2058
2111
|
for i, net_data in enumerate(nets_data):
|
|
2059
2112
|
if not isinstance(net_data, dict):
|
|
@@ -2080,8 +2133,47 @@ def create_batch_cmd(ctx: click.Context, json_file, box: str | None) -> None:
|
|
|
2080
2133
|
"pin": net_data["channel"],
|
|
2081
2134
|
"instrument": instrument
|
|
2082
2135
|
}
|
|
2136
|
+
|
|
2137
|
+
# Reject a channel the present hardware does not offer, the way
|
|
2138
|
+
# `nets add` does. Deliberately narrow:
|
|
2139
|
+
#
|
|
2140
|
+
# * only when the device is actually in the scan. This command is
|
|
2141
|
+
# built to provision a box whose instruments are absent or named
|
|
2142
|
+
# by bare IP, and _get_instrument_from_address already falls back
|
|
2143
|
+
# to "Unknown" for those. Rejecting what we cannot see would break
|
|
2144
|
+
# that, fleet-wide, for hardware unrelated to the U3.
|
|
2145
|
+
# * never for uart, whose channels are per-device tty paths rather
|
|
2146
|
+
# than the static list (see uart_channel_paths).
|
|
2147
|
+
#
|
|
2148
|
+
# What it does catch is the case this exists for: a hand-written
|
|
2149
|
+
# record naming a pin the instrument cannot drive, which used to be
|
|
2150
|
+
# saved without complaint and fail at first use.
|
|
2151
|
+
batch_role = normalized_net["role"]
|
|
2152
|
+
if batch_role != "uart":
|
|
2153
|
+
dev = _get_scanned_device(net_data["address"])
|
|
2154
|
+
role_chans = (dev.get("channels") or {}).get(batch_role) if dev else None
|
|
2155
|
+
if isinstance(role_chans, list) and role_chans:
|
|
2156
|
+
if str(net_data["channel"]) not in [str(ch) for ch in role_chans]:
|
|
2157
|
+
detail = (
|
|
2158
|
+
f"Net {i+1} ('{net_data['name']}'): channel "
|
|
2159
|
+
f"'{net_data['channel']}' is not valid for role "
|
|
2160
|
+
f"'{batch_role}' on {instrument}. Valid: "
|
|
2161
|
+
f"{', '.join(str(ch) for ch in role_chans)}"
|
|
2162
|
+
)
|
|
2163
|
+
hint = _channel_rejection_hint(
|
|
2164
|
+
instrument, batch_role, net_data["channel"])
|
|
2165
|
+
if hint:
|
|
2166
|
+
detail = f"{detail}\n {hint}"
|
|
2167
|
+
channel_errors.append(detail)
|
|
2168
|
+
|
|
2083
2169
|
normalized_nets.append(normalized_net)
|
|
2084
2170
|
|
|
2171
|
+
if channel_errors:
|
|
2172
|
+
for detail in channel_errors:
|
|
2173
|
+
click.secho(detail, fg="red", err=True)
|
|
2174
|
+
click.secho("No nets were saved.", fg="red", err=True)
|
|
2175
|
+
ctx.exit(1)
|
|
2176
|
+
|
|
2085
2177
|
# Use batch save for better performance
|
|
2086
2178
|
_save_nets_batch(ctx, resolved_box, normalized_nets)
|
|
2087
2179
|
|
|
@@ -16,6 +16,7 @@ import platform
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
import click
|
|
18
18
|
from ...config import get_devenv_json, write_lager_json, LAGER_CONFIG_FILE_NAME, get_global_config_file_path, devenv_config_list, expand_devenv_path
|
|
19
|
+
from ...context.ci_detection import get_ci_environment, is_container_ci
|
|
19
20
|
from ...core.param_types import EnvVarType
|
|
20
21
|
|
|
21
22
|
|
|
@@ -150,6 +151,81 @@ def _run_command_local(section, path, cmd_to_run, mount, extra_args, debug, inte
|
|
|
150
151
|
return 1
|
|
151
152
|
|
|
152
153
|
|
|
154
|
+
# Config keys that only apply when we start a container. A .lager is shared between a
|
|
155
|
+
# developer's machine and CI, so carrying these is normal and not worth a warning on every
|
|
156
|
+
# CI run -- they are reported under --verbose instead.
|
|
157
|
+
_DOCKER_ONLY_CONFIG_KEYS = ('network', 'ports', 'platform', 'macaddr', 'hostname',
|
|
158
|
+
'volumes', 'user', 'group')
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _warn_docker_only_options(section, mount, volumes, user, group, debug):
|
|
162
|
+
"""
|
|
163
|
+
Name the options that cannot take effect when the command runs in place.
|
|
164
|
+
|
|
165
|
+
Flags passed on the command line are warned about unconditionally: the caller asked
|
|
166
|
+
for something this path cannot do. Config keys are quieter, for the reason above.
|
|
167
|
+
"""
|
|
168
|
+
ignored = []
|
|
169
|
+
if mount:
|
|
170
|
+
ignored.append('--mount')
|
|
171
|
+
if volumes:
|
|
172
|
+
ignored.append('--volume')
|
|
173
|
+
if user:
|
|
174
|
+
ignored.append('--user')
|
|
175
|
+
if group:
|
|
176
|
+
ignored.append('--group')
|
|
177
|
+
|
|
178
|
+
if ignored:
|
|
179
|
+
click.secho(
|
|
180
|
+
f'Warning: {", ".join(ignored)} ignored: the command runs in the current '
|
|
181
|
+
f'container, so there is nothing to start.',
|
|
182
|
+
fg='yellow', err=True)
|
|
183
|
+
|
|
184
|
+
if debug:
|
|
185
|
+
from_config = [key for key in _DOCKER_ONLY_CONFIG_KEYS if section.get(key)]
|
|
186
|
+
if from_config:
|
|
187
|
+
click.echo(
|
|
188
|
+
f'Ignoring container-only .lager keys: {", ".join(from_config)}', err=True)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _run_command_container(section, cmd_to_run, extra_args, debug, env):
|
|
192
|
+
"""
|
|
193
|
+
Run a command in place, without starting a container.
|
|
194
|
+
|
|
195
|
+
A container-based CI job already runs inside the devenv image: the shell, the
|
|
196
|
+
toolchain and the checkout are all there. There is also no Docker inside that job
|
|
197
|
+
container, so starting one is not an option even if it were wanted.
|
|
198
|
+
|
|
199
|
+
Deliberately does not chdir. The Docker path sets `-w <mount_dir>` because it bind-mounts
|
|
200
|
+
the checkout to that path; here the checkout is wherever the CI system put it, and the
|
|
201
|
+
caller's working directory is already correct.
|
|
202
|
+
"""
|
|
203
|
+
# `or`, not a .get default: a .lager may carry an explicit empty shell, and handing
|
|
204
|
+
# that to subprocess.run raises rather than reporting anything useful.
|
|
205
|
+
shell = section.get('shell') or '/bin/bash'
|
|
206
|
+
full_command = ' '.join((cmd_to_run, *extra_args)).strip()
|
|
207
|
+
|
|
208
|
+
child_env = os.environ.copy()
|
|
209
|
+
for env_var in (*devenv_config_list(section.get('environment')), *env):
|
|
210
|
+
name, _, value = str(env_var).partition('=')
|
|
211
|
+
child_env[name] = value
|
|
212
|
+
|
|
213
|
+
if debug:
|
|
214
|
+
click.echo(f'Command: {full_command}', err=True)
|
|
215
|
+
click.echo(f'Running in the current container ({shell})', err=True)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
proc = subprocess.run([shell, '-c', full_command], check=False, env=child_env)
|
|
219
|
+
return proc.returncode
|
|
220
|
+
except FileNotFoundError:
|
|
221
|
+
click.secho(f'Error: shell `{shell}` not found in this container', fg='red', err=True)
|
|
222
|
+
click.echo('Set DEVENV `shell` in .lager to a shell the image provides.', err=True)
|
|
223
|
+
return 1
|
|
224
|
+
except PermissionError:
|
|
225
|
+
click.secho(f'Error: permission denied running `{shell}`', fg='red', err=True)
|
|
226
|
+
return 1
|
|
227
|
+
|
|
228
|
+
|
|
153
229
|
@click.command(name='exec', context_settings={"ignore_unknown_options": True},
|
|
154
230
|
short_help="Run a saved command in a local docker container")
|
|
155
231
|
@click.pass_context
|
|
@@ -192,6 +268,10 @@ def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount
|
|
|
192
268
|
|
|
193
269
|
section = data['DEVENV']
|
|
194
270
|
|
|
271
|
+
# Captured before the defaulting below, which always resolves a uid/gid: the
|
|
272
|
+
# in-container path warns about what the caller actually asked for, not the default.
|
|
273
|
+
requested_user, requested_group = user, group
|
|
274
|
+
|
|
195
275
|
# Precedence: CLI flag wins, then .lager config, then the current uid/gid.
|
|
196
276
|
if user is None:
|
|
197
277
|
user = section.get('user')
|
|
@@ -248,9 +328,20 @@ def exec_(ctx, cmd_name, extra_args, command, save_as, warn, env, passenv, mount
|
|
|
248
328
|
ctx.exit(1)
|
|
249
329
|
click.echo(f'Saved command: {save_as}')
|
|
250
330
|
|
|
331
|
+
debug = ctx.obj.debug or verbose
|
|
332
|
+
|
|
333
|
+
# Under container-based CI the job is already running inside the devenv image, so the
|
|
334
|
+
# command runs in place. Starting a container from in there would need a Docker the job
|
|
335
|
+
# container does not have. LAGER_CI_OVERRIDE forces this back to the Docker path.
|
|
336
|
+
if is_container_ci(get_ci_environment()):
|
|
337
|
+
_warn_docker_only_options(section, mount, volumes, requested_user, requested_group, debug)
|
|
338
|
+
# passenv needs no argument here: the child inherits our environment already.
|
|
339
|
+
returncode = _run_command_container(section, cmd_to_run, extra_args, debug, env)
|
|
340
|
+
ctx.exit(returncode)
|
|
341
|
+
|
|
251
342
|
# Run the command locally in Docker
|
|
252
343
|
returncode = _run_command_local(
|
|
253
344
|
section, path, cmd_to_run, mount, extra_args,
|
|
254
|
-
|
|
345
|
+
debug, interactive, tty, user, group, env, passenv, volumes
|
|
255
346
|
)
|
|
256
347
|
ctx.exit(returncode)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|