Archive for the 'Nueva versión' Category

Nueva versión de Cronopete

jueves, julio 18th, 2019

Recién salida del horno está Cronopete 4.10. Los cambios son relativamente pequeños, pero necesarios.

Para empezar, ahora ya no copia las carpetas .gvfs, .dbus, .cache ni .var/app/*/cache si se ha marcado la casilla de copiar también las carpetas ocultas del directorio raíz. Esto era importante porque dichas carpetas no contienen datos del usuario, sino otras cosas que no tiene sentido preservar. En el caso de .gvfs (aunque desde hace un tiempo ya no se utiliza este destino sino otro en /run/user), es una carpeta donde se montan las unidades remotas a las que se accede mediante Gio, el framework de Gtk para acceder a ficheros, por lo que lo que hay ahí son datos remotos que deberían ser preservados desde la otra máquina. En el caso de .dbus, contiene el socket para acceder al bus de sesión de D-Bus, por lo que no hay nada que preservar (de hecho, intentar sacar una copia de dicha carpeta da error). En el caso de .cache, como su nombre indica, lo que contiene son ficheros temporales, como por ejemplo la caché del navegador. No tiene ningún sentido desperdiciar disco duro para copiar eso. Y lo mismo para las carpetas .var/app/*/cache, las cuales contienen ficheros temporales de programas en formato flatpack, y que no tiene sentido preservar por el mismo motivo que la carpeta .cache.

El segundo cambio es que ahora permite salir del programa, una opción que me pidieron expresamente. Por supuesto está relativamente escondida para evitar activarla por accidente (un programa de copia de seguridad, por definición, no debe ser trivial de desactivar).

Por último, remodelé un poquito la interfaz de configuración por petición de varios usuarios, pues aunque era una copia perfecta de la de Time Machine de Apple, en la práctica los textos no eran demasiado claros, y había que usar prueba y error para encontrar algunas cosas. Ahora están más claros.

¡A disfrutarlo!

Nueva versión de Cronopete y de Terminus

lunes, mayo 6th, 2019

Este finde he lanzado nuevas versiones de programas. Para empezar, la versión 4.9.0 de Cronopete. El cambio principal consiste en que, ahora sí, detecta correctamente cuando el disco está lleno y procede a borrar una copia antigua para hacer sitio para la nueva. Aunque todo el código era correcto, había un diminuto bug a la hora de detectar que había ocurrido dicho problema: cuando eso ocurre, rsync termina con un código de error 11, pero resulta que waitpid y las funciones equivalentes en vala no devuelven el código de error «tal cual», sino que lo desplazan a los ocho bits superiores, reservando los inferiores para indicar si fue una señal la que provocó la finalización del programa, y cual de ellas. El resultado es que no se detectaba correctamente la situación.

Por otro lado, he lanzado la versión 1.5.0 de Terminus, en la que he corregido la funcionalidad de paste: ahora utiliza el valor del portapapeles en lugar de utilizar lo que haya en el buffer primario. Podría intentar explicar de qué va eso, pero seguro que no lo haría bien porque es un verdadero cristo, así que quien quiera enterarse de como va la selección en X11, que lea esta entrada de freedesktop sobre como funciona el clipboard, y para clarificar algunos conceptos, la entrada del estándar ICCCM.

Nueva versión de Autovala

domingo, abril 7th, 2019

Ayer lancé la versión 1.14.0 de Autovala. Básicamente corrige varios errores de mesón, en concreto cuando se especifica una ruta de instalación no estándar para los ficheros .gir o para una biblioteca. También permite, por fin, hacer «includes» de ficheros en meson, para personalizar la instalación. Y, por último, ya no instala los ficheros CMakeList.txt cuando se utiliza ninja para instalar una aplicación.

Como de costumbre, se puede encontrar en mi página personal.

Nueva versión de Terminus

jueves, marzo 28th, 2019

Acabo de lanzar una corrección rápida de Terminus, mi programa de terminal. Básicamente corrige la funcionalidad de COPY mediante el teclado. Por un error, sólo copiaba la selección al buffer primario, pero no al portapapeles, con lo que, en la práctica, no funcionaba.

También aproveché para traducir algunas frases que estaban pendientes.

Autovala 1.12

domingo, febrero 10th, 2019

Acabo de subir una nueva versión de Autovala, la 1.12. Los principales cambios son tres:

Para empezar, he completado el soporte de Meson. Ahora ya funciona completamente y soporta todas las funcionalidades. Y teniendo en cuenta la diferencia de velocidad a la hora de compilar, es algo que se agradece mucho.

Por otro lado, ahora ya compila los ficheros .gir a .typelib. Este era un paso necesario para que las bibliotecas con Gobject Introspection funcionasen, pero hasta que ayer intenté desarrollar una y utilizarla desde Gnome Shell, no supe que faltaba ese paso.

Por último, corrige un problema en el espacio de nombres. Hasta ahora, al hacer un programa, se creaban una serie de constantes (como el número de versión, o el prefijo de la ruta de instalación) bajo el namespace Constants. Sin embargo, en el caso de una biblioteca, dichas constantes se metían en un namespace diferente, llamado como la biblioteca pero con Constants añadido al final. Así, si la biblioteca se llamaba Pepito, se supone que tú utilizarás el namespace Pepito para el código de la biblioteca, pero las constantes estarán en el namespace PepitoConstants.

Originalmente esto se hizo para evitar conflictos con otros nombres; por desgracia, el compilador de .gir a .typelib exige que cada namespace tenga su propio fichero .gir; y, por otro lado, Vala se empeña en generar un único fichero .gir con todos los espacios de nombres juntos, uno detrás de otro. Además, no podía ser de otra manera si se quería mantener compatibilidad con Meson. El resultado es que era imposible compilar los ficheros .gir de una biblioteca desarrollada con Autovala.

La única solución fue cambiar eso y hacer que las constantes estén en el mismo espacio de nombres que el resto de código de la biblioteca. Es cierto que esto obligará a modificar el código que use dichas constantes, pero también es verdad que los cambios son inmediatos y directos, por lo que no es un problema real.

Como de costumbre, se puede descargar desde mi página web, estando disponible tanto en forma de código fuente (en gitlab) como en paquetes para Debian, Ubuntu, Fedora y Arch.

Presentando CRUST

domingo, agosto 26th, 2018

Acabo de lanzar CRUST. Se trata de un analizador estático de C que permite disponer en C de una gestión de memoria similar a la de RUST.

Y es que RUST está de moda, pues al ofrecer seguridad en el acceso a memoria dinámica pero sin necesidad de un runtime (como un recolector de basura) o de otras técnicas (como el conteo de referencias), permite exprimir al máximo el rendimiento. El problema es que RUST es un lenguaje nuevo, con su sintaxis propia (que, además, diverge de la de C u otros lenguajes bastante), y que, por tanto, tiene una curva de aprendizaje.

Por otro lado, existen casos en los que no se puede utilizar (todavía) RUST, como el de un microcontrolador PIC, Atmel…, pues hace falta un compilador específico. En otros microcontroladores, como los basados en ARM, es posible utilizarlo, pero sigue teniendo el problema de que no es un compilador oficial, y por tanto hay que hacer algún que otro malabar para integrarlo en la toolchain del fabricante.

Es aquí donde CRUST hace su aparición: como ya dije se trata de un analizador estático de C que permite disponer de (más o menos, claro) las mismas comprobaciones de seguridad que ofrece RUST para la gestión de memoria dinámica, de manera que es más difícil que un programa sufra referencias colgantes o dangling pointers, o pérdidas de memoria.

A la hora de diseñar CRUST tenía una cosa muy clara en mente: no podía crear un nuevo lenguaje parecido a C, sino que tenía que seguir siendo C puro, compilable con absolutamente cualquier compilador estándar. Eso eliminaba cualquier tipo de preprocesador del estilo de Metaobject o similares. También suponía rechazar cualquier tipo de conjunto de macros que pudiese alterar el código de la más mínima manera. Y por supuesto, el uso de bibliotecas estaba completamente descartado.

La solución consistió en crear una serie de calificadores específicos, similares en funcionamiento a los calificadores volatile o const ya disponibles en C, que permitan al analizador saber si un puntero concreto es gestionado o no-gestionado, así como otras propiedades importantes para el analizador. Estos calificadores comienzan todos con el prefijo __crust_ para evitar interferencias con nombres de variables o futuras adiciones al lenguaje C. La clave de estos calificadores es que no son necesarios en absoluto para compilar el código.

Por supuesto, ningún compilador aceptaría un código con dichos calificadores, y por eso es necesario incluir un fichero de cabecera (que se incluye con el analizador estático) que define dichos nuevos calificadores como espacios en blanco para el preprocesador de C. De esta manera, a la hora de compilar estos calificadores simplemente «desaparecen», y sólo son tenidos en cuenta cuando se utiliza el analizador. Este es un trozo de dicho fichero de cabecera, para que se entienda mejor:

#ifndef ENABLE_CRUST_TAGS

#ifndef __crust__
#define __crust__
#endif

#ifndef __crust_borrow__
#define __crust_borrow__
#endif

#ifndef __crust_recycle__
#define __crust_recycle__
#endif

#ifndef __crust_alias__
#define __crust_alias__
#endif

#ifndef __crust_no_0__
#define __crust_no_0__
#endif

...

Como se ve, se define cada posible calificador como una cadena vacía, lo que hace que el preprocesador se encargue de limpiar el código y dejarlo listo para el compilador, sin necesidad de modificar nada. Esto permite programar como de costumbre, simplemente etiquetando aquellos punteros que deben ser gestionados como un bloque CRUST, y compilando el código normalmente con el toolchain habitual, y sólo de vez en cuando pasar el analizador estático para comprobar si hemos cometido algún error al liberar o utilizar uno de estos bloques. Por supuesto, no es necesario escribir este fichero a mano, sino que se puede generar automáticamente simplemente llamando al analizador estático con el comando crust –headers, con lo que generará dicho fichero en el directorio actual.

La base de las reglas de gestión de memoria de CRUST (y, por extensión, de RUST) es que cada función es responsable de todos los bloques de memoria que genera o recibe. Así, si una función pide un bloque de memoria dinámica (por ejemplo con malloc), es su responsabilidad liberarlo o asegurarse que sea liberado. Esto puede ocurrir de tres maneras diferentes:

  • Puede liberar el bloque directamente ella misma
  • Puede llamar a otra función pasando dicho bloque como un parámetro, de manera que pase a ser responsabilidad de la nueva función garantizar que se libere dicho bloque
  • Puede devolver el bloque a la función llamante, de manera que ésta recibe la responsabilidad de liberarlo

No hay mucho más. Por supuesto existen, a mayores, otros detalles que hacen que la cosa no sea tan sencilla, por lo que para una explicación más en profundidad recomiendo leer como es el modelo de memoria de RUST.

Un ejemplo sencillo de como trabaja CRUST se puede ver en este trozo de código:

// SIEMPRE añadimos crust.h al principio
// El fichero tiene que estar en el proyecto
#include "crust.h"
#include <unistd.h>

// Definimos una estructura como "gestionada"
// simplemente añadiendo __crust__ a su definición
// Utilizamos un typedef para ahorrarnos tener que poner
// __crust__ en todos los sitios donde se utiliza
typedef __crust__ struct {
	int member;
	int p1;
	int p2;
} *un_tipo_t;

// esta función crea un nuevo bloque "gestionado" y lo devuelve

un_tipo_t funcion1();

// esta función recibe un bloque "gestionado",
// pero no lo libera antes de salir

void funcion2(un_tipo_t __crust_borrow__ parametro);

// esta función recibe un puntero a un bloque "gestionado",
// y además lo libera antes de salir

uint32_t funcion3(un_tipo_t parametro);

void main() {

	un_tipo_t bloque = funcion1();

	funcion2(bloque);
	funcion3(bloque);
}

Aquí vemos varias cosas:

  • Primero hacemos un typedef de un puntero a una estructura, y además incluimos el calificador __crust__. Esto significa que absolutamente cualquier variable de tipo un_tipo_t será gestionada, y por tanto sujeta a las reglas de CRUST.
  • Luego tenemos tres definiciones de funciones que «hacen cosas» con tipos un_tipo_t.
  • Finalmente, tenemos el bloque main. En él creamos un puntero de tipo un_tipo_t y le asignamos el bloque que nos devuelve funcion2.
  • A continuación llamamos con dicho bloque a funcion3. Como dicho parámetro está marcado como __crust_borrow__, sabemos que dicha función nunca liberará dicho bloque, por lo que después de llamarla seguirá estando disponible y podemos seguir utilizándolo.
  • Finalmente llamamos también con dicho bloque a funcion1. Como el parámetro de dicha función no está marcado como __crust_borrow__, sabemos a ciencia cierta que ese bloque que estamos pasando va a ser liberado dentro de ella, por lo que a partir de este punto no podemos volver a utilizarlo.
  • Llegamos al final de la función, y como la variable bloque ya no apunta a nada (pues el bloque fue liberado al llamar a funcion3), no hay riesgo de que tengamos una fuga de memoria.

Este código no devolvería ningún error al pasar por el analizador estático CRUST precisamente porque cumple con precisión las reglas de gestión de memoria. Sin embargo, si hiciésemos un cambio tan sencillo como invertir el orden de las llamadas a funcion2() y funcion3(), obtendríamos un error:

ERROR: Argument 1 when calling function 'funcion2' at line 41 was freed at line 40
Total: 1 errors.

El motivo es que funcion2() libera el bloque de memoria que recibe, lo que significa que cuando llamamos después a funcion3() con él, CRUST sabe que ese bloque de memoria ya no existe, y nos avisa.

Algo similar ocurre si sólo llamamos a funcion2() (que sabemos que no libera el bloque) pero no llamamos a funcion3():

ERROR: Memory block 'bloque', initialized at line 38, is still in use at exit point in line 41
Total: 1 errors.

Aquí CRUST se da cuenta de que el bloque que hemos inicializado no ha sido liberado al llegar al final de la función. Si lo dejásemos así tendríamos una fuga de memoria, y por eso nos avisa diligentemente.

Por supuesto CRUST es lo suficientemente inteligente como para seguir las posibles ramas de ejecución del código. Probemos a modificar la función main() anterior y dejémosla así:

void main() {
	// "tmp" tiene un valor que desconocemos
	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 5) {
		return;
	}

	if (tmp == 8) {
		bloque = NULL;
	}

	if (tmp == 7) {
		bloque = funcion1();
	}

	if (tmp != 3) {
		funcion3(bloque);
	}
	funcion2(bloque);
}

Al pasar este código a través de CRUST obtenemos el siguiente resultado:

ERROR: Memory block 'bloque', initialized at line 42, is still in use at exit point in line 45
ERROR: Assignment to 'bloque' at line 49, which was already assigned at line 42
ERROR: Argument 1 when calling function 'funcion2' at line 59 was freed at line 57
ERROR: Memory block 'bloque', initialized at line 53, is still in use at exit point in line 60
ERROR: Assignment to 'bloque' at line 53, which was already assigned at line 42
ERROR: Memory block 'bloque', initialized at line 42, is still in use at exit point in line 60
Total: 6 errors.

Aquí nos está avisando de todos los errores que hemos cometido, que son:

  • Si tmp vale 5 saldremos en el return de la primera comparación, con lo que el bloque que inicializamos en la línea 42 no se libera y tendremos una fuga de memoria.
  • Si tmp vale 7 u 8 estaremos sobreescribiendo un puntero que apunta a un bloque válido en la línea 49, con lo que tendremos una fuga de memoria.
  • Si tmp tiene un valor diferente de 3 liberaremos el bloque en la línea 49, con lo que al llamar a funcion2() tendremos una referencia colgante.
  • Si tmp vale 3 todo parecerá funcionar correctamente hasta llegar al final de la función, donde nos encontraremos con que el bloque nunca se libera y tendremos una fuga de memoria. Este error nos aparece dos veces porque en una de las ramas de ejecución no liberamos el bloque recibido al principio (línea 42) y en la otra no liberamos el bloque obtenido cuando tmp vale 7.

Por supuesto, CRUST tiene algunas limitaciones. Por ejemplo, sólo recuerda si una variable es NULL (vale 0) o no (valor distinto de 0), pero no valores concretos. Esto significa que este código será analizado correctamente:

void main() {

	un_tipo_t bloque = funcion1();

	if (bloque != NULL) {
		funcion3(bloque);
		bloque = NULL;
	}

	if (bloque != NULL) {
		funcion2(bloque);
	}
}

CRUST sabe que bloque, tal cual es devuelto por funcion1() puede ser NULL o no NULL, pero cuando llega al primer if y analiza ambas posibles ramas, en la de dentro del if marca a bloque como no NULL, y en la de fuera como NULL. Cuando llama a funcion3() el bloque es liberado, y por eso no devuelve un error al asignar NULL a dicha variable. A partir de aquí ambas ramas de ejecución tienen NULL como valor de bloque, y CRUST es capaz de detectar correctamente que jamás se llamará a funcion2(), y por eso no devuelve ningún error.

Sin embargo, este bloque sí daría errores, pues CRUST no llega a tener un nivel de control tan fino de los valores de las variables:

void main() {

	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 3) {
		funcion3(bloque);
	}

	if (tmp != 3) {
		funcion2(bloque);
		funcion3(bloque);
	}
}

Este código devolvería estos errores:

ERROR: Argument 1 when calling function 'funcion2' at line 48 was freed at line 44
ERROR: Argument 1 when calling function 'funcion3' at line 49 was freed at line 44
ERROR: Memory block 'bloque', initialized at line 41, is still in use at exit point in line 51
Total: 3 errors.

Por supuesto, la forma correcta de hacer lo anterior sería esta:

void main() {

	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 3) {
		funcion3(bloque);
	} else {
		funcion2(bloque);
		funcion3(bloque);
	}
}

La cual sí sería analizada correctamente por CRUST.

Todo esto no son más que unas pinceladas, pues hay mucho más en CRUST (por ejemplo el prestamo de bloques, igual que en RUST), por lo que lo mejor es leerse la documentación completa, que viene en formato PDF.

Como de costumbre, se puede encontrar en mi página web y en el respositorio de CRUST en GitLab.

Cronopete 4

sábado, abril 28th, 2018

Acabo de lanzar una nueva versión de Cronopete, la 4.0. Se trata de una versión con muchos cambios internos, aunque externamente puede parecer muy similar.

El primer gran cambio es que ahora tiene un sistema de backends para hacer los backups. Esto era algo que estaba medio hecho desde la primera versión, pero el problema es que, sencillamente, estaba mal hecho: los backends de las versiones viejas sólo daban acceso a un disco, mientras que la lógica de las copias de seguridad estaba por encima. Esto limitaba mucho, porque exigía que donde se hiciesen los backups soportase enlaces a archivo y otras cosas. El nuevo sistema de backend, en cambio, asume completamente TODO el proceso de copia de seguridad; básicamente, cronopete le dice al backend: «quiero que me copies estas carpetas», o «dame la lista de backups que tienes», o » recupera este fichero desde este backup». Pero no le interesa COMO está hecho el backup. Eso es tarea de cada backend.

Por supuesto es fundamental conservar el sistema viejo de copias de seguridad, por lo que el primer backend funcional que tiene hace copias exactamente igual que el sistema anterior, pero con una diferencia: ahora utiliza la utilidad RSYNC en lugar de código propio, lo que ha permitido eliminar de un plumazo un bug misterioso que hacía que, muy de vez en cuando, apareciesen carpetas con nombres raros en la carpeta personal. Por supuesto, se aprovecha de que RSYNC permite hacer copias de seguridad con la misma estructura que las viejas versiones de cronopete (los ficheros que no han cambiado son enlaces duros al mismo fichero de la copia anterior, lo que permite ahorrar muchísimo espacio).

Con el nuevo sistema, implementar un backend para hacer copias en remoto debería ser relativamente trivial, aunque el problema es cuando tendré tiempo de ponerme en serio con ello (a fin de cuentas, es algo que quiero probar bien antes de lanzar).

Otra novedad, esta vez más sencilla, es que por fin permite que varios usuarios compartan un mismo disco de copias. Por un fallo tonto, las versiones anteriores ponían la carpeta cronopete, en el disco de destino, con permisos de escritura sólo para el usuario que había formateado el disco. Eso significaba que dicho usuario podía hacer copias sin problema, pero en cuanto otro usuario intentase usar el mismo disco para hacer sus copias, fallaría y le propondría formatearlo. Ahora la carpeta cronopete tiene permisos de escritura para todo el mundo, y dentro, igual que antes, hay una carpeta por usuario, en la que sólo dicho usuario tiene permisos de lectura, escritura y atravesado (por motivos obvios: otros usuarios no deberían tener acceso a mis copias de seguridad, pues son MIS datos).

Otro cambio es que ahora borra las copias nuevas después de hacer la copia de seguridad, lo que permite garantizar que si enciendes el equipo sólo un momento, se haga al menos una copia rápidamente. Además, el borrado de copias viejas se hace de manera más segura: antes se borraba directamente el directorio, lo que podía suponer un problema si se apagaba el ordenador o si cronopete fallaba en mitad del borrado, pues una copia quedaría «a medias». Ahora, sin embargo, primero se renombran las copias a borrar añadiendo una letra justo antes, de manera que las copias que no se deben tener en cuenta están debidamente etiquetadas; luego se sincroniza el disco, y finalmente se procede a borrar las carpetas así marcadas. Esto tiene otra ventaja, y es que una copia de seguridad no válida nunca aparecerá en la interfaz de restauración de ficheros.

Por último hay varios cambios estéticos y menores, sobre todo en la interfaz de restauración de ficheros. Ahora, por ejemplo, la linea de tiempos muestra la fecha correspondiente, lo que da una idea más precisa de por donde andamos y hasta donde podemos llegar que antes.

También se puede ahora filtrar por tipo de archivo, y, además, cronopete recordará entre ejecuciones si se quería ver en modo iconos o en modo lista de archivos.

Nueva versión de Autovala

miércoles, marzo 28th, 2018

Hoy saqué una nueva versión de Autovala que añade un detalle muy sencillo pero muy importante: el soporte para anotaciones para traductores.

Resulta que xgettext, la herramienta que extrae del código fuente los textos que hay que traducir a las diversas lenguas, tiene una funcionalidad muy importante, que es la posibilidad de añadir como notas para los traductores cualquier comentario del código que se encuentre en la línea inmediatamente anterior a la de una cadena traducible. Hasta ahora yo daba por hecho que eso se realizaba de manera automática, pero hoy descubrí que no, que hace falta pasar un parámetro en la línea de comandos, en concreto –add-comments. Es más, es posible añadir un tag a continuación, y sólo aquellos comentarios que empiecen por dicho tag serán considerados una nota para los traductores.

Como digo, pensaba que era automático, por lo que Autovala no lo tenía en cuenta, pero al descubrir esto decidí que era una funcionalidad lo suficientemente importante como para que estuviese disponible. Así que ahora es posible añadir en la configuración una línea con po_comment_tag, y todos los comentarios previos a una cadena traducible serán añadidos como notas para traductores. Pero también es posible añadirla como po_comment_tag: XXXXXX, en cuyo caso sólo se añadirán aquellos comentarios que comiencen con XXXXXX.

Como de costumbre, es posible bajar Autovala desde mi página web, o bien desde el repositorio Github de Autovala.

Autovala y Multipackager

miércoles, marzo 7th, 2018

Recientemente saqué versiones nuevas de Autovala y Multipackager.

En el caso de Autovala, se trata de la 1.3.0. El motivo fue que en la última actualización de paquetes de mi sistema operativo se pasó a Vala 0.39. Sin embargo, este compilador busca los ficheros VAPI en el directorio de la versión 0.40. La solución consistió en no deducir el directorio donde están los ficheros VAPI a partir de la versión del compilador, sino preguntarle donde los va a buscar (para lo que basta con hacer «valac –api-version»). Con esto, Autovala puede por fin encontrar dichos ficheros y detectar automáticamente las dependencias del programa.

El cambio en Multipackager surgió, precisamente, para corregir un problema al crear los paquetes de Autovala, y es que, debido al cambio de nombre de una biblioteca desde Debian Estable a Debian SID, el fichero de configuración sólo servía para uno de ellos. La solución fue modificarlo para que sea capaz de detectar alternativas a paquetes (el comando «|» en las dependencias, para indicar que se puede poner un paquete cualquiera de una lista).

Como de costumbre, están disponibles en mi web y en mi repositorio de GIT.

Multipackager más rápido

domingo, mayo 22nd, 2016

He lanzado una nueva versión de Multipackager con una interesante novedad: ahora es muchísimo más rápido. Para ello utilizo OverlayFS.

Para entender por qué es así, cabe explicar que cada vez que se utiliza por primera vez un sistema operativo concreto (por ejemplo: debian jessie i386), multipackager genera dos copias: una para construir paquetes, y otra para lanzar shells.

Hasta aquí nada especial. Sin embargo, la copia para construir paquetes no se utiliza nunca directamente. Cuando se va a construir uno, lo primero que se hace es sacar una copia de dicha copia, y sólo entonces se instalan los paquetes necesarios para crear el paquete. Si todo va bien se borra la copia vieja, y ya tenemos la máquina original con nuevos paquetes; si algo falla, se borra la copia nueva y se restaura la vieja. Así nunca se queda un sistema «a medias». Por último se hace una nueva copia para crear, esta vez sí, dentro de ella el nuevo paquete. Esta copia se destruye una vez que se ha terminado la generación.

Este sistema permite garantizar que las copias locales de cada sistema operativo están siempre «sanas», lo que evita tener que generar una nueva completa cada vez que queramos crear un paquete. El inconveniente es que sacar cada copia lleva MUCHO tiempo. Y lo que es peor: a medida que se instalan más y más paquetes, la copia tarda más y más.

La solución consistió en no hacer copias, sino utilizar OverlayFS para poder realizar las operaciones sin modificar la máquina original. Esta operativa es inmediata en el segundo caso, cuando queremos construir el paquete en una máquina que se borrará cuando se termine. Sin embargo, para el primer caso tuve que rascarme un poco la cabeza, pues si la operación de instalar paquetes termina correctamente es necesario «aplicar los cambios» sobre la máquina virtual original. Esta operación, afortunadamente, no es muy complicada.

El resultado es que los tiempos para generar los distintos paquetes se han reducido en un orden de magnitud, lo que no está nada mal, teniendo en cuenta que actualmente estoy generando entre seis y ocho paquetes por programa (debian, ubuntu, fedora y arch, cada una en 32 y 64 bits).


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