En la entrega anterior de esta mini-serie hemos visto como utilizar un toolchain para compilar pequeños programas que hacen uso de la librería C estándar... Pero como podemos proceder cuando nuestro programa utiliza otras librerías?
Pues bien, eso es de lo que vamos a hablar en esta ocasión. Para poder desvelar todos los secretos de la compilación cruzada en entornos complejos (vamos cuando tenemos que usar muchas librerías), lo primero que vamos a hacer es crear una librería. Menuda sorpresa no?
Una librería de Ejemplo
Vamos a generar una sencilla librería con dos funciones. Crearemos un fichero llamadomilib.c
y añadiremos el siguiente código.
#include <stdio.h> int func1 () { printf ("Esta el la funcion1 de mi librería\n"); return 1; } int func2 () { printf ("Esta el la funcion2 de mi librería\n"); return 2; }También necesitaremos un pequeño fichero de cabecera para poder utilizar esta librería en nuestros programas. Aquí lo tenéis:
#ifndef MILIB_H #define MILIB_H #ifdef __cplusplus extern "C" { #endif int func1 (); int func2 (); #ifdef __cplusplus } #endif #endifLo típico verdad?. Nada que comentar. Circulen, circulen. Ahora solo necesitamos un pequeño
Makefile
para poder generar nuestra librería. Lo hemos llamado Makefile.manual
. Algo como esto:
all: libmilib.so libmilib.so: milib.c ${CC} -shared -fPIC -o $@ $< .PHONY: install: cp libmilib.so /usr/lib cp milib.h /usr/includeComo podéis ver, hemos utilizado una variable para llamar al compilador de forma que nos resulte sencillo compilar la librería para otras plataformas.
$ CC=arm-linux-gnueabihf-gcc make -f Makefile.manual $ file libmilib.so libmilib.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=d3d93d5b8d512882c52f6fe131a242e0550d9d91, not strippedEt voilá!... Nuestra librería está lista para ser utilizada en nuestro cacharro ARM preferido.
Probando la Librería
Para poder probar si nuestra librería ha sido correctamente generada, vamos a escribir un pequeño programa que la utilice.#include <stdio.h> #include <milib.h> int main () { printf ("Programa con librerías\n"); func1 (); func2 (); return 0; }Bien. Ahora solo tenemos que compilar este programa y enlazarlo con la librería que acabamos de generar. Veremos un par de formas de hacerlo, y la primera es utilizando los flags de compilación.
$ arm-linux-gnueabihf-gcc -o main main.c -I. -L. -lmilibPuesto que nuestra librería no se encuentra en ninguno de los directorios estándar donde busca el compilador (
/usr/lib
, /usr/local/lib
,...), lo primero que tenemos que hacer es indicar el directorio en el que debe buscar nuestra librería. El parámetro -L.
le indicará al compilador que debe utilizar el directorio actual para ese menester.
Lo mismo sucede con los ficheros .h. Cuando estos no se encuentran en uno de los directorios del sistema (/usr/include
,...), tenemos que decirle al compilador donde puede encontrarlos. En el programa podríamos haber entrecomillado el #include
para milib.h
, para que el compilador busque el fichero en el directorio actual. En este caso estamos intentando cubrir el caso general, en el que la librería ya está en el sistema y estamos intentando escribir un programa que la utiliza. Vamos el .h es un API público.
Una vez que el compilador sabe donde buscar, ya solo tenemos que indicarle que librería queremos añadir. Esto lo conseguimos con el parámetro -lmilib
.
Ahora ya podemos probar nuestro programa. Copiamos los ficheros binarios (la librería y el programa principal) a nuestro dispositivo ARM (o lo que sea que estéis usando), y ejecutamos el programa con un comando como este:
LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ./main2Como ya sabréis la variable
LD_LIBRARY_PATH
, nos permite añadir directorios a la lista que se utiliza para localizar las librerías dinámicas que utiliza un determinado programa. En este caso, estamos añadiendo el directorio actual antes de ejecutar el programa para indicarle al enlazador dinámico que queremos que busque las librerías en este directorio.
Tal y como lo hemos ejecutado, solo actualizamos el valor de LD_LIBRARY_PATH
para la ejecución del programa. Podemos modificarla permanentemente utilizando export
(en shells tipo bash) o setenv
.
Finalmente, también podríamos copiar la librería en uno de los directorios del sistema (por ejemplo, /usr/local/lib
) y de esta forma no tener que especificar un valor especial para LD_LIBRARY_PATH
. En este caso, debéis ejecutar ldconfig
como root
después de copiar el la librería en el directorio del sistema. De esta forma actualizamos la cache del enlazador dinámico.
Si obtenéis un mensaje como este:
./prog: error while loading shared libraries: libmilib.so: cannot open shared object file: No such file or directoryEso significa que el path es incorrecto o la cache no ha sido actualizada (dependiendo del método que utilicéis).
Un Paso Intermedio
Lo que os acabamos de contar funcionan bien para proyectos pequeños y con pocas dependencias. En cuanto la cosa comienza a crecer, este método manual se hace tedioso y propenso a errores... Tenemos que hacerlo mejor. Pero antes de entrar en modo PRO, vamos a dar un pasito intermedio para introducir un concepto que nos hará más sencillo comprender la última parte de este artículo. Lo primero que vamos a hacer es modificar elmakefile
de nuestra librería. Realmente solo vamos a cambiar la regla de instalación, para que copie los ficheros en un directorio en concreto y no directamente en los directorios del sistema. Algo como esto:
install: mkdir -p /tmp/sysroot/usr/lib mkdir -p /tmp/sysroot/usr/include cp libmilib.so /tmp/sysroot/usr/lib cp milib.h /tmp/sysroot/usr/includeEn este caso estamos utilizando un directorio llamado
/tmp/sysroot
. La verdad es que no importa donde pongáis este directorio, en seguida vamos a ver como hacer todo esto de una forma más elegante. Por ahora, dejémoslo ahí. Cuando ejecutemos make install
, la librería que hemos generado y el fichero de cabecera asociado se copiarán en /tmp/sysroot
, siguiendo la estructura típica del sistema de ficheros raíz
de una máquina GNU/Linux.
Lo que esto nos va a permitir, es compilar nuestro programa con un comando como este:
$ arm-linux-gnueabihf-gcc --sysroot /tmp/sysroot/ -o main1 main.c -lmilibEl parámetro
--sysroot
, nos permite indicarle al compilador que, en lugar de utilizar el sistema de ficheros raíz actual, considere que el sistema de ficheros raíz está en el directorio indicado. En este caso /tmp/sysroot
.
De esta forma, podemos compilar todas las librerías que queramos, instalarlas en nuestro SYSROOT y luego utilizarlas para compilar nuestros programas, prácticamente como si estuviéramos compilando nativamente en la máquina destino.
De la misma forma, si necesitamos compilar un programa que hace uso de alguna librería dependiente de la plataforma, simplemente podemos copiarlas en nuestro SYSROOT, y usarlas normalmente en nuestros programas. Un ejemplo típico de este último caso son las liberías OpenGL que suelen ser cerradas en muchas plataformas, o los ficheros de desarrollo de VideoCore de la Rpi que se suelen encontrar bajo /opt/vc
.
En plan PRO
Ahora que ya sabemos lo que tenemos que hacer vamos a ponernos en plan POFESIONAL. Para ello vamos a utilizar las Autotools de GNU. No, no son herramientas para arreglar el coche... ni son las herramientas que utilizan los Transformers.... Se trata de un conjunto de utilidades que nos permiten automatizar el proceso de construcción de programas. Ya hemos hablado de ellas en otras ocasiones, pero bueno.... vamos a hacer este artículo autocontenido y repetirnos un poco. :) Lo que vamos a hacer es adaptar nuestra librería y nuestro programa para que se compilen utilizando autotoolsi> y luego mostraros como este conjunto de herramientas nos va a simplificar todo el proceso que hemos descrito más arriba. Comenzaremos adaptando la librería. Creamos un directorio llamadomilib
. Lo podéis crear donde queráis, y de hecho podéis llamarlo como queráis... esto no es Java :). En este directorio tendremos que añadir dos ficheros: configure.ac
y Makefile.am
. El primero debería parecerse a este:
AC_INIT([milib], 0.1, [roor@papermint-designs.com]) AM_INIT_AUTOMAKE([-Wall -Werror]) AC_PROG_CC AC_HEADER_STDC() AM_PROG_AR AC_PROG_LIBTOOL AC_OUTPUT(Makefile)Y el segundo, el
Makefile.am
, debería parecerse a esto:
lib_LTLIBRARIES = libmilib.la include_HEADERS=milib.h libmilib_la_SOURCES=milib.cListo. Ahora solo tenemos que añadir unos cuantos ficheros (que pueden estar vacios) y generar el famoso
configure
.
$ touch NEWS README AUTHORS ChangeLog $ autoreconf -ifY listo. Ya podemos cross-compilar nuestra librería para lo que nos de la gana. Para una plataforma ARM con soporte hardware de coma flotante (buff!) sería algo así:
$ ./configure --host=arm-linux-gnueabihf --prefix=/tmp/sysroot/usr $ make && make installMola.... Veamos lo que hemos ganado. Ahora podemos indicar el toolchain a utilizar en el
configure
. Para un proyecto tan chorras como el que hemos utilizado en este ejemplo, esto no parece una gran ventaja... pero bueno, cuando tengáis que crear un proyecto que incluya varios programas y librerías....lo agradeceréis. Lo segundo que hemos ganado, es que podemos instalar nuestro paquete en cualquier sitio utilizando el flag --prefix
. De nuevo, esto parece algo muy fácil de hacer en un Makefile
, normal y corriente... cierto, lo es. Pero con autotools es bastante más fácil, sobre todo cuando tienes ficheros de configuración que va a un directorio, páginas del manual, que van a otro, unos binarios que se instalan y que no (tests unitarios),...
Así que ahora tenemos una forma fácil de compilar nuestra librería y añadirla a un SYSROOT. Observad que todo esto lo podéis utilizar para compilar nativamente en la plataforma... sólo tendréis que ejecutar configure
sin parámetros. Como hacéis normalmente en vuestra máquina GNU/Linux.
Compilando El programa
Ahora ya solo nos queda compilar nuestro binario. Para ello crearemos otro directorio para contener todos los ficheros necesarios para compilar el programa usando autotools y, una vez más, crearemos los ficherosconfigure.ac
y Makefile.am
.
El Makefile.am
es el más sencillo. Aquí lo tenéis.
bin_PROGRAMS = main main_SOURCES=main.c main_LDADD=-lmilibEl configure es un poco más complicado. Al parecer, autotools no ofrece soporte directo para utilizar
sysroot
, o al menos, el que escribe y subscribe no ha sido capaz de encontrarlo. Así que, lo que hemos hecho es añadir ese soporte al configure
code>. Esto hace que el fichero sea un poco más complejo. Veamos como ha quedao.
AC_INIT([milib-test], 0.1, [roor@papermint-designs.com]) AM_INIT_AUTOMAKE([-Wall -Werror]) AC_PROG_CC AC_HEADER_STDC() AC_ARG_WITH(sysroot, AS_HELP_STRING([--with-sysroot], [Directory with the target libraries.]),, withval="") if test "x$withval" != "x" ; then CFLAGS=" --sysroot=$withval ${CFLAGS} " CXXFLAGS=" --sysroot=$withval ${CFLAGS} " fi AC_OUTPUT(Makefile) AC_MSG_RESULT(CFLAGS : ${CFLAGS} ) AC_MSG_RESULT(CXXFLAGS : ${CXXFLAGS} )Lo que hemos hecho es añadir un argumento a
configure
, al que hemos llamado --with-sysroot
. Como podéis ver, autools proporciona macros para manejar este tipo de parámetros e integrarlos en las herramientas (si ejecutáis configure --help
veréis los textos de ayuda que hemos añadido a nuestro configure.ac
).
Aparte de eso, que es totalmente estándar, podéis ver un pequeño script en medio de nuestro fichero. Si el if
hacia el final del fichero, es código shell script corriente y moliente. Simplemente comprueba si la variable $withval
no está vacía. En ese caso, añade el flag --sysroot
a las variables CFLAGS
(código C) y CXXFLAGS
(código C++) que se utilizará durante la compilación, junto con el directorio que se haya pasado como parámetro con el argumento --with-sysroot
.
Al final del fichero, mostramos el valor de los flags de compilación para que podáis comprobar que todo ha funcionado correctamente.
Con todo esto, ahora podemos compilar nuestro programa de la siguiente forma:
$ ./configure --host=arm-linux-gnueabihf --with-sysroot=/tmp/sysroot/ --prefix=/tmp/sysroot $ make && make installDe esta forma, nuestro programa se compilará con el toolchain indicado, utilizando el sistema que hayamos replicado bajo
/tmp/sysroot
y se instalará en ese mismo directorio. Al final solo tendremos que copiar todo lo que se encuentre en nuestro directorio SYSROOT en la plataforma destino y todo listo.
Por cierto, ahora que tenéis vuestra librería y aplicación adaptadas para autotools, podéis ejecutar make dist
para generar un paquete con el código fuente!!!. Sí, así de fácil.