How to create your first recipe and enable auto-start using systemd

Introduction

The Yocto Project consists of recipes, dependencies and metadata to instruct the build system how to build software packages for installation into a target Embedded operating system.

This tutorial will guide you through generating a basic recipe to add your application to an existing Yocto project build, including the configuration needed to launch a systemd service. Specifically we will launch our new application as a simple service which runs one time at boot.

Prerequisites

Step 1: Generate the initial build and verify that it boots

This is a summary of the steps outlined here.

export BRANCH="warrior"
mkdir mender-qemu && cd mender-qemu
repo init -u https://github.com/mendersoftware/meta-mender-community \
           -m meta-mender-qemu/scripts/manifest-qemu.xml \
           -b ${BRANCH}
repo sync
source setup-environment qemu
MACHINE=qemux86-64 bitbake core-image-base
 ../sources/meta-mender/meta-mender-qemu/scripts/mender-qemu core-image-base

Step 2: Create a custom layer

We will create a custom layer to hold our new recipe. See this tutorial for more details on custom layers.

Please note that the tutorials on this site will re-use this layer and if you have already created this structure by following another tutorial you can skip this step.

Create a new layer called meta-stargazer using the bitbake-layers helper application:

bitbake-layers create-layer ../sources/meta-stargazer

This will create a basic structure in meta-stargazer directory:

../sources/meta-stargazer/
├── conf
│   └── layer.conf
├── COPYING.MIT
├── README
└── recipes-example
    └── example
        └── example_0.1.bb

3 directories, 4 files

Include the layer in our Yocto Project environment:

bitbake-layers add-layer ../sources/meta-stargazer

Step 3: Create a new recipe

We will use the recipetool utility provided by Yocto to generate a new recipe for the GNU Hello program. This package is setup for building properly with autoconf and automake, and Yocto can automatically generate recipes that use these tools.

mkdir ../sources/meta-stargazer/recipes-example/gnuhello
recipetool create https://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz \
      -o ../sources/meta-stargazer/recipes-example/gnuhello/

Now verify that it builds.

bitbake hello

Note that the recipe is setup to automatically download the sources from the GNU URL. The recipetool utility can also use URIs from source code management systems such as Git. Additionally, the source code for your recipe can be stored directly in the custom layer but it is considered Yocto best practice to keep it separate and have the recipe download it from the appropriate location.

See the Yocto Project Development Tasks Manual for more details on the recipetool utility.

Now let’s add this to our image, boot and verify that the /usr/bin/hello executable exists.

cat >> conf/local.conf <<EOF
IMAGE_INSTALL_append = " hello "
EOF
MACHINE=qemux86-64 bitbake core-image-base
 ../sources/meta-mender/meta-mender-qemu/scripts/mender-qemu core-image-base

Login as root with no password and execute the new binary:

qemux86-64 login: root
root@qemux86-64:~# ls -l /usr/bin/hello
-rwxr-xr-x    1 root     root         34976 Oct 27 12:44 /usr/bin/hello
root@qemux86-64:~# /usr/bin/hello
Hello, world!
root@qemux86-64:~#

Step 4: Modify recipe to add systemd service file.

For this hello-world application, we don’t necessarily need a service file as this application does not provide services to other parts of this operating system. However, for example purposes, we will create an autostart script to run hello at boot time. The output from this invocation will be available in the systemd logs using the journalctl command. This service file can also be manually invoked at runtime.

First, we will create the service file itself which is read and processed by systemd:

mkdir -p ../sources/meta-stargazer/recipes-example/gnuhello/files/
cat > ../sources/meta-stargazer/recipes-example/gnuhello/files/hello.service <<EOF
[Unit]
Description=GNU Hello World startup script

[Service]
ExecStart=/usr/bin/hello

[Install]
WantedBy=multi-user.target
EOF

Now let’s add the recipe settings to integrate this into the systemd configuration for our build:

cat >> ../sources/meta-stargazer/recipes-example/gnuhello/hello_2.10.bb <<EOF
inherit systemd
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE_\${PN} = "hello.service"

SRC_URI_append = " file://hello.service "
FILES_\${PN} += "\${systemd_unitdir}/system/hello.service"

do_install_append() {
  install -d \${D}/\${systemd_unitdir}/system
  install -m 0644 \${WORKDIR}/hello.service \${D}/\${systemd_unitdir}/system
}
EOF

Note: Dollar symbols require to be escaped with a backslash ( \$) to work with cat. If you copy-paste these examples remove the backslashes.

Now, rebuild, boot and verify that the service started and the output is visible in the systemd logs.

MACHINE=qemux86-64 bitbake core-image-base
 ../sources/meta-mender/meta-mender-qemu/scripts/mender-qemu core-image-base
root@qemux86-64:~# systemctl --no-pager status hello
● hello.service - GNU Hello World startup script
   Loaded: loaded (/lib/systemd/system/hello.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Sun 2019-10-27 13:56:09 UTC; 52s ago
  Process: 178 ExecStart=/usr/bin/hello (code=exited, status=0/SUCCESS)
 Main PID: 178 (code=exited, status=0/SUCCESS)

Oct 27 13:56:03 qemux86-64 systemd[1]: Started GNU Hello World startup script.
Oct 27 13:56:04 qemux86-64 hello[178]: Hello, world!
Oct 27 13:56:09 qemux86-64 systemd[1]: hello.service: Succeeded.

root@qemux86-64:~# journalctl -u hello --no-pager
-- Logs begin at Sun 2019-10-27 13:55:32 UTC, end at Sun 2019-10-27 13:57:43 UTC. --
Oct 27 13:56:03 qemux86-64 systemd[1]: Started GNU Hello World startup script.
Oct 27 13:56:04 qemux86-64 hello[178]: Hello, world!
Oct 27 13:56:09 qemux86-64 systemd[1]: hello.service: Succeeded.

Conclusion

In this tutorial we covered adding new recipes to Yocto project builds and enabling autostart services with systemd.

For further reading please visit

If this tutorial was useful to you, please press like, or leave a thank you note to the contributor who put valuable time into this and made it available to you. It will be much appreciated!

5 Likes

Thanks for the great support. I copy-pasted most of the steps and failed to bitbake hallo (step 3). The log file says (only an excerpt but I think the important part) :

mv lib/warn-on-use.h-t lib/warn-on-use.h
| /home/marius/mender-qemu/build/tmp/hosttools/mkdir -p lib/sys
| /bin/bash: line 1: lib/arg-nonnull.h-t: No such file or directory
| /bin/bash: line 30: lib/configmake.h-t: No such file or directory
| /bin/bash: line 1: lib/c++defs.h-t: No such file or directory

Did I miss a step here?
Best regards

Just ran /home/marius/mender-qemu/build/tmp/hosttools/mkdir -p lib/sys manually and then bitbake hello again and it finished. Is this step missing in the documentation or is this meant to be executed automatically?

In Step 4 at the end of the systemd service example the << in front of EOF at the bottom need to be removed. I don’t think I can edit this myself somehow, can I?

Also, it might be worth noting that the dollar signs need to be escaped to work in the cat command in Step 4 in case others also copy and paste that block (just like I did) and then wonder why it doesn’t work. Thus,

cat >> ../sources/meta-stargazer/recipes-example/gnuhello/hello_2.10.bb <<EOF
inherit systemd    
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE_${PN} = "hello.service"

SRC_URI_append = " file://hello.service "
FILES_\${PN} += "\${systemd_unitdir}/system/hello.service"

do_install_append() {
  install -d \${D}/\${systemd_unitdir}/system
  install -m 0644 \${WORKDIR}/hello.service \${D}/\${systemd_unitdir}/system
}
EOF

Edit: I just found another missing escape in the line FILES_\${PN} += "\${systemd_unitdir}/system/hello.service". Fixed above.

Huh. Not sure why but I don’t seem to have edit access either. @mirzak can you take a look?

Hi @HerrMuellerluedensch I just ran the steps above in my warrior based build without any such failure. Can you describe your yocto configuration to me? Did you start fresh with the full setup from above?

I’m pretty sure I did a plain copy and paste of all steps. Just to make sure I started a fresh repo sync and rebuild. I’ll keep you updated.
In the meantime you can probably help me with another issue: Isn’t there an inherit autotools missing in the receipe in step 4? Without that bitbake hello complaints that there is no Makefile.

Regarding the autotools: My bad! Too much fumbling in the receipes!

I just ran a full setup from scratch and triggered the failure you reported. I then immediately re-ran ‘bitbake hello’ and it succeeded. This indicates there is likely a race condition somewhere in the upstream hello sources. For the purposes of this tutorial, I think that’s ok since it’s not related to the specific content here.

Drew

Ok thanks! Sounds ligit.

Can you try now? The post was not in “wiki form”, though strange that you could not edit it since you posted it.

Yes, I have edit access again. Thanks.

I updated the wiki as described before. Feel free to review the changes and cleanup (remove) the chat history.

Hello @drewmoseley @mirzak thanks for the great article. I am trying to achieve the same to start my custom script during boot-up. If I have 2 scripts to be executed once rootfs is mounted or during boot-up how should I include 2 files in image recipe? Should I create 2 separate systemd service files? Can you please let me know how to do this? Assuming script names are custom-script1.sh and custom-script2.sh

Your help will be much appreciated.

Thanks in advance.

It’s really a matter of personal preference. If the two scripts always run together and consecutively then you can just have a single script to call that that is setup as a systemd service. If you ever have a need to start or stop just one of them, then you will need invidividual service files for each.

Drew

Can you post an example using recipetool and GitHub or GitLab. I am currently building an image using your examples above. In the future however I will need to build in a new recipe(s) from Git repos.

Can you give an example or point me to a different example?

As a quick update I am also seeing an error stating that lib/arg-nonnull.d does not exist. The proposed solution above did not solve my issue.

I’m not following what you are trying to do but I think the Yocto docs may help. Specifically devtool can help quickly create new recipes from GIT urls.

Drew

I want to use recipetool to create a recipe that uses a private repository. Can you share how to do this?

This link should help.
Drew