The KF5 Content Snap is kind of snap's variant of a runtime.
How it Works
The way it works is that the core18 snap provides the minimal core of the system (e.g. libc), and then application snap uses the content snap to mount everything needed for KF5 on top of the core.
Example standalone snap content. Library is in the $SNAP/usr/lib:
usr └── bin │ └── kbruch └── usr/lib └── libkf5coreaddons.so
Example of the same snap using the kf5 content snap. Library is in $SNAP/kf5/usr/lib:
usr └── bin └── kbruch kf5 └── usr/lib └── libkf5coreaddons.so
It's worth pointing out that content snap usage is application, not snap specific. So, each application that wants the content needs to have the relevant plug listed.
When an application that relies on a content interface is started, a snap providing that specific content type is "mapped" into a path chosen by the snap. For consistency the KF5 content snap assumes this to always be $SNAP/kf5. To actually use the shared content the snap launcher therefore needs to be aware of the path and set up LD_LIBRARY and what not correctly so that the content snap's stuff may be found.
IOW: the content snap is basically mounted into the $SNAP tree.
The build snap is a regular snap that is more or less a partial sysroot. It's the same tree as the content snap, albeit with headers and cmake configs etc. It's not functional on its own but rather needs an Ubuntu system to be used on. When a build snap is used in a snapcraft.yaml (on a cmake part) the build-snap is installed via snapd and its root path is automatically added to the CMAKE_FIND_ROOT_PATH. The build snap uses some hackery to mimic behavior of a classic snap vis-a-vis rpath injection. Since the build snaps are not in /usr/lib/* the system's gcc/ld will not be able to find implicitly linked libraries (e.g. your application links libpulse, but libpulse links libpulseinternal, or whatever, which is in $SNAP/usr/lib/pulseaudio/ and thus not found). RPath enables us to find libraries inside the build snap properly. So each lib inside the build snap should have an rpath injected.
During cmake's configure stage will then try to find_* stuff in the find root. This technically allows building on top of what is in the system, but unfortunately there are some caveats to it. Firstly various things can make cmake prefer an artifact it finds in the "root" rather than the build snap. A notable example is when the root system has a higher version of KF5 cmake *may* pick that instead because it has no preference when multiple cmake packages match a find_package call (and both match the minimum version - i.e. they are both sufficient). Incorrect link libraries may get pulled in by root-level cmake packages. All in al build snaps can be a bit of a challenge to use when having to mix with system level libraries. There is no good answer to this unfortunately. Perhaps the best way to look at it is to build everything that isn't in a build-snap as a part.
How They are Built
The KF5 content snap is using neon's Qt and KF5. They get simply "repacked" from their existing deb builds. As such the content snap isn't super expensive to build. However, to even figure out what to put into the snap is already quite the challenge.
The snapcraft.yamls used to create the content snap and it's associated build snap are generated by this poorly written and poorly structured script: https://github.com/apachelogger/kf5-snap/blob/core18/atomize-debs.rb run through neon's Jenkins. For the most part what it does is build a list of deb "sources" to pack into the content snap, then translates those to actual debs and builds an exhaustive snapcraft.yaml with all relevant packages in stage-packages and fairly exhaustive exclusion rules to clean up the resulting snap. The content snap also builds some sources as part of its build for whatever reason (at the time of writing primarily plasma-integration and breeze because their debs have unsuitable dependency chains (pulling in Qt 4). The resulting snap is not useful on its own but needs to be used by application snaps via the content interface.
The build snap's snapcraft.yaml is built on top of the content snap, quite literally, it recycles the stage left by the build of the content snap and simply excludes runtime assets not required at build time. So, the build snap is more or less the same tree as the content snap albeit with headers primed as well. For this purpose the content snap's snapcraft.yaml already contains a kf5-dev part that pulls in all the -dev debs, it also stages the part, buuut it doesn't prime it. So, simply put the stage/ of the content snap build is the build snap's content for prime/. One tiny caveat is that there is a helper script run on the tree to wrap all cmake-called binaries in environment-setup similar to the one of snap launchers.
The build snap build is entirely dependent on the content snap build.
The entire build process comes down to this:
- start container
- run atomize-debs
- build list of sources to pack into the content snap
- translate sources to binary packages
- divide binary packages into runtime and buildtime
- add some additional extra packages to either (e.g. gstreamer plugins)
- construct and write a snapcraft.yaml for the content snap
- construct and write a build/snapcraft.yaml for the build snap
- snapcraft both snapcraft.yamls
- publish to candidate