Compile PHP From Scratch on Ubuntu WSL With PHPBrew

Today’s inspiration comes from Ross Bulat’s story: True PHP7 Multi-Threading: How to Rebuild PHP and use pthreads.

Ross’s writes about installing PHP from scratch using Phpbrew (Github) on Debian and CentOS. On today’s story, I will cover installing PHP with the help of Phpbrew, on the Ubuntu 18.04 Bionic Beaver LTS release, provided by Microsoft on the Windows Store as a Windows Subsystem for Linux distribution.

I plan to use Visual Studio Code’s suite of Remote Development extensions for development, which allow you to connect from Windows to a running Windows Subsystem for Linux (WSL) distribution, in this case Ubuntu.

Once Visual Studio Code is connected to the Ubuntu WSL instance, the PHP runtime I will be installing inside WSL as part of today’s article will serve to power both the Visual Studio Code (VSC) PHP IntelliSense and PHP Debug extensions. The PHP installation will be optimized to cover the minimal set of dependencies required to use Drupal 8.

As part of today’s article I will not be installing Apache, Nginx, PHP-FPM or any type of web server, since I use Docker to run my web servers inside containers. However, Visual Studio Code needs a PHP runtime environment in order to interpret any PHP code I am editing (which includes Drupal), which will be provided by the Ubuntu WSL instance I will be setting up in this piece.

Although Microsoft’s Remote Development tools are capable connecting the Visual Studio Code editor to Docker containers, I am not yet fully convinced of their performance, nor their ability to fully support a wide range of Linux distributions. I could also just install PHP natively on Windows or Mac, but after 15 years of doing exactly so, I’d rather not mess with my underlying operating system and install development dependencies in an isolated environment — hence, WSL.

It is important to note that version 2 of Windows Subsystem for Linux is currently available only as part of Windows Insider Program. This recent article by Jordan Lee, Get WSL2 working on Windows 10 (Sep 6, 2019) will help you up to speed with both how to get set up with the Insider Program, and installing WSL version 2.

If you are a current WSL user migrating from version 1 to version 2, as described by Microsoft’s Installation Instructions for WSL 2, you will need to convert any existing WSL 1 distributions you have to version 2 in order to use WSL version 2.

The command to convert an existing WSL 1 distribution to WSL 2 is:

wsl --set-version <Distro> 2

If you want to make WSL 2 your default distribution environment whenever you download new distributions from the Microsoft Store, the command is:

wsl --set-default-version 2

And to verify the current list of WSL distributions available in your system, and their current WSL version, the command is wsl -l -v, where the -v flag stands for WSL version:

wsl -l -v
NAME STATE VERSION
* Alpine Running 2
Ubuntu-18.04 Running 2
docker-desktop Running 2
docker-desktop-data Running 2

While it is not strictly required for you to be running an Insider’s Preview version of Windows 10, or WSL 2 in order to follow this article, I highly recommend it since any upcoming articles I write will deal exclusively with WSL version 2 instead of version 1. The next article after this one will deal directly with using today’s work for Drupal development using Docker, at which point you will need WSL version 2.

Before the Preface gets longer than the main article itself, let’s get started.

.

PHPBrew: A Quick Intro

.

PHPBrew facilitates the process of compiling PHP and related extensions from source.

You, my astute dear reader must be asking: why would I compile PHP and it’s extensions from source when I can just download them from my Linux distribution’s package repository, already compiled? Isn’t that faster?

Yes! But…

I personally like my PHP binaries (the compiled language and .so extensions) to be as close as possible to production in terms of versions.

Last Sunday night while watching the Super Bowl, I pondered in Using Visual Studio Code Remote Explorer with WSL 2 Will Change How You Think About Volume Bind Mounts about some of the ups and downs of using pre-compiled binaries.

The gist of that comparison is that:

  • The PHP-FPM Docker web service that serves my Drupal workload runs on PHP 7.3.14, the latest stable version.
  • The latest Long Term Support (LTS) of Ubuntu distributed through the Windows Store only comes with PHP 7.2.24.
  • I would die of hunger as a sports writer (or as a writer, period).

So while I could just settle with installing Ubuntu WSL, and doing sudo apt-get install -y php7.2, the risk for side effects when the discrepancy between debugging (7.2) and execution (7.3) environments is too great, it’s a risk I’m personally not willing to take.

Earlier today I tried to use Alpine WSL as my main editing environment, but as I write here on Docker Desktop 2 Unix Socket Not Available on Alpine WSL 2, that didn’t pan out. While either Microsoft or Docker figure out why Alpine WSL can’t connect to Docker, I need a WSL-based environment to contain PHP just for editing and debugging.

I could also just compile PHP and all it’s extensions by hand, but the reality is that I need a working editor/debugger and runtime combo sometime before the year 2030, preferably.

Enter PHPBrew.

Before I go recommending PHPBrew, I would like to bring to attention two things:

  1. This article: Blast from the Past (Feb 1, 2019) — by Arne Blankerts, Stefan Priebsch, and none other than Sebastian Bergmann himself — the man that brought us PHPUnit, amongst other important PHP tools.
  2. The fact that PHPBrew does not seem to offer a signature for verifying the integrity of it’s PHAR archive.

Read #1, then read #2 (again).

This is (or was, if they’re still alive) PEAR’s recommended way of downloading their installer:

$ curl -L https://pear.php.net/go-pear.phar | php
# or
$ wget https://pear.php.net/go-pear.phar && php go-pear.phar

Compare that to PHPBrew’s:

curl -L -O https://github.com/phpbrew/phpbrew/releases/latest/download/phpbrew.pharchmod +x phpbrew.phar

# Move the file to some directory within your $PATH
sudo mv phpbrew.phar /usr/local/bin/phpbrew

I can’t help the feeling that both instructions are awfully insecure due to lack of a final, post-download integrity verification. And while PHPBrew’s main maintainer’s commits seem to be GPG-signed over at Github.com, it’s something that certainly gave me some pause before signing on to use PHPBrew. With that being said, I’d never use either in Production. And I would have avoided PHPBrew should have using Alpine WSL for connecting to Docker worked (it didn’t). But here we are, I am I thankful to the PHP community for providing options regardless.

.

Installing PHP 7.3 on Ubuntu for WSL 2 with PHPBrew

.

First, assuming you have curl installed already, download PHPBrew:

curl -L -O https://github.com/phpbrew/phpbrew/releases/latest/download/phpbrew.pharchmod +x phpbrew.phar

# Move the file to some directory within your $PATH
sudo mv phpbrew.phar /usr/local/bin/phpbrew

If you don’t have curl in Ubuntu, just run sudo apt-get install -y curl first. After downloading the PHPBrew PHAR archive, you’ll need to install some some prerequisites before running phpbrew init :

sudo apt-get install -y build-essential bzip2 php7.2 php-bz2

Believe it or not, I needed to install PHP first in order to … install (compile) PHP. In my case, not installing php-bz2 led me to:

After meeting some basic prerequisites, the phpbrew init command ran successfully:

After placing source /home/wsl/.phpbrew/bashrc into my ~/.bashrc , and running source ~/.bashrc I get:

The command used to show the available releases to PHPBrew is phpbrew known :

I’d love to build me a set of Alpine containers with 7.4 and Drupal 9, or Drupal 8-dev once it has full 7.4 support. But since currently my containers run on 7.3, and much of my production environments, let’s start with that 7.3:

Hans… forget ze Gustav. Get ze libxml2 :

It’s a hit: https://packages.ubuntu.com/search?keywords=libxml2&searchon=names&suite=bionic&section=all

Probably a better way to search. The web repository does not support wildcards, like the Alpine one does.

From:

sudo yum install php php-devel php-pear bzip2-devel yum-utils bison re2c libmcrypt-devel libpqxx-devel libxslt-devel pcre-devel libcurl-devel libgsasl-devel openldap-devel httpd-devel readline-devel

Searches, or tracing how am I finding the equivalent packages in Ubuntu WSL:

apt-cache search --names-only 'readline.*dev.*'
apt-cache search --names-only 'php.*dev.*'
apt-cache search --names-only 'bzip'
apt-cache search --names-only 'bz.*dev.*'
apt-cache search --names-only 'bison.*'apt-cache search --names-only 'libmcrypt.*'
apt-cache search --names-only 'libpqxx.*'
apt-cache search --names-only 'libxslt.*'
apt-cache search --names-only 'pcre.*'
apt-cache search --names-only 'libcurl.*'
apt-cache search --names-only 'libgsasl.*'
apt-cache search --names-only '.*readline.*'

And their corresponding, hopefully relevant results:

libreadline-dev - GNU readline and history libraries, development filesphp-all-dev - package depending on all supported PHP development packages
php-dev - Files for PHP module development (default)
php7.2-dev - Files for PHP7.2 module development
bzip2 - high-quality block-sorting file compressor - utilities
libbz2-dev - high-quality block-sorting file compressor library - development
bison - YACC-compatible parser generator
libbison-dev - YACC-compatible parser generator - development library
libmcrypt-dev - De-/Encryption Library development files
libmcrypt4 - De-/Encryption Library
libpqxx-4.0v5 - C++ library to connect to PostgreSQL
libpqxx-dev - C++ library to connect to PostgreSQL (development files)
libxslt1-dev - XSLT 1.0 processing library - development kit
libxslt1.1 - XSLT 1.0 processing library - runtime library
libgsasl7 - GNU SASL library
libgsasl7-dev - Development files for the GNU SASL library
libreadline-dev - GNU readline and history libraries, development files
readline-common - GNU readline and history libraries, common files

The PCRE library search yields a lot of results, and I don’t know which one PHP compiling depends on :/

Not a lot of help here regarding that, but might be a useful resource down the line: http://www.phpinternalsbook.com/php5/build_system/building_php.html

The main candidates seem to be:

libpcre2-dev - New Perl Compatible Regular Expression Library - development files
libpcre2-posix0 - New Perl Compatible Regular Expression Library - posix-compatible runtime files
# orlibpcre3 - Old Perl 5 Compatible Regular Expression Library - runtime files
libpcre3-dev - Old Perl 5 Compatible Regular Expression Library - development files

Particularly confusing here in Ubuntuland seems to be that libpcre2 is called the new while libpcre3 is called the old.

Libcurl has a couple options as well:

I think I’ll go with version 4, since it has companion dev files.

libcurl4 - easy-to-use client-side URL transfer library (OpenSSL flavour)
libcurl4-openssl-dev - development files and documentation for libcurl (OpenSSL flavour)

On readline:

To use the readline functions, you need to install libreadline. You can find libreadline on the home page of the GNU Readline project…

Thank you,… but which version? The PHP manual does not specify. Of course it’s not going to specify, it’s PHP…

Tying it all together.

The requirements are ambiguous, the packages available are ambiguous (but not all), and so will be the potential compilation results. Here’s the dependency install list I’ve compiled so far, minus the comments:

libreadline-dev readline-commonphp-all-dev
php-dev
php7.2-dev
bzip2
libbz2-dev
bison
libbison-dev
libmcrypt-dev
libmcrypt4
libpqxx-4.0v5
libpqxx-dev
libxslt1-dev
libxslt1.1
libgsasl7
libgsasl7-dev
libreadline-dev
readline-common
libcurl4 libcurl4-openssl-devlibpcre2-dev libpcre2-posix0

And the install command for Ubuntu on WSL 2:

sudo apt-get install -y \
libreadline-dev \
readline-common \
php-all-dev \
php-dev \
php7.2-dev \
bzip2 \
libbz2-dev \
bison \
libbison-dev \
libmcrypt-dev \
libmcrypt4 \
libpqxx-4.0v5 \
libpqxx-dev \
libxslt1-dev \
libxslt1.1 \
libgsasl7 \
libgsasl7-dev \
libreadline-dev \
readline-common \
libcurl4 \
libcurl4-openssl-dev \
libpcre2-dev \
libpcre2-posix0

Now, please look at me with a straight face… and tell me that you want to install (pollute) your host OSX or Windows system with all those libraries, of which you don’t even know if you are installing the correct version.

What about the havoc that they could cause with your native system dependencies, or if you start adding and removing dependencies and make a boo-boo, mistype something, remove something you shouldn’t by accident and end up bricking your system — your main source of income and productivity!

I’ve done it, I’ve bricked my own personal or corporate systems many many times over decade and a half of development (I’m sure plenty of you have too), and that’s why I advocate for the use of tooling such Vagrant back in the day (docker-machine, anyone?), and now things such as containers in Docker Desktop, and now WSL on Windows.

Here’s the list of packages to be installed according to apt-get for reference:

The following additional packages will be installed:
autoconf automake autopoint autotools-dev bzip2-doc comerr-dev debhelper dh-autoreconf dh-strip-nondeterminism gettext gir1.2-harfbuzz-0.0 icu-devtools intltool-debian krb5-multidev
libarchive-cpio-perl libarchive-zip-perl libcom-err2 libfile-stripnondeterminism-perl libglib2.0-0 libglib2.0-bin libglib2.0-dev libglib2.0-dev-bin libgraphite2-dev libgssrpc4
libharfbuzz-dev libharfbuzz-gobject0 libharfbuzz-icu0 libicu-dev libicu-le-hb-dev libicu-le-hb0 libiculx60 libidn11-dev libkadm5clnt-mit11 libkadm5srv-mit11 libkdb5-9 libltdl-dev
libmail-sendmail-perl libntlm0 libntlm0-dev libpcre16-3 libpcre2-16-0 libpcre2-32-0 libpcre2-8-0 libpcre3-dev libpcre32-3 libpcrecpp0v5 libpq-dev libpq5 libsodium23 libssl-dev
libsys-hostname-long-perl libtimedate-perl libtinfo-dev libtool libxml2-dev m4 php-pear php-xml php7.2-cli php7.2-json php7.2-opcache php7.2-readline php7.2-xml pkg-config
pkg-php-tools po-debconf python3-distutils python3-lib2to3 shtool zlib1g-dev
Suggested packages:
autoconf-archive gnu-standards autoconf-doc bison-doc doc-base dh-make dwz gettext-doc libasprintf-dev libgettextpo-dev krb5-doc libcurl4-doc libkrb5-dev libldap2-dev librtmp-dev
libssh2-1-dev libglib2.0-doc libgraphite2-utils krb5-user icu-doc libtool-doc mcrypt postgresql-doc-10 libpqxx4-doc readline-doc libssl-doc gfortran | fortran95-compiler gcj-jdk
m4-doc dh-php libmail-box-perl
The following NEW packages will be installed:
autoconf automake autopoint autotools-dev bison bzip2-doc comerr-dev debhelper dh-autoreconf dh-strip-nondeterminism gettext gir1.2-harfbuzz-0.0 icu-devtools intltool-debian
krb5-multidev libarchive-cpio-perl libarchive-zip-perl libbison-dev libbz2-dev libcurl4-openssl-dev libfile-stripnondeterminism-perl libglib2.0-bin libglib2.0-dev libglib2.0-dev-bin
libgraphite2-dev libgsasl7 libgsasl7-dev libgssrpc4 libharfbuzz-dev libharfbuzz-gobject0 libharfbuzz-icu0 libicu-dev libicu-le-hb-dev libicu-le-hb0 libiculx60 libidn11-dev
libkadm5clnt-mit11 libkadm5srv-mit11 libkdb5-9 libltdl-dev libmail-sendmail-perl libmcrypt-dev libmcrypt4 libntlm0 libntlm0-dev libpcre16-3 libpcre2-16-0 libpcre2-32-0 libpcre2-8-0
libpcre2-dev libpcre2-posix0 libpcre3-dev libpcre32-3 libpcrecpp0v5 libpq-dev libpq5 libpqxx-4.0v5 libpqxx-dev libreadline-dev libsodium23 libssl-dev libsys-hostname-long-perl
libtimedate-perl libtinfo-dev libtool libxml2-dev libxslt1-dev m4 php-all-dev php-dev php-pear php-xml php7.2-cli php7.2-dev php7.2-json php7.2-opcache php7.2-readline php7.2-xml
pkg-config pkg-php-tools po-debconf python3-distutils python3-lib2to3 shtool zlib1g-dev
The following packages will be upgraded:
libcom-err2 libglib2.0-0 libxslt1.1

Re-try installing PHP 7.3

Time to re-try installing PHP with phpbrew:

phpbrew install 7.3

Ack:

It’s a trial and error process:

checking for libzip... not found
configure: error: Please reinstall the libzip distribution

Missing libzip, search for it:

These two seem like two good candidates:

libzip-dev - library for reading, creating, and modifying zip archives (development)
libzip4 - library for reading, creating, and modifying zip archives (runtime)

Install:

sudo apt-get install -y libzip-dev libzip4

Then re-run the install.

phpbrew install 7.3

While it builds, it would be nice to do multi-threaded builds using the -j parameter: https://stackoverflow.com/questions/23814510/multi-threaded-make

Here’s somebody with the same question: Parallel compilation support — automatically detecting number of CPU cores for make -j #477

Source: github.com

So it is possible. The results of my nproc is 12:

Therefore, for my install I’m using somethin 8 processor units, out of 12:

phpbrew install --jobs=8 7.3

Take a note of the default extensions that phpbrew install will add, they might or might not be sufficient to meet Drupal 8 dependencies:

===> phpbrew will now build 7.3.14
You haven't enabled any variants. The default variant will be enabled:
[bcmath, bz2, calendar, cli, ctype, dom, fileinfo, filter, ipc, json, mbregex, mbstring, mhash, pcntl, pcre, pdo, pear, phar, posix, readline, sockets, tokenizer, xml, curl, openssl, zip
]
Please run 'phpbrew variants' for more information.

I have three windows open: 1) The build command; 2) The build log I’m tailing; 3) The htop command.

On the htop command, after speciying the number of cores to use, oh boy, do they pick up some activity:

You can see a variety of gcc compiler threads going on at the top of the process list. This is good, to know that you can in fact make full use of the all the resources available under WSL 2, especially if you do have a powerful processor like I do.

If you’re curious what machine I’m using, it’s the Alienware 15 R4:

It’s from 2017, and you can find a review over here: Alienware 17 R4 (QHD, 120Hz variant) review — one of the best out of the box gaming experience you can get right now.

And here’s the end of the build, looks successful. It roughly took 10–15 minutes:

To switch to the newly installed version of PHP:

phpbrew list
phpbrew use php-7.3.14

Then, to install XDebug:

Regarding Drupal minimal dependencies, currently in my Alpine container I have, versus the installed:

# The extensions installed by phpbrew:bcmath, bz2, calendar, cli, ctype, dom, fileinfo, filter, ipc, json, mbregex, mbstring, mhash, pcntl, pcre, pdo, pear, phar, posix, readline, sockets, tokenizer, xml, curl, openssl, zip# The extensions in my Alpine container, for use with Drupal:${PHP_VERSION}-curl \# Required by Composer and Drupal installer.${PHP_VERSION}-mbstring \# The Drupal installer complains when you don't have opcache enabled.${PHP_VERSION}-opcache \# Required in Drupal core composer.json.${PHP_VERSION}-dom \${PHP_VERSION}-gd \${PHP_VERSION}-json \${PHP_VERSION}-pdo \${PHP_VERSION}-pdo_mysql \${PHP_VERSION}-session \${PHP_VERSION}-simplexml \${PHP_VERSION}-tokenizer \${PHP_VERSION}-xml \

So the extensions that I’m probably missing from the Ubuntu WSL distro are:

  1. pdo_mysql
  2. session
  3. tokenizer
  4. gd
  5. opcache

.

What’s possible with the extensions subcommand is:

.

#1 PDO MySQL extension

.

For the pdo_mysql extension, it clearly seems to be available separate from the base pdo extension:

phpbrew ext install pdo_mysql

But it fails:

And the logs show:

In file included from /home/wsl/.phpbrew/build/php-7.3.14/ext/pdo_mysql/pdo_mysql.c:30:0:
/home/wsl/.phpbrew/build/php-7.3.14/ext/pdo_mysql/php_pdo_mysql_int.h:25:11: fatal error: ext/mysqlnd/mysqlnd.h: No such file or directory

Install for extension mysqlnd itself fails with:

phpbrew ext install mysqlnd
...
checking for pkg-config... /usr/bin/pkg-config
configure: error: Cannot find OpenSSL's <evp.h>

Maybe because I’m missing the OpenSSL system package (and dev dependencies): https://packages.ubuntu.com/bionic/openssl

apt-cache search --names-only '.*openssl.*'

I don’t see a dev package for openssl , so probably just the regular package will do:

sudo apt-get install -y openssl

Then retry installing MySQL extensions:

phpbrew ext install mysqlnd

Still not good enough, same error.

Tip: https://serverfault.com/questions/415458/how-to-solve-configure-error-cannot-find-openssls-evp-h

sudo apt-cache search --names-only '.*libssl.*'
sudo apt-get install -y libssl-dev libssl1.1sudo apt list --installed | grep ssl

Like listed here

phpbrew install --jobs=8 7.3 +default +openssl=/usr

.

# 2 Session

.

phpbrew ext install session

# 3 Tokenizer

.

Tokenizer doesn’t list any external dependencies, so I’m good there: https://www.php.net/manual/en/tokenizer.requirements.php

I only need the PHP module:

phpbrew ext install tokenizer

.

#4 Installing GD and dependencies

.

For installing the GD graphics module, I’ll need the underlying system library as well:

apt-cache search --names-only '.*libgd.*' | grep -i 'graphic'

Installing all GD dependencies:

sudo apt-get install -y libgd3 libgd-dev

Installing the extension:

phpbrew ext install gd

.

# 5 Opcache

.

phpbrew ext install opcache

About those build warnings

.

PHP Warning:  PHP Startup: Invalid library (maybe not a PHP library) 'session.so' in Unknown on line 0
Warning: Module 'tokenizer' already loaded in Unknown on line 0

ini

/home/wsl/.phpbrew/php/php-7.3.14/etc/php.ini

The resulting installed extensions:

I probably installed some duplicate extensions that were bundled with core: https://github.com/phpbrew/phpbrew/issues/532

And that’s probably the source of duplicate loaded extensions:

php --iniConfiguration File (php.ini) Path: /home/wsl/.phpbrew/php/php-7.3.14/etc
Loaded Configuration File: /home/wsl/.phpbrew/php/php-7.3.14/etc/php.ini
Scan for additional .ini files in: /home/wsl/.phpbrew/php/php-7.3.14/var/db
Additional .ini files parsed: /home/wsl/.phpbrew/php/php-7.3.14/var/db/gd.ini,
/home/wsl/.phpbrew/php/php-7.3.14/var/db/opcache.ini,
/home/wsl/.phpbrew/php/php-7.3.14/var/db/session.ini,
/home/wsl/.phpbrew/php/php-7.3.14/var/db/tokenizer.ini,
/home/wsl/.phpbrew/php/php-7.3.14/var/db/xdebug.ini

If I open both:

/home/wsl/.phpbrew/php/php-7.3.14/var/db/session.ini
/home/wsl/.phpbrew/php/php-7.3.14/var/db/tokenizer.ini

And comment out the extensions, the problem is now gone:

.

Whew !

.

The first half of Tuesday I spent scratching my head over why Alpine WSL wouldn’t connect to Docker. The second half was spent entirely on this article. It took so long to go through all of this (~ 4–5 hours), that I had to shutdown my laptop at about midnight and just leave publishing for early the next day, Wednesday.

And this covered just compiling PHP with PHPBrew. I did not get the chance to cover actually using Visual Studio Code with the fresh PHP binaries, I think it would be too much to cover in one article. So I’ll leave that for one of the next ones.

.

Happy Dockerizing, Live From Bushwick

.

Organic, fair-sourced DevOps and Full-Stack things. This is a BYOB Establishment — Bring Your Own hipster Beard.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store