diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +build/ diff --git a/.github/actions/build-deb/action.yml b/.github/actions/build-deb/action.yml new file mode 100644 index 0000000000..df07515050 --- /dev/null +++ b/.github/actions/build-deb/action.yml @@ -0,0 +1,115 @@ +name: Build PX4 .deb Package +description: Build PX4 SITL, run cpack, validate the .deb, and upload artifact + +inputs: + target: + description: 'Build target: default or sih' + required: true + artifact-name: + description: Name for the uploaded artifact + required: true + ccache-key-prefix: + description: Prefix for ccache cache keys + default: deb-ccache + ccache-max-size: + description: Maximum ccache size + default: 400M + +runs: + using: composite + steps: + - name: Restore ccache + id: ccache-restore + uses: actions/cache/restore@v4 + with: + path: ~/.ccache + key: ${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}- + ${{ inputs.ccache-key-prefix }}-${{ github.base_ref || 'main' }}- + ${{ inputs.ccache-key-prefix }}- + + - name: Configure ccache + shell: bash + run: | + mkdir -p ~/.ccache + echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf + echo "compression = true" >> ~/.ccache/ccache.conf + echo "compression_level = 6" >> ~/.ccache/ccache.conf + echo "max_size = ${{ inputs.ccache-max-size }}" >> ~/.ccache/ccache.conf + echo "hash_dir = false" >> ~/.ccache/ccache.conf + echo "compiler_check = content" >> ~/.ccache/ccache.conf + ccache -s + ccache -z + + - name: Build PX4 SITL + shell: bash + run: make px4_sitl_${{ inputs.target }} + + - name: ccache stats + if: always() + shell: bash + run: ccache -s + + - name: Save ccache + uses: actions/cache/save@v4 + if: always() + with: + path: ~/.ccache + key: ${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }} + + - name: Build .deb package + shell: bash + run: | + cd build/px4_sitl_${{ inputs.target }} + cpack -G DEB + + - name: Print package info and contents + shell: bash + run: | + cd build/px4_sitl_${{ inputs.target }} + echo "--- Package info ---" + dpkg-deb -I *.deb + echo "--- Package contents ---" + dpkg-deb -c *.deb + + - name: Validate sih package + if: inputs.target == 'sih' + shell: bash + run: | + cd build/px4_sitl_sih + echo "--- Verify NO Gazebo resources ---" + ! dpkg-deb -c px4_*.deb | grep share/gz > /dev/null && echo "PASS: no Gazebo" || { echo "FAIL: Gazebo found"; exit 1; } + echo "--- Install test ---" + dpkg -i px4_*.deb + test -x /opt/px4/bin/px4 || { echo "FAIL: px4 binary not found"; exit 1; } + test -L /usr/bin/px4 || { echo "FAIL: symlink not created"; exit 1; } + test ! -d /opt/px4/share/gz || { echo "FAIL: Gazebo dir should not exist"; exit 1; } + echo "--- Smoke test ---" + /opt/px4/bin/px4 -h + echo "PASS: sih package validation successful" + + - name: Validate gazebo package + if: inputs.target == 'default' + shell: bash + run: | + cd build/px4_sitl_default + echo "--- Verify Gazebo resources in package ---" + dpkg-deb -c px4-gazebo_*.deb | grep share/gz/models > /dev/null || { echo "FAIL: models missing"; exit 1; } + dpkg-deb -c px4-gazebo_*.deb | grep share/gz/worlds > /dev/null || { echo "FAIL: worlds missing"; exit 1; } + echo "--- Install test ---" + dpkg -i px4-gazebo_*.deb + test -x /opt/px4-gazebo/bin/px4 || { echo "FAIL: px4 binary not found"; exit 1; } + test -x /opt/px4-gazebo/bin/px4-gazebo || { echo "FAIL: wrapper not found"; exit 1; } + test -L /usr/bin/px4-gazebo || { echo "FAIL: symlink not created"; exit 1; } + test -d /opt/px4-gazebo/share/gz/models || { echo "FAIL: Gazebo models not installed"; exit 1; } + echo "--- Smoke test ---" + /opt/px4-gazebo/bin/px4 -h + echo "PASS: gazebo package validation successful" + + - name: Upload .deb artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: build/px4_sitl_${{ inputs.target }}/*.deb + if-no-files-found: error diff --git a/.github/workflows/build_deb_package.yml b/.github/workflows/build_deb_package.yml new file mode 100644 index 0000000000..a336e17f77 --- /dev/null +++ b/.github/workflows/build_deb_package.yml @@ -0,0 +1,252 @@ +name: SITL Packages and Containers + +on: + push: + tags: ['v*'] + pull_request: + paths: + - 'cmake/package.cmake' + - 'platforms/posix/CMakeLists.txt' + - 'Tools/packaging/**' + - 'boards/px4/sitl/sih.px4board' + - '.github/workflows/build_deb_package.yml' + - '.github/actions/build-deb/**' + workflow_dispatch: + inputs: + deploy_containers: + description: 'Push container images to registry' + required: false + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write + +env: + RUNS_IN_DOCKER: true + +jobs: + + # --------------------------------------------------------------------------- + # Setup: extract version and determine whether to push containers + # --------------------------------------------------------------------------- + setup: + name: Setup + runs-on: [runs-on,"runner=1cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}",extras=s3-cache,spot=false] + outputs: + px4_version: ${{ steps.px4_version.outputs.px4_version }} + should_push: ${{ steps.push_check.outputs.should_push }} + steps: + - uses: runs-on/action@v2 + - uses: actions/checkout@v4 + with: + fetch-tags: true + submodules: false + fetch-depth: 0 + + - name: Set PX4 version + id: px4_version + run: echo "px4_version=$(git describe --tags --match 'v[0-9]*')" >> $GITHUB_OUTPUT + + - name: Check if we should push containers + id: push_check + run: | + if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]] || \ + [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.deploy_containers }}" == "true" ]]; then + echo "should_push=true" >> $GITHUB_OUTPUT + else + echo "should_push=false" >> $GITHUB_OUTPUT + fi + + # --------------------------------------------------------------------------- + # Build .deb packages (all distros, arches, targets) + # --------------------------------------------------------------------------- + build-deb: + name: "Build .deb (${{ matrix.target }}/${{ matrix.codename }}/${{ matrix.arch }})" + needs: setup + runs-on: [runs-on,"runner=4cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",extras=s3-cache,spot=false] + container: + image: ${{ matrix.container }} + volumes: + - /github/workspace:/github/workspace + strategy: + fail-fast: false + matrix: + include: + # Gazebo builds + - { codename: noble, arch: amd64, runner: x64, container: "ubuntu:24.04", target: default, setup_flags: "" } + - { codename: noble, arch: arm64, runner: arm64, container: "ubuntu:24.04", target: default, setup_flags: "" } + - { codename: jammy, arch: amd64, runner: x64, container: "ubuntu:22.04", target: default, setup_flags: "" } + - { codename: jammy, arch: arm64, runner: arm64, container: "ubuntu:22.04", target: default, setup_flags: "" } + # SIH builds + - { codename: noble, arch: amd64, runner: x64, container: "ubuntu:24.04", target: sih, setup_flags: "--no-sim-tools" } + - { codename: noble, arch: arm64, runner: arm64, container: "ubuntu:24.04", target: sih, setup_flags: "--no-sim-tools" } + - { codename: jammy, arch: amd64, runner: x64, container: "ubuntu:22.04", target: sih, setup_flags: "--no-sim-tools" } + - { codename: jammy, arch: arm64, runner: arm64, container: "ubuntu:22.04", target: sih, setup_flags: "--no-sim-tools" } + steps: + - uses: runs-on/action@v2 + + - name: Fix git in container + run: | + apt-get update && apt-get install -y git + git config --global --add safe.directory $(realpath .) + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Cache apt packages + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives + key: apt-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}-${{ hashFiles('Tools/setup/ubuntu.sh') }} + restore-keys: apt-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}- + + - name: Install dependencies + run: ./Tools/setup/ubuntu.sh --no-nuttx ${{ matrix.setup_flags }} + + - name: Build and package .deb + uses: ./.github/actions/build-deb + with: + target: ${{ matrix.target }} + artifact-name: px4-sitl-debs-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }} + ccache-key-prefix: deb-ccache-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }} + + # --------------------------------------------------------------------------- + # Build Docker images from Noble .debs + # --------------------------------------------------------------------------- + build-docker: + name: "Build Image (${{ matrix.image }}/${{ matrix.arch }})" + needs: [setup, build-deb] + runs-on: [runs-on,"runner=4cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",extras=s3-cache,spot=false] + strategy: + fail-fast: false + matrix: + include: + - { image: sih, target: sih, arch: amd64, runner: x64, platform: "linux/amd64", dockerfile: Dockerfile.sih } + - { image: sih, target: sih, arch: arm64, runner: arm64, platform: "linux/arm64", dockerfile: Dockerfile.sih } + - { image: gazebo, target: default, arch: amd64, runner: x64, platform: "linux/amd64", dockerfile: Dockerfile.gazebo } + - { image: gazebo, target: default, arch: arm64, runner: arm64, platform: "linux/arm64", dockerfile: Dockerfile.gazebo } + steps: + - uses: runs-on/action@v2 + - uses: actions/checkout@v4 + with: + submodules: false + fetch-depth: 1 + + - name: Download Noble .deb artifact + uses: actions/download-artifact@v4 + with: + name: px4-sitl-debs-${{ matrix.target }}-noble-${{ matrix.arch }} + path: docker-context + + - name: Prepare build context + run: | + cp Tools/packaging/px4-entrypoint.sh docker-context/ + ls -lh docker-context/ + + - name: Login to Docker Hub + uses: docker/login-action@v3 + if: needs.setup.outputs.should_push == 'true' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + if: needs.setup.outputs.should_push == 'true' + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: docker-container + platforms: ${{ matrix.platform }} + + - name: Build and push container image + uses: docker/build-push-action@v6 + with: + context: docker-context + file: Tools/packaging/${{ matrix.dockerfile }} + tags: | + px4io/px4-sitl-${{ matrix.image }}:${{ needs.setup.outputs.px4_version }}-${{ matrix.arch }} + ghcr.io/px4/px4-sitl-${{ matrix.image }}:${{ needs.setup.outputs.px4_version }}-${{ matrix.arch }} + platforms: ${{ matrix.platform }} + load: false + push: ${{ needs.setup.outputs.should_push == 'true' }} + provenance: false + cache-from: type=gha,scope=sitl-${{ matrix.image }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=sitl-${{ matrix.image }}-${{ matrix.arch }} + + # --------------------------------------------------------------------------- + # Deploy: create multi-arch manifests and push to registries + # --------------------------------------------------------------------------- + deploy: + name: "Deploy (${{ matrix.image }})" + needs: [setup, build-docker] + if: needs.setup.outputs.should_push == 'true' + runs-on: [runs-on,"runner=1cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}",extras=s3-cache,spot=false] + strategy: + matrix: + image: [sih, gazebo] + steps: + - uses: runs-on/action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify per-arch images exist + run: | + for registry in px4io ghcr.io/px4; do + for arch in amd64 arm64; do + docker manifest inspect ${registry}/px4-sitl-${{ matrix.image }}:${{ needs.setup.outputs.px4_version }}-${arch} \ + || echo "Warning: ${registry}/px4-sitl-${{ matrix.image }}:${{ needs.setup.outputs.px4_version }}-${arch} not found" + done + done + + - name: Create and push multi-arch manifest (Docker Hub) + run: | + VERSION="${{ needs.setup.outputs.px4_version }}" + IMAGE="px4io/px4-sitl-${{ matrix.image }}" + + docker manifest create ${IMAGE}:${VERSION} \ + --amend ${IMAGE}:${VERSION}-arm64 \ + --amend ${IMAGE}:${VERSION}-amd64 + + docker manifest annotate ${IMAGE}:${VERSION} ${IMAGE}:${VERSION}-arm64 --arch arm64 + docker manifest annotate ${IMAGE}:${VERSION} ${IMAGE}:${VERSION}-amd64 --arch amd64 + + docker manifest push ${IMAGE}:${VERSION} + + - name: Create and push multi-arch manifest (GHCR) + run: | + VERSION="${{ needs.setup.outputs.px4_version }}" + IMAGE="ghcr.io/px4/px4-sitl-${{ matrix.image }}" + + docker manifest create ${IMAGE}:${VERSION} \ + --amend ${IMAGE}:${VERSION}-arm64 \ + --amend ${IMAGE}:${VERSION}-amd64 + + docker manifest annotate ${IMAGE}:${VERSION} ${IMAGE}:${VERSION}-arm64 --arch arm64 + docker manifest annotate ${IMAGE}:${VERSION} ${IMAGE}:${VERSION}-amd64 --arch amd64 + + docker manifest push ${IMAGE}:${VERSION} diff --git a/Tools/packaging/Dockerfile.gazebo b/Tools/packaging/Dockerfile.gazebo new file mode 100644 index 0000000000..b003ddb70a --- /dev/null +++ b/Tools/packaging/Dockerfile.gazebo @@ -0,0 +1,84 @@ +# syntax=docker/dockerfile:1 +# PX4 SITL Gazebo Harmonic runtime image +# Runs PX4 SITL with Gazebo Harmonic. Supports X11 forwarding for GUI. +# +# Build: +# make px4_sitl_default && cd build/px4_sitl_default && cpack -G DEB && cd ../.. +# docker build -f Tools/packaging/Dockerfile.gazebo -t px4io/px4-sitl-gazebo:v1.17.0 build/px4_sitl_default/ +# +# Run (headless): +# docker run --rm -it --network host px4io/px4-sitl-gazebo:v1.17.0 +# +# Run (X11 GUI): +# xhost +local:docker +# docker run --rm -it --network host \ +# -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \ +# --gpus all px4io/px4-sitl-gazebo:v1.17.0 + +FROM ubuntu:24.04 AS extract +COPY px4-gazebo_*.deb /tmp/ +RUN apt-get update && apt-get install -y --no-install-recommends binutils \ + && dpkg -x /tmp/px4-gazebo_*.deb /staging \ + && strip /staging/opt/px4-gazebo/bin/px4 \ + && rm -rf /var/lib/apt/lists/* + +FROM ubuntu:24.04 +LABEL maintainer="PX4 Development Team" +LABEL description="PX4 SITL with Gazebo Harmonic simulation" + +ENV DEBIAN_FRONTEND=noninteractive +ENV RUNS_IN_DOCKER=true + +# Install Gazebo Harmonic with buildkit cache mounts for apt +# The --mount=type=cache persists /var/cache/apt and /var/lib/apt across builds +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y --no-install-recommends \ + bc \ + ca-certificates \ + gnupg \ + lsb-release \ + wget \ + && wget -q 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" \ + > /etc/apt/sources.list.d/gazebo-stable.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + gz-harmonic + +# Install PX4 files from .deb +COPY --from=extract /staging/opt/px4-gazebo /opt/px4-gazebo +RUN ln -sf /opt/px4-gazebo/bin/px4-gazebo /usr/bin/px4-gazebo + +# Create the DART physics engine symlink (avoids needing the -dev package) +RUN GZ_PHYSICS_DIR=$(find /usr/lib -maxdepth 3 -type d -name "engine-plugins" -path "*/gz-physics-7/*" 2>/dev/null | head -1) \ + && if [ -n "$GZ_PHYSICS_DIR" ] && [ -d "$GZ_PHYSICS_DIR" ]; then \ + VERSIONED=$(ls "$GZ_PHYSICS_DIR"/libgz-physics*-dartsim-plugin.so.* 2>/dev/null | head -1) \ + && [ -n "$VERSIONED" ] \ + && ln -sf "$(basename "$VERSIONED")" "$GZ_PHYSICS_DIR/libgz-physics-dartsim-plugin.so"; \ + fi + +# Gazebo resource paths +ENV GZ_SIM_RESOURCE_PATH=/opt/px4-gazebo/share/gz/models:/opt/px4-gazebo/share/gz/worlds +ENV GZ_SIM_SYSTEM_PLUGIN_PATH=/opt/px4-gazebo/lib/gz/plugins +ENV GZ_SIM_SERVER_CONFIG_PATH=/opt/px4-gazebo/share/gz/server.config +ENV PX4_GZ_MODELS=/opt/px4-gazebo/share/gz/models +ENV PX4_GZ_WORLDS=/opt/px4-gazebo/share/gz/worlds + +ENV PX4_SIM_MODEL=gz_x500 +ENV HOME=/root + +# MAVLink, MAVSDK, DDS +EXPOSE 14550/udp 14540/udp 8888/udp + +# Platform-adaptive entrypoint: detects Docker Desktop (macOS/Windows) via +# host.docker.internal and configures MAVLink + DDS to target the host. +COPY px4-entrypoint.sh /opt/px4-gazebo/bin/px4-entrypoint.sh +RUN chmod +x /opt/px4-gazebo/bin/px4-entrypoint.sh + +WORKDIR /root + +ENTRYPOINT ["/opt/px4-gazebo/bin/px4-entrypoint.sh"] +CMD [] diff --git a/Tools/packaging/Dockerfile.sih b/Tools/packaging/Dockerfile.sih new file mode 100644 index 0000000000..cfec72dd05 --- /dev/null +++ b/Tools/packaging/Dockerfile.sih @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 +# PX4 SITL SIH runtime image +# Minimal container that runs PX4 with the SIH physics engine (no Gazebo). +# +# Build: +# make px4_sitl_sih && cd build/px4_sitl_sih && cpack -G DEB && cd ../.. +# docker build -f Tools/packaging/Dockerfile.sih -t px4io/px4-sitl-sih:v1.17.0 build/px4_sitl_sih/ +# +# Run (Linux): +# docker run --rm -it --network host px4io/px4-sitl-sih:v1.17.0 +# +# Run (macOS / Windows): +# docker run --rm -it -p 14550:14550/udp -p 14540:14540/udp -p 19410:19410/udp -p 8888:8888/udp px4io/px4-sitl-sih:v1.17.0 + +FROM ubuntu:24.04 AS build +COPY px4_*.deb /tmp/ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y --no-install-recommends binutils \ + && dpkg -x /tmp/px4_*.deb /staging \ + && strip /staging/opt/px4/bin/px4 + +FROM ubuntu:24.04 +LABEL maintainer="PX4 Development Team" +LABEL description="PX4 SITL with SIH physics (no simulator dependencies)" + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends bc + +COPY --from=build /staging/opt/px4 /opt/px4 +RUN ln -sf /opt/px4/bin/px4 /usr/bin/px4 + +# Platform-adaptive entrypoint: detects Docker Desktop (macOS/Windows) via +# host.docker.internal and configures MAVLink + DDS to target the host. +COPY px4-entrypoint.sh /opt/px4/bin/px4-entrypoint.sh +RUN chmod +x /opt/px4/bin/px4-entrypoint.sh + +ENV PX4_SIM_MODEL=sihsim_quadx +ENV HOME=/root + +# MAVLink (QGC, MAVSDK), DDS (ROS 2), jMAVSim/viewer display +EXPOSE 14550/udp 14540/udp 19410/udp 8888/udp + +WORKDIR /root + +ENTRYPOINT ["/opt/px4/bin/px4-entrypoint.sh"] +CMD [] diff --git a/Tools/packaging/postinst b/Tools/packaging/postinst new file mode 100755 index 0000000000..2de9347bcf --- /dev/null +++ b/Tools/packaging/postinst @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +ln -sf /opt/px4-gazebo/bin/px4-gazebo /usr/bin/px4-gazebo +exit 0 diff --git a/Tools/packaging/postrm b/Tools/packaging/postrm new file mode 100755 index 0000000000..1c8227e1c9 --- /dev/null +++ b/Tools/packaging/postrm @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + rm -f /usr/bin/px4-gazebo +fi +exit 0 diff --git a/Tools/packaging/px4-entrypoint.sh b/Tools/packaging/px4-entrypoint.sh new file mode 100644 index 0000000000..7c3ccf9fd2 --- /dev/null +++ b/Tools/packaging/px4-entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Docker entrypoint for PX4 SITL containers. +# +# On Docker Desktop (macOS/Windows), host.docker.internal resolves to the +# host machine. We detect this and configure MAVLink + DDS to send to the +# host IP instead of localhost (which stays inside the container VM). +# +# On Linux with --network host, host.docker.internal does not resolve and +# PX4 defaults work without modification. + +set -e + +# Detect install prefix (SIH uses /opt/px4, Gazebo uses /opt/px4-gazebo) +if [ -d /opt/px4-gazebo ]; then + PX4_PREFIX=/opt/px4-gazebo +else + PX4_PREFIX=/opt/px4 +fi + +if getent hosts host.docker.internal >/dev/null 2>&1; then + DOCKER_HOST_IP=$(getent hosts host.docker.internal | awk '{print $1}') + + # MAVLink: replace default target (127.0.0.1) with the Docker host IP + sed -i "s/mavlink start -x -u/mavlink start -x -t $DOCKER_HOST_IP -u/g" \ + "$PX4_PREFIX/etc/init.d-posix/px4-rc.mavlink" + + # DDS: point uXRCE-DDS client at the host + sed -i "s|uxrce_dds_client start -t udp|uxrce_dds_client start -t udp -h $DOCKER_HOST_IP|" \ + "$PX4_PREFIX/etc/init.d-posix/rcS" +fi + +exec "$PX4_PREFIX/bin/px4" "$@" diff --git a/Tools/packaging/px4-gazebo.sh b/Tools/packaging/px4-gazebo.sh new file mode 100644 index 0000000000..7f26aaafed --- /dev/null +++ b/Tools/packaging/px4-gazebo.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# px4-gazebo: Launch PX4 SITL with Gazebo from the installed .deb package +set -e + +PX4_GAZEBO_DIR="$(cd "$(dirname "$(readlink -f "$0")")/.." && pwd)" +PX4_BINARY="${PX4_GAZEBO_DIR}/bin/px4" + +# Set Gazebo resource paths so gz-sim finds PX4 models, worlds, and plugins. +export PX4_GZ_MODELS="${PX4_GAZEBO_DIR}/share/gz/models" +export PX4_GZ_WORLDS="${PX4_GAZEBO_DIR}/share/gz/worlds" +export PX4_GZ_PLUGINS="${PX4_GAZEBO_DIR}/lib/gz/plugins" +export PX4_GZ_SERVER_CONFIG="${PX4_GAZEBO_DIR}/share/gz/server.config" +export GZ_SIM_RESOURCE_PATH="${GZ_SIM_RESOURCE_PATH}:${PX4_GZ_MODELS}:${PX4_GZ_WORLDS}" +export GZ_SIM_SYSTEM_PLUGIN_PATH="${GZ_SIM_SYSTEM_PLUGIN_PATH}:${PX4_GZ_PLUGINS}" +export GZ_SIM_SERVER_CONFIG_PATH="${PX4_GZ_SERVER_CONFIG}" + +# Gazebo's Physics system searches for "gz-physics-dartsim-plugin" which maps +# to the unversioned libgz-physics-dartsim-plugin.so. The runtime package only +# ships versioned .so files; the unversioned symlink lives in the -dev package. +# Create it if missing so Gazebo finds the DART engine without installing -dev. +GZ_PHYSICS_ENGINE_DIR=$(find /usr/lib -maxdepth 3 -type d -name "engine-plugins" -path "*/gz-physics-7/*" 2>/dev/null | head -1) +if [ -n "$GZ_PHYSICS_ENGINE_DIR" ] && [ -d "$GZ_PHYSICS_ENGINE_DIR" ]; then + UNVERSIONED="$GZ_PHYSICS_ENGINE_DIR/libgz-physics-dartsim-plugin.so" + if [ ! -e "$UNVERSIONED" ]; then + VERSIONED=$(ls "$GZ_PHYSICS_ENGINE_DIR"/libgz-physics*-dartsim-plugin.so.* 2>/dev/null | head -1) + if [ -n "$VERSIONED" ]; then + ln -sf "$(basename "$VERSIONED")" "$UNVERSIONED" 2>/dev/null || true + fi + fi +fi + +exec "${PX4_BINARY}" "$@" diff --git a/Tools/packaging/sih/postinst b/Tools/packaging/sih/postinst new file mode 100755 index 0000000000..23ed21fbdc --- /dev/null +++ b/Tools/packaging/sih/postinst @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +ln -sf /opt/px4/bin/px4 /usr/bin/px4 +exit 0 diff --git a/Tools/packaging/sih/postrm b/Tools/packaging/sih/postrm new file mode 100755 index 0000000000..ecde8ccce7 --- /dev/null +++ b/Tools/packaging/sih/postrm @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + rm -f /usr/bin/px4 +fi +exit 0 diff --git a/Tools/packaging/test_sih_mission.py b/Tools/packaging/test_sih_mission.py new file mode 100644 index 0000000000..7ef70a1abe --- /dev/null +++ b/Tools/packaging/test_sih_mission.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +MAVSDK mission test for PX4 SIH SITL in Docker. + +Takes off to 100m, flies a short 4-waypoint box mission, then lands. +Validates that the SIH Docker container works end-to-end with MAVSDK. + +Prerequisites: + - Docker container running: + docker run --rm --network host px4io/px4-sitl-sih:v1.17.0-alpha1 + - pip install mavsdk + - mavsim-viewer running (optional): + /path/to/mavsim-viewer -n 1 + +Usage: + python3 Tools/packaging/test_sih_mission.py + python3 Tools/packaging/test_sih_mission.py --speed 10 # faster-than-realtime +""" + +import asyncio +import argparse +import sys +import time + +from mavsdk import System +from mavsdk.mission import MissionItem, MissionPlan + + +async def run_mission(speed_factor: int = 1): + drone = System() + print(f"Connecting to drone on udp://:14540 ...") + await drone.connect(system_address="udp://:14540") + + print("Waiting for drone to connect...") + async for state in drone.core.connection_state(): + if state.is_connected: + print(f"Connected (UUID: {state.uuid if hasattr(state, 'uuid') else 'N/A'})") + break + + print("Waiting for global position estimate...") + async for health in drone.telemetry.health(): + if health.is_global_position_ok and health.is_home_position_ok: + print("Global position OK") + break + + # Get home position for reference + async for pos in drone.telemetry.position(): + home_lat = pos.latitude_deg + home_lon = pos.longitude_deg + print(f"Home position: {home_lat:.6f}, {home_lon:.6f}") + break + + # Build a small box mission at 100m AGL + # ~100m offset in each direction + offset = 0.001 # roughly 111m at equator + mission_items = [ + MissionItem( + home_lat + offset, home_lon, + 100, 10, True, float('nan'), float('nan'), + MissionItem.CameraAction.NONE, + float('nan'), float('nan'), float('nan'), + float('nan'), float('nan'), + MissionItem.VehicleAction.NONE, + ), + MissionItem( + home_lat + offset, home_lon + offset, + 100, 10, True, float('nan'), float('nan'), + MissionItem.CameraAction.NONE, + float('nan'), float('nan'), float('nan'), + float('nan'), float('nan'), + MissionItem.VehicleAction.NONE, + ), + MissionItem( + home_lat, home_lon + offset, + 100, 10, True, float('nan'), float('nan'), + MissionItem.CameraAction.NONE, + float('nan'), float('nan'), float('nan'), + float('nan'), float('nan'), + MissionItem.VehicleAction.NONE, + ), + MissionItem( + home_lat, home_lon, + 100, 10, True, float('nan'), float('nan'), + MissionItem.CameraAction.NONE, + float('nan'), float('nan'), float('nan'), + float('nan'), float('nan'), + MissionItem.VehicleAction.NONE, + ), + ] + + mission_plan = MissionPlan(mission_items) + + print(f"Uploading mission ({len(mission_items)} waypoints, 100m AGL)...") + await drone.mission.upload_mission(mission_plan) + print("Mission uploaded") + + print("Arming...") + await drone.action.arm() + print("Armed") + + t0 = time.time() + print("Starting mission...") + await drone.mission.start_mission() + + # Monitor mission progress + async for progress in drone.mission.mission_progress(): + elapsed = time.time() - t0 + print(f" [{elapsed:6.1f}s] Waypoint {progress.current}/{progress.total}") + if progress.current == progress.total: + print(f"Mission complete in {elapsed:.1f}s (speed factor: {speed_factor}x)") + break + + print("Returning to launch...") + await drone.action.return_to_launch() + + # Wait for landing + async for in_air in drone.telemetry.in_air(): + if not in_air: + print("Landed") + break + + print("Disarming...") + await drone.action.disarm() + print("Test PASSED") + + +def main(): + parser = argparse.ArgumentParser(description="PX4 SIH Docker mission test") + parser.add_argument("--speed", type=int, default=1, + help="PX4_SIM_SPEED_FACTOR (must match container)") + args = parser.parse_args() + + try: + asyncio.run(run_mission(args.speed)) + except KeyboardInterrupt: + print("\nInterrupted") + sys.exit(1) + except Exception as e: + print(f"Test FAILED: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/cmake/package.cmake b/cmake/package.cmake index 948523960b..a8b7841763 100644 --- a/cmake/package.cmake +++ b/cmake/package.cmake @@ -33,32 +33,24 @@ # packaging -set(CPACK_PACKAGE_NAME ${PROJECT_NAME}-${PX4_CONFIG}) - set(CPACK_PACKAGE_VENDOR "px4") +set(CPACK_PACKAGE_CONTACT "daniel@agar.ca") +set(CPACK_RESOURCE_FILE_LICENSE "${PX4_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${PX4_SOURCE_DIR}/README.md") + +set(CPACK_SOURCE_GENERATOR "ZIP;TBZ2") + +# Debian version: convert git describe to Debian-compliant format +# v1.17.0-beta1 -> 1.17.0~beta1, v1.17.0 -> 1.17.0 +string(REGEX REPLACE "^v" "" DEB_VERSION "${PX4_GIT_TAG}") +# Replace first hyphen with tilde for pre-release (Debian sorts ~ before anything) +string(REGEX REPLACE "^([0-9]+\\.[0-9]+\\.[0-9]+)-([a-zA-Z])" "\\1~\\2" DEB_VERSION "${DEB_VERSION}") +# Strip any trailing commit info (e.g. -42-gabcdef) +string(REGEX REPLACE "-[0-9]+-g[0-9a-f]+$" "" DEB_VERSION "${DEB_VERSION}") set(CPACK_PACKAGE_VERSION_MAJOR ${PX4_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PX4_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PX4_VERSION_PATCH}) -#set(CPACK_PACKAGE_VERSION ${PX4_GIT_TAG}) - -set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PX4_CONFIG}-${PX4_GIT_TAG}") -set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PX4_CONFIG}-${PX4_GIT_TAG}-src") - -set(CPACK_PACKAGE_CONTACT "daniel@agar.ca") - -set(CPACK_RESOURCE_FILE_LICENSE "${PX4_SOURCE_DIR}/LICENSE") -set(CPACK_RESOURCE_FILE_README "${PX4_SOURCE_DIR}/README.md") - -set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) -# without this you won't be able to pack only specified component -set(CPACK_DEB_COMPONENT_INSTALL YES) - -#set(CPACK_STRIP_FILES YES) - -set(CPACK_SOURCE_GENERATOR "ZIP;TBZ2") -set(CPACK_PACKAGING_INSTALL_PREFIX "") -set(CPACK_SET_DESTDIR "OFF") if("${CMAKE_SYSTEM}" MATCHES "Linux") set(CPACK_GENERATOR "TBZ2") @@ -67,30 +59,61 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") if(EXISTS ${DPKG_PROGRAM}) list(APPEND CPACK_GENERATOR "DEB") - set(CPACK_SET_DESTDIR true) - set(CPACK_PACKAGING_INSTALL_PREFIX "/tmp") + execute_process(COMMAND ${DPKG_PROGRAM} --print-architecture + OUTPUT_VARIABLE DEB_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${DPKG_PROGRAM} --print-architecture OUTPUT_VARIABLE DEB_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE) - message("Architecture: " ${DEB_ARCHITECTURE}) - set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}_${DEB_ARCHITECTURE}") + # Detect Ubuntu/Debian codename for version suffix + find_program(LSB_RELEASE lsb_release) + if(EXISTS ${LSB_RELEASE}) + execute_process(COMMAND ${LSB_RELEASE} -cs + OUTPUT_VARIABLE DEB_CODENAME OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + set(DEB_CODENAME "unknown") + endif() - set(CPACK_INSTALL_PREFIX @DEB_INSTALL_PREFIX@) - message ("==> CPACK_INSTALL_PREFIX = " ${CPACK_INSTALL_PREFIX}) + # Override CPACK_PACKAGE_VERSION with full Debian version. + # CPack DEB ignores CPACK_PACKAGE_VERSION_MAJOR/MINOR/PATCH + # when CPACK_PACKAGE_VERSION is set, so we must replace them. + unset(CPACK_PACKAGE_VERSION_MAJOR) + unset(CPACK_PACKAGE_VERSION_MINOR) + unset(CPACK_PACKAGE_VERSION_PATCH) + set(CPACK_PACKAGE_VERSION "${DEB_VERSION}-${DEB_CODENAME}") - ################################################################################ + # Label-aware package metadata + if(PX4_BOARD_LABEL STREQUAL "sih") + set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/px4") + set(CPACK_DEBIAN_PACKAGE_NAME "px4") + set(CPACK_DEBIAN_FILE_NAME "px4_${DEB_VERSION}-${DEB_CODENAME}_${DEB_ARCHITECTURE}.deb") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libstdc++6") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "PX4 SITL autopilot with SIH physics (no Gazebo)") + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${PX4_SOURCE_DIR}/Tools/packaging/sih/postinst;${PX4_SOURCE_DIR}/Tools/packaging/sih/postrm") + else() + set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/px4-gazebo") + set(CPACK_DEBIAN_PACKAGE_NAME "px4-gazebo") + set(CPACK_DEBIAN_FILE_NAME "px4-gazebo_${DEB_VERSION}-${DEB_CODENAME}_${DEB_ARCHITECTURE}.deb") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libstdc++6, gz-sim8-cli, libgz-sim8-plugins, libgz-physics7-dartsim, gz-tools2") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "PX4 SITL autopilot with Gazebo Harmonic simulation resources") + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${PX4_SOURCE_DIR}/Tools/packaging/postinst;${PX4_SOURCE_DIR}/Tools/packaging/postrm") + endif() - set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Daniel Agar <${CPACK_PACKAGE_CONTACT}>") - set(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) - set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + # Bake the install prefix into the px4 binary so it can locate its ROMFS + # (etc/) without a wrapper script or command-line argument. + if(TARGET px4) + target_compile_definitions(px4 PRIVATE PX4_INSTALL_PREFIX="${CPACK_PACKAGING_INSTALL_PREFIX}") + endif() - set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "PX4 autopilot") - set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_SECTION "misc") - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEB_ARCHITECTURE}) + set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Daniel Agar <${CPACK_PACKAGE_CONTACT}>") - # autogenerate dependency information set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_COMPRESSION_TYPE xz) + set(CPACK_DEBIAN_ARCHITECTURE ${DEB_ARCHITECTURE}) + + message(STATUS "PX4 SITL .deb version: ${DEB_VERSION}-${DEB_CODENAME} (${DEB_ARCHITECTURE})") endif() else() diff --git a/platforms/posix/CMakeLists.txt b/platforms/posix/CMakeLists.txt index 30beec4609..594fa95c99 100644 --- a/platforms/posix/CMakeLists.txt +++ b/platforms/posix/CMakeLists.txt @@ -23,6 +23,9 @@ px4_posix_generate_alias( add_definitions(-DPX4_SOURCE_DIR="${PX4_SOURCE_DIR}" -DPX4_BINARY_DIR="${PX4_BINARY_DIR}") +# PX4_INSTALL_PREFIX is set by cmake/package.cmake after this file is processed. +# It is passed via target_compile_definitions on the px4 target from package.cmake. + add_executable(px4 src/px4/common/main.cpp apps.cpp @@ -80,10 +83,16 @@ target_link_libraries(px4 PRIVATE uORB) # install # +# Detect dpkg early so install rules can branch on it. +# find_program caches the result, so the later call in package.cmake is a no-op. +find_program(DPKG_PROGRAM dpkg) + # Generic install rules (skipped when board provides its own install.cmake) if(NOT EXISTS "${PX4_BOARD_DIR}/cmake/install.cmake") -# px4 dirs +# Legacy top-level install (posix-configs, test data, etc into px4/ prefix). +# Skip when building .deb packages which use a clean /opt/px4-sitl{,-sih}/ layout. +if(NOT DPKG_PROGRAM) install( DIRECTORY ${PROJECT_SOURCE_DIR}/posix-configs @@ -94,6 +103,7 @@ install( ${PROJECT_NAME} USE_SOURCE_PERMISSIONS ) +endif() endif() # NOT board install.cmake @@ -163,6 +173,11 @@ elseif("${PX4_BOARD}" MATCHES "sitl") # install + # Legacy install rules (used by ROS launch, existing CPack tarball workflow). + # Skip when building .deb packages: the .deb rules below provide a clean + # /opt/px4-sitl{,-sih}/ layout and these would duplicate/bloat the package. + if(NOT DPKG_PROGRAM) + # px4 dirs install( DIRECTORY @@ -227,4 +242,82 @@ elseif("${PX4_BOARD}" MATCHES "sitl") OPTIONAL ) + endif() # NOT DPKG_PROGRAM + + #################################################################### + # Install rules for .deb package (only when dpkg is available) + #################################################################### + if(DPKG_PROGRAM AND NOT CMAKE_C_COMPILER MATCHES "emcc$") + + # px4 binary + install(TARGETS px4 + RUNTIME DESTINATION bin + ) + + # module symlinks and alias script + install( + DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ + DESTINATION bin + USE_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "px4-*" + PATTERN "px4-alias.sh" + ) + + # ROMFS (init scripts, mixers, etc.) + install( + DIRECTORY ${PX4_BINARY_DIR}/etc + DESTINATION . + USE_SOURCE_PERMISSIONS + ) + + if(CONFIG_MODULES_SIMULATION_GZ_BRIDGE) + # Gazebo wrapper script (sets GZ_SIM_* env vars pointing at installed resources) + install( + PROGRAMS ${PROJECT_SOURCE_DIR}/Tools/packaging/px4-gazebo.sh + DESTINATION bin + RENAME px4-gazebo + ) + endif() + + if(CONFIG_MODULES_SIMULATION_GZ_BRIDGE) + # Gazebo models + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/Tools/simulation/gz/models + DESTINATION share/gz + OPTIONAL + ) + + # Gazebo worlds + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/Tools/simulation/gz/worlds + DESTINATION share/gz + OPTIONAL + ) + + # Gazebo server config + install( + FILES ${PROJECT_SOURCE_DIR}/Tools/simulation/gz/server.config + DESTINATION share/gz + OPTIONAL + ) + + # Gazebo plugins (built .so files) + install( + DIRECTORY ${CMAKE_BINARY_DIR}/src/modules/simulation/gz_plugins/ + DESTINATION lib/gz/plugins + FILES_MATCHING PATTERN "*.so" + PATTERN "CMakeFiles" EXCLUDE + ) + + # OpticalFlow external library (runtime dep of OpticalFlowSystem plugin) + install( + FILES ${CMAKE_BINARY_DIR}/OpticalFlow/install/lib/libOpticalFlow.so + DESTINATION lib/gz/plugins + OPTIONAL + ) + endif() + + endif() # DPKG_PROGRAM AND NOT emcc + endif() diff --git a/platforms/posix/src/px4/common/main.cpp b/platforms/posix/src/px4/common/main.cpp index 87af3b7344..e34452d5b2 100644 --- a/platforms/posix/src/px4/common/main.cpp +++ b/platforms/posix/src/px4/common/main.cpp @@ -259,6 +259,32 @@ int main(int argc, char **argv) PX4_INFO("instance: %i", instance); } +#if defined(PX4_INSTALL_PREFIX) + + // When installed as a .deb package, default to the baked-in install prefix. + // Working directory defaults to XDG_DATA_HOME/px4/rootfs/. + if (commands_file.empty() && data_path.empty() && working_directory.empty() + && dir_exists(PX4_INSTALL_PREFIX"/etc") + ) { + data_path = PX4_INSTALL_PREFIX"/etc"; + + const char *xdg_data_home = getenv("XDG_DATA_HOME"); + std::string state_base; + + if (xdg_data_home) { + state_base = xdg_data_home; + + } else { + const char *home = getenv("HOME"); + state_base = std::string(home ? home : "/tmp") + "/.local/share"; + } + + working_directory = state_base + "/px4/rootfs"; + working_directory_default = true; + } + +#endif // PX4_INSTALL_PREFIX + #if defined(PX4_BINARY_DIR) // data_path & working_directory: if no commands specified or in current working directory), @@ -745,13 +771,34 @@ std::string pwd() return (getcwd(temp, PATH_MAX) ? std::string(temp) : std::string("")); } +static int mkdir_p(const std::string &path) +{ + std::string tmp = path; + + for (size_t i = 1; i < tmp.size(); ++i) { + if (tmp[i] == '/') { + tmp[i] = '\0'; + + if (mkdir(tmp.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0 && errno != EEXIST) { + return -1; + } + + tmp[i] = '/'; + } + } + + if (mkdir(tmp.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0 && errno != EEXIST) { + return -1; + } + + return 0; +} + int change_directory(const std::string &directory) { - // create directory + // create directory (including intermediate components) if (!dir_exists(directory)) { - int ret = mkdir(directory.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); - - if (ret == -1) { + if (mkdir_p(directory) != 0) { PX4_ERR("Error creating directory: %s (%s)", directory.c_str(), strerror(errno)); return -1; } diff --git a/src/modules/simulation/gz_plugins/optical_flow/CMakeLists.txt b/src/modules/simulation/gz_plugins/optical_flow/CMakeLists.txt index 62d9c7c959..bdd66e604c 100644 --- a/src/modules/simulation/gz_plugins/optical_flow/CMakeLists.txt +++ b/src/modules/simulation/gz_plugins/optical_flow/CMakeLists.txt @@ -64,6 +64,13 @@ target_include_directories(${PROJECT_NAME} add_dependencies(${PROJECT_NAME} OpticalFlow) +# Add $ORIGIN to RPATH so the plugin finds libOpticalFlow.so in its own +# directory when installed (the packaging copies both .so files together). +set_target_properties(${PROJECT_NAME} PROPERTIES + BUILD_RPATH "$ORIGIN;${OPTICAL_FLOW_INSTALL_PREFIX}/lib" + INSTALL_RPATH "$ORIGIN" +) + if (NOT CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib/px4_gz_plugins) endif()