Written by Tobias Hieta— March 27th 2019
We’ve been working on some big changes with regards to how we build Plex Media Server. Since we know many of you are interested in what goes on behind the scenes, we decided this was a great opportunity to lift the curtain a bit, get geeky, and talk about compilers and toolchains.
The Plex Media Server is a huge C++ codebase with many dependencies, which ships on many different devices and platforms. Here are a few stats:
- 1122: number of source files in the repository
- 292764: number of lines in those source files
- 50: number of third party dependencies needed to build the server
- 6: number of platforms we ship on (Linux, Windows, macOS, Android, iOS, FreeBSD)
- 19: number of configurations we build
- 1 hour 15 minutes: roughly how long it takes to build all configurations for a release
The C++ code base is around 11 years old. The first commit by Elan was on the September 20, 2008. Back then it was only built for macOS (well, “OS X”, back then) and did only a fraction of what the server can do today.
As the server grew, we expanded not only the functionality, but also the number of platforms we built for and ran on. Windows support was the first one that was added after macOS and it brought in support for Visual Studio. After this, Linux support was added and that opened the door for running the media server on NAS devices, which led to ARM support. Most NAS devices run on the ARM CPUs because of the lower cost and power needs, which in turn requires a ARM toolchain.
A few years back, we also expanded the server’s role to actually run inside our mobile clients as well. If you are running the Plex client on your iOS or Android device, you actually have a (very stripped down) version of PMS running inside there to help out with sync and offline features.
While all of that was great for expanding Plex to be available on more platforms, it led to a lot of complexity. Prior to our Clang conversion we built the server with 20 different toolchains and compilers. This quickly became unmaintainable:
- Different versions of the same compiler behave differently because of bug fixes and bugs.
- Upgrading to a modern C++ was almost impossible, since our oldest compilers had a lot of quirks and were a real pain to upgrade. In some cases the upstream toolchains never changed.
- Moving to other build slaves was really hard because almost all of the slaves had morphed into very delicate snowflakes, with hand-crafted configurations.
- In order to build for ARM platforms we resorted to a tool called scratchbox that ran the compiler and build system under a emulator. This made the configuration pretty straightforward but very slow and it was never updated for new targets like arm64 which hindered us from moving forward with new NAS devices based on that CPU.
- If we made a single change in any of the dependencies the system required a full rebuild of all dependencies for all platforms, making the compile process take hours.
On top of these configurations we used a python script to build all our dependencies — and even this script had morphed from its initial purpose and scope, which lead to a lot of problems when trying to adopt new platforms and technologies. We knew that replacing this script was the key to move to a more modern toolchain and unlock exciting things for the server team.
Enter the barbarian
Instead of trying to rewrite our script and add to our maintenance burden we started to look around for dependency managers for C/C++. Unfortunately there isn’t a standard one around since the language was built way before the likes of NPM, PIP, and gems. After evaluating several tools we found one that could fit us.
Conan is a C/C++ package manager written in Python that can handle both source and binary dependencies. After some prototyping and concepts, we went ahead and started our conversion to use Conan for our dependencies. With Conan we are able to keep both our dependencies and toolchain in sync, in fact our whole toolchain is a Conan package that we can update the same way we update everything else.
Conan also supported cross-compiles fully, this made our build process faster and a lot more straightforward and we could get rid of CPU emulation and scratchbox.
If you would like to know more about how Plex integrates and uses Conan one of our engineers held a talk on the subject and it’s available on YouTube.
Enter the dragon
Not only did we need a framework for building our dependencies and other tools, we also needed a unified toolchain. We knew that we could never get away from all the issues we had unless we unified our C++ compiler. The choice here was actually much easier and straightforward than choosing our package manager. iOS, Android, and macOS have all selected LLVM Clang as their only compilers. So whatever we chose, we would have to use Clang on some platforms regardless.
Luckily, Clang turns out to be a great choice for Plex. With GCC you need to build a specific toolchain per platform you target. So if we wanted to target macOS, Linux, and Android, we would need to build three different toolchains in order to get that to work. Clang on the other hand, is fully cross-compiling native, meaning you can target any platform from any platform, which drastically reduces the number of toolchains we need to work with. Clang is also very fast for building C++ code — we’ve been able to cut the time it takes to build the Server for some of the platforms by half. Clang generates very fast code as well, which can specifically be seen on lower powered devices like ARM and ARM64 devices, where the LLVM optimization pipeline is very advanced.
What does this mean for me as customer?
Starting with version 1.15.0, we now build Plex Media Server for all platforms except Windows with Clang (and our intention is to move the Windows build to Clang soon as well). In practical terms right now, this generally won’t have a huge impact at this point for you yet. The Server download is a bit smaller on some platforms, some performance will most likely be better on certain platforms, and we have support for a bunch of new ARM64 platforms.
For the future, it means that the server team will be able to bring you new features faster. In the near future we will upgrade our C++ standard to C++14 which brings a huge number of new features for our developers. The developers also have access to much more advanced tooling around building the server (for example Address and Memory Sanitizers). All this should lead to an even better, faster, and more stable Plex Media Server in the future.
With this release, we put months of hard work behind us and it feels fantastic to be able to bring these changes to our aging toolchain and to set the server up for another 11 years of development and customer-focused features.