A Simple C/C++ Buildpack is Easy

If you want to run make during staging, that’s pretty easy. Heroku
has a minimal buildpack,
heroku-buildpack-c,
that will simply run configure (if you’ve got a configure file)
and make. And it totally works on Cloud Foundry, you can go try it!

Oh, wait.

You won’t go try it out. I know you won’t, because it’s a terrible
idea.

It’s totally impractical to write a web service in C++ without using
some sort of framework that would require header files and libraries
that aren’t present in the rootfs.

How impractical? Here’s a simple “Hello, world!” program that I found
by clicking on the first Google result that matched “c web server”:

If you
look at the code,
you’ll see there’s 500+ lines of C code that are simply handling GET
and HEAD requests for various mime types.

That seems pretty amazing; I actually expected it to be much bigger
and more complicated. Regardless, I would never, ever want to do that
myself, for a huge variety of reasons, primarily security, but also to
preserve my sanity and to make sure I was focusing on the
right abstractions.

The hypothesis for the rest of this post it that any robust web app
is going to be using a framework, which will bring along its own
header files and libraries. And thus a buildpack that simply runs
make is not very useful.

A Real-World Example

I was chatting with a business partner a few weeks ago about his
firm’s need for a C++ buildpack. I’ll call him “Bob.” I brought up the
challenges of making sure header files and libraries are available
during staging.

“Wouldn’t it be easier to pre-compile the binary on a development
system where all these files are available, and then deploy using the
binary-buildpack?”
I asked.

“No, I want to preserve the simplicity of cf push for our C++
developers,” Bob responded. “It’s a great model, and there’s no good reason it shouldn’t work for our C++ microservice authors.”

This absolutely blew my mind, as it turned on its head my mental model
of a C++ developer.

For many years I worked with productive C and C++ developers who all
preferred “manual transmission” tooling, to preserve developer
discretion and optimization in how code was built and deployed.

Now Bob, an internet-famous technology executive, was telling me that
he’d prefer “automatic transmission”, as popularly described by
Onsi’s CF Haiku:

here is my source code
run it on the cloud for me
i do not care how

ORLY! Challenge accepted.

CppCMS

Bob mentioned CppCMS as a potential C++ web
framework for microservice authors. I had never heard of it before,
and so step one was to get familiar with it, and deploy a “Hello,
World!” app.

The CppCMS site provides a “Hello, World!” app, and the code is pretty
tight (at least, compared to the
GoHTTP
implementation):

But What About the Headers and Libraries?

Making header files available at staging time seems pretty easy;
they’re just text files, and can be simply copied from a CppCMS
distribution.

But shared-object libraries are harder. They need to be cross-compiled
for the rootfs. How can we make sure this happens safely?

Good news. The CF Buildpacks team has, over the last year,
open-sourced all the tooling that they use to generate binaries for
most of the Official Cloud Foundry™ buildpacks (binary, go, node, php,
python, ruby, and staticfile). One of these tools is
binary-builder,
which cross-compiles binaries for the CF container rootfs.

I wrote a new “recipe” for CppCMS, which is on an
experimental branch,
that downloads and verifies a checksum for a source tarball, then
configures and compiles it for the CF rootfs. It contains the header
files and the libraries (both static and shared), and so contains
everything we need to stage and run a CppCMS application.

The CppCMS Tarball

The binary-builder job from the
experimental branch
creates a file, cppcms-1.0.5-linux-x64.tgz, containing header files
and both static and shared libraries.

We’ll add this tarball to the buildpack as an archive which can be
used if and when the buildpack is used to compile an application; and
we’ll make sure it’s easily identifiable as having been cross-compiled
for
cflinuxfs2
(as opposed to another stack).

bin/detect

Shown above; let’s just look for the cppcms.js script.

If you’re specifying your buildpack at cf push time (with the -b
parameter), this script won’t even run, so it’s not strictly necessary
unless you’ve added it to the admin buildpacks on your CF deployment.

bin/compile

The workhorse of application staging, bin/compile must generate a
droplet that’s deployable, and so has to do a few different things.

First, we’ll untar the appropriate version of CppCMS for your stack into the application’s directory (so it’s in the eventual droplet):

Configuring the Application at Runtime

Finally, we need to make sure that the application is configured
properly to run as a CF application. Primarily, this means listening
on the appropriate port, but also listening on the correct network
interface and making sure that HTTP is turned on (as opposed to
FastCGI or some other craziness).

I showed above how we could do this using jq, which has been in the
rootfs since
v1.2.0.

The other thing we’ll need to do is to set the linker’s path to be
able to find the libraries at runtime:

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:cppcms/lib

Following the existing
buildpack convention,
we’ll name this script after the buildpack, cppcms.sh, and put it
into the droplet’s .profile.d directory (see above for where this is
done in the bin/compile script).