Guidelines for portable software

Last updated on 2024-09-03.

To me, portability is one of the most important attributes of good software. Portable software is software you can build and run with minimal dependencies and in many different kinds of environments, including highly resource-constrained environments. The more portable your software is, the greater the number of people who will be able to use and enjoy it.

Software that is highly portable tends to have other good qualities as well, like being simple in implementation, polite, and respectful.

This list comes from my personal experience with writing and using software, and especially from the frustration I have experienced when building and packaging software for use in environments that are not the typical GNU/Linux desktop. The list written mostly from a Unix perspective, but many of the guidelines apply to software in general, whether or not it's for a Unix-like platform. There are even a few nods to non-Unix environments specifically.

The list begins with the most general principles, and ends in the weird and obscure, things that might be called "tips" more than principles. Now, without further ado...

  1. Write software in C, because that is the most portable language. C is also formally standardized.

  2. For C programs, conform to the ISO standard, preferably an older version like C99 or even C89/C90.

  3. If ISO C does not have the features you need, conform to POSIX.

  4. If you must use Linux-specific features, make it explicit which ones you are using. Make it easy to turn off Linux-specific features at build time.

  5. In general, be reluctant to add new dependencies, and consider transitive dependencies (dependencies of dependencies) when doing so. Be more inclined to implement things yourself.

  6. Don't use bloated languages like C++, Python, Perl, Rust, JavaScript, Go. For simple things that are best done in a scripting language, write shell scripts and test them using a strict POSIX-conforming shell, not Bash. Use standard tools like grep, sed, and awk for text processing.

  7. For more complex scripts, and for even greater portability than that provided by shell scripts, consider Lua. Lua is implemented in ISO standard C, so it runs everywhere. It integrates nicely with C code and is about 100x smaller than the more mainstream scripting languages like Python. When statically linked, it fits into a single binary that's no more than a few hundred kilobytes.

  8. For building your software, use portable Makefiles. Test them using a strict POSIX make, not GNU make. Use GNU make only if you really need specific extra features that it provides, and in that case, name your Makefile "GNUmakefile": GNU make will recognize it, other make implementations will not, and it is an obvious indication that your Makefile requires GNU make.

  9. If your project is too complex for even GNU make, only then should you consider weird complicated build systems like GNU Autotools and cmake. Prefer Autotools over cmake, because Autotools does not introduce build dependencies. If you use cmake then you have added an extra huge build dependency to your project. Another hint: you do not need Autotools just for feature detection. Document your assumptions about what features the platform needs to support, and let the user ensure himself that his system meets the requirements.

  10. Learn which functionality is standardized (for example, as part of ISO C or POSIX), and which are GNU extensions. Don't use GNU extensions. Refer to the POSIX specification and the C99 specification.

  11. Support building and running your software offline as a first-class use case.

  12. Do not depend on the presence of configuration files at runtime. Build sane defaults into the program.

  13. Allow running the program as any user, including root. Do not try to discourage running as root. If you insist on adding stern warning messages about running as root, then please make them easy to disable. Some systems do not have any user accounts other than root.

  14. In shell scripts, do not depend on "sudo". If the script requires privileges, then note that in your documentation. Consider putting privileged functionality in a separate script.

  15. Test with multiple C compilers, not just gcc and clang. Try building with a minimal compiler like chibicc.

  16. Do not assume that glibc is the C library that will be used on Linux systems. Test your software with musl, which is more standards-compliant than glibc.

  17. Do not assume that executables will be dynamically linked. Test your builds with static linking. On Linux, glibc is pretty broken with static linking, so test with musl.

  18. For C and C++ programs, test at compiler optimization level -Os, which may be used when compiling for resource-constrained systems.

  19. When using the GNU C toolchain (GCC and binutils), test your software with the compiler flags -ffunction-sections -fdata-sections and linker flag --gc-sections. This combination of options is sometimes used to discard all unused functions and data definitions from the final executable, making it smaller. It can also expose bugs. Some software does not play nice with it. Make sure yours does, so that people compiling for systems where space is at a premium can take advantage of these options.

  20. For libraries, make it easy to build only a static version of the library. Do not assume that whoever is building the software will always want to build the shared library or even be able to do so. If you are using libtool, then do not use it when building only the static library -- it's totally unnecessary.

  21. Do not hardcode any absolute paths.

  22. Allow native language support (NLS) to be disabled at build time. NLS is often unneeded, and the message files and non-English documentation just end up wasting storage space, which may be highly limited.

  23. Distribute fully-compiled documentation in source code releases. Do not make me have Perl (or whatever else) installed so I can compile a man page from some other format. Ideally, include documentation in plain-text format for maximum portability and usability.

  24. For command line programs, don't generate color output by default. Not all terminals support color. Also, it's just rude.

  25. Don't overthink compression. Stick with gzip and zlib. Distribute source code in .tar.gz format. Or, if you are distributing software mainly for non-Unix platforms, use .zip files. Forget about trendy stuff like zstd (aka "Zstandard", arrogantly named, as it is in no way "standard").

  26. For all files in your source distribution, as well as any files generated by your program whose filename is not provided by the user, use only the most portable character set: uppercase and lowercase letters A-Z a-z of the English alphabet, digits 0-9, the hyphen (-), the underscore (_), and the dot (.).

  27. If anyone could conceivably compile and/or run your software on a DOS-based OS (including MS-DOS itself, DOS clones such as FreeDOS, or versions of Windows from before the year 2001), or could conceivably put your software on a floppy disk, use "8.3" filenames only. Use them for all files in your source distribution and for any files generated by your program at runtime (except when the filename is specified by the user). That is, no more than eight ASCII characters before the extension, and no more than three in the extension. It may sound silly to go out of your way to accomodate such old software, but really, how hard is it to limit yourself to short filenames for your source code?

  28. Do not use file names "aux", "con", "nul", or "prn", with or without a file extension, in any combination of uppercase and lowercase. These are reserved file names on Windows systems.

  29. Stick with ASCII in your source code and documentation. Especially don't use frivolous Unicode characters like fancy checkmarks and quote marks, emojis, and their ilk. The exception is if you need UTF-8 because you are actually writing things in natural languages that ASCII cannot represent.

  30. Do not rely on case-sensitivity of file names, because some commonly used filesystems (most notably, FAT) are not case-sensitive. That is, don't include files in your software that are the same name except for some letters having a different case. You might wonder who would do something that dumb, but in fact, the OpenBSD source code repository is guilty of this.

Good examples of these guidelines:

Programs that violate these guidelines:


33mph.com