Building dependencies in CI
===========================

Until Sep/2023, librsvg's CI has worked by building a container image
out of a snapshot of openSUSE Tumbleweed, a rolling release, that has
all of librsvg's dependencies in it.  What this means for runtime
dependencies like Cairo, Pango, etc. is that they come in a
"reasonably recent" version, but they are not pinned, and can change
any time that the rolling release decides to update them.

This is not entirely terrible: librsvg's run-time dependencies are all
stable, mature libraries that don't change very much.  From librsvg's
viewpoint, the only trouble comes in situations like these:

* A library involved in text rendering changes something, and text
  output changes slightly.  The test suite breaks as a result, since
  it assumes exact rendering based on reference images.

* Less often, a library involved in Bézier path rendering changes
  something, and parts of the test suite break because antialiasing is
  not the same as before.

* Someone runs librsvg's test suite with a different set of dependency
  versions than the test suite assumes; a bunch of tests fail for
  them, and they file bugs that are not very useful.  In the best
  case, they are using newer libraries and the bug reports let me (the
  maintainer) know that I'll need to re-generate the test suite's
  images soon.  In the worst case, I just close those bugs since they
  don't provide useful information.

And probably the biggest problem of all: I am never sure of exactly
what set of versions are okay for the test suite to work; I just
regenerate test reference files when they break after a CI update.

Pinning dependency versions
---------------------------

Librsvg's CI needs to be able to build the library's dependencies in a
custom fashion, without necessarily assuming that the dependencies
come from system libraries.  If we have that ability, then we can do a
few interesting things:

* Pin the versions of dependencies to a particular set, for example,
  one that corresponds to a certain GNOME release.  This is important
  to keep CI working for old branches, so security patches are easy to
  build.

* Compile the dependencies with a particular set of compiler options.
  For example, for fuzzing, dependencies should be built with
  sanitizers like asan/ubsan.  For deep debugging, it would be nice to
  have all the dependencies built in debug mode.  For performance
  testing, compile all the dependencies in release mode, etc.

Which dependencies?  These ones; this list is already sorted in the
correct build order:

* glib
* gobject-introspection
* freetype2
* fontconfig
* cairo
* harfbuzz
* pango
* libxml2
* gdk-pixbuf

How do we achieve that?
-----------------------

**Option 1:** There is a script ``ci/build-dependencies.sh`` that can
already build librsvg's dependencies pinned to particular git tags.
In theory one can pass environment variables to change compiler
options; the script may need some tweaks to change the meson or
autotools invocations.  The script builds and installs the
dependencies to a given prefix, and assumes that
``PATH/LD_LIBRARY_PATH/PKG_CONFIG_PATH`` are tweaked to use things
from that prefix.

**Option 2:** Alternatively, we can outsource the problem and use
GNOME's BuildStream images.  These correspond to specific releases of
the GNOME platform libraries, including librsvg's dependencies... and
librsvg itself, as it *is* a platform library.  One must be a little
careful to keep the test suite from using the "system's" librsvg in
that case, but this is not a huge problem.

Implementation notes - building dependencies explicitly
-------------------------------------------------------

As of Sep/2023 the CI builds three main images:

* An image with the MSRV, just used to test the promise that the MSRV
  can still build the library and its Rust dependencies.

* An image with a recent, stable Rust compiler - the main image used
  for most jobs, and what I use for local development.

* An image with the nightly compiler.  This is not updated frequently
  since the whole CI doesn't regenerate its images nightly.

* (Other images for other architectures or distros, not in scope for
  this discussion.)

All images have whatever RPM versions are available in openSUSE for
librsvg's runtime dependencies.

Proposed change:

* Keep a single image, pre-populated with ``rustup toolchain install
  <version>`` for the MSRV, the stable compiler, and nightly.  Select
  the compiler version on a per-job basis; hopefully this will be fast
  since ``toolchain`` should cache them.

* In that single image, keep pre-built sets of runtime dependencies
  (e.g. libraries built from git tags) in at least two configurations:
  the minimum supported versions, and the latest stable ones.  These
  configurations can live in different prefixes, 
  e.g. ``/usr/local/librsvg-minimum`` and
  ``/usr/local/librsvg-stable``.

The idea is to ensure that the minimum-supported everything (rustc and
dependencies) actually works, in addition to testing the recent-stable
stuff.

Keeping everything in a single container image (versions of the Rust
toolchain, and sets of dependencies installed to different prefixes)
is just an optimization to build and maintain a single image, instead
of the three we have right now.  If we determine that selecting
rustc/deps makes jobs too slow, we can go back to producing multiple
images.

Implementation note - BuildStream to test nightly dependencies
--------------------------------------------------------------

FIXME: alatiera's work goes here.

Other architectures, other distros
----------------------------------

I'd like to keep an ``aarch64`` image; it has let us notice
peculiarities like the different signedness of ``libc::c_char``.  It
doesn't need the minimum/stable/nightly distinction; we can keep it
stable-only as currently.

I think we can drop the Fedora image.  It still runs Fedora 36, is
seldom updated, and I don't pay attention to it.  I'd rather make it
possible to have explicit git versions of dependencies.

