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).

Kitchensink, a demo-application
Kitchensink, a demo-application

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.

Diamond Editor, a CopperSpice-based text editor
Diamond Editor, a CopperSpice-based text editor

.. 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).

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.