Can't build static binary with libssh2

Hi everyone,

I try to build a static crystal app with the shard spider-gazelle/ssh2.
On Ubuntu 20.04 I have all necessary libs available, but at the end there are a lot of linker errors.
So I decided to use the example code in the crystal language reference below “Fully Static Linking” by using an alpine docker container ("The recommended way to build a statically …).
Because crystallang/crystal:latest-alpine doesn’t contain libssh2-dev and libssh2-static I built a new docker image by the following Dockerfile content:

FROM crystallang/crystal:latest-alpine
RUN apk --update add libssh2-dev libssh2-static

Building the image:
docker build -t cry-static-ssh2 .

Then like described:

$ echo 'require "ssh2"' > ssh2-app.cr
$ docker run --rm -it -v $(pwd):/workspace -w /workspace cry-static-ssh2  crystal build ssh2-app.cr --static

No way. Lots of errors:

/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../..
/lib/libssh2.a(openssl.o): in function `read_private_key_from_memory':
/home/buildozer/aports/main/libssh2/src/libssh2-1.9.0/src/openssl.c:720: undefined reference to `BIO_new_mem_buf'
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /home/buildozer/aports/main/libssh2/src/libssh2-1.9.0/s
rc/openssl.c:727: undefined reference to `BIO_free'
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../..
/lib/libssh2.a(openssl.o): in function `read_private_key_from_file':
/home/buildozer/aports/main/libssh2/src/libssh2-1.9.0/src/openssl.c:743: undefined reference to `BIO_new_file'
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /home/buildozer/aports/main/libssh2/src/libssh2-1.9.0/s
rc/openssl.c:751: undefined reference to `BIO_free'
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../..
/lib/libssh2.a(openssl.o): in function `write_bn':
/home/buildozer/aports/main/libssh2/src/libssh2-1.9.0/src/openssl.c:68: undefined reference to `BN_bn2bin'
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../..
... much more ...

Dynamic linking is no problem at all. But I need a static binary.
What I’m doing wrong?
Thanks in advance.

we do static builds of this in docker containers with the following libs installed

apk add --update --no-cache \
    'apk-tools>=2.10.8-r0' \
    ca-certificates \
    'expat>=2.2.10-r1' \
    iputils \
    'libcurl>=7.79.1-r0' \
    libssh2-static \
    yaml-static

I think libssh2-static without the dev might be the answer?

1 Like

Hi stakach,
thank you very much for the answer and suggestion.
Unfortunately the problem persists.

To reproduce the errors without effort, I created the following “one-liner” :bulb:

docker run -it --rm crystallang/crystal:1.3.2-alpine \
  sh -c \
  "
    apk upgrade;
    apk --update --no-cache add libssh2-static
    
    crystal init app ssh2-app
    cd ssh2-app
    mkdir bin
    echo 'require \"ssh2\"' > src/ssh-app.cr
    echo \"dependencies:
 ssh2:
  github: spider-gazelle/ssh2.cr\" >> shard.yml
    
    shards install
    crystal build --static src/ssh-app.cr -o bin/ssh-app
  "

The result is only lots of error messages (undefined reference…) and no static binary. Does this work for you/anybody?

Additional info:

Host-OS: Ubuntu 20.04.4
Docker version 20.10.7, build 20.10.7-0ubuntu5~20.04.2

got it working!
needs these libs: libssh2-static lz4-dev lz4-static yaml-static and I required http as crystal doesn’t link openssl or deflate libs ect unless required (which is why it’s been working for me without issue)

so I’ll update libssh2 to require the crystal std lib files needed to add the required static libs to the linker script


docker run -it --rm crystallang/crystal:1.3.2-alpine \
  sh -c \
  "
    apk upgrade;
    apk --update --no-cache add ca-certificates libssh2-static lz4-dev lz4-static yaml-static
    
    crystal init app ssh2-app
    cd ssh2-app
    mkdir bin
    echo 'require \"http\"; require \"ssh2\"' > src/ssh-app.cr
    echo \"dependencies:
 ssh2:
  github: spider-gazelle/ssh2.cr\" >> shard.yml
    
    shards install
    crystal build --static -o bin/ssh-app src/ssh-app.cr
  "

Thanks for the one liner btw, made it much easier to debug

1 Like

released v1.5.3 so no need to work around the issue with the extra include anymore

Cool. Thank you very much for this great solution. :+1:
Even my first one-liner now works like a charm.

How does your one-liner example link anything when the program has no code except for requiring http and ssh2. IDK about ssh2, but I suppose it doesn’t run some code that actually uses libssh2 just from requiring it?

The thing with having to require http without using it seems very odd as well. If the ssh2 gem needs some lib bindings from stdlib, it should probably require them explicitly. I don’t expect it needs the actual HTTP module?

It’s because the static version of libssh2 expects extern functions provided by openssl and lib_z
so if you don’t require crystal code that uses these libs then the libraries aren’t linked and the linker complains about missing functions

I’ve updated the ssh2 shard to include the required bits of the std lib for static linking to succeed now.

1 Like

But if only libssh2 requires these libraries for linking against them, then you shouldn’t need the Crystal bindings because you don’t actually use these libraries from Crystal directly, right?
If that’s correct, then you just need to add link flags for these lib dependencies to LibSsh2.

How do you do that?

Just add the respective @[Link] annotations. You can add multiple ones to a single lib type. Or actually, you can write them anywhere in your code, they don’t need to annotate anything really.

2 Likes