How the OSMC build system works
The OSMC build system is used to build OSMC packages which are present on the target system. It is sometimes also used to build other parts of OSMC such as the installers or tools that are used to further develop OSMC. The main focus however is to assist the building and packaging of Debian (Deb) based packages which are delivered via APT.
chroot() isolation
OSMC largely uses chroot() based isolation to build the various components of OSMC. This is used in the preparation of toolchains, filesystems, and the compilation of OSMC packages. There are several advantages to this approach, namely:
- The host system which is used to build OSMC is kept clean, and no invasive changes are made.
- The toolchain is easily updatable and replaceable
- Replicable builds: we can guarantee a clean build environment with only the dependencies that we request. This avoids contamination of the build environment and can avoid anomalies or inconsistencies when building binaries which rely on autoconf or cmake.
- Theoretically portable to other GNU systems: some work may be done on this in the future, but as an entire userland for building is shipped, there are very few requirements on the host.
- Revertible – should you make a mistake in your build environment, it is very easy to revert it to the stock OSMC state.
In order to compile foreign (non-native on build host) architectures, we use QEMU userspace emulation. This incurs a performance overhead, but we believe the benefits outweigh the cons. The performance overhead is also heavily negated by using ccache to only build what has changed.
There are however, some issues with QEMU userspace emulation. QEMU does not reliably handle pthreads, which can cause problems with some applications such as Java and Git. We can avoid these issues by running some utilities natively. For example, Git clones are run on the target before entering the toolchain.
For performance benefit, you can build on a device with the same architecture. The build system will install QEMU, but it will not be used unless necessary. This allows any OSMC device to build OSMC packages, and if the target architecture is the same as the architecture the host is running, then emulation will not be used, which will yield better performance.
Build scripts
There are a series of build scripts in the OSMC repository which expose functions that can be used by other packages and build targets throughout the repository.
The most important scripts are:
- scripts/common.sh: this houses a lot of common functionality which is used for building packages, toolchains, filesystems and the target installer
- package/common.sh: this houses common functions used for building OSMC Debian packages.
Improving the build system
There is some technical debt in this area, and we would like to improve the build system. Improvements in this area will reduce the amount of time taken to build new packages and iterate. If you are interested in working on this, then you should get in touch.
Frequently asked questions
Q: Why do you not just cross-compile?
A: There are numerous reasons why we choose not to cross-compile.
- Cross-compiling introduces maintenance issues with patches. Many downstream patches would need to be developed, and maintained, to configure packages to build with a cross-compiler.
- Toolchains should match the ones shipped in the upstream distribution. It’s possible to build external toolchains which can be used, but this also introduces a maintenance burden. We would then need to track upstream libc changes. The current, shipped, cross-compilers in Debian lag between the native GCC release.
- We would lose ability to build packages for all systems – presently, it is possible to build an OSMC package for any OSMC device on any supported build environment. If we introduced cross-compilation, then we would lose some of this flexibility. At best – we would need to ship several different toolchains for each device, which is burdensome.
- Time taken is greater than time saved. The time taken to make these changes would be greater than the time saved by building natively.
- Ideally, we would still need a chroot() like approach. We would still need a chroot() approach to keep the target system clean and make it easy to revert any negative changes in your build environment.
Q: Why not use something like Docker?
A: As with cross-compiling, Docker limits the environment which our build system can run on. There would also be some potential issues with recursive builds. Further, we do not know if enough functionality (the device mapper for example) is exposed in the container. At this time, this would introduce unnecessary dependencies in to the build environment without any gain. We may revisit this in the future.