diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 487a0c3..9c01a9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,3 +51,51 @@ jobs: run: bin/${{ matrix.workspace }}/build.sh - name: Test run: bin/${{ matrix.workspace }}/test.sh + + # The rig's objectives, checked by the StepIt Editor's validator: the + # editor's own checks, then BehaviorTree.CPP loading every file, with the + # node models of the rig's behaviors, the library and the version the + # commander loads them with. A broken objective, e.g. an unknown node or + # port, or a missing subtree, fails here instead of on the robot. + objectives: + name: objectives + runs-on: ubuntu-24.04 + container: ros:jazzy-ros-base + defaults: + run: + shell: bash + steps: + # nodejs runs the JavaScript actions below under act, which does not + # provide one in a container as GitHub does; setup-node installs the + # Node.js the editor uses. + - name: Install git, BehaviorTree.CPP and Node.js + run: apt-get update -qq && apt-get install -qq -y git ros-jazzy-behaviortree-cpp nodejs + - uses: actions/checkout@v4 + # Only the editor's submodule, over HTTPS, as for the commander above. + - name: Check out the editor + run: | + git config --global --add safe.directory '*' + git config --global url.https://github.com/.insteadOf git@github.com: + git submodule update --init --depth 1 modules/stepit-editor + - uses: pnpm/action-setup@v4 + with: + package_json_file: modules/stepit-editor/package.json + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: modules/stepit-editor/pnpm-lock.yaml + # Steps bypass the image's entrypoint, which would source ROS: the + # validator needs it to build, and to find the library when it runs. + - name: Build the validator + working-directory: modules/stepit-editor + run: | + source /opt/ros/jazzy/setup.bash + pnpm install --frozen-lockfile + cmake -S validator -B build/validator -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/ros/jazzy + cmake --build build/validator --parallel + - name: Validate the objectives + working-directory: modules/stepit-editor + run: | + source /opt/ros/jazzy/setup.bash + pnpm run validate "$GITHUB_WORKSPACE/src/plugins/stepit_objectives/objectives" diff --git a/CLAUDE.md b/CLAUDE.md index 22bb1e6..802bddd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,7 +128,9 @@ they reach the real robot: run them with `ROS_DOMAIN_ID=77`. `.github/workflows`: `ci.yml` builds and tests each workspace with the `bin/` scripts in `ros:jazzy-ros-base` (the `plugins` job builds `modules/stepit-commander` first, checked out over HTTPS: `.gitmodules` lists SSH URLs); `ci-format.yml` runs pre-commit without the ament hooks; -`ci-ros-lint.yml` runs those, per package. A new package must be added to the package list +`ci-ros-lint.yml` runs those, per package. The `objectives` job of `ci.yml` builds the editor's native +validator (submodule `stepit-editor`) against the ROS package of BehaviorTree.CPP and runs +`validate` on `src/plugins/stepit_objectives/objectives`: a broken objective fails CI. A new package must be added to the package list of `ci-ros-lint.yml` and to the paths of the ament hooks in `.pre-commit-config.yaml`. ## Conventions diff --git a/README.md b/README.md index 0433dfd..7792838 100644 --- a/README.md +++ b/README.md @@ -480,7 +480,8 @@ only overrides: [`src/plugins`](src/plugins) holds the rig's behaviors and objectives, built on top of the commander's workspace; - the folder the editor opens: the rig's `src/plugins/stepit_objectives/objectives`, - instead of the editor's examples; + instead of the editor's examples, through `BEHAVIORS_DIR`, with this repo + mounted at `~/rig`; - a service of the rig's own, `stepit-macro`, on an image defined in [`docker/Dockerfile`](docker/Dockerfile): it mounts this repo at `~/ws`, and `/dev/input` from the host for the gamepad, and runs @@ -508,14 +509,28 @@ modules: | Workflow | What it checks | |---|---| -| [`ci.yml`](.github/workflows/ci.yml) | Builds and tests both workspaces, each in a job of its own, with the scripts of [`bin`](bin), in a `ros:jazzy-ros-base` container. The job of `src/plugins` first builds the commander's workspace from the `stepit-commander` submodule. | +| [`ci.yml`](.github/workflows/ci.yml) | Builds and tests both workspaces, each in a job of its own, with the scripts of [`bin`](bin), in a `ros:jazzy-ros-base` container. The job of `src/plugins` first builds the commander's workspace from the `stepit-commander` submodule. A third job, `objectives`, validates the objectives with the StepIt Editor's validator, see below. | | [`ci-format.yml`](.github/workflows/ci-format.yml) | The pre-commit hooks that need no ROS: clang-format, black, codespell, and the checks of whitespace and files. | | [`ci-ros-lint.yml`](.github/workflows/ci-ros-lint.yml) | The ament linters of every package: copyright, lint_cmake and cpplint. | The build cannot use `industrial_ci`, as the modules do: it builds one workspace from the sources of the repo, and `src/plugins` is built on top of -the commander's. CI checks out only the `stepit-commander` submodule, over +the commander's. CI checks out only the `stepit-commander` and `stepit-editor` submodules, over HTTPS; the other modules have CI of their own. +The `objectives` job runs the editor's validator, `validate`, on +[`src/plugins/stepit_objectives/objectives`](src/plugins/stepit_objectives/objectives): +the editor's own checks, then BehaviorTree.CPP loading every file with the node +models of the rig's behaviors, built from the ROS package, the library and the +version the commander loads them with. A broken objective, e.g. an unknown node +or port, or a subtree that does not exist, fails the pull request instead of +being refused by the commander on the robot. Run the same check by hand in the +editor's container, where it validates that folder by default: + +``` +./docker/dock.sh shell stepit-editor +validate.sh +``` + The workflows run locally with [Nektos `act`](https://github.com/nektos/act), e.g. `act -W .github/workflows/ci.yml`. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 635dbfe..334aee2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -112,10 +112,15 @@ services: init: true depends_on: - stepit-commander + # Edit the rig's objectives: the commander reads them again before each + # goal, so what the editor saves is what the Run button executes. The whole + # repo is mounted, not the folder alone: git deletes and recreates a folder + # when a branch it switches to lacks it, and a mount of the folder would + # then show the deleted one, empty, until the container is recreated. volumes: - # Edit the rig's objectives: the commander reads them again before each - # goal, so what the editor saves is what the Run button executes. - - ../src/plugins/stepit_objectives/objectives:/home/developer/behaviors + - ..:/home/developer/rig + environment: + BEHAVIORS_DIR: /home/developer/rig/src/plugins/stepit_objectives/objectives command: serve.sh # The camera driver, with its test page and the pictures on diff --git a/modules/stepit-editor b/modules/stepit-editor index 83a2a03..c4c6d7e 160000 --- a/modules/stepit-editor +++ b/modules/stepit-editor @@ -1 +1 @@ -Subproject commit 83a2a03cdc9e7d027b588c22aafab7b671d9721d +Subproject commit c4c6d7e68dbb5340dda07fd169e16faf7fbe11b6