3.11. Make: cómo hacer compilación separada

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 como 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 desde una sólo instrucción desde línea de comandos. Por ejemplo, para el compilador de C++ de GNU se puede hacer:

$ g++ SourceFile1.cpp SourceFile2.cpp

En problema con este método es que el compilador compilará cada fichero individual tanto si el fichero necesita ser recompilado como sino. Cuando un proyecto tiene muchos ficheros, puede resultar prohibitivo recompilar todo cada vez que se cambia una línea en un fichero.

La solución a este problema, desarrollada en Unix pero disponible de alú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 una 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 o Unix[45] (aunque esos makefiles tienen 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. Estás 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.

3.11.1. Las actividades de Make

Cuando escribe make (o cualquiera que sea el nombre del 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 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 fina de la línea.

Como un ejemplo sencillo, el makefile para una 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. Creando grupos de reglas de dependencia, puede modificar sus ficheros fuentes, escribir make y estar seguro de que todos los fichero afectados serán re-construidos correctamente.

Macros

Un makefile puede contener macros (tenga en cuenta que estas macros no tienen nada que ver con las del preprocesador de C/C++). La 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.

Reglas de sufijo

Es algo tedioso tener que decir a make que invoque 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 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 cuales 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 se necesitan reglas explícitas para construir cada cosa, en su lugar le explica cómo construir cosas en base a la extensión del fichero. En este caso dice «Para contruir 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 tiene un significado especial para este makefile en particular. Lo siguiente que aparece es la regla de sufijo .cpp.exe, que dice «cómo convertir 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 esta 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 dependencia), que en este caso se refiere al «fichero .cpp que necesita ser compilado».

Una ver que las reglas de sufijo se han fijado, puede indicar por ejemplo algo tan simple como make Union.exe y se aplicará la regla sufijo, incluso aunque no se mencione «Union» en ninguna parte del makefile.

Objetivos predeterminados

Después de las macros y las reglas de sufijo, make busca la primero «regla» del fichero, y la ejecuta, a menos que se especifica una regla diferente. Así que pare 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 ese 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 «dummy» 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 llamada 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) que target1.exe existe y (2) que 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, breando una lista de objetivos por defecto (típicamente llamada all por convenio, aunque se 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 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.



[45] (N. de 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 variantes actuales del sistema operativo GNU.