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.
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.
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.
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
.
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.