@rainbow-robotics/muscat-plugin-kit 1.3.6-dev.8 → 1.3.6
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 +8 -3
- package/cli/bin/muscat-pk.mjs +5 -0
- package/cli/bundle-templates.mjs +6 -1
- package/cli/docker/Dockerfile.cpp +28 -6
- package/cli/docker/Dockerfile.dev +32 -0
- package/cli/docker/Dockerfile.python +19 -0
- package/cli/src/bridge.mjs +45 -12
- package/cli/src/build.mjs +48 -79
- package/cli/src/cpp.mjs +11 -5
- package/cli/src/dev.mjs +7 -2
- package/cli/src/devBack.mjs +97 -40
- package/cli/src/host_agent.py +143 -83
- package/cli/src/manifest.mjs +11 -0
- package/cli/src/mcp.mjs +398 -54
- package/cli/src/new.mjs +13 -3
- package/cli/src/ports.mjs +21 -7
- package/cli/src/python.mjs +16 -20
- package/cli/src/ros2.mjs +16 -8
- package/cli/src/sdk.mjs +23 -21
- package/cli/src/type.mjs +44 -10
- package/cli/src/uninstall.mjs +3 -20
- package/cli/src/update.mjs +14 -8
- package/cli/src/util.mjs +70 -9
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +13 -1
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +98 -1
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-Du8dJ-nM.js → Color-YHDXOIA2-CdTgUqrV.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Sy5RDbtH.js → Docs-C8wx5zsC.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-guGflRzZ.js → Docs-CbRKwHpQ.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Cb72AJnj.js → Docs-Cf0s04QW.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-pwGqGTV_.js → Docs-CsxMprX5.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-LdjYwuG4.js → Docs-FyPFrbUS.js} +1 -1
- package/cli/storybook-dist/assets/{Docs--Fm8069p.js → Docs-YtqU-aVu.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-YGBLQ4BD.js → DocsRenderer-CFRXHY34-VSENNRoY.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-C1d4n8cq.js → NxAvatar.stories-oCcYtLwq.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-gCBoZdMq.js → NxButton.stories-C5jUScIj.js} +1 -1
- package/cli/storybook-dist/assets/{NxIcons.stories-Dg42riT5.js → NxIcons.stories-DeqrVzVs.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-DB-vLM2J.js → NxNumberInput.stories-gjsEulIN.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-CS8iMIak.js → NxPagination.stories-BK6uSWIa.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-D66BopeS.js → NxPointerHoldButton.stories-BDD5CvSZ.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-CZxddsJ2.js → NxRangeInput.stories-C_nW26_x.js} +3 -3
- package/cli/storybook-dist/assets/{NxSelectBox.stories-CmezGGrm.js → NxSelectBox.stories-DqLfKU9H.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CGAFMITi.js → NxSlideRange.stories-CQjHA8yv.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-B3QInkXX.js → NxSwitch.stories-BMHCiPoL.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-BgJcgZNe.js → NxTextArea.stories-k7r1WVQh.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-CwMQwI9L.js → NxTextInput.stories-Cqfe1I32.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-BuBVNzJT.js → RBAccordion.stories-CJ3Ttuja.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-Crdb6kFF.js → RBAccordionGroup.stories-BThv7tM0.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-CqXgUvA2.js → RBAlert.stories-CSMP8Vun.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-BCw_ksML.js → RBBadge.stories-cBMA2R6F.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-0id4XYoc.js → RBBattery.stories-BKVOskqs.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-CgpG-7Lk.js → RBCard.stories-BTOfK0HC.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-BG02BoGX.js → RBCheckBox.stories-DyE912wG.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-vrjD4GQ4.js → RBCheckIconButton.stories-0sLFU0kM.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-POba8lU-.js → RBCheckLabelButton.stories-c8VecClE.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-XWJbFrHF.js → RBChipTab.stories-CQ9Uav5_.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-BdU0Dzdp.js → RBChipsBoxTab.stories-Ap4NlhAZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-C_0dCNXG.js → RBColorIcons.stories-BPPXjif2.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-Dvrko5No.js → RBCommand.stories-PDpYOKlm.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-CkTn0sHa.js → RBConfirm.stories-BEWvMAYP.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-BH3D08cs.js → RBDateInput.stories-DLdyNnVF.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-C2nNX9fl.js → RBDialog.stories-HsawMNOb.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-D-GrJ0QZ.js → RBDivider.stories-dlWwOxik.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-XLOnLOnh.js → RBDrawer.stories-C87iqGk0.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-BjBSeoV2.js → RBDropdownMenu.stories-B8EQhA0z.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-C6U2zKNE.js → RBEmptyView.stories-SzpIhNDw.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-MCJq5Hta.js → RBIconButton.stories-BQ0nM1gb.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-DxOCiCe0.js → RBIcons.stories-C19g5M5z.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-BVZi1aEs.js → RBInlineLabelButton.stories-DbgI4-1P.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-BDThbEek.js → RBJoyStick.stories-smF-vpGL.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-CaPEKUS6.js → RBLabelButton.stories-s53klwDv.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-CihQzyEL.js → RBLight.stories-BnzkoR51.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-J852XVFh.js → RBMenubar.stories-CW014klu.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-DXyX0cOZ.js → RBMultiRangeInput.stories-qa4tH_4Y.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-C9_49ymO.js → RBPagination.stories-YoQNUpQH.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-DxLE912-.js → RBPopover.stories-Bhpi0OaH.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-DlsfGqWb.js → RBPrompt.stories-iQM5iAka.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-hb5cNWW1.js → RBRadioBox.stories-BGyq4rzx.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-CSuOgeN-.js → RBRadioButton.stories-DF6BcUcJ.js} +1 -1
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-COOQ_HNF.js +15 -0
- package/cli/storybook-dist/assets/{RBResizable.stories-BetjF9vG.js → RBResizable.stories-DSVHvwjp.js} +1 -1
- package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +74 -0
- package/cli/storybook-dist/assets/{RBSheet.stories-D3QbcMKq.js → RBSheet.stories-CAK9hb-p.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-DcTh0IjM.js → RBSideNumberInput.stories-DUymnH-9.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-B59We7ei.js → RBSlider.stories-BwlDh9--.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-9u3_ETbi.js → RBSpinner.stories-VOlkuP6t.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-DrVm1WGX.js → RBSwitch.stories-BiW2dQnT.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-D8Dm7cRW.js → RBTab.stories-DFKVsy0C.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-dRdZ8g1j.js → RBTable.stories-BWG0ZSYI.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-rZeoGwHZ.js → RBTableRow.stories-JVTD83Mh.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-STRV2lgO.js → RBTabs.stories-D-aN385u.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-CfgUbrbJ.js → RBTextArea.stories-Dx5xkmZS.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-B5t8-Y97.js → RBTextField.stories-DmVciExd.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-Bf1edN6l.js → RBTextInput.stories-CCf0VGEv.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-Cnm8JwQ6.js → RBToast.stories-DT0DGn05.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-B0OCo6zd.js → RBToggle.stories-Ce_q3EtP.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-GdYO2Fxg.js → RBToggleGroup.stories-CN40YHsR.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-DXJTCScO.js → RBTooltip.stories-L7btDEpA.js} +1 -1
- package/cli/storybook-dist/assets/SideNumberInput-B5_xLK8z.js +1109 -0
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-DSRwIaVJ.js → entry-preview-raBQdKjS.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-Cj53Wn3d.js → iframe-BmIue__l.js} +2 -2
- package/cli/storybook-dist/assets/{index-CkHtLEe8.js → index-CSIe-YDr.js} +3 -3
- package/cli/storybook-dist/assets/{index-C1OzuK6-.js → index-ClAc6FBW.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CILRoSnB.js → preview-BVJwlGUF.js} +1 -1
- package/cli/storybook-dist/assets/{preview-D_2um8Sd.js → preview-CUgthyJT.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CgOtgtCD.js → preview-DD51RTKv.js} +2 -2
- package/cli/storybook-dist/assets/preview-zuAvMksL.css +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +120 -0
- package/cli/storybook-dist/iframe.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 +211 -7
- package/cli/templates/common/py/main.py +29 -2
- package/cli/templates/cpp/cpp/CMakeLists.txt +6 -7
- package/cli/templates/cpp/cpp/main.cpp +57 -10
- package/cli/templates/readme-cpp/README.md +993 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +4 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +5 -4
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-KRg2glX8.js +0 -15
- package/cli/storybook-dist/assets/RBSelectBox.stories-ILLQQ8M8.js +0 -6
- package/cli/storybook-dist/assets/SideNumberInput-CrF-a1Nr.js +0 -1114
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +0 -1
- package/cli/storybook-dist/assets/preview-CEfMaO1t.css +0 -1
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CtORL3tG.js +0 -102
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; `plugin.router` exposes external endpoints (`common/plugin/{id}/{path}`, needs sdk ≥ 0.0.9.dev5 on the robot; the dev bridge only registers routes — external calls need a running `common`) |
|
|
64
|
+
| `{ "type": "cpp", "entry": "<executable name>" }` | `cpp/` | Built with CMake in Docker against the C++ SDK (apt `rainbow-rb-sdk-dev`, `find_package(rb_sdk)`, dynamic link) |
|
|
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, links the C++ SDK like `cpp` |
|
|
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,6 +257,11 @@ 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
|
+
|
|
260
265
|
On Linux the dev containers run as the invoking user (`--user`), so build output
|
|
261
266
|
under `dist/` and `cpp/build-*` is not left root-owned.
|
|
262
267
|
|
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -35,6 +35,11 @@ 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
|
+
|
|
38
43
|
(function delegateToLocalInstall() {
|
|
39
44
|
try {
|
|
40
45
|
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
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
const root = resolve(fileURLToPath(import.meta.url), "..");
|
|
@@ -19,10 +19,15 @@ 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"));
|
|
22
26
|
|
|
23
27
|
writeFileSync(out,
|
|
24
28
|
`// AUTO-GENERATED by cli/bundle-templates.mjs — do not edit.\n` +
|
|
25
29
|
`// ponytail: generated at publish time; not tracked in git.\n` +
|
|
30
|
+
`export const KIT_VERSION = ${JSON.stringify(version)};\n` +
|
|
26
31
|
`export const TEMPLATES = ${JSON.stringify(files)};\n`
|
|
27
32
|
);
|
|
28
33
|
console.log(`bundled ${Object.keys(files).length} template files → src/templates-embedded.mjs`);
|
|
@@ -10,12 +10,34 @@ 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
|
-
|
|
13
|
+
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
14
|
+
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
15
|
+
ARG RB_SDK_VERSION=0.1.7
|
|
16
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
17
|
+
&& install -d /etc/apt/keyrings \
|
|
18
|
+
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
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
|
|
19
41
|
|
|
20
42
|
WORKDIR /plugin
|
|
21
43
|
|
|
@@ -9,6 +9,38 @@ 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.7
|
|
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
|
+
|
|
12
44
|
WORKDIR /plugin
|
|
13
45
|
|
|
14
46
|
CMD ["bash"]
|
|
@@ -15,6 +15,25 @@ 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
|
+
|
|
18
37
|
WORKDIR /plugin
|
|
19
38
|
|
|
20
39
|
CMD ["bash"]
|
package/cli/src/bridge.mjs
CHANGED
|
@@ -5,24 +5,41 @@
|
|
|
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 } from "node:fs";
|
|
9
|
-
import {
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
10
11
|
import { canSkipImageBuild, hasBin, kitRoot, log, pruneMuskatImages } from "./util.mjs";
|
|
12
|
+
import { MEDIA_PORTS, mediaPortFlags } from "./ports.mjs";
|
|
13
|
+
import { usesHostNetwork } from "./zenohDev.mjs";
|
|
11
14
|
|
|
12
15
|
export const BRIDGE_IMAGE = "muscat-pk-bridge";
|
|
13
16
|
export const BRIDGE_CONTAINER = "muscat-pk-bridge-run";
|
|
14
17
|
export const BRIDGE_PORT = 10000;
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
const
|
|
19
|
+
// 컴파일된 바이너리에는 cli/ 트리가 없어 임베드 스냅샷에서 build context 를 tmp 에 복원한다.
|
|
20
|
+
const BRIDGE_CONTEXT_FILES = ["docker/Dockerfile.bridge", "docker/bridge-package.json", "src/socketBridge.mjs"];
|
|
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
|
+
}
|
|
18
38
|
|
|
19
39
|
function assertDockerReady() {
|
|
20
40
|
if (!hasBin("docker")) {
|
|
21
41
|
throw new Error("Docker not found. `muscat-pk dev` runs the Socket.IO bridge in a container.");
|
|
22
42
|
}
|
|
23
|
-
if (!existsSync(DOCKERFILE)) {
|
|
24
|
-
throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
|
|
25
|
-
}
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
// Is a host TCP port already bound? (Catches another bridge or a stray process.)
|
|
@@ -52,20 +69,28 @@ function waitForPort(port, timeoutMs = 15_000) {
|
|
|
52
69
|
});
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
function ensureImage() {
|
|
72
|
+
async function ensureImage() {
|
|
56
73
|
if (canSkipImageBuild(BRIDGE_IMAGE)) {
|
|
57
74
|
log.step(`${BRIDGE_IMAGE} already built — skipping docker build`);
|
|
58
75
|
return;
|
|
59
76
|
}
|
|
77
|
+
const { dockerfile, context } = await resolveBridgeContext();
|
|
60
78
|
log.step(`docker build -t ${BRIDGE_IMAGE} (first run is slow, then cached)…`);
|
|
61
|
-
const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f",
|
|
79
|
+
const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f", dockerfile, context], { stdio: "inherit" });
|
|
62
80
|
if (r.status !== 0) throw new Error("docker build failed (bridge image).");
|
|
63
81
|
pruneMuskatImages();
|
|
64
82
|
}
|
|
65
83
|
|
|
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
|
+
|
|
66
90
|
/**
|
|
67
91
|
* Build the image, preflight the port, and start the bridge container.
|
|
68
|
-
* Returns
|
|
92
|
+
* Returns false if port 10000 was already in use (caller decides), else { media }:
|
|
93
|
+
* whether the MediaMTX ports are free for the backend's own MediaMTX.
|
|
69
94
|
*/
|
|
70
95
|
export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
71
96
|
assertDockerReady();
|
|
@@ -88,7 +113,14 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
88
113
|
}
|
|
89
114
|
const publishable = pluginPorts.filter((port) => !busy.includes(port));
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
const mediaBusy = [];
|
|
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();
|
|
92
124
|
|
|
93
125
|
log.step("starting Socket.IO bridge container…");
|
|
94
126
|
// The backend joins THIS container's netns (--network container:), and a
|
|
@@ -98,6 +130,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
98
130
|
"-p", `127.0.0.1:${BRIDGE_PORT}:${BRIDGE_PORT}`,
|
|
99
131
|
...(zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : []),
|
|
100
132
|
...publishable.flatMap((port) => ["-p", `127.0.0.1:${port}:${port}`]),
|
|
133
|
+
...(media && !usesHostNetwork() ? mediaPortFlags() : []),
|
|
101
134
|
];
|
|
102
135
|
const up = spawnSync(
|
|
103
136
|
"docker",
|
|
@@ -111,7 +144,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
111
144
|
if (zenohPort) log.step(`Zenoh dev peer port published on :${zenohPort}`);
|
|
112
145
|
if (publishable.length > 0)
|
|
113
146
|
log.step(`plugin backend ports published on 127.0.0.1: ${publishable.join(", ")}`);
|
|
114
|
-
return
|
|
147
|
+
return { media };
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
export function stopBridge() {
|
package/cli/src/build.mjs
CHANGED
|
@@ -74,34 +74,36 @@ 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
|
+
|
|
77
92
|
export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
78
|
-
if (manifestRuntime(root) !== "ros2") return
|
|
93
|
+
if (manifestRuntime(root) !== "ros2") return;
|
|
79
94
|
const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
|
|
80
|
-
|
|
81
|
-
assertDockerReady();
|
|
82
|
-
} catch (e) {
|
|
83
|
-
log.warn(`ros2 build skipped: ${e.message}`);
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
95
|
+
assertDockerReady();
|
|
86
96
|
|
|
87
97
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const skipped = [];
|
|
91
|
-
if (rosClient === "rclcpp") {
|
|
98
|
+
if (manifest.runtime?.rosClient === "rclcpp") {
|
|
92
99
|
// C++ ROS2: compile per arch so each robot picks the right install-{arch}/
|
|
93
100
|
for (const arch of archs) {
|
|
94
101
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
95
|
-
if (!dockerPlatform)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
}
|
|
102
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
103
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
104
|
+
await ensureImage(`${ROS2_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
105
|
+
colconBuild(root, { symlink: false, arch });
|
|
106
|
+
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
105
107
|
}
|
|
106
108
|
} else {
|
|
107
109
|
// rclpy / pure Python: arch-agnostic, build once
|
|
@@ -109,54 +111,38 @@ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
|
109
111
|
colconBuild(root, { symlink: false });
|
|
110
112
|
log.ok("dist/ros2_ws/install/ ready");
|
|
111
113
|
}
|
|
112
|
-
return skipped;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
|
|
116
117
|
// and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
|
|
117
|
-
// robot-compatible executables.
|
|
118
|
-
// to dist/{entry} (legacy). Skipped (with a warning) if Docker is unavailable.
|
|
118
|
+
// robot-compatible executables.
|
|
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
|
-
|
|
123
|
-
assertDockerReady();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
log.warn(`cpp build skipped: ${e.message}`);
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
122
|
+
assertDockerReady();
|
|
128
123
|
|
|
129
124
|
const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
|
|
130
|
-
if (!entry)
|
|
131
|
-
log.warn("manifest.json runtime.entry not set — cpp build skipped");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
125
|
+
if (!entry) throw new Error("manifest.json runtime.entry is required for cpp (the built executable name)");
|
|
134
126
|
|
|
135
|
-
const skipped = [];
|
|
136
127
|
for (const arch of archs) {
|
|
137
128
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
138
|
-
if (!dockerPlatform)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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);
|
|
129
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
130
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
131
|
+
await ensureImage(`${CPP_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
132
|
+
cmakeBuild(root, { buildType: "Release", arch });
|
|
133
|
+
const built = resolve(root, `cpp/build-${arch}`, entry);
|
|
134
|
+
if (!existsSync(built)) {
|
|
135
|
+
throw new Error(`cpp/build-${arch}/${entry} not found — the CMake target name must equal runtime.entry`);
|
|
157
136
|
}
|
|
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/)" : ""}`);
|
|
158
145
|
}
|
|
159
|
-
return skipped;
|
|
160
146
|
}
|
|
161
147
|
|
|
162
148
|
const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
|
|
@@ -426,11 +412,12 @@ export async function runBuild(args) {
|
|
|
426
412
|
|
|
427
413
|
await promptVersion(root);
|
|
428
414
|
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 });
|
|
429
418
|
|
|
430
419
|
const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
|
|
431
420
|
|
|
432
|
-
const skippedArches = { wheels: [], ros2: [], cpp: [] };
|
|
433
|
-
|
|
434
421
|
// python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
|
|
435
422
|
if (!noLock) {
|
|
436
423
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
|
|
@@ -439,30 +426,12 @@ export async function runBuild(args) {
|
|
|
439
426
|
(manifest.type === "ros2" && manifest.rosClient === "rclpy");
|
|
440
427
|
if (needsLock) {
|
|
441
428
|
const { lockAndVendor } = await import("./python.mjs");
|
|
442
|
-
|
|
443
|
-
skippedArches.wheels = await lockAndVendor(pluginRoot, archs);
|
|
444
|
-
} catch (e) {
|
|
445
|
-
log.warn(`python lock/vendor skipped: ${e.message}`);
|
|
446
|
-
skippedArches.wheels = archs;
|
|
447
|
-
}
|
|
429
|
+
await lockAndVendor(pluginRoot, archs);
|
|
448
430
|
}
|
|
449
431
|
}
|
|
450
432
|
|
|
451
|
-
|
|
452
|
-
|
|
433
|
+
await exportRos2Build(root, archs);
|
|
434
|
+
await exportCppBuild(root, distDir, archs);
|
|
453
435
|
calcInstalledSize(pluginRoot);
|
|
454
436
|
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
|
-
}
|
|
468
437
|
}
|
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, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
6
|
+
import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
7
7
|
|
|
8
8
|
export const CPP_DEV_IMAGE = "muscat-pk-cpp-dev";
|
|
9
9
|
|
|
@@ -15,16 +15,19 @@ 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
|
-
|
|
18
|
+
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
19
|
+
const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
|
|
20
|
+
const key = imageKey(dockerfile, sdkVer ?? "");
|
|
21
|
+
if (canSkipImageBuild(image, key)) {
|
|
19
22
|
log.step(`${image} already built — skipping docker build`);
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
22
|
-
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
23
25
|
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
24
26
|
const platformFlags = platform ? ["--platform", platform] : [];
|
|
27
|
+
const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
|
|
25
28
|
const r = spawnSync(
|
|
26
29
|
"docker",
|
|
27
|
-
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
30
|
+
["build", ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
28
31
|
{ stdio: "inherit" },
|
|
29
32
|
);
|
|
30
33
|
if (r.status !== 0) throw new Error("docker build failed.");
|
|
@@ -50,9 +53,12 @@ export function cmakeBuild(root, { image = CPP_DEV_IMAGE, buildType = "Debug", a
|
|
|
50
53
|
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
51
54
|
// arch 빌드 시 arch별 이미지 사용 (로컬 이미지와 platform 불일치 방지)
|
|
52
55
|
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.
|
|
53
58
|
const script = [
|
|
54
|
-
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}
|
|
59
|
+
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}` + (arch ? ` -DCMAKE_BUILD_RPATH='$ORIGIN/lib'` : ""),
|
|
55
60
|
`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`] : []),
|
|
56
62
|
].join(" && ");
|
|
57
63
|
const label = arch ? `cmake build (${buildType}, ${arch}, in container)…` : `cmake build (${buildType}, in container)…`;
|
|
58
64
|
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 { startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
|
|
9
|
+
import { localMuscatContainer, 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,7 +138,11 @@ export async function runDev(args = []) {
|
|
|
138
138
|
pluginPorts: usesHostNetwork() ? [] : portRanges,
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
const muscat = bridgeStarted ? null : localMuscatContainer();
|
|
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) {
|
|
142
146
|
log.warn(`port ${BRIDGE_PORT} is already in use — can't start Socket.IO bridge.`);
|
|
143
147
|
const answer = await prompt(" Proceed without Socket.IO bridge? [y/N]: ");
|
|
144
148
|
if (answer.toLowerCase() !== "y") return;
|
|
@@ -147,6 +151,7 @@ export async function runDev(args = []) {
|
|
|
147
151
|
// Tell dev:back which bridge container to share a netns with, so the backend
|
|
148
152
|
// reaches the bridge at localhost:10000 (no host.docker.internal).
|
|
149
153
|
process.env.MUSCAT_PK_BRIDGE_CONTAINER = BRIDGE_CONTAINER;
|
|
154
|
+
if (bridgeStarted.media) process.env.MUSCAT_PK_MEDIA = "1";
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|