Compilacion en masa

En la entrada Emergiendo expliqué como generar un entorno de compilación cruzada basado en Gentoo para poder compilar cosas para nuestro disco duro, pero luego, en FTP-ando, eché todo por tierra al no poner binarios creados por mí. Ahora ya he sido capaz de compilar, así que, para los impacientes, aquí está el paquete con las últimas versiones de CTorrent (3.2.2), BFTP (2.6) y YAOD (0.7.2). Las instrucciones de instalación son las mismas que en el artículo anterior (FTP-ando). Como gran ventaja, algunos fallos de BFTP han desaparecido, y encima la tasa de transferencia ha subido a 3MBytes/seg.

AVISO: a partir de aquí empiezo a tocar en el sistema operativo del disco duro multimedia, lo que significa que estas acciones sólo las deben realizar aquellos que sepan muy bien lo que hacen. Si alguien se carga su disco, será el único responsable.

Pasando ya a la chicha, la razón de que haya tardado tanto es que la cosa no es tan sencilla, por varias razones:

  • Para empezar, el disco duro tiene su propia versión de la uClibc, por lo que la única manera de garantizar que un programa funcione correctamente es compilarlo contra esa misma versión.
  • Por otro lado, en algunas versiones del compilador GCC cambia el fomato binario de las bibliotecas en C++, por lo que también es conveniente utilizar la misma versión de compilador GCC: para garantizar que la interfaz binaria entre la aplicación y las bibliotecas es la misma.
  • Por último, muchos paquetes no se pueden instalar en un sistema mediante compilación cruzada porque su configure hace cosas raras, como comprobar si GCC compila correctamente un programa de prueba… programa que estará en arquitectura MIPS y, por tanto, no correrá en un Intel, abortando el proceso de compilación.

Y si ya la cosa se veía complicada, encima nos encontramos con que Gentoo no tiene la versión de GCC utilizada en nuestro disco duro (la 3.4.4), y que algunas versiones de uClibc (entre ellas la 0.9.28.3, la de nuestro disco duro) no compilan con según qué compiladores o versiones de binutils. El resultado era desalentador.

La primera idea que consideré para resolver el problema fue meter un sistema Gentoo completo en el disco y ejecutar en él las aplicaciones usando chroot. Por desgracia esta solución es muy ineficiente porque necesita mucho espacio en disco y porque las aplicaciones sólo tendrían acceso a los directorios contenidos dentro del chroot, pero nunca a los de fuera.

La segunda idea consistía en intentar generar un conjunto nativo de compilador GCC, binutils y biblioteca uClibc con las versiones correctas a partir de fuentes oficiales. Por desgracia, el proceso es demasiado complejo y tampoco funciona con todas las versiones de GCC, binutils y uClibc que interesaba.

La tercera idea era usar BuildRoot, el entorno de generación de compiladores cruzados de BusyBox. Por desgracia, una vez más, no sólo no tenía las versiones necesarias, sino que tampoco compilaba cualquier combinación.

Por último, la cuarta, que ha sido la que decidí emplear, consiste en compilar estáticamente los programas deseados utilizando un compilador nativo en el propio disco duro multimedia. De esta manera, al no depender de ninguna biblioteca de enlazado dinámico, el programa funcionará en cualquier sistema, ahorrándonos el problema de la incompatibilidad de versiones. Además, al no importar la versión de binutils, GCC ni uClibc, podemos utilizar versiones más modernas, que siempre tendrán mayor seguridad y fiabilidad. Por último, al compilarse en el sistema de manera nativa, no habrá problemas con ficheros configure que hagan cosas “raras”. Como inconveniente, necesita más memoria que un programa compilado dinámicamente.

Sin embargo, seguía con el problema de conseguir un toolchain para MIPS que funcionase correctamente, cosa compleja porque en Gentoo hay nueve versiones de GCC, cuatro de binutils y tres de uClibc (sin contar revisiones), y no todas las combinaciones compilan. Además, no bastaba con el toolchain, sino que también necesitaba instalar en nativo Bash, GCC, binutils, uClibc, make y el entorno básico con BusyBox. ¿Por donde empezar? Por desgracia la versión más reciente no compilaba, y no podía probar las 108 combinaciones posibles… O al menos, no a mano, así que hice un pequeño script en python que probase a compilar todas las posibles combinaciones. Lo dejé una noche, y el resultado fue que sólo compilaba con el GCC 3.4.6-R2 y GCC 4.1.2, las Binutils 2.18-R4, 2.19.1-R1 o 2.20, y sólo con uClibc 0.9.30.1-R1. En el resto de casos, o fallaba la generación del toolchain para compilación cruzada, o fallaba la compilación de las utilidades nativas.

Una vez descubiertas las versiones, sólo fue necesario generar un toolchain adecuado para compilación cruzada:

crossdev --target mipsel-unknown-linux-uclibc --without-headers --gcc 4.1.2 --binutils 2.20 --libc 0.9.30.1-r1
emerge-wrapper --init

Luego, compilar e instalar las aplicaciones básicas necesarias para poder compilar (lo he cortado por razones de espacio, pero es una única línea):

emerge-mipsel-unknown-linux-uclibc  =binutils-2.20 =gcc-4.1.2 =uclibc-0.9.30.1-r1 bash make gzip bzip2 
unzip tar gnuconfig m4 sys-apps/texinfo gawk coreutils sed grep shadow procps net-tools iputils 
debianutils cpio attr sys-apps/acl kbd nano less diffutils

Vemos que instalamos las mismas versiones de binutils, gcc y uclibc que las usadas para generar la cadena de compilación cruzada. Instalamos coreutils y demás en lugar de busybox (o system) porque las utilidades de éste son versiones recortadas y dan problemas con algunas compilaciones. También instalamos tar, gnuconfig, m4 y texinfo, los cuales serán necesarios para instalar autoconf, automake y libtool. La razón de no instalar estos últimos directamente es que la instalación de Perl por compilación cruzada no funciona; tendremos que hacerlo luego a mano.

Por último, creamos los directorios /proc, /dev y /sys en /usr/mipsel-unknown-linux-uclibc, y ya podemos comprimir ese directorio utilizando

tar -cpvf buildchain.tar /usr/mipsel-unknown-linux-uclibc

copiarlo en el disco duro, en /tmp/hdd/volumes/HDD1, descomprimirlo, y lanzar un entorno de compilación utilizando este script, que automáticamente montará mediante bind los directorios del sistema y utilizará chroot para cambiar al nuevo entorno. Ahora nos bajamos Perl, lo descomprimimos y lo compilamos con

sh Configure -de
make
make install

Cabe recordar que el procesador de este disco duro es MUY LENTO, por lo que es posible que necesite toda la noche para compilarlo. Por otro lado, podemos tener problemas con las fechas de los archivos, por lo que es fundamental cambiarlas utilizando el comando touch * en cada directorio. Esto se puede hacer más cómodamente con la línea

find -type d -exec touch {}/* ;

que hará un touch * de manera recursiva en cada directorio. Una vez hecho esto, instalamos autoconf, automake y libtool (no olvidarse de touch en cada uno), y ya tendremos nuestro sistema de desarrollo completo con el que podremos compilar cualquier utilidad. Sólo hay que tener en cuenta algunos detalles:

  • El procesador del disco duro no es gran cosa, así que la compilación tarda BASTANTE tiempo
  • Es fundamental añadir la opción -static en las opciones de enlazado. En algunos programas es posible hacerlo añadiendo LDFLAGS=-static justo antes de ./configure (ejemplo: para configurar ctorrent, la línea sería LDFLAGS=-static ./configure); otra opción es lanzar primero ./configure y luego editar el Makefile para añadirlo donde se defina LDFLAGS. Como opción final, se puede compilar normalmente y, cuando termine, copiar y pegar las líneas de enlazado (suele ser la última) y añadir -static a mano antes de ejecutarla.
  • Es muy recomendable eliminar el parámetro -g durante la compilación, pues lo que hace es añadir información de depuración totalmente innecesaria, además de impedir que el compilador pueda aplicar muchas optimizaciones. Para hacer esto no queda más remedio que editar manualmente el fichero Makefile (no olvidar que puede haber varios Makefiles en otros subdirectorios. Hay que editarlos todos). También es bueno utilizar el comando strip en el ejecutable final para eliminar los últimos restos de datos de depuración
  • Algunos programas están preparados para versiones de autoconf o automake anteriores a la que hemos instalado. En ese caso basta con hacer un autoreconf para regenerar los ficheros. Este debe hacerse después de autogen.sh si lo hubiera. Una vez hecho esto ya se puede ejecutar ./configure y make.
  • Es necesario ajustar las fechas de los archivos porque, si no, Make se lía y no sabe qué compilar. Para ello basta con ejecutar el comando touch * en todos y cada uno de los directorios del programa a compilar (la línea find -type d -exec touch {}/* ; lo hará de manera recursiva por nosotros). No recomiendo cambiar la hora del disco duro porque, por alguna razón, las veces que lo hice el sistema se volvió inestable, llegando a colgarse incluso.
  • En algunos programas, libtool “se vuelve loco” y empieza a lanzar copias de sí mismo indiscriminadamente, llegando a ahogar al procesador y colgando el disco duro. Si, cuando va a enlazar el código, un PS nos devuelve una gran cantidad de procesos libtool, hay que matarlos rápidamente y enlazar “a mano” el código.

Para aquellos que no quieran pasar todo el trabajo, pueden bajarse el entorno de compilación completo aquí. Basta con descomprimir el .zip, copiar el .tar en el directorio /tmp/hdd/volumes/HDD1 del disco duro y descomprimirlo mediante

tar -xpvf buildchain_1.0.tar

entrar en el directorio con cd buildchain, montar los sistemas de archivos virtuales (proc, dev y sys) con ./mount.sh y lanzar un chroot con ./launch.sh. Una vez se haya terminado, salir y desmontar los sistemas de archivos virtuales con ./umount.sh.

Leave a Reply

Time limit is exhausted. Please reload CAPTCHA.


Utilizamos cookies para garantizar que tenga la mejor experiencia en nuestro sitio web.