Está en la página 1de 10

Conceptos bsicos de make y los Makefile

Los que no tengan paciencia para leer, pueden ir al resumen final, aunque posiblemente no se enteren del todo. El comando de linux make nos ayuda a compilar nuestros programas. Presenta muchas ventajas para programas grandes, en los que hay muchos ficheros fuente (muchos .c y muchos .h) repartidos por varios directorios. Principalmente aporta dos ventajas:

Es capaz de saber qu cosas hay que recompilar. Si cuando estamos depurando nuestro
programa tocamos un fichero fuente, al compilar con make slo se recompilaran aquellos ficheros que dependan del que hemos tocado. Si compilamos a mano con cc, (o el compilador que sea), o tenemos en la cabeza esas dependencias para compilar slo lo que hace falta, o lo compilamos todo. Si el proyecto es grande, se nos olvidar alguna dependencia o nos pasaremos horas compilando. Nos guarda los comandos de compilacin con todos sus parmetros para encontrar libreras, ficheros de cabecera (.h), etc, etc. No tendremos que escribir largas lneas de compilacin con montones de opciones que debemos saber de memoria o, al menos, slo tendremos que hacerlo una vez. Dicho esto, vamos poco a poco con lo ms bsico. Hazte un programita de HolaMundo.c para ir probando. Puede ser como este #include <stdio.h> main() { printf ("Hola mundo\n"); } Lo compilaremos de la forma habitual. $ cc HolaMundo.c -o HolaMundo Lo probamos y funciona!. Nada espectacular hasta ahora. Vuelve a compilarlo con el mismo comando. Se vuelve a compilar y sigue funcionando. Veamos ahora con make. Si haces $ make HolaMundo Pues make te dir que no hay nada que hacer. Primera diferencia con compilar a mano. Como el programa ya est hecho, make no hace nada. Esto, en un programa de muchas lneas de cdigo que tarda varios minutos en compilar, es una gran ventaja. Borra el ejecutable y vuelve a hacer make $ rm HolaMundo $ make HolaMundo Esta vez si se ha compilado. Misteriosamente se ha escrito slo el comando de compilacin y el programa

se ha compilado. Toca ahora el fichero fuente (edtalo, ponle un comentario en algn sitio y slvalo, o smplemente, cmbiale la fecha con touch HolaMundo.c ). Al hacer make, se vuelve a compilar. Esto quiere decir que make es ms listo de lo que pensabamos. No slo mira si el ejecutable ya est hecho, sino que compara adems las fechas del fuente (el HolaMundo.c) y el ejecutable, compilando o no el programa en funcin de estas fechas. make entiende adems de extensiones y compiladores. Fjate, al hacer make HolaMundo, el fichero HolaMundo.c se compila con el compilador cc. Si ahora le cambias la extensin y lo llamas HolaMundo.cpp, al hacer make se compilar con el compilador de c++, el g++. Tambin entiende las extensiones de fortran y de ensamblador. Las versiones ms modernas (al menos las de Solaris, el unix de Sun) incluso son capaces de compilar java. Si pones make HolaMundo.o, tambin sabr generar adecuadamente el fichero objeto (el .o). Todo esto se debe a que make tiene unas reglas implcitas que le indican cmo obtener un ejecutable. Estas reglas, al menos en Solaris, estn escritas en un fichero make.rules que est perdido por los directorios del sistema operativo. En linux debe andar por/usr/share/lib/make/make.rules, aunque supongo que depende de la distribucin. Variables de entorno Con esto ya podemos compilar cualquier programa simple. Vamos ahora a complicar un poco ms el make. Creamos dos directorios. Uno de ellos lo llamamos PRINCIPAL y el otro FUNCION1. En PRINCIPAL ponemos nuestro HolaMundo.c. EnFUNCION1 vamos a crear un fichero .h que llamaremos texto.h y su contenido es este: #define TEXTO "Hola Mundo"

Nuestro HolaMundo.c lo cambiaremos un poco para que contenga lo siguiente: #include <stdio.h> #include <texto.h> main() { printf ("%s\n", TEXTO); } Ahora nos metemos en el directorio PRINCIPAL y hacemos, como antes make HolaMundo. Como es de esperar, obtenemos un error. No sabe encontrar el fichero texto.h, puesto que no est en los directorios por defecto de bsqueda de ficheros .h. El comando make, adems de saber hacer ciertos ejecutables a partir de sus fuentes, tambin es capaz de mirar ciertas variables de entorno para buscar ficheros de cabecera (.h), libreras, etc. Estas variables de entorno estn definidas en el make.rules que mencionamos anteriormente. Una variable bastante interesante es CFLAGS (CPPFLAGS para el compilador de C++). Esta variable puede contener las opciones que queramos que se pasen al compilador. Por ejemplo, si hacermos

$ CFLAGS=-g; export CFLAGS $ make HolaMundo veremos cmo al compilar el fichero HolaMundo se le pasa al compilador la opcin -g (para poder meter luego el debugger). Una de las opciones que se puede pasar al compilador es la opcin -I, que nos permite poner paths de busqueda para ficheros de cabecera (.h). En nuestro ejemplo, y usando un path relativo, deberamos poner algo as como -I../FUNCION1. Vamos a ello: $ CFLAGS=-I../FUNCION1; export CFLAGS $ make HolaMundo Esta vez s debera ir todo correctamente. El fichero Makefile Vamos a complicarle la vida un poco ms al comando make. Pongamos ahora en el directorio FUNCION1 dos ficheros. Unfuncion1.h y un funcion1.c. El contenido de estos ficheros sera: funcion1.h void escribeHolaMundo(); funcion1.c #include <stdio.h> void escribeHolaMundo() { printf ("Hola Mundo\n"); } El fichero texto.h podemos borrarlo porque ya no nos servir ms. En cuanto al programa en el directorio PRINCIPAL, lo modificamos para que ponga esto: #include <funcion1.h> main() { escribeHolaMundo (); } Desde el directorio PRINCIPAL ponemos nuestra variable CFLAGS como antes y hacemos el make. $ CFLAGS=-I../FUNCION1; export CLFAGS $ make HolaMundo Obtenemos otra vez un error. make compila el fichero HolaMundo.c, no compila el fichero funcion1.c y obtenemos un error. Esto ya es demasiado para resolverlo con variables de entorno. En el momento que tenemos dos ficheros .c para construir un nico ejecutable, necesitamos decirle a make cules son los ficheros que debe compilar. Podemos hacer que make sea ms listo escribiendo un fichero, uno de cuyos nombres por defecto

es Makefile, y decirle en l qu cosas queremos que haga y cmo. En ese fichero pondremos cosas como objetivo: dependencia1 dependencia2 ... <tab>comando1 <tab>comando2 <tab>...

objetivo es lo que queremos construir. Puede ser el nombre de un ejecutable, el nombre de una
librera o cualquier palabra que nos inventemos. Para nuestro ejemplo, podemos poner que nuestro objetivo es el nombre de nuestro ejecutable, es decirHolaMundo. dependencia<i> es el nombre de otro objetivo que debe hacerse antes que el nuestro o bien ficheros de los que depende nuestro objetivo. En nuestro ejemplo, las dependencias seran nuestros ficheros fuente HolaMundo.c, ../FUNCION1/funcion1.hy ../FUNCION1/funcion1.c ya que para hacer el ejecutable necesitamos todos esos fuentes y si los tocamos, posiblemente debamos rehacer el ejecutable. <tab> es un tabulador. Es importante que ahi pongamos un tabulador, porque si no el fichero no se lee correctamente. comando<i> es lo que se tiene que ejecutar para construir nuestro objetivo. Se irn ejecutando estos comandos en secuencia. Puede ser cualquier comando de shell vlido. (cc, rm, cp, ls, o incluso un script que nos hayamos hecho). En nuestro ejemplo, sera cc HolaMundo.c -o HolaMundo. Nuestro makefile tendra entonces HolaMundo: HolaMundo.c ../FUNCION1/funcion1.c ../FUNCION1/funcion1.h cc -I../FUNCION1 HolaMundo.c ../FUNCION1/funcion1.c -o HolaMundo Ahora, despus de borrar el ejecutable, si hacemos make (a secas, sin parmetro), se volver a compilar nuestro programa. Si a make no le ponemos parmetro, buscar un fichero Makefile y dentro de l har el primer objetivo que encuentre. En nuestro caso, el nico que hay es HolaMundo. Bueno, una cosa que tengo que advertir es que esta NO es la forma correcta de hacer un Makefile, pero lo hacemos as para que quede ms claro y no introducir demasiados conceptos de golpe. Con lo que debes quedarte es con la sintaxis (objetivo, dependencias y comando) y el cmo funciona. El qu cosas hay que poner y cmo lo iremos explicando poco a poco. Fjate que en el comando de compilacin, el cc, hemos puesto una opcin -I../FUNCION1 para que sea capaz de encontrar los ficheros .h. Si ponemos un comando de compilacin, ya no valen las reglas implcitas que conoce make. Al poner un comando de compilacin, nuestro ejecutable se generar con nuestra comando (regla explcita) y no se coger nada de otro sitio (ni siquiera la variable de entorno CFLAGS). Mejorando el Makefile Este incorrecto Makefile funciona correctamente. Si tocamos cualquier fichero (con touch o editndolo y salvando), se recompilara el programa. Por qu es incorrecto entonces?. Es incorrecto porque si tocamos cualquier fichero, recompila absolutamente todo. Perdemos la gran ventaja de make para proyetos grandes, que consiste en que compila slo aquellas partes que son necesarias. Lo ideal, por ejemplo, es

que si tocamos funcion1.h, slo recompile HolaMundo.c, que incluye funcion1.h, pero que no recompilefuncion1.c, que no incluye para nada a funcion1.h. (otra cosa que que funcion1.c implementa una funcin cuyo prototipo est enfuncion1.h y si tocamos dicho prototipo, debemos tocar tambin el cdigo de funcion1.c) Para conseguir este comportamiento ideal no queda ms remedio que tener "guardados" los ficheros objeto (los .o) que se generan. En un proyecto grande, estos .o se suelen guardar en forma de libreras. Para nuestro ejemplo lo dejaremos con los .o sueltos. Nuestro ejecutable HolaMundo dependera nicamente de los dos .o correspondientes a los .c que tenemos. En el Makefile, deberamos poner HolaMundo: HolaMundo.o ../FUNCION1/funcion1.o cc HolaMundo.o ../FUNCION1/funcion1.o -o HolaMundo Con esto ya no necesitamos la opcin -I../FUNCION1 ya que los ficheros fuente ya estn compilados y no necesitan los .h para nada. Aunque make sabe hacer los .o de una forma por defecto (busca el .c con el mismo nombre y lo compila con las opciones adecuadas), sigue sin saber encontrar los .h que estn en otros directorios. Podemos poner ms reglas en nuestro Makefile para que sepa hacer correctamente los .o. Las pondremos detrs, ya que make hace el primer objetivo que encuentre en el fichero y queremos que este sea nuestro ejecutable. El Makefile quedara: HolaMundo: HolaMundo.o ../FUNCION1/funcion1.o cc HolaMundo.o ../FUNCION1/funcion1.o -o HolaMundo ../FUNCION1/funcion1.o: ../FUNCION1/funcion1.c cc -c ../FUNCION1/funcion1.c -o ../FUNCION1/funcion1.o HolaMundo.o: HolaMundo.c ../FUNCION1/funcion1.h cc -c -I../FUNCION1 HolaMundo.c -o HolaMundo.o Vaya pequeo gran lio !. Cuando ejecutemos make, se tratar de hacer nuestro primer objetivo, HolaMundo. Como este depende de dos .o, se harn primero estos dos. Cada uno de ellos depende de los fuentes (.c y .h) correspondientes. Si los fuentes son ms modernos que el .o o si no hay .o, se compilar. Si el .o es ms moderno que los fuentes, no se har nada. Una vez construidos los .o, se construir HolaMundo en funcin de que las fechas de estos .o sean ms modernas o no que el ejecutable HolaMundo.Veamoslo por pasos con un ejemplo concreto. Supongamos que ya tenamos todo compilado una vez, es decir, tenemos todos los .o y el ejecutable creados. Editamos y salvamosfuncion1.h y ejecutamos make. El comando ms o menos dara los siguientes pasos:

HolaMundo depende de HolaMundo.o y ../FUNCION1/funcion1.o. Miramos estos dos antes de


hacer nada. HolaMundo.o depende de HolaMundo.c y ../FUNCION1/funcion1.h. Como hemos tocado el .h, este es ms moderno queHolaMundo.o, as que se hace la compilacin.

../FUNCION1/funcion1.o depende de ../FUNCION1/funcion.c. Como no hemos tocado nada, el .o


ser ms moderno que el .c y no se compila nada. Como HolaMundo.o se ha generado nuevo, es ms moderno que el ejecutable HolaMundo, as que se compila. Con esto vemos que slo se ha compilado uno de los .c, mientras que el otro no ha hecho falta. Seguimos mejorando cosas. Si recordamos de antes, make tiene unas reglas implcitas que le permiten construir el solito los .o. Slo necesita saber dnde encontrar los ficheros de cabecera (.h). Adems, para un .o nicamente hace falta un .c, as que debera bastar con la regla implcita y la variable de entorno CFLAGS. El Makefile quedara: HolaMundo: HolaMundo.o ../FUNCION1/funcion1.o cc HolaMundo.o ../FUNCION1/funcion1.o -o HolaMundo ../FUNCION1/funcion1.o: ../FUNCION1/funcion1.c HolaMundo.o: HolaMundo.c ../FUNCION1/funcion1.h

y para que funcione correctamente, antes de hacer make, debemos poner la variable de entorno $ CFLAGS=-I../FUNCION1; export CFLAGS $ make En el Makefile slo hemos puesto las dependencias de los .o con los ficheros fuente. Esto es necesario al estar los fuentes por varios directorios y haber ficheros .h. La regla implcita para los .o nicamente har depender al .o de un .c con el mismo nombre y que est en el mismo directorio. Variables en el Makefile Y si me olvido de la variable de entorno? o como suele pasar en un proyecto grande y si tengo 32 directorios de ficheros .h?. Afortunadamente es posible definir estas variables (y cualquier otra que deseemos) dentro del mismo fichero Makefile. De hecho y aprovechando esta posibilidad, vamos a meter en una variable tambin los .o de HolaMundo, que si no tenemos que escribirlos dos veces (una en las dependencias de HolaMundo y otra en el comando de compilacion). El fichero Makefile, despus de estos cambios, quedara: OBJETOS=HolaMundo.o ../FUNCION1/funcion1.o CFLAGS=-I../FUNCION1 HolaMundo: $(OBJETOS) cc $(OBJETOS) -o HolaMundo ../FUNCION1/funcion1.o: ../FUNCION1/funcion1.c HolaMundo.o: HolaMundo.c ../FUNCION1/funcion1.h

Como puedes ver, para asignar la variable se pone el nombre de la variable, un igual y lo que queramos que tenga. Si la lnea es muy larga, se puede partir con la el caracter \ OBJETOS=HolaMundo.o \ ../FUNCION1/funcion1.o eso s, asegurate que inmediatamente detrs de la \ est el retorno de carro, que no haya ningn espacio ni nada parecido detrs de la \ u obtendrs un montn de errores extraos. Para usar el contenido de la variable, se pone $ y entre parntesis el nombre de la variable. La utilidad makedepend Nos queda el tema de las dependencias del .o Qu pasa si mis .c incluyen .h que a su vez incluyen otros y estos a su vez otros? Tengo que saber y mantener todas las dependencias a mano?. Afortunadamente no es necesario. Unix nos proporciona mecanismos para hacerlo ms fcil. En Solaris, basta que el Makefile tenga una "directiva" que le diga que se encargue de ello. No nos hace falta poner las dependencias ni ocuparnos de nada. El Makefile, para Solaris, quedara # Esta es la directiva que le hace mantener automticamente las dependencias .KEEP.STATE: OBJETOS=HolaMundo.o ../FUNCION1/funcion1.o CFLAGS=-I../FUNCION1 HolaMundo: $(OBJETOS) cc $(OBJETOS) -o HolaMundo Ya est. Al encontrar .KEEP.STATE, make almacenar en un fichero oculto todas las dependencias de los .o con los .h y sabr en cada momento si es necesario o no recompilar los .o. Por cierto, cualquier lnea que empiece con #, make la considera como un comentario. En linux esta directiva no da error, pero tampoco hace lo que se espera de ella. Simplemente, parece que la ignora (si alguien sabe algo del tema, agradezco la informacin chuidiang@gmail.com) . En linux, la opcin que he encontrado, es generar el Makefile sin los objetivos de los .o, es decir OBJETOS=HolaMundo.o ../FUNCION1/funcion1.o CFLAGS=-I../FUNCION1 HolaMundo: $(OBJETOS) cc $(OBJETOS) -o HolaMundo Una vez hecho esto, se ejecuta un comando makedepend, al que se le deben pasar todos los .c de nuestro proyecto y las opciones -I necesarias para que encuentre los .h $ makedepend -I../FUNCION1 HolaMundo.c ../FUNCION1/funcion1.c

Este comando mirar todas las depencias de los .c con los .h y aadir las lneas de objetivos .o al final del Makefile. Es decir, estas dos lneas ../FUNCION1/funcion1.o: ../FUNCION1/funcion1.c HolaMundo.o: HolaMundo.c ../FUNCION1/funcion1.h Como puedes ver, algo ms incomodo que el .KEEP.STATE de Solaris, pero mejor que escribirlo todo a mano, especialmente si unos .h incluyen a otros que a su vez incluyen a otros. Bueno, lo de makedepend sigue siendo un poco pesado. Cuando lo llamemos, debemos poner un montn de cosas detrs, sobre todo si nuestro proyecto es muy grande. Es bastante habitual, aprovechando variables que tenemos en Makefile, poner un objetivo nuevo que se llame depend y cuyo comando sea makedepend. De esta forma, luego ejecutaremos make depend y se har solo. El Makefile quedara OBJETOS=HolaMundo.o ../FUNCION1/funcion1.o FUENTES=HolaMundo.c ../FUNCION1/funcion1.c CFLAGS=-I../FUNCION1 HolaMundo: $(OBJETOS) cc $(OBJETOS) -o HolaMundo depend: makedepend $(CFLAGS) $(FUENTES) Hemos aadido una variable FUENTES con los fuentes .c de nuestro proyecto y hemos reaprovechado la variable CFLGAS para elmakedepend. Ahora, con $ make depend se generaran dentro de Makefile todas las dependencias de los .o. Basta con hacer esta llamada una sola vez y servir mientras no cambiemos los include de nuestros fuentes o creemos fuentes nuevos. En makefile.tar tienes en formato tar los dos directorios (PRINCIPAL y FUNCION1), con los ficheros que hay dentro de ellos. Puedes descargarlo en un directorio de trabajo, qutale la dichosa extensin .txt y descomprimirlo con tar -xf makefile.tar. Un ltimo apunte En un proyecto grande, los .o se suelen guardar en librerias. Lo habitual es que en cada directorio de fuentes se haga un Makefileespecfico para construir una librera con los fuentes de ese directorio. El Makefile principal llamar a los Makefile de cada directorio (porque ese ser el comando de compilacin que nosotros pongamos) y luego construya el principal. Es decir, si con funcion1.c hacemos una librera libfuncion1.a y tiene su propio Makefile en su propio directorio FUNCION1, elMakefile de PRINCIPAL podra ser algo as como CFLAGS=-I../FUNCION1 LDFLAGS=-L../FUNCION1

HolaMundo : HolaMundo.o ../FUNCION1/libfuncion1.a cc -o HolaMundo HolaMundo.o $(LDFLAGS) -lfuncion1 ../FUNCION1/libfuncion1.a: make -C ../FUNCION1 Veamos con detalle algunas cosas LDFLAGS es una variable igual que CFLAGS, pero que le dice a las reglas implcitas qu opciones usar para el linkado. En este caso la estamos usando para la opcin -L, que dice dnde hay libreras. No hay ninguna regla implicita en nuestro Makefile que lo vaya a usar, pero hemos llamado a la variable de la misma manera. Para hacer la librera, llamamos a make con la opcin -C. Esta opcin le dice a make que debe cambiarse al directorio ../FUNCION1antes de ejecutarse. All encontrar el Makefile para hacer la librera. En el Makefile de PRINCIPAL no ponemos dependencias para la librera. Estas dependencias estarn puestas en el Makefile de FUNCION1 y ser este el que decida si hay que reconstruir o no la librera. Resumen Hazte el directorio donde vaya tu programa principal un fichero de nombre Makefile. Este fichero debe contener: CFLAGS=-I<directorio1> -I<directorio2> ... OBJETOS=<objeto1> <objeto2> ... FUENTES=<fuente1> <fuente2> ... MiEjecutable: $(OBJETOS) <tab>cc $(OBJETOS) -o MiEjecutable depend: <tab>makedepend $(CFLGAS) $(FUENTES) <directorio1>, <directorio2>, ... son directorios donde tengas ficheros .h de tu proyecto. No hace falta que pongas los del sistema (stdio.h y dems), que esos se ponen solos. <objeto1>, <objeto2>, ... son los .o de tu proyecto, es decir, todos y cada uno de los .c, pero cambindoles la extensin por .o. Estos objetos deben incluir el path donde estn los .c. Por ejemplo, si tengo /users/chuidiang/proyecto/directorio1/fichero.c, uno de los objetos ser /users/chuidiang/proyecto/directorio1/fichero.o <fuente1>, <fuente2>, ... son los fuentes, igual que los <objeto1>, <objeto2>, pero terminados en .c en vez de .o y tambin con el path. errores. <tab> es un tabulador. Es importante que est ah, si no obtendremos

Cada vez que en tu proyecto generes .h o .c nuevos o cambies los includes de los fuentes ya existentes, actualiza el ficheroMakefile (FUENTES, OBJETOS y CFLAGS) y ejecuta una vez $ make depend

Cuando quieras compilar, simplemente escribe $ make Por supuesto, ambos comandos en el directorio donde est el fichero Makefile. En makefile.tar tienes un ejemplo con dos directorios. Descargalo, qutale la dichosa extensin .txt, descomprmelo con tar -xf makefile.tar, vete al directorio PRINCIPAL y haz make depend y luego make. Puedes jugar con ello para ver cmo funciona o modificarlo para tus propios proyectos.

También podría gustarte