How to support Linux environments as a software developer/publisher.
How to limit your support cost: Limit, constrain and focus.
As a software developer or publisher, you may have at some point considered whether it is worth expanding your offerings to cover Linux environments. Compared to as recently as 5 years ago it’s much easier than it used to be. Between modern game engines such as Unity and Unreal supporting Linux natively, to web-application based frameworks such as Electron keeping you to a single code base, and cross-platform frameworks such as .Net Core, Mono and Java there are lots of different ways to target Linux while sharing the overwhelming majority of your code base.
While nice in theory, anyone who has ever actually done this will tell you that it’s rarely so simple. Just ask the development team behind Planetary Annihilation who shipped for Windows, Mac and Linux:
And I can’t say I’m surprised. At time of writing Distrowatch lists 306 different Linux distributions, 165 of which are categorised as ‘Desktop’ distributions. There’s no feasible way to target and test all of them. Ignoring issues like different library and frameworks in use, it’s just not possible. Even picking the 10 most popular distributions won’t work.
My company has been shipping for and supporting Linux alongside Windows for over 6 years successfully. Linux actually accounts for a larger install base than Windows at this point.
So I’m going to talk briefly about some of the steps developers and publishers can take to minimise their support footprint and deliver a better user experience. This is more of a focus on Desktop Linux for games and applications rather than servers, but a lot of the same principles apply.
Target no more than two distributions, no more than two versions, and only one architecture.
Pick two popular distributions. On the desktop this basically means Ubuntu and Mint (which is actually a Ubuntu derivative to make things easier). You should only support a single platform, that being x86_64. Don’t even think about 32 bit x86 or ARM support. If you’re targeting corporate customers then you may need to include a Red-Hat-like distribution such as CentOS or RHEL Desktop.
From each distribution, you take the most recent long-term support version (or second-most recent major version in the absence of a separate distinct LTS version), and the most recent absolute version. At time of writing for Ubuntu for example this means 18.04 LTS and 18.10, for Mint this means 18.3 and 19.1.
Build and test your application(s) against those versions, using only their out-of-the-box configuration save for applying any pending updates.
The sole exception to this is possibly allowing for the installation of the proprietary nVidia drivers for games — but ideally you should try and target the open-source Nouveau drivers. If you cannot then your application must detect this scenario and gracefully exit with an explanation to the user. It may be necessary to use a pre-start script to accommodate this. Use of AMD hardware is generally encouraged on Linux for graphics owing to their commitment to standards compliant open-source drivers.
If you find yourself in a situation where your application only works with a newer version of a library/driver than the target distribution version ships with — then you should straight up abandon support for that version, but be super communicative about this. Then in the future once said distribution supports that version, promptly re-visit support. Do not encourage users to manually upgrade their system or use pre-release or non-official libraries (such as PPA repositories on Ubuntu)
If it doesn’t work with stock, it doesn’t work — full stop.
Be firm, but fair with your users.
At launch, you should make it super clear to users that the following conditions to Linux support apply:
- Only those distributions and versions are supported at all and that you do not intend to ever support extra distributions (even if this may change later)
- Only the out-of-the-box libraries and drivers provided by the distribution maintainers are supported. (Save for the note above)
- Emails, support tickets and bugs for non-supported configurations will be rejected and ignored.
On that last point, if you wish to have any kind of email or ticketing based support, you should implement a semi-automated self-help system first that prompts the user to provide their distribution name and version to pre-filter out unsupported configurations before a human ever has to set eyes on them. Aggressively close and reject any tickets that aren’t from a supported configuration.
If you can statically link it, you should
There’s no particular reason to concern yourself with the file size of your executable, so any libraries that you can statically link you should, even if your target OS includes them as standard or in the repositories. This protects you against future OS updates breaking your application because of an incompatibility in a new version of a shared library that your code depends on. It also helps make your final product as portable as possible across different distributions should you decide to broaden your support or if brave users should decide to try their luck.
Take a moment to review the licences for any libraries you *do* embed, while most will be LGPL or similar, any that are straight up GPL will have licencing implications. Generally GPL libraries will not ship statically linkable versions by default and they are mercifully very few.
Implement a package repository and serve updates from there.
On Ubuntu, Mint and other Debian-likes this requires nothing more than a standard webserver that can serve static files. Building .deb packages is fairly straightforward from a binary plus data files.
Using this approach allows you to ensure that your packages contain information on any prerequisites and dependencies (only those that you absolutely cannot embed in your app) but also means your application is kept up to date whenever the user updates the rest of their system. Then like you do with Windows and Mac OS you continue testing against major updates and releasing updates/revisions as applicable.
This approach is used by a number of major players, including Discord, Spotify, Microsoft, VALVe and Google. It also spares you from having to implement your own update mechanism — you simply push revised packages to your repository and you’re done.