Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


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.

  Inicio.


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