Como crear un makefile básico

Publicado el 11 agosto 2015 por José José Molino Ortega @BlogLinceus

Cómo crear un Makefile básico


Un makefile no es ni más ni menos que un fichero de texto plano que define una serie de reglas que, a través de la aplicación Make, automatiza la compilación de código de forma simple y organizada. Make puede ser empleado para compilar cualquier tipo de lenguaje y desde cualquier sistema operativo, pero históricamente se ha empleado mayormente en C y entornos Unix. No obstante, a día de hoy cualquier entorno decente que integre las herramientas básicas de programación suele incluir su propia adaptación de Make, permitiéndole compilar haciendo uso de su propio sistema de guiones (véase por ejemplo Miscrosoft Visual Studio, Netbeans o Eclipse).

Realizar un makefile básico no es nada complicado, basta con entender las siguientes dos premisas: Todo fichero makefile debe llamarse "makefile"; El contenido de todo makefile debe contener el siguiente formato:


nombre_de_una_regla: dependencias_de_la_regla (si la hubiere)
(tabulación) comando_a_ejecutar
Ejemplo:

Para entender el ejemplo anterior hay que tener claro un par de cosas: La regla por defecto debe llamarse "all". No es obligatorio, pero sí cómodo, puesto que "all" es una palabra reservada que indica cual es la regla por defecto a ejecutar en caso de que ejecutemos un "make" a secas, sin argumentos. Si en vez de llamarse "all" se llamara "compilar", habría que ejecutar el comando "make compilar" para poder ejecutar esta regla. También hay que tener presente que make fallará (dará error) si ejecutamos el comando en un directorio donde no exista un archivo makefile. Cuando una línea de la regla falle, Make abortará su ejecución, notificando el error que ha detectado.

Profundizando más en el ejemplo, vemos que hemos empleado el gcc como compilador, pero nos podría valer cualquier otro, puesto que todos los compiladores estándares de C suelen emplear los mismos parámetros de entrada. En este caso si nos fijamos hemos hecho la compilación en dos líneas: En la primera compilamos el código fuente contenido en el fichero main.c (debe indicarse con el argumento "-c") a un objeto binario (y no ejecutable) main.o (donde el argumento "-o" indica que se trata del nombre del fichero output): En la segunda línea empleamos el main.o que hemos generado previamente y lo transformamos en un ejecutable llamado "main".

Cabe destacar que Make no tiene por qué ir ligado al lenguaje C (aunque ambas van bastante cogidas de la mano), también puede emplearse para compilar proyectos en C++ o Java:


Ejemplo con C++


Ejemplo con Java


No obstante, las reglas de un makefile no tienen por qué definir únicamente comandos de compilación, también pueden ejecutar órdenes de shell:


En este ejemplo el comando "make clean" borraría los ficheros main y main.o


Pero la magia de los makefiles no radica en su utilidad de automatizar la compilación de código (para ello ya existen los shell scripts), si no en su alto grado de personalización, permitiendo el uso de variables y de dependencias. Por ejemplo, podemos indicar que cuando se ejecute la regla "all", se requiera previamente la ejecución de otra regla que cree los directorios "tmp" y "build", que serán empleados para guardar respectivamente el objeto binario y el posterior ejecutable:


También podemos emplear variables para indicar en qué directorio se va a encontrar cada recurso. Para crear una variable nos bastará con emplear el siempre intuitivo "=", pero para hacer uso de ella deberemos de envolverla entre paréntesis y hacer uso préviamente del símbolo "$":


Hay que destacar que el uso de dependencias no tienen por qué ir ligadas a reglas, también pueden indicar la necesidad de que exista un fichero:


En este ejemplo vemos que "all" se ejecutará si la regla folder acaba con éxito y a su vez existe el fichero $(source_dir)/main.c. Como podéis apreciar, a nivel de dependencias no existe ninguna diferencia para asignar una regla o un archivo. Esto se debe a que Make por defecto buscará la existencia de un fichero o directorio y en caso de no hallarlo buscará su correspondiente regla. En este ejemplo no se ejecutará dicha regla si se da el caso de que tengamos en nuestro proyecto un archivo llamado "folder". Así que si vemos que en nuestro proyecto hay ficheros con nombres que pueden resultar problemáticos para Make, podemos forzar que se haga uso de reglas para determinadas palabras empleando la etiqueta ".PHONY":


La prioridad de Make en comprobar la existencia de ficheros antes que la ejecución de reglas no es una casualidad, si no que está hecho aposta para poder ahorrar tiempos de compilación en proyectos grandes. Esto se debe a que es bastante común crear una regla por objeto compilable y asignarle como nombre el mismo que tiene el fichero que genera. De esta forma, si un objeto no existe se ejecuta diréctamente la regla que fuerza su compilación.


Explicación del ejemplo:
1- Si existe el fichero main.o, se hace uso de él.
2- Si no existe el fichero main.o, ejecutamos la regla con su mismo nombre, el cual generará el fichero.
3- Si cambiamos el contenido de main.c, debemos de hacer un make clean para compilar los cambios al objeto main.o.
4- Si antes de "all" insertáramos la línea ".PHONY: main.o", nunca se comprobaría la existencia del fichero main.o puesto que se ejecutaría siempre su correspondiente regla.

También hay que tener cuidado cuando insertemos reglas que ejecuten comandos de shell. Ten en cuenta que comandos como mkdir pueden fallar si se intenta crear un fichero que ya existe y comandos como rm pueden fallar si se intenta borrar un fichero que no existe. Esto lo comento porque como he mencionado antes, el fallo de una línea hace que Make aborte la ejecución del resto de reglas.


Por ejemplo, en el caso anterior, un "make clean ; make" funcionaría bien, pero sería imposible compilar la aplicación si hiciéramos "make folder ; make". Esto nos obligaría a realizar previamente un "make clean" cada vez que quisiéramos hacer un "make". Para solventar este paso podemos insertar al principio de una línea el carácter "-", puesto que este carácter indica que Make debe ignorar el valor que retorna la línea. Es decir, en determinadas líneas podemos solicitar que Make continúe ejecutando el resto de líneas pase lo que pase:


Por último sólo remarcar que puedes insertar comentarios dentro de un makefile empleando el carácter "#". Con todo ello ya deberías de saber realizar makefiles básicos.