Baking can be a pain
I started my journey into learning BitBake, Yocto and making a Linux image for embedded devices. What have I gotten myself into this time?
First things first
What even is all this I am talking about? Well the company I work for currently manages a product called LmP (Linux microPlatform) which is Linux for embedded devices, or just any device really. It is also not very micro anymore, but it is a good base for if you want to get a Linux system on any of a number of devices. It is based off of Yocto.
That is another project that just started once long ago to get as many different devices running a barebones version of Linux and that you could customize. Not unlike getting Debian on there for instance. What they proposed is a way of doing things and then having a reference/implementation of those proposed ways and it is called Poky.
Now how this all is handled is through some tooling called bitbake
. This is what ties everything together.
Imagination
Imagine just putting text in files at certain locations and then invoking a command and magically it spits out an Operating System image. That is in essence what bitbake
does. It is half Python/half Bash shell scripting, it has it's own syntax, you can configure variables. Those variables in turn determine the outcome of whatever it is you are trying to do. The difficult part is that you have to sort of guess what will happen as you cannot really follow it completely yourself. You rely on other so called layers
and inside those layers are recipes
and configurations
.
Recipes
So the core of this all are the recipes. These are what let you (cross-)compile the binaries onto the final image and install them. This took some trial and error. You have to supply a reference to the source, can be a GitHub repo for example. At a certain commit or tag or branch and then also supply a License file to make sure the checksum succeeds and it will be taken into a account for your SBOM type stuff.
Then you want to go through the compile from source for whatever that project has, whether it is a Makefile
or a python setup.py
or what have you. Just go through the steps of it all in order to get the binary you want.
First experience
My first experience was to get the fish
shell, that I love very much, as the shell of choice for my LmP. Eventually my bitbake
recipe looked like this:
DESCRIPTION = "fish shell"
DEPENDS = "ncurses pcre2"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://COPYING;md5=fbac53dd4da483010e69e10c2221f02d"
PV="3.6.1"
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}-${PV}:"
SRCREV = "f39bc9317d252ce2229ebc11a8a6fe2d98abf430"
SRC_URI = "git://github.com/fish-shell/fish-shell;protocol=https;branch=master"
S = "${WORKDIR}/git"
inherit cmake
This was certainly not the exact first version, but since that project is very well setup and follows a lot of standard, somewhat agreed upon, flows then you just have to state where the source will be located and then you inherit cmake
and it will do the rest for you. Very nice.
Now came a much harder task. I wanted to get AstroNvim in there. I mean just look at the dependencies there.
Fonts were not a problem, as were the other tooling binaries.
No what was difficult was NeoVim source itself. It relied on Lua. To install Lua unto LmP is a nightmare. Well not to have it as a runtime during your final image, that part is easy. No, what happens during the bitbake
stage is you cross compile things.
One of the steps of NeoVim during compilation is to use Lua to get a shared object. That is not a problem, running that is though. So image having a x86
host system cross compiling a aarch64
Lua shared object. No worries. Now imagine running that cross compiled Lua shared object as part of the rest of the cross compilation of NeoVim source code. That is where it gets tricky as the cross compiled binary cannot run on the host system, but if you use a host system binary then it cannot be used in the thereupon following compilation steps.
What to do?
Workaround
My workaround was simple. Install qemu
use it with docker
to easily get a throwaway system with a specific architecture. Then compile neovim
on there and package it as a .deb
. Then have that specific .deb
be the source for bitbake
. Done.
This is what the recipe for neovim looks like now:
SUMMARY = "Docker cross compiled Neovim"
DESCRIPTION = "neovim"
DEPENDS = "dpkg-native ldconfig-native"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
PV = "0.10.0-dev"
SRC_URI = "file://nvim-linux64.deb;unpack=0 \
file://nvim-linux64_x86-64.deb;unpack=0 \
"
FILES:${PN} += "/usr/share/nvim/runtime ${libdir}"
do_install:append:x86-64() {
${STAGING_BINDIR_NATIVE}/dpkg --instdir=${D}/ \
--force-architecture \
--admindir=${STAGING_DIR}/var/lib/dpkg/ \
-i ${WORKDIR}/nvim-linux64_x86-64.deb
rm -r ${D}/usr/share/icons
rm -r ${D}/usr/share/applications
}
do_install:append:aarch64() {
${STAGING_BINDIR_NATIVE}/dpkg --instdir=${D}/ \
--force-architecture \
--admindir=${STAGING_DIR}/var/lib/dpkg/ \
-i ${WORKDIR}/nvim-linux64.deb
rm -r ${D}/usr/share/icons
rm -r ${D}/usr/share/applications
}
For now this is a break in the journey. Next up I will want to introduce the wasm runtime for docker and see if I can get it to work on there.