Keep build-only dependencies out of the main image (#356) - #2
Conversation
Co-authored-by: Josh Whitley <josh.whitley@picknik.ai> Co-authored-by: David Vadovszki <david.vadovszki@picknik.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d480302 to
fa197ca
Compare
|
[written by AI] Folded in the Their PR also proposes a main-image CI smoke job. Worth doing, and it is listed as a follow-up here — with one adjustment. Its assertion is Two other differences worth noting for whoever reconciles these:
Measured effect of the exec split on its own: 296 → 285 apt packages, 208.6 MB → 203.1 MB. Smaller than the 68 MB the dropped |
8e55878 to
b558b9e
Compare
[written by AI]
Addresses space-ros#356. Stacked on PickNikRobotics/space-ros#388 (
add-docker) — review that first; this PR's base isadd-docker, so the diff shown here is only the size work.The Earthly version of this work is space-ros#357, which space-ros#388 conflicts with by deleting the
Earthfile. This is that work ported to theDockerfile, minus the parts that measurement or review killed.Problem
Everything the main image needs to compile Space ROS also ships in it: GCC, CMake, binutils,
ros-dev-tools(which drags in colcon, rosdep, vcstool, bloom), plus the headers and CMake config files of the built install. None of it is used to run Space ROS.Approach
pre-installation-buildstage holdingbison build-essential cmake git wget.setup,ikos-install, androsdepderive from it;prepare-image— the final image's parent — stays on plainpre-installationand no longer installsros-dev-tools. The dev image reinstallsros-dev-toolsin theimagestage as before.buildstrips the install for non-dev variants:include/,share/**/cmake/,*.cmake,CMakeLists.txt,*.pyc. Done inbuild, beforeimagecopies${SPACEROS_DIR}— a delete in a later layer only writes a whiteout over the parent layer and reclaims nothing.clang-formatadded toexcluded-deps.txt, so main skips it and dev still gets it.rosdep --dependency-types exec.rosdeps.shresolved everything the workspace needs to compile, and the runtime image installed all of it. Therosdepstage now emits two scripts:rosdeps.sh(everything, unchanged, used bybuildand restored in the dev image) androsdeps-exec.sh(exec deps only, installed byprepare-image). Credit to @sylvesterkaczmarek, whose parallel Earthfile-based attempt at Reduce image size by removing build dependencies unused at runtime space-ros/space-ros#356 (sylvesterkaczmarek/space-ros#1) is where this idea came from.prepare-imageruns the rosdep script withAPT::Install-Recommends "false"and cleans/var/lib/apt/lists/*in that same layer. Without the first, droppingros-dev-toolsnewly pulls 23 MB of Recommends (manpages-dev, DejaVu fonts, libgd/libjpeg/libtiff/X11) behindlibc6-dev; the baseline dodged that only becausebuild-essentialhad already installedlibc6-devunder--no-install-recommends. Without the second, therm -rfalready in theimagestage masks those bytes instead of reclaiming them.docs/USAGE.md: one note that:latestcarries no compiler, CMake, or colcon and no headers, and that building against Space ROS means:dev.Two latent bugs this surfaced
Both are pre-existing and only fixed here because dropping
ros-dev-toolsexposed them. Both were found by running the image, not by reading it.python3-yamlreached the image only throughros-dev-tools. Replace Earthly with Docker Buildx for Space ROS builds (#107) space-ros/space-ros#388 converted the Earthfile'spip3 install pyyaml lark packaging catkin_pkg psutilto apt packages but droppedpyyaml; it kept working by accident. It is not a resolved rosdep at all — onlylibyaml-devis. Without itimport rclpyraisesModuleNotFoundError: No module named 'yaml'and the wholeros2CLI is dead. Now requested explicitly inprepare-image.python3-argcompleteis a resolved rosdep thatexcluded-deps.txtfiltered out, so it too arrived only viaros-dev-tools. It is a realros2cliruntime dependency: without itros2cliswallows the extension'sImportErrorand silently drops everyros2 topicverb (bw delay echo find hz info list pub type) andros2 service echo. No error, the commands just cease to exist — andros2 pkg liststill returns all 212 packages, so a smoke test that only checks package discovery passes green on a broken image. Removed fromexcluded-deps.txt.Verification
Both variants built locally and compared against
add-dockerat 8c27019.add-dockerdocker image inspect --format '{{.Size}}')ros2 pkg list−259.8 MB, −56%. 138 apt packages gone (gcc/g++/binutils/cmake/make, git/wget/subversion/mercurial/bzr, all of colcon, rosdep, vcstool, bloom, clang-format, python3-pytest); zero packages added. Of the shrink,
/opt/ros/spacerosdrops 168 MB → 128 MB,include/alone accounting for 40 MB. The exec-dependency split is 5.5 MB of that (296 → 285 packages): less than the 68 MB the droppedpython3-devchain measures on its own, because other retained-devpackages still pulllibc6-dev.Parity against the baseline image, not just liveness:
ros2 {topic,node,param,service,action,interface,lifecycle,pkg} --helpexpose byte-identical verb sets.ros2 topic pub+ros2 topic echo --onceround-trips astd_msgs/msg/String;rclpy.create_nodeworks.import rclpy, yaml, lark, catkin_pkg, psutil, netifaces, numpy, packaging, argcompleteall succeed.gcc/cmake/colcon/git/vcs/clang-formatall present,/opt/ikos/bin/ikospresent,include/and 154*Config.cmakefiles retained. It compiles and runs arclcppnode built against the install. Itsadd-dockerbaseline was not built, so no dev-image delta is claimed — but the exec split is verified against a dev image built from this branch without it: identical 606-package set, no additions, no removals, confirmingrosdeps.shrestores exactly whatprepare-imageno longer installs.pre-commit(includinghadolint) passes.docker buildx build --call checkis clean formain/dev×image/export-build-test/export-repos.Deliberately not done
rm -rf /usr/include, from Reduce core image size (#356) space-ros/space-ros#357. @ivanperez-keera objected there to removing files a package owns, leaving dpkg's view of the filesystem inconsistent. Agreed; the-devpackages would have to go instead, which is the follow-up below.localepurgeand dpkgpath-exclude, from Reduce core image size (#356) space-ros/space-ros#357. Measured, they buy nothing:ubuntu:noblealready ships/etc/dpkg/dpkg.cfg.d/excludes, which drops man pages and.motranslations as packages unpack. A/B build ofubuntu:noble+locales+git wget curl cmake build-essential python3-pipgives 569,976 KB withoutlocalepurgeand 570,496 KB with — 520 KB larger, the cost of the package itself — with/usr/share/localeat 612 KB either way. The Earthfile builds from the same base, so this holds for Reduce core image size (#356) space-ros/space-ros#357 too.Follow-ups
ros2 topic listand a Python import check rather thanros2 pkg list— per thepython3-argcompletebug above, package discovery survives breakage that removes half the CLI, so a package-discovery assertion passes green on a broken image.vcs import --retry 3does not cover ref-type determination. A CI run on this branch failed in thesourcesstage with a one-off TLS error from vcstool'sCould not determine ref type of version, which the retry did not apply to. Transient (siblings cloned fine in the same command, and both other jobs in the same run passed the same stage), but the retry gap is real.rosdeps.sh's header line is dead code. The generator writesapt-get install -y \followed by rosdep's#[apt] Installation commands:comment, so the continuation is swallowed and the real work is oneapt-get install -y <pkg>per line, emitted verbatim by rosdep. That is why this PR sets the apt config instead of adding a flag there. Worth straightening out separately, in Replace Earthly with Docker Buildx for Space ROS builds (#107) space-ros/space-ros#388's code.