Documentation
Software
Hello world tutorial
Thirty odd years of tradition shouldn't be trifled with, so we're going to use this program in its classic form for this tutorial:
#include "stdio.h"
main() {
printf("hello, world\n");
}
Like the original hello world tutorial, we'll be glossing over a lot of detailed explanations in the interests of rapidly giving you the big picture. Though the program itself remains unchanged from 1974, we will bring it into the 21st century by building it as an OpenEmbedded package.
OpenEmbedded is based upon a tool called BitBake. Like make, BitBake is a tool for executing tasks and managing metadata.
Just as the make tool has "makefiles", BitBake has "recipes". A BitBake recipe tells the build system:
The following discussion assumes that you have already set up a development environment as described in Getting started.
We'll look at several ways to write a recipe, starting with the simplest, and progressing to more involved techniques that you might use for more complex programs:
For very simple programs like hello world, it's often convenient to keep the source file(s) in the build tree right along with the recipe. That's the first approach that we will explore.
Recipes are typically given names of the form:
packagename_versionnumber.bb
so we will call ours helloworld_1.0.0.bb.
Without further ado, here is our recipe:
DESCRIPTION = "hello world sample program"
PR = "r0"
DEPENDS = ""
SRC_URI = " \
file://hello.c \
"
S = "${WORKDIR}"
do_compile () {
${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
}
do_install () {
install -d ${D}${bindir}/
install -m 0755 ${S}/hello ${D}${bindir}/
}
FILES_${PN} = "${bindir}/hello"
Most of this should be fairly easy to follow, but let's look at it line by line.
The DESCRIPTION specifies the text that will be displayed by the package management sytem to describe what the package is. Keep it brief, but clear.
Our simple program does not depend on any other packages to build or run, so we set the DEPEND variable to null. If there were dependencies, we would list them here.
The PR line says that this is revision 0 of the recipe. When you are developing a recipe you should bump this revision value each time you make a change to the recipe.
The next few lines, starting with SRC_URI, tell the build system where to find source code for the package. SInce we're going to implement the simplest case (source code in the local file system) we specify the name of the source file "hello.c" with a file:// prefix. If there were multiple source files, we would list them all here.
The next line defines where the above source code should be copied and built by the build system. S is a special Bitbake variable that refers to the "unpacked source code directory". In this case we are specifying WORKDIR, which is a unique working directory that is created by the build sytem for each package. We'll discuss directory structure in more detail a bit later.
Now we get to the meat of the matter: how to compile and install our program.
The do_compile function does exaclty what you would expect, it gives the details of how to compile the package. In this case, the task isn't too complex. We are using the standard c cross compiler with the standard compiler and linker settings, we're specifying that the output file should be called hello, and that the source file is hello.c. Doesn't get much simpler! When it completes, we should have the newly created binary sitting in S.
The do_install function tells the package management system how to install our program. It uses the linux install command to create the destination directory (if it doesn't already exist) and then to install the hello command in that directory. You'll notice it does so using some special BitBake variables. Similar to the S variable above, the D variable specifies the destination directory where the completed application and all of its files are installed in preparation for packaging. The bindir variable is just a convenient way to specify usr/bin.
The final line tell the build system that we would like to create a package called "helloworld" that installs a single file: the binary "hello" that we generated above. The PN variable is yet another special BitBake variable that contains the package name. It is derived from the packagename portion of our recipe name as described above.
To learn more about the special BitBake variable see the section on Variables often used in recipes.
So now that we've written our source code and build recipe, we need to put them where the build system can find them. Package recipes are stored in one of three locations within the gumstix-oe directory:
If you haven't already created the user.collection/packages directory, now is the time to do so.
Each package you create should be given its own subdirectory in user.collection/packages. Package directories are typically given the same name as the package itself, so we'll call ours helloworld. The package recipe (in this case helloworld_1.0.0.bb) should be placed in that package directory. The source file (hello.c) should be placed in a subdirectory called files. So for the hello word example, we'll have a structure like this:
user.collection
|
`-packages
|
`-helloworld
|
|-files
| |
| `-hello.c
|
`-helloworld_1.0.0.bb
To build the hello world package, simply type the following at a console prompt. Unlike make, you don't need to cd into the package directory to do this. BitBake knows where to look for recipes and it will automatically find and build the helloworld package.
$ bitbake helloworld
After the build completes, you will find the resulting package in the gumstix-oe/tmp/deploy/glibc/ipk/armv5te directory with the name helloworld_1.0.0-r0_armv5te.ipk.
Installation is a two step process. First, from your build machine command line, copy the ipkg file to your gumstix using the scp utility:
$ scp ~/gumstix/gumstix-oe/tmp/deploy/glibc/ipk/armv5te/helloworld_1.0.0-r0_armv5te.ipk root(at)192.160.0.2:/home/root
The above assumes that the IP address of your gumstix is 192.168.0.2, you will need to change this to reflect the actual IP adress of your gumstix (you can find your IP address using the ifconfig command.
To install the package, from the command line prompt on your gumstix console type:
$ cd /home/root $ ipkg install helloworld_1.0.0-r0_armv5te.ipk
And finally, at long last, the moment of truth:
$ hello hello, world $
Success! We've built our first working program using gumstix-oe.
It's certainly nice to be able to install our program using the ipkg package manager, but often it is even more convenient to build programs right into the root file system image.
This is quite easy to do. As you might imagine, all it takes is another recipe!
There's a lot of nitty-gritty detail work in defining all of the ingredients that go into a functioning root file system image. In order to avoid all of that work, we'll have our image inherit from the gumstix-basic-image recipe. The resulting image will include everything in gumstix-basic-image plus our helloworld package.
This results in an extremely simple recipe:
require ${GUMSTIXTOP}/com.gumstix.collection/packages/images/gumstix-basic-image.bb
PR="r0"
IMAGE_INSTALL += "helloworld"
We'll name this recipe helloworld-image.bb and store it in our helloworld package directory, which will now look like this:
user.collection
|
`-packages
|
`-helloworld
|
|-files
| |
| `-hello.c
|
|-helloworld_1.0.0.bb
`-helloworld-image.bb
Note: If the package you create happens to be a library package, you should not explicitly add it to the IMAGE_INSTALL list. Just add the packages that use the library to the list and make sure they DEPEND on your library package.
To build our rootfs image, we just type:
$ bitbake helloworld-image
For information on how to find the resulting rootfs and kernel images, as well as how to reflash your motherboard please visit the Getting started section of the documentation.
Next let's look at using a makefile with our helloworld package. You might want to use this approach if you have a legacy program that is currently built using a makefile, but you want the convenience of being able to make it available as a downloadable package or want to include it in your rootfs image.
First let's create a makefile for our helloworld program (and give it the classic name Makefile):
hello: hello.c
${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
We'll store this file in the files subdirectory of our package, so we have the following directory structure:
user.collection
|
`-packages
|
`-helloworld
|
|-files
| |
| |-Makefile
| `-hello.c
|
|-helloworld_1.0.0.bb
`-helloworld-image.bb
Next we'll do some minor modifications to our recipe. Surprisingly, we're going to remove more than we add!
DESCRIPTION = "hello world sample program"
PR = "r0"
DEPENDS = ""
SRC_URI = " \
file://hello.c \
file://Makefile \
"
S = "${WORKDIR}"
do_install () {
install -d ${D}${bindir}/
install -m 0755 ${S}/hello ${D}${bindir}/
}
FILES_${PN} = "${bindir}/hello"
Our first change is to add Makefile to the SRC_URI list.
The second, and final, change is to entirely remove the do_compile function. We don't need it since the build system's default behaviour is to look for and execute a makefile in the S directory.
That's it! The steps to build and test the package are exactly the same as above.
Now we'll look at the case where the source code is fetched from a remote machine. This might be useful if you've developed a package whose source code you would like to share with others.
The first step is to create a tarball containing your source code and then store it on a server. Tarballs are typically given names of the form:
packagename-versionnumber.tgz
Also, it is typical for tarballs to extract into a directory called:
packagename-versionnumber
So to create our tarball, we would start with a structure like this:
helloworld-1.0.0 | |-Makefile `-hello.c
And then, after cd-ing to the directory containing this structure:
tar czf helloworld-1.0.0.tgz helloworld-1.0.0
The tgz file can now be uploaded to your server. In this case, lets assume you have made your tarball available at http://www.mysite.com/downloads/helloworld-1.0.0.tgz
Since the source code is now stored on a remote server, our package recipe folder will no longer require the files subdirectory:
user.collection
|
`-packages
|
`-helloworld
|
|-helloworld_1.0.0.bb
`-helloworld-image.bb
Our recipe only requires a few minor changes (and in fact gets even shorter):
DESCRIPTION = "hello world sample program"
PR = "r0"
DEPENDS = ""
SRC_URI = " \
http://www.mysite.com/downloads/helloworld-${PV}.tgz \
"
do_install () {
install -d ${D}${bindir}/
install -m 0755 ${S}/hello ${D}${bindir}/
}
FILES_${PN} = "${bindir}/hello"
The first change simply replaces the list of source and make files with a URL for the tarball.
The second change is possible because the build system sets the S variable to ${WORKDIR}${P} by default. Since we've constructed our tarball in the standard fashion, we are able to delete the line in our recipe that used to explicity set S for the location of our source files.
Once again, the steps to build and test the package are exactly the same as first example. When you bitbake this version of the recipe, watch the output carefully. You will see the build system download the source code, then extract, build, and package it.
For our final example, let's suppose that we are a user of the helloworld package in the previous section rather than the author.
Though we really like the functionality of the program, we've just got a thing about commas. Seeing that comma in "hello, world" just annoys us each time we use the program. Rather than completely rewriting the code, we can patch the existing application.
The first step is to prepare a patch file with our desired change (we'll call it nocomma.patch):
--- helloworld-1.0.0/hello-orig.c 2008-01-24 10:59:43.000000000 -0800
+++ helloworld-1.0.0/hello.c 2008-02-04 21:04:40.000000000 -0800
@@ -4,6 +4,6 @@
main()
{
- printf("hello, world\n");
+ printf("hello world\n");
}
We'll reinstate the files subdirectory, since we need to store our patch file there:
user.collection
|
`-packages
|
`-helloworld
|
|-files
| |
| |-nocomma.patch
|
|-helloworld_1.0.0.bb
`-helloworld-image.bb
Our recipe is going to require a one line change:
DESCRIPTION = "hello world sample program"
PR = "r0"
DEPENDS = ""
SRC_URI = " \
http://www.mysite.com/downloads/helloworld-${PV}.tgz \
file://nocomma.patch;patch=1 \
"
do_install () {
install -d ${D}${bindir}/
install -m 0755 ${S}/hello ${D}${bindir}/
}
FILES_${PN} = "${bindir}/hello"
We've simply added a line to specify that the file nocomma.patch should be applied as a patch to the downloaded application source code.
The steps to build and test the package are exactly the same as the previous examples. When you bitbake this version of the recipe, watch the output carefully. Since the previous example already downloaded the source tarball, there will be no download phase -- the build system will use a cached version of the tarball from /usr/share/sources (wich was created when you set up your build sytem).
Hopefully this tutorial has given you enough information to to begin writing your own package recipes. For more detailed information on OpenEmbedded and BitBake please consult: