02 enero 2007

Reglas para numerar las versiones de los ensamblados

Los nombres fuertes (strong name) para las librerías que hemos conseguido usando las claves de snk son interesantes y los voy a aplicar, pero no solucionan nuestro problema con las versiones, ya que formando parte del nombre fuerte se incluye también la versión del ensamblado, que al cambiar hace que no funcione la dependencia.
Nuestro problema es: tenemos múltiples dependencias entre las librerías, y se generan en distintas soluciones, y como Release (sin información de depuración). El número de versión de las librerías se genera ahora automáticamente, indicando 0.5.* en el atributo AssemblyVersion. Hay otra versión, la del fichero, pero no indicamos AssemblyFileVersion, y así toma automáticamente el mismo número de versión que el ensamblado. Por lo cual estamos cambiando el número de versión del ensamblado cada vez que lo generamos, rompiendo cualquier posibilidad de compatibilidad. Por ejemplo, si generamos A con la versión 0.5.0.0, luego compilamos B sobre esa versión, y después actualizamos A a la versión 0.5.0.1, en una solución C que use A y B necesitaremos dos versiones de A: la 0.5.0.0 para B, y la 0.5.0.1 para C. La culpa es de B, que depende estrictamente de una versión concreta. Para evitarlo, hasta ahora hemos usado nombres débiles, que ni siquiera incluyen el número de versión (la hemos borrado manualmente del nombre del ensamblado en la referencia del archivo .csproj). Pero esto nos conducirá al DLL Hell, así que vamos a fortalecer el mecanismo para evitarlo, basándonos en las herramientas que ofrecen los assemblies de .net.
La solución propuesta por los desarrolladores de Microsoft es trabajar con las políticas de versiones (version policy). Según he leído en Informit, donde se explica paso a paso y con claridad, hay 3 niveles de políticas: de aplicación, de publicador y de administrador (máquina). Todas sobreescriben la dependencia encontrada en la assembly compilada. En una aplicación puedes establecer una política, en el archivo .config, forzando que se dependa de una versión concreta de una librería. También un publicador puede indicar que en lugar de usarse una versión se use otra, con la idea de que si el fabricante de una librería saca una versión corregida (y lógicamente totalmente compatible), todas las aplicaciones que usaban la versión anterior ahora usen la nueva versión. Y por último puede establecerse a nivel de máquina, en un archivo .config global del framework, un cambio de dependencias como los descritos. Está claro que nuestro objetivo es una política de publicador: si estabas usando la versión 0.5.0.0 de la librería A, puedes usar también (o mejor) la versión 0.5.0.1. Pero para conseguir esto, hay que generar una nueva assembly que contiene la declaración de la nueva dependencia, lo cual es un mecanismo bastante complejo como para descartarlo para nuestro problema, que es mucho más rutinario. Esta solución tiene un ámbito de aplicación muy márginal, a la vista de la solución más lógica y sencilla que comentamos a continuación.
Intentando entender cómo funciona el propio .net, veo los siguientes hechos:
  1. Las librerías tienen una versión sencilla, como 2.0.0.0, mientras que tienen una versión de archivo complejo, conteniendo información concreta de la versión. Como ya sabemos, para la dependencia se usa sólo la versión pública, que no cambia.
  2. Cuando Microsoft actualiza alguna de estas librerías, el nuevo archivo tiene distinta versión, pero la versión pública del ensamblado es la misma, con lo que las aplicaciones siguen funcionando. Esta claro que para esto Microsoft debe asegurarse de que la nueva librería sea compatible con la anterior.
Por lo cual, la mecánica parece ser: que sólo se cambie el número de versión cuando el nuevo ensamblado sea incompatible con el anterior. Esta comprobación corre por nuestra cuenta, ya que Visual Studio 2005 no la realiza tal como lo hacía VB6. Si mantenemos la versión del ensamblado constante, para conocer la versión real tenemos que consultar la versión del archivo (así funciona la librería del framework de .net), y esta actualización no es automática (el * no funciona aquí) así que tenemos que cambiarla manualmente en cada nueva versión (después cuento cómo se puede automatizar). Y hay que tener siempre presente el gran peligro: si realmente no hay compatibilidad, las aplicaciones pueden fallar; el GAC no almacenará varias copias de la librería si tienen la misma versión.
Conclusión: ¿Cómo vamos a trabajar? Seguiremos trabajando con la numeración 0.5 en Nibi.Negocio, aunque ahora como 0.5.0.0 en lugar de 0.5.d.s, con d y s variables según la fecha. La versión interna, que seguirá la numeración 0.5.d.s (aunque de forma manual, y usando s de forma distinta: será 0 para la primera versión de un día, 1 para la siguiente, y así sucesivamente), será la que anotemos como versión. Como estamos en desarrollo, el incremento de 0.5 será libre, pasando a 0.6, 0.7 conforme el proceso de desarrollo lo considere. Cuando salga la versión definitiva, pasaremos a la versión 1.0, sobre la que construiremos todo el árbol de librerías para regenerar sus dependencias. Desde aquí tendremos dos caminos (ambos posibles): comenzar el desarrollo de una nueva versión (por ejemplo la 2.0), usando números de versión como 1.1, 1.6... como queramos. Y a la vez generar versiones con correcciones puntuales de la versión 1.0, que mantendrán su versión pública 1.0.0.0 y sólo se distinguirán por su versión de archivo. Por esto, no acabo de encontrar la utilidad a las version policy de publicador, salvo cuando se quiere cambiar la versión pública manteniendo la compatibilidad, caso que creo que no vamos a abordar nunca.