Dockerfile example how to compile libcurl for Android inside Docker container

Update: 5 july 2015 – See updated version of dockerfile at the end of post with new NDK / SSL / clang toolchain.

If you never heard of Docker be sure to check it out as fast as possible. There are lot of publications out there. At first it looks like another virtualisation software but it is actually more like new paradigm. Someone may call it very advanced chroot, someone may call virtual containers with version control and building scripts, and so on. I like it as the idea of application-centric containers – your application can keep whole operating system as a coating and its making perfect separation from outside influence. As well you can easily reproduce production process environment at another location. It makes virtualisation easy and fun.

Libcurl in docker

Almost everything can be done inside containers now. Recently i had to recompile curl for Android as static lib using latest NDK toolchain. Its not so complicated to do on your local machine (if it is not Windows) but now there is a more clean way to do this time-wasting operation. You can go to digital ocean, create droplet with Docker and using Dockerfile from the end of this post compile it while drinking coffee.

Dockerfile is simple script to automate container image creation. In our case script will

  • setup compilation tools / utils
  • download sdk/ndk
  • create custom cross-compilation toolchain
  • download source code for libs (zlib, lib curl)
  • setup environment settings for cross compilation
  • configure and make libs
  • gather output at one folder and create the way to get compiled libs

 

So lets create it step by step:

FROM field is describing source OS image. You can specify version of ubuntu or choose something else.

As next step there is installation of compilation tools and some utils. Note that each command inside docker file produces intermediate image which is use like cache if you run your docker file again. This saves you a lot of time when you are tuning or recreating your container image with different options.

Here we download SDK / NDK using official links from Google – you can edit it to more recent versions. Also we create /Android folder and use it as WORKDIR. Any new command will run in this dir.

Extraction of NDK produces more then 4.5GB (for now). Docker aims to make containers as light as possible so watch out for free space at your storage as images can consume it pretty fast if don’t clean up them.

ENV command is setting environment variable. Note that you can’t use export command as you  do in sh scripts to set local compilation variables because every command in Dockerfile produces new image. Here we just set SDK/NDK folders.

As next step we create stand alone compilation toolchain using script from NDK. As parameters you select platform and arch. This is example is for ARM but I’m sure you will modify it with easy to make several builds for couple of arms and x86. After creation of toolchain we add it to PATH env variable.

Here we set ENV variables for cross-compilation. CC – main C compiler.

Here we download last version of ZLib (you can change the link to latest version), extract, configure and make it using –static parameter. And we doing it using newly created android toolchain. As last step we put result libz.a into /Android/output folder where we will put all the results.

The most important step – here we download curl. Configure script of curl has a lot of parameters. I don’t use SSL at the moment so i don’t need to compile OpenSSL (may be i will add it later here). The most important params here are –host=arm-linux-androideabi –target=arm-linux-androideabi as they identify cross-compilation for configuration script.

Also here we setup compilation options CFLAGS / LDFLAGS. You can tune this to your content.

Note – if something goes wrong during ./configure run you can append “|| cat config.log” to the end of line to see your errors during the building of container image.

Last step is to set ENTRYPOINT. We have to provide the result of our compilation to host OS (or we can easily make some web server and host it – but i decided to go first way).

To build container put Dockerfile into some folder on host OS and run

android/curl here is just a name i gave to new created image. The process will consume some time. You can see the list of created images using command: docker images

And we have to run container only to copy output files to host:

Key -v is mounting local ~/Android/output folder to container’s /output folder.

See whole Dockerfile:

So this small script creates all your compilation environment and does the job!

Screenshot 2014-10-28 11.56.35

As you can see if something changes this Dockerfile gives you ability to recompile your libs using different NDK or toolchain pretty fast. Now you can think how to expand this solution to make automatic building of your projects on server side (daily builds). Docker also gives some new freedom for distributed architectures. At the moment i look at CoreOs as perfect environment for containers to live in. But this is subject of separate thread.

Please report any mistakes in comments.

BONUS:

We can compile some more libs the same way – here is Libzip for Android.

UPDATE (5 July 2015) – OpenSSL + CURL using latest NDK with clang3.6 support 

I just put new Dockerfile on github which contains build section for OpenSSL library (libssl.a / lib crypto.a). So it’s pretty easy to build lib curl with SSL which supports HTTPS protocol. Also Docker file uses latest NDK with clang3.6 as building toolchain. This version is for armv7, but i’m sure it not so complicated to change it to other ARCH.

Some important lines from my current Application.mk / Android.mk files (just for example – yours might be different):

This configuration also gives ability to use C++14 on Android! :)

  • Mohamed KALLEL

    A good post. this helped me to build libcurl for android. Thank you

  • Rakesh Desai

    Thanks. This helped me too build libcurl with NDK.
    However I am not able to link libcurl.a to my Android project via build.gradle.
    Do you have any suggestion here?

    • Victor Laskin

      My Android.mk has two lines related to this:
      LOCAL_C_INCLUDES += $(LOCAL_PATH)/libs/curl/include
      LOCAL_LDLIBS := ./libs/libzip.a ./libs/libcurl.a -lz -llog -landroid -lEGL -lGLESv2 -lOpenSLES

      For Gradle its, probably, same params inside ndk{ … } section.

      • Rakesh Desai

        Thanks for the response.
        For now, we are trying to use Android.mk (instead of gradle) & have same values set as above (instead of include folder we have copied .h files to our source folder).
        But we get following error:
        jni/curlrules.h:143:41: error: size of array ‘__curl_rule_01__’ is negative
        jni/curlrules.h:153:53: error: size of array ‘__curl_rule_02__’ is negative

        Did you face something similar?

        • Victor Laskin

          You, probably, dont have curlbuild.h configured. Check that it has #define CURL_SIZEOF_LONG 4 near the bottom.

          • Rakesh Desai

            Yes, we got similar feedback from a forum post & accordingly changed CURL_SIZEOF_LONG to 4.
            This resolved the error.
            Thanks a lot.

          • Guest

            Sorry to bother again.
            With the change in value for SIZEOF_LONG, compilation went thr’ but linking gives error as ‘incompatible target’ & then ‘undefined reference’ errors.
            I think we seem to have some issue with architecture flag.

            objdump gives output as below:
            $ objdump -f libcurl.a
            In archive libcurl.a:
            libcurl_la-file.o: file format elf64-x86-64
            architecture: i386:x86-64, flags 0x00000010:

            Do you think the architecture should be 32 bit instead?
            I tried to build libcurl with –host=arm-32-linux-androideabi in ./configure but still objdump gives architecture as 64 bit.

  • Rakesh Desai

    Pasting as a separate query as earlier comment might not have reached Victor.

    Sorry to bother again.
    With the change in value for SIZEOF_LONG,
    compilation went thr’ but linking gives error as ‘incompatible target’
    & then ‘undefined reference’ errors.
    I think we seem to have some issue with architecture flag.

    objdump gives output as below:
    $ objdump -f libcurl.a
    In archive libcurl.a:
    libcurl_la-file.o: file format elf64-x86-64
    architecture: i386:x86-64, flags 0x00000010:

    Do you think the architecture should be 32 bit instead?
    I tried to build libcurl with –host=arm-32-linux-androideabi in ./configure but still objdump gives architecture as 64 bit.

    • Victor Laskin

      Try this: /android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-objdump -f libcurl.a

      My output:
      In archive libcurl.a:
      libcurl_la-file.o: file format elf32-littlearm
      architecture: arm, flags 0x00000010:
      HAS_SYMS
      start address 0x00000000

      • Rakesh Desai

        For me it seem to be like this…
        /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump -f libcurl.a

        And I get ouput as…

        /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-share.o: File format not recognized
        /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-http_digest.o: File format not recognized
        /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-md4.o: File format not recognized
        …..

        • Victor Laskin

          Are you sure you did not switch the files somewhere? Try to rebuild it in container and check right after it.

          • Rakesh Desai

            Deleted earlier version, rebuilt libcurl & checked immediately after build but the output is same as…

            /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-curl_schannel.o: File format not recognized
            /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-curl_darwinssl.o: File format not recognized
            /Android/android-ndk-r10c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump: libcurl_la-gskit.o: File format not recognized
            ……….

            Please note that I am running the commands directly in Linux VM & not using docker container.
            I have Ubuntu 12.04.2 LTS (GNU/Linux 3.5.0-23-generic x86_64) on my VM.
            Are you using 32-bit Linux?

            It seems that we face issue because of mismatch of 32-bit &

            64-bit.
            In ./configure is the the build param correct as –build=x86_64-unknown-linux-gnu?

          • Victor Laskin

            Then something is wrong with your ENV settings / paths (and you are not actually cross-compiling). Using docker you could avoid such problems and this post was aimed to show this profit as well. Container has 64bit system inside.

          • Rakesh Desai

            okay, I will try to use docker script.

            We are trying to use complied libcurl.a with Android SDK on Windows platform. But I believe that should be fine?

          • Victor Laskin

            yes. If you already compiled something using NDK on Windows there should not be any troubles.

          • Rakesh Desai

            I compiled libcurl.a with docker script & that seem to work fine.

            Output of objdump also matches with your output.
            Thanks a lot.

            This version is compiled for arm architecture & we use Windows machine for Android development which uses x86 emulator. Hence now I’ll try to modify docker script to target x86 arch.

          • Rakesh Desai

            While trying to compile libcurl for X86 modifying docker script suitably, I get following error:

            configure: error: C compiler cannot create executables
            See `config.log’ for more details
            make: *** [all] Error 77

            The docker image gets generated but obviously not libculr.a.
            I think I’ll need some changes in docker script to access config.logs by running docker container?

          • Victor Laskin

            You could try to send me your Dockerfile for X86 build (to email). May be its something obvious.

          • Rakesh Desai

            Have sent in email the Dockerfile for X86 build.

          • Victor Laskin
          • Rakesh Desai

            I was able to build libcurl.a with this docker script but when I tried to use it to compile .so module, I get ‘undefined reference’ error as below.

            [x86] Compile++ : myLib <= main.cpp
            Successfully remade target file obj/local/x86/objs/myLib/main.o'.
            File
            obj/local/x86/objs/myLib/util.o' does not exist.
            Must remake target obj/local/x86/objs/myLib/util.o'.
            [x86] Compile++ : myLib <= util.cpp
            Successfully remade target file
            obj/local/x86/objs/myLib/util.o'.
            Must remake target `obj/local/x86/libmyLib.so'.
            [x86] SharedLibrary : libmyLib.so

            jni/libs/x86/libcurl.a(libcurl_la-netrc.o):netrc.c:function Curl_parsenetrc: error: undefined reference to 'getpwuid_r'
            collect2.exe: error: ld returned 1 exit status
            make.exe: *** [obj/local/x86/libmyLib.so] Error

            Can you suggest what could be the issue here?
            This is similar error which I used to get earlier when compiled libcurl within my own environment.

          • Victor Laskin

            Linker cant find something. The issue is that lib is linked in different enviroment. Maybe some dependant libs should be rebuild with –static-libs, or try -lidn flag first

          • Rakesh Desai

            I am not sure about -lidn flag.
            Do you mean adding –lidn flag to ./configure?
            Something like…
            RUN cd curl-7.38.0 && ./configure –host=arm-linux-androideabi –disable-shared –lidn…..

          • Victor Laskin

            Its linker flag to include lib that is missing inside .a (if that lib exists in your building environment)

  • Rakesh Desai

    As we use SSL, I am trying to compile libcurl with OpenSSL with two modifications to the Doocker script.
    1) Added openssl to the tools list as below:
    RUN apt-get update && apt-get install -y
    automake
    build-essential
    wget
    p7zip-full
    bash
    curl
    openssl

    2) Added ssl switch to ./configure i.e. –with-ssl=/usr/local/ssl/include/openssl
    The final ./configure looks like:
    RUN cd curl-7.38.0 && ./configure –host=arm-linux-androideabi –disable-shared –enable-static –disable-dependency-tracking –with-ssl=/usr/local/ssl/include/openssl –with-zlib=/Android/zlib –without-ca-bundle –without-ca-path –enable-ipv6 –disable-ftp –disable-file –disable-ldap –disable-ldaps –disable-rtsp –disable-proxy –disable-dict –disable-telnet –disable-tftp –disable-pop3 –disable-imap –disable-smtp –disable-gopher –disable-sspi –disable-manual –target=arm-linux-androideabi –build=x86_64-unknown-linux-gnu || cat config.log

    With this I don’t get any error & libcurl does get compiled. However size of compiled version (compared to non-ssl version) does not change & when I try to use it gives ‘unsupported protocol’ error.
    As I am not sure where openssl gets installed in the image, I have given default path in –with-ssl switch.
    Looks like this path is not correct.
    Can you suggest what could be the correct path for –with-ssl?

    • Victor Laskin

      You need to staticly cross-compile open-ssl lib from sources the same way you are compiling curl.

      • Rakesh Desai

        Can I follow Libzip for Android example for it?

        • Victor Laskin
          • Rakesh Desai

            Yes, I was also looking at the same link.
            Let me try out & will get back.

          • Rakesh Desai

            I modified docker script to add few env variables & make openssl as below:

            ENV ARCH_FLAGS -march=armv7 -mfloat-abi=softfp -mfpu=vfpv3-d16
            ENV CPPFLAGS -std=c++11
            ENV LINK $CXX
            ENV ARCH_LINK -march=armv7 -Wl,–fix-cortex-a8
            ENV LDFLAGS ${ARCH_LINK}
            ENV CPPFLAGS ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64
            ENV CXXFLAGS ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 -frtti -fexceptions
            ENV CFLAGS ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64

            # download, configure and make openssl

            RUN curl -O https://www.openssl.org/source/openssl-1.0.2.tar.gz &&
            tar -xzf openssl-1.0.2.tar.gz &&
            mv openssl-1.0.2 openssl
            RUN cd openssl && ./config –static &&
            make &&
            ls -hs . &&
            cp libopenssl.a /Android/output

            ENV CROSS_COMPILE arm-linux-androideabi

            And added “–with-ssl=/Android/openssl” option to ./configure.

            But I keep getting following error inspite of some trials of changing few variables.
            Can’t seem to understand why it keeps complaining about line option ‘-m64’. Any idea?

            Configured for linux-x86_64.
            making all in crypto…
            make[1]: Entering directory /Android/openssl/crypto'
            /usr/bin/perl ../util/mkbuildinf.pl "arm-linux-androideabi-gcc -I. -I.. -I../inc
            lude -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H --static -Wa,--n
            oexecstack -m64 -DL_ENDIAN -DTERMIO -O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_A
            SM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DS
            HA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_AS
            M -DECP_NISTZ256_ASM" "linux-x86_64" >buildinf.h
            arm-linux-androideabi-gcc -I. -I.. -I../include -DOPENSSL_THREADS -D_REENTRANT
            -DDSO_DLFCN -DHAVE_DLFCN_H --static -Wa,--noexecstack -m64 -DL_ENDIAN -DTERMIO -
            O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPEN
            SSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES
            _ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -march=armv7 -mf
            loat-abi=softfp -mfpu=vfpv3-d16 -fpic -ffunction-sections -funwind-tables -fstac
            k-protector -fno-strict-aliasing -finline-limit=64 -c -o cryptlib.o cryptlib.c
            arm-linux-androideabi-gcc: error: unrecognized command line option '-m64'
            make[1]: Leaving directory
            /Android/openssl/crypto’
            make[1]: *** [cryptlib.o] Error 1
            make: *** [build_crypto] Error 1
            INFO[0034] The command [/bin/sh -c cd openssl && ./config –static && make &&
            ls -hs . && cp libopenssl.a /Android/output] returned a non-zero cod
            e: 2

            I have also send docker script in email.

          • Victor Laskin

            Have you tried ./Configure android-armv7 as it was written at stackoverflow as i dont see this line in your Dockerfile?

          • Rakesh Desai

            Yes, initially I used it but it gave error of offending argument as below hence removed from the script later:-

            Step 34 : RUN cd openssl && ./config –static android-armv7 && make && ls -hs .
            && cp libopenssl.a /Android/output
            —> Running in 19bcfa72d7e2
            Operating system: x86_64-whatever-linux2
            Configuring for linux-x86_64
            target already defined – linux-x86_64 (offending arg: android-armv7)
            INFO[0001] The command [/bin/sh -c cd openssl && ./config –static android-armv7
            && make && ls -hs . && cp libopenssl.a /Android/output] returne
            d a non-zero code: 255
            docker@boot2docker:/home/curl_SSL$

          • Victor Laskin

            Looks like a lot of ENV variables must be set – look here: http://wiki.openssl.org/index.php/Android

          • Rakesh Desai

            After setting some more environment variables now compilation seem to happen but it fails in linking.
            Tried various changes in path related variables but can’t get rid of following error:
            /Android/toolchain-arm/bin/../lib/gcc/arm-linux-androideabi/4.9/../../../../arm-
            linux-androideabi/bin/ld: error: cannot find -ldl
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_globallookup: error: unde
            fined reference to ‘dlopen’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_globallookup: error: unde
            fined reference to ‘dlsym’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_globallookup: error: unde
            fined reference to ‘dlclose’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_bind_func: error: undefin
            ed reference to ‘dlsym’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_bind_func: error: undefin
            ed reference to ‘dlerror’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_bind_var: error: undefine
            d reference to ‘dlsym’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_bind_var: error: undefine
            d reference to ‘dlerror’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_load: error: undefined re
            ference to ‘dlopen’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_load: error: undefined re
            ference to ‘dlclose’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_load: error: undefined re
            ference to ‘dlerror’
            ../libcrypto.a(dso_dlfcn.o):dso_dlfcn.c:function dlfcn_unload: error: undefined
            reference to ‘dlclose’
            collect2: error: ld returned 1 exit status
            make[2]: *** [link_app.] Error 1
            make[2]: Leaving directory /Android/openssl/apps'
            make[1]: *** [openssl] Error 2
            make[1]: Leaving directory
            /Android/openssl/apps’
            make: *** [build_apps] Error 1
            INFO[0142] The command [/bin/sh -c cd openssl && ./config –static no-ssl2 no-ss
            l3 no-comp no-hw no-engine && make depend && make && ls -hs . && cp l
            ibssl.a /Android/output] returned a non-zero code: 2

            I have sent latest docker file in email.

  • Rakesh Desai

    Hi Victor,

    We got that every Android OS has libssl and libcrypto libraries that are part of OpenSSL, but not included in NDK for using them. You can pull them via below command:

    $adb pull /system/lib/libssl.so /myndk/platforms/android-19/arch-arm/usr/lib

    $adb pull /system/lib/libcrypto.so /myndk/platforms/android-19/arch-arm/usr/lib

    We are able to pull these arm based libssl.so & libcrypto.so modules.
    Now the question is how can we get them into docker image so that we can use it for libcurl build?

    Thanks
    Rakesh

    • Victor Laskin

      You can use COPY command inside Dockerfile. But i consider this as bad way.

      • Rakesh Desai

        Tried to use COPY as below:
        #Build SSL section starts
        # download, configure and make openssl

        RUN curl -O https://www.openssl.org/source/openssl-1.0.2.tar.gz &&
        tar -xzf openssl-1.0.2.tar.gz &&
        mv openssl-1.0.2 openssl

        copy libssl.so /openssl
        copy libcrypto.so /openssl
        #Build SSL section ends

        And then added –with-ssl=/Android/openssl in ./configure but the compiled libcurl does not seem to link libssl as size of libcurl.a don’t change & also ssl functionality doesn’t work.

        Am I missing something?

  • Pingback: Nifty Things for Week Ending 27 March » Ramblings()

  • jattind

    Hi Victor,

    I am new to docker. I installed docker and I built the docker file. I can see the image in my repo (android/curl latest f4b6bd643336). How do I use the static lib? Is it only available within docker container?