diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 00000000..63038bc2 --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,2 @@ + +/.artifacts \ No newline at end of file diff --git a/.devcontainer/.vscode-docker/c_cpp_properties.json b/.devcontainer/.vscode-docker/c_cpp_properties.json new file mode 100644 index 00000000..04f7c4d2 --- /dev/null +++ b/.devcontainer/.vscode-docker/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/opt/ros/**", + "/usr/share/c++-mscl/source/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++14", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.devcontainer/.vscode-docker/launch.json b/.devcontainer/.vscode-docker/launch.json new file mode 100644 index 00000000..d757c5ba --- /dev/null +++ b/.devcontainer/.vscode-docker/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "microstrain launch", + "request": "launch", + "target": "/home/microstrain/src/ros_mscl/launch/microstrain.launch", + "type": "ros", + "preLaunchTask": "build" + } + ] +} \ No newline at end of file diff --git a/.devcontainer/.vscode-docker/settings.json b/.devcontainer/.vscode-docker/settings.json new file mode 100644 index 00000000..87236d6a --- /dev/null +++ b/.devcontainer/.vscode-docker/settings.json @@ -0,0 +1,10 @@ +{ + "python.autoComplete.extraPaths": [ + "/home/ros-mscl/devel/lib/python3/dist-packages", + "/opt/ros/noetic/lib/python3/dist-packages" + ], + "python.analysis.extraPaths": [ + "/home/ros-mscl/devel/lib/python3/dist-packages", + "/opt/ros/noetic/lib/python3/dist-packages" + ] +} \ No newline at end of file diff --git a/.devcontainer/.vscode-docker/tasks.json b/.devcontainer/.vscode-docker/tasks.json new file mode 100644 index 00000000..1aa4ea70 --- /dev/null +++ b/.devcontainer/.vscode-docker/tasks.json @@ -0,0 +1,20 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "/ros_entrypoint.sh", + "args": [ + "catkin_make", + "-DCMAKE_BUILD_TYPE=DEBUG" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile.deploy b/.devcontainer/Dockerfile.deploy new file mode 100644 index 00000000..8a5bc877 --- /dev/null +++ b/.devcontainer/Dockerfile.deploy @@ -0,0 +1,31 @@ +ARG DEV_IMAGE +ARG ARCH="amd64" +ARG ROS_VERSION="noetic" +ARG ROS_MSCL_DIR="/usr/share/ros-mscl" +FROM ${DEV_IMAGE} as builder + +# We will do everything from now on as root since we will be building and copying to global directories +USER root + +# Build the driver and it's messages +ARG ROS_MSCL_DIR +COPY ros_mscl "${ROS_MSCL_DIR}/src/ros_mscl" +COPY mscl_msgs "${ROS_MSCL_DIR}/src/mscl_msgs" +RUN set -ex \ + && cd "${ROS_MSCL_DIR}" \ + && /ros_entrypoint.sh catkin_make install -DCMAKE_BUILD_TYPE="RELEASE" + +# Build a lighter runtime image from the core ros image which is smaller +FROM ${ARCH}/ros:${ROS_VERSION}-ros-core + +# Copy the built binaries from the dev image as well as any dependencies +ARG ROS_MSCL_DIR +ENV ROS_MSCL_DIR "${ROS_MSCL_DIR}" +COPY --from=builder ${ROS_MSCL_DIR}/install ${ROS_MSCL_DIR}/install +COPY --from=builder /usr/share/c++-mscl/libmscl.so /usr/share/c++-mscl/libmscl.so + +# Make sure that MSCL is loadable +ENV LD_LIBRARY_PATH "${LD_LIBRARY_PATH}:/usr/share/c++-mscl" + +# Default command will be to run ros launch file +CMD ["/bin/bash", "-c", ". ${ROS_MSCL_DIR}/install/setup.bash && roslaunch ros_mscl microstrain.launch"] \ No newline at end of file diff --git a/docker/Dockerfile b/.devcontainer/Dockerfile.dev similarity index 67% rename from docker/Dockerfile rename to .devcontainer/Dockerfile.dev index ac15494e..6ecc2431 100644 --- a/docker/Dockerfile +++ b/.devcontainer/Dockerfile.dev @@ -1,11 +1,16 @@ -FROM ros:noetic-ros-base-focal +ARG ARCH="amd64" +ARG ROS_VERSION="noetic" +FROM ${ARCH}/ros:${ROS_VERSION}-ros-base # Install MSCL (github deb install is kind of a pain) ENV DEBIAN_FRONTEND="noninteractive" RUN set -ex \ && apt-get update && apt-get install -y \ jq \ + gdb \ + git \ curl \ + sudo \ && install_github_deb () { \ org="${1}"; \ repo="${2}"; \ @@ -25,3 +30,15 @@ COPY mscl_msgs /tmp/src/mscl_msgs RUN set -ex \ && rosdep install --from-paths /tmp/src --ignore-src -r -y \ && rm -rf /tmp/src + +# Add a user that will be used when shelling into this container and allow them to use devices +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN set -ex \ + && groupadd -g ${USER_ID} microstrain \ + && useradd -N -m -u ${USER_ID} -g ${GROUP_ID} -G "dialout" microstrain + +# Make the directory where we will mount all the files into +USER microstrain +RUN set -ex \ + && mkdir -p ${HOME}/src \ No newline at end of file diff --git a/.devcontainer/Makefile b/.devcontainer/Makefile new file mode 100644 index 00000000..8e67a960 --- /dev/null +++ b/.devcontainer/Makefile @@ -0,0 +1,82 @@ +.PHONY: all build build-shell clean + +# These variables should be overridden in CI builds to configure this build +version ?= latest +docker ?= docker +arch ?= amd64 +ros_version ?= noetic + +# Just set some directory values to support out of tree builds +makefile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +docker_dir := $(dir $(makefile_path)) +project_dir := $(abspath $(docker_dir)/..) +project_name := $(shell echo $(notdir $(project_dir)) | tr A-Z a-z) +artifacts_dir := $(docker_dir)/.artifacts/$(arch)-$(ros_version) +build_dir := $(artifacts_dir)/build +devel_dir := $(artifacts_dir)/devel +docker_catkin_root := /home/microstrain +docker_catkin_src_dir := $(docker_catkin_root)/src +docker_catkin_build_dir := $(docker_catkin_root)/build +docker_catkin_devel_dir := $(docker_catkin_root)/devel + +# All of these directories have to exist before we can run most tasks +dir_deps := $(artifacts_dir) $(build_dir) $(devel_dir) + +# Grab the user and group ID so that the files we create can be deleted and managed from the base system +user_id := $(shell id -u) +group_id := $(shell id -g) + +# Set up some variables for the docker build +dev_dockerfile := $(docker_dir)/Dockerfile.dev +deploy_dockerfile := $(docker_dir)/Dockerfile.deploy +build_args := --build-arg http_proxy --build-arg https_proxy --build-arg no_proxy --build-arg USER_ID=$(user_id) --build-arg GROUP_ID=$(group_id) +run_args := -e http_proxy -e https_proxy -e no_proxy +run_mounts := -v "$(project_dir)/ros_mscl:$(docker_catkin_src_dir)/ros_mscl" -v "$(project_dir)/mscl_msgs:$(docker_catkin_src_dir)/mscl_msgs" -v "$(build_dir):$(docker_catkin_build_dir)" -v "$(devel_dir):$(docker_catkin_devel_dir)" +dev_image_name := $(arch)/$(project_name)-dev:$(ros_version) +deploy_image_name := $(arch)/$(project_name):$(ros_version) +dev_image_artifact := $(artifacts_dir)/.image + +all: image + +image: $(dev_image_artifact) $(deploy_dockerfile) + @$(docker) build \ + --build-arg DEV_IMAGE="$(dev_image_name)" \ + -t $(deploy_image_name) \ + -f $(deploy_dockerfile) \ + $(build_args) \ + $(project_dir) + +$(dev_image_artifact): $(dev_dockerfile) | $(dir_deps) + @$(docker) build \ + -t $(dev_image_name) \ + -f $(dev_dockerfile) \ + $(build_args) \ + $(project_dir) + @echo $(dev_image_name) > $@ + +build-shell: $(dev_image_artifact) + @$(docker) run \ + -it \ + --rm \ + -v /dev:/dev \ + --user "microstrain" \ + -w $(docker_catkin_root) \ + --privileged \ + $(run_args) \ + $(run_mounts) \ + $$(cat $<) + +$(artifacts_dir): + @mkdir -p $@ + +$(build_dir): + @mkdir -p $@ + +$(devel_dir): + @mkdir -p $@ + +clean: + @rm -rf "$(build_dir)" "$(devel_dir)" + @rm -f "$(dev_image_artifact)" + @docker ps -a | grep "$(project_name)" | grep "$(arch)" | grep "$(ros_version)" | tr -s " " | cut -d' ' -f1 | xargs docker rm -f || echo "No containers to remove" + @docker images | grep "$(project_name)" | grep "$(arch)" | grep "$(ros_version)" | tr -s " " | cut -d' ' -f1 | xargs docker rmi -f || echo "No images to remove" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b7514387 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +{ + "name": "amd64/ros-mscl-dev:noetic", + "context": "..", + "dockerFile": "Dockerfile.dev", + "extensions": [ + "ms-iot.vscode-ros", + "ms-vscode.cpptools", + "ms-python.python", + "ms-vscode.cmake-tools" + ], + "containerUser": "root", + "remoteUser": "microstrain", + "runArgs": [ + "--privileged", + "--security-opt", "seccomp=unconfined" + ], + "settings": { + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/ros_entrypoint.sh", + "args": ["/bin/bash"] + } + }, + "terminal.integrated.defaultProfile.windows": "bash" + }, + "postCreateCommand": "/ros_entrypoint.sh roscore", + "mounts": [ + "source=/dev,target=/dev,type=bind,consistency=cached", + "source=${localWorkspaceFolder}/ros_mscl,target=/home/microstrain/src/ros_mscl,type=bind,consistency=cached", + "source=${localWorkspaceFolder}/mscl_msgs,target=/home/microstrain/src/mscl_msgs,type=bind,consistency=cached", + "source=${localWorkspaceFolder}/.devcontainer/.vscode-docker,target=/home/microstrain/.vscode,type=bind,consistency=cached" + ], + "workspaceMount": "", + "workspaceFolder": "/home/microstrain" +} \ No newline at end of file diff --git a/README.md b/README.md index c20142e3..12cf325d 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,36 @@ This will launch two nodes that publish data to different namespaces: An example subscriber node can be found here: [ROS-MSCL Examples](https://github.com/LORD-MicroStrain/ROS-MSCL/tree/master/Examples) +## Docker Integration + +### VSCode + +The easiest way to use docker while still using an IDE is to use VSCode as an IDE. Follow the steps below to develop on this repo in a docker container + +1. Install the following dependencies: + 1. [VSCode](https://code.visualstudio.com/) + 1. [Docker](https://docs.docker.com/get-docker/) +1. Open VSCode and install the following [plugins](https://code.visualstudio.com/docs/editor/extension-marketplace): + 1. [VSCode Docker plugin](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker) + 1. [VSCode Remote Containers plugin](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) +1. Open this directory in a container by following [this guide](https://code.visualstudio.com/docs/remote/containers#_quick-start-open-an-existing-folder-in-a-container) +1. Once the folder is open in VSCode, you can build the project by running `Ctrl+Shift+B` to trigger a build, or `Ctrl+p` to open quick open, then type `task build` and hit enter +1. Once the project is built, you can run the project by following [this guide](https://code.visualstudio.com/docs/editor/debugging) + +### Make + +If you are comfortable working from the command line, or want to produce runtime images, the [Makefile](./devcontainer/Makefile) in the [.devcontainer](./devcontainer) directory +can be used to build docker images, run a shell inside the docker images and produce a runtime image. Follow the steps below to setup your environment to use the `Makefile` + +1. Install the following dependencies: + 1. [Make](https://www.gnu.org/software/make/) + 1. [Docker](https://docs.docker.com/get-docker/) + +The `Makefile` exposes the following tasks. They can all be run from the `.devcontainer` directory: +* `make build-shell` - Builds the docker image and starts a shell session in the image allowing the user to develop and build the ROS project using common commands such as `catkin_make` +* `make image` - Builds the runtim image that contains only the required dependencies and the ROS node. The resulting image is names `ros-mscl` +* `make clean` - Cleans up after the above two tasks + ## License ROS-MSCL is released under the MIT License - see the `LICENSE` file in the source distribution. diff --git a/docker/.gitignore b/docker/.gitignore deleted file mode 100644 index 2c022f95..00000000 --- a/docker/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Build artifacts -/.image* -/devel -/build diff --git a/docker/Makefile b/docker/Makefile deleted file mode 100644 index f40f01ba..00000000 --- a/docker/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -.PHONY: all build build-shell clean - -# These variables should be overridden in CI builds to configure this build -version ?= latest -docker ?= docker - -# Just set some directory values to support out of tree builds -makefile_path := $(abspath $(lastword $(MAKEFILE_LIST))) -docker_dir := $(dir $(makefile_path)) -project_dir := $(abspath $(docker_dir)/..) -project_name := $(shell echo $(notdir $(project_dir)) | tr A-Z a-z) -build_dir := $(docker_dir)/build -devel_dir := $(docker_dir)/devel -docker_catkin_root := /home/$(project_name) -docker_catkin_src_dir := $(docker_catkin_root)/src -docker_catkin_build_dir := $(docker_catkin_root)/build -docker_catkin_devel_dir := $(docker_catkin_root)/devel - -# Set up some variables for the docker build -dockerfile := $(docker_dir)/Dockerfile -build_args := --build-arg http_proxy --build-arg https_proxy --build-arg no_proxy -run_args := -e http_proxy -e https_proxy -e no_proxy -run_mounts := -v "$(project_dir)/ros_mscl:$(docker_catkin_src_dir)/ros_mscl" -v "$(project_dir)/mscl_msgs:$(docker_catkin_src_dir)/mscl_msgs" -v "$(build_dir):$(docker_catkin_build_dir)" -v "$(devel_dir):$(docker_catkin_devel_dir)" -image_name := $(project_name):$(version) -image_artifact := .image-$(project_name) - -all: build -build: $(image_artifact) -$(image_artifact): $(dockerfile) - @$(docker) build \ - -t $(image_name) \ - -f $(dockerfile) \ - $(build_args) \ - $(project_dir) - @echo $(image_name) > $@ - -build-shell: $(image_artifact) - @$(docker) run \ - -it \ - --rm \ - -w $(docker_catkin_root) \ - $(run_args) \ - $(run_mounts) \ - $$(cat $<) - -clean: $(image_artifact) - @$(docker) run \ - -it \ - --rm \ - -w $(docker_catkin_root) \ - --entrypoint="/bin/bash" \ - $(run_args) \ - $(run_mounts) \ - $$(cat $<) -c " \ - chown -R $(shell id -u):$(shell id -g) $(docker_catkin_build_dir) $(docker_catkin_install_dir) $(docker_catkin_log_dir) \ - " - @rm -rf "$(build_dir)" "$(devel_dir)" - @rm -f "$(image_artifact)" - @docker ps -a | grep "$(project_name)" | grep $(version) | tr -s " " | cut -d' ' -f1 | xargs docker rm -f || echo "No containers to remove" - @docker images | grep "$(project_name)" | grep $(version) | tr -s " " | cut -d' ' -f1 | xargs docker rmi -f || echo "No images to remove"