Mostrando entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta programación. Mostrar todas las entradas

sábado, septiembre 29, 2018

Autotools, tutorial de uso (VIII): Añadir soporte para ejecutar tests

Introducción

Test-driven development (TDD) es una técnica de desarrollo de software consistente en escribir primero las pruebas que debe de superar una unidad de código determinada, posteriormente implementar dicha unidad y llegado el caso, refactorizarla. Por otra parte, las pruebas se puede desarrollar para probar ciertas unidades de código que ya se tienen . Estos últimos años, donde los sistemas de integración contínua son cada vez más comunes, implementar pruebas que nos permitan detectar cuanto antes problemas en el código es una práctica muy recomendable de ingeniería de software.

Las autotools (automake, autoconf, etc) permite construir de manera fácil los ejecutables de las pruebas. Después de generar el makefile, estas pruebas se ejecutarán con un simple:

make check

Esta pequeña entrada se dan los pasos básicos para poder incorporar a nuestro proyecto que usa las autotools la ejecución de las pruebas.

Añadiendo los tests a nuestro proyecto con automake

Supongamos que tenemos una estructura de directorios tal que así de un proyecto llamado tut6:

tut6
  src
    tests

Queremos que cuando se ejecute la orden make check se ejecuten los tests necesarios de nuestro paquete de software. Estos checks pueden ser programas compilados o bien scripts que llaman a otros programas. Para eso, se define una varible TESTS en nuestro Makefile.am situado en el directorio tests, que es donde residierán los programas o scripts que se quiere ejecutar en la fase de pruebas. Por ejemplo, si el programa que hace los tests se llama list_test bastaría con poner en el Makefile.am:

TESTS = list_test

Por otra parte, puede que sea necesario compilar programas específicos para tests, que no se instalarán y que sólo se ejecutaran cuando el usuario quiera pasar los tests. Para ello existe una serie de variables, cuyo prefijo es check_ que nos permitirán compilar el programa, librerias, etc que nos interese durante la ejecución de los pasos asociados al make check.

Siguiendo el ejemplo anterior, supongamos que el programa list_test depende del fichero list_test.c y del fichero situado en las fuentes list.c. Queremos que dicho programa se compile cuando se ejecuta la orden make check en nuestro proyecto. Bien, pues basta con añadir a nuestro fichero Makefile.am las siguientes líneas:

TOP = $(top_srcdir)
TESTS = list_test
check_PROGRAMS = list_test
# List lib
list_test_SOURCES = list_test.c $(TOP)/src/list.c
list_test_CFLAGS = -I$(TOP)/src/

Como puede verse, las variables se establecen con el prefijo del programa (list_test_ en este ejemplo) corresponde a opciones que se pasarán al compilador de C (si fuera, C++, sería _CPPFLAGS)

Puede encontrarse el código de un ejemplo de todo lo anterior en tut6

viernes, septiembre 28, 2018

Tutorial de las autotools. Índice de artículos

Hace algunos años escribí varios artículos en el blog a modo de tutorial sobre el uso de las autotools . Para que la información sea más fácil de localizar, he organizado este pequeño índice con los artículos.

Aunque hace bastante tiempo que no escribo sobre el uso de esta herramienta, tengo otro par de artículos en la recámara que estoy retocando para añadir a esta lista. Espero que os sea útil.

martes, septiembre 19, 2017

Tutorial de uso de funciones en Bash

Una de las funcionalidades que tiene la shell Bash es la posibilidad de usar funciones las cuales no son más que una agrupación de comandos con un nombre, que se ejecutará cuando se utilize dicho nombre para involcarlas. Esto permite construir librerías de funciones que realizen operaciones que interesan.

Definición de funciones

En Bash se pueden declarar funciones de varias maneras:

#!/bin/bash

function func1(){
    echo "Function 1"
}
func2(){
    echo "Function 2"
}
# Debe haber un espacio entre el nombre
# y la llave
function func3 {
    echo "Function 3"
}

La manera más portable - y la que recoge POSIX - es la segunda, definir el cuerpo de la función usando los paréntesis.

A la hora de definir los bloques de comandos que forman el cuerpo de la función se puede hacer usando las llaves { ... body ... } o los paréntesis (... body ...). La diferencia es que en el segundo caso la función se ejecutará en una subshell. Si se usa la versión 4 de bash se puede ver con en este ejemplo el comportamiento de cada manera definir la función: Si se hacen dos ejecuciones del mismo: La llamada a func1 siempre devolverá el mismo PID que corresponde con el del shell desde donde se está llamando, mientras que en el segundo caso variará, ya que el comando se ejecuta en otra subshell.

#!/bin/bash
function func1() {
    /bin/echo "FUNC1 $BASHPID"
}
function func2() (
    /bin/echo "FUNC2 $BASHPID"
)
func1
func2

La salida de ejecutar un script con ese contenido usando source es la siguiente:.

$ source script.sh
func 1 31238
func 2 31329
$ source script.sh 
func 1 31238
func 2 31338

Se ve como func1 tiene el mismo PID (el del shell desde donde se ejecuta el source), mientras que cambia el de la subshell. Recordar que pare que bash defina la variable $BASHPID debe ser la versión cuatro.

Alcance de variables

Una cosa particular de las funciones es que las variables que se definan en ellas, tienen alcance global por defecto del script que se está ejecutando, a menos que explicitamente se les añada el modificador local. Por ejemplo, el siguiente script la llamada a func1 asignará un valor a TEMPFUNCVAR1 que se verá tras la ejecución, cosa que no pasará con TEMPFUNCVAR2 de la segunfa función.

#!/bin/bash
func1() {
    echo "Func 1"
    FUNCVAR1="You can see me"
}
func2() {
    echo "Func 2"
    local FUNCVAR2="You can't see me"
}

func1
echo "FUNCVAR1=$FUNCVAR1"
func2
echo "FUNCVAR2=$FUNCVAR2"

Si se ejecuta el script anterior, se ve que la variable que ha sido declarada con local no muestra el contenido tras ejecutar la función:

$ script.sh
Func 1
FUNCVAR1=You can see me
Func 2
FUNCVAR2=

Parámetros de la función

Cuando se llama a una función con parámetros, estos pasan a ocupar los parámetros posicionales de la shell:

  • $* se expande a todos los parámetros posiciones ($1,$2,...), con la particularidad que sigue unas reglas de expansión si está entre dobles comillas.
  • $@ Exactamente igual que el anterior, pero los parámetros se expanden cada uno a una palabra distinta.
  • $# Es el número de parámetros de la función.
  • $1,$2,...$9 son los 9 primeros parámetros de la función. Si se necesitan más, estos se numera con ayuda de las llaves, es decir ${10}, ${11}, etc
  • .

El siguiente ejemplo de código coge todo los parámetros que se le pasa a la función, imprime el número de parámetros y luego cada parámetro en una línea distinta, con usando el comando interno shift para desplazar la lista de parámetros a la izquierda al irlos consumiendo.

#!/bin/bash
func1() {
    echo "Number of parameters: $#"
    while [ ! -z "$1" ]; do
        echo "$1"
        shift
    done
}
func1 You can see five parameters
func1 two parameters
func1

Al ejecutar el script anterior la salida es la siguiente:

$ ./script.sh
Number of parameters: 5
You
can
see
five
parameters
Number of parameters: 2
two
parameters
Number of parameters: 0

Si aprovechamos la expansión del parámetro $@ la anterior función tb se puede implementar de la siguiente manera. La ejecución de este script da el mismo resultado que el anterior:

#!/bin/bash
func1() {
    echo "Number of parameters: $#"
    for param in $@;do
        echo "$param"
    done
}
func1 You can see five parameters
func1 two parameters
func1

Valores de retorno de la función

El valor de retorno de la función se devuelve por defecto en la variable $?. Las reglas que definen el valor que tomará dicha variable a la salida de la función son las siguientes:

  • Si no existe ninguna llamada a return, el valor de retorno de la función será el valor del último comando que se ha ejecutado.
  • Si se usa return sin argumento numérico, ocurre igual que en caso anterior, el valor de retorno es el de último comando ejecutado.
  • Si se usa un valor numérico, ese es el valor de retorno de la función.

Hacer notar los siguientes dos puntos:

  • Por convención en Unix, los comandos suelen devolver un valor de cero en caso de que la ejecución del mismo haya tenido éxito.
  • El mecanismo de retorno de valores de ejecución de Bash nada más que permite valores numéricos. Para devolver cadenas, hay que usar otras construcciones.

Veamos un ejemplo de todo lo anterior

#!/bin/bash
RANDOM_NAME="/tmp/$(cat /dev/urandom | LC_ALL="C" tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
func1() {
    cp -f  "$RANDOM_NAME" "$RANDOM_NAME_1" 2> /dev/null
}
func2() {
    cp -f  "$RANDOM_NAME" "$RANDOM_NAME_1" 2> /dev/null
    return
}
func3() {
    return 10
}
func1
echo $?
func2
echo $?
func3
echo $?

El script anterior va a generar un nombre aleatorio y se definen tres funciones:

  • func1 va a realizar una operación que va a fallar, copiar un fichero que no existe. Como es el último comando que se ejecuta en la función, su valor de retorno será lo que devuelva la función a través de $?.
  • func2 Utiliza un return sin parámetros, en este caso el valor de retorno será el del comando anterior, que vuelve a ser el cp que falla y es lo que retorna.
  • func3 Por último, la función en este caso, retorna directamente el valor 10.

Resumen

Este es un tutorial muy básico sobre el uso de funciones en bash que permite empezar a usar las mismas en nuestros scripts. He dejado algunos puntos sin tocar, para los que tengáis curiosidad, se puede consultar el manual de bash y el enlace sobre shell scripting de la sección referencia. Para una próxima entrada me gustaría tratar de:

  • Como definir las funciones con una sola línea.
  • Como devolver cadenas desde una función.
  • Cuál es la mejor manera de agrupar funciones para usarlas como una librería.
  • Redirección de la salida de una función

Referencias

viernes, abril 14, 2017

Calcular la letra del DNI ... en Locomotive Basic (CPC)

Reconozco que esto es una frikada. Pero el otro día venía dándole vueltas, y pensé: ¿Implementar el algoritmo de cálculo de DNI en Locomotive Basic, el intérprete de Basic que viene con los ordenadores Amstrad de la serie CPC. El algoritmo es muy sencillo: Hay que calcular el resto de la división del número de DNI y 23. Dicho valor sirve para indexar la siguiente tabla y obtener la letra del DNI.

T R W A G M Y F P D X
0 1 2 3 4 5 6 7 8 9 10
B N J Z S Q V H L C K E
1112 13 14 15 16 17 18 19 20 21 22

Hacer esta operación en un ordenador que soporte división entera de 32 bits es muy sencillo. Mi pequeño reto es hacerlo en BASIC del CPC. Esto da para varias limitaciones que hay que tener en cuenta.:

  • No se puede usar aritmética en coma flotante
  • El Locomotive Basic sólo tiene aritmética entera de 16 bits en complemento a 2
  • No quería usar una rutina en código máquina

Tras cerca de 25 años sin tocar el Basic de Amstrad, programando en lenguajes estructurados, tiene su aquel volver a usar números de línea, el goto, colocar el cursor en la pantalla,... e implementar una simple división entre 23 - aquí sí, podemos usar la división entera del Locomotive Basic, ya que como mucho el número mayor a dividir será 207 - y diseñar el pequeño programita en Basic con la siguiente captura y el listado del programa

Y este es el listado en Locomotive Basic que lo genera

10 ' Calcula la letra de DNI
20 MODE 1
30 t$="Letra de dni"
40 msg1$="Pulsa q para salir"
50 msg2$="Pulsa c para borrar"
60 msg3$="Pulsa n para nuevo"
70 dl$="TRWAGMYFPDXBNJZSQVHLCKE"
80 tlen%=LEN(t$)
90 xp=(20-tlen%)/2
100 LOCATE xp,1
110 PEN 1
120 PRINT t$
130 '
140 PEN 2
150 LOCATE 1,3
160 st$="DNI: " + STRING$(8,CHR$(127))
170 ldni%=LEN(st$)
180 PRINT st$;
190 PEN 3
200 LOCATE ldni% + 2, 3
210 PRINT CHR$(127);
220 PEN 1
230 LOCATE 1,5:PRINT msg1$;
240 LOCATE 1,6:PRINT msg2$;
250 ' Bucle principal
260 P%=0
270 dni$=""
280 PEN 1
290 WHILE p%<8
300 k$=INKEY$
310 IF k$="" GOTO 300
320 IF k$="q" THEN 640
330 IF k$="c" THEN 140
340 IF ASC(k$) < ASC("0") OR ASC(k$)>ASC("9") THEN PRINT CHR$(7): GOTO 300
350 p%=p% + 1
360 dni$ = dni$ + k$
370 LOCATE 5 + p%,3
380 PRINT k$;
390 WEND
400 inx%=3
410 div%=(ASC(MID$(dni$,1,1))-ASC("0"))*10
420 div%=div% + (ASC(MID$(dni$,2,1))-ASC("0"))
430 ' Este bucle funciona porque vamos
440 ' diviendo modulo 23
450 WHILE inx% < 9
460 q% = div% \ 23
470 IF q% <> 0 THEN div% = div% MOD 23
480 div%=div%*10
490 div% = div% + (ASC(MID$(dni$,inx%,1))-ASC("0"))
500 inx%=inx%+1
510 WEND
520 r% = (div% MOD 23) + 1
530 LOCATE ldni%+2,3
540 PEN 3
550 PRINT MID$(dl$,r%,1);
560 LOCATE 1,6:PRINT SPACE$(LEN(msg2$))
570 LOCATE 1,6:PRINT msg3$;
580 K$=INKEY$
590 IF K$="" THEN 580
600 IF K$="q" THEN 640
610 IF K$="n" THEN LOCATE 1,6:PRINT SPACE$(LEN(msg3$)):GOTO 130
620 GOTO 580
630 ' Clear screen
640 PEN 1
650 MODE 1
660 END

Supongo que el siguiente reto será hacerlo en ensamblador de Z80A y Amstrad ;). Veremos si tengo un hueco.

miércoles, noviembre 19, 2014

Programación en C orientada a objetos

Ayer dando un repaso a algunos Reddit que sigo encontré un hilo muy interesante de como usar técnicas de programación orientanda a objectos en el leguaje C. El artículo sobre el que empieza el debate, C Object Oriented Programming es una gran introducción a las mismas.Nos podemos encontrar estos patrones en el código fuente de proyectos como el kernel de Linux, SQLite, o las librerias glib, en su sistema de objetos GObject. Esta última librería es la que más he usado yo para manejar esos conceptos OOP en C. Los comentarios en el hilo de reddit dan más referencias a este tipo de técnicas, por ejemplo Object-oriented design patterns in the kernel, part 1 y Object-oriented design patterns in the kernel, part 2 y un enlace al libro Object-Oriented Programming With ANSI-C. También se pueden ver en acción en el compilador original de C++ cfront y ver su uso en C++ Internals.

Todos los enlaces los he ido recopilando del hilo de reddit.

martes, mayo 07, 2013

Autotools, tutorial de uso (II): Un ejemplo sencillo

En esta segunda entrada sobre las autotools voy a describir un sencillo ejemplo que compila un programa en C. El código fuente del mismo se puede bajar del git autotools-tutorial. En el directorio tut1 se encuentran las plantillas Makefile.am y configure.ac, un subdirectorio m4 con un fichero de macros ,acexample.m4 - vacío de momento - , el código fuente que se va a compilar, tut1.c y por último un shell script que va a ejecutar los diferentes herramientas de las autotools para generar los ficheros intermedios.

Cuando las distintas herramientas procesan las plantillas, se va sustituyendo aquellas construcciones que reconoce copiando a los ficheros de salida el resto de la entrada. El resultado de procesar el fichero configure.ac, será un fichero configure, que no es más que un shell script, con lo cual se podrá incluir código Bourne Shell1 que las diferentes herramientas copiaran sin tocar - o haciendo las sustituciones correspondientes - , mientras que la plantilla Makefile.am se puede incluir código de Makefile que será copiado sin modificar a la salida.

A continuación se describe un pequeño ejemplo de las plantillas para generar el script configure y a partir de la ejecución del mismo se generan los Makefile, que permitirá compilar un programa simple:

configure.ac

 1 dnl Esto es un comentario
 2 dnl Esta macro fuerza a una versión mínima de autoconf
 3 AC_PREREQ([2.59])
 4 AC_INIT([tut1],[1.0],[cterron@users.sourceforge.net],[tut1.tar.gz],
 5         [https://sourceforge.net/p/autotutorial/])
 6 dnl Necesitamos el compilador de C
 7 AC_CONFIG_HEADERS([config.h])
 8 AC_PROG_CC
 9 dnl Init automake 
10 AM_INIT_AUTOMAKE
11 dnl Debe de ir al final de la plantilla. Genera el fichero config.status
12 AC_CONFIG_FILES([Makefile])
13 AC_OUTPUT

El fichero configure.ac es procesado por varias utilidades - autoconf, autoheader y automake -. Es procesado por el macroprocesador m4. En este pequeño ejemplo se pueden ver algunas de las características de este lenguaje de macros:

  • En primer lugar tenemos la macro dnl. Esto lo que hace es descartar toda la entrada del fichero hasta el fin de línea, lo cual es útil para poner comentarios. También nos puede server el símbolo #.
  • AC_PREREQ es una macro es opcional, pero nos sirve para indicar cual es la versión mínima de autoconf que se va a usar. En este caso se necesita como mínimo la versión 2.59. Aquí se puede ver una característica del lenguaje m4, que es el paso de parámetros: Estos tienen que ir encerrados entre corchetes, [2.59]
  • AC_INIT es la macro que se encarga de emitir el código en el configure que se encargará de realizar la inicialización del script. Tiene dos parámetros obligatorios, que son el nombre del paquete y la versión (tut1 y 1.0 en el ejemplo). Los otros tres parámetros son opcionales. Esta macro debe de estar presente en el fichero antes de cualquier macro que genere una salida. La definición de esta macro generará una serie de variables que se podrán usar en el fichero configure, así como unas macros de preprocesador, que también podremos usar.
  • AC_CONFIG_HEADERS Esta macro va a generar un fichero de definiciones en C, cuyo nombre final se pasa como parámetro, en este caso config.h. Pero para generar dicho fichero, por defecto el script configure que se generará espera encontrar un fichero config.h.in que se genera a partir del procesamiento de este fichero por autoheader..
  • AM_INIT_AUTOMAKE Esta macro es la encargada de inicializar todo el conjunto de código necesario para poder procesar los ficheros Makefile.in y generar los Makefiles. Puede llevar una lista de parámetros opciones que se utiliza para procesar cada fichero Makefile.am.
  • AC_CONFIG_FILES Esta macro especifica que ficheros de salida debe de crear el automake. En este ejemplo sólo queremos crear el fichero Makefile en el directorio actual.
  • AC_OUTPUT Procesa la lista de ficheros que se especifica. En este caso, puesto que los Makefile se están especificando con la macro AC_CONFIG_FILES no es necesario poner parámetros. Si se le da una lista de parámetros, son nombres de fichero separados por espacios, lo que hará el sistema es procesar el nombre junto al sufijo .in cuando se ejecute. Por ejemplo AC_OUTPUT([start.py]), hará que cuando se ejecute el configure el sistema busque start.py.in y lo procese de manera adecuada. Puede llevar dos parámetros opcionales que indican comandos antes y después de ejecutar la salida del fichero.

Makefile.am

1 bin_PROGRAMS= tut1
2 tut1_SOURCES = tut1.c

El formato que sigue la plantilla de Makefile.am es muy similar: Siempre un nombre y un sufijo separado por un subrayado. Así:

  • En primer lugar está bin_PROGRAMS que es una lista separada por espacios de los programas que se van a compilar y que se instalarán cuando tras compilar se ejecute la orden make install. La instalación por defecto será en $prefix/bin, lo que en la terminología de GNU se llama bindir2
  • La segunda línea nos define cuales son los ficheros fuentes necesarias para compilar el binario tut1, en este caso sólo tenemos tut1.c

Generación de los scripts

Aunque el script autogen.sh que hay en el directorio de ejemplo hace todos los pasos, a continuación se detallan para que se puedan ver cuales son pasos:

1 aclocal -I m4
2 rm -rf autom4te.cache/
3 autoheader
4 automake --foreign
5 autoconf

En primer lugar se genera el fichero aclocal.m4 ejecuanto aclocal, en el mismo directorio donde está el resto de ficheros de plantilla. Se va a usar las macros que estén en el directorio m4, cosa que pasamos como parámetro con la opción -I. Decir que, aunque no estaba en el dibujo que se publicó en la entrada anterior, al ejecutar aclocal se incluirá diversas definiciones de macros en el fichero aclocal.m4, entre otras, aquellas que definen las macros que usará automake

La segunda orden borra un directorio caché que crea las diversas ejecuciones de las autotools para ir más rápido en la ejecución

autoheader va a recorrer el fichero configure.ac, para encontrar la primera llamada a la macro AC_CONFIG_HEADERS de donde tomará el nombre del fichero de salida que va a generar, añadiéndole el sufijo .in. Así si dicha macro tiene como parámetro el nombre config.h, generará un fichero llamado config.h.in donde estará las posibles definiciones que se hayan hecho en el configure.ac.

automake va a generar los ficheros Makefile.in a partir de los Makefile.am que se tengan en los directorios. Generará las reglas por defecto, definirá variables, definirá todos los objetivos estándar de construcción (install, install-exec,clean, ....). Algunas definiciones que serán procesadas cuando se ejecute el configure quedará entre símbolos @, para ser procesado posteriormente por configure. El parámetro --foreign le indica al automake que es un proyecto que no sigue con los estándares GNU a la hora de tener ciertos ficheros de documentación. Un trozo de un fichero Makefile.in generado a partir del Makefile.am puede verse a continuación.

....
 15 @SET_MAKE@
 16 
 17 VPATH = @srcdir@
 18 pkgdatadir = $(datadir)/@PACKAGE@
 19 pkglibdir = $(libdir)/@PACKAGE@
 20 pkgincludedir = $(includedir)/@PACKAGE@
....

Pueden verse variables que definirá el proceso (por ejemplo $(libdir)) o zonas de texto cuyo valor será sustituido cuando se ejecute el configure y procese este fichero (por ejemplo @SET_MAKE@ o @PACKAGE)

Por último, autoconf procesará el fichero configure.ac para generar el fichero configure. Una vez generado lo podemos ejecutar con ./configure, y podremos ver como se procesa los ficheros, y se generan los ficheros Makefile.

Documentación

Notas

  1. Curiosamente, hay que usar construcciones lo más compatibles posible, porque no todos los Bourne Shell se comportan igual.
  2. Estoy adelantando información, ya que el configure define una serie de variables - a fin de cuenta es un shell script - que se pueden usar a lo largo de todo nuestro sistema para especificar directorios u otros valores de configuración.

Technorati Tags: ,

viernes, abril 19, 2013

Autotools, tutorial de uso (I): Introducción

Para bien o para mal, me toca de vez en cuando mantener un software que usa el conjunto de herramientas de GNU para poder compilar. Este conjunto de herramientas - autoconf, automake y libtool - pretende facilitar la configuración de software que se distribuye en código fuente, facilitando al usuario la instalación del mismo. Algunas de las posibilidades que permite un programa que utilice de manera correcta estas utilidades para detectar el entorno de compilación son:

  • Detectar y configurar las rutas de librerias y programas que se necesita para compilar.
  • Especificar diferentes opciones de configuración del código fuente que se quiere compilar.
  • Especificar la ruta a cualquier elemento necesario para la compilación de un software.
  • Generar toda una serie de ficheros Makefiles, con una serie de regla estándar.
  • Facilitar la compilación cruzada de herramientas.

Estos programas parten de una serie de plantillas, con unos nombres estándar, que una vez procesado por los mismos genera un fichero shell script llamado configure. Cuando se ejecuta este shell script, analizará el sistema para saber cual es el entorno de compilación que está presente, generará los ficheros que se le indique , en especial los Makefile, de tal manera que se tenga la tan común manera de instalar fuentes en el mundo Unix:

./configure --prefix=/usr/local
make
make check
# Como superusuario
make install

Sin embargo, no todo es tan bonito como describe el manual del autoconf (Those who do not understand Autoconf are condemned to reinvent it, poorly., en especial porque han surgido multitud de alternativas para sustituir a las autotools1). Alguno de los problemas que he encontrado a lo largo del tiempo que he usado estos programas:

  • Están diseñados para facilitar la vida al usuario del mismo, pero no al desarrollador que utiliza el paquete. Esto es un arma de doble filo, porque muchas veces esa falta de facilidad para generar las plantillas, hace que se den demasiadas cosas por supuestas, dificultando la vida a los usaurios.
  • El lenguaje que utiliza autoconf , M4, no es precisamente cómodo. Si a esto le sumanos que se puede mezclar trozos de shell scripts en las plantillas puede que se acabe generando ficheros de configuración no tan portables como pueden parecer a primera vista.
  • Hay gran cantidad de scripts que parece que sólo se han probado en sistemas Linux. Cuando se intenta usar en otros Unix, no funcionan correctamente.

Arquitectura general

Los autotools parten de una serie de ficheros de plantilla que serán procesados por cada programa para generar los ficheros de salida. Los programas que se van a manejar son automake, autoconf, autoheader y aclocal. Cada uno de estos tienen una determinada plantilla de entrada, y generará una salida:

  • aclocal Este programa se utiliza para generar el fichero aclocal.m4, consolidando en un único fichero macros que vienen de varios paquetes más las que el usuario quiera añadir, de tal manera que sean fácilmente accesibles por autoconf. Sin embargo, en las últimas versiones de autoconf existe la posibilidad de espcificar un directorio. El funcionamiento de este programa es buscar en todos los ficheros .m4 que encuentre, para posteriormetne analizar la plantilla configure.ac y copiar la definición de las macros que se usen en esta última plantilla al fichero aclocal.m4.
  • automake Este programa lee las plantillas define en Makefile.am , configure.ac para generar el fichero Makefile.in. Este último f¡chero será posteriormente procesado en la ejecución del configure para generar los ficheors Makefile definitivos que permitirán compilar el proyecto.
  • autoheader Este programa va usar como entrada la plantilla configure.ac, la cual va a procesar buscando posibles definiciciones para genera el fichero config.h.in. Este programa nos ayuda a tener en un sólo fichero las posibles definiciones del preprocesador que se realizan en la plantilla. Posteriormente, cuando se ejecute el script configure, definirá las macros de acuerdo al entorno que vaya detectando durante su ejecución
  • autoconf Este programa, por último, usar como entrada la plantilla configure.ac, aclocal.m5 o el directorio de macros especificado para procesar todo el conjunto y generar el fichero shell script configure, el cual al ejecutarse intentará detectar el entorno donde se está ejecutando, teniendo en cuenta parámetros de línea de comandos y las plantillas, procesar los ficheros que acaben en .in - como por ejemplo Makefile.in o config.h.in -, realizando las correspondientes sustituciones de variables y valores, para generar los ficheros definitivos que permitan la compilación del programa.

Normalmente, todos estos programas se llaman desde un fichero, que por convención llamamos autogen.sh cuyo contenido es el siguiente suponiendo que en el directorio donde llamamos al programa tengamos las correspondientes plantillas en los ficheros configure.ac y Makefile.am, además de las posibles macros m4 que necesitemos en el directorio m4.

#!/bin/sh
aclocal -I m4 && \
rm -rf autom4te.cache/ && \
autoheader && \
automake --foreign &&
autoconf

Todo el esquema anterior puede verse en el siguiente gráfico:

En el gráfico anterior, las cajas transparentes son los ficheros que utiliza de entrada cada una de las herramientas, con sus nombres por defecto, de tal manera que no sea necesario especificarlos en la línea de comandos. Las cajas sombreadas corresponde a cada uno de los comandos que se llaman.

Ejemplo básico

Un primer ejemplo puede encontrarse en el tutorial 1 que he subido al proyecto Auto Tools tutorial de Sourceforge. En el primer tutorial tenemos dos ficheros muy básicos, más el código fuente del programa y el script que se encarga de generar el fichero configure. Se puede bajar con git, ver como no hay ningun fichero make ni configure, y ejecutar la secuencia:

./autogen.sh
./configure
make
./tut1

Tras la ejecución del último programa nos debe de devolver la palabra Ok.En la próxima entrada explicaré en que consiste cada una de estas plantillas.

Documentación

Notas

  1. qmake,cmake,ant, o incluso porqué no decirlo, un simple Makefile bien hecho, como han demostrado proyectos como Plan 9 o Qmail.

Technorati Tags: ,

jueves, febrero 28, 2013

Apuntes de un curso de tecnología de base de datos

Hace unos días, mientras buscaba información sobre algoritmos de tablas hash con respaldo en disco, di con la web de un curso sobre tecnologías y algoritmos usados en bases de datos de la Universidad de Toronto, concretamente Database System Technology CSC443

El programa incluye algoritmos comúnmente usado en base de datos como árboles B+ o índices hash, o una pequeña introducción a la búsqueda de textos con ayudas de índices inversos. Decir que parte de los algoritmos descritos en este curso pueden verse en Berkeley DB.

Otra referencias sobre bases de datos es el artículo que describe las ideas detrás de la implementación de Berkekey DB, LIBTP: Portable, Modular Transactions for UNIX


Technorati Tags: ,

jueves, febrero 02, 2012

Algunas notas rápidas de valgrind (I)

Una de las mejoras herramientas que he usado en estos últimos tiempos para la programación en C es valgrind, un conjunto de herramientas
que nos permite instrumentalizar nuestros binarios para analizar el comportamientos de éstos. Fundamentalmente lo uso para analizar consumo de memoria y posibles memory leaks
que tengan los programas. Aparte da información de accesos erróneos de memoria.

La herramienta de instrumentación más sencilla que usa es el detector de memory leaks. Para ello suelo ejecutar el programa de la siguiente manera:

valgrind --log-file=leak --leak-check=full --dsymutil=yes ./binario argumento1 argumento2 ...
Esta es la manera más sencilla de generar un informe, en este caso usando la herramienta de instrumentación por defecto, que en este caso es memcheck y
es la encargada de detectar los problemas de memoria (leaks, escritura fuera de bloques reservados, saltos tomados a partir de valores de variables que
no han sido inicialidados), y se le dice a dicha utilidad, con --leak-check=full que memcheck utilice información detallada de la gestión de memoria.

El resto de las opciones indica el fichero donde queremos que se almacene la información, --log-file=fichero, y la opción --dsymutil=yes es una opción específica
de MacOS X para que utilice la utilidad dsymutil para tener
la información de depuración almacenada en el binario.

Para que valgrind nos pueda tener los números de líneas del programa, hay que compilarlo con información de depuración (por ejemplo la opción del gcc para que esto ocurra es -g)


#include <stdio.h>
#include <stdlib.h>
int func1(void)
{
        char *p = NULL;
        p = malloc (1000);
}
int func2 (void)
{
        int asco;
        if (asco)
        {
                printf ("Uno\n");
        }
        else
        {
                printf ("Dos\n");
        }
}
int main (int argc,char **argv)
{
        func1 ();
        func2 ();
        exit (0);
}

Por ejemplo el código anterior tiene un memory leak y una comparación tomada en función de una variable que no ha sido inicialidada. Si compilamos este
programa con la opción -g y ejecutamos el valgrind:

gcc -g -o binario test1.c

valgrind --log-file=leak --leak-check=full --dsymutil=yes ./binario

En el fichero leak nos generará la siguiente salida:

==16246== Memcheck, a memory error detector
==16246== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==16246== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==16246== Command: ./a.out
==16246== Parent PID: 16092
==16246==
==16246== Conditional jump or move depends on uninitialised value(s)
==16246==    at 0x100000E5E: func2 (test1.c:11)
==16246==    by 0x100000EB0: main (test1.c:23)
==16246==
==16246==
==16246== HEAP SUMMARY:
==16246==     in use at exit: 7,087 bytes in 34 blocks
==16246==   total heap usage: 34 allocs, 0 frees, 7,087 bytes allocated
==16246==
==16246== 1,000 bytes in 1 blocks are definitely lost in loss record 9 of 10
==16246==    at 0xB823: malloc (vg_replace_malloc.c:266)
==16246==    by 0x100000E41: func1 (test1.c:6)
==16246==    by 0x100000EAB: main (test1.c:22)
==16246==
==16246== LEAK SUMMARY:
==16246==    definitely lost: 1,000 bytes in 1 blocks
==16246==    indirectly lost: 0 bytes in 0 blocks
==16246==      possibly lost: 0 bytes in 0 blocks
==16246==    still reachable: 6,087 bytes in 33 blocks
==16246==         suppressed: 0 bytes in 0 blocks
==16246== Reachable blocks (those to which a pointer was found) are not shown.
==16246== To see them, rerun with: --leak-check=full --show-reachable=yes
==16246==
==16246== For counts of detected and suppressed errors, rerun with: -v
==16246== Use --track-origins=yes to see where uninitialised values come from
==16246== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 1 from 1)

Podemos ver los dos errores el memory leak de no liberal el malloc y el salto tomado en base a una variable que no esta inicialida.

Referencias


Technorati Tags:

viernes, diciembre 30, 2011

¿Puede ser la dirección 0 de memoria un puntero válido en C y Unix?

Teóricamente, según el standar C, la definición del llamado puntero nulo es una expresión entera de valor 0, representada por la macro NULL, que debe estar definida en el fichero stddef.h como mínimo. Cuando programamos en C, solemos usar dicho valor para indicar un puntero no válido, comparamos con él y a veces para evitar cantadas usadamos la macro assert para asegurarnos que no vamos a usar el valor del puntero nulo para acceder a algún objeto en memoria. Desreferenciar el puntero nulo teóricamente debe de devolverver un valor no definido (cuando no directamente por los mecanismos de protección de memoria el sistema operativo acabe abortando la ejecución del programa).

Esto no sería una anécdota hasta que el otro día ejecutando un programa bajo valgrind para buscar posibles problemas en la gestión de memoria del mismo me encontré una llamada a mmap , bajo MacOS X que me devolvía como puntero 0 y errno = 0,es decir, que era un puntero válido. Notar que mmap en caso de error devuelve MAP_FAILED, que suele estar definido como -1 (0xFFFFFFFF en una máquina en el que el tamaño del puntero sea de 32 bits), lo cual hacía que mi programa cascara miserablemente en una macro assert que tenía puesta para evitar los punteros nulos. Desconozco que tipo de interacción tendrá valgrind con la llamada mmap en MacOS X, pero se produce.

No soy el único que se ha encontrado con esto, como puede verse en el código que hay en NULL can be a valid address. Si se ejecuta el programa en el enlace anterior baja la shell normal, muere con un segfaul. Pero bajo valgrind no, el NULL pointer se convierte en una dirección válida.


Technorati Tags: ,