rtems-proxy 2.1.0__tar.gz → 3.0.0b6__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.
- rtems_proxy-3.0.0b6/.claude/hooks/sandbox-check.sh +43 -0
- rtems_proxy-3.0.0b6/.claude/settings.json +18 -0
- rtems_proxy-3.0.0b6/.claude/skills/rtems-hybrid-debug/SKILL.md +374 -0
- rtems_proxy-3.0.0b6/.claude/statusline-command.sh +57 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.copier-answers.yml +1 -1
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.devcontainer/devcontainer.json +15 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/CONTRIBUTING.md +1 -1
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_dist.yml +7 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/ci.yml +2 -3
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.gitignore +6 -0
- rtems_proxy-3.0.0b6/.gitmodules +3 -0
- rtems_proxy-3.0.0b6/Dockerfile +63 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/PKG-INFO +10 -5
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/README.md +5 -0
- rtems_proxy-3.0.0b6/docs/example-bl19i-va-ioc-01.md +337 -0
- rtems_proxy-3.0.0b6/docs/hybrid.md +326 -0
- rtems_proxy-3.0.0b6/docs/overview.md +107 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/pyproject.toml +6 -6
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/requirements.txt +1 -1
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/__main__.py +87 -39
- rtems_proxy-3.0.0b6/src/rtems_proxy/_version.py +24 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/configure.py +11 -12
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/copy.py +17 -14
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/globals.py +61 -37
- rtems_proxy-3.0.0b6/src/rtems_proxy/hybrid.py +283 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/rsync.sh.jinja +8 -10
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/PKG-INFO +10 -5
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/SOURCES.txt +14 -3
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/requires.txt +2 -1
- rtems_proxy-3.0.0b6/tests/test_globals.py +39 -0
- rtems_proxy-3.0.0b6/update-schema +16 -0
- rtems_proxy-3.0.0b6/uv.lock +1019 -0
- rtems_proxy-3.0.0b6/vacuumSpace-fix.patch +175 -0
- rtems_proxy-3.0.0b6/vacuumSpace.ibek.support.yaml.fixed +823 -0
- rtems_proxy-2.1.0/.python-version +0 -1
- rtems_proxy-2.1.0/Dockerfile +0 -46
- rtems_proxy-2.1.0/rtems-proxy.code-workspace +0 -14
- rtems_proxy-2.1.0/src/rtems_proxy/_version.py +0 -34
- rtems_proxy-2.1.0/uv.lock +0 -1022
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/ISSUE_TEMPLATE/issue.md +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/copilot-instructions.md +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/pages/index.html +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/pages/make_switcher.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_container.yml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_pypi.yml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_release.yml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_test.yml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.github/workflows/_tox.yml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.gitleaks.toml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.pre-commit-config.yaml +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.vscode/extensions.json +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.vscode/launch.json +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.vscode/settings.json +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/.vscode/tasks.json +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/LICENSE +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/renovate.json +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/setup.cfg +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/__init__.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/connect.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/telnet.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/trace.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy/utils.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/dependency_links.txt +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/entry_points.txt +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/src/rtems_proxy.egg-info/top_level.txt +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/tests/conftest.py +0 -0
- {rtems_proxy-2.1.0 → rtems_proxy-3.0.0b6}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UserPromptSubmit hook. Verifies the Claude sandbox is intact before
|
|
3
|
+
# every prompt. Exit 2 blocks the prompt and surfaces the message.
|
|
4
|
+
#
|
|
5
|
+
# Belt-and-suspenders against the "user invoked Claude via a non-shadow
|
|
6
|
+
# path" bypass — the bwrap launcher sets IS_SANDBOX=1, so an unset
|
|
7
|
+
# value means we are not in the sandbox.
|
|
8
|
+
#
|
|
9
|
+
# Skip the gate on Claude Code Web (CLAUDE_CODE_REMOTE=true). The
|
|
10
|
+
# hosted runtime is already sandboxed by Anthropic, and the local
|
|
11
|
+
# positive assertions below (GIT_CONFIG_GLOBAL=/etc/claude-gitconfig,
|
|
12
|
+
# GIT_CONFIG_SYSTEM=/dev/null) only hold inside our bwrap shadow.
|
|
13
|
+
|
|
14
|
+
fail() { echo "BLOCKED: $1" >&2; exit 2; }
|
|
15
|
+
|
|
16
|
+
[ "${CLAUDE_CODE_REMOTE:-}" = "true" ] && exit 0
|
|
17
|
+
|
|
18
|
+
[ "${IS_SANDBOX:-}" = "1" ] || \
|
|
19
|
+
fail "IS_SANDBOX unset — Claude was launched outside the bwrap shadow. Run via /usr/local/bin/claude."
|
|
20
|
+
|
|
21
|
+
# Strict-under-/root: the host gitconfig must NOT be readable.
|
|
22
|
+
[ ! -e "$HOME/.gitconfig" ] || ! [ -s "$HOME/.gitconfig" ] || \
|
|
23
|
+
fail "$HOME/.gitconfig is reachable — strict-under-/root inversion broken or the file mask regressed."
|
|
24
|
+
|
|
25
|
+
# Env scrub: tokens that may have been on the host shell must be empty.
|
|
26
|
+
[ -z "${GH_TOKEN:-}" ] || fail "GH_TOKEN is set inside the sandbox — --clearenv allowlist regressed."
|
|
27
|
+
[ -z "${GITHUB_TOKEN:-}" ] || fail "GITHUB_TOKEN is set inside the sandbox — --clearenv allowlist regressed."
|
|
28
|
+
[ -z "${ANTHROPIC_API_KEY:-}" ] || fail "ANTHROPIC_API_KEY is set inside the sandbox — --clearenv allowlist regressed."
|
|
29
|
+
[ -z "${SSH_AUTH_SOCK:-}" ] || fail "SSH_AUTH_SOCK is set inside the sandbox — --clearenv allowlist regressed."
|
|
30
|
+
[ -z "${DISPLAY:-}" ] || fail "DISPLAY is set inside the sandbox — --clearenv allowlist regressed."
|
|
31
|
+
|
|
32
|
+
# Curated gitconfig steering.
|
|
33
|
+
[ "${GIT_CONFIG_GLOBAL:-}" = "/etc/claude-gitconfig" ] || \
|
|
34
|
+
fail "GIT_CONFIG_GLOBAL is '${GIT_CONFIG_GLOBAL:-<unset>}', not /etc/claude-gitconfig — git would fall back to the host gitconfig."
|
|
35
|
+
[ "${GIT_CONFIG_SYSTEM:-}" = "/dev/null" ] || \
|
|
36
|
+
fail "GIT_CONFIG_SYSTEM is '${GIT_CONFIG_SYSTEM:-<unset>}', not /dev/null — git would read the host /etc/gitconfig."
|
|
37
|
+
|
|
38
|
+
# /run/secrets must be empty.
|
|
39
|
+
if [ -d /run/secrets ] && [ -n "$(ls -A /run/secrets 2>/dev/null)" ]; then
|
|
40
|
+
fail "/run/secrets is non-empty — Docker/Compose secrets are reachable. tmpfs mask regressed."
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
exit 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"UserPromptSubmit": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": ".claude/hooks/sandbox-check.sh"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"statusLine": {
|
|
15
|
+
"type": "command",
|
|
16
|
+
"command": ".claude/statusline-command.sh"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rtems-hybrid-debug
|
|
3
|
+
description: >-
|
|
4
|
+
Debugging recipes for hybrid RTEMS5 IOCs driven by rtems-proxy — inspecting
|
|
5
|
+
cross-compiled IOC binary symbols from a Linux box, the ibek st.cmd <-> NFS
|
|
6
|
+
layout contract, and telling "not linked" apart from "not iocsh-registered".
|
|
7
|
+
Use when an RTEMS IOC boots but a st.cmd command is "not found", PVs are
|
|
8
|
+
missing, or boot paths look wrong.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Debugging hybrid RTEMS5 IOCs
|
|
12
|
+
|
|
13
|
+
Context: rtems-proxy generates an IOC's runtime (ibek + msi), places it on
|
|
14
|
+
NFS/TFTP, and drives a VME crate via motBoot. The crate NFS-mounts its per-IOC
|
|
15
|
+
export at `/epics` and TFTP-boots a PowerPC `.boot` image. Most "it boots but
|
|
16
|
+
doesn't work" failures are in the **Generic IOC build** or the
|
|
17
|
+
**path/layout contract**, not the instance `ioc.yaml`.
|
|
18
|
+
|
|
19
|
+
## Inspecting symbols in the RTEMS IOC binary (from any Linux box)
|
|
20
|
+
|
|
21
|
+
The build tree (`.../ioc/<gen-ioc>/bin/RTEMS-beatnik/`) holds two files:
|
|
22
|
+
|
|
23
|
+
- `<IOC>` (no extension, large, e.g. 31 MB) — **unstripped ELF, has `.symtab`**.
|
|
24
|
+
This is the one to inspect.
|
|
25
|
+
- `<IOC>.boot` (small, e.g. 3.9 MB) — stripped boot image TFTP'd to the crate.
|
|
26
|
+
No symbols.
|
|
27
|
+
|
|
28
|
+
Stock RHEL8 `binutils` reads the cross-compiled `ELF32 PowerPC` image fine —
|
|
29
|
+
`readelf`/`nm` symbol listing is target-independent, so **no PowerPC
|
|
30
|
+
cross-toolchain is needed** (only `objdump -d` disassembly would need it).
|
|
31
|
+
`file` may be absent on DLS boxes; use `readelf -h` to confirm arch.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
F=.../bin/RTEMS-beatnik/<IOC> # the no-extension ELF, NOT .boot
|
|
35
|
+
readelf -h "$F" | grep -E 'Class|Machine' # ELF32 / PowerPC
|
|
36
|
+
nm "$F" | grep -i <symbol> # is it linked at all?
|
|
37
|
+
nm "$F" | grep ' [Tt] ' # all defined functions
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## "not linked" vs "not iocsh-registered" — the key distinction
|
|
41
|
+
|
|
42
|
+
A st.cmd line like `DLS8516Configure(...)` failing with **"not found"** does
|
|
43
|
+
NOT necessarily mean the function is missing. An iocsh command needs more than
|
|
44
|
+
the function symbol — the module must also export an iocsh **registrar**.
|
|
45
|
+
Look for the registration machinery EPICS generates per command:
|
|
46
|
+
|
|
47
|
+
- `T <Func>` — the C function (callable from code)
|
|
48
|
+
- `t <Func>CallFunc`, `d <Func>FuncDef`, `d <Func>InitArg*` — the iocsh wrapper
|
|
49
|
+
- `t <Func>Register` + `D pvar_func_<Func>Register` — the `epicsExportRegistrar`
|
|
50
|
+
|
|
51
|
+
Three outcomes:
|
|
52
|
+
1. **Function symbol absent** → module not linked into the IOC. Fix: add the
|
|
53
|
+
lib (+ its dbd) to the Generic IOC build. (e.g. pvlogging's
|
|
54
|
+
`set_logging_enable`/`set_max_array_length` were 0 matches = not linked.)
|
|
55
|
+
2. **Function present but no `pvar_func_<Func>Register`** → linked but never
|
|
56
|
+
wrapped as an iocsh command. Fix: add the iocsh registration in the support
|
|
57
|
+
module C source + `registrar(...)` in its `.dbd`, then rebuild. (e.g.
|
|
58
|
+
`DLS8516Configure` was `T` but had no `...ConfigureRegister`, while its
|
|
59
|
+
sibling `DLS8516Display` did — a copy-paste omission, betrayed by mashed
|
|
60
|
+
`DLS85158516*` symbol names.)
|
|
61
|
+
3. **Registrar present but command still not found** → the module `.dbd` isn't
|
|
62
|
+
in the loaded `ioc.dbd` (composition problem). Compare with a sibling
|
|
63
|
+
command that works.
|
|
64
|
+
|
|
65
|
+
Diff a working sibling against the broken one (`nm "$F" | grep -i 8515` vs
|
|
66
|
+
`8516`) to localise which of the three it is.
|
|
67
|
+
|
|
68
|
+
## ibek st.cmd <-> NFS layout contract
|
|
69
|
+
|
|
70
|
+
ibek's `st.cmd.jinja` renders fixed paths the crate reads after mounting its
|
|
71
|
+
export at `/epics`:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
cd "{{ get_env('IOC') }}" -> /epics/ioc
|
|
75
|
+
dbLoadDatabase dbd/ioc.dbd -> /epics/ioc/dbd/ioc.dbd
|
|
76
|
+
STREAM_PROTOCOL_PATH /epics/runtime/protocol/
|
|
77
|
+
set_requestfile_path("/epics", "runtime") -> autosave *.req
|
|
78
|
+
dbLoadRecords {{ get_env('RUNTIME_DIR') }}/ioc.db
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`get_env` has **no default** — an unset env var renders empty. So:
|
|
82
|
+
|
|
83
|
+
- **Foot-gun:** if `RUNTIME_DIR` is unset when ibek runs, `dbLoadRecords`
|
|
84
|
+
becomes `/ioc.db` (wrong). rtems-proxy must pass `RUNTIME_DIR=/epics/runtime`
|
|
85
|
+
to the ibek subprocess (`hybrid.py:_run_ibek_generate`).
|
|
86
|
+
- The NFS export must therefore be laid out as **`runtime/`** (st.cmd, ioc.db,
|
|
87
|
+
protocol/, autosave *.req) and **`ioc/dbd/`** — not flat. `_copy_to_nfs`
|
|
88
|
+
builds exactly these two subfolders.
|
|
89
|
+
- Autosave `*.req` aggregates (`autosave_settings.req`/`autosave_positions.req`)
|
|
90
|
+
are emitted by ibek into the runtime dir only if a loaded template has a
|
|
91
|
+
matching `<stem>_settings.req`/`_positions.req` under `/epics/support/**`;
|
|
92
|
+
none match -> none emitted (not an error).
|
|
93
|
+
|
|
94
|
+
When changing the runtime files on a real crate, the NFS export must be
|
|
95
|
+
emptied and re-copied (stale flat files — e.g. an old st.cmd pointing at the
|
|
96
|
+
retired `/epics_rtems_root` mount — will otherwise be booted).
|
|
97
|
+
|
|
98
|
+
## ibek substitution foot-gun: shared `.template` -> positional row misalignment
|
|
99
|
+
|
|
100
|
+
ibek (`render_db.py`) merges **every** `databases:` entry across **all** entities
|
|
101
|
+
that points at the **same** `.template` path into **one** msi `pattern { … }`
|
|
102
|
+
block. The header is taken from the arg-key order of the **first** entity to
|
|
103
|
+
reference that file; every other entity's row values are then appended **in that
|
|
104
|
+
entity's own arg order — ibek never re-keys by name**. So if two different
|
|
105
|
+
`entity_model`s instantiate the same template with args in different order, the
|
|
106
|
+
second's values land in the **wrong columns**, silently.
|
|
107
|
+
|
|
108
|
+
- **Symptoms (at `dbLoadRecords` of the expanded `ioc.db`):**
|
|
109
|
+
`Can't set "<rec>.<FIELD>" to "<value>": Illegal choice` / `No digits to
|
|
110
|
+
convert` (a string value landed in a menu/numeric field), and garbage record
|
|
111
|
+
names like `4:SEQCCHV` (a delay/number value landed in the `device` column).
|
|
112
|
+
- **Diagnose:** regenerate the subst to a scratch dir
|
|
113
|
+
(`ibek runtime generate --no-pvi <ioc.yaml> /epics/ibek-defs/*.yaml -o /tmp/x`;
|
|
114
|
+
exclude a symlinked def and pass a writable copy if you need to edit one),
|
|
115
|
+
then for each `file "…template" {` block compare the `pattern { … }` header to
|
|
116
|
+
the offending rows. A quick check: every value row must have the **same column
|
|
117
|
+
count** as the header (regex `"([^"]*)"` per row); a count mismatch = guaranteed
|
|
118
|
+
break, and equal counts can still be **semantically** swapped (e.g. SELM↔gauge).
|
|
119
|
+
- **Fix:** make the wrapper model list that template's args in the **exact** order
|
|
120
|
+
of the owning module's own `entity_model`. When the owning model uses
|
|
121
|
+
`args: { .*: }` (regex = all params), the header is the entity's **full**
|
|
122
|
+
param list **including ibek's injected `type, entity_enabled` prefix** — the
|
|
123
|
+
wrapper must reproduce those two leading (ignored) columns too.
|
|
124
|
+
|
|
125
|
+
### Companion foot-gun: re-instantiating a group template needs a unique device
|
|
126
|
+
|
|
127
|
+
A "wrapper" model (e.g. vacuumSpace `space`/`space_b`) that re-instantiates
|
|
128
|
+
another module's group template (`mks937aGaugeGroup`, `digitelMpcIonpGroup`,
|
|
129
|
+
`dlsPLC_vacValveGroup`, …) must give each group a **unique device**, or the
|
|
130
|
+
group's `$(device):PLOG/:P/:STA/…` records collide with the wrapper's own
|
|
131
|
+
`space.template` records → `Record "…:PLOG" of type sel redefined with new type
|
|
132
|
+
calc` + `dbRecordHead: tempList not empty`. The DLS builder convention
|
|
133
|
+
(`vacuumSpace/etc/builder.py::_make_groups`) is `device = $(device):<COMP>G`
|
|
134
|
+
(`GAUGEG`/`IONPG`/`IMGG`/`PIRGG`/`VALVEG`), and `space.template`'s
|
|
135
|
+
`gauge`/`ionp`/… macros then point at those group devices (`{{ device }}:GAUGEG`)
|
|
136
|
+
so the top-level space reads the group's combined output. The builder only makes
|
|
137
|
+
a group when ≥2 of a component exist; an ibek model can't express that
|
|
138
|
+
conditional, so the pragmatic equivalent is **always** make the group (padding
|
|
139
|
+
to 8 with the first device) — collision-free and correct, at the cost of extra
|
|
140
|
+
internal `:<COMP>G` PVs for single-device components.
|
|
141
|
+
|
|
142
|
+
## Generic-IOC top `Makefile` foot-guns (`.../ioc/<gen-ioc>/Makefile`)
|
|
143
|
+
|
|
144
|
+
The top-level Makefile of a DLS generic IOC (e.g. `bl-va-ioc-01`) generates
|
|
145
|
+
`data/msi.vars` and stages StreamDevice protocol files into `data/` for the NFS
|
|
146
|
+
export. Three non-obvious traps, all hit June 2026:
|
|
147
|
+
|
|
148
|
+
- **`data/` is `.gitignore`d** (like `bin/dbd/db/lib`), so a fresh clone lacks
|
|
149
|
+
it. Any recipe that redirects into it (`> data/msi.vars`) dies on a clean
|
|
150
|
+
build with `/bin/sh: data/...: No such file or directory`. Every such target
|
|
151
|
+
must `@mkdir -p $(@D)` (or `mkdir -p data`) as its first recipe line. Latent
|
|
152
|
+
until you build a fresh clone — an old clone where `data/` already exists
|
|
153
|
+
masks it.
|
|
154
|
+
- **Top-level `DATA += ...` is DEAD.** EPICS's `DATA`/`buildInstall` install
|
|
155
|
+
mechanism only fires inside **App** dirs, **never at TOP**. A top Makefile that
|
|
156
|
+
does `DATA += $(all_protos)` (protos gathered from `$(SYS_EDM_PATHS)`, i.e.
|
|
157
|
+
every module's `data/*.proto*`) installs **nothing** → `data/` ends up with
|
|
158
|
+
only `msi.vars` → rtems-proxy's `hybrid.py` glob `data/*.proto*` finds nothing
|
|
159
|
+
→ the NFS `runtime/protocol/` folder is created but **empty**. Fix: an explicit
|
|
160
|
+
`protocols` target hooked on `all` that copies the protos itself:
|
|
161
|
+
```makefile
|
|
162
|
+
all: submodules protocols data/msi.vars
|
|
163
|
+
protocols:
|
|
164
|
+
@mkdir -p data
|
|
165
|
+
@for f in $(all_protos); do install -m 644 "$$f" data/; done
|
|
166
|
+
```
|
|
167
|
+
Use `install -m 644`, **not `cp`**: prod-sourced protos are mode `555`
|
|
168
|
+
(read-only), so a second build's `cp` hits `Permission denied: cannot create
|
|
169
|
+
regular file 'data/x.protocol'` trying to overwrite the read-only dest.
|
|
170
|
+
`install` unlinks+recreates and pins a deterministic world-readable+writable
|
|
171
|
+
mode (what the NFS root-squash export needs; see the dbd-perms section).
|
|
172
|
+
- **Submodule lazy-init.** `ibek-support` / `ibek-support-dls` are git
|
|
173
|
+
submodules, empty after a fresh clone, and the `configure/CONFIG` build umask
|
|
174
|
+
does NOT reach them. Hook a `submodules` target on `all` that inits **only the
|
|
175
|
+
un-checked-out ones** — a blanket `git submodule update --init` would detach an
|
|
176
|
+
already-populated submodule to its recorded SHA and **orphan local branch
|
|
177
|
+
work** in it:
|
|
178
|
+
```makefile
|
|
179
|
+
submodules:
|
|
180
|
+
@git submodule status | awk '/^-/ { print $$2 }' | while read p; do \
|
|
181
|
+
git submodule update --init --recursive "$$p"; done
|
|
182
|
+
```
|
|
183
|
+
(`git submodule status` prefixes an un-initialised submodule with `-`.)
|
|
184
|
+
Running inside a build recipe also gives the checkout the build umask (0022) →
|
|
185
|
+
world-traversable, fixing the otherwise-unreachable submodule perms.
|
|
186
|
+
|
|
187
|
+
## DLS8515/8516 serial: "port connects but device gives No reply"
|
|
188
|
+
|
|
189
|
+
Distinct from "could not connect" (missing `/dev/ttyNNN` node — see the
|
|
190
|
+
`DLS8516Configure` registrar story). `No reply within 1000 ms` from StreamDevice
|
|
191
|
+
means the asyn port opened fine but the **line parameters are wrong** (baud,
|
|
192
|
+
data bits, parity, stop) so framing is garbled and nothing comes back.
|
|
193
|
+
|
|
194
|
+
Card → port → module map (this IOC): cards configure as `ty_<card>_<chan>`.
|
|
195
|
+
`DLS8515Configure(40,…)`→`ty_40_*`, `(41,…)`→`ty_41_*` are **8515**;
|
|
196
|
+
`DLS8516Configure(42,…)`→`ty_42_*` is **8516**. Both `*Configure` funcs call the
|
|
197
|
+
same `DLS85158516Configure()` and pre-config every channel to **9600 8N2**
|
|
198
|
+
(`drvDLS8515-RTEMS.c`; legacy `drvDLS8515.c` identical) — so the driver default
|
|
199
|
+
is NOT what separates a working card from a failing one.
|
|
200
|
+
|
|
201
|
+
Diagnostic: correlate working vs failing ports against `asynSetOption` lines in
|
|
202
|
+
the generated st.cmd (`/ioc_nfs/runtime/st.cmd`). DLS devices commonly run
|
|
203
|
+
**7E2** (bits 7, parity Even, stop 2), NOT the driver's 8N2 default — so a
|
|
204
|
+
channel that relies on the default talks to a 7E2 device at 8N2 and gets no
|
|
205
|
+
reply. Ports with explicit settings in `ioc.yaml` get `asynSetOption` and work;
|
|
206
|
+
ports without get the wrong framing and fail.
|
|
207
|
+
|
|
208
|
+
- **Watch the emit gating:** a channel template may only emit `asynSetOption`
|
|
209
|
+
when `baud:` is present, silently dropping `parity:`/`stop:` set without a
|
|
210
|
+
baud. Diff `ioc.yaml` channel params against the actual `asynSetOption` lines
|
|
211
|
+
in st.cmd — a channel whose `parity: E` never appears in st.cmd is the smoking
|
|
212
|
+
gun. The fix is per-channel serial settings in `ioc.yaml` (recovered from the
|
|
213
|
+
original VxWorks/XmlBuilder build), and/or a template fix to emit asynSetOption
|
|
214
|
+
for parity/stop/bits independent of baud.
|
|
215
|
+
|
|
216
|
+
- **Mapped-enum vs template mismatch (the parity bug, fixed June 2026):** ibek's
|
|
217
|
+
`ioc_factory.py::fixup_enums` renders an enum param differently depending on
|
|
218
|
+
whether its `values:` are MAPPED. `{E: even, O: odd, N: none}` → the param
|
|
219
|
+
renders as the *value* (`"even"`/`"odd"`/`"none"`); `{E:, O:, N:}` (null
|
|
220
|
+
values) → it renders as the *key* (`"E"`). DLS8515channel.parity is mapped, so
|
|
221
|
+
a template testing `parity == "E"` never matched and **no parity asynSetOption
|
|
222
|
+
was emitted** — every 7E2 gauge ran 7N2, framing errors, "No reply within
|
|
223
|
+
1000 ms". Fix in `DLS8515.ibek.support.yaml`: make both channels' parity enum
|
|
224
|
+
mapped to asyn's literal values and have the template emit
|
|
225
|
+
`asynSetOption(...,"parity","{{parity}}")` gated on `parity != "none"`. Parity
|
|
226
|
+
was the only mapped enum and the only one that broke; baud/data/stop/flow are
|
|
227
|
+
unmapped and their keys are already the literal asyn values (7,2,H,S).
|
|
228
|
+
|
|
229
|
+
## "findInterface asynInt32Type" on every FINS record — missing FINS port layer
|
|
230
|
+
|
|
231
|
+
DLS PLC records (`dlsPLC.vacValve`/`read100`/`interlock`/`temperature`, and the
|
|
232
|
+
`:ACTUALCON` ao, `:INTn:RESET` ao) use `DTYP=asynInt32` with
|
|
233
|
+
`@asyn(PORT, addr, 0) FINS_DM_READ`/`FINS_DM_WRITE`. The `asynInt32` interface
|
|
234
|
+
**and** the `FINS_DM_*` drvUser strings are provided by a **FINS device port**
|
|
235
|
+
created by `finsDEVInit(finsPort, serialPort)` (after
|
|
236
|
+
`HostlinkInterposeInit(serialPort)`) — NOT by the bare serial port. If st.cmd
|
|
237
|
+
only has `drvAsynSerialPortConfigure`/`asynSetOption` for `PORT` and no
|
|
238
|
+
`HostlinkInterposeInit`/`finsDEVInit`, the port is plain octet-only, so every
|
|
239
|
+
FINS record fails at init:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
<PV> devAsynInt32::initCommon findInterface asynInt32Type
|
|
243
|
+
recGblRecordError: ao: init_record Error (514,11) PV: <PV>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
These are non-fatal for iocInit (see the "boots but zero PVs" section — they do
|
|
247
|
+
not abort `iocBuild`), **but the affected PVs never talk to the PLC**, so it is
|
|
248
|
+
a real failure, not noise, when those PVs are the point of the IOC.
|
|
249
|
+
|
|
250
|
+
Root cause seen June 2026 (bl19i-va-ioc-01): the builder2ibek conversion dropped
|
|
251
|
+
the FINS port objects and set each dlsPLC entity's `port:` directly to the
|
|
252
|
+
**underlying serial port** (`ty_40_5`/`ty_41_0`/`ty_41_1`/`ty_41_7`). The
|
|
253
|
+
instance `ioc.yaml` had **zero** `FINS.FINSHostlink` entities. Confirm in two
|
|
254
|
+
greps:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
grep -ni 'FINS\|Hostlink' .../config/ioc.yaml # broken IOC: NONE
|
|
258
|
+
grep -nE 'HostlinkInterposeInit|finsDEVInit' /ioc_nfs/runtime/st.cmd # NONE
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
A working sibling (bl15i-va-ioc-01.yaml) has one `FINS.FINSHostlink` per serial
|
|
262
|
+
port carrying FINS devices, and the dlsPLC entities point at the **FINS** name,
|
|
263
|
+
not the serial name:
|
|
264
|
+
|
|
265
|
+
```yaml
|
|
266
|
+
- type: FINS.FINSHostlink
|
|
267
|
+
asyn_port: ty_42_0 # the serial port (asyn.AsynSerial name)
|
|
268
|
+
name: TMPCC1.Hostlink # the FINS port — MUST be a DISTINCT name
|
|
269
|
+
# ...
|
|
270
|
+
- type: dlsPLC.read100
|
|
271
|
+
port: TMPCC1.Hostlink # references the FINS port, NOT ty_42_0
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`FINS.FINSHostlink` (`FINS.ibek.support.yaml`) emits exactly:
|
|
275
|
+
`HostlinkInterposeInit("{{asyn_port}}")` then
|
|
276
|
+
`finsDEVInit("{{name}}", "{{asyn_port}}")`. `HostlinkInterposeInit` interposes on
|
|
277
|
+
the serial port **in place**; `finsDEVInit` creates a **new** asyn port on top —
|
|
278
|
+
hence the FINS name must differ from the serial name (asyn port names are
|
|
279
|
+
unique).
|
|
280
|
+
|
|
281
|
+
Fix (purely an instance `ioc.yaml` change — **no Generic-IOC rebuild**): for each
|
|
282
|
+
serial port that carries FINS devices, add a `FINS.FINSHostlink` (distinct
|
|
283
|
+
`name`, `asyn_port:` = the serial port) and repoint every dlsPLC `port:` from the
|
|
284
|
+
serial name to that FINS name, then regen. The FINS driver is already linked —
|
|
285
|
+
`ioc.dbd` has `registrar(finsDEVRegister)` **and**
|
|
286
|
+
`registrar(HostlinkInterposeRegister)`, so the iocsh commands exist; they were
|
|
287
|
+
just never called.
|
|
288
|
+
|
|
289
|
+
## Regenerating & deploying the runtime (`msi` must be on PATH)
|
|
290
|
+
|
|
291
|
+
`rtems-proxy start` runs `ibek runtime generate2` then **`msi`** (EPICS macro
|
|
292
|
+
expansion of the .db) then copies `runtime/` + `ioc/` to the NFS export. `msi`
|
|
293
|
+
ships with epics-base but is **not on PATH by default** — without it the run
|
|
294
|
+
prints `msi: command not found` / `msi expansion failed` and stops before the
|
|
295
|
+
NFS copy. It lives at `/epics/epics-base/bin/linux-x86_64/msi`; this repo
|
|
296
|
+
symlinks it into `.venv/bin/` (already on PATH) so the subprocess finds it.
|
|
297
|
+
If that symlink is missing (e.g. venv rebuilt), recreate it or
|
|
298
|
+
`export PATH=/epics/epics-base/bin/linux-x86_64:$PATH` before the run.
|
|
299
|
+
|
|
300
|
+
Regen + verify a serial/template change (no crate connection needed):
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
rtems-proxy start --hybrid --no-connect \
|
|
304
|
+
--instance /workspaces/i19-services/services/bl19i-va-ioc-01
|
|
305
|
+
# generate2 writes /epics/runtime/st.cmd; after msi the NFS copy lands at
|
|
306
|
+
# /ioc_nfs/runtime/st.cmd (this is the file the crate boots).
|
|
307
|
+
grep -n 'asynSetOption\|parity' /ioc_nfs/runtime/st.cmd
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Note `/epics/ibek-defs/<MODULE>.ibek.support.yaml` is a **symlink** into the
|
|
311
|
+
module's `ibek-support-*` tree (e.g.
|
|
312
|
+
`/dls_sw/work/R7.0.7/ioc/BL/bl-va-ioc-01/ibek-support-dls/DLS8515/...`), so
|
|
313
|
+
editing the module def is picked up by the next regen with no copy step. The
|
|
314
|
+
`IOC binary not found ... .boot` message at the end is expected under
|
|
315
|
+
`--no-connect` (nothing is built/booted) and does not affect the generated
|
|
316
|
+
st.cmd.
|
|
317
|
+
|
|
318
|
+
## "boots but zero PVs" — a fatal iocInit step aborts before the CA server
|
|
319
|
+
|
|
320
|
+
`iocInit` runs `iocBuild` → `iocRun`. If any **`iocBuild`** step fails (e.g.
|
|
321
|
+
`iocBuild: asInit Failed.`), iocInit returns early and **never reaches
|
|
322
|
+
`iocRun`**, so the CA server never starts — the crate prompt still returns but
|
|
323
|
+
serves no PVs. When triaging a no-PV log, scan for a `*Build: ... Failed` /
|
|
324
|
+
fatal line near `iocInit`; everything above it (asyn `findInterface` failures,
|
|
325
|
+
`ao: init_record Error (514,11)`, `save_restore: Can't open file`, unregistered
|
|
326
|
+
iocsh commands) is **non-fatal noise** that does not stop PVs on its own.
|
|
327
|
+
(Non-fatal ≠ harmless: mass `findInterface asynInt32Type` failures mean a whole
|
|
328
|
+
class of PVs is dead — see the FINS port-layer section above.)
|
|
329
|
+
|
|
330
|
+
- **Foot-gun (pvlogging):** `_copy_to_nfs` stages only `runtime/` and
|
|
331
|
+
`ioc/dbd/` — nothing under `/epics/support/...`. The `pvlogging` module's
|
|
332
|
+
st.cmd line `asSetFilename /epics/support/pvlogging/src/access.acf` then
|
|
333
|
+
points at a file absent from the NFS export, `asInit` fails hard, iocInit
|
|
334
|
+
aborts, no PVs. Fix: **remove pvlogging from the instance `ioc.yaml`** and
|
|
335
|
+
re-gen runtime assets (confirmed fix, June 2026). Any support module that
|
|
336
|
+
needs an absolute `/epics/support/...` file at boot has the same problem —
|
|
337
|
+
the file must be copied into the NFS tree too, or the feature dropped.
|
|
338
|
+
|
|
339
|
+
## "registerRecordDeviceDriver failed <every recordtype>" — unreadable dbd dir
|
|
340
|
+
|
|
341
|
+
A boot log where `dbLoadDatabase dbd/ioc.dbd` is immediately followed by
|
|
342
|
+
`registerRecordDeviceDriver failed` for *every* recordtype (aSub, ai, ao, bi …)
|
|
343
|
+
plus `registryJLinkAdd failed calc` means **pdbbase is empty** — the dbd never
|
|
344
|
+
loaded. It is NOT a dbd-content or binary problem:
|
|
345
|
+
|
|
346
|
+
- `registerRecordDeviceDriver failed X` (base `registryCommon.c`) fires only
|
|
347
|
+
when `dbFindRecordType(pdbbase,"X")` misses *and* `registryRecordTypeAdd`
|
|
348
|
+
already **succeeded** — so the binary's record support is linked fine; the
|
|
349
|
+
recordtype just isn't in pdbbase.
|
|
350
|
+
- The real error is one line up, easily missed because it has no newline:
|
|
351
|
+
`filename="…/dbStatic/dbLexRoutines.c" line number=NNN dbRead opening file
|
|
352
|
+
dbd/ioc.dbd`. That is `dbReadCOM` reporting **`dbOpenFile()` returned NULL**
|
|
353
|
+
→ `goto cleanup` → nothing parsed. The crate **could not open the file**.
|
|
354
|
+
|
|
355
|
+
Root cause seen June 2026: the `ioc/dbd/` directory on the NFS export was mode
|
|
356
|
+
**0750** (`drwxr-x---`). The crate NFS-mounts the export as a root-squashed /
|
|
357
|
+
anonymous user, so it traverses `ioc/` (0755) but is denied entry to `dbd/` —
|
|
358
|
+
open fails. `runtime/st.cmd` loads fine precisely because `runtime/` is 0755.
|
|
359
|
+
The 0750 came from `_copy_to_nfs` doing `rsync -r` of the build-tree `dbd/`
|
|
360
|
+
(which is 0750), and `cp -a` then propagating it to the live export.
|
|
361
|
+
|
|
362
|
+
**Rule: every directory the crate reads under `/epics` must be world-traversable
|
|
363
|
+
(o+rx) and every file world-readable (o+r).** Confirm with
|
|
364
|
+
`namei -l /ioc_nfs/ioc/dbd/ioc.dbd` — any dir without world `x` breaks the boot.
|
|
365
|
+
|
|
366
|
+
- `_copy_to_nfs` now copies dbd with `rsync -r --chmod=D755,F644` plus an
|
|
367
|
+
explicit `chmod 0755` on `ioc/` and `ioc/dbd/` (the explicit chmod is needed
|
|
368
|
+
because with a `src/` trailing slash rsync does NOT re-perm the transfer's
|
|
369
|
+
**top** destination dir, only its contents).
|
|
370
|
+
- Manual one-shot repair on the live export:
|
|
371
|
+
`chmod -R a+rX /dls_sw/<bl>/epics/rtems/<ioc>` (capital `X` = add traverse to
|
|
372
|
+
dirs only, never makes data files executable), then reboot.
|
|
373
|
+
- The deploy step is `cp -a /ioc_nfs/. /dls_sw/<bl>/epics/rtems/<ioc>/` — `cp -a`
|
|
374
|
+
**preserves** perms, so fixing `/ioc_nfs` then re-deploying carries the fix.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code status line: model + context usage.
|
|
3
|
+
#
|
|
4
|
+
# Reads Claude's JSON status payload from stdin and prints a colored
|
|
5
|
+
# one-liner: username · model · cwd · ctx · cost. Uses jq for JSON
|
|
6
|
+
# parsing so no python is needed — works fine inside the bwrap sandbox
|
|
7
|
+
# where the host's python is masked off. If jq is missing, falls
|
|
8
|
+
# through to a bash-only degraded line.
|
|
9
|
+
|
|
10
|
+
input=$(cat)
|
|
11
|
+
|
|
12
|
+
degraded_line() {
|
|
13
|
+
local username cwd short_cwd
|
|
14
|
+
username=$(whoami 2>/dev/null || echo "?")
|
|
15
|
+
cwd=$(printf '%s' "$input" | sed -n 's/.*"current_dir"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
|
16
|
+
[ -z "$cwd" ] && cwd="$PWD"
|
|
17
|
+
short_cwd="${cwd/#$HOME/~}"
|
|
18
|
+
printf "\033[0;35m%s\033[0m \033[0;33m%s\033[0m \033[2;37m(no jq — degraded statusline)\033[0m" \
|
|
19
|
+
"$username" "$short_cwd"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
command -v jq >/dev/null 2>&1 || { degraded_line; exit 0; }
|
|
23
|
+
|
|
24
|
+
# Single jq pass emits tab-separated fields so a malformed value can't
|
|
25
|
+
# bleed across columns. `// empty` returns empty strings rather than
|
|
26
|
+
# the literal "null"; cost defaults to 0 for the printf below.
|
|
27
|
+
IFS=$'\t' read -r model cwd used remaining cost < <(
|
|
28
|
+
printf '%s' "$input" | jq -r '
|
|
29
|
+
[
|
|
30
|
+
(.model.display_name // "unknown model"),
|
|
31
|
+
(.workspace.current_dir // .cwd // ""),
|
|
32
|
+
(.context_window.used_percentage // empty | tostring),
|
|
33
|
+
(.context_window.remaining_percentage // empty | tostring),
|
|
34
|
+
(.cost.total_cost_usd // 0 | tostring)
|
|
35
|
+
] | @tsv
|
|
36
|
+
' 2>/dev/null
|
|
37
|
+
) || { degraded_line; exit 0; }
|
|
38
|
+
|
|
39
|
+
if [ -z "$model" ]; then
|
|
40
|
+
degraded_line
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
short_cwd="${cwd/#$HOME/~}"
|
|
45
|
+
username=$(whoami 2>/dev/null || echo "unknown")
|
|
46
|
+
cost_info=$(printf 'cost: $%.2f' "${cost:-0}")
|
|
47
|
+
|
|
48
|
+
if [ -n "$used" ] && [ -n "$remaining" ]; then
|
|
49
|
+
# printf %.0f rounds half-away-from-zero, matching the old
|
|
50
|
+
# int(round(...)) behaviour closely enough for a status line.
|
|
51
|
+
context_info=$(printf 'ctx: %.0f%% used / %.0f%% left' "$used" "$remaining")
|
|
52
|
+
else
|
|
53
|
+
context_info="ctx: new session"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
printf "\033[0;35m%s\033[0m \033[0;36m%s\033[0m \033[0;33m%s\033[0m \033[0;32m%s\033[0m \033[0;31m%s\033[0m" \
|
|
57
|
+
"$username" "$model" "$short_cwd" "$context_info" "$cost_info"
|
|
@@ -64,6 +64,21 @@
|
|
|
64
64
|
{
|
|
65
65
|
"target": "/workspaces/${localWorkspaceFolderBasename}/.venv",
|
|
66
66
|
"type": "volume"
|
|
67
|
+
},
|
|
68
|
+
// WARNING- remove these to work on this repo outside of DLS ///////////////////
|
|
69
|
+
// mount in /dls_sw readonly for testing hybrid mode
|
|
70
|
+
// slave propagation ensures autofs submounts on the host
|
|
71
|
+
// are visible inside the container without needing to trigger them from within it
|
|
72
|
+
{
|
|
73
|
+
"type": "bind",
|
|
74
|
+
"source": "/dls_sw",
|
|
75
|
+
"target": "/dls_sw,readonly,bind-propagation=slave"
|
|
76
|
+
},
|
|
77
|
+
// folders we would like to work on from here!
|
|
78
|
+
{
|
|
79
|
+
"source": "/dls_sw/work/R7.0.7/ioc/BL/",
|
|
80
|
+
"target": "/dls_sw/work/R7.0.7/ioc/BL/",
|
|
81
|
+
"type": "bind"
|
|
67
82
|
}
|
|
68
83
|
],
|
|
69
84
|
// Mount the parent as /workspaces so we can pip install peers as editable
|
|
@@ -24,4 +24,4 @@ It is recommended that developers use a [vscode devcontainer](https://code.visua
|
|
|
24
24
|
|
|
25
25
|
This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects.
|
|
26
26
|
|
|
27
|
-
For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/5.0.
|
|
27
|
+
For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/5.0.2/how-to.html).
|
|
@@ -29,6 +29,13 @@ jobs:
|
|
|
29
29
|
- name: Check for packaging errors
|
|
30
30
|
run: uvx twine check --strict dist/*
|
|
31
31
|
|
|
32
|
+
- name: Setup Python
|
|
33
|
+
# the runner's default python may be older than requires-python in
|
|
34
|
+
# pyproject.toml; pin to a supported version for the install/run checks
|
|
35
|
+
uses: actions/setup-python@v5
|
|
36
|
+
with:
|
|
37
|
+
python-version: "3.13"
|
|
38
|
+
|
|
32
39
|
- name: Install produced wheel
|
|
33
40
|
run: python -m pip install dist/*.whl
|
|
34
41
|
|
|
@@ -5,11 +5,10 @@ on:
|
|
|
5
5
|
branches:
|
|
6
6
|
- main
|
|
7
7
|
tags:
|
|
8
|
-
-
|
|
8
|
+
- "*"
|
|
9
9
|
pull_request:
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
|
-
|
|
13
12
|
lint:
|
|
14
13
|
uses: ./.github/workflows/_tox.yml
|
|
15
14
|
with:
|
|
@@ -19,7 +18,7 @@ jobs:
|
|
|
19
18
|
strategy:
|
|
20
19
|
matrix:
|
|
21
20
|
runs-on: ["ubuntu-latest"] # can add windows-latest, macos-latest
|
|
22
|
-
python-version: ["3.
|
|
21
|
+
python-version: ["3.13", "3.14"]
|
|
23
22
|
fail-fast: false
|
|
24
23
|
uses: ./.github/workflows/_test.yml
|
|
25
24
|
with:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# This container is bases on epics-base so that we have access to the msi tool
|
|
2
|
+
# and CA client tools for diagnostics.
|
|
3
|
+
FROM ghcr.io/epics-containers/epics-base-developer:7.0.10ec1 AS developer
|
|
4
|
+
|
|
5
|
+
# Add any system dependencies for the developer/build environment here
|
|
6
|
+
RUN apt-get update -y && apt-get install -y --no-install-recommends \
|
|
7
|
+
rsync \
|
|
8
|
+
telnet \
|
|
9
|
+
&& apt-get dist-clean
|
|
10
|
+
|
|
11
|
+
# The build stage installs the context into the venv
|
|
12
|
+
FROM developer AS build
|
|
13
|
+
|
|
14
|
+
# Change the working directory to the `app` directory
|
|
15
|
+
# and copy in the project
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
COPY . /app
|
|
18
|
+
RUN chmod o+wrX .
|
|
19
|
+
|
|
20
|
+
# Tell uv sync to install python in a known location so we can copy it out later
|
|
21
|
+
ENV UV_PYTHON_INSTALL_DIR=/python
|
|
22
|
+
|
|
23
|
+
# Sync the project without its dev dependencies
|
|
24
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
25
|
+
uv sync --locked --no-editable --no-dev
|
|
26
|
+
|
|
27
|
+
ENV PATH=/app/.venv/bin:$PATH
|
|
28
|
+
|
|
29
|
+
# Create directories for hybrid mode compatibility in devcontainers
|
|
30
|
+
RUN mkdir -p /epics/ioc /epics/runtime /ioc_tftp /ioc_nfsv2
|
|
31
|
+
|
|
32
|
+
# rtems-proxy rewrites the ibek-defs symlink farm and generates runtime assets
|
|
33
|
+
# at start time. In the cluster it runs as a non-root user, so these dirs must
|
|
34
|
+
# be writable+traversable by all (the symlink unlink/create needs w+x on the
|
|
35
|
+
# dir, not just r). Mounted volumes (/ioc_nfs, /ioc_tftp) get write access from
|
|
36
|
+
# the pod securityContext, not here.
|
|
37
|
+
RUN mkdir -p /epics/ibek-defs /epics/runtime /epics/autosave \
|
|
38
|
+
&& chmod a+rwx /epics/ibek-defs /epics/runtime /epics/autosave
|
|
39
|
+
|
|
40
|
+
# The runtime stage copies the built venv into a runtime container
|
|
41
|
+
FROM ghcr.io/epics-containers/epics-base-runtime:7.0.10ec1 AS runtime
|
|
42
|
+
|
|
43
|
+
# Add apt-get system dependencies for runtime here if needed
|
|
44
|
+
# RUN apt-get update -y && apt-get install -y --no-install-recommends \
|
|
45
|
+
# some-library \
|
|
46
|
+
# && apt-get dist-clean
|
|
47
|
+
|
|
48
|
+
# Copy the python installation from the build stage
|
|
49
|
+
COPY --from=build /python /python
|
|
50
|
+
|
|
51
|
+
# Copy the environment, but not the source code
|
|
52
|
+
COPY --from=build /app/.venv /app/.venv
|
|
53
|
+
ENV PATH=/app/.venv/bin:$PATH
|
|
54
|
+
|
|
55
|
+
# rtems-proxy rewrites the ibek-defs symlink farm and generates runtime assets
|
|
56
|
+
# at start time as a non-root user in the cluster, so these dirs must be
|
|
57
|
+
# writable+traversable by all (symlink unlink/create needs w+x on the dir).
|
|
58
|
+
RUN mkdir -p /epics/ibek-defs /epics/runtime \
|
|
59
|
+
&& chmod a+rwx /epics/ibek-defs /epics/runtime /epics/autosave
|
|
60
|
+
|
|
61
|
+
# change this entrypoint if it is not the same as the repo
|
|
62
|
+
ENTRYPOINT ["rtems-proxy"]
|
|
63
|
+
CMD ["--version"]
|