@rainbow-robotics/muscat-plugin-kit 1.3.6-dev.6 → 1.3.6-dev.8
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.
- package/cli/README.md +3 -8
- package/cli/bin/muscat-pk.mjs +0 -5
- package/cli/bundle-templates.mjs +1 -6
- package/cli/docker/Dockerfile.cpp +6 -28
- package/cli/docker/Dockerfile.dev +0 -32
- package/cli/docker/Dockerfile.python +0 -19
- package/cli/src/bridge.mjs +12 -45
- package/cli/src/build.mjs +79 -48
- package/cli/src/cpp.mjs +5 -11
- package/cli/src/dev.mjs +2 -7
- package/cli/src/devBack.mjs +40 -97
- package/cli/src/host_agent.py +83 -143
- package/cli/src/manifest.mjs +0 -11
- package/cli/src/mcp.mjs +54 -398
- package/cli/src/new.mjs +3 -13
- package/cli/src/ports.mjs +7 -21
- package/cli/src/python.mjs +20 -16
- package/cli/src/ros2.mjs +8 -16
- package/cli/src/sdk.mjs +21 -23
- package/cli/src/templates-embedded.mjs +4 -0
- package/cli/src/type.mjs +10 -44
- package/cli/src/uninstall.mjs +20 -3
- package/cli/src/update.mjs +8 -14
- package/cli/src/util.mjs +9 -70
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +1 -13
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +1 -98
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-C5ttlJWI.js → Color-YHDXOIA2-Du8dJ-nM.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CCfiEyzU.js → Docs--Fm8069p.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-B3AV-YvP.js → Docs-Cb72AJnj.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BN206lKl.js → Docs-LdjYwuG4.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BqH2prGO.js → Docs-Sy5RDbtH.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DhncK7Aw.js → Docs-guGflRzZ.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CGN1_Ua8.js → Docs-pwGqGTV_.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-D-w8Kq64.js → DocsRenderer-CFRXHY34-YGBLQ4BD.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-oCcYtLwq.js → NxAvatar.stories-C1d4n8cq.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-C5jUScIj.js → NxButton.stories-gCBoZdMq.js} +1 -1
- package/cli/storybook-dist/assets/{NxIcons.stories-DeqrVzVs.js → NxIcons.stories-Dg42riT5.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-gjsEulIN.js → NxNumberInput.stories-DB-vLM2J.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-BK6uSWIa.js → NxPagination.stories-CS8iMIak.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BDD5CvSZ.js → NxPointerHoldButton.stories-D66BopeS.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-C_nW26_x.js → NxRangeInput.stories-CZxddsJ2.js} +3 -3
- package/cli/storybook-dist/assets/{NxSelectBox.stories-DqLfKU9H.js → NxSelectBox.stories-CmezGGrm.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CQjHA8yv.js → NxSlideRange.stories-CGAFMITi.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-BMHCiPoL.js → NxSwitch.stories-B3QInkXX.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-k7r1WVQh.js → NxTextArea.stories-BgJcgZNe.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-Cqfe1I32.js → NxTextInput.stories-CwMQwI9L.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-CJ3Ttuja.js → RBAccordion.stories-BuBVNzJT.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BThv7tM0.js → RBAccordionGroup.stories-Crdb6kFF.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-CSMP8Vun.js → RBAlert.stories-CqXgUvA2.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-cBMA2R6F.js → RBBadge.stories-BCw_ksML.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-BKVOskqs.js → RBBattery.stories-0id4XYoc.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-BTOfK0HC.js → RBCard.stories-CgpG-7Lk.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DyE912wG.js → RBCheckBox.stories-BG02BoGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-0sLFU0kM.js → RBCheckIconButton.stories-vrjD4GQ4.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-c8VecClE.js → RBCheckLabelButton.stories-POba8lU-.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-CQ9Uav5_.js → RBChipTab.stories-XWJbFrHF.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-Ap4NlhAZ.js → RBChipsBoxTab.stories-BdU0Dzdp.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-BPPXjif2.js → RBColorIcons.stories-C_0dCNXG.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-PDpYOKlm.js → RBCommand.stories-Dvrko5No.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-BEWvMAYP.js → RBConfirm.stories-CkTn0sHa.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-DLdyNnVF.js → RBDateInput.stories-BH3D08cs.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-HsawMNOb.js → RBDialog.stories-C2nNX9fl.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-dlWwOxik.js → RBDivider.stories-D-GrJ0QZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-C87iqGk0.js → RBDrawer.stories-XLOnLOnh.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-B8EQhA0z.js → RBDropdownMenu.stories-BjBSeoV2.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-SzpIhNDw.js → RBEmptyView.stories-C6U2zKNE.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-BQ0nM1gb.js → RBIconButton.stories-MCJq5Hta.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-C19g5M5z.js → RBIcons.stories-DxOCiCe0.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-DbgI4-1P.js → RBInlineLabelButton.stories-BVZi1aEs.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-smF-vpGL.js → RBJoyStick.stories-BDThbEek.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-s53klwDv.js → RBLabelButton.stories-CaPEKUS6.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-BnzkoR51.js → RBLight.stories-CihQzyEL.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-CW014klu.js → RBMenubar.stories-J852XVFh.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-qa4tH_4Y.js → RBMultiRangeInput.stories-DXyX0cOZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-YoQNUpQH.js → RBPagination.stories-C9_49ymO.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-Bhpi0OaH.js → RBPopover.stories-DxLE912-.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-iQM5iAka.js → RBPrompt.stories-DlsfGqWb.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-BGyq4rzx.js → RBRadioBox.stories-hb5cNWW1.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DF6BcUcJ.js → RBRadioButton.stories-CSuOgeN-.js} +1 -1
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-KRg2glX8.js +15 -0
- package/cli/storybook-dist/assets/{RBResizable.stories-DSVHvwjp.js → RBResizable.stories-BetjF9vG.js} +1 -1
- package/cli/storybook-dist/assets/RBSelectBox.stories-ILLQQ8M8.js +6 -0
- package/cli/storybook-dist/assets/{RBSheet.stories-CAK9hb-p.js → RBSheet.stories-D3QbcMKq.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-DUymnH-9.js → RBSideNumberInput.stories-DcTh0IjM.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-BwlDh9--.js → RBSlider.stories-B59We7ei.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-VOlkuP6t.js → RBSpinner.stories-9u3_ETbi.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BiW2dQnT.js → RBSwitch.stories-DrVm1WGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-DFKVsy0C.js → RBTab.stories-D8Dm7cRW.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BWG0ZSYI.js → RBTable.stories-dRdZ8g1j.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-JVTD83Mh.js → RBTableRow.stories-rZeoGwHZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-D-aN385u.js → RBTabs.stories-STRV2lgO.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-Dx5xkmZS.js → RBTextArea.stories-CfgUbrbJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-DmVciExd.js → RBTextField.stories-B5t8-Y97.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-CCf0VGEv.js → RBTextInput.stories-Bf1edN6l.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-DT0DGn05.js → RBToast.stories-Cnm8JwQ6.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-Ce_q3EtP.js → RBToggle.stories-B0OCo6zd.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-CN40YHsR.js → RBToggleGroup.stories-GdYO2Fxg.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-L7btDEpA.js → RBTooltip.stories-DXJTCScO.js} +1 -1
- package/cli/storybook-dist/assets/SideNumberInput-CrF-a1Nr.js +1114 -0
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-D_nGAXUk.js → entry-preview-DSRwIaVJ.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-23VS_ZeI.js → iframe-Cj53Wn3d.js} +2 -2
- package/cli/storybook-dist/assets/{index-ClAc6FBW.js → index-C1OzuK6-.js} +1 -1
- package/cli/storybook-dist/assets/{index-Ckae_HWe.js → index-CkHtLEe8.js} +3 -3
- package/cli/storybook-dist/assets/preview-CEfMaO1t.css +1 -0
- package/cli/storybook-dist/assets/{preview-BVJwlGUF.js → preview-CILRoSnB.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CD0LINug.js → preview-CgOtgtCD.js} +2 -2
- package/cli/storybook-dist/assets/preview-D_2um8Sd.js +3 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CtORL3tG.js +102 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/index.html +1 -1
- package/cli/storybook-dist/index.json +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +7 -211
- package/cli/templates/common/py/main.py +2 -29
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +7 -6
- package/cli/templates/cpp/cpp/main.cpp +10 -57
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +0 -4
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +4 -5
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-COOQ_HNF.js +0 -15
- package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +0 -74
- package/cli/storybook-dist/assets/SideNumberInput-B5_xLK8z.js +0 -1109
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +0 -1
- package/cli/storybook-dist/assets/preview-P8yWWsME.js +0 -3
- package/cli/storybook-dist/assets/preview-zuAvMksL.css +0 -1
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +0 -120
- package/cli/templates/readme-cpp/README.md +0 -993
package/cli/README.md
CHANGED
|
@@ -60,10 +60,10 @@ and `50051` are reserved and must not be used by external plugins.
|
|
|
60
60
|
| runtime | source | notes |
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| `{ "type": "none" }` | — | UI-only plugin; no backend container |
|
|
63
|
-
| `{ "type": "python", "entry": "main.py" }` | `py/` | Uses `rainbow.rb_sdk.RBPluginCall` for UI/backend messaging
|
|
64
|
-
| `{ "type": "cpp", "entry": "<executable name>" }` | `cpp/` | Built with CMake in Docker
|
|
63
|
+
| `{ "type": "python", "entry": "main.py" }` | `py/` | Uses `rainbow.rb_sdk.RBPluginCall` for UI/backend messaging |
|
|
64
|
+
| `{ "type": "cpp", "entry": "<executable name>" }` | `cpp/` | Built with CMake in Docker; current template is a run skeleton |
|
|
65
65
|
| `{ "type": "ros2", "rosClient": "rclpy", ... }` | `ros2_ws/` | ROS2 Python node; may use Python SDK alongside ROS2 APIs |
|
|
66
|
-
| `{ "type": "ros2", "rosClient": "rclcpp", ... }` | `ros2_ws/` | ROS2 C++ node built with colcon/CMake
|
|
66
|
+
| `{ "type": "ros2", "rosClient": "rclcpp", ... }` | `ros2_ws/` | ROS2 C++ node built with colcon/CMake |
|
|
67
67
|
|
|
68
68
|
All non-`none` runtimes run in Docker during `muscat-pk dev:back`, `muscat-pk
|
|
69
69
|
dev`, and build/export flows so development matches the robot environment.
|
|
@@ -257,11 +257,6 @@ would break `dev` offline. Pre-seed an air-gapped machine with
|
|
|
257
257
|
`docker save … | gzip` + `docker load`, or force a rebuild with
|
|
258
258
|
`MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
259
259
|
|
|
260
|
-
The C++ images (`Dockerfile.cpp`, `Dockerfile.dev`) install `rainbow-rb-sdk-dev`
|
|
261
|
-
from `https://rainbowrobotics.github.io/rb-sdk` (suite `dev`), pinned by the
|
|
262
|
-
`RB_SDK_VERSION` build arg. Set `MUSCAT_PK_RB_SDK_VERSION=<version>` (with
|
|
263
|
-
`MUSCAT_PK_REBUILD_IMAGE=1`) to match a robot running a different SDK version.
|
|
264
|
-
|
|
265
260
|
On Linux the dev containers run as the invoking user (`--user`), so build output
|
|
266
261
|
under `dist/` and `cpp/build-*` is not left root-owned.
|
|
267
262
|
|
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -35,11 +35,6 @@ function readVersion(binPath) {
|
|
|
35
35
|
|
|
36
36
|
const isBinaryMode = typeof Bun !== "undefined";
|
|
37
37
|
|
|
38
|
-
// 바이너리에는 package.json 이 없다 — 컴파일 시 임베드한 버전을 쓴다.
|
|
39
|
-
if (isBinaryMode && !process.env.MUSCAT_PK_VERSION) {
|
|
40
|
-
process.env.MUSCAT_PK_VERSION = (await import("../src/templates-embedded.mjs")).KIT_VERSION;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
(function delegateToLocalInstall() {
|
|
44
39
|
try {
|
|
45
40
|
const localBin = resolveLocalCliBin();
|
package/cli/bundle-templates.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Pre-build step: serialise cli/templates/** into cli/src/templates-embedded.mjs
|
|
3
3
|
// so that `bun build --compile` can embed them without needing the filesystem.
|
|
4
4
|
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
|
|
5
|
-
import { resolve } from "node:path";
|
|
5
|
+
import { resolve, relative } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
const root = resolve(fileURLToPath(import.meta.url), "..");
|
|
@@ -19,15 +19,10 @@ function walk(baseDir, prefix) {
|
|
|
19
19
|
}
|
|
20
20
|
walk(resolve(root, "templates") + "/", "");
|
|
21
21
|
walk(resolve(root, "docker") + "/", "docker/");
|
|
22
|
-
// Dockerfile.bridge 의 COPY 대상
|
|
23
|
-
files["src/socketBridge.mjs"] = readFileSync(resolve(root, "src/socketBridge.mjs"), "base64");
|
|
24
|
-
|
|
25
|
-
const { version } = JSON.parse(readFileSync(resolve(root, "../package.json"), "utf8"));
|
|
26
22
|
|
|
27
23
|
writeFileSync(out,
|
|
28
24
|
`// AUTO-GENERATED by cli/bundle-templates.mjs — do not edit.\n` +
|
|
29
25
|
`// ponytail: generated at publish time; not tracked in git.\n` +
|
|
30
|
-
`export const KIT_VERSION = ${JSON.stringify(version)};\n` +
|
|
31
26
|
`export const TEMPLATES = ${JSON.stringify(files)};\n`
|
|
32
27
|
);
|
|
33
28
|
console.log(`bundled ${Object.keys(files).length} template files → src/templates-embedded.mjs`);
|
|
@@ -10,34 +10,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
10
10
|
cmake \
|
|
11
11
|
&& rm -rf /var/lib/apt/lists/*
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
&& printf 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk %s main\n' stable dev > /etc/apt/sources.list.d/rb-sdk.list \
|
|
20
|
-
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
21
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
22
|
-
|
|
23
|
-
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
24
|
-
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
25
|
-
ARG MEDIAMTX_VERSION=1.12.3
|
|
26
|
-
RUN set -eux; \
|
|
27
|
-
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
28
|
-
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
29
|
-
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
30
|
-
| tar -xz -C /usr/local/bin mediamtx; \
|
|
31
|
-
mkdir -p /etc/mediamtx; \
|
|
32
|
-
printf '%s\n' \
|
|
33
|
-
'logLevel: warn' \
|
|
34
|
-
'rtsp: yes' 'rtspAddress: :8554' \
|
|
35
|
-
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
36
|
-
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
37
|
-
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
38
|
-
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
39
|
-
'paths:' ' all_others:' ' source: publisher' \
|
|
40
|
-
> /etc/mediamtx/mediamtx.yml
|
|
13
|
+
# TODO(cpp-sdk): install the rainbow cpp SDK (dev package) here, pinned to the
|
|
14
|
+
# robot's version, so plugins build/link against the same SDK as production.
|
|
15
|
+
# Plan: apt package + dynamic link (the robot provides the .so at runtime;
|
|
16
|
+
# we do NOT bundle it into the plugin binary). e.g.:
|
|
17
|
+
# RUN apt-get update && apt-get install -y rainbow-cpp-sdk-dev=<robot-version> \
|
|
18
|
+
# && rm -rf /var/lib/apt/lists/*
|
|
41
19
|
|
|
42
20
|
WORKDIR /plugin
|
|
43
21
|
|
|
@@ -9,38 +9,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
9
9
|
python3-colcon-common-extensions \
|
|
10
10
|
&& rm -rf /var/lib/apt/lists/*
|
|
11
11
|
|
|
12
|
-
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
13
|
-
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
14
|
-
ARG RB_SDK_VERSION=0.1.6
|
|
15
|
-
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
16
|
-
&& install -d /etc/apt/keyrings \
|
|
17
|
-
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
18
|
-
&& printf 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk %s main\n' stable dev > /etc/apt/sources.list.d/rb-sdk.list \
|
|
19
|
-
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
20
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
21
|
-
|
|
22
|
-
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
23
|
-
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
24
|
-
ARG MEDIAMTX_VERSION=1.12.3
|
|
25
|
-
RUN set -eux; \
|
|
26
|
-
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
27
|
-
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
28
|
-
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
29
|
-
| tar -xz -C /usr/local/bin mediamtx; \
|
|
30
|
-
mkdir -p /etc/mediamtx; \
|
|
31
|
-
printf '%s\n' \
|
|
32
|
-
'logLevel: warn' \
|
|
33
|
-
'rtsp: yes' 'rtspAddress: :8554' \
|
|
34
|
-
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
35
|
-
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
36
|
-
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
37
|
-
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
38
|
-
'paths:' ' all_others:' ' source: publisher' \
|
|
39
|
-
> /etc/mediamtx/mediamtx.yml
|
|
40
|
-
|
|
41
|
-
# `docker exec` skips /ros_entrypoint.sh — source ROS for the login shells dev:back runs.
|
|
42
|
-
RUN echo ". /opt/ros/${ROS_DISTRO}/setup.sh" > /etc/profile.d/ros.sh
|
|
43
|
-
|
|
44
12
|
WORKDIR /plugin
|
|
45
13
|
|
|
46
14
|
CMD ["bash"]
|
|
@@ -15,25 +15,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
# cpp SDK shared lib must be present for the python module to load. Install the
|
|
16
16
|
# rainbow cpp SDK (apt, pinned to the robot's version) here once it exists.
|
|
17
17
|
|
|
18
|
-
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
19
|
-
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
20
|
-
ARG MEDIAMTX_VERSION=1.12.3
|
|
21
|
-
RUN set -eux; \
|
|
22
|
-
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
23
|
-
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
24
|
-
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
25
|
-
| tar -xz -C /usr/local/bin mediamtx; \
|
|
26
|
-
mkdir -p /etc/mediamtx; \
|
|
27
|
-
printf '%s\n' \
|
|
28
|
-
'logLevel: warn' \
|
|
29
|
-
'rtsp: yes' 'rtspAddress: :8554' \
|
|
30
|
-
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
31
|
-
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
32
|
-
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
33
|
-
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
34
|
-
'paths:' ' all_others:' ' source: publisher' \
|
|
35
|
-
> /etc/mediamtx/mediamtx.yml
|
|
36
|
-
|
|
37
18
|
WORKDIR /plugin
|
|
38
19
|
|
|
39
20
|
CMD ["bash"]
|
package/cli/src/bridge.mjs
CHANGED
|
@@ -5,41 +5,24 @@
|
|
|
5
5
|
// it at localhost:10000 too — no host.docker.internal needed (robust cross-OS).
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { createServer, connect } from "node:net";
|
|
8
|
-
import { existsSync
|
|
9
|
-
import {
|
|
10
|
-
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
11
10
|
import { canSkipImageBuild, hasBin, kitRoot, log, pruneMuskatImages } from "./util.mjs";
|
|
12
|
-
import { MEDIA_PORTS, mediaPortFlags } from "./ports.mjs";
|
|
13
|
-
import { usesHostNetwork } from "./zenohDev.mjs";
|
|
14
11
|
|
|
15
12
|
export const BRIDGE_IMAGE = "muscat-pk-bridge";
|
|
16
13
|
export const BRIDGE_CONTAINER = "muscat-pk-bridge-run";
|
|
17
14
|
export const BRIDGE_PORT = 10000;
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
async function resolveBridgeContext() {
|
|
23
|
-
const context = resolve(kitRoot, "cli");
|
|
24
|
-
const dockerfile = resolve(context, "docker/Dockerfile.bridge");
|
|
25
|
-
if (existsSync(dockerfile)) return { dockerfile, context };
|
|
26
|
-
|
|
27
|
-
const { TEMPLATES } = await import("./templates-embedded.mjs");
|
|
28
|
-
const dir = resolve(tmpdir(), "muscat-pk-bridge-context");
|
|
29
|
-
for (const key of BRIDGE_CONTEXT_FILES) {
|
|
30
|
-
const b64 = TEMPLATES[key];
|
|
31
|
-
if (!b64) throw new Error(`Embedded file missing: ${key}. Reinstall muscat-plugin-kit.`);
|
|
32
|
-
const file = resolve(dir, key);
|
|
33
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
34
|
-
writeFileSync(file, Buffer.from(b64, "base64"));
|
|
35
|
-
}
|
|
36
|
-
return { dockerfile: resolve(dir, "docker/Dockerfile.bridge"), context: dir };
|
|
37
|
-
}
|
|
16
|
+
const DOCKERFILE = resolve(kitRoot, "cli/docker/Dockerfile.bridge");
|
|
17
|
+
const BUILD_CONTEXT = resolve(kitRoot, "cli");
|
|
38
18
|
|
|
39
19
|
function assertDockerReady() {
|
|
40
20
|
if (!hasBin("docker")) {
|
|
41
21
|
throw new Error("Docker not found. `muscat-pk dev` runs the Socket.IO bridge in a container.");
|
|
42
22
|
}
|
|
23
|
+
if (!existsSync(DOCKERFILE)) {
|
|
24
|
+
throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
|
|
25
|
+
}
|
|
43
26
|
}
|
|
44
27
|
|
|
45
28
|
// Is a host TCP port already bound? (Catches another bridge or a stray process.)
|
|
@@ -69,28 +52,20 @@ function waitForPort(port, timeoutMs = 15_000) {
|
|
|
69
52
|
});
|
|
70
53
|
}
|
|
71
54
|
|
|
72
|
-
|
|
55
|
+
function ensureImage() {
|
|
73
56
|
if (canSkipImageBuild(BRIDGE_IMAGE)) {
|
|
74
57
|
log.step(`${BRIDGE_IMAGE} already built — skipping docker build`);
|
|
75
58
|
return;
|
|
76
59
|
}
|
|
77
|
-
const { dockerfile, context } = await resolveBridgeContext();
|
|
78
60
|
log.step(`docker build -t ${BRIDGE_IMAGE} (first run is slow, then cached)…`);
|
|
79
|
-
const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f",
|
|
61
|
+
const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f", DOCKERFILE, BUILD_CONTEXT], { stdio: "inherit" });
|
|
80
62
|
if (r.status !== 0) throw new Error("docker build failed (bridge image).");
|
|
81
63
|
pruneMuskatImages();
|
|
82
64
|
}
|
|
83
65
|
|
|
84
|
-
/** A running Muscat container (e.g. muscat-dev) that owns host port 10000, or null. */
|
|
85
|
-
export function localMuscatContainer() {
|
|
86
|
-
const r = spawnSync("docker", ["ps", "--filter", `publish=${BRIDGE_PORT}`, "--format", "{{.Names}}"], { encoding: "utf8" });
|
|
87
|
-
return (r.stdout || "").split("\n").map((n) => n.trim()).find((n) => n && n !== BRIDGE_CONTAINER) ?? null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
66
|
/**
|
|
91
67
|
* Build the image, preflight the port, and start the bridge container.
|
|
92
|
-
* Returns false if port
|
|
93
|
-
* whether the MediaMTX ports are free for the backend's own MediaMTX.
|
|
68
|
+
* Returns true if bridge started, false if port was already in use (caller decides).
|
|
94
69
|
*/
|
|
95
70
|
export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
96
71
|
assertDockerReady();
|
|
@@ -113,14 +88,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
113
88
|
}
|
|
114
89
|
const publishable = pluginPorts.filter((port) => !busy.includes(port));
|
|
115
90
|
|
|
116
|
-
|
|
117
|
-
for (const port of MEDIA_PORTS) {
|
|
118
|
-
if (await isPortInUse(port)) mediaBusy.push(port);
|
|
119
|
-
}
|
|
120
|
-
const media = mediaBusy.length === 0;
|
|
121
|
-
if (!media) log.warn(`MediaMTX port(s) already in use: ${mediaBusy.join(", ")} — video streaming disabled in dev.`);
|
|
122
|
-
|
|
123
|
-
await ensureImage();
|
|
91
|
+
ensureImage();
|
|
124
92
|
|
|
125
93
|
log.step("starting Socket.IO bridge container…");
|
|
126
94
|
// The backend joins THIS container's netns (--network container:), and a
|
|
@@ -130,7 +98,6 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
130
98
|
"-p", `127.0.0.1:${BRIDGE_PORT}:${BRIDGE_PORT}`,
|
|
131
99
|
...(zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : []),
|
|
132
100
|
...publishable.flatMap((port) => ["-p", `127.0.0.1:${port}:${port}`]),
|
|
133
|
-
...(media && !usesHostNetwork() ? mediaPortFlags() : []),
|
|
134
101
|
];
|
|
135
102
|
const up = spawnSync(
|
|
136
103
|
"docker",
|
|
@@ -144,7 +111,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
144
111
|
if (zenohPort) log.step(`Zenoh dev peer port published on :${zenohPort}`);
|
|
145
112
|
if (publishable.length > 0)
|
|
146
113
|
log.step(`plugin backend ports published on 127.0.0.1: ${publishable.join(", ")}`);
|
|
147
|
-
return
|
|
114
|
+
return true;
|
|
148
115
|
}
|
|
149
116
|
|
|
150
117
|
export function stopBridge() {
|
package/cli/src/build.mjs
CHANGED
|
@@ -74,36 +74,34 @@ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
|
|
|
74
74
|
// For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
|
|
75
75
|
// rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
|
|
76
76
|
// rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
|
|
77
|
-
// Cross-arch C++ builds run inside an emulated container. Docker Desktop ships the
|
|
78
|
-
// emulation; native Linux needs binfmt/QEMU — check up front so the error says so.
|
|
79
|
-
function assertCanBuildArch(arch, dockerPlatform) {
|
|
80
|
-
const info = spawnSync("docker", ["info", "--format", "{{.Architecture}}"], { encoding: "utf8" });
|
|
81
|
-
const host = { x86_64: "amd64", aarch64: "arm64", arm64: "arm64" }[(info.stdout || "").trim()] ?? "";
|
|
82
|
-
if (arch === host) return;
|
|
83
|
-
const r = spawnSync("docker", ["run", "--rm", "--platform", dockerPlatform, "ubuntu:24.04", "true"], { stdio: "ignore" });
|
|
84
|
-
if (r.status === 0) return;
|
|
85
|
-
throw new Error(
|
|
86
|
-
`can't build ${arch} on this ${host || "machine"}: Docker has no ${arch} emulation.\n` +
|
|
87
|
-
` enable it once: docker run --privileged --rm tonistiigi/binfmt --install ${arch}\n` +
|
|
88
|
-
` or build only this machine's arch: muscat-pk build --arch ${host || "<arch>"}`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
77
|
export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
93
|
-
if (manifestRuntime(root) !== "ros2") return;
|
|
78
|
+
if (manifestRuntime(root) !== "ros2") return [];
|
|
94
79
|
const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
|
|
95
|
-
|
|
80
|
+
try {
|
|
81
|
+
assertDockerReady();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
log.warn(`ros2 build skipped: ${e.message}`);
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
96
86
|
|
|
97
87
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
|
|
98
|
-
|
|
88
|
+
const rosClient = manifest.runtime?.rosClient;
|
|
89
|
+
|
|
90
|
+
const skipped = [];
|
|
91
|
+
if (rosClient === "rclcpp") {
|
|
99
92
|
// C++ ROS2: compile per arch so each robot picks the right install-{arch}/
|
|
100
93
|
for (const arch of archs) {
|
|
101
94
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
102
|
-
if (!dockerPlatform)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
if (!dockerPlatform) { log.warn(`unknown arch '${arch}' — skipped`); skipped.push(arch); continue; }
|
|
96
|
+
const archImage = `${ROS2_DEV_IMAGE}-${arch}`;
|
|
97
|
+
try {
|
|
98
|
+
await ensureImage(archImage, { platform: dockerPlatform });
|
|
99
|
+
colconBuild(root, { symlink: false, arch });
|
|
100
|
+
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
101
|
+
} catch {
|
|
102
|
+
log.warn(`${arch} ros2 build skipped — QEMU not available`);
|
|
103
|
+
skipped.push(arch);
|
|
104
|
+
}
|
|
107
105
|
}
|
|
108
106
|
} else {
|
|
109
107
|
// rclpy / pure Python: arch-agnostic, build once
|
|
@@ -111,38 +109,54 @@ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
|
111
109
|
colconBuild(root, { symlink: false });
|
|
112
110
|
log.ok("dist/ros2_ws/install/ ready");
|
|
113
111
|
}
|
|
112
|
+
return skipped;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
// For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
|
|
117
116
|
// and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
|
|
118
|
-
// robot-compatible executables.
|
|
117
|
+
// robot-compatible executables. rb-runtime prefers dist/{arch}/ and falls back
|
|
118
|
+
// to dist/{entry} (legacy). Skipped (with a warning) if Docker is unavailable.
|
|
119
119
|
export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"]) {
|
|
120
|
-
if (manifestRuntime(root) !== "cpp") return;
|
|
120
|
+
if (manifestRuntime(root) !== "cpp") return [];
|
|
121
121
|
const { assertDockerReady, ensureImage, cmakeBuild, CPP_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./cpp.mjs");
|
|
122
|
-
|
|
122
|
+
try {
|
|
123
|
+
assertDockerReady();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
log.warn(`cpp build skipped: ${e.message}`);
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
123
128
|
|
|
124
129
|
const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
|
|
125
|
-
if (!entry)
|
|
130
|
+
if (!entry) {
|
|
131
|
+
log.warn("manifest.json runtime.entry not set — cpp build skipped");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
126
134
|
|
|
135
|
+
const skipped = [];
|
|
127
136
|
for (const arch of archs) {
|
|
128
137
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
129
|
-
if (!dockerPlatform)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
if (!dockerPlatform) { log.warn(`unknown arch '${arch}' — skipped`); skipped.push(arch); continue; }
|
|
139
|
+
const archImage = `${CPP_DEV_IMAGE}-${arch}`;
|
|
140
|
+
try {
|
|
141
|
+
await ensureImage(archImage, { platform: dockerPlatform });
|
|
142
|
+
cmakeBuild(root, { buildType: "Release", arch });
|
|
143
|
+
const built = resolve(root, `cpp/build-${arch}`, entry);
|
|
144
|
+
if (existsSync(built)) {
|
|
145
|
+
const archDir = resolve(distDir, arch);
|
|
146
|
+
mkdirSync(archDir, { recursive: true });
|
|
147
|
+
const dest = resolve(archDir, entry);
|
|
148
|
+
copyFileSync(built, dest);
|
|
149
|
+
chmodSync(dest, 0o755);
|
|
150
|
+
log.ok(`cpp binary → dist/${arch}/${entry}`);
|
|
151
|
+
} else {
|
|
152
|
+
log.warn(`cpp binary not found at cpp/build-${arch}/${entry}`);
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
log.warn(`${arch} cpp build skipped — QEMU not available`);
|
|
156
|
+
skipped.push(arch);
|
|
136
157
|
}
|
|
137
|
-
const archDir = resolve(distDir, arch);
|
|
138
|
-
mkdirSync(archDir, { recursive: true });
|
|
139
|
-
const dest = resolve(archDir, entry);
|
|
140
|
-
copyFileSync(built, dest);
|
|
141
|
-
chmodSync(dest, 0o755);
|
|
142
|
-
const libDir = resolve(root, `cpp/build-${arch}`, "lib");
|
|
143
|
-
if (existsSync(libDir)) cpSync(libDir, resolve(archDir, "lib"), { recursive: true, verbatimSymlinks: true });
|
|
144
|
-
log.ok(`cpp binary → dist/${arch}/${entry}${existsSync(libDir) ? " (+ lib/)" : ""}`);
|
|
145
158
|
}
|
|
159
|
+
return skipped;
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
|
|
@@ -412,12 +426,11 @@ export async function runBuild(args) {
|
|
|
412
426
|
|
|
413
427
|
await promptVersion(root);
|
|
414
428
|
const archs = await promptArch(args);
|
|
415
|
-
// Drop a previous ZIP of this version so a failed build can't leave a stale one to upload.
|
|
416
|
-
const version = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).version || "0.0.0";
|
|
417
|
-
rmSync(resolve(root, `${basename(root)}-${version}.zip`), { force: true });
|
|
418
429
|
|
|
419
430
|
const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
|
|
420
431
|
|
|
432
|
+
const skippedArches = { wheels: [], ros2: [], cpp: [] };
|
|
433
|
+
|
|
421
434
|
// python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
|
|
422
435
|
if (!noLock) {
|
|
423
436
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
|
|
@@ -426,12 +439,30 @@ export async function runBuild(args) {
|
|
|
426
439
|
(manifest.type === "ros2" && manifest.rosClient === "rclpy");
|
|
427
440
|
if (needsLock) {
|
|
428
441
|
const { lockAndVendor } = await import("./python.mjs");
|
|
429
|
-
|
|
442
|
+
try {
|
|
443
|
+
skippedArches.wheels = await lockAndVendor(pluginRoot, archs);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
log.warn(`python lock/vendor skipped: ${e.message}`);
|
|
446
|
+
skippedArches.wheels = archs;
|
|
447
|
+
}
|
|
430
448
|
}
|
|
431
449
|
}
|
|
432
450
|
|
|
433
|
-
await exportRos2Build(root, archs);
|
|
434
|
-
await exportCppBuild(root, distDir, archs);
|
|
451
|
+
skippedArches.ros2 = await exportRos2Build(root, archs) ?? [];
|
|
452
|
+
skippedArches.cpp = await exportCppBuild(root, distDir, archs) ?? [];
|
|
435
453
|
calcInstalledSize(pluginRoot);
|
|
436
454
|
if (!noZip) packZip(pluginRoot);
|
|
455
|
+
|
|
456
|
+
// Build summary
|
|
457
|
+
const allSkipped = [...new Set([...skippedArches.wheels, ...skippedArches.ros2, ...skippedArches.cpp])];
|
|
458
|
+
if (allSkipped.length > 0) {
|
|
459
|
+
const line = "─".repeat(58);
|
|
460
|
+
console.log(`\n${line}`);
|
|
461
|
+
log.warn(`Cross-arch builds skipped (QEMU not available): ${allSkipped.join(", ")}`);
|
|
462
|
+
if (skippedArches.wheels.length) log.warn(` wheels: ${skippedArches.wheels.join(", ")}`);
|
|
463
|
+
if (skippedArches.ros2.length) log.warn(` ros2: ${skippedArches.ros2.join(", ")}`);
|
|
464
|
+
if (skippedArches.cpp.length) log.warn(` cpp: ${skippedArches.cpp.join(", ")}`);
|
|
465
|
+
log.warn(` Enable: docker run --privileged --rm tonistiigi/binfmt --install all`);
|
|
466
|
+
console.log(line);
|
|
467
|
+
}
|
|
437
468
|
}
|
package/cli/src/cpp.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Used by dev:back (interactive dev loop) and build (export the binary into the ZIP).
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
|
-
import { canSkipImageBuild,
|
|
6
|
+
import { canSkipImageBuild, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
7
7
|
|
|
8
8
|
export const CPP_DEV_IMAGE = "muscat-pk-cpp-dev";
|
|
9
9
|
|
|
@@ -15,19 +15,16 @@ export function assertDockerReady() {
|
|
|
15
15
|
|
|
16
16
|
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
17
17
|
export async function ensureImage(image = CPP_DEV_IMAGE, { platform = null } = {}) {
|
|
18
|
-
|
|
19
|
-
const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
|
|
20
|
-
const key = imageKey(dockerfile, sdkVer ?? "");
|
|
21
|
-
if (canSkipImageBuild(image, key)) {
|
|
18
|
+
if (canSkipImageBuild(image)) {
|
|
22
19
|
log.step(`${image} already built — skipping docker build`);
|
|
23
20
|
return;
|
|
24
21
|
}
|
|
22
|
+
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
25
23
|
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
26
24
|
const platformFlags = platform ? ["--platform", platform] : [];
|
|
27
|
-
const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
|
|
28
25
|
const r = spawnSync(
|
|
29
26
|
"docker",
|
|
30
|
-
["build", ...platformFlags,
|
|
27
|
+
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
31
28
|
{ stdio: "inherit" },
|
|
32
29
|
);
|
|
33
30
|
if (r.status !== 0) throw new Error("docker build failed.");
|
|
@@ -53,12 +50,9 @@ export function cmakeBuild(root, { image = CPP_DEV_IMAGE, buildType = "Debug", a
|
|
|
53
50
|
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
54
51
|
// arch 빌드 시 arch별 이미지 사용 (로컬 이미지와 platform 불일치 방지)
|
|
55
52
|
const actualImage = arch ? `${CPP_DEV_IMAGE}-${arch}` : image;
|
|
56
|
-
// Export builds ship lib/ next to the binary: librb_sdk.so (robot has no SDK) + any .so the
|
|
57
|
-
// plugin copied there itself. Found via RPATH $ORIGIN/lib.
|
|
58
53
|
const script = [
|
|
59
|
-
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}
|
|
54
|
+
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}`,
|
|
60
55
|
`cmake --build ${buildDir} -j`,
|
|
61
|
-
...(arch ? [`rm -f ${buildDir}/lib/librb_sdk.so.* && if readelf -d $(find ${buildDir} -maxdepth 1 -type f -executable) 2>/dev/null | grep -q librb_sdk; then mkdir -p ${buildDir}/lib && cp -a $(dpkg -L rainbow-rb-sdk | grep -E '/librb_sdk[.]so[.][0-9]') ${buildDir}/lib/; fi`] : []),
|
|
62
56
|
].join(" && ");
|
|
63
57
|
const label = arch ? `cmake build (${buildType}, ${arch}, in container)…` : `cmake build (${buildType}, in container)…`;
|
|
64
58
|
log.step(label);
|
package/cli/src/dev.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { runDevUi } from "./devUi.mjs";
|
|
8
8
|
import { runDevBack, stopDevBackContainers } from "./devBack.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
|
|
10
10
|
import { assertManifestPorts, assertManifestVariables } from "./manifest.mjs";
|
|
11
11
|
import { formatPortRanges } from "./ports.mjs";
|
|
12
12
|
import {
|
|
@@ -138,11 +138,7 @@ export async function runDev(args = []) {
|
|
|
138
138
|
pluginPorts: usesHostNetwork() ? [] : portRanges,
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
if (muscat) {
|
|
143
|
-
log.step(`local Muscat container '${muscat}' owns :${BRIDGE_PORT} — backend joins its network (gateway, MediaMTX)`);
|
|
144
|
-
process.env.MUSCAT_PK_MUSCAT_CONTAINER = muscat;
|
|
145
|
-
} else if (!bridgeStarted) {
|
|
141
|
+
if (!bridgeStarted) {
|
|
146
142
|
log.warn(`port ${BRIDGE_PORT} is already in use — can't start Socket.IO bridge.`);
|
|
147
143
|
const answer = await prompt(" Proceed without Socket.IO bridge? [y/N]: ");
|
|
148
144
|
if (answer.toLowerCase() !== "y") return;
|
|
@@ -151,7 +147,6 @@ export async function runDev(args = []) {
|
|
|
151
147
|
// Tell dev:back which bridge container to share a netns with, so the backend
|
|
152
148
|
// reaches the bridge at localhost:10000 (no host.docker.internal).
|
|
153
149
|
process.env.MUSCAT_PK_BRIDGE_CONTAINER = BRIDGE_CONTAINER;
|
|
154
|
-
if (bridgeStarted.media) process.env.MUSCAT_PK_MEDIA = "1";
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|