Contents
Getting a working toolchain and DSLinux build environment
Start out by following the instructions at CompilingDSLinux. Before you can start compiling programs to run on DSLinux you have to have a working toolchain set up. If you can't figure it out, then read it again. Use Google if there's still something you can't figure out.
The cross-compilation shell
OK, have a working toolchain? DSLinux built successfully? Good.
In the top-level directory of the source tree, run
make xsh
You'll see some environment variables being printed and a new fancy shell prompt. You're now in a special command shell with a cross-compilation environment.
Type the following (from now on $ means a command that you're supposed to type):
$ echo $CC
It should output "ucfront-gcc arm-linux-elf-gcc".
Keep the shell open, we'll need it for a while still.
Compiling and running a test app
Now that we have everything set up, we'll start by getting something really simple to compile.
$ mkdir user/helloworld $ cd user/helloworld $ cat <<"EOF" > helloworld.c
After you typed that you'll see "> ". That means the shell is expecting more input. Copy and paste the following into the terminal:
#include <stdio.h>
int main() {
printf("Hello DSLinux World!\n");
return 0;
}Then press ENTER, type EOF and press ENTER again. If this didn't make sense you can use a text editor to create helloworld.c instead.
Now we'll compile helloworld manually (to do a proper port we have to set up a Makefile and do various other things):
$ $CC $CFLAGS $LDFLAGS helloworld.c -o helloworld
If you didn't see any errors you should have a helloworld executable to run in DSLinux. To check you can type:
$ file helloworld helloworld: BFLT executable - version 4 gotpic
If the file command doesn't output BFLT executable, then something went wrong. If file did give you the right output, copy helloworld to linux/usr/bin on your CF/SD card and start DSLinux. Now run helloworld:
$ helloworld Hello DSLinux World!
It worked! Now we'll move on to getting a "real" app running.
Porting an app
Getting the app to build
For this example I've chosen to port units, a useful app for unit conversion (kilometres to miles, etc). Download the units source tarball (units-x.xy.tar.gz) from http://www.gnu.org/software/units/units.html. At the time of writing the newest version was 1.86.
Go up one directory to the user/ directory.
$ cd ..
Create directory for the program and change into it:
$ mkdir units $ cd units
Unpack the units tarball, and rename the resulting directory to src
$ tar zxvf /path/to/units-1.86.tar.gz units-1.86/... ...
(Anyone who is about to complain that /path/to/units-1.86.tar.gz doesn't exist...Please don't.)
$ mv units-1.86 src $ cd src $ ls ChangeLog getopt.c Makefile.in parse.tab.c texi2man units.h configure getopt.h Makefile.OS2 parse.y units.c units.info configure.ac INSTALL makeobjs.cmd README units.dat units.man COPYING install-sh mkinstalldirs README.OS2 units.doc units.texinfo getopt1.c Makefile.dos NEWS strfunc.c units.dvi
The source code. But what do we do with it?
In this case we see that there is a configure script, which means that the app is using GNU autotools.
In such cases one is usually just supposed to type ./configure && make to do a normal compile.
$ ./configure checking for C compiler default output file name... a.out checking whether the C compiler works... configure: error: cannot run C compiled programs. If you meant to cross compile, use `--host'. See `config.log' for more details.
Indeed we did mean to cross compile, let's do what the script suggested. We'll also add --prefix=/usr, because that's the prefix in DSLinux where we'll put the app. In this case it is important because units has to know where to find a datafile. I use --host=${CROSS} because the ${CROSS} variable contains the prefix of the binaries in the toolchain.
$ ./configure --prefix=/usr --host=${CROSS}
... (lots of output)
config.status: creating MakefileIf you didn't see any error messages units is now "configured" for building.
$ make
If you didn't see any error messages (warnings from the compiler are often OK) units should now be compiled. For good measure, check units, as was done previously with the helloworld program, using the file command. Again, if the file command did not output BFLT Executable, something went wrong. One reason for this may be that variables which say how to build the program have been changed in units' Makefile! Check the Makefile (see below) if it is redefining any variables already set in the DSLinux environment. Generally we do not want CC, CFLAGS or LDFLAGS to be redefined by the Makefile. We will be writing our own Makefile shortly, though, so don't panic. (This is just in case you want to test the program before the next step).
Testing the app
But now what? We can't use make install, so how are we supposed to know where binaries, datafiles, etc should be copied?
Answer: by inspecting the Makefile or installing it first. Preferably both. You shouldn't attempt to port programs you haven't first tested on your linux box.
A package for units should be available for your distro.
Debian/ubuntu # apt-get install units
Gentoo # emerge units
Fedora # yum install units (just guessing)
And so on. If a package isn't available for your distribution you can compile and install units yourself.
I use debian, so I typed dpkg -L units to get a list of files installed by the units package.
The files of interest for installing in DSLinux are units.dat (unit definitions) and units (binary).
Copy them to your CF/SD card and see if units runs.
$ cp units.dat /path/to/card/linux/usr/share $ cp units /path/to/card/linux/usr/bin $ sudo umount /path/to/card
You can also transfer the files over wifi from DSLinux if you want to (just make sure you don't corrupt the files in the transfer). In DSLinux try to run units.
$ units
2438 units, 71 prefixes, 32 nonlinear units
You have: 1 mile
You want: kilometres
* 1.609344
/ 0.62137119
You have:Yay, it worked! To exit units press Ctrl-D (EOF). But it's no "port" yet.
We are now going to automate what we did above. To do so, we are going to write a Makefile.
What is a Makefile?
Makefiles describe how to build a program. Makefiles are parsed by a program called 'make'. Using information gathered from Makefiles, the make tool automates dependency handling during the build process. This means that it is not neccessary to compile a whole project all over again just because a change was made to a single file, for example. Instead, make will figure out what parts of the project need to be rebuilt to integrate the change.
The whole DSLinux build process is handled by make.
Makefiles have very simple syntax. There are macros and rules.
Macros
You can can create macros like this:
TOP_DIR = /top
and expand macros like this:
$(TOP_DIR)
So wherever you write $(TOP_DIR) now, make will see /top instead.
Rules
Makefiles define targets that are to be built. Targets are usually files to create during the build process. Each target can have dependencies (other files needed to create a given file, for example). A Makefile rule defines the commands used to create a target. The commands used to create a target are run if the dependencies of the target have a newer timestamp than the target, or if the target does not exist at all. A complete rule looks like this:
target: dependencies (optional) <tab> command1 <tab> command2 <tab> command3 <tab> command4
The tab character is very important. It is used to indent the commands used to create a target. Note that you cannot use spaces - use tabs. If your editor inserts several spaces instead of a tab character, change the settings or use another one.
Targets need not necessarily be files. They can also be "phony" targets, that do not produce a file but are used to run a couple of commands. Phony targets may have dependencies, but they do not produce a file that has their name.
An example
This example Makefile was derived from the Makefile for the fwver app bundled into DSLinux. fwver was made by pepsiman. The example Makefile may not be obvious at first glance. It makes use of some make magic we have not yet talked about. For example, make knows how to make a .o file from the .c file. We do not need to tell it how to do that.
# Makefile for helloworld
EXEC = helloworld
OBJS = helloworld.o
# declare these targets as phony, so make will not try to look
# for files named like them:
.PHONY: all romfs clean
# 'all' is a phony target. It is called when the build process
# enters this directory and wants to build the program.
all: $(EXEC)
# This is the rule that builds the hello world executable.
$(EXEC): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS$(LDLIBS_$@))
# The romfs target is also phony. It is used to copy your app
# into the DSLinux filesystem image.
romfs:
$(ROMFSINST) /bin/$(EXEC)
# clean is another phony target that removes every file generated
# during the build.
clean:
rm -f $(EXEC) *.elf *.gdb *.o
Creating a Makefile for the app
We will now create a Makefile that does the exact same thing we did above. Note that the elaborate comments in this example Makefile should be removed before submitting it in a patch. Now, here it is:
# Makefile for units
# 'all' is the default target. It will be called
# by the DSLinux build system.
# In our case, it depends on the hidden file .compiled,
# which is created by one of the targets below.
all: .compiled
# Before we can compile units, we need to run the
# configure script. But we do not want to run configure
# more often than needed. The trick is to create
# a file just after running configure, and have make
# only run the configure script if the file does not
# already exist. In our case, we name that file
# .configured. It is a hidden file because its name
# starts with a dot, so you can only see it in the
# output of ls -a, not just ls. Here is the rule that
# runs configure and creates the .configured file if
# it does not exist.
# Note: $@ is a special pre-defined macro that expands to
# the target of the rule. Hence, in this rule, $@ expands
# to .configured.
.configured:
cd src && ./configure --prefix=/usr --host=${CROSS}
touch $@
# Once the .configured file exists, we know that the
# program has been configured sucessfully. Now we can
# run make in the source code directory of the program
# to compile it. $(MAKE) is a macro that expands to the
# name of the currently used make program. We use it instead
# of just 'make' in case the make binary has been renamed
# to something else for some reason. The '-C src' argument
# tells make to change into the src directory before doing anything.
# After compiling, we create the .compiled file, which satisfies
# the 'all' target above.
.compiled: .configured
$(MAKE) -C src
touch $@
# The clean target gets called when make clean is run.
# We should only run make clean in the source tree
# of the program if a Makefile has been generated
# by the configure script already. This is achieved
# by the test -f src/Makefile command.
# The minus (-) preceeding the test command tells
# make to ignore errors reported by the command,
# and continue with the next line nonetheless.
# Thus we remove the .configured and .compiled files
# in any case, which causes everything to start from
# scratch again the next time make is run.
clean:
-test -f src/Makefile && $(MAKE) -C src clean
rm -f .configured .compiled
# We have to add a romfs target so units and units.dat gets
# copied to the right place automatically.
romfs: .compiled
$(ROMFSINST) ./src/units /bin/units
$(ROMFSINST) ./src/units.dat /usr/share/units.dat
# The dependencies of the .PHONY target are those targets
# that do not create a file that has their name.
# We have to add them to the special .PHONY target otherwise
# they wouldn't be built if a file that has their name exists
# or gets created or updated somehow.
.PHONY: all clean romfsunits.1 is a man page and units.info is a info document, neither of which are used in DSLinux. So we ignore those.
Now save the Makefile.
Test the Makefile
$ make clean $ make # to make sure it still works $ make # to make sure it does not run configure again this time $ make clean
Makefiles for C++ programs
If you want to build a C++ program, look at /user/rtest. This is a sample & well documented "Hello World". Note that in the config directory, you need to add the requirement for uClibc++.
Integrating the app with the DSLinux build system
Now that we have units properly set up, it's time to tell the DSLinux build system about it.
Type exit, and you'll be back in the dslinux directory.
Open config/config.in in a text editor.
$ myeditor config/config.in
Search for tripwire, and add the following to the next line (config.in is sorted alphabetically):
bool 'units' CONFIG_USER_UNITS_UNITS
Open config/Configure.help in a text editor.
$ myeditor config/Configure.help
Add the following in an appropriate place:
CONFIG_USER_UNITS_UNITS The Units program converts quantities expressed in various scales to their equivalents in other scales.
Finally open user/Makefile in a text editor.
$ myeditor user/Makefile
Add the following in an appropriate place:
dir_$(CONFIG_USER_UNITS_UNITS) += units
Now we have to make sure units gets built.
$ make menuconfig
Navigate to Kernel/Library/Defaults Selection, Customize Vendor/User Settings and press y. Choose Exit, Exit, Yes. You'll now get another dialog. Navigate to Miscellaneous Applications, units. Press y and choose Exit, Exit, Yes.
Type make to build DSLinux.
$ make
If you did everything correctly the build should now be done without any fatal errors and units should be in the image.
$ ls -lh images/linux/usr/bin/units images/linux/usr/share/units.dat -rwxr----- 1 jss jss 45K 2006-11-22 23:16 images/linux/usr/bin/units -rw-r----- 1 jss jss 201K 2006-11-22 23:16 images/linux/usr/share/units.dat
It worked! We're now very close to having a patch to submit (first you should test the app extensively).
Submitting your port
What is needed and why?
To add a new program, we need the unmodified sources of it so we can import them into the repository on a special branch. This way we have access to the base line dslinux-specific patches for the program are based on. Having the original source around also helps when the program is being updated to a new version later.
We also need a patch that make the source compile with dslinux. This patch gets comitted to the main branch (trunk) in the Subversion repository, so if people checkout the trunk (as they usually do) they will get the ported version of the program by default.
The patch should include any modification made to the unmodifed source code of the program, and also changes made to Makefiles and other files in the source tree to tie the program into the build system.
Creating the patch
Please read CreatingPatches before reading on here!
First, make sure to clean out any binary files created during the build process of your program. If there are binary files in your patch the patch won't work!
At the top-level directory of the DSLinux source tree, type the following:
$ svn diff config/config.in config/Configure.help user/Makefile > /tmp/units.diff
You now have a file called /tmp/units.diff containing a unified diff of our changes to the DSLinux build system.
Now we have to add to that file the changes to the units program itself. But first, clean out the units directory so we don't get any junk in the diff. You should also check for backup files created by your text editor. Unpack the original units tarball, and then run diff on the original source tree and the one you modified:
$ make -C user/units clean # remove all junk (hopefully) $ tar -C user/units -zxvf /path/to/units-1.86.tar.gz # extract original source code $ diff -urN user/units/units-1.86 user/units/src | less # view diff
Use enter/space/pgup/pgdown to browse through the diff and q to exit less.
Hmm, the diff looks OK but there are two junk files left that were generated by the configure script.
Delete those:
$ rm user/units/src/config.status user/units/src/config.log
Run diff again.
$ diff -urN user/units/units-1.86 user/units/src | less
Looks OK!
Now append the diff to /tmp/units.diff:
$ diff -urN user/units/units-1.86 user/units/src >> /tmp/units.diff
Also, add the Makefile you created to the patch:
$ diff -u /dev/null user/units/Makefile >> /tmp/units.diff
Now send your patch (/tmp/units.diff) to the dslinux-devel mailing list. Include a URL to the original sources of the application in the body of your mail.
Porting a library
For this tutorial, I show you how to add the library libmad to DSLinux. It is a good idea to read about porting an application first.
Create a directory
uClinux libraries are created in the lib directory:
cd lib mkdir libmad
Now put all files for this library into the libmad folder.
Modify the lib Makefile
You have to add the new library "libmad" to lib/Makefile. First add the libmad directory to the makefile:
dir_$(CONFIG_LIB_MAD) += libmad dir_$(CONFIG_LIB_MAD_FORCE) += libmad
Then add the directory to the LINKLIBS variable in the makefile:
$(ROOTDIR)/lib/libid3tag/*.a \
$(ROOTDIR)/lib/libmad/*.a Don't forget to add a "\" to the line before!
Add the include file
Almost every library adds an include file to export its services. This include file must be copied/linked into the uClinux include path. Edit include/Makefile and add "mad.h" to the LINKHDRS variable:
$(ROOTDIR)/lib/libid3tag/id3tag.h,. \ $(ROOTDIR)/lib/libmad/mad.h,.
Don't forget to add a "\" to the line before!
Modify the config files
Edit config/config.in. Add a configure option for libmad to the Library configuration:
bool 'Build libmad' CONFIG_LIB_MAD_FORCE
If you are adding a program using this library, you must add a dependency to build libmad if the program is selected:
bool 'madplay' CONFIG_USER_MADPLAY
if [ "$CONFIG_USER_MADPLAY" = "y" ]; then
define_bool CONFIG_LIB_ZLIB y
define_bool CONFIG_LIB_ID3TAG y
define_bool CONFIG_LIB_MAD y
fiIn Configure.help, add a help text for libmad:
CONFIG_LIB_MAD_FORCE Embedded MP3 decoder. Only enable this if you want to force the library to be built. The Config will make sure this library is built if it is needed.
Write a Makefile for the library
Each library needs a Makefile for building. Most libraries have their own build mechanism. If it uses a configure script, put the source code of the library into a subdirectory of the lib/yourlibrary directory, as described above.
You can also create a Makefile that compiles everything manually. This is much more work than the method described above and should only be used for libraries and applications that do not come with a configure script.
Here is an example Makefile that compiles the libmad library manually:
# Makefile for libmad in uClinux
# ==============================
# Special compiler options
CFLAGS += -DHAVE_CONFIG_H -DASO_INTERLEAVE1 -DASO_IMDCT -DFPM_ARM
CFLAGS += -Wall -O -fforce-mem -fforce-addr -fthread-jumps
CFLAGS += -fcse-follow-jumps -fcse-skip-blocks -fexpensive-optimizations
CFLAGS += -fregmove -fschedule-insns2 -fstrength-reduce -fomit-frame-pointer
# Header files
INCS:= config.h version.h fixed.h bit.h timer.h stream.h frame.h \
synth.h decoder.h global.h layer12.h layer3.h huffman.h \
D.dat imdct_s.dat qc_table.dat rq_table.dat sf_table.dat
# Object files
OBJS:= version.o fixed.o bit.o timer.o stream.o frame.o synth.o \
decoder.o layer12.o layer3.o huffman.o imdct_l_arm.o
.PHONY: all
all: libmad.a
libmad.a: $(OBJS) $(INCS)
$(AR) rv $@ $(OBJS)
$(RANLIB) $@
# We only link statically in DSLinux, hence libraries are never installed.
# Thus the romfs target does nothing.
.PHONY: romfs
romfs:
.PHONY: clean
clean:
-rm -f *.[oa] *~As you see, it's pretty simple. If the library has used autoconf/automake, you must create a file config.h. Remove all files not used for the DSLinux build process.
Add the config option to all config.vendor files
Add a line
# CONFIG_LIB_MAD_FORCE is not set
to all config.vendor files in vendors/Nintendo/*
Now start with the usual make menuconfig / make cycle.
