Packaging CopperSpice
CopperSpice is a collection of libraries – a toolkit, if you will – for developing cross-platform applications in C++. That should sound familiar to KDE people, and it is an LGPL v2.1 fork of Qt from around 2013. In many ways, it is a purely-QWidget continuation, which modernized (C++17, CMake) much earlier than Qt itself. There are some applications that use it, but CopperSpice is rarely packaged (Arch only until today!) for use as a system library. Its consumers probably build CopperSpice locally as part of a product, and this post explains why (and what I did to make to packageable on FreeBSD).
Above is a screenshot of kitchensink, the “demo application” for CopperSpice. It is a multiple-document-interface window, showing off various widgets and things. Graphics scenes, multimedia, and a clock. You can never have too many clocks.
.. and here’s diamond, a compact text editor that uses CopperSpice libraries.
Packaging Principles
Packaging CopperSpice took longer than expected, because I was working under a set of (initially implicit-to-me) assumptions of what it means to package something for FreeBSD. Retroactively, I’m writing them down:
- Must install everything to (somewhere under)
/usr/local
, or$(LOCALBASE)
. Ideally the latter, but I can live with a fixed prefix to start with: very few people choose for/opt
or so in the FreeBSD world. - Must be co-installable with Qt5 and Qt6 without being overly weird about it.
- CopperSpice applications that are packaged should be able to run from the command-line, with no special settings or user-specific configuration files. They should use the system-wide CopperSpice libraries.
- No vendoring (or minimal vendoring) and no duplication of libraries.
- Executables belong in
/bin/
, data in/share/
somewhere. hier(7) makes some of this explicit.
I did not pick these specifically to be annoying for CopperSpice, but these are the reasons that it took a week worth of source-wrangling (mostly CMake bits) to get it to work. The code is just fine, and frankly if I’d thrown up my hands and said “this doesn’t need to package tidily” it could have been a 15 minute job.
Patching the Code
The CopperSpice source code is fairly nice. It “just works” – compiles
largely warning-free except for some missing override
statements –
and the resulting libraries seem to work.
I say “seem to work” because a library has no function on its own: it needs an application to use the library, to get something done. Naively building an application – kitchensink and diamond are applications from the CopperSpice authors – spits out a directory which contains an application, and some resources (a program icon, configuration files) and .. copies of the CopperSpice libraries.
This is where packaging and upstream collide. There are no airbags to cushion the blow. As far as I can tell, upstream just drops everything into a single directory. So there is no shared-library and no need for any kind of co-installability. Applications aren’t intended to be installed in a shared prefix. Libraries are endlessly copied.
From a “product” kind of software release this makes a bit of sense:
take the tarball, extract it somewhere, run the software from there.
There’s a FreeBSD “binary package” of diamond from upstream:
it’s a tarball that extracts to .
(!). But I can extract it wherever
I like, and then run diamond from there. If I had an /opt/diamond
this would work even for multiple users on the system: but it is anathema to
software packaging in the “old school”.
The one code patch I applied to libCsCore was to give it a default plugin path, so that on FreeBSD it looks in the shared, system-wide, installed location of the libraries. That removes the need to copy those plugin libraries around – but then we need to fight the build.
Fighting the Build
I have patched 28 CMake files in the build to not do silly things.
“Silly” is, like I’ve said, in the eye of the beholder: from a
the-product-is-a-tarball perspective, RPATH is a thing that happens to other
people. For a packager, RPATH set to $ORIGIN
(that specific string)
is like the first step on the road to R’lyeh.
So here’s a list of things I patched :
- don’t set RPATH to
$ORIGIN
- set a default plugin path to search
- link publicly to pthreads (this is a FreeBSD thing)
- install includes into a
/copperspice/
subdirectory (co-exist with Qt, which e.g. uses/qt5/
) - install plugins to the directories where they are searched for
That last one takes a bit of explanation: the default
build-and-install of CopperSpice puts libraries (libCsCore.so), platform-plugins (CsGuiXcb), and support bits (CsMultimedia_gst)
all in one install directory.
During an application build-and-install, the libraries are copied,
and so are the plugins that the application needs. The plugin libraries are copied into /platforms/
.
So it is the consumer
(application) that is responsible for shuffling the bits
into a directory hierarchy that matches what the internal code expects.
In FreeBSD packaging, I’ve patched the install locations so that
platform-plugins end up in a subdirectory /platforms/
under
the default plugin path; that’s different from how it would install
them itself, but it makes it possible to find the installed
plugins, in shared locations. In other words: I’ve forced a shared plugin installation
on CopperSpice, and now I need to help the libraries and applications understand
that that shared location exists.
Fighting the Application Build
Since applications are expected to shuffle bits into place in their own installation-directory – and to duplicate the libraries – patching applications mostly means “don’t do that then”. There is no need to copy libCSCore.so to the local installation directory when it’s packaged and installed as a shared directory and meant to be a shared directory.
Mostly this means commenting-out cs_copy_
lines in CMake.
Similarly, applications that are written to install to
a private, non-shared directory just bung everything in there:
libraries, executable, data files. For packagers who think that
CMAKE_INSTALL_DATADIR
is a thing that should be followed
(e.g. from the GNUInstallDirs module), this is a pain in the package
because there are two things to change:
- patch the build to install data to the data directory
- patch the application to search for data in the data directory.
For an application like diamond,
syntax files are copied into a /syntax/
subdirectory; I patched
the build to install to a location under /share/
, but then the
code searches for syntax files in the applicationDirPath()
(e.g. in /usr/local/bin
which is where the executable is installed).
So cue up a patch to search somewhere else. After that, the application is pretty solid.
Spot the Difference
I haven’t tried to do much of anything with CopperSpice. So I can’t speak to the programming experience. From a product perspective, there is a culture of bung-it-in-a-directory which doesn’t work for me as a packager. It might work for non-packaged applications. It might fit really well with AppImage, or similar new(er)-style application delivery formats.
Anyway, as a fork, CopperSpice inherited some of Qt’s excellent
documentation, so here’s CopperSpice 1.73
and here’s Qt 5.15 documentation
for QCoreApplication
.
The Qt version refers to the commercial-only release 5.15.8 as of this writing, so it may not match what is currently available to Open Source consumers of the KDE Patch Collection for Qt.
Future
Now that the first ports have landed, there’s some examples of how CopperSpice-based applications can be turned into ports, and there might be more. If you know of any that would make sense in the ports tree, drop me a note (or submit a PR via bugs.freebsd.org).
- CopperSpice
- kitchensink source
- diamond source
- Repology link
- Arch packaging I found this once I was about halfway through packaging, and used it to confirm that I was doing something sensible; Arch, like FreeBSD, ends up patching bits and bobs so that things install to “traditional” locations.
Who did the real work: Ansel Sermersheim & Barbara Geller do all the heavy lifting upstream. I’m just shoving their bits into an uncomfortable BSD corset.