I had an interesting discussion with @peci1 over on the exiv2 github and we decided to move it here for some more opinions/recommendations because it illustrates a larger issue with the standard ROS 2 build tooling when a company wants to build a larger software stack with colcon and rosdep.
First, some background
- I work at a company that uses ROS 2 with C++
- We use colcon to build the software we maintain
- We use package.xml files for colcon to know build dependencies
- We have a few forks of ROS repos (rosidl, for example) with changes that are not merged upstream
- We also rely a lot on system packages from outside ROS
- These system packages have known bugs or missing ABI compatible features on Ubuntu 22ās apt version, so we want to use newer versions. exiv2 is one such example, but there about 15 packages in such a state. I usually upstream our patches, but in some cases the repos are also unmaintained, so we carry these patches internally
- We do NOT worry about breaking compatibility with what the ROS distribution or Ubuntu distribution because we have a product to deliver and no time to worry about ABI outside our closed source applications
- We donāt care about internal ABI, we donāt use SemVer on internal package, and our internal messages arenāt stable either. We use VCS to freeze the state of our source code with
vcs export --exact
when we do a release.
Constraints
- We want to avoid duplicating building the same library multiple times with tools like FetchContent because our CI build times get too slow
- We donāt have a large enough team or funding to switch to conan, switch to bazel, switch to yocto, and so far, we havenāt set up the ROS build farm interally thatās also intimidating amount of work.
- We want to do our best to only use one version of a library in the project (IE, donāt link against 4 different versions of exiv2 in four different packages). Due to the size of our project, this is a reasonable restriction
- Maintainers of non-ROS packages like exiv2 are not interested in a package.xml in their repo
- The ubuntu package maintainers appear to be understaffed and underfunded so long-standing bugs still exist that affect us, largely in their CMake code that show up when you cross compile. Many packages in Ubuntu distros are built with autotools even though the packages support CMake for years. Iām not ready to sign up as an Ubuntu package maintainer , so this problem is probably not going away any time soon
How we currently solve the problem:
- For packages like exiv2, we have a repo that contains shell scripts called ābuild_exiv2.shā and āinstall_exiv2.shā.
Originally, these were CMake builds and Cmake installs, but we recently started uploading debians and installing debians - In docker for our dev and deploy images, we use a
RUN install_exiv2.sh
which takes advantage of the docker build cache. This happens AFTER rosdep. - We skip rosdep keys on packages like
exiv2
because we want to avoid installing the same library twice - this has bitten us multiple times. If you try compiling some of our ROS package outside of our development environment that installed exiv2, it will fail to compile. You can get around this by runninginstall_exiv2.sh
. - We use ccache to speed up builds. We donāt really care how large our source workspace gets because we can pin dependencies and ccache makes it really fast for already built packages.
- We use vcs to keep track of all ROS-package dependencies
- For meta-repos, we use
yq
to do a merge of thevcs
files, this ensures we only build one version of a repo (This has some problems if two packages pin different versions, so we manually keep these in sync) - We cross compile all our source code with a sysroot, and carry many internal patches for libraries that do not cross compile well, as well as workarounds in our cross compile toolchain that rewrite absolute paths to relative paths when we donāt have time to fix upstream CMake code
- The upstream fixes can be quite involved. Hereās a recent example I did for LibKML
What we would like:
- All packages we build from source for to be in the ROS workspace
- Rosdep doesnāt need a list of keys to skip that we propograte through all our repos
- Reduce the number of custom lines of code maintaining custom packaging
- Spend as little time on devops as possible long term
- Not make extra work every time we want to break internal ABI
master
branch of everything we build to work with a cross compile toolchain, and the eventual upgrade to Ubuntu 24/26/28 to have debians that work in sysroot out of the box.
A simple use case illustrating the difficulty:
- Create a ROS workspace with a
src
directory - Clone exiv2 in the workspace
git clone https://github.com/Exiv2/exiv2.git
- Create another package that depends on it:
ros2 pkg create exiv2_consumer --build-type ament_cmake --dependencies exiv2
- Run rosdep as normal
rosdep install --from-paths src --ignore-src
- Oh crap, it wants to install Ubuntuās old broken version:
executing command [sudo -H apt-get install exiv2]
even though exiv2 is in my workspace - Ok, add a
--skip-keys exiv2
everywhere we call rosdep.rosdep install --from-paths src --ignore-src --skip-keys exiv2
- Dependencies are installed, time to build!
colcon build
CMake Error at cmake/Findinih.cmake:28 (message):
inih library not found
Call Stack (most recent call first):
cmake/findDependencies.cmake:84 (find_package)
CMakeLists.txt:84 (include)
Sighā¦ (proceeds to submit a PR to our pre-colcon build repo with yet another shell script that needs installs apt dependencies and exiv2 in all dockerfiles that consume exiv2_consumer
)
Other approaches Iāve seen (docker-based)
# Dockerfile in project/repo1
RUN apt-get update && apt-get install
< giant list of packages that are outside rosdep
# Dockerfile in project/repo2
RUN apt-get update && apt-get install
< A slightly different giant list of packages that are outside rosdep
Or, a slighty better version (ignore the actual syntax, you get the point)
# Dockerfile in project/repo1
RUN apt-get update && cat local-apt-pkgs-list.txt | xargs apt-get install
# Dockerfile in project/repo2
RUN apt-get update && apt-get install
cat local-apt-pkgs-list.txt | xargs apt-get install &&
cat project/repo1/local-apt-pkgs-list.txt
# And it spirals out of control with >10 repos
Or this way of treating docker as our dependency manager which you have periodic build system failures for a rolling hash or tagging nightmares every time you want to break ABI
# Dockerfile in project/repo1
FROM ros:rolling as base
COPY --from project/repo1/base /usr /usr
COPY --from project/repo2/base /usr /usr
...
Questions:
- How are you solving this workflow issue? (If itās conan, or a smattering of pre-colcon shell scripts, please share)
- Are there any quick wins I could contribute to colcon and/or rosdep to make this workflow easier
- Is there any chance there could be a way to supply a 3rd party package.xml for a non-ROS package so that dependencies can be managed through rosdep without requiring maintainers to accept ROS tooling into their repos?