diff --git a/.github/workflows/build-cartographer-ws.yaml b/.github/workflows/build-cartographer-ws.yaml index 2c91b739..6bc892e8 100644 --- a/.github/workflows/build-cartographer-ws.yaml +++ b/.github/workflows/build-cartographer-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for cartographer-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-cartographer-ws.yaml - cartographer_ws/docker/Dockerfile diff --git a/.github/workflows/build-husky-ws.yaml b/.github/workflows/build-husky-ws.yaml index 1b04d77c..620be474 100644 --- a/.github/workflows/build-husky-ws.yaml +++ b/.github/workflows/build-husky-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for husky-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-husky-ws.yaml - husky_ws/docker/Dockerfile diff --git a/.github/workflows/build-kobuki-ws.yaml b/.github/workflows/build-kobuki-ws.yaml index 02843463..7449e031 100644 --- a/.github/workflows/build-kobuki-ws.yaml +++ b/.github/workflows/build-kobuki-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for kobuki-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-kobuki-ws.yaml - kobuki_ws/docker/Dockerfile diff --git a/.github/workflows/build-orbslam3-ws.yaml b/.github/workflows/build-orbslam3-ws.yaml index e2e31ebc..76b9c405 100644 --- a/.github/workflows/build-orbslam3-ws.yaml +++ b/.github/workflows/build-orbslam3-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for orbslam3-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-orbslam3-ws.yaml - orbslam3_ws/docker/Dockerfile diff --git a/.github/workflows/build-ros1-bridge-ws.yaml b/.github/workflows/build-ros1-bridge-ws.yaml index 598a0088..97b5f676 100644 --- a/.github/workflows/build-ros1-bridge-ws.yaml +++ b/.github/workflows/build-ros1-bridge-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for ros1-bridge-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-ros1-bridge-ws.yaml - ros1_bridge_ws/docker/Dockerfile diff --git a/.github/workflows/build-rtabmap-ws.yaml b/.github/workflows/build-rtabmap-ws.yaml index 21dd74e4..48295687 100644 --- a/.github/workflows/build-rtabmap-ws.yaml +++ b/.github/workflows/build-rtabmap-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for rtabmap-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-rtabmap-ws.yaml - rtabmap_ws/docker/Dockerfile diff --git a/.github/workflows/build-template-ws.yaml b/.github/workflows/build-template-ws.yaml index 159d4eed..9a303fce 100644 --- a/.github/workflows/build-template-ws.yaml +++ b/.github/workflows/build-template-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for template-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-template-ws.yaml - template_ws/docker/Dockerfile diff --git a/.github/workflows/build-vlp-ws.yaml b/.github/workflows/build-vlp-ws.yaml index 33aeef7d..f09f4940 100644 --- a/.github/workflows/build-vlp-ws.yaml +++ b/.github/workflows/build-vlp-ws.yaml @@ -3,7 +3,7 @@ name: Build Docker Image for vlp-ws on: push: branches: - - "master" + - "main" paths: - .github/workflows/build-vlp-ws.yaml - vlp_ws/docker/Dockerfile diff --git a/.github/workflows/test-common.yaml b/.github/workflows/test-common.yaml index d7f164a1..e1c1c965 100644 --- a/.github/workflows/test-common.yaml +++ b/.github/workflows/test-common.yaml @@ -15,5 +15,5 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Lint filenames - run: python tests/lint_filenames.py \ No newline at end of file + - name: Run tests + run: tests/test_all.sh \ No newline at end of file diff --git a/README.md b/README.md index 4cd3bddc..0167252a 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ The following Docker images can be pulled by `docker pull ` or by `d Pulling the pre-built Docker images can bypass the time-consuming building process (for both docker compose & devcontainers). -| Docker Image | Description | amd64 | arm64 | -|--------------|-------------|-------|-------| -| [j3soon/ros2-template-ws](https://hub.docker.com/r/j3soon/ros2-template-ws/tags) | [`./template_ws`](./template_ws) | ✔️ | ❌ | -| [j3soon/ros2-orbslam3-ws](https://hub.docker.com/r/j3soon/ros2-orbslam3-ws/tags) | [`./orbslam3_ws`](./orbslam3_ws) | ✔️ | ❌ | -| [j3soon/ros2-rtabmap-ws](https://hub.docker.com/r/j3soon/ros2-rtabmap-ws/tags) | [`./rtabmap_ws`](./rtabmap_ws) | ✔️ | ❌ | -| [j3soon/ros2-ros1-bridge-ws](https://hub.docker.com/r/j3soon/ros2-ros1-bridge-ws/tags) | [`./ros1_bridge_ws`](./ros1_bridge_ws) | ✔️ | ❌ | -| [j3soon/ros2-ros1-bridge-build-ws](https://hub.docker.com/r/j3soon/ros2-ros1-bridge-build-ws/tags) | [`./ros1_bridge_ws`](./ros1_bridge_ws) | ✔️ | ❌ | -| [j3soon/ros2-cartographer-ws](https://hub.docker.com/r/j3soon/ros2-cartographer-ws/tags) | [`./cartographer_ws`](./cartographer_ws) | ✔️ | ❌ | -| [j3soon/ros2-husky-ws](https://hub.docker.com/r/j3soon/ros2-husky-ws/tags) | [`./husky_ws`](./husky_ws) | ✔️ | ✔️ | -| [j3soon/ros2-kobuki-ws](https://hub.docker.com/r/j3soon/ros2-kobuki-ws/tags) | [`./kobuki_ws`](./kobuki_ws) | ✔️ | ✔️ | -| [j3soon/ros2-vlp-ws](https://hub.docker.com/r/j3soon/ros2-vlp-ws/tags) | [`./vlp_ws`](./vlp_ws) | ✔️ | ✔️ | +| Docker Image | Description | amd64 | arm64 | Notes | +|--------------|-------------|-------|-------|-------| +| [j3soon/ros2-template-ws](https://hub.docker.com/r/j3soon/ros2-template-ws/tags) | [`./template_ws`](./template_ws) | ✔️ | ❌ | | +| [j3soon/ros2-orbslam3-ws](https://hub.docker.com/r/j3soon/ros2-orbslam3-ws/tags) | [`./orbslam3_ws`](./orbslam3_ws) | ✔️ | ❌ | | +| [j3soon/ros2-rtabmap-ws](https://hub.docker.com/r/j3soon/ros2-rtabmap-ws/tags) | [`./rtabmap_ws`](./rtabmap_ws) | ✔️ | ❌ | | +| [j3soon/ros2-ros1-bridge-ws](https://hub.docker.com/r/j3soon/ros2-ros1-bridge-ws/tags) | [`./ros1_bridge_ws`](./ros1_bridge_ws) | ✔️ | ❌ | Skip linting | +| [j3soon/ros2-ros1-bridge-build-ws](https://hub.docker.com/r/j3soon/ros2-ros1-bridge-build-ws/tags) | [`./ros1_bridge_ws`](./ros1_bridge_ws) | ✔️ | ❌ | Skip linting | +| [j3soon/ros2-cartographer-ws](https://hub.docker.com/r/j3soon/ros2-cartographer-ws/tags) | [`./cartographer_ws`](./cartographer_ws) | ✔️ | ❌ | | +| [j3soon/ros2-husky-ws](https://hub.docker.com/r/j3soon/ros2-husky-ws/tags) | [`./husky_ws`](./husky_ws) | ✔️ | ✔️ | Support real husky | +| [j3soon/ros2-kobuki-ws](https://hub.docker.com/r/j3soon/ros2-kobuki-ws/tags) | [`./kobuki_ws`](./kobuki_ws) | ✔️ | ✔️ | Support real kobuki | +| [j3soon/ros2-vlp-ws](https://hub.docker.com/r/j3soon/ros2-vlp-ws/tags) | [`./vlp_ws`](./vlp_ws) | ✔️ | ✔️ | Support real vlp | ## Contributors diff --git a/cartographer_ws/.devcontainer/devcontainer.json b/cartographer_ws/.devcontainer/devcontainer.json index 02f47b10..6ab46b9c 100644 --- a/cartographer_ws/.devcontainer/devcontainer.json +++ b/cartographer_ws/.devcontainer/devcontainer.json @@ -1,12 +1,10 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "ROS2 and Catorgrapher Container", + "name": "Catographer", "dockerComposeFile": "../docker/compose.yaml", - "service": "ros2-cartographer", - - // workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/cartographer_ws", - + "service": "cartographer-ws", + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/cartographer_ws", // Vscode extensions "customizations": { "vscode": { @@ -16,11 +14,10 @@ "twxs.cmake", "donjayamanne.python-extension-pack", "eamodio.gitlens", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", "ms-iot.vscode-ros" ] } - }, - - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/cartographer_ws/.devcontainer/postCreateCommand.sh b/cartographer_ws/.devcontainer/postCreateCommand.sh deleted file mode 100644 index efce9e5b..00000000 --- a/cartographer_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/cartographer_ws/.gitignore b/cartographer_ws/.gitignore index d56eeb47..2bc3ebb3 100644 --- a/cartographer_ws/.gitignore +++ b/cartographer_ws/.gitignore @@ -1,8 +1,7 @@ +# Visual Studio Code .vscode + +# ROS2 basic directories /build /install /log -docker/cache/* -!docker/cache/.gazebo -docker/cache/.gazebo/* -!docker/cache/.gazebo/.gitkeep \ No newline at end of file diff --git a/cartographer_ws/README.md b/cartographer_ws/README.md index 254d1e18..a1c87a8f 100644 --- a/cartographer_ws/README.md +++ b/cartographer_ws/README.md @@ -7,7 +7,7 @@ git clone https://github.com/j3soon/ros2-essentials.git ``` ```bash -cd ros2-agv-essentials/cartographer_ws/docker +cd ros2-essentials/cartographer_ws/docker docker compose pull docker compose up -d --build ``` @@ -18,7 +18,7 @@ docker compose up -d --build ```sh docker attach ros2-cartographer-ws - cd /home/ros2-agv-essentials/cartographer_ws + cd /home/ros2-essentials/cartographer_ws ``` - Open the turtlebot simulation in `tmux` @@ -41,7 +41,7 @@ docker compose up -d --build ```sh docker attach ros2-cartographer-ws -cd /home/ros2-agv-essentials/cartographer_ws +cd /home/ros2-essentials/cartographer_ws rosdep update rosdep install --from-paths src --ignore-src --rosdistro humble -y colcon build diff --git a/cartographer_ws/docker/.bashrc b/cartographer_ws/docker/.bashrc index 7c9df020..ed090d4a 100644 --- a/cartographer_ws/docker/.bashrc +++ b/cartographer_ws/docker/.bashrc @@ -1,5 +1,30 @@ # Source global ROS2 environment source /opt/ros/$ROS_DISTRO/setup.bash +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet +if [ ! -f $ROS2_WS/install/setup.bash ]; then + echo "Workspace has not been built yet. Building workspace..." + cd $ROS2_WS + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html + if [ $(arch) == "aarch64" ]; then + colcon build --symlink-install + else + colcon build --symlink-install + fi + echo "Workspace built." +fi +# TODO: Source other workspace environments as underlay # Source workspace environment -# Note: If you have not built your workspace yet, the following command will fail source $ROS2_WS/install/setup.bash diff --git a/cartographer_ws/docker/Dockerfile b/cartographer_ws/docker/Dockerfile index 5436a1d8..f6e5178c 100644 --- a/cartographer_ws/docker/Dockerfile +++ b/cartographer_ws/docker/Dockerfile @@ -1,50 +1,103 @@ -# Base Image : https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble -FROM osrf/ros:humble-desktop-full +# Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble +FROM osrf/ros:humble-desktop-full AS amd64 +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +FROM arm64v8/ros:humble AS arm64 + +# Use docker automatic platform args to select the base image. +# It may be `arm64` or `amd64` depending on the platform. +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="assume0701@gmail.com" +# Arguments for the default user ARG USERNAME=user ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Create the user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - # - # [Optional] Add sudo support. Omit if you don't need to install software after connecting. - && apt-get update \ - && apt-get install -y sudo \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME \ - && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get upgrade -y \ + +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get install -y python3-pip \ + +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ && rm -rf /var/lib/apt/lists/* -ENV SHELL /bin/bash # Install common tools -RUN apt-get update && apt-get install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ curl \ git \ - git-extras \ htop \ + iputils-ping \ + nano \ net-tools \ tmux \ + tree \ + unzip \ vim \ wget \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ && rm -rf /var/lib/apt/lists/* -# Install turtlebot3, RVIZ, Gazebo and Cartographer -RUN apt-get update && apt-get install -y \ - ros-$ROS_DISTRO-gazebo-ros-pkgs \ +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ + && rm -rf /var/lib/apt/lists/* + +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-gazebo-ros-pkgs \ + ros-$ROS_DISTRO-gazebo-ros2-control \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-cartographer \ ros-$ROS_DISTRO-turtlebot3* \ ros-$ROS_DISTRO-rqt-robot-steering \ && rm -rf /var/lib/apt/lists/* -COPY .bashrc /home/$USERNAME/.bashrc - USER $USERNAME +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here +ENTRYPOINT [] CMD ["/bin/bash"] diff --git a/cartographer_ws/docker/cache/.gazebo/.gitkeep b/cartographer_ws/docker/cache/.gazebo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/cartographer_ws/docker/compose.yaml b/cartographer_ws/docker/compose.yaml index 8bc01aa9..cb079eb1 100644 --- a/cartographer_ws/docker/compose.yaml +++ b/cartographer_ws/docker/compose.yaml @@ -1,42 +1,69 @@ services: - ros2-cartographer: - build: . + cartographer-ws: + build: + context: . + dockerfile: Dockerfile + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. + # Reference: https://docs.docker.com/compose/compose-file/build/#platforms + # platforms: + # - "linux/arm64" image: j3soon/ros2-cartographer-ws container_name: ros2-cartographer-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/cartographer_ws + working_dir: /home/ros2-essentials/cartographer_ws environment: - - DISPLAY=${DISPLAY} - # Set ros2 environment variables. + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html - - ROS_LOCALHOST_ONLY=1 - - ROS_DOMAIN_ID=42 - - ROS2_WS=/home/ros2-agv-essentials/cartographer_ws + # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. + - RCUTILS_COLORIZED_OUTPUT=1 + - ROS2_WS=/home/ros2-essentials/cartographer_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [ gpu ] volumes: - # Mount local timezone into container. ( Readonly ) + # Mount local timezone into container. # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Direct Rendering Infrastructure + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. - /dev/dri:/dev/dri # Mount sound card to prevent Gazebo warning. - /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. + # - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 - - ./cache/.gazebo:/home/user/.gazebo - # Mounting the following directories will forbid direct deletion. - # Consider mount these directories only if the build process is slow. - # "source=${localWorkspaceFolder}/../cache/humble/build,target=/home/ws/build,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/install,target=/home/ws/install,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/log,target=/home/ws/log,type=bind" - # Mount workspace - - ../..:/home/ros2-agv-essentials + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/husky_ws/.devcontainer/devcontainer.json b/husky_ws/.devcontainer/devcontainer.json index 5431daa5..f2284404 100644 --- a/husky_ws/.devcontainer/devcontainer.json +++ b/husky_ws/.devcontainer/devcontainer.json @@ -1,10 +1,10 @@ -/* For reference, see https://aka.ms/devcontainer.json */ +/* Reference: https://aka.ms/devcontainer.json */ { - "name": "Husky Simulation", + "name": "Husky", "dockerComposeFile": "../docker/compose.yaml", "service": "husky-ws", // Workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/husky_ws", + "workspaceFolder": "/home/ros2-essentials/husky_ws", // Vscode extensions "customizations": { "vscode": { @@ -19,7 +19,5 @@ "ms-iot.vscode-ros" ] } - }, - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/husky_ws/.devcontainer/postCreateCommand.sh b/husky_ws/.devcontainer/postCreateCommand.sh deleted file mode 100755 index a6a4f868..00000000 --- a/husky_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update --fix-missing -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src --rosdistro humble -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/husky_ws/.gitignore b/husky_ws/.gitignore index 6bf2ffcd..2bc3ebb3 100644 --- a/husky_ws/.gitignore +++ b/husky_ws/.gitignore @@ -1,12 +1,7 @@ +# Visual Studio Code .vscode # ROS2 basic directories /build /install /log - -# Gazebo cache -docker/cache/* -!docker/cache/.gazebo -docker/cache/.gazebo/* -!docker/cache/.gazebo/.gitkeep \ No newline at end of file diff --git a/husky_ws/README.md b/husky_ws/README.md index 321dab3e..b90e9424 100644 --- a/husky_ws/README.md +++ b/husky_ws/README.md @@ -55,7 +55,7 @@ Before attempting any examples, please remember to build the packages first. If you encounter any dependency errors, please use rosdep to resolve them. ```bash -cd /home/ros2-agv-essentials/husky_ws +cd /home/ros2-essentials/husky_ws rosdep update rosdep install --from-paths src --ignore-src --rosdistro humble -y colcon build @@ -97,7 +97,7 @@ ros2 launch husky_navigation slam_launch.py ```bash= # Move to the workspace, source .bashrc, and bringup husky. -cd /home/ros2-agv-essentials/husky_ws +cd /home/ros2-essentials/husky_ws source ~/.bashrc ./script/husky-bringup.sh @@ -115,4 +115,4 @@ To maintain reproducibility, we have frozen the following packages at specific c * [clearpathrobotics/clearpath_computer_installer](https://github.com/clearpathrobotics/clearpath_computer_installer) (at commit 7e7f415) is released under the [BSD-3-Clause License](https://github.com/clearpathrobotics/clearpath_computer_installer/blob/main/LICENSE). * [clearpathrobotics/clearpath_robot/clearpath_robot/debian/udev](https://github.com/clearpathrobotics/clearpath_robot/blob/17d55f1b27d3fe19fb82e7df64dca96dbd345837/clearpath_robot/debian/udev) (at commit 17d55f1) is released under the [BSD 3-Clause License](https://github.com/clearpathrobotics/clearpath_robot/blob/17d55f1b27d3fe19fb82e7df64dca96dbd345837/LICENSE). -Further changes based on the packages above are release under the [Apache-2.0 License](https://github.com/j3soon/ros2-essentials/blob/master/LICENSE), as stated in the repository. +Further changes based on the packages above are release under the [Apache-2.0 License](https://github.com/j3soon/ros2-essentials/blob/main/LICENSE), as stated in the repository. diff --git a/husky_ws/docker/.bashrc b/husky_ws/docker/.bashrc index 44dfefc6..1f680580 100644 --- a/husky_ws/docker/.bashrc +++ b/husky_ws/docker/.bashrc @@ -1,12 +1,34 @@ # Source global ROS2 environment source /opt/ros/$ROS_DISTRO/setup.bash -# Source custom global environment -source ~/ros2_ws/install/local_setup.bash -# Source Clearpath environment -# Note: The setup.bash file is created by the script "/usr/sbin/clearpath-robot-generate", -# do not modify this file manually, as it will be overwritten. Try to modify the -# "/etc/clearpath/robot.yaml" file instead, and then run the script again. +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet +if [ ! -f $ROS2_WS/install/setup.bash ]; then + echo "Workspace has not been built yet. Building workspace..." + cd $ROS2_WS + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html + if [ $(arch) == "aarch64" ]; then + colcon build --symlink-install + else + colcon build --symlink-install + fi + echo "Workspace built." +fi +# TODO: Source other workspace environments as underlay +# Source Clearpath robot environment +source ~/husky_driver_ws/install/local_setup.bash +# Source Clearpath default environment installed by `clearpath_computer_installer.sh` source /etc/clearpath/setup.bash # Source workspace environment -# Note: If you have not built your workspace yet, the following command will fail source $ROS2_WS/install/setup.bash diff --git a/husky_ws/docker/Dockerfile b/husky_ws/docker/Dockerfile index 59c28651..e15a4efb 100644 --- a/husky_ws/docker/Dockerfile +++ b/husky_ws/docker/Dockerfile @@ -1,64 +1,93 @@ -# Base Image : https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble -FROM arm64v8/ros:humble AS arm64 -# Base Image : https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble +# Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble FROM osrf/ros:humble-desktop-full AS amd64 +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +FROM arm64v8/ros:humble AS arm64 # Use docker automatic platform args to select the base image. # It may be `arm64` or `amd64` depending on the platform. -# Reference: -# - https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="yuzhong1214@gmail.com" -ARG TARGETARCH +# Arguments for the default user ARG USERNAME=user ARG USER_UID=1000 -ARG USER_GID=$USER_UID -# Create the user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - # - # [Optional] Add sudo support. Omit if you don't need to install software after connecting. - && apt-get update \ - && apt-get install -y sudo \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME \ - && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get install -y python3-pip \ +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* -ENV SHELL /bin/bash -# ******************************************************** -# * Anything else you want to do like clean up goes here * -# ******************************************************** +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* # Install common tools -RUN apt-get update && apt-get install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ curl \ git \ - git-extras \ htop \ + iputils-ping \ + nano \ net-tools \ tmux \ + tree \ + unzip \ vim \ wget \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ && rm -rf /var/lib/apt/lists/* -RUN if [ "$TARGETARCH" = "amd64" ]; then \ +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ apt-get update && apt-get install -y \ ros-$ROS_DISTRO-gazebo-ros-pkgs \ ros-$ROS_DISTRO-gazebo-ros2-control \ && rm -rf /var/lib/apt/lists/*; \ fi -# Install ROS2 packages -RUN apt-get update && apt-get install -y \ +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-rviz2 \ - # + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ # The packages below are used by the Husky repository. ros-$ROS_DISTRO-controller-manager \ ros-$ROS_DISTRO-diff-drive-controller \ @@ -83,24 +112,27 @@ RUN apt-get update && apt-get install -y \ ros-$ROS_DISTRO-slam-toolbox \ ros-$ROS_DISTRO-imu-tools \ ros-$ROS_DISTRO-teleop-twist-keyboard \ - && sudo rm -rf /var/lib/apt/lists/* - -COPY .bashrc /home/$USERNAME/.bashrc + && rm -rf /var/lib/apt/lists/* -# [Optional] Set the default user. Omit if you want to keep the default as root. USER $USERNAME -CMD ["/bin/bash"] +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here # Setup husky controller by the script. # Build certain packages from source for arm64. COPY script /home/$USERNAME/script -RUN sudo apt-get update \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + sudo apt-get update \ && bash -ie /home/$USERNAME/script/install-clearpath-robot.sh \ && sudo rm -rf /var/lib/apt/lists/* # Note: The script need to be run as user, not root. # Reference: https://github.com/clearpathrobotics/clearpath_computer_installer/tree/main COPY clearpath_computer_installer /home/$USERNAME/clearpath_computer_installer -RUN sudo apt-get update \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + sudo apt-get update \ && bash -ie /home/$USERNAME/clearpath_computer_installer/clearpath_computer_installer.sh \ && sudo rm -rf /var/lib/apt/lists/* # Setup udev rules. @@ -108,3 +140,6 @@ COPY udev_rules /home/$USERNAME/udev_rules RUN /home/$USERNAME/udev_rules/install_udev_rules.sh # Generate robot configuration files. RUN bash -ie /home/$USERNAME/script/husky-generate.sh + +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/husky_ws/docker/cache/.gazebo/.gitkeep b/husky_ws/docker/cache/.gazebo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/husky_ws/docker/compose.yaml b/husky_ws/docker/compose.yaml index 124f14cf..8bb97b10 100644 --- a/husky_ws/docker/compose.yaml +++ b/husky_ws/docker/compose.yaml @@ -3,7 +3,7 @@ services: build: context: . dockerfile: Dockerfile - # Specify the target platform to build the image. + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. # Reference: https://docs.docker.com/compose/compose-file/build/#platforms # platforms: # - "linux/arm64" @@ -11,23 +11,27 @@ services: container_name: ros2-husky-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/husky_ws + working_dir: /home/ros2-essentials/husky_ws environment: - - DISPLAY=${DISPLAY} - # Set ros2 environment variables. + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing - # - ROS_LOCALHOST_ONLY=1 - - ROS_DOMAIN_ID=42 - - ROS2_WS=/home/ros2-agv-essentials/husky_ws + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. - RCUTILS_COLORIZED_OUTPUT=1 - # If you want to access GPU, please uncomment the lines below. - # Reference : https://docs.docker.com/compose/gpu-support/ + - ROS2_WS=/home/ros2-essentials/husky_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ # deploy: # resources: # reservations: @@ -36,17 +40,30 @@ services: # count: all # capabilities: [ gpu ] volumes: - # Mount local timezone into container. ( Readonly ) + # Mount local timezone into container. # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Mount devices + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. + # - /dev/dri:/dev/dri + # Mount sound card to prevent Gazebo warning. + # - /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + # - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 - - ./cache/.gazebo:/home/user/.gazebo - # Mount workspace - - ../..:/home/ros2-agv-essentials + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/husky_ws/docker/script/install-clearpath-robot.sh b/husky_ws/docker/script/install-clearpath-robot.sh index b946ed62..a2dfef21 100755 --- a/husky_ws/docker/script/install-clearpath-robot.sh +++ b/husky_ws/docker/script/install-clearpath-robot.sh @@ -7,8 +7,8 @@ sudo bash -c 'echo "deb https://packages.clearpathrobotics.com/stable/ubuntu $(l sudo apt-get update # Create custom global workspace -mkdir -p ~/ros2_ws/src -cd ~/ros2_ws/src +mkdir -p ~/husky_driver_ws/src +cd ~/husky_driver_ws/src # `clearpath_computer_installer.sh` requires `ros-humble-clearpath-robot`. # Install from source since package doesn't exist on arm64, error message: # @@ -24,7 +24,7 @@ cd .. # Ref: https://github.com/clearpathrobotics/clearpath_robot/pull/19 # Ref: https://github.com/clearpathrobotics/clearpath_robot/pull/22 # I think this package isn't used anyway. -sed -i 's/micro_ros_agent<\/exec_depend>//' ~/ros2_ws/src/clearpath_robot/clearpath_generator_robot/package.xml +sed -i 's/micro_ros_agent<\/exec_depend>//' ~/husky_driver_ws/src/clearpath_robot/clearpath_generator_robot/package.xml # Temporarily remove `sevcon_traction` dependency # Ref: https://github.com/clearpathrobotics/clearpath_robot/commit/2ef406f30c074e578db6ea799f5c2714bce1c15d @@ -32,23 +32,23 @@ sed -i 's/micro_ros_agent<\/exec_depend>//' ~/ros2_ws/src/clearpath_robot/clearpath_generator_robot/package.xml +sed -i 's/sevcon_traction<\/exec_depend>//' ~/husky_driver_ws/src/clearpath_robot/clearpath_generator_robot/package.xml # Temporarily remove `umx_driver` dependency # This package is only used for redshift and chrobotics, so it isn't required for our case. # Ref: https://github.com/clearpathrobotics/clearpath_robot/blob/40b1c40a7d229ede7a674cb0fb359fc83c754adb/clearpath_sensors/launch/redshift_um7.launch.py#L69 # Ref: https://github.com/clearpathrobotics/clearpath_robot/blob/40b1c40a7d229ede7a674cb0fb359fc83c754adb/clearpath_sensors/launch/chrobotics_um6.launch.py#L69 -sed -i 's/umx_driver<\/exec_depend>//' ~/ros2_ws/src/clearpath_robot/clearpath_sensors/package.xml +sed -i 's/umx_driver<\/exec_depend>//' ~/husky_driver_ws/src/clearpath_robot/clearpath_sensors/package.xml # Temporarily remove `valence_bms_driver` dependency # Ref: https://github.com/clearpathrobotics/clearpath_robot/pull/47 # This type of batteries are only used in W200, so they aren't required for Husky. # Ref: https://github.com/clearpathrobotics/clearpath_robot/blob/40b1c40a7d229ede7a674cb0fb359fc83c754adb/clearpath_generator_robot/clearpath_generator_robot/launch/generator.py#L152-L156 # Ref: https://github.com/clearpathrobotics/clearpath_config/blob/996eb50d0b05c87b65b8ffddcddd33239abd422e/clearpath_config/platform/battery.py#L51-L54 -sed -i 's/valence_bms_driver<\/exec_depend>//' ~/ros2_ws/src/clearpath_robot/clearpath_generator_robot/package.xml +sed -i 's/valence_bms_driver<\/exec_depend>//' ~/husky_driver_ws/src/clearpath_robot/clearpath_generator_robot/package.xml # Continue building the workspace -cd ~/ros2_ws +cd ~/husky_driver_ws rosdep update rosdep install -i --from-path src --rosdistro humble -y colcon build diff --git a/husky_ws/src/citysim/README.md b/husky_ws/src/citysim/README.md index d05d662f..8f76e68c 100644 --- a/husky_ws/src/citysim/README.md +++ b/husky_ws/src/citysim/README.md @@ -11,7 +11,7 @@ Please ensure that you have set up the `GAZEBO_MODEL_PATH` correctly. ```bash cd $ROS2_WS/src/citysim -export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/home/ros2-agv-essentials/husky_ws/src/citysim/models +export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/home/ros2-essentials/husky_ws/src/citysim/models gazebo worlds/simple_city.world ``` diff --git a/kobuki_ws/.devcontainer/devcontainer.json b/kobuki_ws/.devcontainer/devcontainer.json index afa06486..0d2cb501 100644 --- a/kobuki_ws/.devcontainer/devcontainer.json +++ b/kobuki_ws/.devcontainer/devcontainer.json @@ -1,10 +1,10 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "kobuki_ws", + "name": "Kobuki", "dockerComposeFile": "../docker/compose.yaml", "service": "kobuki-ws", - // workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/kobuki_ws", + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/kobuki_ws", // Vscode extensions "customizations": { "vscode": { @@ -19,7 +19,5 @@ "ms-iot.vscode-ros" ] } - }, - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/kobuki_ws/.devcontainer/postCreateCommand.sh b/kobuki_ws/.devcontainer/postCreateCommand.sh deleted file mode 100755 index efce9e5b..00000000 --- a/kobuki_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/kobuki_ws/.gitignore b/kobuki_ws/.gitignore index 6bf2ffcd..2bc3ebb3 100644 --- a/kobuki_ws/.gitignore +++ b/kobuki_ws/.gitignore @@ -1,12 +1,7 @@ +# Visual Studio Code .vscode # ROS2 basic directories /build /install /log - -# Gazebo cache -docker/cache/* -!docker/cache/.gazebo -docker/cache/.gazebo/* -!docker/cache/.gazebo/.gitkeep \ No newline at end of file diff --git a/kobuki_ws/README.md b/kobuki_ws/README.md index 3a0fa567..8d4b6d4d 100644 --- a/kobuki_ws/README.md +++ b/kobuki_ws/README.md @@ -25,7 +25,7 @@ This repository is primarily based on the [kobuki-base](https://github.com/kobuk > If you only need to bring up the real Kobuki robot, you don't need to compile the workspace. The Kobuki driver is already included in the Docker image. After the Docker image is built, you can directly bring up the robot. ```bash -cd /home/ros2-agv-essentials/kobuki_ws +cd /home/ros2-essentials/kobuki_ws # For x86_64 architecture colcon build --symlink-install @@ -69,12 +69,12 @@ ros2 launch kobuki_launch kobuki.launch.py is_sim:=true ```bash # Inside the container -cd /home/ros2-agv-essentials/kobuki_ws +cd /home/ros2-essentials/kobuki_ws ./script/kobuki-bringup.sh # or Outside the container cd /path/to/kobuki_ws/docker -docker compose run kobuki-ws /home/ros2-agv-essentials/kobuki_ws/script/kobuki-bringup.sh +docker compose run kobuki-ws /home/ros2-essentials/kobuki_ws/script/kobuki-bringup.sh ``` If you have successfully connected to the Kobuki, you should hear a sound from it. Otherwise, there may be errors. You can try re-plugging the USB cable, restarting the Kobuki, or even restarting the container. @@ -101,12 +101,12 @@ To control the Kobuki with a keyboard, you can use the `teleop_twist_keyboard` p # - Angular 0.3 # Inside the container -cd /home/ros2-agv-essentials/kobuki_ws +cd /home/ros2-essentials/kobuki_ws ./script/kobuki-teleop.sh # or Outside the container cd /path/to/kobuki_ws/docker -docker compose run kobuki-ws /home/ros2-agv-essentials/kobuki_ws/script/kobuki-teleop.sh +docker compose run kobuki-ws /home/ros2-essentials/kobuki_ws/script/kobuki-teleop.sh ``` ### Launch the demo of SLAM @@ -152,7 +152,7 @@ Note that the arm64 architecture is emulated by the QEMU, so it may consume a lo When the building process ends, use `docker compose up -d` and attach to the container by running `docker attach ros2-kobuki-ws`. After that, we can start building the ROS packages. If you have built the packages for the x86_64 architecture before, remember to delete the `build`, `install`, and `log` folders. ```bash -cd /home/ros2-agv-essentials/kobuki_ws +cd /home/ros2-essentials/kobuki_ws colcon build --symlink-install --packages-ignore velodyne_gazebo_plugins ``` diff --git a/kobuki_ws/docker/.bashrc b/kobuki_ws/docker/.bashrc index 9a063f87..2641d9fb 100644 --- a/kobuki_ws/docker/.bashrc +++ b/kobuki_ws/docker/.bashrc @@ -1,7 +1,32 @@ # Source global ROS2 environment source /opt/ros/$ROS_DISTRO/setup.bash +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet +if [ ! -f $ROS2_WS/install/setup.bash ]; then + echo "Workspace has not been built yet. Building workspace..." + cd $ROS2_WS + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html + if [ $(arch) == "aarch64" ]; then + colcon build --symlink-install --packages-ignore velodyne_gazebo_plugins + else + colcon build --symlink-install + fi + echo "Workspace built." +fi +# TODO: Source other workspace environments as underlay # Source kobuki driver workspace environment source ~/kobuki_driver_ws/install/local_setup.bash # Source workspace environment -# Note: If you have not built your workspace yet, the following command will fail source $ROS2_WS/install/setup.bash diff --git a/kobuki_ws/docker/Dockerfile b/kobuki_ws/docker/Dockerfile index feb91c20..b0823e44 100644 --- a/kobuki_ws/docker/Dockerfile +++ b/kobuki_ws/docker/Dockerfile @@ -1,63 +1,95 @@ -# Base Image : https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble -FROM arm64v8/ros:humble AS arm64 -# Base Image : https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble +# Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble FROM osrf/ros:humble-desktop-full AS amd64 +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +FROM arm64v8/ros:humble AS arm64 # Use docker automatic platform args to select the base image. # It may be `arm64` or `amd64` depending on the platform. -# Reference: -# - https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="yuzhong1214@gmail.com" -ARG TARGETARCH +# Arguments for the default user ARG USERNAME=user ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Create the user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - # - # [Optional] Add sudo support. Omit if you don't need to install software after connecting. - && apt-get update \ - && apt-get install -y sudo \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME \ - && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get upgrade -y \ - && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get install -y python3-pip \ + +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* -ENV SHELL /bin/bash -# ******************************************************** -# * Anything else you want to do like clean up goes here * -# ******************************************************** +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* # Install common tools -RUN apt-get update && apt-get install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ curl \ git \ - git-extras \ htop \ + iputils-ping \ + nano \ net-tools \ tmux \ + tree \ + unzip \ vim \ wget \ + zip \ && rm -rf /var/lib/apt/lists/* -# Install ROS2 RVIZ and Gazebo -RUN if [ "$TARGETARCH" = "amd64" ]; then \ +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ + && rm -rf /var/lib/apt/lists/* + +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ apt-get update && apt-get install -y \ ros-$ROS_DISTRO-gazebo-ros-pkgs \ ros-$ROS_DISTRO-gazebo-ros2-control \ && rm -rf /var/lib/apt/lists/*; \ - elif [ "$TARGETARCH" = "arm64" ]; then \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* + +RUN if [ "$TARGETARCH" = "arm64" ]; then \ # Fix the issue of Eigen - # Reference: - # - https://gitlab.com/libeigen/eigen/-/issues/2326 + # Reference: https://gitlab.com/libeigen/eigen/-/issues/2326 sed -i 's/EIGEN_ALWAYS_INLINE eigen_packet_wrapper() {}/EIGEN_ALWAYS_INLINE eigen_packet_wrapper() = default;/' /usr/include/eigen3/Eigen/src/Core/GenericPacketMath.h; \ fi @@ -65,7 +97,8 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \ # The list of packages below was identified by executing the following command: # sudo rosdep install --from-paths src --ignore-src -y # Install these packages to avoid having to run `rosdep install` every time you build the workspace. -RUN apt-get update && apt-get install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-rviz2 \ ros-$ROS_DISTRO-ros-testing \ ros-$ROS_DISTRO-kobuki-velocity-smoother \ @@ -81,7 +114,6 @@ RUN apt-get update && apt-get install -y \ ros-$ROS_DISTRO-imu-tools \ ros-$ROS_DISTRO-pointcloud-to-laserscan \ ros-$ROS_DISTRO-teleop-twist-keyboard \ - # # Install navigation dependencies # Reference: # - https://docs.nav2.org/development_guides/build_docs/index.html @@ -92,18 +124,22 @@ RUN apt-get update && apt-get install -y \ ros-$ROS_DISTRO-robot-localization \ && rm -rf /var/lib/apt/lists/* -COPY .bashrc /home/$USERNAME/.bashrc - -# [Optional] Set the default user. Omit if you want to keep the default as root. USER $USERNAME -CMD ["/bin/bash"] +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here # Setup udev rules for Kobuki. COPY udev_rules /home/$USERNAME/udev_rules RUN /home/$USERNAME/udev_rules/install_udev_rules.sh # Build the kobuki driver workspace -COPY --chown=$USER_UID:$USER_GID kobuki_driver_ws /home/$USERNAME/kobuki_driver_ws +COPY --chown=$USER_UID kobuki_driver_ws /home/$USERNAME/kobuki_driver_ws RUN cd /home/$USERNAME/kobuki_driver_ws \ && rm -rf COLCON_IGNORE \ - && /bin/bash -c "source /opt/ros/$ROS_DISTRO/setup.bash; colcon build --symlink-install" \ No newline at end of file + && /bin/bash -c "source /opt/ros/$ROS_DISTRO/setup.bash; colcon build --symlink-install" + +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/kobuki_ws/docker/cache/.gazebo/.gitkeep b/kobuki_ws/docker/cache/.gazebo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/kobuki_ws/docker/compose.yaml b/kobuki_ws/docker/compose.yaml index 3063a4a0..7471d5f6 100644 --- a/kobuki_ws/docker/compose.yaml +++ b/kobuki_ws/docker/compose.yaml @@ -3,7 +3,7 @@ services: build: context: . dockerfile: Dockerfile - # Specify the target platform to build the image. + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. # Reference: https://docs.docker.com/compose/compose-file/build/#platforms # platforms: # - "linux/arm64" @@ -11,52 +11,59 @@ services: container_name: ros2-kobuki-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/kobuki_ws + working_dir: /home/ros2-essentials/kobuki_ws environment: - - DISPLAY=${DISPLAY} - # Set ros2 environment variables. + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing - - ROS_LOCALHOST_ONLY=1 - - ROS_DOMAIN_ID=42 - - ROS2_WS=/home/ros2-agv-essentials/kobuki_ws + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. - RCUTILS_COLORIZED_OUTPUT=1 - # If you want to access GPU, please uncomment the lines below. - # Reference : https://docs.docker.com/compose/gpu-support/ - # deploy: - # resources: - # reservations: - # devices: - # - driver: nvidia - # count: all - # capabilities: [ gpu ] + - ROS2_WS=/home/ros2-essentials/kobuki_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [ gpu ] volumes: - # Mount local timezone into container. ( Readonly ) + # Mount local timezone into container. # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Mount devices. - # If you don't want to mount all devices, you can specify the ones you need. - # But make sure to mount the device below: - # - Direct Rendering Infrastructure: /dev/dri - # - Sound Card: /dev/snd - # - Kubuki Base: usually /dev/ttyUSB0 + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. + # - /dev/dri:/dev/dri + # Mount sound card to prevent Gazebo warning. + # - /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + # - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 - - ./cache/.gazebo:/home/user/.gazebo - # Mounting the following directories will forbid direct deletion. - # Consider mount these directories only if the build process is slow. - # "source=${localWorkspaceFolder}/../cache/humble/build,target=/home/ws/build,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/install,target=/home/ws/install,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/log,target=/home/ws/log,type=bind" - # Mount workspace - - ../..:/home/ros2-agv-essentials + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/orbslam3_ws/.devcontainer/devcontainer.json b/orbslam3_ws/.devcontainer/devcontainer.json index fe8dbbfa..2751f884 100644 --- a/orbslam3_ws/.devcontainer/devcontainer.json +++ b/orbslam3_ws/.devcontainer/devcontainer.json @@ -1,12 +1,10 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "ROS2 and ORB-SLAM3 Container", + "name": "ORB-SLAM3", "dockerComposeFile": "../docker/compose.yaml", - "service": "ros2-orbslam3", - - // workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/orbslam3_ws", - + "service": "orbslam3-ws", + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/orbslam3_ws", // Vscode extensions "customizations": { "vscode": { @@ -16,11 +14,10 @@ "twxs.cmake", "donjayamanne.python-extension-pack", "eamodio.gitlens", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", "ms-iot.vscode-ros" ] } - }, - - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/orbslam3_ws/.devcontainer/postCreateCommand.sh b/orbslam3_ws/.devcontainer/postCreateCommand.sh deleted file mode 100644 index efce9e5b..00000000 --- a/orbslam3_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/orbslam3_ws/.gitignore b/orbslam3_ws/.gitignore index 123266bd..891f5aab 100644 --- a/orbslam3_ws/.gitignore +++ b/orbslam3_ws/.gitignore @@ -1,2 +1,11 @@ +# Visual Studio Code +.vscode + +# ROS2 basic directories +/build +/install +/log + +# ROS2 bag files V1_02_medium.bag V1_02_medium/ diff --git a/orbslam3_ws/README.md b/orbslam3_ws/README.md index 8b50150f..448ba90f 100644 --- a/orbslam3_ws/README.md +++ b/orbslam3_ws/README.md @@ -7,7 +7,7 @@ git clone https://github.com/j3soon/ros2-essentials.git ``` ```bash -cd ros2-agv-essentials/orbslam3_ws/docker +cd ros2-essentials/orbslam3_ws/docker docker compose pull docker compose up -d --build ``` @@ -17,7 +17,7 @@ docker compose up -d --build - Attach to the container ```sh docker attach ros2-orbslam3-ws - cd /home/ros2-agv-essentials/orbslam3_ws + cd /home/ros2-essentials/orbslam3_ws ``` - Prepare data, only need to be done once - Download dataset (~1.2G) diff --git a/orbslam3_ws/docker/Dockerfile b/orbslam3_ws/docker/Dockerfile index 7d26637a..54f8c52b 100644 --- a/orbslam3_ws/docker/Dockerfile +++ b/orbslam3_ws/docker/Dockerfile @@ -1,112 +1,161 @@ -FROM osrf/ros:humble-desktop-full +# Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble +FROM osrf/ros:humble-desktop-full AS amd64 +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +FROM arm64v8/ros:humble AS arm64 + +# Use docker automatic platform args to select the base image. +# It may be `arm64` or `amd64` depending on the platform. +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="assume0701@gmail.com" -LABEL description = \ - "A image for ROS2 humble and ORB-SLAM3 develop environment." +# Arguments for the default user ARG USERNAME=user +ARG USER_UID=1000 + +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get upgrade -y \ +# Install common tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + curl \ + git \ + htop \ + iputils-ping \ + nano \ + net-tools \ + tmux \ + tree \ + unzip \ + vim \ + wget \ + zip \ && rm -rf /var/lib/apt/lists/* -############################################# -# --- Basic setup --- -RUN apt-get update && \ - apt-get install -y \ - # Basic tools - vim \ - curl \ - tree \ - wget \ - tmux \ - # ORB-SLAM dependencies - libopencv-dev \ - opencv-data \ - libglew-dev \ - ffmpeg \ - libboost-system-dev \ - libeigen3-dev \ - # ROS dependencies - ros-$ROS_DISTRO-vision-opencv \ - ros-$ROS_DISTRO-message-filters \ - # Python related - python3-pip && \ - rm -rf /var/lib/apt/lists/* - -# add user with default bash -RUN adduser --disabled-password --gecos '' --shell /bin/bash ${USERNAME} && \ - adduser ${USERNAME} sudo && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers - -############################################# -# --- Build Pangolin from source --- -USER ${USERNAME} -WORKDIR /home/${USERNAME} - -# get pangolin sources, certain commit : https://github.com/stevenlovegrove/Pangolin/tree/d484494645cb7361374ac0ef6b27e9ee6feffbd7 +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ + && rm -rf /var/lib/apt/lists/* + +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-gazebo-ros-pkgs \ + ros-$ROS_DISTRO-gazebo-ros2-control \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + # ORB-SLAM dependencies + libopencv-dev \ + opencv-data \ + libglew-dev \ + ffmpeg \ + libboost-system-dev \ + libeigen3-dev \ + # ROS dependencies + ros-$ROS_DISTRO-vision-opencv \ + ros-$ROS_DISTRO-message-filters \ + && rm -rf /var/lib/apt/lists/* + +USER $USERNAME +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here + +# Build Pangolin from source at commit: https://github.com/stevenlovegrove/Pangolin/tree/d484494645cb7361374ac0ef6b27e9ee6feffbd7 +# TODO(@j3soon): Simplify the git commands here RUN mkdir -p /home/${USERNAME}/Pangolin/build && \ cd /home/${USERNAME}/Pangolin && \ git init && \ git remote add origin https://github.com/stevenlovegrove/Pangolin.git && \ git pull origin d484494645cb7361374ac0ef6b27e9ee6feffbd7 && \ git rm -r --cached . - -WORKDIR /home/${USERNAME}/Pangolin/build - -# build pangolin -RUN cmake .. && \ +RUN cd /home/${USERNAME}/Pangolin/build && \ + cmake .. && \ cmake --build . -############################################# -# --- Build ORB-SLAM3 from source -WORKDIR /home/${USERNAME} - +# Build ORB-SLAM3 from source at commit: https://github.com/UZ-SLAMLab/ORB_SLAM3/commit/4452a3c4ab75b1cde34e5505a36ec3f9edcdc4c4 +# TODO(@j3soon): Simplify the git commands here RUN mkdir -p /home/${USERNAME}/ORB_SLAM3 && \ cd /home/${USERNAME}/ORB_SLAM3 && \ git init && \ git remote add origin https://github.com/UZ-SLAMLab/ORB_SLAM3.git && \ git pull origin 4452a3c4ab75b1cde34e5505a36ec3f9edcdc4c4 && \ git rm -r --cached . - -# Change to c++14 for compile error +# Change to c++14 for resolving compile error # Ref : https://github.com/UZ-SLAMLab/ORB_SLAM3/issues/387 # Ref : https://github.com/Soldann/MORB_SLAM/issues/3 -RUN sed -i 's/++11/++14/g' ORB_SLAM3/CMakeLists.txt && \ - sed -i 's/++11/++14/g' ORB_SLAM3/Thirdparty/g2o/CMakeLists.txt && \ - sed -i 's/++11/++14/g' ORB_SLAM3/Thirdparty/Sophus/CMakeLists.txt - -RUN cd ORB_SLAM3 && \ +RUN sed -i 's/++11/++14/g' /home/${USERNAME}/ORB_SLAM3/CMakeLists.txt && \ + sed -i 's/++11/++14/g' /home/${USERNAME}/ORB_SLAM3/Thirdparty/g2o/CMakeLists.txt && \ + sed -i 's/++11/++14/g' /home/${USERNAME}/ORB_SLAM3/Thirdparty/Sophus/CMakeLists.txt +RUN cd /home/${USERNAME}/ORB_SLAM3 && \ ./build.sh - -RUN cd ORB_SLAM3/Thirdparty/Sophus/build && \ +RUN cd /home/${USERNAME}/ORB_SLAM3/Thirdparty/Sophus/build && \ sudo make install -############################################# -# --- Build ROS2-SLAM3 +# Build ORB-SLAM3 for ROS2 RUN mkdir -p /home/${USERNAME}/test_ws/src - WORKDIR /home/${USERNAME}/test_ws - RUN colcon build - RUN mkdir -p src/orbslam3_ros2 && \ cd src/orbslam3_ros2 && \ git init && \ git remote add origin https://github.com/zang09/ORB_SLAM3_ROS2.git && \ git pull origin 00c54335ccc010d74c1e24e336aa817604124947 && \ git rm -r --cached . - # Fix path error and colcon build error # Ref : https://github.com/alsora/ros2-ORB_SLAM2/issues/8#issuecomment-1461570970 RUN sed -i 's/foxy\/lib\/python3.8/humble\/lib\/python3.10/g' src/orbslam3_ros2/CMakeLists.txt && \ sed -i 's/Pangolin)/Pangolin OpenCV)/g' src/orbslam3_ros2/CMakeLists.txt && \ sed -i 's/Install\/ORB_SLAM\/ORB_SLAM3/ORB_SLAM3/g' src/orbslam3_ros2/CMakeModules/FindORB_SLAM3.cmake - RUN rosdep update && \ . /opt/ros/humble/setup.sh && \ colcon build --symlink-install --packages-select orbslam3 -# Create work directory and went in -WORKDIR /home/${USERNAME}/ros_ws - -CMD ["bash"] +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/orbslam3_ws/docker/compose.yaml b/orbslam3_ws/docker/compose.yaml index 48dab944..88a94a74 100644 --- a/orbslam3_ws/docker/compose.yaml +++ b/orbslam3_ws/docker/compose.yaml @@ -1,28 +1,69 @@ services: - # Container-1 - ros2-orbslam3: - # Build or Reference the image - build: . + orbslam3-ws: + build: + context: . + dockerfile: Dockerfile + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. + # Reference: https://docs.docker.com/compose/compose-file/build/#platforms + # platforms: + # - "linux/arm64" image: j3soon/ros2-orbslam3-ws container_name: ros2-orbslam3-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true - command: bash + command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/orbslam3_ws - - # Container Settings + working_dir: /home/ros2-essentials/orbslam3_ws environment: - - DISPLAY=${DISPLAY} + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY + # References: + # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html + # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html + # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. + - RCUTILS_COLORIZED_OUTPUT=1 + - ROS2_WS=/home/ros2-essentials/orbslam3_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [ gpu ] volumes: - # Mount local timezone + # Mount local timezone into container. + # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - # X-11 mount + # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Direct Rendering Infrastructure + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. - /dev/dri:/dev/dri - # Mount workspace - - ..:/home/ros2-agv-essentials/orbslam3_ws + # Mount sound card to prevent Gazebo warning. + - /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. + # - /dev:/dev + # Mount Gazebo models directory to reuse models downloaded during first launch. + # Reference: https://answers.ros.org/question/365658 + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/ros1_bridge_ws/.devcontainer/devcontainer.json b/ros1_bridge_ws/.devcontainer/devcontainer.json index 223480c7..d273ace1 100644 --- a/ros1_bridge_ws/.devcontainer/devcontainer.json +++ b/ros1_bridge_ws/.devcontainer/devcontainer.json @@ -1,12 +1,12 @@ -/* For reference, see https://aka.ms/devcontainer.json */ +/* Reference: https://aka.ms/devcontainer.json */ { - "name": "ros1-bridge", + "name": "Ros1 Bridge", "dockerComposeFile": "../docker/compose.debug.yaml", "service": "ros1-bridge-build", "runServices": [ "ros1-bridge-build" ], - // workspace settings + // Workspace settings "workspaceFolder": "/", // Vscode extensions "customizations": { @@ -23,4 +23,4 @@ ] } } -} \ No newline at end of file +} diff --git a/rtabmap_ws/.devcontainer/devcontainer.json b/rtabmap_ws/.devcontainer/devcontainer.json index 271bd372..2c486931 100644 --- a/rtabmap_ws/.devcontainer/devcontainer.json +++ b/rtabmap_ws/.devcontainer/devcontainer.json @@ -1,12 +1,10 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "ROS2 and RTAB-Map Container", + "name": "RTAB-Map", "dockerComposeFile": "../docker/compose.yaml", - "service": "ros2-rtabmap", - - // workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/rtabmap_ws", - + "service": "rtabmap-ws", + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/rtabmap_ws", // Vscode extensions "customizations": { "vscode": { @@ -16,11 +14,10 @@ "twxs.cmake", "donjayamanne.python-extension-pack", "eamodio.gitlens", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", "ms-iot.vscode-ros" ] } - }, - - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/rtabmap_ws/.devcontainer/postCreateCommand.sh b/rtabmap_ws/.devcontainer/postCreateCommand.sh deleted file mode 100644 index efce9e5b..00000000 --- a/rtabmap_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/rtabmap_ws/.gitignore b/rtabmap_ws/.gitignore index f14775a5..2bc3ebb3 100644 --- a/rtabmap_ws/.gitignore +++ b/rtabmap_ws/.gitignore @@ -1,8 +1,7 @@ +# Visual Studio Code .vscode + +# ROS2 basic directories /build /install /log -docker/cache/* -!docker/cache/.gazebo -docker/cache/.gazebo/* -!docker/cache/.gazebo/.gitkeep diff --git a/rtabmap_ws/README.md b/rtabmap_ws/README.md index 3f4931bc..222110b9 100644 --- a/rtabmap_ws/README.md +++ b/rtabmap_ws/README.md @@ -7,7 +7,7 @@ git clone https://github.com/j3soon/ros2-essentials.git ``` ```bash -cd ros2-agv-essentials/rtabmap_ws/docker +cd ros2-essentials/rtabmap_ws/docker docker compose pull docker compose up -d --build ``` @@ -15,7 +15,7 @@ docker compose up -d --build - Attach to the container ```sh docker attach ros2-rtabmap-ws - cd /home/ros2-agv-essentials/rtabmap_ws + cd /home/ros2-essentials/rtabmap_ws colcon build --symlink-install ``` diff --git a/rtabmap_ws/docker/.bashrc b/rtabmap_ws/docker/.bashrc index c61ceaa5..ed090d4a 100644 --- a/rtabmap_ws/docker/.bashrc +++ b/rtabmap_ws/docker/.bashrc @@ -1,5 +1,30 @@ # Source global ROS2 environment source /opt/ros/$ROS_DISTRO/setup.bash +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet +if [ ! -f $ROS2_WS/install/setup.bash ]; then + echo "Workspace has not been built yet. Building workspace..." + cd $ROS2_WS + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html + if [ $(arch) == "aarch64" ]; then + colcon build --symlink-install + else + colcon build --symlink-install + fi + echo "Workspace built." +fi +# TODO: Source other workspace environments as underlay # Source workspace environment -# Note: If you have not built your workspace yet, the following command will fail -source $ROS2_WS/install/setup.bash \ No newline at end of file +source $ROS2_WS/install/setup.bash diff --git a/rtabmap_ws/docker/Dockerfile b/rtabmap_ws/docker/Dockerfile index da6ed2bb..add27ae2 100644 --- a/rtabmap_ws/docker/Dockerfile +++ b/rtabmap_ws/docker/Dockerfile @@ -1,46 +1,105 @@ -FROM introlab3it/rtabmap:jammy +# Base Image: https://hub.docker.com/r/introlab3it/rtabmap/tags?page=1&name=jammy +FROM introlab3it/rtabmap:jammy AS amd64 +# Base Image: https://hub.docker.com/r/introlab3it/rtabmap/tags?page=1&name=jammy +FROM introlab3it/rtabmap:jammy AS arm64 + +# Use docker automatic platform args to select the base image. +# It may be `arm64` or `amd64` depending on the platform. +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="assume0701@gmail.com" -LABEL description = \ - "A image for ROS2 humble and RTAB-Map develop environment." +# Arguments for the default user ARG USERNAME=user +ARG USER_UID=1000 + +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache -RUN apt-get update && apt-get upgrade -y \ +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* -############################################# -# --- Basic setup --- -RUN apt-get update && \ - apt-get install -y \ - sudo \ - vim \ - curl \ - tree \ - tmux \ - # --- - # Simulation required packages - ros-humble-turtlebot3 \ - ros-humble-turtlebot3-gazebo \ - ros-humble-gazebo-ros-pkgs \ - ros-humble-joint-state-publisher-gui \ - ros-humble-rqt-robot-steering \ - # --- - # RTAB-MAP required packages - ros-humble-rtabmap-ros && \ - rm -rf /var/lib/apt/lists/* - -# Install Gazebo -RUN curl -sSL http://get.gazebosim.org | sh - -# add user with default bash -RUN adduser --disabled-password --gecos '' --shell /bin/bash ${USERNAME} && \ - adduser ${USERNAME} sudo && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* -COPY .bashrc /home/$USERNAME/.bashrc +# Install common tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + curl \ + git \ + htop \ + iputils-ping \ + nano \ + net-tools \ + tmux \ + tree \ + unzip \ + vim \ + wget \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* -# Login with user -USER ${USERNAME} +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ + && rm -rf /var/lib/apt/lists/* + +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-gazebo-ros-pkgs \ + ros-$ROS_DISTRO-gazebo-ros2-control \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* -CMD ["bash"] \ No newline at end of file +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-turtlebot3 \ + ros-$ROS_DISTRO-turtlebot3-gazebo \ + ros-$ROS_DISTRO-joint-state-publisher-gui \ + ros-$ROS_DISTRO-rqt-robot-steering \ + ros-$ROS_DISTRO-rtabmap-ros \ + && rm -rf /var/lib/apt/lists/* + +USER $USERNAME +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/rtabmap_ws/docker/cache/.gazebo/.gitkeep b/rtabmap_ws/docker/cache/.gazebo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/rtabmap_ws/docker/compose.yaml b/rtabmap_ws/docker/compose.yaml index 5e69b239..e0392a3c 100644 --- a/rtabmap_ws/docker/compose.yaml +++ b/rtabmap_ws/docker/compose.yaml @@ -1,57 +1,69 @@ services: - # Container for ROS2 rtabmap rtabmap-ws: - # Build or Reference the image - build: . + build: + context: . + dockerfile: Dockerfile + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. + # Reference: https://docs.docker.com/compose/compose-file/build/#platforms + # platforms: + # - "linux/arm64" image: j3soon/ros2-rtabmap-ws container_name: ros2-rtabmap-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true - command: bash + command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/rtabmap_ws - - # Container Settings + working_dir: /home/ros2-essentials/rtabmap_ws environment: - - DISPLAY=${DISPLAY} - # Set ros2 environment variables. + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing - - ROS_LOCALHOST_ONLY=1 - - ROS_DOMAIN_ID=42 - - ROS2_WS=/home/ros2-agv-essentials/rtabmap_ws + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. - RCUTILS_COLORIZED_OUTPUT=1 - # If you want to access GPU, please uncomment the lines below. - # Reference : https://docs.docker.com/compose/gpu-support/ - # deploy: - # resources: - # reservations: - # devices: - # - driver: nvidia - # count: all - # capabilities: [ gpu ] + - ROS2_WS=/home/ros2-essentials/rtabmap_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [ gpu ] volumes: - # Mount local timezone into container. ( Readonly ) + # Mount local timezone into container. # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Direct Rendering Infrastructure + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. - /dev/dri:/dev/dri # Mount sound card to prevent Gazebo warning. - /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. + # - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 - - ./cache/.gazebo:/home/user/.gazebo - # Mounting the following directories will forbid direct deletion. - # Consider mount these directories only if the build process is slow. - # "source=${localWorkspaceFolder}/../cache/humble/build,target=/home/ws/build,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/install,target=/home/ws/install,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/log,target=/home/ws/log,type=bind" - # Mount workspace - - ../..:/home/ros2-agv-essentials \ No newline at end of file + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/scripts/create_workspace.sh b/scripts/create_workspace.sh index a6c10486..941964a4 100755 --- a/scripts/create_workspace.sh +++ b/scripts/create_workspace.sh @@ -38,9 +38,10 @@ rm -rf "${NEW_WS_DIR}/README.md" echo "# ${NEW_WS_NAME_UNDERSCORE}" > "${NEW_WS_DIR}/README.md" # Replace the "template_ws" with new workspace name in each file. +sed -i 's/"name": "Template",/"name": "TODO",/g' "${NEW_WS_DIR}/.devcontainer/devcontainer.json" sed -i "s/template_ws/${NEW_WS_NAME_UNDERSCORE}/g" "${NEW_WS_DIR}/.devcontainer/devcontainer.json" sed -i "s/template-ws/${NEW_WS_NAME_HYPHEN}/g" "${NEW_WS_DIR}/.devcontainer/devcontainer.json" sed -i "s/template_ws/${NEW_WS_NAME_UNDERSCORE}/g" "${NEW_WS_DIR}/docker/compose.yaml" sed -i "s/template-ws/${NEW_WS_NAME_HYPHEN}/g" "${NEW_WS_DIR}/docker/compose.yaml" -echo "Done." \ No newline at end of file +echo "Done." diff --git a/template_ws/.devcontainer/devcontainer.json b/template_ws/.devcontainer/devcontainer.json index b7c6fef6..08abed1e 100644 --- a/template_ws/.devcontainer/devcontainer.json +++ b/template_ws/.devcontainer/devcontainer.json @@ -1,9 +1,9 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "template-ws", + "name": "Template", "dockerComposeFile": "../docker/compose.yaml", "service": "template-ws", - // workspace settings + // Workspace settings "workspaceFolder": "/home/ros2-essentials/template_ws", // Vscode extensions "customizations": { @@ -20,4 +20,4 @@ ] } } -} \ No newline at end of file +} diff --git a/template_ws/.gitignore b/template_ws/.gitignore index 171c8a96..2bc3ebb3 100644 --- a/template_ws/.gitignore +++ b/template_ws/.gitignore @@ -1,6 +1,7 @@ +# Visual Studio Code .vscode # ROS2 basic directories /build /install -/log \ No newline at end of file +/log diff --git a/template_ws/docker/.bashrc b/template_ws/docker/.bashrc index 2fb551f1..ed090d4a 100644 --- a/template_ws/docker/.bashrc +++ b/template_ws/docker/.bashrc @@ -25,5 +25,6 @@ if [ ! -f $ROS2_WS/install/setup.bash ]; then fi echo "Workspace built." fi +# TODO: Source other workspace environments as underlay # Source workspace environment source $ROS2_WS/install/setup.bash diff --git a/template_ws/docker/Dockerfile b/template_ws/docker/Dockerfile index 73ecfa05..f7173a6f 100644 --- a/template_ws/docker/Dockerfile +++ b/template_ws/docker/Dockerfile @@ -1,6 +1,6 @@ -# Base Image : https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble +# Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble FROM osrf/ros:humble-desktop-full AS amd64 -# Base Image : https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble FROM arm64v8/ros:humble AS arm64 # Use docker automatic platform args to select the base image. @@ -86,8 +86,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ # && rm -rf /var/lib/apt/lists/* USER $USERNAME -COPY .bashrc /home/$USERNAME/.bashrc # Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here ENTRYPOINT [] CMD ["/bin/bash"] diff --git a/template_ws/docker/compose.yaml b/template_ws/docker/compose.yaml index 00261058..abe1e143 100644 --- a/template_ws/docker/compose.yaml +++ b/template_ws/docker/compose.yaml @@ -19,7 +19,6 @@ services: environment: # Set X11 server environment variable for existing display. - DISPLAY=$DISPLAY - # TODO: Set ros2 environment variables. # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html @@ -30,9 +29,10 @@ services: # Domain ID is set to 0 to allow communication through ros1_bridge. - RCUTILS_COLORIZED_OUTPUT=1 - ROS2_WS=/home/ros2-essentials/template_ws + # TODO: Add more environment variables here. # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ # deploy: - # # Reference : https://docs.docker.com/compose/gpu-support/ # resources: # reservations: # devices: @@ -53,12 +53,15 @@ services: - /dev/dri:/dev/dri # Mount sound card to prevent Gazebo warning. - /dev/snd:/dev/snd - # Uncomment the line below and comment out the two entries above to enable USB support. + # Mount shared memory for ROS2 communication. + - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. # - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 # Note that this volume is shared among all workspaces. - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. # Mount root workspace to allow easy access to all workspaces. - ../..:/home/ros2-essentials volumes: diff --git a/tests/diff_base/.devcontainer/devcontainer.json b/tests/diff_base/.devcontainer/devcontainer.json new file mode 100644 index 00000000..43c72c3e --- /dev/null +++ b/tests/diff_base/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +/* Reference: https://aka.ms/devcontainer.json */ +{ + "name": "{PLACEHOLDER}", + "dockerComposeFile": "../docker/compose.yaml", + "service": "{PLACEHOLDER}-ws", + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/{PLACEHOLDER}_ws", + // Vscode extensions + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-themes", + "twxs.cmake", + "donjayamanne.python-extension-pack", + "eamodio.gitlens", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", + "ms-iot.vscode-ros" + ] + } + } +} diff --git a/tests/diff_base/.gitignore b/tests/diff_base/.gitignore new file mode 100644 index 00000000..071f689a --- /dev/null +++ b/tests/diff_base/.gitignore @@ -0,0 +1,8 @@ +# Visual Studio Code +.vscode + +# ROS2 basic directories +/build +/install +/log +{PLACEHOLDER_MULTILINE} \ No newline at end of file diff --git a/tests/diff_base/docker/.bashrc b/tests/diff_base/docker/.bashrc new file mode 100644 index 00000000..29f8cd2f --- /dev/null +++ b/tests/diff_base/docker/.bashrc @@ -0,0 +1,31 @@ +# Source global ROS2 environment +source /opt/ros/$ROS_DISTRO/setup.bash +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet +if [ ! -f $ROS2_WS/install/setup.bash ]; then + echo "Workspace has not been built yet. Building workspace..." + cd $ROS2_WS + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html + if [ $(arch) == "aarch64" ]; then + colcon build --symlink-install{PLACEHOLDER} + else + colcon build --symlink-install + fi + echo "Workspace built." +fi +# TODO: Source other workspace environments as underlay +{PLACEHOLDER_MULTILINE} +# Source workspace environment +source $ROS2_WS/install/setup.bash diff --git a/tests/diff_base/docker/.dockerignore b/tests/diff_base/docker/.dockerignore new file mode 100644 index 00000000..2fb484e4 --- /dev/null +++ b/tests/diff_base/docker/.dockerignore @@ -0,0 +1,3 @@ +* +!.bashrc +{PLACEHOLDER_MULTILINE} \ No newline at end of file diff --git a/tests/diff_base/docker/Dockerfile b/tests/diff_base/docker/Dockerfile new file mode 100644 index 00000000..a1eb1d02 --- /dev/null +++ b/tests/diff_base/docker/Dockerfile @@ -0,0 +1,97 @@ +# Base Image: {PLACEHOLDER} +FROM {PLACEHOLDER} AS amd64 +# Base Image: {PLACEHOLDER} +FROM {PLACEHOLDER} AS arm64 + +# Use docker automatic platform args to select the base image. +# It may be `arm64` or `amd64` depending on the platform. +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +FROM $TARGETARCH +ARG TARGETARCH +{PLACEHOLDER_MULTILINE} +# Arguments for the default user +ARG USERNAME=user +ARG USER_UID=1000 + +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* + +# Install common tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + curl \ + git \ + htop \ + iputils-ping \ + nano \ + net-tools \ + tmux \ + tree \ + unzip \ + vim \ + wget \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ + && rm -rf /var/lib/apt/lists/* + +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-gazebo-ros-pkgs \ + ros-$ROS_DISTRO-gazebo-ros2-control \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* +{PLACEHOLDER_MULTILINE} +USER $USERNAME +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +{PLACEHOLDER_MULTILINE} +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here +{PLACEHOLDER_MULTILINE} +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/tests/diff_base/docker/compose.yaml b/tests/diff_base/docker/compose.yaml new file mode 100644 index 00000000..d18260d2 --- /dev/null +++ b/tests/diff_base/docker/compose.yaml @@ -0,0 +1,71 @@ +services: + {PLACEHOLDER}-ws: + build: + context: . + dockerfile: Dockerfile + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. + # Reference: https://docs.docker.com/compose/compose-file/build/#platforms + # platforms: + # - "linux/arm64" + image: j3soon/ros2-{PLACEHOLDER}-ws + container_name: ros2-{PLACEHOLDER}-ws + stdin_open: true + tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. + {PLACEHOLDER_#}privileged: true + command: /bin/bash + network_mode: host + working_dir: /home/ros2-essentials/{PLACEHOLDER}_ws + environment: + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY + # References: + # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html + # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html + # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing + - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. + - ROS_DOMAIN_ID=0 + # Domain ID is set to 0 to allow communication through ros1_bridge. + - RCUTILS_COLORIZED_OUTPUT=1 + - ROS2_WS=/home/ros2-essentials/{PLACEHOLDER}_ws + # TODO: Add more environment variables here. +{PLACEHOLDER_MULTILINE} + # TODO: Uncomment the lines below to enable GPU support. + {PLACEHOLDER_#}# Reference: https://docs.docker.com/compose/gpu-support/ + {PLACEHOLDER_#}deploy: + {PLACEHOLDER_#} resources: + {PLACEHOLDER_#} reservations: + {PLACEHOLDER_#} devices: + {PLACEHOLDER_#} - driver: nvidia + {PLACEHOLDER_#} count: all + {PLACEHOLDER_#} capabilities: [ gpu ] + volumes: + # Mount local timezone into container. + # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + # Mount X11 server + - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. + - $HOME/.Xauthority:/home/user/.Xauthority + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. + {PLACEHOLDER_#}- /dev/dri:/dev/dri + # Mount sound card to prevent Gazebo warning. + {PLACEHOLDER_#}- /dev/snd:/dev/snd + # Mount shared memory for ROS2 communication. + {PLACEHOLDER_#}- /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. + {PLACEHOLDER_#}- /dev:/dev + # Mount Gazebo models directory to reuse models downloaded during first launch. + # Reference: https://answers.ros.org/question/365658 + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. +{PLACEHOLDER_MULTILINE} + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache diff --git a/tests/lint_comp_template.py b/tests/lint_comp_template.py new file mode 100644 index 00000000..45e962f7 --- /dev/null +++ b/tests/lint_comp_template.py @@ -0,0 +1,89 @@ +import difflib +import glob +import logging +import os +import re +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +def get_regexp_from_template_line(line): + regexp = re.escape(line) \ + .replace(r'\{PLACEHOLDER_\#\}', '(# )?') \ + .replace(r'\{PLACEHOLDER\}', '.*') + return f"^{regexp}$" + +def compare_file_with_template(filepath, targetpath=None, ignored_workspaces=[]): + targetpath = targetpath or filepath + logging.info(f"Checking if '{targetpath}' matches the template...") + template_path = f"{repo_dir}/tests/diff_base/{filepath}" + template = Path(template_path).read_text().splitlines(keepends=True) # keepends to preserve trailing newlines + for filename in glob.glob(f"{repo_dir}/*_ws/{targetpath}"): + # Skip certain cases intentionally + if any(ws in filename for ws in ignored_workspaces): + continue + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + content = Path(filename).read_text().splitlines(keepends=True) + diff = list(filter(lambda x: x.startswith('- ') or x.startswith('+ ') or x.startswith(' '), difflib.ndiff(template, content))) + def error(msg, i): + diff.insert(i+2, "! <<< Parsing failed before reaching here >>>\n") + logging.info('\n' + ''.join(diff)) + logging.error(msg) + raise ValueError(f"'{filepath}' does not match the template: '{filename}'") + i = 0 + while i < len(diff): + if "- {PLACEHOLDER_MULTILINE}" in diff[i]: # don't use exact match to avoid cases without trailing newline + i += 1 + while i < len(diff) and diff[i].startswith('+ '): + i += 1 + continue + if diff[i].startswith(' '): + i += 1 + continue + if i+1 >= len(diff): + error("Odd lines", i) + # Stack and compare + if diff[i].startswith('- '): + stack = [diff[i]] + j = i+1 + while j < len(diff) and diff[j].startswith('- '): + stack.append(j) + j += 1 + k = 0 + while k < len(stack) and j+k < len(diff) and diff[j+k].startswith('+ '): + if "- {PLACEHOLDER_MULTILINE}" in diff[i+k]: + raise error("{PLACEHOLDER_MULTILINE} is not supported in this case yet", i) + regexp = get_regexp_from_template_line(diff[i+k][2:]) + if not re.match(regexp, diff[j+k][2:]): + error("Expected line deletion and addition to differ only in the placeholder", i) + k += 1 + if k != len(stack): + error("Expected no line deletion and addition", i) + elif diff[i].startswith('+ '): + stack = [diff[i]] + j = i+1 + while j < len(diff) and diff[j].startswith('+ '): + stack.append(j) + j += 1 + k = 0 + while k < len(stack) and j+k < len(diff) and diff[j+k].startswith('- '): + if "- {PLACEHOLDER_MULTILINE}" in diff[j+k]: + raise error("{PLACEHOLDER_MULTILINE} is not supported in this case yet", i) + regexp = get_regexp_from_template_line(diff[j+k][2:]) + if not re.match(regexp, diff[i+k][2:]): + error("Expected line deletion and addition to differ only in the placeholder", i) + k += 1 + if k != len(stack): + error("Expected no line deletion and addition", i) + else: + raise ValueError("Unexpected diff prefix") + i = j + k + +compare_file_with_template(".devcontainer/devcontainer.json", ignored_workspaces=["ros1_bridge_ws"]) +compare_file_with_template(".gitignore") +compare_file_with_template("docker/.bashrc") +compare_file_with_template("docker/.dockerignore", ignored_workspaces=["ros1_bridge_ws", "orbslam3_ws"]) +compare_file_with_template("docker/compose.yaml", ignored_workspaces=["ros1_bridge_ws"]) +compare_file_with_template("docker/Dockerfile", ignored_workspaces=["ros1_bridge_ws"]) diff --git a/tests/lint_compose.py b/tests/lint_compose.py new file mode 100644 index 00000000..3255731f --- /dev/null +++ b/tests/lint_compose.py @@ -0,0 +1,16 @@ +import glob +import logging +import os +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +logging.info("Checking `compose.yaml` files...") +for filename in glob.glob(f"{repo_dir}/*_ws/docker/compose*.yaml"): + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + content = Path(filename).read_text() + if "version:" in content: + # Ref: https://docs.docker.com/compose/compose-file/04-version-and-name/#version-top-level-element-optional + raise ValueError(f"`version` should not exist since it's obsolete: '{filename}'") diff --git a/tests/lint_dockerfile.py b/tests/lint_dockerfile.py new file mode 100644 index 00000000..e5853701 --- /dev/null +++ b/tests/lint_dockerfile.py @@ -0,0 +1,28 @@ +import glob +import logging +import os +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +logging.info("Checking `Dockerfile` files...") +for filename in glob.glob(f"{repo_dir}/*_ws/docker/Dockerfile*"): + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + # Skip certain cases intentionally + if "ros1_bridge_ws" in filename: + continue + content = Path(filename).read_text() + if "ros-humble-" in content: + raise ValueError(f"`ros-humble-*` should not exist, use `ros-$ROS_DISTRO-*` instead: '{filename}'") + if "$USER_GID" in content: + raise ValueError(f"`$USER_GID` should not exist since it's unnecessary: '{filename}'") + if "RUN apt-get update" in content: + raise ValueError(f"`RUN apt-get update` should not exist, use cache mounts instead: '{filename}'") + if "RUN sudo apt-get update" in content: + raise ValueError(f"`RUN sudo apt-get update` should not exist, use cache mounts instead: '{filename}'") + if "RUN apt-get install" in content: + raise ValueError(f"`RUN apt-get install` should not exist, use with `apt-get update` instead: '{filename}'") + if " apt-get install" in content: + raise ValueError(f"` apt-get install` should not exist, use with `apt-get update` in the same line instead: '{filename}'") diff --git a/tests/lint_filenames.py b/tests/lint_filenames.py index 8a12d45e..d307fb29 100644 --- a/tests/lint_filenames.py +++ b/tests/lint_filenames.py @@ -1,7 +1,12 @@ -import os import glob +import logging +import os + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") -# Check if all default files exist +logging.info("Checking if all default files exist...") DEFAULT_FILES = [ ".gitignore", "README.md", @@ -11,10 +16,8 @@ "docker/Dockerfile", ".devcontainer/devcontainer.json", ] -current_dir = os.path.dirname(os.path.realpath(__file__)) -repo_dir = os.path.realpath(f"{current_dir}/..") for filename in DEFAULT_FILES: - print(f"Checking existence of: '{filename}'...") + logging.debug(f"Checking existence of: '{filename}'...") for workspace_path in glob.glob(f"{repo_dir}/*_ws"): if not os.path.isfile(f"{workspace_path}/{filename}"): # Skip certain cases intentionally @@ -24,13 +27,16 @@ # Report error raise ValueError(f"'{filename}' does not exist in: '{workspace_path}'") -# Check compose.yaml files -for filename in glob.glob(f"{repo_dir}/**/compose*.yaml", recursive=True): - print(f"Checking: '{filename[len(repo_dir)+1:]}'...") - with open(filename, "r") as f: - content = f.read() - if "version:" in content: - # Ref: https://docs.docker.com/compose/compose-file/04-version-and-name/#version-top-level-element-optional - raise ValueError(f"version should not exist since it's obsolete: '{filename}'") - -print("All checks passed!") +logging.info("Checking if all obsolete files do not exist...") +OBSOLETE_FILES = [ + "docker/cache/.gazebo/.gitkeep", + "docker/compose.yml", + "docker/docker-compose.yaml", + "docker/docker-compose.yml", + ".devcontainer/postCreateCommand.sh", +] +for filename in OBSOLETE_FILES: + logging.debug(f"Checking non-existence of: '{filename}'...") + for workspace_path in glob.glob(f"{repo_dir}/*_ws"): + if os.path.isfile(f"{workspace_path}/{filename}"): + raise ValueError(f"'{filename}' exists in: '{workspace_path}'") diff --git a/tests/lint_gitignore.py b/tests/lint_gitignore.py new file mode 100644 index 00000000..da2a26c6 --- /dev/null +++ b/tests/lint_gitignore.py @@ -0,0 +1,17 @@ +import glob +import logging +import os +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +logging.info("Checking `.gitignore` files...") +for filename in glob.glob(f"{repo_dir}/*_ws/.gitignore"): + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + content = Path(filename).read_text() + if "gazebo" in content: + raise ValueError(f"`gazebo` should not exist since it's obsolete: '{filename}'") + if "docker/cache" in content: + raise ValueError(f"`docker/cache` should not exist since it's obsolete: '{filename}'") diff --git a/tests/lint_readme.py b/tests/lint_readme.py new file mode 100644 index 00000000..5a8277e3 --- /dev/null +++ b/tests/lint_readme.py @@ -0,0 +1,15 @@ +import glob +import logging +import os +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +logging.info("Checking `README.md` files...") +for filename in glob.glob(f"{repo_dir}/*_ws/README.md"): + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + content = Path(filename).read_text() + if "ros2-agv-essentials" in content: + raise ValueError(f"`ros2-agv-essentials` should not exist, use `ros2-essentials` instead: '{filename}'") diff --git a/tests/lint_workflows.py b/tests/lint_workflows.py new file mode 100644 index 00000000..36a29285 --- /dev/null +++ b/tests/lint_workflows.py @@ -0,0 +1,16 @@ +import glob +import logging +import os +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +current_dir = os.path.dirname(os.path.realpath(__file__)) +repo_dir = os.path.realpath(f"{current_dir}/..") + +logging.info("Checking if `master` branch is accidentally used in github workflows...") +for filename in glob.glob(f"{repo_dir}/.github/workflows/*.yaml"): + logging.debug(f"Checking: '{filename[len(repo_dir)+1:]}'...") + content = Path(filename).read_text() + if "master" in content: + # Ref: https://github.com/j3soon/ros2-essentials/pull/44#pullrequestreview-2251404984 + raise ValueError(f"`master` should not exist since it's obsolete: '{filename}'") diff --git a/tests/test_all.sh b/tests/test_all.sh new file mode 100755 index 00000000..2b32c34f --- /dev/null +++ b/tests/test_all.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e + +# Get the directory of this script. +# Reference: https://stackoverflow.com/q/59895 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)" +cd $SCRIPT_DIR +# Loop through all Python files in the current directory +for script in *.py; do + echo "Running $script..." + python3 "$script" +done +echo "All tests have passed." diff --git a/vlp_ws/.devcontainer/devcontainer.json b/vlp_ws/.devcontainer/devcontainer.json index a402fe92..2060c085 100644 --- a/vlp_ws/.devcontainer/devcontainer.json +++ b/vlp_ws/.devcontainer/devcontainer.json @@ -1,12 +1,10 @@ /* Reference: https://aka.ms/devcontainer.json */ { - "name": "ROS2 and VLP-16 Container", + "name": "VLP-16", "dockerComposeFile": "../docker/compose.yaml", "service": "vlp-ws", - - // workspace settings - "workspaceFolder": "/home/ros2-agv-essentials/vlp_ws", - + // Workspace settings + "workspaceFolder": "/home/ros2-essentials/vlp_ws", // Vscode extensions "customizations": { "vscode": { @@ -16,11 +14,10 @@ "twxs.cmake", "donjayamanne.python-extension-pack", "eamodio.gitlens", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", "ms-iot.vscode-ros" ] } - }, - - // Lifecycle scripts - "postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postCreateCommand.sh" -} \ No newline at end of file + } +} diff --git a/vlp_ws/.devcontainer/postCreateCommand.sh b/vlp_ws/.devcontainer/postCreateCommand.sh deleted file mode 100644 index efce9e5b..00000000 --- a/vlp_ws/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,6 +0,0 @@ -sudo apt-get update -sudo rosdep update -# Note: The following commands are commented out to prevent unintended install/builds. -# sudo rosdep install --from-paths src --ignore-src -y -# sudo chown -R user /home/ros2-agv-essentials/ -# colcon build \ No newline at end of file diff --git a/vlp_ws/.gitignore b/vlp_ws/.gitignore index d56eeb47..2bc3ebb3 100644 --- a/vlp_ws/.gitignore +++ b/vlp_ws/.gitignore @@ -1,8 +1,7 @@ +# Visual Studio Code .vscode + +# ROS2 basic directories /build /install /log -docker/cache/* -!docker/cache/.gazebo -docker/cache/.gazebo/* -!docker/cache/.gazebo/.gitkeep \ No newline at end of file diff --git a/vlp_ws/README.md b/vlp_ws/README.md index 7d6573f0..0002ad37 100644 --- a/vlp_ws/README.md +++ b/vlp_ws/README.md @@ -129,13 +129,13 @@ ros2 launch vlp_cartographer vlp_driver.launch.py - LiDAR driver ```bash - docker exec -it ros2-vlp-ws /home/ros2-agv-essentials/vlp_ws/scripts/lidar-driver-bringup.sh + docker exec -it ros2-vlp-ws /home/ros2-essentials/vlp_ws/scripts/lidar-driver-bringup.sh ``` - After launching the driver, launch the cartographer in another terminal ```bash - docker exec -it ros2-vlp-ws /home/ros2-agv-essentials/vlp_ws/scripts/lidar-slam-bringup.sh + docker exec -it ros2-vlp-ws /home/ros2-essentials/vlp_ws/scripts/lidar-slam-bringup.sh ``` --- diff --git a/vlp_ws/docker/.bashrc b/vlp_ws/docker/.bashrc index c1169b05..d8352e3e 100644 --- a/vlp_ws/docker/.bashrc +++ b/vlp_ws/docker/.bashrc @@ -1,21 +1,30 @@ # Source global ROS2 environment source /opt/ros/$ROS_DISTRO/setup.bash -# Source workspace environment - -# Check if the workspace has been built by check install/setup.bash +# Optionally perform apt update if it has not been executed yet +if [ -z "$( ls -A '/var/lib/apt/lists' )" ]; then + echo "apt-get update has not been executed yet. Running sudo apt-get update..." + sudo apt-get update +fi +# Optionally perform rosdep update if it has not been executed yet +if [ ! -d $HOME/.ros/rosdep/sources.cache ]; then + echo "rosdep update has not been executed yet. Running rosdep update..." + rosdep update +fi +# Optionally build the workspace if it has not been built yet if [ ! -f $ROS2_WS/install/setup.bash ]; then echo "Workspace has not been built yet. Building workspace..." cd $ROS2_WS - - # If command "arch" is "aarch64", don't build velodyne_gazebo_plugins + # Ref: https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html + rosdep install --from-paths src --ignore-src -y -r + # TODO: If command `arch` outputs `aarch64`, consider adding `--packages-ignore ` to ignore x86 packages + # Ref: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html if [ $(arch) == "aarch64" ]; then colcon build --symlink-install --packages-ignore velodyne_gazebo_plugins else colcon build --symlink-install fi - echo "Workspace built." fi - -# Note: If you have not built your workspace yet, the following command will fail +# TODO: Source other workspace environments as underlay +# Source workspace environment source $ROS2_WS/install/setup.bash diff --git a/vlp_ws/docker/Dockerfile b/vlp_ws/docker/Dockerfile index 95721625..485e0a3b 100644 --- a/vlp_ws/docker/Dockerfile +++ b/vlp_ws/docker/Dockerfile @@ -1,62 +1,115 @@ -# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble -FROM arm64v8/ros:humble AS arm64 # Base Image: https://hub.docker.com/r/osrf/ros/tags?page=1&name=humble FROM osrf/ros:humble-desktop-full AS amd64 +# Base Image: https://hub.docker.com/r/arm64v8/ros/tags?page=1&name=humble +FROM arm64v8/ros:humble AS arm64 # Use docker automatic platform args to select the base image. # It may be `arm64` or `amd64` depending on the platform. -# Reference: -# - https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope +# Ref: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope FROM $TARGETARCH +ARG TARGETARCH LABEL org.opencontainers.image.authors="assume0701@gmail.com" -LABEL description = \ - "An image for ROS2 VLP LiDAR." -ARG TARGETARCH +# Arguments for the default user ARG USERNAME=user ARG USER_UID=1000 -ARG USER_GID=$USER_UID -############################################# -# --- Basic setup --- +# Keep downloaded packages for caching purposes +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache -# Install necessary packages, Note: formation ref: template_ws -RUN apt-get update && apt-get install -y \ - sudo \ - vim \ +# Upgrade packages +# Ref: https://pythonspeed.com/articles/security-updates-in-docker/ +# Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#example-cache-apt-packages +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1264502398 +# Ref: https://github.com/moby/buildkit/issues/1673#issuecomment-1987107404 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* + +# Install sudo and create a user with sudo privileges +# Ref: https://stackoverflow.com/a/65434659 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y sudo \ + && useradd -m -s /bin/bash -u $USER_UID -G sudo $USERNAME \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && rm -rf /var/lib/apt/lists/* + +# Install common tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ curl \ - tree \ + git \ + htop \ + iputils-ping \ + nano \ + net-tools \ tmux \ + tree \ + unzip \ + vim \ + wget \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# Install Python pip +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install custom tools +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + git-extras \ && rm -rf /var/lib/apt/lists/* +# Install ROS2 Gazebo packages for amd64 +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-gazebo-ros-pkgs \ + ros-$ROS_DISTRO-gazebo-ros2-control \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# Install ROS2 RVIZ and other custom ROS2 packages +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ + ros-$ROS_DISTRO-rviz2 \ + && rm -rf /var/lib/apt/lists/* + +# TODO: Add more commands here +# For example, to install additional packages, uncomment the following lines and add the package names +# RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +# apt-get update && apt-get install -y \ +# $OTHER_PACKAGES \ +# && rm -rf /var/lib/apt/lists/* + # Install ROS2 and Gazebo simulation packages when using `amd64` platform -RUN if [ "$TARGETARCH" = "amd64" ]; then \ - apt update && apt install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-turtlebot3-gazebo \ - ros-$ROS_DISTRO-gazebo-ros-pkgs \ ros-$ROS_DISTRO-joint-state-publisher-gui \ ros-$ROS_DISTRO-rqt-robot-steering \ && rm -rf /var/lib/apt/lists/*; \ - curl -sSL http://get.gazebosim.org | sh; \ fi # Install ROS2 velodyne package -RUN apt-get update && \ - apt-get install -y \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ + apt-get update && apt-get install -y \ ros-$ROS_DISTRO-turtlebot3 \ ros-$ROS_DISTRO-diagnostic-updater \ - ros-$ROS_DISTRO-velodyne && \ - rm -rf /var/lib/apt/lists/* - -# # add user with default bash -RUN adduser --disabled-password --gecos '' --shell /bin/bash ${USERNAME} && \ - adduser ${USERNAME} sudo && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers - -COPY .bashrc /home/${USERNAME}/.bashrc - -# Login with user -USER ${USERNAME} + ros-$ROS_DISTRO-velodyne \ + && rm -rf /var/lib/apt/lists/* -CMD ["bash"] \ No newline at end of file +USER $USERNAME +# Create Gazebo cache directory with correct ownership to avoid permission issues after volume mount +RUN mkdir /home/$USERNAME/.gazebo +# TODO: Run additional commands as non-root user here +COPY .bashrc /home/$USERNAME/.bashrc +# TODO: Copy additional files here +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/vlp_ws/docker/cache/.gazebo/.gitkeep b/vlp_ws/docker/cache/.gazebo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/vlp_ws/docker/compose.yaml b/vlp_ws/docker/compose.yaml index 319f7fc3..364ae7ae 100644 --- a/vlp_ws/docker/compose.yaml +++ b/vlp_ws/docker/compose.yaml @@ -1,12 +1,9 @@ services: - # Container for ROS2 VLP LiDAR vlp-ws: - # Build or Reference the image build: context: . dockerfile: Dockerfile - # Specify the target platform to build the image. - # It could still be automatically detected. + # TODO: Specify the target platform to build the image, otherwise it will build for the host platform. # Reference: https://docs.docker.com/compose/compose-file/build/#platforms # platforms: # - "linux/arm64" @@ -14,53 +11,59 @@ services: container_name: ros2-vlp-ws stdin_open: true tty: true + # TODO: Comment the line below if the workspace don't need to communicate through `/dev/*` devices. privileged: true - command: bash + command: /bin/bash network_mode: host - working_dir: /home/ros2-agv-essentials/vlp_ws - - # Container Settings + working_dir: /home/ros2-essentials/vlp_ws environment: - - DISPLAY=${DISPLAY} - # Set ros2 environment variables. + # Set X11 server environment variable for existing display. + - DISPLAY=$DISPLAY # References: # - https://docs.ros.org/en/humble/Concepts/Intermediate/About-Domain-ID.html # - https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html # - https://docs.ros.org/en/humble/Tutorials/Demos/Logging-and-logger-configuration.html#console-output-colorizing - ROS_LOCALHOST_ONLY=0 + # Localhost only is disabled by default to allow communication with external devices. - ROS_DOMAIN_ID=0 - - ROS2_WS=/home/ros2-agv-essentials/vlp_ws + # Domain ID is set to 0 to allow communication through ros1_bridge. - RCUTILS_COLORIZED_OUTPUT=1 - # If you want to access GPU, please uncomment the lines below. - # Reference : https://docs.docker.com/compose/gpu-support/ - # deploy: - # resources: - # reservations: - # devices: - # - driver: nvidia - # count: all - # capabilities: [ gpu ] + - ROS2_WS=/home/ros2-essentials/vlp_ws + # TODO: Add more environment variables here. + # TODO: Uncomment the lines below to enable GPU support. + # # Reference: https://docs.docker.com/compose/gpu-support/ + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [ gpu ] volumes: - # Mount local timezone into container. ( Readonly ) + # Mount local timezone into container. # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # Mount X11 server - /tmp/.X11-unix:/tmp/.X11-unix + # X11-unix is mounted to allow GUI applications to display on host. - $HOME/.Xauthority:/home/user/.Xauthority - # Direct Rendering Infrastructure + # Xauthority is mounted to allow X11 forwarding for remote display. + # Mount Direct Rendering Infrastructure (DRI) for hardware acceleration support such as OpenGL. - /dev/dri:/dev/dri # Mount sound card to prevent Gazebo warning. - /dev/snd:/dev/snd # Mount shared memory for ROS2 communication. - /dev/shm:/dev/shm + # TODO: Uncomment the line below and comment out the three entries above to enable USB support. + # - /dev:/dev # Mount Gazebo models directory to reuse models downloaded during first launch. # Reference: https://answers.ros.org/question/365658 - - ./cache/.gazebo:/home/user/.gazebo - # Mounting the following directories will forbid direct deletion. - # Consider mount these directories only if the build process is slow. - # "source=${localWorkspaceFolder}/../cache/humble/build,target=/home/ws/build,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/install,target=/home/ws/install,type=bind", - # "source=${localWorkspaceFolder}/../cache/humble/log,target=/home/ws/log,type=bind" - # Mount workspace - - ../..:/home/ros2-agv-essentials \ No newline at end of file + # Note that this volume is shared among all workspaces. + - gazebo-cache:/home/user/.gazebo + # TODO: Add more volume mounts here. + # Mount root workspace to allow easy access to all workspaces. + - ../..:/home/ros2-essentials +volumes: + gazebo-cache: + name: ros2-gazebo-cache