Deploying Yesod to Heroku, can't build statically
Asked Answered
D

4

32

I'm very new to Yesod and I'm having trouble building Yesod statically so I can deploy to Heroku.

I have changed the default .cabal file to reflect static compilation

if flag(production)
   cpp-options:   -DPRODUCTION
   ghc-options:   -Wall -threaded -O2 -static -optl-static
else
   ghc-options:   -Wall -threaded -O0

And it no longer builds. I get a whole bunch of warnings and then a slew of undefined references like this:

Linking dist/build/personal-website/personal-website ...
/usr/lib/ghc-7.0.3/libHSrts_thr.a(Linker.thr_o): In function
`internal_dlopen':
Linker.c:(.text+0x407): warning: Using 'dlopen' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/ghc-7.0.3/unix-2.4.2.0/libHSunix-2.4.2.0.a(HsUnix.o): In
function `__hsunix_getpwent':
HsUnix.c:(.text+0xa1): warning: Using 'getpwent' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/ghc-7.0.3/unix-2.4.2.0/libHSunix-2.4.2.0.a(HsUnix.o): In
function `__hsunix_getpwnam_r':
HsUnix.c:(.text+0xb1): warning: Using 'getpwnam_r' in statically
linked applications requires at runtime the shared libraries from the
glibc version used for linking
/usr/lib/libpq.a(thread.o): In function `pqGetpwuid':
(.text+0x15): warning: Using 'getpwuid_r' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/libpq.a(ip.o): In function `pg_getaddrinfo_all':
(.text+0x31): warning: Using 'getaddrinfo' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/ghc-7.0.3/site-local/network-2.3.0.2/
libHSnetwork-2.3.0.2.a(BSD__63.o): In function `sD3z_info':
(.text+0xe4): warning: Using 'gethostbyname' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/ghc-7.0.3/site-local/network-2.3.0.2/
libHSnetwork-2.3.0.2.a(BSD__164.o): In function `sFKc_info':
(.text+0x12d): warning: Using 'getprotobyname' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/ghc-7.0.3/site-local/network-2.3.0.2/
libHSnetwork-2.3.0.2.a(BSD__155.o): In function `sFDs_info':
(.text+0x4c): warning: Using 'getservbyname' in statically linked
applications requires at runtime the shared libraries from the glibc
version used for linking
/usr/lib/libpq.a(fe-misc.o): In function `pqSocketCheck':
(.text+0xa2d): undefined reference to `SSL_pending'
/usr/lib/libpq.a(fe-secure.o): In function `SSLerrmessage':
(.text+0x31): undefined reference to `ERR_get_error'
/usr/lib/libpq.a(fe-secure.o): In function `SSLerrmessage':
(.text+0x41): undefined reference to `ERR_reason_error_string'
/usr/lib/libpq.a(fe-secure.o): In function `initialize_SSL':
(.text+0x2f8): undefined reference to `SSL_check_private_key'
/usr/lib/libpq.a(fe-secure.o): In function `initialize_SSL':
(.text+0x3c0): undefined reference to `SSL_CTX_load_verify_locations'
(... snip ...)

If I just compile with just -static and without -optl-static everything builds fine but the application crashes when it tries to start on Heroku.

2011-12-28T01:20:51+00:00 heroku[web.1]: Starting process with command
`./dist/build/personal-website/personal-website -p 41083`
2011-12-28T01:20:51+00:00 app[web.1]: ./dist/build/personal-website/
personal-website: error while loading shared libraries: libgmp.so.10:
cannot open shared object file: No such file or directory
2011-12-28T01:20:52+00:00 heroku[web.1]: State changed from starting
to crashed

I tried adding libgmp.so.10 to the LD_LIBRARY_PATH as suggested in here and then got the following error:

2011-12-28T01:31:23+00:00 app[web.1]: ./dist/build/personal-website/
personal-website: /lib/libc.so.6: version `GLIBC_2.14' not found
(required by ./dist/build/personal-website/personal-website)
2011-12-28T01:31:23+00:00 app[web.1]: ./dist/build/personal-website/
personal-website: /lib/libc.so.6: version `GLIBC_2.14' not found
(required by /app/dist/build/personal-website/libgmp.so.10)
2011-12-28T01:31:25+00:00 heroku[web.1]: State changed from starting
to crashed
2011-12-28T01:31:25+00:00 heroku[web.1]: Process exited

It seems that the version of libc that I'm compiling against is different. I tried also adding libc to the batch of libraries the same way I did for libgmp but this results in a segmentation fault when the application starts on the Heroku side.

Everything works fine on my PC. I'm running 64bit archlinux with ghc 7.0.3. The blog post on the official Yesod blog looked pretty easy but I'm stumped at this point. Anyone have any ideas? If there's a way to get this thing working without building statically I'm open to that too.

EDIT

Per Employed Russians answer I did the following to fix this.

First created a new directory lib under the project directory and copied the missing shared libraries into it. You can get this information by running ldd path/to/executable and heroku run ldd path/to/executable and comparing the output.

I then did heroku config:add LD_LIBRARY_PATH=./lib so when the application is started the dynamic linker will look for libraries in the new lib directory.

Finally I created an ubuntu 11.10 virtual machine and built and deployed to Heroku from there, this has an old enough glibc that it works on the Heroku host.

Edit: I've since written a tutorial on the Yesod wiki

Dehydrate answered 28/12, 2011 at 15:40 Comment(0)
S
64

I have no idea what Yesod is, but I know exactly what each of your other errors means.

First, you should not try to link statically. The warning you get is exactly right: if you link statically, and use one of the routines for which you are getting the warning, then you must arrange to run on a system with exactly the same version of libc.so.6 as the one you used at build time.

Contrary to popular belief, static linking produces less, not more, portable executables on Linux.

Your other (static) link errors are caused by missing libopenssl.a at link time.

But let's assume that you are going to go the "sane" route, and use dynamic linking.

For dynamic linking, Linux (and most other UNIXes) support backward compatibility: an old binary continues to work on newer systems. But they don't support forward compatibility (a binary built on a newer system will generally not run on an older one).

But that's what you are trying to do: you built on a system with glibc-2.14 (or newer), and you are running on a system with glibc-2.13 (or older).

The other thing you need to know is that glibc is composed of some 200+ binaries that must all match exactly. Two key binaries are /lib/ld-linux.so and /lib/libc.so.6 (but there are many more: libpthread.so.0, libnsl.so.1, etc. etc). If some of these binaries came from different versions of glibc, you usually get a crash. And that is exactly what you got, when you tried to place your glibc-2.14 libc.so.6 on the LD_LIBRARY_PATH -- it no longer matches the system /lib/ld-linux.

So what are the solutions? There are several possibilities (in increasing difficulty):

  1. You could copy ld-2.14.so (the target of /lib/ld-linux symlink) to the target system, and invoke it explicitly:

    /path/to/ld-2.14.so --library-path <whatever> /path/to/your/executable
    

    This generally works, but can confuse an application that looks at argv[0], and breaks for applications that re-exec themselves.

  2. You could build on an older system.

  3. You could use appgcc (this option has disappeared, see this for description of what it used to be).

  4. You could set up a chroot environment matching the target system, and build inside that chroot.

  5. You could build yourself a Linux-to-olderLinux crosscompiler

Spastic answered 28/12, 2011 at 16:23 Comment(8)
Okay, so I've copied ld-2.14.so into my heroku build along with libc.so.6 and libgmp.so.10 from my system and changed the heroku run command to the one you specified. The application now crashes silently (at least there's no output from the crash in the heroku logs, the state simply changes from starting to crashed). If I run heroku run ./lib/ld-linux-x86-64.so.2 --list /path/to/executable all library dependencies are fullfilled, but most are satisfied by the system libraries. Do I have to copy all libraries use by the application from my system?Dehydrate
You don't have to build statically; from what I understand, even without -static and friends, GHC will bundle the GHC RTS in with your executable (which you need, since Heroku probably doesn't have the GHC runtime available as a lib), unless you explicitly compile with the -dynamic flag. By default, libraries used by the RTS are liked dynamically, which would be wise for your case. See also: #700408Matriculation
I found that out since posting. It looks like the primary problem is the fact that my libc is newer than what's on the Heroku hosts, as Employed Russian suggested.Dehydrate
Thanks Employed russian, I wound up creating an ubunut virtual machine to build on, that seems to work now.Dehydrate
I think this statement is very imprecise: "Contrary to popular belief, static linking produces less, not more, portable executables on Linux." As you know, Linux never breaks API compatbility. Did you mean to say Glibc instead of Linux? I really don't see how this has anything to do with Linux proper.Featheredge
@JanusTroelsen You are correct in that this statement is about GLIBC and not Linux proper. However, 99.99% of Linux user programs are using GLIBC, so your distinction is somewhat academic.Spastic
@EmployedRussian The distinction is not academic; the confusion is introduced by the fact that the asker is not linking "truly statically". For a truly statical link, ldd theproducedbinary should show not a dynamic executable and thus depend on no .so files at all, and there must not be any warnings like warning: Using '...' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking; this can be achieved for example by using musl instead of glibc. If these are true, then statically linked executables are much more portable.Scapegoat
So while it's true that "99.99% of Linux user programs are using GLIBC", for the purpose of building Haskell programs, that number doesn't matter, as the programmer has control of whether they use GLIBC or not. The only thing that remains is: How easy can we make it to link e.g. against musl?Scapegoat
L
5

You have several issues.

You should not build production binaries on bleeding edge distributions. The libraries on the production system will not be forward compatible.

You should not link glibc statically - it will always at runtime try to load additional libraries. For example cpu-based assembly. That is what your first warnings are about.

The last linker errors look like they are related to a missing openssl library on the command line.

But all in all - downgrade your distribution.

Lindholm answered 28/12, 2011 at 16:29 Comment(1)
Given that heroku is still on that old version - which makes sense with the intention to provide a stable platform - one could call glibcs > 2.11 hardly "bleeding edge". Your answer might have made sense in Dec 2011, though.Daph
C
0

I had similar problems launching to Heroku (which uses glibc-2.11) where I had an application that required glibc-2.14, but I did not have access to the source and could not re-build it. I tried many things and nothing worked.

My workaround was to launch the service on Amazon Elastic Beanstalk and just provide an API interface.

Craggie answered 12/7, 2013 at 21:38 Comment(0)
A
0

I found the information provided useful as well, I think the various descriptions miss a critical issue I also ran into while forcing an updated version of Vagrant to start working again.

It's the dependency references internal to something like complicated installs, like Yesod to Heroku. Those interanl refences need to be preserved.

This is the script I wrote to make problems go away (at least, hopefully, for a little while):

#!/bin/bash

cd $HOME/
GLIBC_VERSION="2.17"
GLIBC_PREFIX="/usr/glibc/"
VAGRANT_VERSION="2.2.19"

# Install the basic build system utilities.
yum groupinstall -y "Development tools"
yum install -y curl patchelf

# Grab the tarball with the GNU libc source code.
curl -Lfo glibc-${GLIBC_VERSION}.tar.gz "https://ftp.gnu.org/gnu/glibc/glibc-${GLIBC_VERSION}.tar.gz"
echo "a3b2086d5414e602b4b3d5a8792213feb3be664ffc1efe783a829818d3fca37a  glibc-${GLIBC_VERSION}.tar.gz" | sha256sum -c || exit 1

# Extract the secrets and get ready to rumble.
tar xzvf glibc-${GLIBC_VERSION}.tar.gz

# The configure script requrires an independent build directory.
mkdir -p glibc-build && cd glibc-build

# Configure glibc with a GLIBC_PREFIX so it doesn't conflict with distro libc files..
../glibc-${GLIBC_VERSION}/configure --prefix="${GLIBC_PREFIX}" --libdir="${GLIBC_PREFIX}/lib" \
--libexecdir="${GLIBC_PREFIX}/lib" --enable-multi-arch

# Compile and then install GNU libc.
make -j8 && make install

# Download and install Vagrant.
curl -Lfo vagrant_${VAGRANT_VERSION}_x86_64.rpm "https://releases.hashicorp.com/vagrant/${VAGRANT_VERSION}/vagrant_${VAGRANT_VERSION}_x86_64.rpm"
echo "990e8d2159032915f21c0f1ccdcbca1a394f7937e06e43dc1dabe605d208dc20  vagrant_${VAGRANT_VERSION}_x86_64.rpm" | sha256sum -c || exit 1
yum install -y vagrant_${VAGRANT_VERSION}_x86_64.rpm

# Patch the binaries and shared libraries inside the Vagrant directory, so they use the new version of GNU libc.
(find /opt/vagrant/ -type f -exec file {} \; )| grep "dynamically linked" | awk -F':' '{print $1}' | while read FILE ; do
  patchelf --set-rpath /opt/vagrant/embedded/lib:/opt/vagrant/embedded/lib64:/usr/glibc/lib:/usr/lib64:/lib64:/lib --set-interpreter /usr/glibc/lib/ld-linux-x86-64.so.2 "${FILE}"
done

The script should be pretty easy to understand, and adapt easily to whatever MacGuffin you want to make work, provied you understand it.

The only tricky part is the rpath you pass to patchelf. Upi need to make sure you preserve the search paths, and precedence your software requires. Or you end up fixing one problem only to create another equally frustrating roadblock.

P.S. Don't forget the update the hashes for any file you down. In particular, you need to compile/install a different version of GNU libc, you will need to update that hash to match the version you want to use.

Adventuresome answered 31/3, 2022 at 18:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.