|
| Home / Docencia / Ingeniería de Telecomunicación / Laboratorio de Arquitectura de Ordenadores / Laboratorio 2 | |
|
|
|
|
|
|
| Laboratorio de Arquitectura de Ordenadores | |||
|
|||
|
|
La herramienta make es utilizada habitualmente en el desarrollo de proyectos con gran número de archivos interdependientes. Utiliza reglas definidas en un archivo llamado 'makefile' para construir un archivo 'destino' especificado por el usuario a partir de un cierto número de archivos 'fuente', de los que el destino depende. El archivo destino se crea sólo si no existe o está desactualizado, es decir, si alguno de los archivos fuente es más reciente que el archivo destino. Dicho de otra forma, el archivo destino es construido de nuevo solo si alguno de los archivos fuente ha sido modificado desde la última vez que el archivo destino fue creado. Esto implica que al construir un proyecto completo compuesto de muchos archivos, solo aquellos desactualizados se volverán a construir, ahorrando de este modo una cantidad considerable de tiempo y recursos. Make es invocado de la siguiente forma: make [opciones] [destino(s)] Las opciones más habituales son:
A lo largo de este tutorial, veremos como definir destinos y las reglas para crearlos. |
|||
|
|
Como se puede imaginar, make es especialmente útil al programar aplicaciones relativamente grandes con cierta cantidad de archivos de distintos tipos. A partir de las reglas especificadas en el makefile, make crea un árbol de dependencias que es utilizado para construir el destino solicitado por el usuario. Veamos el siguiente ejemplo, en el que tenemos una aplicación compuesta de los siguientes archivos:
El archivo de código fuente data.c incluye al archivo de cabecera data.h, mientras que io.c incluye a io.h. El archivo main.c incluye a ambos. Sin make, habría que utilizar los siguientes comandos para compilar la aplicación y crear un ejecutable llamado project1:
Ejemplo 1 Así creamos los archivos objeto a partir de los archivos de código y de cabecera apropiados en cada caso, para después utilizar los tres archivos objeto generados para construir un archivo ejecutable. make inferirá el árbol de dependencias a partir de las reglas incluidas en el makefile mostrado en la figura. La utilidad de make radica en el hecho de que construirá un archivo si y solo si cualquiera de sus fuentes (o fuentes de las fuentes, y así sucesivamente) es más reciente que el archivo. ![]() Figura 1: (de http://www.eng.hawaii.edu/Tutor/Make/) Veamos algunos casos:
|
|||
|
|
Como ya hemos mencionado, un makefile se compone de reglas que indican a make como construir un destino a partir de sus fuentes. Una regla tiene el siguiente formato genérico:
Estructura 1 Veamos el significado de cada elemento:
Un ejemplo de regla en un makefile sería, por tanto:
Ejemplo 2 Ahora presentamos un ejemplo que utilizaremos a lo largo del tutorial: supongamos que tenemos una aplicación capaz de planificar un viaje calculando el tiempo que lleva conducir de una ciudad a otra a una velocidad dada. La aplicación se compone de los siguientes archivos:
El árbol de dependencias es el siguiente: ![]() Figura 2 Utilizando lo que hemos aprendido hasta ahora, el makefile tendría la siguiente forma:
Ejemplo 3 Añadir comentarios al makefile suele ser muy útil, para hacerlo solo es necesario comenzar una línea cualquiera con el carácter '#'', como se muestra en el ejemplo. Observe que, en realidad, podríamos compilar los ejecutables directamente desde los archivos .c y .h, pero el paso adicional a través de los archivos objeto (.o) nos permitirá mostrar más características de make. |
|||
|
|
Utilizando el makefile en el Ejemplo 3, podemos crear nuestra aplicación mediante las siguientes llamadas a make:
Ejemplo 4 Hasta ahora todos los destinos son archivos, de modo que no podemos construir toda la apliación con una sola llamada a make. Para hacer esto, puede incluir destinos simbólicos, es decir, destinos con un nombre cualquiera que no se corresponden con archivos. Por ejemplo, podemos introducir un destino llamado 'all' para construir (actualizar) la aplicación completa con una sola llamada a make.
Ejemplo 5 En el ejemplo hemos incluido un segundo destino simbólico llamado 'clean', que carece de lista de dependencias, con lo que los comandos se ejecutan siempre si el destino se invoca. Con este makefile, construir la aplicación completa se reduce a teclear 'make all'. Es más, si no se especifica ningún destino, make asume que solo se desea construir el primero, de modo que podríamos utilizar simplemente 'make'. Para borrar todos los archivos objeto, utilizaríamos 'make clean'. |
|||
|
|
Llegados a este punto, ya ha aprendido todo lo necesario para construir cualquier tipo de makefile para cualquier tipo de aplicación. A partir de ahora, veremos como reducir el tamaño de estos makefiles y hacerlos tan reutilizables como sea posible. Comenzaremos con las macros o variables. Las macros de make son similares a las variables utilizadas en la programación en shell script, puesto que almacenan cadenas que pueden ser referencias a través de los nombres de esas macros. Se inicializan mediante NOMBRE = valor, y se acceden mediante $(NOMBRE). Se suele utilizar la convención de escribir el nombre de las macros en mayúsculas. Podemos modificar nuestro ejemplo para incluir una macro con el nombre del archivo que contiene la tabla de distancias de la siguiente manera:
Ejemplo 6 Ahora es más sencillo cambiar el nombre de este archivo, puesto que solo es necesario cambiar una línea del makefile. Por supuesto, esto se puede extender a los nombres de otros archivos, como muestra el siguiente ejemplo:
Ejemplo 7 El siguiente ejemplo utiliza la posibilidad de sustituir texto en una macro: para sustituir una subcadena en la macro por otra, se puede hacer $(MACRO:antigua_subcadena=nueva_subcadena). Esta herramienta se aprovecha en el ejemplo para utilizar la misma macro para el nombre del ejecutable, el archivo de código fuente y el archivo objeto. También existe la posibilidad de definir una macro en la llamada a make, haciendo 'make NOMBRE_MACRO=valor'. Por ejemplo, en este ejemplo la macro DATA se define llamando a make con 'make DATA=dist_table.dat'
Ejemplo 8 No obstante, si no se define DATA al llamar a make, no se sustituirá nada.
Para acabar, presentamos una lista con algunas de las macros predefinidas por make que pueden ser muy útiles al escribir un makefile:
En este último ejemplo, se muestra un uso intensivo de estas y otras macros:
Ejemplo 9 |
||||
|
|
Algunas de las reglas en un makefile son bastante similares entre sí. Por ejemplo, las dos primeras reglas de nuestro ejemplo (las que construyen los ejecutables de los dos programas) son idénticas, con la única diferencia en los nombres de los archivos. Las reglas implícitas son aquellas que se utilizan para construir destinos sin incluir una regla específica para ellos en el makefile. El funcionamiento es el siguiente: si no se define una regla específica para el destino que se indica a make, se intenta utilizar una de estas reglas implícitas, primero una de las definidas por el usuario, y si ninguna es apropiada una de entre las predefinidas por make. Comencemos presentando dos reglas implícitas predefinidas por make:
La aplicación de la segunda regla predefinida nos permite eliminar las dos primera reglas de nuestro makefile, puesto que make usará la regla implícita predefinida exactamente de la misma forma para crear los porgramas. Este makefile produce un resultado idéntico al anterior, pero es considerablemente más corto:
Ejemplo 10 Observe que la primera regla implícita predefinida no se puede usar directamente en nuestro caso, puesto que no incluiría los archivos de cabecera (extensión .h) en la lista de dependencias. Una posible solución es modificar la macro CFLAGS para incluir estos archivos con la opción -i del compilador de C. La única desventaje es que las reglas para construir ambos archivos objeto incluirían los tres archivos de cabecera, pero esto no es muy grave en este caso:
Ejemplo 11 Si compara la longitud de este ejemplo con la de, por ejemplo, el Ejemplo 9, queda claro hasta que punto se puede llegar a simplificar un makefile sin perder funcionalidad. Además de las reglas implícitas predefinidas por make, puede definir sus propias reglas implícitas, o modificar aquellas predefinidas, utilizando la siguiente sintaxis para especificar la regla implícita en el makefile:
Ejemplo 12 De hecho, ésta es la regla predefinida utilizada por make para construir cualquier archivo objeto cuando no existe ninguna regla específica. Observe que la opción -o no es necesaria para la mayoría de compiladores, pero aun así se incluye por generalidad. Supongamos que desea que los nombre de los archivos involucrados se muestren en un mensaje cada vez que se aplica esta regla. En ese caso incluiría la siguiente regla implícita en el makefile:
Ejemplo 13 Evidentemente, hay una gran cantidad de situaciones en la que el uso de reglas implícitas originales o modificadas puede ahorrar mucho tiempo, especialmente en proyectos complejos con gran número de archivos. Un redefinición de esta regla más realista sería la siguiente, que utiliza una macro INCLUDEDIR que contiene el directorio en el que se almacenan los archivos de cabecera y una macro INCLUDE con los archivos de cabecera comunes a ser incluidos en cualquier caso. De esta forma, permite crear un archivo objecto a partir de los archivos de código fuente y de cabecera con el mismo nombre y los archivos de cabecera comunes:
Ejemplo 14 |
|||||
|
|
Make proporciona algunos destinos predefinidos que aumentan su funcionalidad:
Por otra parte, hay dos prefijos para comandos con las siguientes características:
Podemos modificar nuestro ejemplo para incluir alguna de estas opciones:
Ejemplo 15 |
|||
|
|
En el último apartado de este tutorial presentamos las condiciones (decisiones) que make permite definir dentro de una regla. Las principales son las siguientes:
Para ilustrar el uso de condiciones, modificamos el Ejemplo 9 de forma que podamos definir un directorio de destino para el primer programa, que puede ser especificado mediante una macro DESTDIR que contiene el directorio deseado al llamar a make (mediante 'make DESTDIR=valor) o tomar el valor de un directorio por defecto especificado en la macro DEFAULTDESTDIR si DESTDIR no está definida:
Ejemplo 16 |
|||
|
|
La descripción detallada de todas las posibilidades que ofrece make y de todos los trucos para escribir makefiles más cortos y efectivos escapa al propósito de este tutorial. No obstante, hay una cierta cantidad de tutoriales en Internet que puede utilizar para mejorar sus habilidades a la hora de utilizar make y los makefiles en proyectos realmente complejos. Aquí tiene un selección:
Si prefiere un libro, dispone de una guía bastante extensa y útil, con multitud de ejemplos:
Make se incluye en la mayoría de distribuciones de Linux, pero si desea utilizarlo sobre Windows, aquí tiene la versión de GNU para este sistema operativo. |
|||
|
|
El ejercicio consiste en preparar un makefile para un sencillo proyecto (una calculadora que puede funcionar por línea de comandos o interactivamente) compuesto de los siguientes archivos
Revise el código fuente proporcionado para obtener más detalle. Piense en que pasaría si ejecutara el programa basado en comandos sin parámetros. La aplicación se compone de dos ejecutables, uno para cada tipo de calculadora. Debe preparar un makefile con las siguientes características:
|
|||
|
|
Recuerde que:
Puede ser interesante utilizar una extensión (por ejemplo .x) para los ejecutables, a fin de reducir el tamaño del makefile definiendo reglas implícitas apropiadas. |
|||
|
|
| © Ralf Seepold - last updated by Mario Ibáñez 01/04/2006 |