1.4.0a1 GNU Make
§1 Sinopsis
El compilador GNU cpp dispone de su correspondiente utilidad Make para
la construcción de proyectos; ya sean ejecutables o librerías de cualquier
tipo. Seguramente esta versión de Make es de las
que cuentan con mejor y más extensa documentación sobre su
funcionamiento. El manual correspondiente está disponible en la sección de manuales
online de la organización GNU
www.gnu.org.
Aconsejo consultarlo aunque se esté utilizando otro compilador, dado que en
realidad las versiones de Make de las distintas plataformas son muy parecidas, si no idénticas, y
sus
enseñanzas le resultarán de gran ayuda. Por supuesto, para escribir un
makefile, es necesario conocer las opciones del compilador, enlazador y
cualquier otra utilidad que sea utilzada con
él. En nuestro caso, la documentación sobre los compiladores GNU gcc y
demás utilidades está
igualmente disponible en la sección de manuales de la citada organización (
www.gnu.org).
Nota: para la redacción del presente capítulo he utilizado los siguientes:
Manual GNU Gcc: "Using the GNU Compiler Collection" For GCC Version 4.1.1.
Manual GNU Make: "The GNU Make Manual" for GNU make version 3.81. Edition 0.70, last updated 1 April 2006.
Manual Enlazador GNU: "Using ld The GNU linker" version 2 January 1994
Manual Utilidades Binarias (binutils GNU): "Preliminary documentation for the GNU binary utilities. version 2.12"
Esta página no pretende ser un sustituto de los manuales citados,
solo una breve introducción en la que destacaremos sus características
principales y algún ejemplo sencillo de uso. Advirtamos también, que al objeto de que puedan ser reproducidos por la mayor cantidad de lectores posible, los
ejemplos incluidos en esta sección se suponen realizados bajo Windows32 utilizando la versión
3.9 de MinGW incluida en la versión 4.9.9.2 del entorno de desarrollo Dev-C++ (
CompiladoresC) [5].
Aunque las herramientas y entornos GNU son asociados generalmente con el mundo Unix/Linux, debemos recordar que existen versiones para las plataformas más comunes (prácticamente todas las imaginables), incluyendo por supuesto las de Microsoft. Respecto a estas últimas, existen herramientas GNU que permiten la construcción de aplicaciones C/C++ para las distintas versiones de Windows. En concreto, existen dos que merecen ser consideradas: nos referimos a Cygwin y MinGW que se sustentan en filosofías muy distintas para conseguir sus objetivos.
Cygwin consiste en una capa ("layer") de software, que permite que aplicaciones que han sido desarrollados para Unix/Linux, puedan ser compiladas y ejecutadas en Windows. Su principal ventaja es que permite trasladar el enorme arsenal de herramientas Unix/Linux de código abierto a estos entornos [1]. Por su parte, MinGW hace honor a su nombre, acrónimo de "Minimalist GNU for Windows", en el sentido de que incluye las herramientas mínimas para construir ejecutables en Windows. En este caso, las aplicaciones deben ser escritas para Windows, de forma análoga a como se haría en cualquier otra plataforma nativa de estos sistemas. Por ejemplo, para los compiladores Visual C++ de MS o C++ Builder de Borland-Imprise.
En realidad MinGW consiste básicamente en una versión del compilador GNU gcc para Windows, que incluye el compilador propiamente dicho, el enlazador y el depurador (GDB). Así como una colección de ficheros de cabecera, de "makefiles" y ficheros "scripts". Para que estos últimos puedan ser ejecutados en Windows utilizando una sintaxis POSIX [2], existe una utilidad especial denominada MSYS (Minimal SYStem); que remeda el shell de Linux/Unix, incluyendo un intérprete de comandos ("Command Line Interpreter" CLI) y un conjunto mínimo de las herramientas del shell de Unix/Linux. Por ejemplo, touch; cat; grep; mount; tail; xargs, etc. (esta utilidad ya viene incluida por defecto en la plataforma Dev-C++).
§2 GNU Make en la práctica
Los principios de funcionamiento los explicaremos mediante un ejemplo, en el que construimos una pequeña aplicación C++ compuesta por tres ficheros: main.cpp; string.cpp y string.h. Como puede verse, dos ficheros fuente y uno de cabecera.
Para nuestro propósito, utilizaremos un makefile al que denominaremos make.my, con el siguiente diseño:
# Project: una clase string
CPP = g++.exe # el compilador GNU C++
OBJ = main.o string.o
LINKOBJ = main.o string.o
LIBS = -L"C:/DEV-CPP/lib"
CXXINCS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \
-I"C:/DEV-CPP/include/c++/3.4.2/backward" \
-I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \
-I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include"
BIN = string.exe
CXXFLAGS = $(CXXINCS) -fno-elide-constructors
.PHONY: clean
all: string.exe
clean:
${RM} $(OBJ) $(BIN)
$(BIN): $(OBJ)
$(CPP) $(LINKOBJ) -o "string.exe" $(LIBS)
main.o: main.cpp
$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
string.o: string.cpp
$(CPP) -c string.cpp -o string.o $(CXXFLAGS)
§3 Invocación
Suponemos que la utilidad make.exe, junto con el resto de binarios MinGW, está en el directorio C:\Dev-Cpp\bin, y que los tres ficheros de nuestro proyecto se encuentran en D:\LearnC. Utilizando una ventana DOS de Windows, nos situamos en el directorio de trabajo (donde residen los fuentes):
C:\Windows>D:
D:\>cd LearnC
D:\LearnC>
A continuación establecemos el camino de búsqueda ("Path"), de forma que nuestras órdenes encuentren los binarios correspondientes:
D:\LearnC>set PATH=C:\Dev-Cpp\bin;%path%
Hecho esto, ya podemos invocar Make con el comando:
make -f makefile.my
Suponiendo que los ficheros de la aplicación son correctos, después de un instante, se obtiene la respuesta a nuestra orden:
g++.exe -c main.cpp -o main.o -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include"
-I"C:/DEV-CPP/include/c++/3.4.2/backward" -I"C:/DEV-CPP/include/c++/3.4.2/mingw32"
-I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include" -fno-elide-constructors
g++.exe -c string.cpp -o string.o -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include"
-I"C:/DEV-CPP/include/c++/3.4.2/backward" -I"C:/DEV-CPP/include/c++/3.4.2/mingw32"
-I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include" -fno-elide-constructors
g++.exe main.o string.o -o "string.exe" -L"C:/DEV-CPP/lib"
Además del ejecutable string.exe, en nuestro directorio de trabajo aparecen los ficheros main.o y string.o, que han sido necesarios para la construcción del primero. Es significativo que si repetimos la orden anterior sin realizar absolutamente ninguna modificación en los ficheros del proyecto, obtendríamos una respuesta distinta:
Nothing to be done for `all'.
La razón es que, una vez construido el ejecutable y todos los ficheros
intermedios necesarios, Make encuentra que todas las dependencias son
correctas y no es necesario hacer nada más. Observe que all es el primer target del
makefile; es justamente lo que Make
intenta hacer en ausencia de alguna otra indicación al respecto (algún goal
específico en la invocación
1.4.0a). Sin embargo, mediante la opción
-B
("Build"), es posible ordenar a Make que reconstruya el proyecto
con todos sus ficheros intermedios aunque no se haya efectuado ningún cambio:
make -B -f makefile.my
Como puede verse, GNU Make puede ser invocada con distintas opciones; en este caso,
mediante la opción -f, le hemos indicado que utilice nuestro script make.my, en lugar del fichero por
defecto (por defecto Make intenta usar los ficheros GNUmakefile, makefile, y Makefile,
sin ningún sufijo y en ese orden). En caso contrario, si
no existe ninguno de los ficheros citados en nuestro directorio y no se le indica
ninguna opción, Make responde con un mensaje de error:
*** No targets specified and no makefile found. Stop.
La opción -v muestra la versión de Make utilizada:
D:\LearnC>make -v
GNU Make 3.80
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
También podemos solicitar una relación de las principales opciones. Para ello puede usarse la opción -h ("help")
make -h
en nuestro caso, obtenemos la siguiente salida [3]:
Usage: C:\DEV-CPP\BIN\MAKE.EXE [options] [target] ...
Options:
-b, -m
Ignored for compatibility.
-B, --always-make
Unconditionally make all targets.
-C DIRECTORY, --directory=DIRECTORY
Change to DIRECTORY before doing anything.
-d
Print lots of debugging information.
--debug[=FLAGS]
Print various types of debugging information.
-e, --environment-overrides
Environment variables override makefiles.
-f FILE, --file=FILE, --makefile=FILE
Read FILE as a makefile.
-h, --help
Print this message and exit.
-i, --ignore-errors Ignore errors from commands.
-I DIRECTORY, --include-dir=DIRECTORY
Search DIRECTORY for included makefiles.
-j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.
-k, --keep-going
Keep going when some targets can't be made.
-l [N], --load-average[=N], --max-load[=N]
Don't start multiple jobs unless load is below N.
-n, --just-print, --dry-run, --recon
Don't actually run any commands; just print them.
-o FILE, --old-file=FILE, --assume-old=FILE
Consider FILE to be very old and don't remake it.
-p, --print-data-base Print make's internal database.
-q, --question
Run no commands; exit status says if up to date.
-r, --no-builtin-rules Disable the built-in implicit rules.
-R, --no-builtin-variables Disable the built-in variable settings.
-s, --silent, --quiet Don't echo commands.
-S, --no-keep-going, --stop
Turns off -k.
-t, --touch
Touch targets instead of remaking them.
-v, --version
Print the version number of make and exit.
-w, --print-directory Print the current directory.
--no-print-directory Turn off -w, even if it was turned on implicitly.
-W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
Consider FILE to be infinitely new.
--warn-undefined-variables Warn when an undefined variable is referenced.
§3.1 Probar sin arriesgar
GNU Make permite comprobaciones del tipo "que tal si...". La panoplia de opciones es bastante amplia; aquí solo indicaremos alguna de ellas (consulte la sección 9.3 del manual, "Instead of Executing the Commands", para más información al respecto).
Por ejemplo, la opción -n muestra exactamente las mismas salidas que en una invocación normal (que comandos son invocados), pero sin que tales invocaciones ocurran realmente, de forma que no se produce ningún resultado. En nuestro caso, una invocación del tipo
make -n -f makefile.my
produciría exactamente las salidas señaladas en el epígrafe anterior pero sin que se realizara ninguna acción.
Mediante la opción -W nombre-fichero, es posible ordenar a Make que
se comporte "como si" alguno de los ficheros
involucrados en el makefile tuviese un "timestamp" con la fecha
actual aunque en realidad no sea así. Por ejemplo, si en el caso anterior,
después de haber realizado una compilación completa queremos simular que el
fichero strin.o tiene la fecha actual, y por tanto es posterior a la de string.exe,
ejecutamos el comando
make -W string.o -f makefile.zat
Ante esta suposición, Make interpreta que debe volver a enlazar los objetos para obtener una versión actualizada del ejecutable, y por tanto, ejecutar el comando de la línea 22. El ejecutable es reconstruido y se obtiene la siguiente respuesta.
g++.exe main.o string.o -o "string.exe" -L"C:/DEV-CPP/lib"
Combinando las opciones anteriores es posible comprobar "qué pasaría", pero sin que efectivamente se realice la reconstrucción del ejecutable:
make -n -W string.o -f makefile.zat
§4 Radiografía de un makefile GNU
A continuación desgranamos el significado de las distintas líneas del
script make.my reseñado antes .
§4.1 Comentarios
La primera y tercera líneas contienen comentarios:
# Project: una clase string
CPP = g++.exe # el compilador GNU C++
Cualquier contenido a continuación del símbolo ampersand ( # ) es un comentario y no es tenido en cuenta por Make.
§4.2 Macros
Las líneas 3 a 12 inclusive contienen macros (
1.4.0a)
CPP = g++.exe # el compilador GNU C++
OBJ = main.o string.o
LINKOBJ = main.o string.o
LIBS = -L"C:/DEV-CPP/lib"
CXXINCS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \
-I"C:/DEV-CPP/include/c++/3.4.2/backward" \
-I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \
-I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include"
BIN = string.exe
CXXFLAGS = $(CXXINCS) -fno-elide-constructors
Posteriormente, cuando estas macros aparecen en las líneas de comando, son sustituidas automáticamente por su contenido. Por ejemplo, el comando de la línea 19:
${RM} $(OBJ) $(BIN)
será transformado en:
rm -f main.o string.o string.exe
Al llegar aquí es conveniente recordar lo indicado en la página anterior
respecto a las macros predefinidas (
1.4.0a), donde señalábamos que GNU Make
dispone, entre otras, de la macro RM predefinida como
RM = rm -f
así que la utilizamos en el comando de la línea 19 sin haberla declarado antes. Observe que no estamos invocando ninguna opción del shell de windows (command.com), sino el ejecutable rm.exe; un módulo de MinGW que remeda el comando del mismo nombre del shell de Unix/Linux. Evidentemente el resultado que se pretende es borrar los ficheros resultantes de operaciones anteriores si los hubiere.
Nota: es posible obtener una relación de las macros y reglas implícitas predefinidas en GNU Make, invocándolo con la opción -p, pero como la respuesta es bastante extensa, si está utilizando la versión MinGW para Windows, es mejor enviar la salida a un fichero auxiliar que puede ser posteriormente inspeccionado con cualquier editor de texto plano. Por ejemplo:
make -p > macros.txt
Observe también que las líneas 7 a 10 constituyen una sola macro (la
opción -I indica al compilador los directorios que debe explorar
para encontrar los ficheros de cabecera). Lo mismo que ocurre con la sintaxis en los fuentes C++, también aquí es posible señalar
la continuación de una sentencia en la línea siguiente colocando una barra
invertida ( \ ) al final de la línea.
La macro CXXFLAGS de la línea 12:
CXXFLAGS = $(CXXINCS) -fno-elide-constructors
contiene todas las directivas que se pasarán al compilador para obtener los
ficheros main.o y string.o (líneas 25 y 28). Como puede verse, además de
las direcciones de los ficheros de cabecera ya mencionadas, incluimos la
directiva -fno-elide-constructors, que es específica de este proyecto.
Se refiere a que el Estandar C++ permite que una implementación omita crear
objetos temporales que solo son utilizados para inicializar otros objetos del
mismo tipo. Esta opción ordena al compilador a deshabilitar esta optimización,
forzándolo a invocar al constructor-copia en todos los casos (
4.11.2d4). De forma análoga podríamos haber
incluido cualquier otra opción de compilación que fuese necesaria para nuestro
proyecto (están detalladas en el manual del compilador).
Es digno de mención que, aunque es lícito utilizar cualquier nombre para
las etiquetas de las macros, es regla de buena práctica utilizar dentro de lo
posible, los que ya están predefinidos en Make con el mismo propósito
(recordar lo señalado en la página anterior al tratar de las reglas
implícitas y de las macros predefinidas
1.4.0a). Esto permite
utilizar las reglas implícitas y hacer nuestro texto más legible para otros
programadores. En nuestro caso, CXXFLAGS es una etiqueta que es utilizada
por Make en dos de sus macros implícitas [4]. Son las
siguientes:
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
A su vez, estas macros intervienen en las siguientes reglas y macros predefinidas:
LINK.C = $(LINK.cc)
LINK.cpp = $(LINK.cc)
%: %.cc # commands to execute (built-in):
$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
.cc: # Not a target.
$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
COMPILE.cpp = $(COMPILE.cc)
COMPILE.C = $(COMPILE.cc)
%.o: %.cc # commands to execute (built-in):
$(COMPILE.cc) $(OUTPUT_OPTION) $<
.cc.o: # Not a target
$(COMPILE.cc) $(OUTPUT_OPTION) $<
Aunque en nuestro makefile solo utilizamos la macro predefinida RM, no está de más recordar la conveniencia de respetar la nomenclatura "normalizada" para dar nombre a nuestras propias macros, manteniendo en lo posible su sentido original para las etiquetas utilizadas en las reglas implícitas de Make.
§4.3 Objetivos principales
Las líneas 14 a 19 son de lo más interesante, y constituyen el núcleo de la operatividad de este makefile.
.PHONY: clean
all: string.exe
clean:
${RM} $(OBJ) $(BIN)
La primera es una regla un tanto particular; carece línea de comando y el
resultado .PHONY de su línea de dependencias, es una etiqueta que tiene un
significado especial para GNU Make. Es una especie de "calificador de
reglas", en el sentido de que indica que los identificadores contenidos en
sus prerrequisitos (clean) son a su vez objetivos especiales
("phony targets"). Recuerde que, como señalábamos en la introducción (
1.4.0a), con este nombre
denominamos aquellos objetivos que no consisten en la construcción de ejecutables o librerías,
sino en determinadas acciones. Por
ejemplo, borrar ciertos ficheros o moverlos de sitio; acciones estas que se
identifican por un nombre.
Nota: además de .PHONY, existen distintos tipos de estos "calificadores" especiales. Son los siguientes: .SUFFIXES, .DEFAULT, .PRECIOUS, .INTERMEDIATE, .SECONDARY, .SECONDEXPANSION, .DELETE_ON_ERROR, .IGNORE, .LOW_RESOLUTION_TIME, .SILENT, .EXPORT_ALL_VARIABLES y .NOTPARALLEL. Consulte el manual GNU Make al respecto ("Special Built-in Target Names").
La razón de ser de este calificador es que, en casos como la regla de las líneas 18-19, en los que no existen prerrequisitos, el comando
${RM} $(OBJ) $(BIN)
será ejecutado cada vez que se invoque make -f clean. Sin embargo, si por alguna razón se creara un fichero de nombre clean en el directorio, al carecer de prerrequisitos, Make considerará que la regla está siempre satisfecha y non ejecutará el comando. Declarándolo phony target en la regla de la línea 15, estamos seguros que el makefile funcionará correctamente aunque se creara un ficheros de nombre clean en el directorio correspondiente.
Nota: esta seguridad adicional, puede ser considerada excesiva en directorios con proyectos de unos pocos ficheros, no lo es tanto en proyectos grandes donde existen distintos subdirectorios con centenares de ficheros y en los que trabajan decenas de programadores.
Como resultado de lo anterior, la regla de la línea 16:
all: string.exe
contiene el primer objetivo propiamente dicho del makefile y constituye por tanto su objetivo principal ("goal"). Es el que se alcanza cuando se invoca el makefile sin ningún "target" específico.
§4.4 Comandos
Aparte de la regla 16 ya comentada
, las líneas 21 a 28 contienen las reglas para construir los ficheros
intermedios y para enlazarlos juntos en el ejecutable final, así como las
condiciones requeridas en cada caso. En concreto, las líneas 24-25:
main.o: main.cpp
$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
indican que si el "timestamp" (
5.5.1) del fichero main.o es anterior al del
fuente main.cpp, o sencillamente main.o no existe, se invoca al compilador g++.exe con las opciones -c, -o y CXXFLAGS ya comentadas
.
La opción -c señala que debe compilarse el fuente main.cpp para obtener un
objeto, pero sin ensamblar el fichero resultante. La opción -o señala
que el nombre del fichero de salida ("output") debe ser main.o.
La regla de las líneas 27-28 es análoga, aunque refiere a la obtención del objeto string.o a partir del fuente string.cpp:
string.o: string.cpp
$(CPP) -c string.cpp -o string.o $(CXXFLAGS)
Finalmente, las líneas 21-22:
$(BIN): $(OBJ)
$(CPP) $(LINKOBJ) -o "string.exe" $(LIBS)
se refieren a la invocación del compilador (en realidad el enlazador) para ensamblar los ficheros anteriores, main.o y string.o, en un fichero ejecutable de nombre string.exe. Esta operación debe efectuarse si el "timestamp" de alguno de los ficheros-objeto fuese posterior al del ejecutable.
Observe que una vez establecido el objetivo principal ("goal"), es indiferente el orden de aparición del resto de reglas el makefile. Aquí la orden de enlazar aparece antes de que se hayan creado los objetos que serán enlazados. La razón es que Make se encarga de analizar las dependencias y ejecutar los comandos en el orden adecuado.
[1] A la fecha (Septiembre de 2006) no he tenido ocasión de utilizarla, aunque he de advertir que algunas opiniones al respecto, recogidas en los foros especializados, señalan que Cygwin es particularmente difícil de instalar y mantener. Además, la política de licencias relativas a las aplicaciones desarrolladas en esta plataforma es mucho más restrictiva que la de MinGW, lo que debe ser tenido en cuenta a la hora de desarrollar aplicaciones comerciales.
[2] POSIX es el acrónimo de "Portable Operating System Interface" referido a los sistemas Unix. Es un conjunto de estándares IEEE que pretende portabilidad entre las distintas variantes de este Sistema Operativo (una especie de Unix estándar). En particular, el estándar IEEE 1003.1 define una interfaz normalizada para estos sistemas; la IEEE 1003.2 define el shell y las utilidades, mientras que la IEEE 1003.4 define las extensiones de tiempo real.
[3] Como el resultado tiene más de las 24 filas que pueden verse en una ventana DOS de Window, es posible ordenar que la salida sea redirigida un fichero de texto que puede inspeccionarse después con el block de notas ("notepad") o cualquier editor de texto plano:
make -h > texto.txt
[4] Aunque sea utilizado en algunas macros predefinidas, algunos de estos nombres, como es el caso de CXXFLAGS, no cuentan con ninguna definición implícita, de forma que es nuestra responsabilidad definir dicha macro en nuestro makefile si queremos aprovechar tales reglas.
[5] Además de la anterior, si está usando, o planea utilizar esta plataforma, puede ser una
buena idea consultar la página de documentación del proyecto MinGW (
www.mingw.org).