Basic examples

By now you should know enough about the bitbake recipes to be able to create a basic recipe. We'll cover a simple single file recipe and then a more advanced example that uses the autotools helper class (to be described later) to build an autoconf based package.

Hello world

Now it's time for our first recipe. This is going to be one of the simplest possible recipes: all code is included and there's only one file to compile and one readme file. While this isn't all that common it's a useful example because it doesn't depend on any of the helper classes which can sometime hide a lot of what is going on.

First we'll create the myhelloworld.c file and a readme file. We'll place this in the files subdirectory, which is one of the places that is searched for file:// URI's:

mkdir packages/myhelloworld
mkdir packages/myhelloworld/files
cat > packages/myhelloworld/files/myhelloworld.c
#include 

int main(int argc, char** argv)
{
        printf("Hello world!\n");
        return 0;
}
^D
cat > packages/myhelloworld/files/README.txt
Readme file for myhelloworld.
^D

Now we have a directory for our recipe, packages/myhelloworld, and we've created a files subdirectory in there to store our local files. We've created two local files, the C source code for our helloworld program and a readme file. Now we need to create the bitbake recipe.

First we need the header section, which will contain a description of the package and the release number. We'll leave the other header variables out for now:

DESCRIPTION = "My hello world program"
PR = "r0"

Next we need to tell it which files we want to be included in the recipe, which we do via file:// URI's and the SRC_URI variable:

SRC_URI = "file://myhelloworld.c \
           file://README.txt"

Note the use of the \ to continue a file and the file of file:// local URI's, rather than other types such as http://.

Now we need provide a compile task which tells bitbake how to compile this program. We do this by defining a do_compile function in the recipe and providing the appropriate commands:

do_compile() {
        ${CC} ${CFLAGS} ${LDFLAGS} ${WORKDIR}/myhelloworld.c -o myhelloworld
}

Note the:

  • use of the pre-defined compiler variables, ${CC}, ${CFLAGS} and ${LDFLAGS}. These are setup automatically to contain the settings required to cross-compile the program for the target.

  • use of ${WORKDIR} to find the source file. As mentioned previously all files are copied into the working directory and can be referenced via the ${WORKDIR} variable.

And finally we want to install the program and readme file into the destination directory so that it'll be packaged up correctly. This is done via the install task, so we need to define a do_install function in the recipe to describe how to install the package:

do_install() {
        install -m 0755 -d ${D}${bindir} ${D}${docdir}/myhelloworld
        install -m 0644 ${S}/myhelloworld ${D}${bindir}
        install -m 0644 ${WORKDIR}/README.txt ${D}${docdir}/myhelloworld
}

Note the:

  • use the install command to create directories and install the files, not cp.

  • way directories are created before we attempt to install any files into them. The install command takes care of any subdirectories that are missing, so we only need to create the full path to the directory - no need to create the subdirectories.

  • way we install everything into the destination directory via the use of the ${D} variable.

  • way we use variables to refer to the target directories, such as ${bindir} and ${docdir}.

  • use of ${WORKDIR} to get access to the README.txt file, which was provided via file:// URI.

We'll consider this release 0 and version 0.1 of a program called helloworld. So we'll name the recipe myhelloworld_0.1.bb:

cat > packages/myhelloworld/myhelloworld_0.1.bb
DESCRIPTION = "Hello world program"
PR = "r0"

SRC_URI = "file://myhelloworld.c \
           file://README.txt"

do_compile() {
        ${CC} ${CFLAGS} ${LDFLAGS} ${WORKDIR}/myhelloworld.c -o myhelloworld
}

do_install() {
        install -m 0755 -d ${D}${bindir} ${D}${docdir}/myhelloworld
        install -m 0644 ${S}/myhelloworld ${D}${bindir}
        install -m 0644 ${WORKDIR}/README.txt ${D}${docdir}/myhelloworld
}
^D

Now we are ready to build our package, hopefully it'll all work since it's such a simple example:

~/oe%> bitbake -b packages/myhelloworld/myhelloworld_0.1.bb
NOTE: package myhelloworld-0.1: started
NOTE: package myhelloworld-0.1-r0: task do_fetch: started
NOTE: package myhelloworld-0.1-r0: task do_fetch: completed
NOTE: package myhelloworld-0.1-r0: task do_unpack: started
NOTE: Unpacking /home/lenehan/devel/oe/local-packages/myhelloworld/files/helloworld.c to /home/lenehan/devel/oe/build/titan-glibc-25/tmp/work/myhelloworld-0.1-r0/
NOTE: Unpacking /home/lenehan/devel/oe/local-packages/myhelloworld/files/README.txt to /home/lenehan/devel/oe/build/titan-glibc-25/tmp/work/myhelloworld-0.1-r0/
NOTE: package myhelloworld-0.1-r0: task do_unpack: completed
NOTE: package myhelloworld-0.1-r0: task do_patch: started
NOTE: package myhelloworld-0.1-r0: task do_patch: completed
NOTE: package myhelloworld-0.1-r0: task do_configure: started
NOTE: package myhelloworld-0.1-r0: task do_configure: completed
NOTE: package myhelloworld-0.1-r0: task do_compile: started
NOTE: package myhelloworld-0.1-r0: task do_compile: completed
NOTE: package myhelloworld-0.1-r0: task do_install: started
NOTE: package myhelloworld-0.1-r0: task do_install: completed
NOTE: package myhelloworld-0.1-r0: task do_package: started
NOTE: package myhelloworld-0.1-r0: task do_package: completed
NOTE: package myhelloworld-0.1-r0: task do_package_write: started
NOTE: Not creating empty archive for myhelloworld-dbg-0.1-r0
Packaged contents of myhelloworld into /home/lenehan/devel/oe/build/titan-glibc-25/tmp/deploy/ipk/sh4/myhelloworld_0.1-r0_sh4.ipk
Packaged contents of myhelloworld-doc into /home/lenehan/devel/oe/build/titan-glibc-25/tmp/deploy/ipk/sh4/myhelloworld-doc_0.1-r0_sh4.ipk
NOTE: Not creating empty archive for myhelloworld-dev-0.1-r0
NOTE: Not creating empty archive for myhelloworld-locale-0.1-r0
NOTE: package myhelloworld-0.1-r0: task do_package_write: completed
NOTE: package myhelloworld-0.1-r0: task do_populate_staging: started
NOTE: package myhelloworld-0.1-r0: task do_populate_staging: completed
NOTE: package myhelloworld-0.1-r0: task do_build: started
NOTE: package myhelloworld-0.1-r0: task do_build: completed
NOTE: package myhelloworld-0.1: completed
Build statistics:
  Attempted builds: 1
~/oe%>

The package was successfully built, the output consists of two .ipkg files, which are ready to be installed on the target. One contains the binary and the other contains the readme file:

~/oe%> ls -l tmp/deploy/ipk/*/myhelloworld*
-rw-r--r--  1 lenehan lenehan 3040 Jan 12 14:46 tmp/deploy/ipk/sh4/myhelloworld_0.1-r0_sh4.ipk
-rw-r--r--  1 lenehan lenehan  768 Jan 12 14:46 tmp/deploy/ipk/sh4/myhelloworld-doc_0.1-r0_sh4.ipk
~/oe%>

It's worthwhile looking at the working directory to see where various files ended up:

~/oe%> find tmp/work/myhelloworld-0.1-r0
tmp/work/myhelloworld-0.1-r0
tmp/work/myhelloworld-0.1-r0/myhelloworld-0.1
tmp/work/myhelloworld-0.1-r0/myhelloworld-0.1/patches
tmp/work/myhelloworld-0.1-r0/myhelloworld-0.1/myhelloworld
tmp/work/myhelloworld-0.1-r0/temp
tmp/work/myhelloworld-0.1-r0/temp/run.do_configure.21840
tmp/work/myhelloworld-0.1-r0/temp/log.do_stage.21840
tmp/work/myhelloworld-0.1-r0/temp/log.do_install.21840
tmp/work/myhelloworld-0.1-r0/temp/log.do_compile.21840
tmp/work/myhelloworld-0.1-r0/temp/run.do_stage.21840
tmp/work/myhelloworld-0.1-r0/temp/log.do_configure.21840
tmp/work/myhelloworld-0.1-r0/temp/run.do_install.21840
tmp/work/myhelloworld-0.1-r0/temp/run.do_compile.21840
tmp/work/myhelloworld-0.1-r0/install
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-locale
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-dbg
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-dev
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc/usr
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc/usr/share
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc/usr/share/doc
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc/usr/share/doc/myhelloworld
tmp/work/myhelloworld-0.1-r0/install/myhelloworld-doc/usr/share/doc/myhelloworld/README.txt
tmp/work/myhelloworld-0.1-r0/install/myhelloworld
tmp/work/myhelloworld-0.1-r0/install/myhelloworld/usr
tmp/work/myhelloworld-0.1-r0/install/myhelloworld/usr/bin
tmp/work/myhelloworld-0.1-r0/install/myhelloworld/usr/bin/myhelloworld
tmp/work/myhelloworld-0.1-r0/image
tmp/work/myhelloworld-0.1-r0/image/usr
tmp/work/myhelloworld-0.1-r0/image/usr/bin
tmp/work/myhelloworld-0.1-r0/image/usr/share
tmp/work/myhelloworld-0.1-r0/image/usr/share/doc
tmp/work/myhelloworld-0.1-r0/image/usr/share/doc/myhelloworld
tmp/work/myhelloworld-0.1-r0/myhelloworld.c
tmp/work/myhelloworld-0.1-r0/README.txt
~/oe%>

Things to note here are:

  • The two source files are in tmp/work/myhelloworld-0.1-r0, which is the working directory as specified via the ${WORKDIR} variable;

  • There's logs of the various tasks in tmp/work/myhelloworld-0.1-r0/temp which you can look at for more details on what was done in each task;

  • There's an image directory at tmp/work/myhelloworld-0.1-r0/image which contains just the directories that were to be packaged up. This is actually the destination directory, as specified via the ${D} variable. The two files that we installed were originally in here, but during packaging they were moved into the install area into a subdirectory specific to the package that was being created (remember we have a main package and a -doc package being created.

  • The program was actually compiled in the tmp/work/myhelloworld-0.1-r0/myhelloworld-0.1 directory, this is the source directory as specified via the ${S} variable.

  • There's an install directory at tmp/work/myhelloworld-0.1-r0/install which contains the packages that were being generated and the files that go in the package. So we can see that the myhelloworld-doc package contains the single file /usr/share/doc/myhelloworld/README.txt, the myhelloworld package contains the single file /usr/bin/myhelloworld and the -dev, -dbg and -local packages are all empty.

At this stage it's good to verify that we really did produce a binary for the target and not for our host system. We can check that with the file command:

~/oe%> file tmp/work/myhelloworld-0.1-r0/install/myhelloworld/usr/bin/myhelloworld
tmp/work/myhelloworld-0.1-r0/install/myhelloworld/usr/bin/myhelloworld: ELF 32-bit LSB executable, Hitachi SH, version 1 (SYSV), for GNU/Linux 2.4.0, dynamically linked (uses shared libs), for GNU/Linux 2.4.0, not stripped
~/oe%> file /bin/ls
/bin/ls: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.4.0, dynamically linked (uses shared libs), for GNU/Linux 2.4.0, stripped
~/oe%>

This shows us that the helloworld program is for an SH processor (obviously this will change depending on what your target system is), while checking the /bin/ls program on host shows us that the host system is an AMD X86-64 system. That's exactly what we wanted.

An autotools package

Now for an example of a package that uses autotools. These are programs that you need to run a configure script for, passing various parameters, and then make. To make these work when cross-compiling you need to provides a lot of variables to the configure script. But all the hard work as already been done for you. There's an autotools class which takes care of most of the complexity of building an autotools based packages.

Let's take a look at the tuxnes recipe which is an example of a very simple autotools based recipe:

%~oe> cat packages/tuxnes/tuxnes_0.75.bb
DESCRIPTION = "Tuxnes Nintendo (8bit) Emulator"
HOMEPAGE = "http://prdownloads.sourceforge.net/tuxnes/tuxnes-0.75.tar.gz"
LICENSE = "GPLv2"
SECTION = "x/games"
PRIORITY = "optional"
PR = "r1"

SRC_URI = "http://heanet.dl.sourceforge.net/sourceforge/tuxnes/tuxnes-0.75.tar.gz"

inherit autotools

This is a really simple recipe. There's the standard header that describes the package. Then the SRC_URI, which in this case is a http URL that causes the source code to be downloaded from the specified URI. And finally there's an "inherit autotools" command which loads the autotools class. The autotools class will take care of generating the require configure, compile and install tasks. So in this case there's nothing else to do - that's all there is to it.

It would be nice if it was always this simple. Unfortunately there's usually a lot more involved for various reasons including the need to:

  • Pass parameters to configure to enable and disable features;

  • Pass parameters to configure to specify where to find libraries and headers;

  • Make modifications to prevent searching for headers and libraries in the normal locations (since they below to the host system, not the target);

  • Make modifications to prevent the configure script from tying to compile and run programs - any programs it compiles will be for the target and not the host and so cannot be run.

  • Manually implement staging scripts;

  • Deal with lots of other more complex issues;

Some of these items are covered in more detail in the advanced autoconf section.