Cuando se usa compilación separada (dividiendo el código en varias unidades de traducción), aparece la necesidad de un medio para compilar automáticamente cada fichero y decirle al enlazador cómo montar todas las piezas - con las librerías apropiadas y el código de inicio - en un fichero ejecutable. La mayoría de los compiladores le permiten hacerlo con una sola instrucción desde línea de comandos. Por ejemplo, para el compilador de C++ de GNU se puede hacer:
$ g++ SourceFile1.cpp SourceFile2.cpp
El problema con este método es que el compilador compilará cada fichero individual tanto si el fichero necesita ser recompilado como si no. Cuando un proyecto tiene muchos ficheros, puede resultar prohibitivo recompilar todo si sólo se ha cambiado un fichero.
La solución a este problema, desarrollada en Unix pero disponible de algún modo en todos los sistemas, es un programa llamado make. La utilidad make maneja todos los ficheros individuales de un proyecto siguiendo las instrucciones escritas en un fichero de texto llamado makefile. Cuando edite alguno de los ficheros del proyecto y ejecute make, el programa make seguirá las directrices del makefile para comparar las fechas de los ficheros fuente con las de los ficheros resultantes correspondientes, y si un fichero fuente es más reciente que su fichero resultante, make recompila ese fichero fuente. make sólo recompila los ficheros fuente que han cambiado, y cualquier otro fichero que esté afectado por el fichero modificado. Usando make no tendrá que recompilar todos los ficheros de su proyecto cada vez que haga un cambio, ni tendrá que comprobar si todo se construye adecuadamente. El makefile contiene todas las instrucciones para montar el proyecto. Aprender a usar make le permitirá ahorrar mucho tiempo y frustraciones. También descubrirá que make es el método típico para instalar software nuevo en máquinas GNU/Linux o Unix[48] (aunque esos makefiles tienden a ser mucho más complicados que los que aparecen en este libro, y a menudo podrá generar automáticamente un makefile para su máquina particular como parte del proceso de instalación).
Como make está disponible de algún modo para prácticamente todos los compiladores de C++ (incluso si no lo está, puede usar makes libres con cualquier compilador), será la herramienta usada en este libro. Sin embargo, los fabricantes de compiladores crean también sus propias herramientas para construir proyectos. Estas herramientas preguntan qué ficheros hay en el proyecto y determinan las relaciones entre ellos. Estas herramientas utilizan algo similar a un makefile, normalmente llamado fichero de proyecto, pero el entorno de programación mantiene este fichero para que el programador no tenga que preocuparse de él. La configuración y uso de los ficheros de proyecto varía de un entorno de desarrollo a otro, de modo que tendrá que buscar la documentación apropiada en cada caso (aunque esas herramientas proporcionadas por el fabricante normalmente son tan simples de usar que es fácil aprender a usarlas jugando un poco con ellas - mi método educativo favorito).
Los makefiles que acompañan a este libro deberían funcionar bien incluso si también usa una herramienta específica para construcción de proyectos.
Cuando escribe make (o cualquiera que sea el nombre de su programa make), make busca un fichero llamado makefile o Makefile en el directorio actual, que usted habrá creado para su proyecto. Este fichero contiene una lista de dependencias entre ficheros fuente. make comprueba las fechas de los ficheros. Si un fichero dependiente tiene una fecha más antigua que el fichero del que depende, make ejecuta la regla indicada después de la dependencia.
Todos los comentarios de los makefiles empiezan con un # y continúan hasta el final de la línea.
Como un ejemplo sencillo, el makefile para un programa llamado «hello» podría contener:
# A comment hello.exe: hello.cpp mycompiler hello.cpp
Esto dice que hello.exe (el objetivo) depende de hello.cpp. Cuando hello.cpp tiene una fecha más reciente que hello.exe, make ejecuta la «regla» mycompiler hello.cpp. Puede haber múltiples dependencias y múltiples reglas. Muchas implementaciones de make requieren que todas las reglas empiecen con un tabulador. Para lo demás, por norma general los espacios en blanco se ignoran de modo que se pueden usar a efectos de legibilidad.
Las reglas no están restringidas a llamadas al compilador; puede llamar a cualquier programa que quiera desde make. Creando conjuntos interdependientes de dependencias y reglas, puede modificar sus ficheros fuente, escribir make y estar seguro de que todos los ficheros afectados serán reconstruidos correctamente.
Un makefile puede contener macros (tenga en cuenta que estas macros no tienen nada que ver con las del preprocesador de C/C++). Las macros permiten reemplazar cadenas de texto. Los makefiles del libro usan una macro para invocar el compilador de C++. Por ejemplo,
CPP = mycompiler hello.exe: hello.cpp $(CPP) hello.cpp
El = se usa para indicar que CPP es una macro, y el $ y los paréntesis expanden la macro. En este caso, la expansión significa que la llamada a la macro $(CPP) será reemplazada con la cadena mycompiler. Con esta macro, si quiere utilizar un compilador diferente llamado cpp, sólo tiene que cambiar la macro a:
CPP = cpp
También puede añadir a la macro opciones del compilador, etc., o usar otras macros para añadir dichas opciones.
Es algo tedioso tener que decir a make cómo invocar al compilador para cada fichero cpp del proyecto, cuando se sabe que básicamente siempre es el mismo proceso. Como make está diseñado para ahorrar tiempo, también tiene un modo de abreviar acciones, siempre que dependan del sufijo de los ficheros. Estas abreviaturas se llaman reglas de sufijo. Una regla de sufijo es la forma de indicar a make cómo convertir un fichero con cierta extensión (.cpp por ejemplo) en un fichero con otra extensión (.obj o .exe). Una vez que le haya indicado a make las reglas para producir un tipo de fichero a partir de otro, lo único que tiene que hacer es decirle a make cuáles son las dependencias respecto a otros ficheros. Cuando make encuentra un fichero con una fecha previa a otro fichero del que depende, usa la regla para crear la versión actualizada del fichero objetivo.
La regla de sufijo le dice a make que no necesita reglas explícitas para construir cada cosa, sino que puede averiguar cómo construir las cosas en base a la extensión del fichero. En este caso dice «Para construir un fichero con extensión .exe a partir de uno con extensión .cpp, invocar el siguiente comando». Así sería para ese ejemplo:
CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $<
La directiva .SUFFIXES le dice a make que debe vigilar las extensiones que se indican porque tienen un significado especial para este makefile en particular. Lo siguiente que aparece es la regla de sufijo .cpp.exe, que dice «Así se convierte cualquier fichero con extensión .cpp a uno con extensión .exe» (cuando el fichero .cpp es más reciente que el fichero .exe). Como antes, se usa la macro $(CPP), pero aquí aparece algo nuevo: $<. Como empieza con un $ es que es una macro, pero ésta es una de las macros especiales predefinidas por make. El $< se puede usar sólo en reglas de sufijo y significa «cualquier prerrequisito que dispare la regla» (a veces llamado dependiente), que en este caso se refiere al «fichero .cpp que necesita ser compilado».
Una vez que las reglas de sufijo se han fijado, puede indicar por ejemplo algo tan simple como make Union.exe y se aplicará la regla de sufijo, incluso aunque no se mencione «Union» en ninguna parte del makefile.
Después de las macros y las reglas de sufijo, make busca el primer «objetivo» del fichero, y lo construye, a menos que se especifique otra cosa. Así que para el siguiente makefile:
CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $< target1.exe: target2.exe:
Si ejecuta simplemente make, se construirá target1.exe (usando la regla de sufijo predeterminada) porque ése es el primer objetivo que make va a encontrar. Para construir target2.exe se debe indicar explícitamente diciendo make target2.exe. Esto puede resultar tedioso de modo que normalmente se crea un objetivo «ficticio» por defecto que depende del resto de objetivos, como éste:
CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $< all: target1.exe target2.exe
Aquí, all no existe y no hay ningún fichero llamado all, así que cada vez que ejecute make, el programa verá que all es el primer objetivo de la lista (y por tanto el objetivo por defecto), entonces comprobará que all no existe y analizará sus dependencias. Comprueba target1.exe y (usando la regla de sufijo) comprobará (1) si target1.exe existe y (2) si target1.cpp es más reciente que target1.exe, y si es así ejecutará la regla (si proporciona una regla explícita para un objetivo concreto, se usará esa regla en su lugar). Después pasa a analizar el siguiente fichero de la lista de objetivos por defecto. De este modo, creando una lista de objetivos por defecto (típicamente llamada all por convenio, aunque puede tener cualquier nombre) puede conseguir que se construyan todos los ejecutables de su proyecto simplemente escribiendo make. Además, puede tener otras listas de objetivos no predeterminadas para hacer otras cosas - por ejemplo, podría hacer que escribiendo make debug se reconstruyeran todos los ficheros pero incluyendo información de depuración.
[48] (N. del T.) El método del que habla el autor se refiere normalmente a software instalado a partir de su código fuente. La instalación de paquetes binarios es mucho más simple y automatizada en la mayoría de las distribuciones actuales de GNU/Linux.