This is the very first time I'm learn about compiling a program to single binary executable especially to distribute them for different machine, for context I'm compiling project written in vlang and I named it vot, I never learn or write C/C++ before so dealing with compilation in vlang is tricky for me.
The problem
For a while, I always got my executable built successfully until one day after I did a system upgrade to my local machine (I use Arch btw) compiling my project as usual then copied the binary to my production server and got this error:
./vot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38`. by ./vot)
Once I got that error as I asked in vlang's discord the problem was my compiled binary linking to newer glibc
than in my production server (newer glibcs usually keep backwards compatibility with older ones, but the reverse is not true).
The solution
We can solve this by compiling directly on machine with the older glibc. Sometimes people use docker containers or jails with an old Debian inside. Or another approach is using Alpine containers, which uses musl by default.
I decide to use Alpine since I need more different type of systems to test my binary executable.
Getting started with Docker
First make sure docker installed of course, then create a new container with alpine:3.17
image, you should have locked version instead of using latest
tag to prevent breaking changes.
docker run --name alpine -it alpine:3.17 /bin/sh
After run above command your system will pull image automatically if your system don't have one and once you logged into Alpine container do upgrade system:
apk update
apk upgrade
apk add git build-base sqlite-dev sqlite-static
This will install core package for your Alpine system, the build-base
is the build tools used for working with C things.
Next is to setup your environment, installing V and cloning the project inside your running Alpine container:
# Installing V
cd /opt && git clone https://github.com/vlang/v && cd v
make
./v self
./v symlink
# Clone project
cd /root
git clone https://codeberg.org/bramaudi/vot && cd vot
v install # installing v project dependencies
The build command
Here is the trick, if you just build with v -prod .
then the output binary will uses shared library and mostly only works with the very similar machine, if not then it will output error like:
zsh: no such file or directory: ./vot
To find what's going on and why the executable is not working you can see the needed library using ldd
:
$ ldd ./vot
linux-vdso.so.1 (0x00007ffd13d0c000)
libsqlite3.so.0 => /usr/lib/libsqlite3.so.0 (0x00007f2f33086000)
libc.musl-x86_64.so.1 => not found
libm.so.6 => /usr/lib/libm.so.6 (0x00007f2f32f99000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f2f32c00000)
/lib/ld-musl-x86_64.so.1 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f2f333a9000)
As you can see we are missing the musl lib which not installed by default in my production server which using Ubuntu, the problem can be solved by installing musl into that machine but this is only works if you actually know what the problem and how to debug.
The proper way to solve this are using static linking when compiling binary in my opinion.
with static linking, you get bigger executables, but the benefit is that they are frequently more portable between linux distros
So instead of just v -prod .
use:
v -prod -cflags -static .
While I running above command then if you're missing some static libraries you will get error like this:
/usr/lib/gcc/x86_64-alpine-linux-musl/12.2.1/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -lsqlite3: No such file or directory
Fortunately at that time I read Alpine Linux: Effortless static linking and portable applications for C/C++ then what I need to do is just installing static library for sqlite, like said in the article;
For third-party libraries, sometimes, Alpine Linux already has a static library package with the name
*-static
, likexz-static
orsqlite-static
, which can be grabbed with theapk add
command.
then I can just install it in my container like so:
apk add sqlite-static
From this point I successfully built a executable that can run both on my local and production server without installing additional package in both system.
In case I need to build binary for different architecture like Termux on Android then just build in it.
UPDATE:
I recently try compiling with my local machine (outside container, use Arch) and got
/usr/bin/ld: cannot find -lsqlite3: No such file or directory
which means I missing the static sqlite library, but I was found out that pacman
doesn't have that, I'm struggling for several hour and finally got the answer for manually do static linking, now I get it why the mentioned article title said word "Effortless".
The static library here are *.a
files found in /usr/lib
folder, those would only be used for compiling software and the solution was copying it from my Alpine container.
The exact missing file caused the error was /usr/lib/libsqlite3.a
, this fixed missing sqlite static library, in case I dealing with the same error in the future but with different library.
Not only effortless, even compile using Alpine container resulting in smaller binary size than in Archlinux machine.
UPDATE 2:
Just found out another way of how to get *.a
file from Alpine repository, browse this link:
https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/
Let say we want sqlite-static
then use find in page and you will get .apk
file, download it and rename to .tar.gz
.