diff --git a/.docker/Dockerfile b/.docker/Dockerfile index b309cdd2..0e2c553c 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -18,6 +18,7 @@ RUN apt-get -q update \ clang-tools \ python3-pip \ python3-dev \ + python3-venv \ lsb-release \ wget \ gnupg \ @@ -47,14 +48,14 @@ RUN apt-get -q update \ # FROM ci AS robot -# Configure a new non-root user -ARG USERNAME=blue +# +# Ubuntu 24.04 "Noble", which is used as the base image for +# jazzy and rolling images, now includes a user "ubuntu" at UID 1000 +ARG USERNAME=ubuntu ARG USER_UID=1000 ARG USER_GID=$USER_UID -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ +RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME \ && usermod -a -G dialout $USERNAME \ && echo "source /usr/share/bash-completion/completions/git" >> /home/$USERNAME/.bashrc @@ -65,6 +66,16 @@ ENV DEBIAN_FRONTEND=noninteractive USER $USERNAME ENV USER=$USERNAME +# Python in Ubuntu is now marked as a "Externally managed environment", +# Per best practice, create a venv for local python packages +# +# These two ENVs effectively "activate" the venv for subsequent calls to +# python/pip in the Dockerfile +WORKDIR /home/$USERNAME +ENV VIRTUAL_ENV=/home/$USERNAME/.venv/blue +RUN python3 -m venv --system-site-packages --symlinks $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + # Install MAVROS dependencies WORKDIR /home/$USERNAME RUN wget https://raw.githubusercontent.com/mavlink/mavros/ros2/mavros/scripts/install_geographiclib_datasets.sh \ @@ -112,43 +123,53 @@ RUN . "/opt/ros/${ROS_DISTRO}/setup.sh" \ && colcon build RUN echo "source ${USER_WORKSPACE}/install/setup.bash" >> /home/$USERNAME/.bashrc \ - && echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /home/$USERNAME/.bashrc + && echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /home/$USERNAME/.bashrc \ + && echo "\n# Ensure colcon is run in the venv\nalias colcon='python3 -m colcon'" >> /home/$USERNAME/.bashrc FROM robot AS desktop ENV DEBIAN_FRONTEND=noninteractive -ENV GZ_VERSION=garden - -# Install Gazebo Garden: https://gazebosim.org/docs/garden/install_ubuntu -RUN sudo wget https://packages.osrfoundation.org/gazebo.gpg -O /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null \ - && sudo apt-get -q update \ - && sudo apt-get -y --quiet --no-install-recommends install \ - gz-garden \ - && sudo apt-get autoremove -y \ - && sudo apt-get clean -y \ - && sudo rm -rf /var/lib/apt/lists/* +ENV GZ_VERSION=harmonic + +# Install Gazebo Harmonic: https://gazebosim.org/docs/harmonic/install_ubuntu +USER root +# Install custom rosdep list +ADD --chown=root:root --chmod=0644 https://raw.githubusercontent.com/osrf/osrf-rosdep/master/gz/00-gazebo.list /etc/ros/rosdep/sources.list.d/00-gazebo.list +RUN wget https://packages.osrfoundation.org/gazebo.gpg -O /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null \ + && apt-get -q update \ + && apt-get -y --quiet --no-install-recommends install \ + gz-${GZ_VERSION} \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* # Install ArduPilot and ardupilot_gazebo dependencies -RUN sudo apt-get -q update \ - && sudo apt-get -q -y upgrade \ - && sudo apt-get -q install --no-install-recommends -y \ +RUN apt-get -q update \ + && apt-get -q -y upgrade \ + && apt-get -q install --no-install-recommends -y \ + python3-pexpect \ python3-wxgtk4.0 \ + python3-future \ rapidjson-dev \ xterm \ - libgz-sim7-dev \ rapidjson-dev \ libopencv-dev \ - && sudo apt-get autoremove -y \ - && sudo apt-get clean -y \ - && sudo rm -rf /var/lib/apt/lists/* + cppzmq-dev \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* +USER $USERNAME # Clone ArduSub # ArduSub is installed for simulation purposes ONLY # When deployed onto hardware, the native installation of ArduSub # (on the FCU) will be used. WORKDIR /home/$USERNAME -RUN git clone https://github.com/ArduPilot/ardupilot.git --recurse-submodules +# Really should do version pinning but Sub-4.5 is waaaay behind master +# (e.g. it doesn't know about "noble" yet) +ARG ARDUPILOT_RELEASE=master +RUN git clone -b ${ARDUPILOT_RELEASE} https://github.com/ArduPilot/ardupilot.git --recurse-submodules # Install ArduSub dependencies WORKDIR /home/$USERNAME/ardupilot diff --git a/.docker/compose/nouveau-desktop.yaml b/.docker/compose/nouveau-desktop.yaml index f18588de..0f62bd89 100644 --- a/.docker/compose/nouveau-desktop.yaml +++ b/.docker/compose/nouveau-desktop.yaml @@ -1,7 +1,7 @@ version: "3" services: blue: - image: ghcr.io/robotic-decision-making-lab/blue:rolling-desktop + image: ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop environment: - DISPLAY=${DISPLAY} - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} diff --git a/.docker/compose/nvidia-desktop.yaml b/.docker/compose/nvidia-desktop.yaml index da240d51..b7046b56 100644 --- a/.docker/compose/nvidia-desktop.yaml +++ b/.docker/compose/nvidia-desktop.yaml @@ -1,7 +1,7 @@ version: "3" services: blue: - image: ghcr.io/robotic-decision-making-lab/blue:rolling-desktop-nvidia + image: ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop-nvidia environment: - DISPLAY=${DISPLAY} - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} diff --git a/.docker/compose/robot.yaml b/.docker/compose/robot.yaml index faf1be02..f3c79ff9 100644 --- a/.docker/compose/robot.yaml +++ b/.docker/compose/robot.yaml @@ -1,10 +1,6 @@ services: blue: - image: ghcr.io/robotic-decision-making-lab/blue:rolling-robot - build: - dockerfile: .docker/Dockerfile - target: robot - context: ../../ + image: ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-robot network_mode: host privileged: true cap_add: diff --git a/.docker/docker-bake.hcl b/.docker/docker-bake.hcl new file mode 100644 index 00000000..8aad319b --- /dev/null +++ b/.docker/docker-bake.hcl @@ -0,0 +1,58 @@ +# +# +# + + +variable "BLUE_ROS_DISTRO" { default = "rolling" } + +variable "BLUE_GITHUB_REPO" { default = "robotic-decision-making-lab/blue" } + +group "default" { + targets = ["ci", "robot", "desktop", "desktop-nvidia"] +} + +target "ci" { + dockerfile = ".docker/Dockerfile" + target = "ci" + context = ".." + args = { + ROS_DISTRO = "${BLUE_ROS_DISTRO}" + } + tags = [ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-ci" + ] + cache_from =[ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-ci-cache", + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-robot-cache", + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop-cache", + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop-nvidia-cache" + ] + cache_to = [ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-ci-cache" + ] + platforms = ["linux/amd64", "linux/arm64"] +} + +target "robot" { + inherits = [ "ci" ] + target = "robot" + cache_to = [ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-robot-cache" + ] +} + +target "desktop" { + inherits = [ "ci" ] + target = "desktop" + cache_to = [ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop-cache" + ] +} + +target "desktop-nvidia" { + inherits = [ "ci" ] + target = "desktop-nvidia" + cache_to = [ + "ghcr.io/${BLUE_GITHUB_REPO}:${BLUE_ROS_DISTRO}-desktop-nvidia-cache" + ] +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e8ff75ad..ba678edc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,7 +37,7 @@ jobs: - name: Run ROS Industrial CI uses: ros-industrial/industrial_ci@master env: - DOCKER_IMAGE: ghcr.io/robotic-decision-making-lab/blue:${{ matrix.env.IMAGE }} + DOCKER_IMAGE: ghcr.io/${{ github.repository }}:${{ matrix.env.IMAGE }} CXXFLAGS: >- -Wall -Wextra -Wpedantic -Wwrite-strings -Wunreachable-code -Wpointer-arith -Wredundant-decls CC: ${{ env.CLANG_TIDY && 'clang' }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index b809dc5a..335826e2 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -1,8 +1,8 @@ name: Docker on: - schedule: - - cron: "0 17 * * 6" + # schedule: + # - cron: "0 17 * * 6" push: branches: - main @@ -18,7 +18,7 @@ env: PUSH: ${{ (github.event_name != 'pull_request') && (github.repository == 'apl-ocean-engineering/blue') }} jobs: - ci: + docker_build: strategy: fail-fast: false matrix: @@ -27,164 +27,204 @@ jobs: permissions: packages: write contents: read + env: + BLUE_ROS_DISTRO: ${{ matrix.ROS_DISTRO }} + BLUE_GITHUB_REPO: ${{ github.repository }} steps: - - name: Checkout repository + - + name: Checkout repository uses: actions/checkout@v4 - - name: Log into registry - if: env.PUSH == 'true' - uses: docker/login-action@v3.3.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - + # Add support for more platforms with QEMU (optional) + # https://github.com/docker/setup-qemu-action + name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Extract Docker metadata - if: env.PUSH == 'true' - id: meta - uses: docker/metadata-action@v5.5.1 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} - - - name: Build and push Docker image - uses: docker/build-push-action@v6.5.0 - with: - context: . - file: .docker/Dockerfile - build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} - target: ${{ github.job }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - push: ${{ env.PUSH }} - - robot: - strategy: - fail-fast: false - matrix: - ROS_DISTRO: [rolling] - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3.2.0 - - - name: Set up Docker Buildx + - + name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log into registry - if: env.PUSH == 'true' - uses: docker/login-action@v3.3.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract Docker metadata - if: env.PUSH == 'true' - id: meta - uses: docker/metadata-action@v5.5.1 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} - - - name: Build and push Docker image - uses: docker/build-push-action@v6.5.0 - with: - context: . - file: .docker/Dockerfile - build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} - target: ${{ github.job }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - push: ${{ env.PUSH }} - platforms: linux/amd64,linux/arm64 - - desktop: - strategy: - fail-fast: false - matrix: - ROS_DISTRO: [rolling] - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log into registry + - if: env.PUSH == 'true' + name: Log into registry uses: docker/login-action@v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract Docker metadata - if: env.PUSH == 'true' - id: meta - uses: docker/metadata-action@v5.5.1 + # - + # name: Extract Docker metadata + # if: env.PUSH == 'true' + # id: meta + # uses: docker/metadata-action@v5.5.1 + # with: + # images: ghcr.io/${{ github.repository }} + # tags: | + # type=raw,value=${{ matrix.ROS_DISTRO }}-${{ matrix.stage }} + + - if: github.event_name == 'push' + name: Build and push (non PR) + uses: docker/bake-action@v5.5.0 with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} - - - name: Build and push Docker image - uses: docker/build-push-action@v6.5.0 - with: - context: . - file: .docker/Dockerfile - build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} - target: ${{ github.job }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + workdir: .docker push: ${{ env.PUSH }} - - desktop-nvidia: - strategy: - fail-fast: false - matrix: - ROS_DISTRO: [rolling] - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log into registry - if: env.PUSH == 'true' - uses: docker/login-action@v3.3.0 + set: | + *.platform=linux/amd64 + *.cache-from=type=gha,scope=ci + *.cache-from=type=gha,scope=robot + *.cache-from=type=gha,scope=desktop + *.cache-from=type=gha,scope=desktop-nvidia + ci.cache-to=type=gha,mode=max,scope=ci + robot.cache-to=type=gha,mode=max,scope=robot + desktop.cache-to=type=gha,mode=max,scope=desktop + desktop-nvidia.cache-to=type=gha,mode=max,scope=desktop-nvidia + + - if: github.event_name == 'pull_request' + name: Build and push (PR) + uses: docker/bake-action@v5.5.0 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract Docker metadata - if: env.PUSH == 'true' - id: meta - uses: docker/metadata-action@v5.5.1 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} - - - name: Build and push Docker image - uses: docker/build-push-action@v6.5.0 - with: - context: . - file: .docker/Dockerfile - build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} - target: ${{ github.job }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - push: ${{ env.PUSH }} + workdir: .docker + targets: | + ${{ matrix.stage }} + set: | + *.platform=linux/amd64 + *.cache-from=type=gha,scope=ci + *.cache-from=type=gha,scope=robot + *.cache-from=type=gha,scope=desktop + *.cache-from=type=gha,scope=desktop-nvidia + *.cache-to= + + + + + # robot: + # strategy: + # fail-fast: false + # matrix: + # ROS_DISTRO: [rolling] + # runs-on: ubuntu-latest + # permissions: + # packages: write + # contents: read + # steps: + # - name: Checkout repository + # uses: actions/checkout@v4 + + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v3.2.0 + + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v3 + + # - name: Log into registry + # if: env.PUSH == 'true' + # uses: docker/login-action@v3.3.0 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Extract Docker metadata + # if: env.PUSH == 'true' + # id: meta + # uses: docker/metadata-action@v5.5.1 + # with: + # images: ghcr.io/${{ github.repository }} + # tags: | + # type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} + + # - name: Build and push Docker image + # uses: docker/build-push-action@v6.5.0 + # with: + # context: . + # file: .docker/Dockerfile + # build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} + # target: ${{ github.job }} + # tags: ${{ steps.meta.outputs.tags }} + # labels: ${{ steps.meta.outputs.labels }} + # push: ${{ env.PUSH }} + # platforms: linux/amd64,linux/arm64 + + # desktop: + # strategy: + # fail-fast: false + # matrix: + # ROS_DISTRO: [rolling] + # runs-on: ubuntu-latest + # permissions: + # packages: write + # contents: read + # steps: + # - name: Checkout repository + # uses: actions/checkout@v4 + + # - name: Log into registry + # if: env.PUSH == 'true' + # uses: docker/login-action@v3.3.0 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Extract Docker metadata + # if: env.PUSH == 'true' + # id: meta + # uses: docker/metadata-action@v5.5.1 + # with: + # images: ghcr.io/${{ github.repository }} + # tags: | + # type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} + + # - name: Build and push Docker image + # uses: docker/build-push-action@v6.5.0 + # with: + # context: . + # file: .docker/Dockerfile + # build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} + # target: ${{ github.job }} + # tags: ${{ steps.meta.outputs.tags }} + # labels: ${{ steps.meta.outputs.labels }} + # push: ${{ env.PUSH }} + + # desktop-nvidia: + # strategy: + # fail-fast: false + # matrix: + # ROS_DISTRO: [rolling] + # runs-on: ubuntu-latest + # permissions: + # packages: write + # contents: read + # steps: + # - name: Checkout repository + # uses: actions/checkout@v4 + + # - name: Log into registry + # if: env.PUSH == 'true' + # uses: docker/login-action@v3.3.0 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Extract Docker metadata + # if: env.PUSH == 'true' + # id: meta + # uses: docker/metadata-action@v5.5.1 + # with: + # images: ghcr.io/${{ github.repository }} + # tags: | + # type=raw,value=${{ matrix.ROS_DISTRO }}-${{ github.job }} + + # - name: Build and push Docker image + # uses: docker/build-push-action@v6.5.0 + # with: + # context: . + # file: .docker/Dockerfile + # build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} + # target: ${{ github.job }} + # tags: ${{ steps.meta.outputs.tags }} + # labels: ${{ steps.meta.outputs.labels }} + # push: ${{ env.PUSH }} diff --git a/.gitignore b/.gitignore index eb28dd58..3d806e06 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ mav.parm mav.tlog mav.tlog.raw logs/ + +.docker/docker-bake.override.hcl