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:
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.
Makefile.uk: Used to specify which sources to build (and optionally where to fetch them from), include paths, flags and any application-specific targets.
Config.uk: A Kconfig-like snippet used to populate Unikraft's menu with application-specific options.
exportsyms.uk:
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
exportsyms.uk
file is not present, all symbols will be exported.
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/unikraftUK_LIBS ?= $(PWD)/workdir/libsUK_PLATS ?= $(PWD)/workdir/platsLIBS := $(UK_LIBS)/lib1:$(UK_LIBS)/lib2:$(UK_LIBS)/libNPLATS ?=all:@make -C $(UK_ROOT) A=$(PWD) L=$(LIBS) P=$(PLATS)$(MAKECMDGOALS):@make -C $(UK_ROOT) A=$(PWD) L=$(LIBS) P=$(PLATS) $(MAKECMDGOALS)
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 Makefile.uk
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.1APPNAME_URL = http://app.org/$(APPNAME_ZIPNAME).zip$(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.
APPNAME_PATCHDIR=$(APPNAME_BASE)/patches$(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 pathsAPPNAME_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 sourcesAPPNAME_ASFLAGS-y += -DFLAG_NAME1 ... -DFLAG_NAMEN [for assembly files]APPNAME_CFLAGS-y += -DFLAG_NAME1 ... -DFLAG_NAMEN [for C files]APPNAME_CXXFLAGS-y += -DFLAG_NAME1 ... -DFLAG_NAMEN [for C++ files]
With all of these in place, you can save Makefile.uk
, 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 Makefile.uk
file.
For example:
$(APPNAME_BUILD)/.prepared: [dependencies to further targets]cmd && $(TOUCH) $@UK_PREPARE += $(APPNAME_BUILD)/.prepared
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.cAPPNAME_FILENAME_FLAGS-y += -DFLAGAPPNAME_FILENAME_INCLUDES-y += -Iextra/include
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|variantnameAPPNAME_FILENAME_VARIANTNAME_FLAGS-y += -DFLAG2APPNAME_FILENAME_VARIANTNAME_INCLUDES-y += -Iextra/include
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 ofARCHFLAGS-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 Makefile.uk
.
To see full examples of Makefile.uk
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 baseAPPNAME_BUILD - Path to target build dirAPPNAME_EXPORTS - Path to the list of exported symbols(default is '$(APPNAME_BASE)/exportsyms.uk')APPNAME_ORIGIN - Path to extracted archive(when fetch or unarchive was used)APPNAME_CLEAN APPNAME_CLEAN-y - List of files to clean additionalon make cleanAPPNAME_SRCS APPNAME_SRCS-y - List of source files to becompiledAPPNAME_OBJS APPNAME_OBJS-y - List of object files to be linkedfor the libraryAPPNAME_OBJCFLAGS APPNAME_OBJCFLAGS-y - link flags (e.g., define symbolsas internal)APPNAME_CFLAGS APPNAME_CFLAGS-y - Flags for C files of the libraryAPPNAME_CXXFLAGS APPNAME_CXXFLAGS-y - Flags for C++ files of the libraryAPPNAME_ASFLAGS APPNAME_ASFLAGS-y - Flags for assembly files of thelibraryAPPNAME_CINCLUDES APPNAME_CINCLUDES-y - Includes for C files of thelibraryAPPNAME_CXXINCLUDES APPNAME_CXXINCLUDES-y - Includes for C++ files of thelibraryAPPNAME_ASINCLUDES APPNAME_ASINCLUDES-y - Includes for assembly files ofthe libraryAPPNAME_FILENAME_FLAGS - Flags for a *specific* source fileAPPNAME_FILENAME_FLAGS-y of the library (not exposed to itsvariants)APPNAME_FILENAME_INCLUDES - Includes for a *specific* sourceAPPNAME_FILENAME_INCLUDES-y file of the library (not exposedto its variants)APPNAME_FILENAME_VARIANT_FLAGS - Flags for a *specific* source fileAPPNAME_FILENAME_VARIANT_FLAGS-y and variant of the libraryAPPNAME_FILENAME_VARIANT_INCLUDES - Includes for a *specific* sourceAPPNAME_FILENAME_VARIANT_INCLUDES-y file and variant of the library
Unikraft's configuration system is based on Linux's KConfig system.
With Config.uk
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 Makefile.uk
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 Makefile.uk
).
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 dependenciesconfig APPHELLOWORLD_DEPENDENCIESbooldefault y# dependencies with `select`:select LIBNOLIBC if !HAVE_LIBCselect LIB1select 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/Config.uk
.
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 configurationconfig APPHELLOWORLD_OPTIONbool "Boolean Option 1"default yhelpHelp 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 exportsyms.uk
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
exportsyms.uk
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 Makefile.uk
to add:
LIBYOURAPPNAME_SRCS-$(CONFIG_LIBYOURAPPNAME) += $(LIBYOURAPPNAME_BASE)/extra.ld
An example context of extra.ld
:
SECTIONS{.uk_fs_list : {PROVIDE(uk_fslist_start = .);KEEP (*(.uk_fs_list))PROVIDE(uk_fslist_end = .);}}INSERT AFTER .text;
This will add the section .uk_fs_list
after the .text
section.
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.
Feel free to ask questions, report issues, and meet new people.