Tabla de contenidos
En este capítulo, daré un ejemplo de un paquete razonablemente sencillo, pero completo: un emulador de SNES, conocido como ZSNES. Veremos todas las fases, desde que nos descargamos el código fuente hasta que tenemos el paquete instalado en nuestro sistema y funcionando. Existen muchas guías de creación de paquetes, pero en mi opinión la información se halla bastante fragmentada. De todas formas, el Ubuntu MOTU Team tiene excelentes introducciones acerca del tema en su wiki [5], y también hay una guía por parte de la comunidad Debian [4], aunque en mi opinión la de Ubuntu está más actualizada.
Para simplificar, describiré el proceso de forma secuencial. Sin embargo, lo normal es que sea iterativo, teniendo muchas revisiones intermedias del paquete hasta dejarlo listo para su distribución. Se ven aspectos más avanzados en el siguiente capítulo.
Antes de poder introducir los ficheros fuente de nuestro paquete en el repositorio Subversion que previamente preparamos, hemos de crear una primera versión de nuestro paquete.
Tras descargar el código fuente de
la página
oficial a
/tmp/packages/zsnes151src.tar.bz2,
crearemos el esqueleto básico del paquete mediante
dh_make, una de las muchas herramientas del
paquete debhelper de
ayuda. Ejecutaremos las siguientes órdenes en una terminal
dentro del directorio /tmp/packages:
tar -xjf zsnes151src.tar.bz2
mv zsnes_1_51 zsnes-1.510
cd zsnes-1.510
dh_make -e tudireccion@decorreo -c GPL -s --createorig
La carpeta que hemos creado (zsnes-1.510)
obedece al convenio seguido por Debian
nombrepaquete-versionUpstream, donde la
versión del programa original se entiende como una serie de
números separados por puntos: así,
zsnes-1.510 es más reciente que
zsnes-1.6, y menos que
zsnes-1.600.
Por otro lado, las opciones pasadas a dh_make son:
Especifica nuestra dirección email como desarrollador del paquete. Se usa en el registro de cambios (de ahora en adelante el Changelog), y para realizar firmas digitales.
Indica que el código original sigue la General Public License. Otras opciones incluyen la LGPL, BSD o la licencia artística. En otro caso, se nos dejará un hueco (posteriormente veremos dónde) para que lo rellenemos con el texto de la licencia en cuestión.
Existen varios tipos de paquete Debian: de un solo binario, de varios, bibliotecas, o paquetes que emplean CDBS (el resto usan únicamente debhelper). Aquí hemos decidido hacer un paquete de un solo binario, mediante debhelper. Después veremos también cómo hacer un paquete con CDBS.
Creamos en el directorio padre un fichero
zsnes_1.510.orig.tar.gz con el
código fuente original, para poder comparar con la
versión que usemos al construir el paquete y volcar las
diferencias a un fichero diff.
Ya tenemos el esqueleto del paquete. Todos los ficheros
específicos de él se hallan bajo el directorio
/tmp/packages/zsnes-1.510/debian. Si
examinamos dicho directorio, veremos que hay un gran número de
ficheros. No utilizaremos los ejemplos incluidos, indicados
por la extensión .ex, así que los
retiraremos, junto con el fichero
README.Debian, dado que no hay nada
especial acerca de nuestro paquete:
rm debian/*.{ex,EX} debian/README.Debian
Iremos rellenando cada fichero de control en
debian con los datos necesarios. Iremos
detallando su sintaxis y semántica a lo largo de esta sección.
Éste es el registro de cambios de nuestro paquete. Aquí iremos indicando los cambios realizados a lo largo de cada versión del paquete, no del software original. Este fichero es el que nuestros usuarios leerán para ver qué hay de nuevo en cada versión del paquete.
Escribiremos nuestra primera entrada:
zsnes (1.510-0ubuntu1) gutsy; urgency=low * Versión inicial del paquete -- Antonio Garcia <nyoescape@gmail.com> Fri, 19 Feb 2008 13:49:12 +0100
Vemos cómo la versión actual del paquete junto con su última fecha y autor del cambio se hallan codificados en el registro. También se tiene en cuenta la distribución (en nuestro caso gutsy, de Ubuntu), y la urgencia del cambio (por lo general baja, a menos que se trata de una vulnerabilidad de seguridad o algo del estilo).
Para una misma versión del software original, tendremos
distintas versiones del paquete, separadas del número
de versión original por un guión, como vemos aquí. En
particular, los paquetes de Ubuntu
[11] usan el esquema
-XubuntuY, indicando que se trata de
la Y-ésima versión del paquete de Ubuntu originado de
la X-ésima versión del paquete Debian (0 si no proviene
de un paquete Debian). Los números de versión de
paquete comienzan por 1.
La razón de este esquema de versionado es para
permitir una fácil integración con los paquetes
Debian. Normalmente, la política de Ubuntu es sólo
crear nuevos paquetes o versiones de éstos si el
paquete Debian está anticuado o tiene algún
problema. Así, si los de Debian sacan una nueva
versión, como la -3, a partir de
-2ubuntu3, se reflejará dicha
información de forma correcta.
Así, para la próxima versión del paquete, sólo tendremos que añadir la entrada en cuestión al registro, y nuestros guiones de ayuda harán el resto del trabajo.
Mucho cuidado con el formato del registro, es muy rígido. El espaciado debe ser exactamente el mismo que en el ejemplo, como los dos espacios entre la dirección de correo y la fecha, o el espacio inicial al inicio de la misma línea.
Para este fichero no hay que hacer nada: sólo contiene un número entero, indicando qué versión del paquete debhelper estamos usando.
Este fichero es muy importante: describe todos los paquetes que estamos definiendo y enuncia sus dependencias. El formato es también bastante rígido, pero muy simple. Utiliza una serie de campos delimitados por ':' y saltos de línea.
Por supuesto, no existe ninguna receta mágica que nos
diga las dependencias de un programa cualquiera. Para
ello, normalmente tendremos que examinar la
documentación del desarrollador original, y/o el guión
de compilación que utilice: como aquí usan las
autotools, podríamos
consultar src/configure.in. Una
buena referencia respecto a las
autotools es el Autobook
[13].
Por suerte, los desarrolladores de ZSNES han incluido
dichas dependencias en
docs/install.txt, con lo que no
tendremos que ir buscando en los guiones de
compilación.
El fichero que usaremos será éste:
Source: zsnes
Section: games
Priority: optional
Maintainer: Antonio Garcia <nyoescape@gmail.com>
Build-Depends: cdbs, debhelper (>= 4.1.0), autotools-dev, fakeroot,
desktop-file-utils, g++ (>= 4), libsdl1.2-dev, nasm (>= 0.98),
zlib1g-dev (>= 1.2.3), libpng12-dev (>= 1.2), libncurses5-dev,
libgl1-mesa-dev
Standards-Version: 3.7.2
Package: zsnes
Architecture: i386
Depends: ${shlibs:Depends}
Description: Emulador de Super Nintendo
Emulador de la consola Super Nintendo con más funciones disponibles.
Permite guardar y cargar estados, grabar demostraciones, y aplicar
diversos filtros. Tiene una compatiblidad inmejorable.
En éste y en cualquier otro fichero de control de Debian, no debemos olvidar poner un salto de línea justo al final del fichero.
Examinando el fichero de campo a campo, tenemos:
Source: zsnesIndica que el paquete fuente del que derivan todos se llama "zsnes".
Section: gamesPor la política de Debian [2], todo paquete se halla en alguna sección de las disponibles. Así indicamos qué tipo de aplicación es: un juego, un editor, etc.
Priority: optionalIndica la importancia del paquete: desde imprescindibles (required), pasando por importantes (important), estándar (standard), opcionales (optional), y extra (tienen conflicto con alguno de más prioridad).
Maintainer: Antonio Garcia <nyoescape@gmail.com>Nombre y dirección de contacto del desarrollador del paquete.
Build-Depends: ...Paquetes requeridos para poder compilar este paquete. Incluye las herramientas para paquetes Debian y las dependencias del propio programa.
Standards-Version: 3.7.2
Versión de la política de Debian que este documento
sigue. Realmente se halla compuesta por varios
documentos, todos situados bajo el directorio
/usr/share/doc/debian-policy.
Package: zsnesNombre de uno de los paquetes binarios generados a partir del fuente. Aquí sólo hay uno y tiene el mismo nombre.
Architecture: i386Arquitectura a la que va dirigida el paquete. Existe una gran variedad de valores, pero nos interesan sobre todo i386 (la IA-32 habitual), source (código fuente) y all (código sin una arquitectura definida, como programas Java, o guiones de algún lenguaje interpretado como Perl o Python).
Depends
Paquetes requeridos para que éste se instale y
funcione correctamente. La variable
${shlib:Depends} incluye las
dependencias deducidas de forma automática en
cuanto a bibliotecas dinámicas se refiere.
DescriptionIncluye una descripción corta de una sola línea y otra más larga de varias líneas del contenido del paquete. Al igual que siempre, su formato es muy rígido: toda línea de la descripción larga comienza por un espacio, y líneas vacías únicamente añaden un punto ('.'). El campo termina tras la primera línea sin dicho espacio inicial.
Mucho cuidado con los acentos y demás en el nombre del desarrollador del paquete y otros campos: podrían causar problemas en el interior de la jaula chroot, que sólo tiene soporte para los caracteres ASCII de 7 bits.
Contiene la información relativa a la licencia del paquete y del programa original, junto con datos acerca de los autores originales, su copyright y de dónde descargamos el código fuente.
En este caso sólo tenemos que rellenar sin más los
campos. No se fuerza ningún formato particular sobre el
fichero. Es importante sustituir "Upstream Author(s)" por
"Upstream Authors" y fijarnos en la información en
docs/authors.txt del código fuente, o
los verificadores de paquetes que veremos después darán
avisos al respecto.
En este fichero listamos los directorios en que vamos a instalar algún fichero. Si no lo listamos aquí, dicho directorio no va a hallarse disponible durante la construcción del paquete, así que hay que tener cuidado. Las rutas deben de seguir el Filesystem Hierarchy Standard (FHS), disponible a través de la orden man hier desde cualquier terminal.
Algunas rutas importantes y sus contenidos son:
/binEjecutables usados en modo monousuario. Normalmente realizan tareas de mantenimiento a bajo nivel, entre otras cosas. Instalados a través de paquetes Debian.
/bootConfiguración de GRUB, ficheros de imagen de los kernels disponibles, etc.
/devÁrbol de directorios donde cada dispositivo conectado al sistema es un fichero.
/etcFicheros de configuración global (para todos los usuarios).
/homeDirectorios de casa de cada usuario, con espacio para cada uno de ellos.
/mntDispositivos externos montados temporalmente: particiones de Windows, CD, DVD, pendrives, etc.
/procÁrbol de directorios con información del kernel en cada fichero: procesos en ejecución, dispositivos disponibles, etc.
/rootDirectorio de casa del superusuario.
/sbinEjecutables para uso del superusuario.
/usrDatos, programas y bibliotecas compartidos por todos los usuarios.
/usr/binEjecutables para todos los usuarios, instalados a través de paquetes Debian.
/usr/libBibliotecas para todos los usuarios, instalados a través de paquetes Debian.
/usr/localSimilar a /usr, pero para uso del administrador.
/usr/shareDatos compartidos por todos los usuarios.
/usr/share/manPáginas de man disponibles. Toda página se halla dentro de una sección. En particular, la de zsnes estaría en la 1, tras ver las instrucciones disponibles a través de la orden man man.
Dado que tenemos que instalar el ejecutable para todos los
usuarios y una página man, nuestro
fichero dirs contendrá:
usr/bin usr/share/man/man1
En este fichero, las rutas no
incluyen una barra inicial, como suelen hacer. Para ser
más exactos, son rutas a crear dentro del área temporal
de construcción del directorio
debian/zsnes, donde colocaremos
todos los ficheros tal y como se descomprimirán después
bajo el directorio raíz, /.
Aquí está el fichero más importante de todos. Es el que decide qué hay que hacer exactamente para compilar e instalar el paquete completo: documentación, binarios, guiones y ficheros de datos.
De todas formas, en términos generales, no es más que un
makefile, si bien uno que puede
hacerse muy complejo: el objetivo
build compila,
install instala dentro del área de
construcción del paquete, y clean
retira los ficheros generados durante la
construcción. Esta última se halla bajo la ruta relativa
debian/zsnes respecto del directorio
principal del paquete.
Dado que escribir una y otra vez un
makefile completo para muchas
aplicaciones parecidas era una pérdida de tiempo, se han
desarrollado diversos paquetes que factorizan cierta
funcionalidad común, como instalar páginas
man, tipos MIME, entradas de menú, y
cosas del estilo: son los guiones del paquete
debhelper. Prácticamente nadie hoy en
día desarrolla sus paquetes sin estos guiones.
Algunos desarrolladores han decidido ir un paso más allá,
y factorizar reglas para perfiles completos de
aplicaciones. Así, si sabemos que se trata de una
aplicación desarrollada a través de las autotools, sólo
tendremos que aplicar dicho perfil, añadiendo las opciones
oportunas que pasar a configure, por
ejemplo. Esto es el Common Debian Build System (CDBS)
[3], que usaremos en esta
guía. Existen perfiles para aplicaciones Python, Perl,
GNOME, KDE, Java (basadas en Ant), o incluso para aquellas
con un simple makefile.
Por supuesto, para paquetes complicados, este sistema se queda corto, pero son minoría comparados con los demás. Además, no es sólo cuestión de simplicidad: factorizando la mayor proporción posible de reglas, blindaremos nuestro paquete ante cambios en la política de Debian en el futuro.
De todas formas, en general, la comunidad de desarrolladores se halla muy dividida entre usar o no CDBS: aunque factoriza mucha complejidad, resulta difícil de comprender y aprovechar en casos difíciles, a menos que seamos capaces de leer complejos ficheros makefile por nosotros mismos, dado que no existe mucha documentación detallada al respecto: para CDBS, el código es la mejor documentación.
Dado que resulta imposible entender bien CDBS si no se comprende antes el sistema tradicional, en esta sección explicaremos las dos alternativas. Comenzaremos por el sistema "tradicional" con los guiones de debhelper, y luego veremos cómo CDBS factoriza la mayor parte de estas reglas.
Partiendo del esqueleto que automáticamente nos ha creado dh_make, lo retocamos para este paquete en particular. Vamos a ver qué tal ha quedado, y luego explicaremos qué partes exactamente hemos cambiado, y por qué:
#!/usr/bin/make -f
# -*- makefile -*-
# This file was originally written by Joey Hess and Craig Small. As a
# special exception, when this file is copied by dh-make into a dh-make
# output file, you may use that output file without restriction. This
# special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# El código se halla en un subdirectorio, no en la raíz
SRCDIR = src
# Opciones a pasar a configure (--enable-release activa optimizaciones)
CONFIGURE_FLAGS = --disable-cpucheck --enable-release --with-x --with-opengl
# Opciones a usar en el compilador
CFLAGS = -Wall -g
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
configure: configure-stamp
configure-stamp:
dh_testdir
touch configure-stamp
build: build-stamp
build-stamp: configure-stamp
dh_testdir
cd $(SRCDIR) && \
force_arch=i586 ./configure $(CONFIGURE_FLAGS) --prefix=/usr
$(MAKE) -C $(SRCDIR)
touch $@
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
# Limpiamos también las herramientas internas usadas por ZSNES en su
# compilación
-$(MAKE) -C $(SRCDIR) clean tclean
# Borramos los ficheros temporales generados por la compilación
$(RM) $(SRCDIR)/tools/depbuild $(SRCDIR)/config.{log,status,h} $(SRCDIR)/Makefile
dh_clean
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
$(MAKE) -C $(SRCDIR) install DESTDIR=$(CURDIR)/debian/zsnes
# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installexamples
# dh_install
# dh_installmenu
# dh_installdebconf
# dh_installlogrotate
# dh_installemacsen
# dh_installpam
# dh_installmime
# dh_python
# dh_installinit
# dh_installcron
# dh_installinfo
dh_installman $(SRCDIR)/linux/zsnes.1
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure
Aunque es bastante largo, conceptualmente es sencillo, gracias al uso de debhelper. Un par de cosas a destacar:
Dado que nuestro código fuente se halla bajo un
subdirectorio y no en el directorio raíz del
paquete, añadimos una variable
SRCDIR, cuyo valor tendremos en
cuenta para realizar un cambio de directorio antes
de cada orden de compilación.
Por otro lado, CURDIR contiene la
ruta del directorio raíz actual desde el cual se
está construyendo el paquete. La ruta
$(CURDIR)/debian/zsnes contiene
un árbol de directorios que sigue el FHS, y se
corresponde con los ficheros contenidos en el
paquete zsnes.
Bajo el objetivo de compilación
build-stamp añadimos las órdenes
requeridas para compilar: invocamos al guión
configure con las opciones
necesarias e iniciamos la compilación. La opción
-C pasada a
make hace el cambio de
directorio antes de comenzar, justo como
cd hace para las
demás. Hay que hacerlo para cada orden y no al
principio debido al hecho de que tras cada orden
volvemos al directorio original, al restaurarse el
estado anterior del shell.
Repetimos el cambio en la invocación a
make para los otros
objetivos clean e
install. Puede verse cómo se pasa
la variable de entorno DESTDIR
con la ruta al área de construcción para la
instalación: evidentemente, el
makefile debe de estar hecho
para tener esto en cuenta. Tenemos la suerte para
este paquete de que ya sea así: de lo contrario,
tendríamos que adaptar dicho fichero, ¡y
posiblemente el resto del programa!
Por último, en el objetivo
binary-arch tenemos una serie de
llamadas a distintos guiones de
debhelper. Comentaremos y
descomentaremos según nos haga falta: así, por
ejemplo, un programa escrito en Perl no necesita
dh_link ni
dh_strip, al no generar
ejecutables.
Hemos añadido un argumento a
dh_installman con la página
man que queremos que se
instale. Al igual que con todo lo demás, si no
hubiera una, tendríamos que crearla nosotros. Lo más
usual en este caso es escribir un fichero SGML o XML
DocBook y transformarlo a nroff
(el formato de las páginas
man) mediante
docbook-to-man o una hoja
de estilos XSLT, por ejemplo. Podríamos partir del
ejemplo creado antes por
dh_make>,
zsnes.sgml.ex.
Un detalle importante: la mayoría de los guiones suponen
que los ficheros bajo nuestro directorio
debian siguen una serie de
convenciones. Por ejemplo,
dh_installdocs supone que existe
algún fichero debian/zsnes.docs o
debian/docs que liste la
documentación a instalar. En este caso, aprovechamos la
documentación que ya trae ZSNES, con lo que tendríamos
esto en debian/docs:
docs/srcinfo.txt docs/README.SVN docs/opengl.txt docs/stdards.txt docs/authors.txt docs/todo.txt docs/install.txt docs/thanks.txt docs/support.txt docs/README.LINUX docs/readme.txt/about.txt docs/readme.txt/faq.txt docs/readme.txt/history.txt docs/readme.txt/gui.txt docs/readme.txt/advanced.txt docs/readme.txt/index.txt docs/readme.txt/games.txt docs/readme.txt/netplay.txt docs/readme.txt/readme.txt docs/readme.txt/support.txt docs/readme.htm/styles/release.css docs/readme.htm/styles/print.css docs/readme.htm/styles/jipcy.css docs/readme.htm/styles/radio.css docs/readme.htm/styles/corner.png docs/readme.htm/styles/plaintxt.css docs/readme.htm/styles/shared.css docs/readme.htm/images/zsneslogo.png docs/readme.htm/images/netplay.png docs/readme.htm/images/quick.png docs/readme.htm/images/saveslot.png docs/readme.htm/images/cheat.png docs/readme.htm/images/gui.png docs/readme.htm/images/config.png docs/readme.htm/images/game.png docs/readme.htm/images/f1_menu.png docs/readme.htm/images/misc.png docs/readme.htm/netplay.htm docs/readme.htm/about.htm docs/readme.htm/games.htm docs/readme.htm/advanced.htm docs/readme.htm/gui.htm docs/readme.htm/support.htm docs/readme.htm/readme.htm docs/readme.htm/history.htm docs/readme.htm/license.htm docs/readme.htm/faq.htm docs/readme.htm/index.htm docs/readme.1st
Hemos usado ya otro fichero más del mismo estilo:
dirs es realmente el fichero que
dh_installdirs utiliza, por
ejemplo.
Ahora que ya sabemos cuál es la estructura real de un fichero de reglas, lo reescribiremos empleando CDBS:
#!/usr/bin/make -f # -*- makefile -*- DEB_SRCDIR = src include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/autotools.mk DEB_CONFIGURE_SCRIPT_ENV += force_arch=i586 DEB_CONFIGURE_EXTRA_FLAGS = --disable-cpucheck --with-x \ --enable-release --with-opengl DEB_INSTALL_MANPAGES_zsnes += $(DEB_SRCDIR)/linux/zsnes.1
Sorprendentemente, esto es todo: ocho líneas. Los dos include se ocupan de importar los conjuntos de reglas de apoyo para el uso interno de debhelper e implementar el soporte para el perfil de las autotools, respectivamente.
A continuación, pasamos las mismas opciones a
configure que antes: pedimos que
compile para Pentium o superior, que emplee aceleración 3D
y un interfaz gráfico, optimice algo más de lo normal
(
--enable-release
) y no intente
autodetectar nuestra CPU. Finalmente, indicamos que
instale la página man
src/linux/zsnes.1 que incluye ZSNES.
Usaremos esta versión de debian/rules
para realizar el resto del documento, aprovechando algunas
funcionalidades adicionales que aporta. Sin embargo,
internamente, es exactamente lo mismo de antes.
Con todo listo, ya podemos construir el
paquete. Situándonos en el directorio principal del paquete,
/tmp/packages/zsnes-1.510, ejecutaremos:
dpkg-buildpackage -rfakeroot
Tras un cierto tiempo, nos preguntará la contraseña de nuestra
clave privada para firmar el paquete de forma automática. Poco
después, tendremos en
/tmp/packages nuestra primera versión del
paquete Debian,
zsnes_1.510-0ubuntu1_i386.deb, junto con
un fichero .dsc que describe el paquete
fuente que también hemos construido, y un
diff.gz con las diferencias respecto a
las fuentes originales.
Con nuestra primera versión del paquete lista, sólo nos queda
inyectar el paquete en el repositorio Subversion. Nos
situaremos en ~/packages y ejecutaremos:
svn-inject -c2 -o /tmp/packages/zsnes_1.510-0ubuntu1.dsc \
file:///home/tunombredeusuario/.svnDebian
Tras un cierto tiempo, ya tendremos enviado al repositorio
central nuestro paquete, y nuestra copia de trabajo habrá sido
creada, con lo que no necesitaremos más
/tmp/packages.
La opción -o evita que se guarde el código
fuente en el repositorio, usando únicamente archivos
tar.gz en el subdirectorio
tarballs del directorio principal del
repositorio, que no se hallará bajo control de versiones.
El repositorio creado tiene la siguiente estructura:
~/packages/tarballs
Contiene los ficheros tar.gz con
las fuentes originales de nuestros paquetes.
~/packages/zsnes/build-areaSe trata del directorio destino en el que se depositarán todos los paquetes y demás ficheros que vayamos produciendo.
~/packages/zsnes/branchesAlmacena las distintas ramas de desarrollo. Normalmente contendría las distintas versiones del código original, pero al usar tarballs, es prácticamente inútil en nuestro caso.
~/packages/zsnes/tagsPermite asociar números de versión con determinadas revisiones del repositorio. Posteriormente veremos cómo se usa.
~/packages/zsnes/trunk
Aquí se almacena la versión actual del paquete. Sólo
almacenamos el directorio debian,
para ahorrar la complejidad y tiempo de descarga
necesario de otra forma.