viernes, 14 de junio de 2024

El trabajo de actualizar ZinjaI

Ya comenté lo que más tiempo me lleva cuando quiero publicar actualizaciones de PSeInt. Ahora voy a comentar qué es lo que me está tomando tanto con ZinjaI. Considerando que llevo años sin actualizarlo, pueden imaginarse que esto va a ser largo.

Nota: La numeración es solo para organizar el contenido, pero el orden no implica más o menos tiempo/importancia.


1. El Toolchain

ZinjaI para Windows se distribuye con el "compilador" incluido, de modo que con solo instalar ZinjaI el usuario ya pueda empezar a programar (a diferencia de GNU/Linux, donde es el sistema el que provee un compilador). Actualizar ZinjaI implica también cada tanto actualizar el compilador que incluye. Hasta ahora ZinjaI usaba mingw32, que creo que fue una buena elección cuando comencé, pero actualmente este proyecto se ha quedado atrás y en desuso, en favor de otras alternativas con soporte para 64bits. MinGW-w64 (aunque el nombre es similar, es otro port diferente) es una de las opciones más utilizadas en Windows.

Cambiar el toolchain implica retocar o repasar muchas cosas, no solo descargar y descomprimir los archivos. Mínimamente hay que actualizar las configuraciones de ZinjaI (los archivos en toolchain/ que le dicen dónde está el compilador), y actualizar la documentación (para reflejar los cambios de versiones y mencionar correctamente fuentes y licencias). Y hay una documentación nueva específica, ya que ahora el toolchain será como un complemento preinstalado y tendrá su propia entrada en la ayuda. También hay que analizar qué herramientas adicionales incluye y cuáles hay que agregarle (gdb, gprof, gcov, etc).

Finalmente, vale mencionar que aún diciendo "voy a usar mingw64-w64" no está todo dicho. Se ofrecen diferentes compilaciones que generan binarios no compatibles entre sí (cambian en cómo implementan el manejo de excepciones, o el soporte para threads), por lo cual el cambió implicó investigar un poco para elegir una. Y además, conforme avanza el nro de versión, pueden avanzar también los requisitos mínimos. Por ej, la versión que utilizo no es la última al día de hoy (es la ante-última), porque la nueva parece depender de bibliotecas de Windows que no estarían presentes en versiones desactualizadas de Windows.

Disclamer: Tal vez en un futuro pueda descansar en WSL, pero de momento veo que ZinjaI todavía se utiliza en muchas instalaciones de Windows desactualizadas. Prefiero que el usuario/alumno tenga todo listo en 3 clicks y no hacerle instalar algo por fuera, y además personalmente al no ser usuario de Windows no tengo mucha experiencia con eso.

 

2. Las Bibliotecas

De la mano del cambio de toolchain, hay que recompilar y actualizar casi todos los complementos. Esto es algo que tengo medianamente automatizado con scripts de bash de forma de poder actualizar un link de descarga y volver a generar el complemento automáticamente. Pero esto es así de simple cuando hay un cambio menor, aquí cambia el compilador completo (no solo la versión, de gcc 6 a gcc 12 que ya es mucho, sino el port de mingw32 a Mingw64-w64). Esto implica en primer lugar retocar la instalación y configuración del toolchain en wine (que es lo que uso para compilar todo desde mi GNU/Linux), y luego los propios scripts de compilación de las bibliotecas. 

Más aún, muchas de ellas requieren pequeños parches para compilar, ya que el código no compila "como viene". Esto era especialmente cierto con mingw32. Encontrar cada parche implica generalmente encontrarse primero con el error al querer compilar, googlear bastante, probar soluciones, hasta encontrar en algún foro escondida la respuesta, armar los archivos necesarios para que el script aplique el parche automáticamente, y revisarlo luego en cada actualización. También sucede que utilizan diferentes herramientas para compilar (make, cmake, premake, etc), así que más trabajo para esos scripts.

La mayoría de los complementos corresponden a bibliotecas externas, e implican entonces mantener además las plantillas de programas y de proyecto de ZinjaI para que encuentren correctamente los archivos compilados.

Finalmente, igual que con el compilador (que también es un complemento), hay que actualizar la documentación y tener cuidado con las licencias.

 

3. La Ayuda

La ayuda que provee cppreference probablemente sea la parte más utilizada del sistema de ayuda. Intento que ese contenido se integre dentro de ZinjaI, pero dado que no lo generé yo, debo adaptarlo para ello. Por ej, reconstruir el índice de la misma para que se use desde el motor de búsqueda de ZinjaI, o ajustar algunos enlaces para que funcione todo correctamente offline y embebido.

Pero este complemento lleva algo de trabajo adicional por el hecho de que el componente de wxWidgets para mostrar html rápido y simple, no soporta la mayoría de las características del html moderno: no lee css, no muestra imágenes svg, no respeta muchísimas propiedades de los divs, etc. Entonces hago un preproceso de los archivos antes de armar el complemento (por ejemplo convertir las imágenes de svg a png), y mucho trabajo luego "al vuelo" al mostrarlo. Es decir, ZinjaI edita el html antes de pasárselo a wx, para hacerlo más amigable con lo que entiende el componente, o para reemplazar algunos estilos que no soporta por otros que sí. Toda esta edición es muy ad-hoc y puede cambiar al actualizar el complemento si cpp reference decide cambiar sus estilos o la organización de sus htmls.

Nota: hay un control alternativo en wx para mostrar html en todo su esplendor. Pero lo que hace es básicamente embeber todo un navegador y delegarle el trabajo a ese motor (Edge o WebKit), así que es tan pesado como abrir el browser del sistema e implica ciertas dependencias, por eso lo evito. De todas formas, si el usuario quiere, desde la vista básica que ofrezco en ZinjaI tiene un link para pasar al navegador de su sistema con un solo click.

 

4. El paso a wxWidgets 3.x

Esto es algo que debería haber ocurrido hace mucho tiempo, pero debido al trabajo que implicaba se fue postergando. Cuando finalmente comencé a migrar mi código para que utilice la nueva versión de wx y en modo unicode, fue cuando dejé de publicar actualizaciones, ya que el cambio rompió muchas cosas que debía arreglar antes de seguir publicando actualizaciones para que no parezca un retroceso. Hay cosas que aún siguen rotas, pero dado que lo más grande parece funcionar y que yo personalmente lo vengo usando/probando desde hace un par de años, ya es hora de publicarlo como esté, con la esperanza de que esto ponga a funcionar otra vez el modo bazar.

Es importante notar que este cambio no solo afecta al código, sino también al proceso de compilación. Y dado que este era algo engorroso, fue una oportunidad para actualizarlo y documentarlo mejor. Pero obviamente eso implicó más trabajo y más demora.

Nota: pasar a wx3 en este contexto es tratar de que lo que ya tenía siga funcionando, tal vez simplificar algunas cosas. Pero en realidad el paso abre la puerta a muchas nuevas funcionalidades, algunas muy necesarias (como por ejemplo trabajar con fuentes unicode). Todavía no he podido explotar esto último debidamente.

 

4. El Instalador

Como dije en el párrafo anterior, el proceso de compilación se vio afectado por los cambios y decidí rehacerlo casi que desde cero. Sigo con la misma idea de automatizarlo con scripts de bash para correr todo de GNU/Linux, usando VirtualBox para las versiones de GNU/Linux, wine para las de Windows, y osxcross para las de macOS. Pero el manejo de los Makefiles, y el funcionamiento del script que los invoca y luego invoca al generador del instalador cambió drásticamente. Ya había hecho una adaptación similar con PSeInt, así que tenía un buen modelo para empezar. Pero el proceso de ZinjaI es mucho más complejo que el de PSeInt (varía más entre un sistema operativo y otro, debe considerar archivos/herramientas de terceros, los fuentes están organizados de forma diferente, etc).

Sumado a esto, cambié el generador de instaladores, de NSIS que utilizaba anteriormente a Inno Setup. Originalmente el cambio fue culpa de los antivirus, ya que noté (haciendo prueba y error con virustotal) que con Inno generaba menos falsos positivos en PSeInt. Pero además parece ofrecerme algunas opciones adicionales interesantes, así que decidí cambiarlo en ZinjaI también y unificar las herramientas que uso en ambos proyectos. Pero obviamente, esto implicó armar un archivo de configuración del instalador completamente diferente, y mucho googlear para lograrlo.

 

5. Los Componentes de Terceros

Para algunas operaciones/funcionalidades ZinjaI invoca a herramientas que son tomadas de otros proyectos. Ejemplos: make, diff, file, gprof2dot, graphviz, lizard. Son más cosas que mantener actualizadas, que documentar correctamente, y en algunos casos que compilar.

Algunas de estas herramientas están desarrolladas en Python, y para no requerir una instalación de Python a la par de ZinjaI, las "compilé" a exe. Python no es un lenguaje para "compilar" en este sentido, así que el proceso es bastante engorroso. Cuando las empecé a incluir logré hacerlo gracias a py2exe y Python 2.7. Actualmente el código de estas herramientas en algunos casos ha cambiado mucho, la versión a usar de Python es la 3 y no la 2, y el mecanismo para convertir a exe cambió muchísimo también. La receta que apliqué la primera vez ya no sirve ni como punto de partida. Y la "nueva" no parece tan simple. Ya perdí un par de mañanas tratando de "compilar" lizard y todavía no pude.

 

6. Los Componentes Adicionales Propios

Hay componentes de ZinjaI que se implementan como subprocesos separados de la GUI principal (aunque esta los invoca de forma transparente para el usuario). Por ejemplo, el instalador de complemento, o el programa para encontrar las dependencias del ejecutable en Windows (esas que se ven en la segunda pestaña del cuadro de "Propiedades del Ejecutable").

El instalador de complementos tiene algunos cambios importantes, como por ejemplo una mínima gestión de dependencias para evitar un error muy común entre los estudiantes: instalar un complemento de 64bits en un ZinjaI de 32.

Otros componentes, como el que analiza las dependencias de un ejecutable (lsdeps) también requieren de una actualización que todavía no llegué a hacer (ya que ahora debe entender ejecutables de 64bits, no solo 32).

Hay otras cosas a mejorar, y mucho . Por ejemplo, el parser que uso para el autocompletado y el árbol de símbolos, se basa en una versión de cbrowser que ya tiene más de 10 años, y debería reemplazar por algo completamente nuevo (ya que parece que el proyecto original tampoco se actualiza hace 10 años). La herramienta (con algunos parches míos) sigue funcionando lo suficientemente bien para los proyectos de los alumnos, que no usan funcionalidades demasiado avanzadas de C++; pero definitivamente se queda corto para un proyecto más avanzado que utilice las características de lo que se suele llamar C++ "moderno".

 

7. El port a macOS

Esto siempre es un dolor de cabeza por varios motivos. Primero, no tengo una mac real para probar, así que trabajo un poco a ciegas. Mi única herramienta es una máquina virtual usualmente desactualizada (y que es muuuy lenta). Además, porque Apple cada tantos años hace cambios drásticos, como variar la arquitectura de sus procesadores, poner nuevas restricciones de seguridad que a veces el desarrollador open source no tiene forma fácil de satisfacer (code-signing, certificados pagos, notarización, etc.... y Windows apunta en la misma dirección), o cambiar su toolchain completo (cuando pasó de gcc a clang, por ej, pude adaptarme a ese cambio, pero no al pasar de gdb a lldb).

Con todo esto, la última versión para macOS ya no debe funcionar en ninguna mac en uso. Tengo un toolchain relativamente actualizado porque lo armé para recuperar la versión de mac en la última actualización de PSeInt; pero en ZinjaI implica mucho más que solo recompilar.


8. El Autocompletado

Para el autocompletado de bibliotecas estándar y de complementos, ZinjaI utiliza unos archivos que llamo índices con la información necesaria. Generar esos índices es todo un problema. 

En general no lo puedo hacer con el parser a partir del código por dos motivos: las limitaciones del parser en algunos casos, pero principalmente el hecho de que el código expondría muchísimos detalles de implementación que no deberían verse (macros, templates, headers "ocultos", etc... las cosas suelen no ser lo que parecen). 

Entonces a veces los suelo generar manualmente (la mayoría de lo que hace a las bibliotecas estándar de C y C++ se fue armando copiando, pegando y editando manualmente a partir de referencias como las de cppreference o cplusplus.com). En algunos pocos casos el parser me da un buen punto de partido y queda poco por corregir manualmente (ej: SFML). En otros (y tiende a ser la mayoría), los intento generar a partir de la documentación; pero para eso debo desarrollar alguna herramienta ad-hoc que la parsee. El de wx, por ej, fue generado inicialmente por un programa que escribí que extraía esa información desde los htmls de la referencia oficial. Pero en cuando algo de estilo o del formato de esa referencia cambia, la herramienta deja e funcionar.

Por esto muchos índices de autocompletado (la mayoría) no están suficientemente actualizados.

 

9. Otras Tareas

Hay más cosas que hacer para publicar la actualización. Algunas tal vez más obvias como subir los archivos, actualizar la página web, escribir el changelog y la noticia, un tweet, diseñar una nueva splash-screen, etc. Otras no tan obvias, como pasar los instaladores por virustotal, encontrarse con 8 falsos positivos, adivinar qué puede estar resultando sospechoso, hacer cambios innecesarios, rezar para que al volver a pasarlos desaparezca la mayoría, y repetir hasta que solo queden dos o tres en antivirus que nadie usa.

 

Conclusiones

Como verán, actualizar ZinjaI es muchísimo trabajo. Particularmente mucho en este momento debido a los cambios grandes que se juntaron; muchos de los cuales ya no serán necesarios luego en cada actualización (o al menos no en esta magnitud), y otros tantos estarán mejor automatizados. 

Casi nada de lo que mencioné está 100% resuelto. Hay cambios que casi ni empezaron (el port a versiones recientes de macOS, un nuevo parser). Hay cambios en camino, que podrían terminar de completarse luego de una primera actualización (los autocompletados, los complementos por ejemplo, o los componentes de terceros). Y hay cambios que, aún incompletos, ya ofrecen una funcionalidad suficiente. Ya me resigné a no llegar con todo, en breve tendremos un gran actualización con lo que hay hasta ahora. Recuerden que yo siempre uso la versión de desarrollo de ZinjaI (para desarrollar ZinjaI y PSeInt, para dar clases, para mis otros desarrollos relacionados a mis temas de "investigación" en la universidad, etc); así que me consta que, aunque incompleto, anda lo suficientemente bien como para empezar a compartirlo (probarlo en producción :).

2 comentarios:

  1. Apreciamos su arduo trabajo y dedicación en sus proyectos Zinjai y PseInt. Cuando el tiempo lo permita, nos gustaría por ejemplo que los reportes del foro se fueran implementado de apoco en lugar de lanzar una gran actualización, esto es por ejemplo si alguien encontró un error ortográfico o se solicita algo ‘fácil’ de integrar o actualizar se efectúe, porque de lo contrario esos reportes se quedan en el olvido, cuestiones que podrían ser muy útiles para todos. Mantener lanzamientos frecuentes nos emociona y nos mantienen activos tanto en los foros como en el uso de las herramientas que nos proporcionas.

    Gracias.

    ResponderEliminar
  2. Gracias por el enorme aporte, y no deje de mantener vivas estas herramientas que sirven de mucho en especial para el ambito educativo, gracias y exitos siempre!

    ResponderEliminar