I am a software/firmware developer, working in Dresden, Germany. In my free time, I spent some time on developing apps, presenting some interesting findings here in my blog.
FPM: Cleaning up folders when uninstalling RPM packages
When working in a larger organization (or you just want to ensure you are using a properly defined development environment), it is always a good idea packaging software components you are using and installing them from these packages to ensure all developers use the same version of needed modules to build and run a project.
When working on Linux, there are two major package formats being used: The Debian format (using the *.deb
file name extension) and the RedHat Package Manager (RPM) format (which uses the *.rpm
) file name extension.
Building such packages from scratch can sometimes be a little cumbersome, but to ease this, there’s a nice little tool available: fpm
!
fpm
is a nice little tool, written in Ruby, which can be used to create a variety of package formats without struggle. It can easily be installed; just make sure you install Ruby first and then you can get fpm
using Ruby’s own package manager gem
like this:
# Assuming you are on an RPM based Linux:
sudo yum install -y ruby
# To build RPM packages, you'll also need rpm-build:
sudo yum install -y rpm-build
# Use gem to install fpm:
gem install fpm
Now, if you want to create an RPM package for - le’t say - a program HelloWorld
, this could look like:
VERSION=1.2.3
curl -o helloworld-$VERSION.tar.gz https://example.com/releases/helloworld-$VERSION.tar.gz
tar -xf helloworld-$VERSION.tar.gz
cd helloworld
# Let's assume the software is built using cmake:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/helloworld/$VERSION
cmake --build build
# Install it into a temporary location:
DESTDIR=$PWD/install_root cmake --install build
# And now, package it:
fpm \
-C $PWD/install_root \
-s dir \
-t rpm \
--version $VERSION \
--name helloworld_$VERSION \
--rpm-autoreq \
--description "HelloWorld, version $VERSION" \
$(ls $PWD/install_root)
👉 Note, that we include the version number also in the installation location and the package name. That way, different versions of the package can be installed next to each other - this can be useful when you need to quickly switch between versions of a module, especially if you use tools like environment modules on top.
Now, the above works and should produce a usable RPM package (well, given you package something real 😉). But there is a but here!
RPM has a notion of ownership over files and directories. An RPM package produced like that will only own the files that get installed. However, if we uninstall the package, we will notice that the folder structure will still be there (although all files properly have been removed)!
In most cases, this isn’t too bad (empty folders usually don’t do any harm). But sometimes they are! In my case, I packaged Python and over time released an updated version of the same Python interpreter that updated older versions of the package in-place. An update is mostly the same as an uninstall of the old and install of the new version. What happened? In my case, I had the new version of Python properly installed, but there were empty folders of some packages that got installed alongside Python in the modules folder. These empty folders caused issues with Python, because Python’s built in plugin mechanism found these empty folders and treated them like they where still valid. This caused very strange and hard to track down failures with some of our scripts that used that Python installation (not to mention that these bugs were not reproducible when installing only the new package e.g. within a Docker container 😉).
So, long story short: How to get rid of these extra folders after an upgrade/uninstall? Easy, we need to tell fpm
/rpm-build
, that the package we create owns the folders it installs:
fpm \
-C $PWD/install_root \
-s dir \
-t rpm \
--version $VERSION \
--name helloworld_$VERSION \
--rpm-autoreq \
--description "HelloWorld, version $VERSION" \
--directories /opt/helloworld/$VERSION \
$(ls $PWD/install_root)
We simply use the --directories
option, which will mark the folder (recursively) as being owned by our package. Uninstalling our package will no longer leave empty folders around!
⚠️ Please note that we specify the full installation prefix above! In the end,we don’t want our package to own more folders as it actually does 😉