Autotools y amigos

De Wiki GNOME Chile
Saltar a: navegación, buscar

Contenido

Autotools y sus amigos

En esta lección, aprenderemos a crear y distribuir un proyecto usando las no bien ponderadas Autotools (Autoconf/Automake y amigos).

Comenzando

Primero, debemos crear un directorio donde irá nuestro proyecto. En mi caso, /home/jci/code/proyecto1

$ mkdir ~/code/proyecto1
$ cd ~/code/proyecto1

Pueden elegir el directorio que deseen

Dentro de ese directorio deben colocar los siguientes archivos (o bien, los encontrarán en cualquier proyecto GNU):

  • AUTHORS, donde van los autores del programa
  • LICENSE, donde describen la licencia que adoptarán para el programa
  • Changelog, un control de cambios del programa
  • COPYING, donde va una copia de la licencia (en el caso de GPL, GPLv2, etc)
  • INSTALL, con instrucciones especiales para la instalación del programa
  • README, con instrucciones acerca del programa, o bien para indicar algo que debe leer el usuario antes de ejecutar el programa

Los archivos anteriores no tienen que ver directamente con el sistema de construcción. Están como manera informativa, indicar la licencia en la cual se distribuyen, entre otros elementos (como dependencias, autores, entre otros tópicos).

Ahora, estos archivos SON requeridos por automake, al momento de construir la distribución de nuestro proyecto. Lo veremos un poco mas adelante, cuando veamos en profundidad el uso de automake.

Recordar que automake es parte de las herramientas de construcción de GNU para distribución de programas de codigo abierto. Asi que es la razón de por que son necesarios. Aunque hay muchos proyectos que tienen esos mismos archivos en blanco, la regla del dedo gordo es que no usen esos archivos en blanco, nunca! si quieren distribuir su proyecto con otras personas.

Empecemos.

configure.ac

Esta es la plantilla para el script de configure. Indica, por ejemplo, las dependencias para compilar, tests para ver si existe o no alguna aplicación o biblioteca del sistema, entre otras.

Su misión es solamente proveer un unico punto para la configuración para la construcción del programa, ademas de hacerlo portable. Es la base de la mayoría de las herramientas de construcción de GNU (o en otras partes, encuentran el termino como GNU tools). O bien, de las que se distribuyen como tal.

El único inconveniente de este archivo es que esta escrito en lenguaje de macros, que una vez usando autoconf, seran expandidas a un shell script. Algo complicado al principio de entender, pero despues se entiende mucho mejor.

Ahora, supongamos que mi proyecto se llama "diamante" y que el fuente del programa esta en la ruta src/diamante.c. Haremos un archivo configure.ac para mostrar la forma de crear uno correctamente.

Este es el contenido de mi configure.ac para el programa "diamante":

AC_INIT([Diamante], [0.1], [[email protected]], [diamante])

AC_CONFIG_SRCDIR(src/diamante.c)

AC_CONFIG_HEADER(config.h)

AM_INIT_AUTOMAKE
AM_MAINTAINER_MODE

AC_PROG_CC
AC_HEADER_STDC

AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)

GTK_MODULES="gtk+-2.0"
PKG_CHECK_MODULES(GTK, $GTK_MODULES)
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)

AC_OUTPUT([
Makefile
src/Makefile
])

Veamos que significa cada cosa.

Directivas y Variables

AC_INIT indica que Autoconf debe iniciarse para crear el proyecto. Los argumentos son

(nombre_proyecto, version, email, nombrecorto)

AC_CONFIG_SRC indica cual es el directorio donde se encuentra el codigo fuente de nuestro programa (en este caso, src/diamante.c).

AC_CONFIG_HEADERS le indica al preprocesador de C que deba leer un archivo antes de compilar. Por lo general, es config.h. Este config.h se crea de forma automática usando autoheader.

AM_INIT_AUTOMAKE hace que Automake inicie ;)

AM_MAINTAINER_MODE hace que se habilite la opción --enable-maintainer-mode en la configuracion del programa, para agregar opciones tales como bibliotecas con soporte de debugging, entre otras.

AC_PROG_CC y AC_HEADER_STDC le indica a Autoconf que debe aceptar un compilador de C y que debe poseer las cabeceras estandar. Por lo general, si no estan instaladas, el error sera el siguiente

C compiler error : cannot create executables

Autoconf permite el uso de variables. En este caso, creé la variable GTK_MODULES, donde agregué los modulos necesarios para que el programa pudiera compilar. En este caso, gtk+-2.0.

PKG_CHECK_MODULES permite que pkg-config (importante, lo vimos en la primera lección) pueda verificar si los modulos existen. Y los comprueba desde la variable GTK_MODULES. Además, llena una variable llamada GTK.

La macro AC_SUBST pasa valores desde autoconf a automake (para la creación del Makefile). Es decir, si existe una cadena para reemplazo (generalmente, $(CADENA) ) en un Makefile.in, configure reemplazará la cadena con el valor que exista en los parentesis de AC_SUBST.

Por ejemplo,

AC_SUBST(CFLAGS)

CFLAGS es una variable que contiene, por ejemplo, el siguiente valor:

-l -g -d --march="i686" 

Si existe algo así en el Makefile.in

CFLAGS=$(CFLAGS)

sera reemplazado así

CFLAGS=-l -g -d --march="i686"

al crear el archivo Makefile desde la plantilla. Esto se verá mas claro una vez que explique mas adelante el uso de AC_OUTPUT. Asi que sigan leyendo! :D

CFLAGS es una variable muy importante al momento de construir cualquier proyecto que requiere el compilador de C (GNU C Compiler, Compilador GNU de C o mas conocido como GCC en este caso particular). Esta variable indica que modificadores, opciones, variables o banderas pasar al compilador al momento de construir el proyecto. Algunas veces contiene elementos de optimización, otras veces contiene elementos para la depuración del proyecto, entre otras. Por ejemplo, la opci&ocute;n -g indica que el compilador debe agregar opciones para depuración. Si requieren mas info, pueden consultar la pagina de manual de gcc.

Hasta el momento, tengo creado un directorio con los siguientes archivos:

./configure.ac
./src
./src/diamante.c

Por el momento no me voy a preocupar por los archivos faltantes (NEWS, README, AUTHORS y ChangeLog), ya que no son necesarios para la construccion del proyecto por ahora. Pero para distribuirlo, los necesitaremos.

Ahora, necesito que algunas plantillas (.m4) se copien al directorio de mi proyecto. Lo puedo hacer con aclocal:

$ aclocal

lo que copiará un archivo llamado aclocal.m4

Luego, ejecutar autoheader, para que cree automaticamente el archivo config.h.in

$ autoheader

Ahora, ejecutar

$ automake --add-missing --gnu

La salida será la siguiente:

configure.ac: installing `./install-sh'
configure.ac: installing `./mkinstalldirs'
configure.ac: installing `./missing'

Ahora, ejecutemos autoconf

$ autoconf

Y listo. Tendremos un configure limpiecito.

ALTO! Veamos que pasa al ejecutar ./configure

$ ./configure
[...challa...]
configure: creating ./config.status
config.status: error: cannot find input file: Makefile.in

Hmmm...Makefile.in...de donde vendra eso?

Un Makefile en una distribución con autotools esta creado con una serie de plantillas. Primero Makefile.am para automake que genera un Makefile.in, que es una plantilla para un archivo Makefile.

Como no existe el archivo Makefile.in, no puede generar el archivo Makefile.

Cómo se sabe esto?

Veamos las ultimas lineas del archivo configure.ac:

AC_OUTPUT([
Makefile
src/Makefile
])

AC_OUTPUT le indica a autoconf que genere esos archivos de forma automágica usando plantillas, o bien archivos .in. En este caso, Makefile.in y src/Makefile.in van a generar los archivos Makefile y src/Makefile, en ese orden. Ninguno de ellos esta. Tampoco podremos crear los archivos a mano, ya que esa es pega de Automake.

Creamos entonces un Makefile.am en el directorio raiz:

$ touch Makefile.am

y volvemos a ejecutar

$ automake --gnu --add-missing

La salida es un poco mas larga que la vez anterior:

Makefile.am: installing `./COPYING'
Makefile.am: installing `./INSTALL'
Makefile.am: required file `./NEWS' not found
Makefile.am: required file `./README' not found
Makefile.am: required file `./AUTHORS' not found
Makefile.am: required file `./ChangeLog' not found

Me indica que los archivos NEWS, README, AUTHORS y ChangeLog no existen. Creemos estos archivos en blanco:

$ touch NEWS README AUTHORS ChangeLog

No es necesario volver a ejecutar automake, ya que no hemos modificado Makefile.am

Ahora, un configure nuevamente:

$ ./configure

Esta vez, mágicamente, funciono. Sin ningun error. Pero aun falta.

Por ahora, configure.ac y configure dejémoslo de lado. Concentrémonos en el archivo Makefile.am

Makefile.am

Por el momento el archivo Makefile.am de la raiz del proyecto esta vacío. Pero agreguemos lo siguiente en el:

SUBDIRS=src

La directiva SUBDIRS indica a Automake cuales subdirectorios seguir una vez que termine con el make del directorio raíz. Por ejemplo, si hubiera puesto

SUBDIRS=src po intltool foo bar

Al ejecutar make, hubiera ejecutado los makefiles de los directorios src, po, intltool, foo y bar, y luego el del raíz. Y en ese orden.

Volvamos a ejecutar automake

$ automake --gnu --add-missing

Y ahora

$ ./configure

Si todo va bien, ejecutemos

$ make

Debería pasar el proyecto sin ningun problema. Mi salida fue la siguiente:

$ make
make  all-recursive
make[1]: Entering directory `/home/jci/code/diamante'
Making all in src
make[2]: Entering directory `/home/jci/code/diamante/src'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/home/jci/code/diamante/src'
make[2]: Entering directory `/home/jci/code/diamante'
make[2]: Leaving directory `/home/jci/code/diamante'
make[1]: Leaving directory `/home/jci/code/diamante'

Perfecto, nuestro sistema esta casi listo.

Pero solo falta ahora crear un programa, ya que diamante.c esta vacío! Y ademas, poder construirlo para distribuirlo. Es la razón de este capítulo!.

Así que coloquemos un miserable hola mundo de la primera lección. O el programa que quieran.

Yo voy a ir con este :)

#include <gtk/gtk.h>

int main(int argc, char * argv[])
{
	GtkWidget * ventana;
	GtkWidget * boton;

	gtk_init(&argc, &argv);
	
	ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	boton = gtk_button_new_with_label("Hola Mundo");
	
	gtk_container_add(GTK_CONTAINER(ventana), boton);
	gtk_widget_show_all(ventana);

	gtk_main();
}

Ahora, el siguiente actor : src/Makefile.am.

Ya que Autotools es un sistema de construcción automática, y hemos dicho en el archivo Makefile.am, de la raiz del proyecto que uno de los subdirectorios a ejecutar un Makefile es en src/, entonces debemos también tener una plantilla (src/Makefile.am) para Automake.

Agreguemos lo siguiente en src/Makefile.am:

INCLUDES = -I$(top_srcdir) $(GTK_CFLAGS)

LIBS = $(GTK_LIBS)

bin_PROGRAMS = diamante

Recuerdan que AC_SUBST en Autoconf permit&ia reemplazar cadenas con los valores que tuvieran las variables? Las que se reemplazan son GTK_CFLAGS y GTK_LIBS.

La directiva INCLUDES indica que rutas de archivos de cabeceras agregar para compilar efectivamente un programa.

Recuerdan que para compilar un programa en GTK+ era necesario usar el metodo de los backquotes para no colocar una tremenda linea?

$ gcc -o diamante.o diamante `pkg-config --cflags gtk+-2.0`

INCLUDES es equivalente. Para el caso de pkg-config, es usar el modificador --cflags.

Ahora, en INCLUDES hay ademas una variable $top_srcdir. Esa indica el directorio raíz de nuestro proyecto. Es solo en caso que usemos el archivo config.h para algo.

La directiva LIBS es la directiva para enlazar con las bibliotecas necesarias. En los ejemplos anteriores era algo así

$ gcc -o diamante diamante.o `pkg_config --libs gtk+-2.0`

En este caso, es equivalente. El modificador --libs para pkg-config es lo mismo.

bin_PROGRAMS indica cual es el archivo binario a generar despues que se compiló el archivo. Recordar que se indica cuales archivos compilar en la directiva AC_CONFIG_SRCDIR de Autoconf :-D

De nuevo, ejecutamos automake en la raiz de nuestro proyecto.

$ automake --gnu --add-missing

Luego

$ ./configure

Y después

$ make

Y veran que empezará a compilar el fuente que estaba en el directorio src/. Excelente!

$ ls src/
diamante  diamante.c  diamante.o  Makefile  Makefile.am  Makefile.in

Perfecto! Es justo lo que necesito. Y hemos generado nuestro primer proyecto con Autotools!

Creando la distribucion de nuestro proyecto

Ahora veamos como podemos distribuirlo para colocar en nuestra página web y cachiporrearnos en el asado con los amigos que "publiqué mi primer programa en GTK+" :) .

make

Antes de seguir, voy a explicar algunas opciones que son básicas.

Despues de ejecutar

$ ./configure

Pueden construir el proyecto con

$ make

make install

Pueden ejecutar despues

$ make install

para instalar su proyecto. Por lo general, lo haran en /usr /local /bin, pero solo podrán hacerlo si tienen permiso de superusuario en ellos.

En algun capitulo a futuro voy a hablar para poder tener un proyecto donde quieran.

make clean

También pueden ejecutar

$ make clean 

que lo que hace es limpiar de los archivos de construcción y archivos resultantes de nuestro proyecto. No borra los Makefiles y podremos ejecutar make cuando queramos.

En mi caso,

$ make clean

dio el siguiente resultado:

$ make clean
Making clean in src
make[1]: Entering directory `/home/jci/code/diamante/src'
test -z "diamante" || rm -f diamante
rm -f *.o core *.core
make[1]: Leaving directory `/home/jci/code/diamante/src'
Making clean in .
make[1]: Entering directory `/home/jci/code/diamante'
make[1]: Nothing to be done for `clean-am'.
make[1]: Leaving directory `/home/jci/code/diamante'

make distclean

Lo interesante viene si quieren distribuir su proyecto. Para eso pueden usar

$ make distclean

Con make distclean, los archivos que se generan de forma automática en el sistema de construcción son eliminados, junto con los archivos que se compilan. Asi queda nuestro proyecto sin Makefiles ni archivos de dependencias, corefiles, entre otros.

$ make distclean
make[1]: Entering directory `/home/jci/code/diamante/src'
test -z "diamante" || rm -f diamante
rm -f *.o core *.core
rm -f *.tab.c
rm -f 
rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
rm -rf ./.deps
rm -f Makefile
make[1]: Leaving directory `/home/jci/code/diamante/src'
Making distclean in .
make[1]: Entering directory `/home/jci/code/diamante'
rm -f 
rm -f config.h stamp-h1
rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
make[1]: Leaving directory `/home/jci/code/diamante'
rm -f config.status config.cache config.log configure.lineno
rm -f Makefile

make distcheck

Lo mas importante.

Va a sonar a chiste, pero hay que ejecutar de nuevo

$ ./configure

Y una vez que termine, ejecutar

$ make distcheck

La opción distcheck realiza una compilación limpia, copia el arbol de compilación completo en un directorio y ve si puede construir el proyecto. Si es asi, al final tendremos este lindo mensaje:

=============================================
diamante-0.1.tar.gz is ready for distribution
=============================================

Ese archivo .tar.gz podremos pasarlo a nuestros amigos, o bien respaldar donde queramos. :D

Veamos que tiene dentro...

$ tar tfz diamante-0.1.tar.gz
diamante-0.1/
diamante-0.1/aclocal.m4
diamante-0.1/AUTHORS
diamante-0.1/Makefile.am
diamante-0.1/README
diamante-0.1/install-sh
diamante-0.1/depcomp
diamante-0.1/config.h.in
diamante-0.1/INSTALL
diamante-0.1/ChangeLog
diamante-0.1/configure
diamante-0.1/src/
diamante-0.1/src/Makefile.am
diamante-0.1/src/diamante.c
diamante-0.1/src/Makefile.in
diamante-0.1/missing
diamante-0.1/Makefile.in
diamante-0.1/NEWS
diamante-0.1/mkinstalldirs
diamante-0.1/configure.ac
diamante-0.1/COPYING

Versiones

La pregunta mas logica que se deben estar haciendo es "por que supo que era la version 0.1?"

Recuerdan la primera linea de AC_INIT del archivo configure.ac?

AC_INIT([Diamante], [0.1], [[email protected]], [diamante])

El segundo parámetro es la versión de nuestro programa.

No me creen? Cambien el valor de 0.1 a 0.2. Vuelvan a ejecutar todo en este orden:

$ autoheader
$ autoconf
$ automake --gnu --add-missing
$ make distclean
$ ./configure
$ make distcheck
...[challa]...

=============================================
diamante-0.2.tar.gz is ready for distribution
=============================================

Bonito, no?

Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas