lunes, 2 de diciembre de 2013

SFML y sus dependencias

Los cambios en la interfaz binaria de los objetos compilados con MinGW hicieron que deba recompilar algunas bibliotecas externas. Compilar una biblioteca no siempre es fácil en Windows, y encima con SFML me llevé algunas sorpresas interesantes que vengo a comentar aquí.

Las bibliotecas libres suelen tener sus procesos de construcción basados en alguna herramienta libre para tal fin, como pueden ser los scripts de autotools, o la herramienta cmake. Estas herramientas son comunes en GNU/Linux, pero no en Windows (lo común en Windows creo que sería tener un proyecto de Visual Studio). Entonces, como conseguir un entorno apto para estas compilaciones no es tan directo, siempre que podamos conseguir las versiones ya compiladas de las bibliotecas para Windows, mejor (más fácil y más rápido). Es lo que pasó al principio con SFML. Para armar el complemento de sus primeras versiones (1.5 y 1.6), yo no compilé SFML, sino que bajé la versión compilada que se ofrece en su sitio oficial y le agregué lo necesario para utilizarla en ZinjaI (como las plantillas de proyectos ya configurados y los indices de autocompletado).

Más o menos cuando mingw rompió la compatibilidad hacia atrás de sus binarios empezó a salir a la luz la segunda versión estable de SFML, y para esta versión utilizaron el nuevo mingw. Entonces, pude hacer lo mismo con SFML-2.0, descargarla ya compilada y simplemente utilizarla con el nuevo compilador. Pero el problema aparece cuando quiero seguir trabajando con la versión anterior de la biblioteca, pero tengo ahora instalado este nuevo compilador. Digamos por ejemplo que quiero abrir algún proyecto que ya había desarrollada antes de que estuviera disponible la segunda versión pero luego de actualizar ZinjaI. Ahora, con el cambio de compilador, esos binarios dejaron de servir, y me veo entonces obligado a recompilar yo la biblioteca a partir de sus fuentes para obtener nuevos binarios que puedan resolver este problema.

Antes de comentar las sorpresas, para que se entienda mejor, repasemos qué pasa cuando usamos una biblioteca. Cuando un archivo fuente de C++ (un .cpp) hace un #include, está haciendo solo promesas. Incluir un .h es como copiar y pegar (literalmente) en ese punto del archivo .cpp todo el código del .h. Si el .h está bien hecho solo tiene prototipos de funciones, clases con sus métodos. Pero en general no tendrá las implementaciones (la excepción son los templates). Ya comenté antes que para usar una biblitoca se necesitan los .h y los .a (o .lib). Es decir, los headers (los que le prometen al compilador que la biblioteca puede hacer cosas) y los objetos binarios (los que cumplen esas promesas, los código compilados que finalmente hacen esas cosas). Los objetos binarios son los mismos .o que vemos en la carpeta de temporales empaquetados en un gran archivo .a o .lib (como si fuera en un zip). Cuando prometemos y no cumplimos, recibimos un error del tipo "undefined reference to...".

Una molestia común es que al usar una biblioteca que a su vez utiliza otras bibliotecas, al configurar el enlazado del proyecto tenemos que decirle donde están los binarios de todas (de la que nosotros utilizamos directamente, y también de las que a su vez esta utilizó). Por ejemplo, SFML utiliza OpenGL para dibujar, entonces al compilar un programa debemos enlazar tanto SFML como OpenGL. Esta lista de bibliotecas puede hacerse un tanto larga (y encima el orden en que se listan también es importante). Como uno de los objetivos de SFML es la simpleza, el autor buscó hacer algún truco para evitarlo. Este truco me dió dolores de cabeza varios hasta darme cuenta cual era.

 Proceso de compilación normal cuando una biblioteca (SFML) depende de otra (OpenGL).

Con el compilador nuevo, recompilé SFML desde sus fuentes descargados del repositorio svn (ya que no encontré en otro lado los fuentes de la versión vieja de la misma). Para poder compilar SFML tuve que adaptar manualmente los Makefiles ya que estaban pensados solo para Linux, y además compilar también otras bibliotecas que SFML necesita, particularmente OpenAL y SndFile. El problema es que compilando todo de la forma "normal", para enlazar tenía que indicarle al compilador que utilice los binarios de SFML, pero también de OpenAL, de SndFile, de OpenGL, y de varias más de Windows. Esto no pasaba con los binarios viejos, y entonces esto no estaba configurado así en los templates de proyecto viejos, y entonces esto generaría errores al tratar de recompilar un proyecto viejo. Ahí recién me plantié la pregunta de ¿porqué en el viejo no necesité enlazar todas esas dependencias adicionales?

Después de muchos intentos fallidos, encontré entre los fuentes unos scripts bats que hacian un truco. Para explicar el truco tomemos por ejemplo el hecho de que el módulo sfml-graphics requiere OpenGL. El truco consiste entonces en extraer todos los objetos (.o) del .a de OpenGL y agregarlos al .a de sfml-graphics. Entonces, dentro de sfml-graphics ya queda todo lo necesario. A primera vista no supe si pensar que eso era una burrada o una genialidad. Entiendo por qué lo hizo, para simplificar el proceso de enlazado (piensen que si tuvieran que armar ustedes el proyecto en lugar de utilizar una plantilla preconfigurada, o compilar manualmente desde la consola). Pero también hay que tener en cuenta que es peligroso. Al incluir los binarios de esas bibliotecas, para no meter la pata se necesita garantizar que lo que digan esos binarios va a coincidir con lo que digan los .h. Pero los .h de OpenGL vienen con MinGW, mientras que los objetos que vamos a usar ahora vienen con SFML. Si no coinciden vamos a ver errores muy raros, y si intentamos corregirlo de la forma tradicional (agregando estas dependencias al comando de enlazado), los mensajes serán aún más extraños. No es que sean difíciles de traducir o entender, es que no se condicen con el error, ya que el compilador asume que estas diferencias no existen (confía en lo que le pasamos) y entonces lo despistan y le impiden detectar correctamente el problema (a veces hasta puede que compile sin errores pero el programa reviente inexplicablemente). Esto no es tan frecuente porque la mayoría de estas bibliotecas casi no cambian en el tiempo, pero es un problema en potencia. Por otro lado, si usamos dos bibliotecas que hagan este truco tenemos dos veces esas dependencias, y nuevamente estamos en problemas.

 Proceso de compilación alterado para simplificar la llamada al compilador. Si las versiones de las dependencias coinciden (binarios y cabeceras, ahora de orígenes diferentes), el ejecutable que resulta es totalmente equivalente.

Sumado a eso, tuve la ¿mala suerte? (no se todavía) de encontrame con otro problema. Para reproducir el truco debía colocar dentro de la biblioteca de audio de sfml los objetos de OpenAL y como también de SndFile (las dos bibliotecas que usa SFML para el audio, además de las estándar de Windows). Había dicho que el truco era extraer los .o de una biblioteca y agregarlos a la otra. El problema es que los .o de estas dos tenían los mismos nombres y entonces no podía agregarlos a ambos a la otra porque se pisaban entre sí. Probé algunas posibles soluciones rápidas pero no funcionaron. Por este conflicto de nombres no pude recompilar todo a pata. Pero encontré que en el repositorio de SFML había versiones ya compiladas de estas dos biblitocas que funcionaban con el compilador nuevo a pesar de estar compiladas con el viejo (lo que no sea orientado a objetos, probablemente no sufra el cambio de compilador). Estos binarios tenían los objetos con diferentes nombres y entonces no generaban problemas. Con ellos armé el complemento actualizado que está subido al sitio.

Por curiosidad personal le escribí al autor de SFML (el gran Laurent Gomila) para preguntarle si había algún motivo adicional para aplicar el truco a pesar de los peligros, que me estuviera perdiendo, y por sobre todo para saber cómo había cambiado los nombres de los objetos dentro de los binarios de las bibliotecas para no tener problemas con los nombres. Laurent respondió muy rápidamente, el mismo día, lo cual es valorable porque en general no todos los desarrolladores de soft libre ocupados en tantas cosas como el mismo desarrollo y sus propios trabajos se toman el tiempo de atender a consultas tan particulares y que no aportan mucho como esta. Básicamente dijo que había hecho el truco para simplificarle la vida al usuario, pero luego se dió cuenta de los problemas (antes que yo se lo marque), y por eso las nuevas versiones yo no van a estar construidas de esa forma, sino que van a requerir que el linker reciba tooda la lista de dependencias. Este cambio lo hizo luego de publicar SMFL-2.0, y aparentemente tampoco está aplicado en SFML-2.1, por lo que las primeras versiones públicas y estables siguen siendo "simples", mientras que las próximas probablemente ya no lo serán. Esto va a implicar que al actualizar ya no compilen los proyectos creados anteriormmente si no hacemos los cambios en el enlazado (o rehacemos los proyectos con una plantilla nueva), pero a la larga es una buena práctica. También dijo que no había tenido problemas con los nombres, probablemente descargó OpenAL y SndFile ya compiladas de sus sitios y ya venían con nombres diferentes vaya uno a saber por qué. Si alguien tiene más detalles de cómo se nombran al compilar, o qué se puede hacer con la herramienta ar para saltear el problema, le agredecería que me cuente en los comentarios.

Por el momento entonces, vimos un truco interesante, que en ciertos contexto facilita las cosas. Hay que conocer bastante bien cómo funciona el proceso de compilación de programas y bibliotecas para poder hacer estas cosas, y entender también cuando conviene y cuando no. Sigo sin decidirme si es una mala idea o una idea genial. Por las dudas yo no lo haría, por algo no se hace frecuentemente, pero está bueno discutir estas cuestiones y usarlas para aprender un poco más en el proceso.

No hay comentarios:

Publicar un comentario