Custom Linux build for Raspberry PI with Buildroot

It seems working in embedded means you’ll, sooner or later, use a Raspberry PI. At my current work place we use Raspberry PIs, connected to the hardware we’re developing for, as Github CI runners. Anytime we want to add a new runner we need to flash an SD card and manually install all the dependencies and tools.

What I’m looking for is an ability to generate Raspberry PI images, pre-installed with all the necessary (+ some of my favourite) tools. That can be achieved thanks to projects like Yocto or Buildroot. This post relates to the latter, which is considered to be simpler and easier to get started with.

After some experimentation, I managed to add the nnn - my favourite terminal file browser - to the build process.

First I’ve created the Makefile that tells Buildroot how to build nnn:

################################################################################
#
# nnn
#
################################################################################

NNN_VERSION = v4.6
# Buildroot guesses the NNN_SITE_METHOD from NNN_SITE contents.
NNN_SITE = https://github.com/jarun/nnn/releases/download/$(NNN_VERSION)

# nnn's Makefile uses $(PKG_CONFIG)
NNN_DEPENDENCIES = host-pkgconf
NNN_DEPENDENCIES += musl-fts
NNN_DEPENDENCIES += readline
NNN_DEPENDENCIES += ncurses

define NNN_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D)
endef

define NNN_INSTALL_TARGET_CMDS
	$(TARGET_MAKE_ENV) $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D) DESTDIR="$(TARGET_DIR)" install
endef

$(eval $(generic-package))

This file has been saved as package/nnn/nnn.mk.

The first paragraph describes the version of the package and where to find it. I’m using a release URL but you could use a Github repository URL - check github helper in the Buildroot manual.

The NNN_DEPENDENCIES refer to packages already present in the buildroot/package directory.

Buildroot uses Kconfig for project configuration. That system needs to be aware of the the new package and its dependencies:

config BR2_PACKAGE_NNN
	bool "nnn"
	select BR2_TOOLCHAIN_BUILDROOT_WCHAR
	select BR2_USE_WCHAR
	select BR2_PACKAGE_READLINE
	select BR2_PACKAGE_NCURSES
	select BR2_PACKAGE_NCURSES_WCHAR
	select BR2_PACKAGE_MUSL_FTS
	help
		https://github.com/jarun/nnn

This file has been saved as package/nnn/Config.in.

Notice I’m using select instruction here. Maybe depends on would be more fitting here. Certain BR2_PACKAGE_ options refer to dependencies specified in the nnn.mk, other enable the wide character (a char type that might be bigger than 8-bit) support.

The package/nnn/Config.in has to be included in the package/Config.in - a file which sources each package’s Config.in. It also groups packages in categories. I have appended source "package/nnn/Config.in" in the Miscellaneous category.

What’s left to do is to use a predefined config for Raspberry PI 4 and to build nnn.

make raspberrypi4_defconfig
make nnn
/home/user/buildroot/output/host/lib/gcc/arm-buildroot-linux-uclibcgnueabihf/11.3.0/../../../../arm-buildroot-linux-uclibcgnueabihf/bin/ld: /tmp/ccodl1K7.o: in function `du_thread':
nnn.c:(.text+0x1768): undefined reference to `fts_open'
/home/user/buildroot/output/host/lib/gcc/arm-buildroot-linux-uclibcgnueabihf/11.3.0/../../../../arm-buildroot-linux-uclibcgnueabihf/bin/ld: nnn.c:(.text+0x178c): undefined reference to `fts_read'
/home/user/buildroot/output/host/lib/gcc/arm-buildroot-linux-uclibcgnueabihf/11.3.0/../../../../arm-buildroot-linux-uclibcgnueabihf/bin/ld: nnn.c:(.text+0x17bc): undefined reference to `fts_close'

Heartbreaking. This happens because nnn actually doesn’t only use its Makefile to build. It references a bash script in its Makefile, which installs musl-fts and links to it statically.

In order to fix it, I have modified the nnn’s Makefile such that it links to the musl-fts dynamically. I have saved this change to a patch file, with git diff > 0001_lfts.patch.

diff --git a/Makefile b/Makefile
index 79042c33..14845237 100644
--- a/Makefile
+++ b/Makefile
@@ -151,7 +151,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wshadow
 CFLAGS += $(CFLAGS_OPTIMIZATION)
 CFLAGS += $(CFLAGS_CURSES)
 
-LDLIBS += $(LDLIBS_CURSES) -lpthread
+LDLIBS += $(LDLIBS_CURSES) -lpthread -lfts
 
 # static compilation needs libgpm development package
 ifeq ($(strip $(O_STATIC)),1)

This file has been saved as package/nnn/0001_lfts.patch. Buildroot picks up patches automatically. This time the build step should succeed.

make nnn-dirclean
make nnn

You’ll actually see Buildroot picking up that patch and applying it.

Running make, without any target, makes Buildroot build an image for Raspberry PI 4, with nnn pre-installed.

I might continue by adding my code editor of choice, Kakoune.