Internals of the Build Process

Internals of the Unikraft Build System#

As we saw in the Build Process section, Unikraft uses a set of files as part of the configuration and build steps. When porting an application, you want to be aware of these files and tune them to your needs. This section delves into the internals of the Unikraft build system and how they are useful for porting an application.

In order for an application or library to work with the Unikraft Make-based build system, you need to provide some specific files:

  1. Makefile: Used to specify where the main Unikraft repository is with respect to the application's repository, as well as repositories for any external libraries the application needs to use. The Makefile is not required when porting an external library.

  2. Used to specify which sources to build (and optionally where to fetch them from), include paths, flags and any application-specific targets.

  3. A Kconfig-like snippet used to populate Unikraft's menu with application-specific options.

  4. A text file where each line contains the name of one symbol that should be exported to other libraries. This file usually contains only the main() function for an application that is developed/ported as a single library to Unikraft.

If an file is not present, all symbols will be exported.

  1. extra.ld: Optional, contains an amendment to the main linker script.

In the tutorial below, we will focus on files belonging to an application (i.e. not to a library). The process of porting a library is very similar, just change the APP prefix of the variable names to LIB.


The Makefile is generally short and simple and might remind you of Linux kernel modules that are built off-tree. For most applications the Makefile should contain no more than the following:

UK_ROOT ?= $(PWD)/workdir/unikraft
UK_LIBS ?= $(PWD)/workdir/libs
UK_PLATS ?= $(PWD)/workdir/plats
LIBS := $(UK_LIBS)/lib1:$(UK_LIBS)/lib2:$(UK_LIBS)/libN
@make -C $(UK_ROOT) A=$(PWD) L=$(LIBS) P=$(PLATS)

You can notice that the Makefile is just a wrapper around the Unikraft core Makefile. You can get the same result if you use:

$ make -C workdir/unikraft A=. L=workdir/libs/lib1:workdir/libs/lib2:workdir/libs/libN

The Unikraft build system provides a number of functions and macros to make it easier to write the file. Also, ensure that all variables that you define begin with APP[NAME]_ or LIB[NAME]_ (e.g., APPHELLOWORLD_) so that they are properly namespaced.

The first thing to do is to call the Unikraft addlib function to register the application with the system (note the letters lib: everything in Unikraft is ultimately a library). This function call will also populate APPNAME_BASE (containing the directory path to the application sources) and APPNAME_BUILD (containing the directory path to the application's build directory):

$(eval $(call addlib,appname))

where name would be replaced by your application's name. In case your main application code should be downloaded as an archive from a remote server, the next step is to set up a variable to point to a URL with the application sources (or objects, or pre-linked libraries, see further below), and to call the Unikraft fetch function to download and unpack those sources (APPNAME_ORIGIN is populated containing the directory path to the extracted files):

APPNAME_ZIPNAME = myapp-v0.0.1
$(eval $(call fetch,appname,$(APPNAME_URL)))

Next we set up a call to the Unikraft patch function. Even if you don't have any patches yet, it's good to have this set up in case you need it later.

$(eval $(call patch,appname,$(APPNAME_PATCHDIR),$(APPNAME_ZIPNAME)))

With all of this in place, you can already start testing things out:

$ make menuconfig
[choose appropriate options and save configuration, see user's guide]
$ make

You should see Unikraft downloading the application archive and unpacking it. It will do the same for libraries specified in the Makefile or through the menu. Libraries will then be built. There will be nothing to build for your application yet, as we haven't specified any sources to build. When building, Unikraft creates a build/ directory and places all temporary and object files under it; the application sources are placed under build/origin/[tarballname]/.

To tell Unikraft which source files to build, we add files to the APPNAME_SRCS-y variable:

APPNAME_SRCS-y += $(APPNAME_BASE)/path_to_src/myfile.c

For source files, Unikraft so far supports C (.c), C++ (.cc, .cxx, .cpp) and assembly files (.s, .S).

In case you have pre-compiled object files, you could add them (but due to possible incompatible compilation flags of your pre-compiled object files, you should handle this with care):

APPNAME_OBJS-y += $(APPNAME_BASE)/path_to_src/myobj.o

You can also use APPNAME_OBJS-y to add pre-built libraries (as .o or .a):

APPNAME_OBJS-y += $(APPNAME_BASE)/path_to_lib/mylib.a

Once you have specified all of your source files (and optionally binary files), it is generally also necessary to specify include paths and compile flags:

# Include paths
APPNAME_ASINCLUDES += -I$(APPNAME_BASE)/path_to_include/include [for assembly files]
APPNAME_CINCLUDES += -I$(APPNAME_BASE)/path_to_include/include [for C files]
APPNAME_CXXINCLUDES += -I$(APPNAME_BASE)/path_to_include/include [for C++ files]
# Flags for application sources
APPNAME_ASFLAGS-y += -DFLAG_NAME1 ... -DFLAG_NAMEN [for assembly files]

With all of these in place, you can save, and type make. Assuming that the chosen Unikraft libraries provide all of the support that your application needs, Unikraft should compile and link everything together, and output one image per target platform specified in the menu.

In addition to all the functionality mentioned, applications might need to perform a number of additional tasks after the sources are downloaded and unpacked, but before the compilation takes place (e.g., run a configure script or a custom script that generates source code from source files). To support this, Unikraft provides the UK_PREPARE variable, connected to the internal prepare target, which you can set to a temporary marker file and from there to a target in your file. For example:

$(APPNAME_BUILD)/.prepared: [dependencies to further targets]
cmd && $(TOUCH) $@

In this way, you ensure that cmd is run before any compilation takes place. If you use fetch, add $(APPNAME_BUILD)/.origin as dependency. If you use patch, add $(APPNAME_BUILD)/.patched instead.

Furthermore, you may find it necessary to specify compile flags or includes only for a specific source file. Unikraft supports this through the following syntax:

APPNAME_SRCS-y += $(APPNAME_BASE)/filename.c

It is also possible to compile a single source file multiple times with different flags. For this case, Unikraft supports variants:

APPNAME_SRCS-y += $(APPNAME_BASE)/filename.c|variantname

Note: The build system treats the reserved isr variant differently. This variant is intended for build units that contain code that can also be called from interrupt context. Separate global architecture flags are used to generate interrupt-safe code (ISR_ARCHFLAGS-y instead of ARCHFLAGS-y). Generally, these flags avoid the use of extended machine units which aren't saved by the processor before entering interrupt context (e.g. floating point units, vector units).

Finally, you may also need to provide "glue" code, for instance to implement the main() function that Unikraft expects you to implement by calling your application's main or init routines. As a rule of thumb, we suggest to place any such files in the application's main directory (APPNAME_BASE), and any includes they may depend on under APPNAME_BASE/include/. And, of course, don't forget to add the source files and include path to

To see full examples of files, you can browse the available repositories for applications or external libraries.

Reserved variable names in the name scope are so far:

APPNAME_BASE - Path to source base
APPNAME_BUILD - Path to target build dir
APPNAME_EXPORTS - Path to the list of exported symbols
(default is '$(APPNAME_BASE)/')
APPNAME_ORIGIN - Path to extracted archive
(when fetch or unarchive was used)
APPNAME_CLEAN APPNAME_CLEAN-y - List of files to clean additional
on make clean
APPNAME_SRCS APPNAME_SRCS-y - List of source files to be
APPNAME_OBJS APPNAME_OBJS-y - List of object files to be linked
for the library
APPNAME_OBJCFLAGS APPNAME_OBJCFLAGS-y - link flags (e.g., define symbols
as internal)
APPNAME_CFLAGS APPNAME_CFLAGS-y - Flags for C files of the library
APPNAME_CXXFLAGS APPNAME_CXXFLAGS-y - Flags for C++ files of the library
APPNAME_ASFLAGS APPNAME_ASFLAGS-y - Flags for assembly files of the
APPNAME_ASINCLUDES APPNAME_ASINCLUDES-y - Includes for assembly files of
the library
APPNAME_FILENAME_FLAGS - Flags for a *specific* source file
APPNAME_FILENAME_FLAGS-y of the library (not exposed to its
APPNAME_FILENAME_INCLUDES - Includes for a *specific* source
APPNAME_FILENAME_INCLUDES-y file of the library (not exposed
to its variants)
APPNAME_FILENAME_VARIANT_FLAGS - Flags for a *specific* source file
APPNAME_FILENAME_VARIANT_FLAGS-y and variant of the library
APPNAME_FILENAME_VARIANT_INCLUDES - Includes for a *specific* source
APPNAME_FILENAME_VARIANT_INCLUDES-y file and variant of the library

Unikraft's configuration system is based on Linux's KConfig system. With you define possible configurations for your application and define dependencies to other libraries. This file is included by Unikraft to render the menu-based configuration, and to load and store configurations (aka .config). Each setting that is defined in this file will be globally populated as a set variable for all files, as well as a defined macro in your source code when you use "#include <uk/config.h>". Please ensure that all settings are properly namespaced. They should begin with [APPNAME]_ (e.g. APPHELLOWORLD_). Please also note that some variable names are predefined for each application or library namespace (see

As best practice, we recommend to begin the file with defining dependencies with an invisible boolean option that is set to y (i.e. an option that will not be visible in the menuconfig screen):

### Invisible option for dependencies
default y
# dependencies with `select`:
select LIB1
select LIB2

In this example, LIB1 and LIB2 would be enabled (the user can't unselect them). Additionally, if the user did not provide and select any libc, the Unikraft internal replacement nolibc would be selected. You can find a documentation of the syntax in the Linux kernel tree. Of course, you could also directly define a dependency on a particular libc (e.g., libmusl), instead. You can also depend on feature flags (like HAVE_LIBC) to provide or hide options. The feature flag HAVE_LIBC in this example is set as soon as a proper and full-fledged libc was selected by the user. You can get an overview of available feature flags in libs/

Any other setting of your application can be a type of boolean, int, or string. You are even able to define dropdown-selections. See the KConfig documentation for details on the syntax. Please note that Unikraft does not have tristates (the m option, for kernel modules, is not available).

### App configuration
bool "Boolean Option 1"
default y
Help text of Option 1

Unikraft provides separate namespaces for each library. This means that every function and variable will only be visible and linkable internally.

To make a symbol visible for other libraries, add it to this file. It is simply a flat file, with one symbol name per line. Line comments may be introduced by the hash character (#).

If you are writing an application, you need to add your program entry point to this file. Most likely nothing else should be there. For a library, all external API functions must be listed.

For the sake of file structure consistency, it is not recommended to change the default path of this symbols file, unless it is really necessary (e.g., multiple libraries are sharing the same base folder, this symbols file is part of a remotely fetched archive). You can override it by defining the APPNAME_EXPORTS variable. The path must be either absolute (you can refer with $(APPNAME_BASE) to the base directory of your application sources) or relative to the Unikraft sources directory.

If no file is present within a given library (internal or external), all the symbols will be exported by default.


If your library/application needs a section in the final executable image, edit your to add:


An example context of extra.ld:

.uk_fs_list : {
PROVIDE(uk_fslist_start = .);
KEEP (*(.uk_fs_list))
PROVIDE(uk_fslist_end = .);

This will add the section .uk_fs_list after the .text section.

After Porting#

After porting an application or library, you can share it with the rest of the community by creating a PR. You can find more about that on the contributing page.

Edit this page on GitHub

Connect with the community

Feel free to ask questions, report issues, and meet new people.

Join us on Discord!

Getting Started

What is a unikernel?Install CLI companion toolUnikraft InternalsRoadmap

© 2023  The Unikraft Authors. All rights reserved. Documentation distributed under CC BY-NC 4.0.