[EXPERIMENT] LibreELEC → Yocto/BitBake Build Pipeline Migration

  • Hey all,

    I wanted to share a personal experiment I've been running: an exploratory migration of the LibreELEC build pipeline to a Yocto/BitBake-based system.

    Repository: https://github.com/adam-aph/LibreELEC.new-30-March-2026.git

    The branch is based on the master libreelec.tv cut from March 30. This started as a private experiment - my excuse to work with Claude Code on a larger, real-world codebase - so temper expectations accordingly.


    🔧 WHY BOTHER?

    For those curious whether there's any genuine technical merit here (beyond "Yocto is what the cool kids use"), here's what actually changes for a codebase like LibreELEC:

    • Cryptographic task signatures replace fragile .stamp_* files
      Changing an ffmpeg configure option, patch, or dependency in package.mk triggers precisely the affected downstream packages (Kodi, gstreamer plugins, etc.) - no more volunteers reaching for rm -rf build.* to restore correctness, and no more PKG_NEED_UNPACK hacks.
    • sstate-cache gives reliable task-level binary reuse across machines and CI
      Tweaking one kernel config or driver only rebuilds the delta, instead of re-executing large chunks of the 5000+ recipe tree that the current linear stamp logic often invalidates unnecessarily.
    • Task-level DAG dependency modeling eliminates build races
      PKG_DEPENDS_TARGET + make -jN has inherent race conditions when multiple packages populate sysroot headers/libraries concurrently. BitBake replaces shell sequencing with mathematically enforced ordering, down to do_populate_sysroot vs do_configure.
    • Isolated native (-native) sysroots hermetically seal the toolchain and host tools
      cmake, pkg-config, sed, gawk, Python - all of it. This removes the occasional host contamination that leaks through the current packages/toolchain/ wrapper across varied volunteer distros (Arch, Ubuntu, NixOS, etc.).
    • Layer + bbappend architecture cleanly separates BSP/vendor specifics
      Device trees, kernel patches (RPi, Rockchip, Amlogic) and conditional logic move out of core packages/linux/ and packages/mesa/ into isolated vendor layers. Dropping an old SoC becomes a layer deletion rather than untangling shell if [ "$PROJECT" = ... ] blocks scattered across hundreds of package.mk files.
    • PACKAGECONFIG provides declarative, graph-aware feature toggles
      Global flags like pulseaudio, lirc, various codecs are automatically wired with their dependencies and configure flags across recipes - replacing the current manual threading of SUPPORT_*=yes through per-package shell conditionals in make_target() and options files.
    • module.bbclass and kernel-devsrc formalize out-of-tree driver builds
      Correct kernel tree preparation, KDIR, headers, and module packaging/stripping for WiFi dongles and proprietary drivers - instead of fragile sequencing scripts in packages/linux-drivers/ that depend on exact prior kernel unpack/configure timing.
    • Reproducible builds become an auditable property, not volunteer discipline
      sstate + the dependency graph make it easier to verify bit-identical images across machines without relying on stamp + shell environment assumptions.

    These are codebase-specific wins. That said - for a small volunteer team where the shell system is already well-understood, highly optimized for Kodi-centric embedded use, and operationally successful, the migration cost is massive. The strongest drivers are incremental correctness, host isolation, and long-term BSP maintainability as hardware targets grow.


    ⚠️ CURRENT STATE & CAVEATS

    • Converted 42 arch-variants across 28 PROJECT/DEVICE combinations.
    • Clean build achieved: libreelec-generic-x86-64 only
    • Not tested on any physical device (it will very likely fail)
    • Getting to a clean x86_64 build took roughly 100 hours working with Claude Code - this is not a weekend project


    🤔 IS THERE INTEREST?

    Honestly, I'm not sure this brings value for you - maybe it does. I'm sharing it in case someone sees potential here, or wants to pick it up.

    Realistically, continuing this properly would mean sponsored API tokens, access to physical hardware for testing, and significant time investment. That's essentially a part-time (or full-time) job, which isn't where I am right now.

    That said, I'm happy to discuss it. If you see value in this direction, think it's worth pursuing, or have thoughts on what "continued" would even look like in practice - feel free to DM me. No promises, but I'm not closing the door either.

    Cheers!


    Build:

    Result:

    Code
     303M May 22 14:30 LibreELEC-Generic.x86_64-12.0.img.gz
       1M May 22 14:30 LibreELEC-Generic.x86_64-12.0.img.gz.sha256
       1M May 22 14:30 LibreELEC-Generic.x86_64-12.0.manifest
     275M May 22 14:30 LibreELEC-Generic.x86_64-12.0.squashfs
       1M May 22 14:30 LibreELEC-Generic.x86_64-12.0.testdata.json
     549M May 22 14:30 LibreELEC-Generic.x86_64-12.0.wic
     303M May 22 14:30 LibreELEC-Generic.x86_64-12.0.wic.gz
       1M May 22 14:30 libreelec-image.env

    Complete Target Matrix - 42 arch-variants across 28 PROJECT/DEVICE combinations:

    PROJECTDEVICEARCHKERNELBOOTLOADERDISPLAYSERVERGRAPHIC_DRIVERSpatch_archNotes
    GenericGenericx86_64defaultsyslinuxnocrocus i915 iris r300 r600 radeonsi vmware virtiox86GBM/DRM; OpenGL=mesa
    GenericGeneric-legacyx86_64defaultsyslinuxx11crocus i915 iris radeonsi nvidia vmware virtiox86X11/Fluxbox; OpenGL=mesa
    Genericgbmx86_64defaultsyslinuxnocrocus i915 iris r300 r600 radeonsi vmware virtiox86GBM/DRM (same as Generic)
    Genericwaylandx86_64defaultsyslinuxwlcrocus i915 iris nvidia-ng r300 r600 radeonsi vmware virtiox86Wayland/Sway; OpenGLES=mesa
    Genericx11x86_64defaultsyslinuxx11crocus i915 iris radeonsi nvidia vmware virtiox86X11/Fluxbox; OpenGL=mesa
    RPiRPiarmraspberrypibcm2835-bootloadernovc4armRPi 1/Zero; arm1176jzf-s; KERNEL_TARGET=zImage
    RPiRPi2armraspberrypibcm2835-bootloadernovc4armRPi 2/3; cortex-a7; KERNEL_TARGET=zImage
    RPiRPi4aarch64raspberrypibcm2835-bootloadernovc4aarch64cortex-a72; KERNEL_TARGET=Image
    RPiRPi4armraspberrypibcm2835-bootloadernovc4aarch64arm32 on arm64 kernel; cortex-a53; TARGET_KERNEL_PATCH_ARCH=aarch64
    RPiRPi5aarch64raspberrypibcm2835-bootloadernovc4aarch64cortex-a76; KERNEL_TARGET=Image
    RPiRPi5armraspberrypibcm2835-bootloadernovc4aarch64arm32 on arm64 kernel; cortex-a53; TARGET_KERNEL_PATCH_ARCH=aarch64
    AmlogicAMLGXaarch64amlogicu-bootnolima panfrostaarch64cortex-a53; OPENGLES=mesa
    AmlogicAMLGXarmamlogicu-bootnolima panfrostaarch64arm32 on arm64 kernel; cortex-a53
    RockchipRK3288armdefaultu-bootnopanfrostarmcortex-a17; arm-only SoC
    RockchipRK3328aarch64defaultu-bootnolimaaarch64cortex-a53; ATF
    RockchipRK3328armdefaultu-bootnolimaaarch64arm32 on arm64 kernel; cortex-a53
    RockchipRK3399aarch64defaultu-bootnopanfrostaarch64cortex-a72.cortex-a53; ATF
    RockchipRK3399armdefaultu-bootnopanfrostaarch64arm32 on arm64 kernel; cortex-a72.cortex-a53
    RockchipRK356Xaarch64rockchipu-bootnopanfrostaarch64cortex-a55; LINUX=rockchip
    RockchipRK356Xarmrockchipu-bootnopanfrostaarch64arm32 on arm64 kernel; cortex-a55
    RockchipRK3576aarch64rockchipu-bootnopanfrostaarch64cortex-a72.cortex-a53; LINUX=rockchip
    RockchipRK3576armrockchipu-bootnopanfrostaarch64arm32 on arm64 kernel
    RockchipRK3588aarch64rockchipu-bootnopanfrostaarch64cortex-a76.cortex-a55; LINUX=rockchip
    RockchipRK3588armrockchipu-bootnopanfrostaarch64arm32 on arm64 kernel
    AllwinnerA64aarch64defaultu-bootno(mesa via OPENGLES)aarch64cortex-a53; ATF+crust
    AllwinnerA64armdefaultu-bootno(mesa via OPENGLES)aarch64arm32 on arm64 kernel; cortex-a53
    AllwinnerH2-plusarmdefaultu-bootno(mesa via OPENGLES)armcortex-a7; arm-only; UBOOT_FIRMWARE=crust
    AllwinnerH3armdefaultu-bootno(mesa via OPENGLES)armcortex-a7; arm-only; UBOOT_FIRMWARE=crust
    AllwinnerH5aarch64defaultu-bootno(mesa via OPENGLES)aarch64cortex-a53; ATF+crust
    AllwinnerH5armdefaultu-bootno(mesa via OPENGLES)aarch64arm32 on arm64 kernel; cortex-a53
    AllwinnerH6aarch64defaultu-bootno(mesa via OPENGLES)aarch64cortex-a53; ATF+crust (sun50i_h6)
    AllwinnerH6armdefaultu-bootno(mesa via OPENGLES)aarch64arm32 on arm64 kernel; cortex-a53
    AllwinnerR40armdefaultu-bootno(mesa via OPENGLES)armcortex-a7; arm-only; no ATF
    NXPiMX6armdefaultu-bootnoetnavivarmcortex-a9; arm-only
    NXPiMX8aarch64defaultu-bootnoetnavivaarch64cortex-a53; ATF+firmware-imx
    NXPiMX8armdefaultu-bootnoetnavivaarch64arm32 on arm64 kernel; cortex-a53
    QualcommDragonboardaarch64defaultu-bootnofreedrenoaarch64cortex-a53; mkbootimg host tool
    QualcommDragonboardarmdefaultu-bootnofreedrenoaarch64arm32 on arm64 kernel; cortex-a53
    ARMARMv7armdefaultu-bootnolimaarmGeneric ARMv7 reference; cortex-a8
    ARMARMv8aarch64defaultu-bootnolimaaarch64Generic ARMv8 reference; cortex-a53
    ARMARMv8armdefaultu-bootnolimaaarch64Generic ARMv8 reference; arm32 on arm64 kernel
    SamsungExynosarmsamsungu-bootnopanfrostarmcortex-a15.cortex-a7; LINUX=samsung
  • What exactly is the point of this project? To build a distro with Kodi as main app made with main inportant components from LE?

    You obviously can't call it LE because it is not even closely LE :/

  • The point of the project was simple: it started as a personal exercise to test the limits of Claude Code on a large, complex codebase. I chose LibreELEC precisely because its current build system is mature and contains over 5,000 recipes, making it an excellent real-world stress test.

    To answer your question: No, the goal wasn't to fork the distro or create a 'fake' LibreELEC. The goal was to see what LibreELEC’s exact package set, configurations, and architecture would look like if they were driven by a modern, industry-standard build system (Yocto/BitBake) instead of the current custom shell-and-stamp system.

    As for the name, calling the repository 'LibreELEC.new' was just a standard way to label a structural experiment on a LibreELEC codebase. It’s a proof-of-concept, not a rebranding campaign.

    Whether migrating the build pipeline to Yocto brings actual value to the LE community - via better host isolation, declarative feature toggles, or cleaner BSP layers - isn't for me to decide. I listed the theoretical technical benefits in my post, but it is entirely up to the core team and community to decide if those benefits are worth the massive migration cost.

    I’m just sharing the data and the code from the experiment in case anyone finds it useful.

  • Still don't get the point. Yocto is already solid build system and there is no need to port LE packages to it. At least not the majority of them (only media related).

    Looks like you choose the wrong way here to start with LE and move to yocto. You should start with yocto and add only required missing things to get final result.

    But this is just my opinion.

  • I think there is a fundamental misunderstanding here of what Yocto actually is.

    Yocto is not a pre-built operating system or a standard Linux distribution where you just 'add a few missing media packages' on top. Yocto is an empty build engine framework. It provides the toolchain and the build mechanics, but it contains zero software packages of its own.

    To build any functional operating system with Yocto - let alone one as specific as LibreELEC - you have to write or port BitBake recipes for every single component, from systemd and the kernel configuration down to Kodi and its dependencies.

    Suggesting to 'start with Yocto and add only missing things' implies Yocto provides the rest out of the box. It doesn't. Porting the LibreELEC package set into Yocto recipes is exactly how a Yocto-based distribution is built from scratch.

    My experiment was a proof-of-concept showing how LibreELEC’s existing infrastructure could be translated into that framework. It wasn't about changing the software inside the distro - it was about changing the engine that compiles it.

  • Honestly, I'm not sure this brings value for you - maybe it does

    Several of the project staff have experience with Yocto/OE for other projects and work requirements and it has inspired changes to our own in the past. It does benefit from having far greater build capabilities, but it's also quite a bit more complex and is thus less accessible to the type of hobbyist developers we attract (for our hobbyist distro) and that's long recognised as something important for the project and the ecosystem of forks that surround us. In that sense it's an interesting exercise to see, and kudos for getting things as far as bootable images, but I think we're unlikely to change the buildsystem we use.

    Things I'd like to see done (which we mostly know how to do, but time..) is moving to a reusable toolchain as this would massively reduce the total time required for building nightly images with CI and would probably allow us to test-build PR's. The uboot_helper script could also be expanded to more of a build-matrix function; e.g. https://github.com/LibreELEC/LibreELEC.tv/pull/6427 which has bit-rotted a little as people's lives changed and became less active. We are laggards at adopting things largely due to being a bunch of hobbyists and the desire to not fcuk up something that basically works :)

  • Thanks for the thoughtful perspective - keeping the barrier to entry low for hobbyist contributors is a completely valid reason to avoid Yocto's steep learning curve. Since this was just a personal playground to stress-test Claude Code, I’m happy with how it turned out. The repo is there if anyone ever wants to see how the toolchain isolation or dependency mapping looked.