Skip to content

Commit 0a199c7

Browse files
committed
feat(guides): Add guide for porting external libraries
Introduce a new guide in `library-porting.mdx` explaining the process of porting external libraries to Unikraft. Additionally, reformat `basic-porting.mdx` and `library-porting.mdx` to adhere to the linter's requirement of a single sentence per line. Signed-off-by: Stefan Stancu <stefanstancu2006@gmail.com>
1 parent bec37f6 commit 0a199c7

2 files changed

Lines changed: 202 additions & 41 deletions

File tree

content/guides/basic-porting.mdx

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
2-
32
title: Porting Application to Unikraft
43
description: |
5-
We explore how to port an application on top of Unikraft.
4+
We explore how to port a new application on top of Unikraft.
65
---
76

87
As you have seen in the previous sessions, there are several applications already ported that you can use with Unikraft.
@@ -33,16 +32,16 @@ fi
3332
check_exists_and_create_symlink "libs/musl"
3433
```
3534

36-
1. Re-run the `setup.sh` script again, you should have a new directory: `workdir/libs/musl`.
35+
Re-run the `setup.sh` script again, you should have a new directory: `workdir/libs/musl`.
3736

3837
```console
3938
$ ls workdir/libs/musl
4039
abort.c Makefile.rules Makefile.uk.musl.errno Makefile.uk.musl.locale ......
4140
```
4241

43-
1. In the `Makefile`, change `UK_LIBS` to `UK_LIBS ?= $(LIBS_BASE)/musl`.
44-
After all that, when you run `make menuconfig`, you should see `musl: A C standard library` under `Library Configuration -->`.
42+
In the Makefile, change `UK_LIBS` to `UK_LIBS ?= $(LIBS_BASE)/musl`.
4543

44+
After all that, when you run `make menuconfig`, you should see `musl: A C standard library` under Library Configuration.
4645
After all that, we can start the actual porting.
4746
In order to not get confused between multiple files, we will create two directories, `include/` and `src/`, where we will copy the header and source files that we need.
4847

@@ -51,7 +50,7 @@ $ mkdir include/
5150
$ mkdir src/
5251
```
5352

54-
After that, we can clone the `coreutils` and extract the files necessary for `echo`:
53+
After that, we can clone the coreutils and extract the files necessary for echo:
5554

5655
```console
5756
$ git clone https://github.com/coreutils/coreutils coreutils
@@ -67,28 +66,26 @@ APPCHELLO_CINCLUDES-y += -I$(APPCHELLO_BASE)/include
6766
```
6867

6968
This tells the build system to use the `echo.c` file as a source file, and the `include/` directory as a path to search for header files.
69+
After all this is done, we can try to run make.
7070

71-
After all this is done, we can try to run `make`.
7271
We will receive a lot of build errors, as expected.
7372
We need to solve them one by one.
74-
7573
First, we will receive some errors about missing headers, like `config.h`, `timespec.h`, etc.
76-
To solve this, we will use the very blunt approach, `kill them all`.
74+
To solve this, we will use the very blunt approach, kill them all.
7775
We remove all the `#include` lines from the `echo.c` file, and go from there.
7876

79-
**Note** that this is obviously a bad idea for most applications.
80-
We should find the headers and copy them, but in our case, since `echo` does not need much, we can figure out what to add on the way.
81-
77+
Note that this is obviously a bad idea for most applications.
78+
We should find the headers and copy them, but in our case, since echo does not need much, we can figure out what to add on the way.
8279
For now, we leave only the `stdio.h` and `sys/types.h` as include statements.
8380
We will add more of them later.
8481

8582
Now, we receive some errors regarding undeclared things:
8683

87-
```text
88-
/projects/unikraft/catalog-core/c-hello/src/echo.c:29:30: error: false undeclared here (not in a function)
84+
```plaintext
85+
/projects/unikraft/catalog-core/c-hello/src/echo.c:29:30: error: 'false' undeclared here (not in a function)
8986
29 | enum { DEFAULT_ECHO_TO_XPG = false };
90-
/projects/unikraft/catalog-core/c-hello/src/echo.c:37:21: error: EXIT_SUCCESS undeclared (first use in this function)
91-
37 | affirm (status == EXIT_SUCCESS);
87+
/projects/unikraft/catalog-core/c-hello/src/echo.c:37:21: error: 'EXIT_SUCCESS' undeclared (first use in this function)
88+
37 | affirm (status == EXIT_SUCCESS);
9289
......
9390
```
9491

@@ -103,16 +100,16 @@ We can add some headers now.
103100
This way we get rid of some of the undefined symbols.
104101
Next, we see some weird undefined functions called in the `usage()` function:
105102

106-
```text
107-
/projects/unikraft/catalog-core/c-hello/src/echo.c:39:3: warning: implicit declaration of function affirm [-Wimplicit-function-declaration]
108-
39 | affirm (status == EXIT_SUCCESS);
103+
```plaintext
104+
/projects/unikraft/catalog-core/c-hello/src/echo.c:39:3: warning: implicit declaration of function 'affirm' [-Wimplicit-function-declaration]
105+
39 | affirm (status == EXIT_SUCCESS);
109106
| ^~~~~~
110-
/projects/unikraft/catalog-core/c-hello/src/echo.c:41:11: warning: implicit declaration of function ‘_’ [-Wimplicit-function-declaration]
111-
41 | printf (_("\
107+
/projects/unikraft/catalog-core/c-hello/src/echo.c:41:11: warning: implicit declaration of function '_' [-Wimplicit-function-declaration]
108+
41 | printf (_("\
112109
| ^
113110
```
114111

115-
Let's skip the usage function, we can just have the usage function exit.
112+
Let's skip the `usage` function, we can just have the `usage` function exit.
116113

117114
```c
118115
void
@@ -123,7 +120,7 @@ usage (int status)
123120
```
124121
125122
Finally, we only have one screen of errors to solve.
126-
We get an `LC_ALL undeclared` error, we need to `#include <locale.h>`.
123+
We get an `LC_ALL` undeclared error, we need to `#include <locale.h>`.
127124
128125
There are some more GNU-specific functions, like `version_etc`, `proper_name`, `bindtextdomain`, etc.
129126
They have to do just with the program metadata, like authors, licensing and versioning, so we can delete them (the following lines:)
@@ -143,8 +140,7 @@ We can also remove the `FALLTHROUGH` line from the main switch statement, since
143140

144141
Finally, when we try to `make` again, we only have 3 main errors left: undefined reference to `STREQ`, undefined reference to `close_stdout`, and implicit declaration of `c_isxdigit`.
145142
Other than that, there are the `FALLTHROUGH` warnings from above.
146-
147-
For the `STREQ`, we can search that in the `coreutils` repo:
143+
For the `STREQ`, we can search that in the coreutils repo:
148144

149145
```console
150146
$ grep -r STREQ coreutils
@@ -160,7 +156,6 @@ This makes sense, it's just a wrapper over `strcmp`, so let's copy it into our s
160156
```
161157
162158
We also need to `#include <string.h>`, since we use `strcmp`.
163-
164159
The `close_stdout` function is nowhere in the `coreutils/` repo, so we can assume that it's a function that closes the standard output descriptor, since it's called at the program exit.
165160
We add it to our source file:
166161
@@ -172,12 +167,10 @@ void close_stdout(void)
172167
```
173168

174169
For this, we need to also include `unistd.h`.
175-
176170
Now, the only thing left is the `c_isxdigit` function.
177-
Again, it's not found in the `coreutils` repository, but we can assume it's just the `isxdigit` function from libc, so we replace `c_isxdigit` with `isxdigit` and we include the `ctype.h` header.
178-
171+
Again, it's not found in the coreutils repository, but we can assume it's just the `isxdigit` function from libc, so we replace `c_isxdigit` with `isxdigit` and we include the `ctype.h` header.
179172
With all of this, our application finally builds.
180-
We can run it as usual, using `qemu`:
173+
We can run it as usual, using qemu:
181174

182175
```console
183176
$ qemu-system-x86_64 -nographic -kernel workdir/build/c-hello_qemu-x86_64
@@ -193,20 +186,16 @@ oOo oOO| | | | | (| | | (_) | _) :_
193186
[ 2.469429] Info: [libukboot] <boot.c @ 498> Environment variables:
194187
[ 2.469967] Info: [libukboot] <boot.c @ 500> PATH=/bin
195188
[ 2.470469] Info: [libukboot] <boot.c @ 506> Calling main(1, ['workdir/build/c-hello_qemu-x86_64'])
196-
(an empty newline here)
197189
```
198190

199191
This will lead to nothing being printed, as we would run `echo` with no arguments.
200192
To pass arguments to our application, we can use the `-append` flag:
201193

202194
```console
195+
203196
$ qemu-system-x86_64 -nographic -kernel workdir/build/c-hello_qemu-x86_64 -append "Hello from Unikraft"
204197
Powered by
205-
o. .o _ _ __ _
206-
Oo Oo ___ (_) | __ __ __ _ ' _) :_
207-
oO oO ' _ `| | |/ / _)' _` | |_| _)
208-
oOo oOO| | | | | (| | | (_) | _) :_
209-
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
198+
[... Unikraft Banner ...]
210199
Pan 0.19.0~9603a4ab
211200
[ 2.470131] Info: [libukboot] <boot.c @ 472> Pre-init table at 0x253148 - 0x253148
212201
[ 2.470999] Info: [libukboot] <boot.c @ 483> Constructor table at 0x253148 - 0x253148
@@ -223,10 +212,10 @@ So finally, `echo` works.
223212
Using the same steps, try to port the `pwd` command, located in `pwd.c`.
224213
Some tips:
225214

226-
* `#define nullptr NULL`
227-
* `typedef long int idx_t;`
228-
* You can remove everything related to `robust_getcwd`, since Unikraft is POSIX-compatible, so it will not use those functions.
229-
* You can change the weird `x*alloc` functions to simple `malloc`s.
215+
- `#define nullptr NULL`
216+
- `typedef long int idx_t;`
217+
- You can remove everything related to `robust_getcwd`, since Unikraft is POSIX-compatible, so it will not use those functions.
218+
- You can change the weird `x*alloc` functions to simple `malloc`s.
230219

231220
When you run the unikernel, it should print `/`, as it is in the root directory.
232-
You can use a filesystem and change the working directory to test that it works fine, you can see the `nginx` example on how to run an application using a filesystem.
221+
You can use a filesystem and change the working directory to test that it works fine, you can see the nginx example on how to run an application using a filesystem.

content/guides/library-porting.mdx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
---
2+
title: Porting External Libraries to Unikraft
3+
description: |
4+
We explore how to port external library dependencies on top of Unikraft.
5+
---
6+
7+
## Porting External Libraries
8+
9+
Being a library operating system, Unikraft unikernels are mainly a collection of internal and external libraries, alongside the ported application.
10+
As a consequence, a large library pool is mandatory in order to make the project compatible with as many applications as possible.
11+
12+
An application ported on top of Unikraft is essentially a library with an already provided main() function.
13+
External libraries require files specific to the Unikraft build system (Config.uk, Makefile.uk, Makefile) together with glue code that might be missing from the Unikraft ecosystem.
14+
15+
## Directory Structure
16+
17+
External libraries should be placed in the `.unikraft/libs` folder.
18+
The standard working directory structure looks like this:
19+
20+
```plaintext
21+
app-directory/
22+
|-- Makefile
23+
|-- Makefile.uk
24+
`-- .unikraft/
25+
|-- libs/
26+
| `-- lib-mylib/
27+
`-- unikraft/
28+
```
29+
30+
## Examining a Ported Library: lib-libhogweed
31+
32+
Let's examine core components of an existing port, such as lib-libhogweed.
33+
34+
### Glue Code
35+
36+
Sometimes, an external library's requirements are not fully present in Unikraft.
37+
The solution is to add "glue code" manually to the library's sources.
38+
Glue code is also useful when a library includes test modules; you can wrap them into a single function that Unikraft can invoke.
39+
40+
### Configuration: Config.uk
41+
42+
The `Config.uk` file defines the menu configuration for `make menuconfig`:
43+
44+
```plaintext
45+
menuconfig LIBHOGWEED
46+
bool "libhogweed - Public-key algorithms"
47+
default n
48+
select LIBMUSL # Include dependencies
49+
```
50+
51+
You can also create auxiliary menus for test cases:
52+
53+
```plaintext
54+
config TESTSUITE
55+
bool "testsuite - tests for libhogweed"
56+
default n
57+
if TESTSUITE
58+
config TEST_X
59+
bool "test x functionality"
60+
default y
61+
endif
62+
```
63+
64+
### Build Rules: Makefile.uk
65+
66+
The `Makefile.uk` sets header search paths and can trigger configuration scripts like `./configure`:
67+
68+
```makefile
69+
# Include glue header paths
70+
LIBHOGWEED_COMMON_INCLUDES-y += -I$(LIBHOGWEED_BASE)/include
71+
72+
# Trigger internal configuration scripts
73+
$(LIBHOGWEED_EXTRACTED)/config.h: $(LIBHOGWEED_BUILD)/.origin
74+
$(call verbose_cmd,CONFIG,libhogweed: $(notdir $@), \
75+
cd $(LIBHOGWEED_EXTRACTED) && ./configure --enable-mini-gmp \
76+
)
77+
LIBHOGWEED_PREPARED_DEPS = $(LIBHOGWEED_EXTRACTED)/config.h
78+
```
79+
80+
## Practical Work: Porting kdtree
81+
82+
Now that you understand the process of porting libraries, let's practice by porting the kdtree library.
83+
This exercise will help you apply what you've learned about the Unikraft build system and glue code.
84+
85+
### Step 1: Set up the Library Structure
86+
87+
First, define helper variables in your `Makefile.uk` to point to the extracted archive.
88+
After running `make prepare`, the library content will be under `build/libkdtree/origin/`.
89+
Use variables to reference these paths consistently:
90+
91+
```makefile
92+
LIBKDTREE_BASE = $(UK_LIBS)/libkdtree
93+
LIBKDTREE_EXTRACTED = build/libkdtree/origin
94+
```
95+
96+
### Step 2: Add Headers to the Build
97+
98+
Identify where the library's header files are located and add them to the build system's include paths:
99+
100+
```makefile
101+
LIBKDTREE_COMMON_INCLUDES-y += -I$(LIBKDTREE_EXTRACTED)/include
102+
```
103+
104+
This tells the build system where to find the library's public headers.
105+
106+
### Step 3: Register Source Files
107+
108+
Add the library's C source code files to the build system.
109+
Identify the core source files and register them in `Makefile.uk`:
110+
111+
```makefile
112+
LIBKDTREE_SRCS-y += $(LIBKDTREE_EXTRACTED)/src/kdtree.c
113+
LIBKDTREE_SRCS-y += $(LIBKDTREE_EXTRACTED)/src/kdutil.c
114+
```
115+
116+
### Step 4: Verify Initial Compilation
117+
118+
Run `make` to check if there are any compilation errors:
119+
120+
```console
121+
$ make
122+
```
123+
124+
If you encounter unresolved dependencies, try adding `musl` to your `Config.uk`:
125+
126+
```plaintext
127+
select LIBMUSL
128+
```
129+
130+
A successful compilation will show libkdtree objects in the build output.
131+
132+
### Step 5: Add Test Configurations
133+
134+
If the library includes test cases, add configuration variables for them in `Config.uk`:
135+
136+
```plaintext
137+
config LIBKDTREE_TESTS
138+
bool "kdtree tests"
139+
default n
140+
```
141+
142+
### Step 6: Register Test Sources
143+
144+
In your `Makefile.uk`, register the test source files.
145+
Use compiler flags (`-D`) to rename main functions and avoid conflicts:
146+
147+
```makefile
148+
LIBKDTREE_SRCS-$(CONFIG_LIBKDTREE_TESTS) += $(LIBKDTREE_EXTRACTED)/tests/test_kdtree.c
149+
LIBKDTREE_CFLAGS-$(CONFIG_LIBKDTREE_TESTS) += -Dmain=test_kdtree_main
150+
```
151+
152+
### Step 7: Integrate Tests with Your Application
153+
154+
In your application's `main.c`, call the test wrapper function:
155+
156+
```c
157+
#ifdef CONFIG_LIBKDTREE_TESTS
158+
extern int test_kdtree_main(void);
159+
test_kdtree_main();
160+
#endif
161+
```
162+
163+
### Success Criteria
164+
165+
When you run the unikernel with the tests enabled, you should see output like:
166+
167+
```plaintext
168+
Total tests : 2
169+
Total errors: 0
170+
```
171+
172+
This indicates that the library has been successfully ported and all tests are passing.

0 commit comments

Comments
 (0)