Snapping Nextcloud: PHP 7

In the previous post of this series, we discussed the process of and lessons learned from building Apache for the Nextcloud snap. Nextcloud is a web application written in PHP, so for this post we'll focus on getting PHP into the snap.

PHP 7, like Apache, is included in the Ubuntu archives. However, I was planning on using the PHP Apache module, and since I was building Apache from source, I figured there'd be a lot of hoops for me to jump through if I tried to shoehorn the .deb into working for me. Since building Apache modules is really pretty straight-forward, I just went straight for building the PHP Apache module from source, as a new part in the snapcraft.yaml. Of course, it uses the various Apache headers and tools for building modules, which is another part in the YAML, and that relationship is specified by using the after keyword in snapcraft, something like this:

# ...
plugin: apache
# ...
plugin: autotools
after: [apache]
# ...
# ...

Using the after keyword like this tells snapcraft that the php part depends upon the apache part, and apache should be staged before php is built. However, going down this path quickly led me to:

Lesson 3: Project build systems aren't really prepared for snapcraft

This is pretty similar to Lesson 2 in the previous post in that the problem is the same, but the effect is different. As I mentioned in the previous post, Apache writes its prefix everywhere, including its tools for building modules (e.g. apxs). This has the effect that modules built using apxs get installed into that same prefix. There doesn't seem to be any way to redirect it elsewhere.

This is a problem because snapcraft was designed for parts to be completely independent. They may depend upon another part if it's already in the staging area, but they cannot alter each others files or directories. We could work around this fact by rewriting the prefix in apxs to point to the staging area, but then we hit another problem: snapcraft was also designed for each part to go through an explicit lifecycle: pull, build, stage, and prime (followed by the final snap). Having parts install directly into the staging area means they essentially skip the build step, which throws snapcraft's state tracking off and makes it unhappy. Future snapcraft developments may make this easier, but we're not there yet.

The solution to this problem without fighting either snapcraft or apxs too much is to simply build Apache modules as part of the Apache plugin, thus keeping it within the same part (you'll notice the third-party-modules config option within the plugin). When developing that code, I quickly ran into another issue:

Lesson 4: DESTDIR is not standard

I've mentioned the concept of relocatable builds a few times: snapcraft configures projects to install into location X, installs into location X, and snaps run from location Y. That's actually not quite true. It's actually more like: configure to install into location X, install into location Y, and run from location Z. What do I mean by this? Well, snapcraft builds each part within a parts/<partname>/build/ subdirectory, and installs them into parts/<partname>/install/. It does this even though the project might be configured to install into /usr/bin/, and it uses DESTDIR to do that.

For example, if you look at snapcraft's make plugin, you'll see that it runs make followed by make install DESTDIR=<installdir>. Many build systems respect this as a de-facto standard, and essentially install their artifacts into DESTDIR/<prefix>.

However, PHP does not. The PHP build system completely ignores DESTDIR and tries to install to the configured prefix, in the root filesystem somewhere, and dies because it doesn't have permission to do so. It wouldn't get into the snap that way anyway! This actually led to the creation of a new snapcraft feature for the autotools plugin: the install-via option. Using that option it's possible to make the autotools plugin not use DESTIR, and instead use a series of incantations like this:

$ ./configure --prefix=parts/<partname>/install
$ make
$ make install

Note the lack of DESTDIR there; it's just installing with the prefix instead. This allowed PHP to install correctly into the snap.

Note that I'm actually currently in the process of moving to PHP FPM instead of the Apache module, for various reasons, but these lessons still apply.

The next post in the series will discuss MySQL.