Programación: la herramienta make y el makefile
Cuando exponga los primeros ejemplos de programación en C serán sencillos, compuestos generalmente por un solo fichero de código fuente y con GNU gcc podrás compilarlo muy fácilmente. En cambio, no todos el software es tan sencillo, y muchos programas se componen de gran cantidad de ficheros fuente, un ejemplo de ello es el propio kernel Linux. Es posible que tampoco necesites compilarlos todos, por ejemplo, que quieras evitar algunos módulos o ficheros específicos para una arquitectura…
Pues bien, en esos casos, sería recomendable trabajar con una herramienta que te facilite todo esto. Para ello tienes varias opciones. Una de ellas, quizás la más fácil y la que deja tu proyecto preparado para el futuro (p.e.: expandirte a otras plataformas, etc.), es usar CMake para que te ahorre gran parte del trabajo. Otra de las opciones es, que es la que prefiero, hacer directamente tú un makefile y la herramienta GNU make.
A lo largo de la serie de programación ya iremos usando todas estas herramientas con ejemplos prácticos concretos. Ahora solo me interesa que las conozcas y sepas cómo se usan a nivel básico.
ÍNDICE:
Procedimiento make
Make es una herramienta de gestión de dependencias usada en entornos UNIX. Cuando tu código se compone de varios ficheros de código fuente, es una utilidad muy práctica para compilar de una forma automatizada todo lo que necesitas y pasarle a gcc las opciones necesarias. Además, make sabrá qué cosas hay que recompilar en caso de que hagas cambios, algo bastante práctico cuando el proyecto es grande.
En Linux puedes usar GNU make, una herramienta bajo el proyecto make que sustituye a la original… El comando make tiene una serie de parámetros y objetivos que le puedes pasar. Seguramente ya lo conozcas si has configurado y compilado tu propio kernel Linux. Puedes obtener más información aquí o leyendo el manual:
man make
Pero para que make funcione y pueda compilar tu proyecto debes crear un fichero conocido como makefile desde donde obtenga todo lo que necesita para el proceso. Es decir, son las instrucciones escritas para decirle lo que debe hacer.
Por ejemplo, estos dos comandos obtendrían el mismo resultado:
gcc hola.c -o hola make hola
Pero aquí asumimos que es un programa sencillo compuesto por un solo fichero fuente. Pero fíjate que curioso, si yo cambiase algo en el código fuente (cambiase la fecha de modificación del fichero fuente) y volviese a ejecutar el primer comando, GNU gcc no compilaría, puesto que detectaría que ya está creado el binario, en cambio make sí que compilaría de nuevo. El motivo es que es mucho más inteligente y detecta cambios.
Además de eso, make entiende los formatos o extensiones. Por ejemplo, si es un hola.c sabe que le debe encargar la tarea a gcc, mientras que si es un hola.cpp se lo encomendará a g++, etc.
Crear un makefile
Imagina que tienes estos tres ficheros de código fuente. Uno llamado principal.c:
#include <hola.h> main() { muestraHolaMundo (); }
Otro hola.h de cabecera:
void muestraHolaMundo();
Y otro llamado hola.c:
#include <stdio.h> void muestraHolaMundo() { printf ("Hola Mundo\n"); return 0; }
Normalmente usarías el siguiente comando para compilar, puesto que 3 ficheros siguen siendo pocos, pero… ¿y si tienes 10, 20 o cientos de ellos? Ya no sería tan simple como ejecutar:
gcc -o hola principal.c hola.c -I
El formato general para un makefile es:
VARIABLES ... OBJETIVO: DEPENDENCIAS COMANDO ...
Los objetivos que introduces serán los que luego puedes usar agregando diferentes opciones al comando. Por ejemplo make install, make clean, make mrproper, make clobber, make config, etc. Es decir, con estos targets especificas la zona del makefile que quieres ejecutar, y se procesarán todas las órdenes que haya en ella.
Un makefile simple para usar make en la compilación para este ejemplo sería:
hola: principal.c hola.c gcc -o hola principal.c hola.c -I.
¡ATENCIÓN con los espacios en los makefiles, o podría dar error! A veces da muchos problemas y revisas todo, aparentemente no ves nada erróneo y simplemente se trata de los espacios y sangrías…
Eso seria un makefile equivalente a ejecutar gcc, muy simple. Pero podemos seguir mejorándolo. Por ejemplo, si hacemos cambios compilaría todo de nuevo. Y eso en proyectos grandes podría ser una pesadilla. Para que solo compile lo que ha cambiado se podría mejorar el makefile y dejarlo así:
CC=gcc CFLAGS=-I. hola: principal.o hola.o $(CC) -o hola principal.o hola.o
En este caso estaría bien para la mayoría de casos, pero si haces un cambio en hola.h no recompilaría los .c, y debería. Por eso, se podría seguir mejorando el makefile para que recompile también ambos .c en caso de hacer cambios en .h:
CC=gcc CFLAGS=-I. DEPS = hola.h %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hola: principal.o hola.o $(CC) -o hola principal.o hola.o
Ahora primero creará la dependencia de los .c. Ahora ya tendríamos un makefile bastante decente, y podríamos usar make para compilar sin problemas nuestro proyecto. No obstante se podría mejorar aún más. Por ejemplo, agregar un objetivo make clean para limpiar tras la compilación:
CC=gcc CFLAGS=-I. DEPS = hola.h %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hola: principal.o hola.o $(CC) -o hola principal.o hola.o clean: rm -f hola principal.o hola.o
Ahora, a parte de compilar, ejecutar el binario, y también podríamos limpiar con:
make ./hola make clean
¿Se puede seguir mejorando más el makefile? La respuesta es sí. Podemos hacerlo algo más genérico sacando los nombres de los ficheros y declarándolos al incio y usando algunos macros especiales:
CC=gcc CFLAGS=-I. DEPS = hola.h OBJ = principal.o hola.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hola: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) clean: rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
Puedes usar .PHONY para decirle a make que el target u objetivo clean en este caso es ficticio y no debe crear fichero, ya que si por ejemplo hubiese un fichero llamado clean en el directorio del código fuente, entonces no haría nada:
CC=gcc CFLAGS=-I. DEPS = hola.h OBJ = principal.o hola.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hola: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) .PHONY: clean clean: rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
Te recomiendo que comiences haciendo pequeños makefiles simples, y cada vez vayas agregando complejidad o leas los makefiles de los paquetes que descargues para ir viendo cómo se forman…
Procedimiento CMake
CMake (Cross platform Make) se puede usar en varias plataformas, entre ellas GNU/Linux. Sirve para automatizar código, es decir, para construir, probar y empaquetar tu software, una suite de herramientas totalmente independientes y de más alto nivel que el equivalente make en *nix.
Más información – CMake.org
Por ejemplo, imagina que tienes un programa compuesto por tres ficheros de código fuente llamados: main.c, hola.h y hola.c. Ahora no importa su contenido, simplemente quiero que entiendas el funcionamiento. Para facilitar la compilación, podrías crear un fichero llamado CMakeList.txt:
#Versión mínima requerida cmake_minimum_required (VERSION 2.8) #Nombre del proyecto project (Hola) #Los ficheros a agregar add_executable(hola main.c hola.c)
Tienes a tu disposición más opciones disponibles para agregar. Te recomiendo que leas la documentación específica.
Ahora, el siguiente paso sería ejecutar la herramienta CMake:
cmake CMakeList.txt
La salida de este comando será un makefile que podrás usar con make para la compilación:
make
Otra opción es hacer que compile directamente y obtengas el binario en vez de ejecutar el comando anterior, puedes usar (desde el mismo directorio):
mkdir build cd build cmake .. make
Otras alternativas
A parte de make y su implementación GNU make, también puedes encontrar otras alternativas. Por ejemplo:
- BitBake para Python.
- Apache Buildr para Java.
- GNU Build System o autotools, con una serie de herramientas para generar archivos make apropiados. Con herramientas como Autoconf y Automake.
- qmake y xmake, herramientas para crear makefiles multiplataforma.
- etc
Espero que te haya servido de ayuda…
Pingback: Programación: empaquetar tus primeros programas | ArchiTecnologia