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.0a  Utilidad make (construir proyectos grandes)

"The art of constructin makefiles is arcane; fortunately, Microsoft development environment constructs these automatically for you. Other development environments provide similar tools and facilities, either as explicit control files or as part of a "proyect description file"". Brent E. Rector y Joseph M. Newcomer. "Win32 Programming". Addison-Wesley. Abril 2005.

§1  Presentación del problema:

En la práctica los programas C++ no son tan simples que ocupen un solo fichero fuente, lo normal es que ocupen varios, y los proyectos grandes pueden contener decenas de fuentes, a los que hay que sumar las librerías estándar o específicas; los ficheros de recursos ("resource files"); los de cabecera, etc. Estos proyectos pueden dar lugar a múltiples ficheros ejecutables y a librerías de distinto tipo.  En estas circunstancias, si un proyecto se compone de una serie de n fuentes, digamos por ejemplo:  p1.cp2.cp3.c; ... pn.c;  para construir la aplicación es necesario compilar individualmente (cada uno quizás con sus propias opciones de compilación), con lo que se obtienen n objetos:  p1.objp2.objp3.obj; ... pn.obj, que posteriormente son enviados al enlazador para construir los ejecutables y/o librerías.

Cada vez que se realiza una modificación en un fuente, o se cambia la versión de una librería, un fichero de recursos, un fichero de cabecera, etc. es necesario recompilar los nuevos objetos y volver a enlazar.  En proyectos grandes es un gran consumo de tiempo rehacer toda la aplicación cada vez, incluyendo los módulos que no han sufrido modificación (como hemos hecho en el ejemplo anterior  1.4.0), por lo que se intenta que los cambios, recompilaciones, enlazados, etc. solo afecten a los módulos estrictamente necesarios.  Además, pueden existir decenas de comandos involucrados en la operación, cada uno con multitud de argumentos. Todo lo cual es difícil de mantener y memorizar.

§2  Make

Con objeto de facilitar el trabajo de controlar qué objetos, están desfasados respecto a otros módulos, y qué partes de la aplicación deben ser reconstruidas, bien porque son de fecha anterior a la última modificación del fuente, o porque sencillamente no existen (fuentes nuevos que no han sido compilados ninguna vez), los entornos de programación C++ disponen de una utilidad especial denominada Make  (en Borland C++ es make.exe; en MS Visual C++ es nmake.exe).

Make es una utilidad que simplifica los ciclos de compilación y enlazado; ayuda a construir rápidamente grandes proyectos porque permite compilar solo aquellos fuentes que han sufrido modificación desde la última compilación y reconstruir los ejecutables que dependen de los objetos resultantes. Además, permite establecerse una serie de reglas que especifican como debe procederse con las circunstancias especiales de cada caso. Pero make es algo más que una utilidad ligada a la compilación y enlazado, se trata de una herramienta genérica para ejecutar comandos en base a ciertas dependencias, y aunque generalmente está ligado a las operaciones de compilar y enlazar ficheros, puede especificarse prácticamente cualquier comando aceptado por el Sistema Operativo.

Nota: en cierta forma, Make es una utilidad un tanto extraña, o cuando menos "peculiar". Por sus características no se parece a nada que hayamos visto antes; de forma que cuesta un poco "meterse" en su filosofía de trabajo. A pesar de ser cierto lo dicho -que es una herramienta genérica-, en realidad su diseño está orientado a resolver un problema muy concreto (descrito brevemente en el punto anterior), lo que es sin duda el origen de su singularidad. Como suele ocurrir con este tipo de aplicaciones de diseño muy específico, hace bien su trabajo (para propósitos generales existen lenguajes Script mucho más adecuados). Además no es una herramienta exclusiva de C++, otros compiladores utilizan la misma o muy parecida, ya que resuelve un problema que es general a todos los lenguajes que dependen de un proceso de compilación y enlazado.

El lector que se enfrenta por primera vez al asunto, puede pensar que las definiciones y explicaciones que se exponen a continuación son un "rollo" difícil de entender. De momento no tiene imágenes mentales previas sobre las que construir las ideas que le ayuden a comprender de que se está hablando (tampoco se pueden poner ejemplos hasta que se tienen un mínimo de definiciones y explicaciones), pero tened paciencia, en cuanto se llega a los ejemplos concretos se empieza a entender "de que va el asunto" y cual es su intríngulis.


§3  Filosofía de trabajo

La filosofía de trabajo de Make se basa en una sencilla frase: "Ejecutar comandos en base a ciertas dependencias para conseguir determinados objetivos". Adelantemos aquí que las palabras: comandos, dependencias y objetivos ("targets") deben tomarse en un sentido amplio.  Comandos son acciones que pueden responder cualquier orden aceptada por el SO y/o específicas que puede realizar Make;  dependencias son una serie de relaciones que puede establecer el programador; suelen referirse a relaciones entre ficheros. Generalmente del tipo "la última modificación del fichero XXX debe ser posterior a la del fichero YYY; en caso contrario, se ejecutará el comando zzz". Cuando las dependencias se refieren a la existencia de ciertos ficheros, que son necesarios para crear otro, también es frecuente designarlas como prerrequisitos. Los objetivos o resultados son generalmente la construcción de ejecutables; ficheros objeto; ficheros de recursos, o librerías de cualquier tipo, aunque un target puede ser también el nombre que se le da a una determinada acción. Por ejemplo, borrar ciertos ficheros; moverlos de sitio; etc. En este último caso se denominan objetivos adicionales o falsos ("phony targets").

Observe que, en determinados casos, los prerrequisitos (existencia de ciertos ficheros como condición previa para la construcción de otros) son también objetivos para Make, por lo que deben incluirse los comandos necesarios para su construcción [1], pero estos objetivos no siempre son necesarios. Si tales ficheros existen y están actualizados, no son considerados como "targets".  En este sentido, se denominan objetivos principales ("Goals") los que constituyen el fin último de Make.  Los demás objetivos son accesorios y no siempre necesarios.  Es evidente que en la invocación de Make se pretende siempre al menos un goal, aunque puede ser un phony goal.


§4  Makefiles

Make es en realidad un intérprete de comandos con su propio lenguaje de "scripts" [3], de forma que las instrucciones de operación se encuentran en un fichero de texto ASCII denominado makefile (puede tener distintas terminaciones, por ejemplo .mak). Un makefile contiene reglas que en cada caso indican al intérprete qué resultados ("goals") se pretenden; qué acciones son necesarias, y qué condiciones deben presentarse para que tales acciones sean  ejecutadas.  Make analiza este escript para determinar el orden en que deben ser construidos los distintos componentes. Como resultado se pueden obtener uno o varios ficheros (generalmente ejecutables) a los que denominamos fichero resultado. Por ejemplo, si un ejecutable se obtiene a partir de tres ficheros-objeto, estos deben ser construidos antes de ser entregados al enlazador para que forme con ellos un solo fichero.  Esto puede deducirlo Make automáticamente, sin necesidad de que coloquemos las instrucciones que generan los ficheros-objeto antes de las que generan el ejecutable. Los makefiles pueden incluso contener instrucciones para instalar el programa resultante en la máquina cliente.

Distribución e instalación de aplicaciones.  Generalmente las aplicaciones son distribuidas en forma de ficheros comprimidos que contienen los ejecutables y sus librerías. Por ejemplo, DLLs, así como los ficheros de datos; de ayuda; de configuración, etc. Generalmente un programa de instalación es el encargado de desempaquetar los módulos e instalarlos en los lugares adecuados de la máquina del cliente, y en caso necesario. Por ejemplo, en muchas aplicaciones Windows, incluir las entradas correspondientes en el Menú de Inicio, y realizar las modificaciones pertinentes en el Fichero de Registro. En ocasiones se incluye también un programa de desinstalación que, en su caso, además de deshacer los cambios efectuados en el fichero de registro, se encarga de borrar los ficheros y/o directorios instalados.

En el mundo Linux es muy frecuente que las aplicaciones sean distribuidas mediante los fuente, delegándose en el usuario la tarea de construir la aplicación e instalarla [6].  Los ficheros necesarios suelen venir también en formato comprimido, de los que existen varios tipos, aunque los más comunes son los conocidos tarball;  ficheros obtenidos con una utilidad (tar) que los empaqueta en un único fichero .tar. A continuación otra utilidad comprime el fichero resultante, con lo que se obtiene un fichero .tar.gz o algo similar.

Para facilitar la construcción, junto con los fuentes y cualquier fichero auxiliar necesario, suele incluirse un makefile.  Aunque también es frecuente que el makefile sea construido por el propio usuario en base un script de nombre configure, que puede ser ejecutado (mejor diríamos interpretado) por el shell del Sistema [7]. configure está diseñado de forma que genere un makefile adecuado a las características del sistema anfitrión, para lo que comprueba el tipo de procesador; si es de 32 o 64 bits. Etc. El sistema permite que la aplicación resultante pueda ser confeccionada "a medida" del anfitrión. Además, en ocasiones se permite que el usuario personalice algunas opciones de la aplicación según sus preferencias o necesidades. Por ejemplo, la localización preferida para los ficheros de la aplicación, selección del idioma, etc.


Los makefile son ficheros ASCII (texto plano) que contienen las instrucciones, que al ser interpretadas por make, permiten construir el proyecto. Esto supone que para cada fichero a construir, el makefile incluya información del siguiente tenor:

  • Objetivos: definen nombre y extensión del fichero que se desea construir. Tanto el principal ("goal") como los dependientes.
  • Camino ("path"): lista de directorios que informan al compilador donde encontrar los ficheros necesarios (librerías, includes, etc).
  • Dependencias: fichero/s cuyo "timestamp" (fecha y hora de creación) debe comprobarse para ver si son más modernos que el del fichero a construir, y en tal caso, proceder a la construcción o reconstrucción.
  • Comandos: órdenes que se trasladan directamente al "Shell" del SO o representan la invocación de determinadas utilidades (compilador, enlazador, etc). Son los responsables de alcanzar los objetivos propuestos.


Naturalmente, la redacción de las líneas de órdenes del makefile requiere el conocimiento del lenguaje "script" de la versión de Make utilizada, así como del compilador, enlazador, compilador de recursos y cualquier otra herramienta necesaria para construir el proyecto.  Recordemos que, tanto las peculiaridades de Make como del resto de herramientas, dependen de la plataforma (Windows, Unix, Linux, Solaris, etc.) y que las "suites" de programación C++ actuales permiten construir el makefile de un proyecto de forma automática [5].  Sin embargo, aun utilizando entornos de programación avanzados, no está de más un mínimo conocimiento de las peculiaridades de Make, ya que, a semejanza de lo que ocurre con la edición de páginas Web, donde de vez en cuando es necesario manejar directamente el código HTML, también aquí es a veces necesario hacer retoques manuales en el makefile o utilizar "includes" de nuestra propia cosecha. 

En general las líneas del fichero makefile son de cuatro tipos:

  • Comentarios:  Cualquier cosa que se ponga detrás del carácter almohadilla  #.  Por ejemplo:

    bcc32 hola.cpp      #  compilar el fichero hola.cpp

    #  la línea anterior produce un ejecutable

  • Reglas   Las hay de dos tipos:

    • Implícitas: se refieren a dependencias y comandos que atañen a la compilación o enlazado de ficheros específicos; vienen preconstruidas en la lógica interna de Make y no necesitan ser explicitadas por el programador .

    • Explícitas: instrucciones para la consecución de los objetivos redactadas por el programador .

  • Macros :  Se trata de expansiones/sustituciones parecidas a las del propio lenguaje C++ . Representan una comodidad sintáctica, y al igual que sus homónimas del C++, presentan la ventaja de poder agrupar determinadas definiciones en un solo punto, lo que hace más fácil el mantenimiento de los makefiles.

  • Directivas:  Recuerdan las de Pascal o las del propio lenguaje C++ .

§5  Reglas:

Como se ha señalado, en principio Make tiene sus propias opciones por defecto, pero el fichero de instrucciones makefile puede contener reglas específicas para cada caso.  Observe que aquí, "regla" puede tomarse en un sentido muy amplio; como una acción que ocurre solo bajo determinadas condiciones. Como se ha indicado, las reglas pueden ser de dos tipos: explícitas e implícitas , y se componen de dos o más líneas con la siguiente sintaxis:

resultado  : prerrequisitos ...  # línea de dependencias
    comando                      # línea de comando
    ...
    ...

  Las líneas de comando deben tener un sangrado (al menos un espacio) respecto a las líneas de dependencias correspondientes [2]. Por ejemplo, una regla podría tener el siguiente diseño:

fuente.exe: fuente.o             # Línea de dependencias

   bcc32 -P -RT fuente.cpp       # línea de comando (ojo al sangrado)

Aquí fuente.exe es el objetivo o resultado ("target") deseado y fuente.o es el prerrequisito.  La regla debe interpretarse como sigue: sifuente.exe no existe, o su timestamp es anterior al de fuente.o, o fuent.o no existe, ejecutar el comando indicado en la segunda línea (obtener el fuente.exe a partir de la compilación y enlazado de fuente.cpp).

También es destacable que una regla puede carecer de prerrequisitos, lo que ocurre cuando no se requieren ningunas condiciones especiales para obtener el resultado (ejecutar el/los comandos asociados).  Sería el siguiente caso:

cleanAll:
   rm -f pr1.o pr2.o pr3.o main.exe


Mientras que la línea de dependencias utiliza diversas sintaxis, según se trate de reglas explícitas o implícitas, la línea de comando siguen siempre la misma.

Make soporta múltiples líneas de dependencia para un solo resultado, y un resultado puede tener múltiples líneas de comando. Sin embargo, solo una línea de dependencia debe contener una línea de comando correspondiente. Por ejemplo:

Destino1: dependent1 dep2 dep3 dep4 dep5

Destino1: dep6 dep7 dep8

  bcc32 -c $**

Cualquier línea del makefile puede continuar en la línea siguiente utilizando la barra inclinada ( \ ) al final de la primera línea. Ejemplo:

MIFUENTE.EXE: FILE1.OBJ \      # Línea de dependencia

              FILE2.OBJ        # continuación de la anterior

  bcc32 file1.obj file2.obj    # Línea de comando

§5.1  Reglas explícitas

Las reglas explícitas son las instrucciones contenidas en el makefile para conseguir cierto resultado. Como se ha indicado, tienen al menos dos líneas, de dependencia y de comando. Su sintaxis es la siguiente:

resultado [resultado...]:[:][{path}] [prerrequisito[s]...]  # L. de dependencia
  [prefijo] comando                  # Línea de comando


Volvemos a insistir que la acción (indicada por el comando) no tiene que consistir necesariamente en construir un ejecutable, y que el resultado puede ser un simple nombre que identifica una serie de relaciones. Por ejemplo el fichero:

# Makefile para copiar ficheros .htm en el directorio de objetos

copiahtm : {D:\ZWeb\Zhome\Cpp\}E1.htm E2.htm                     
  &copy $** objetos                                              

sería un makefile correcto para Make 5.2 de Borland, que no tendría ninguna relación con la construcción de un ejecutable. Aquí el resultado copiahtm sería lo que hemos denominado un phony goal ; una etiqueta para designar las relaciones señaladas en el resto de la línea. El comando expande el contenido de la etiqueta (gracias al indicador &) como si fuese una macro, y aplica al resultado de la expansión, el comando copy del "Shell" del SO. Como es de prever, el resultado es que los ficheros E1.htm y E2.htm del directorio D:\ZWeb\Zhome\Cpp, son copiados al directorio objetos.


  Línea de dependencia:  se componen de uno o más nombres de fichero resultado seguidos de ( : ) o dos dobles puntos ( :: ). Un doble punto ( : ) significa que una regla se refiere al resultado/s; dos dobles puntos ( :: ) significan que dos o más reglas son para el/los resultado/s.

  resultado:  especifica el nombre del objetivo ("target") a conseguir. Generalmente es un nombre de fichero. Debe ser un comienzo de línea en el fichero makefile, y no puede ser precedido con espacios o tabulaciones. Para indicar más de un objetivo, deben separarse sus nombres con espacios o tabulaciones. No puede utilizarse un nombre de resultado más de una vez en una regla explícita.

  path:  una lista de direcciones que indican a make donde buscar los ficheros dependientes. Estas direcciones deben estar separadas por punto y coma (;), encerrando todo con corchetes { } (no dejar espacio entre el corchete de cierre y el primer fichero !!). Ejemplo :

copiahtm : {D:\ZWeb\Zhome\Cpp\}E1.htm E1_1.htm


  prerrequisito/s:  nombre de los objetivos que deben satisfacerse antes de ejecutar el comando señalado por la regla. Generalmente son nombres de ficheros cuyas fecha y hora deben ser comprobadas por make para comprobar si son más recientes que el fichero destino. Cada nombre debe ser precedido por un espacio. Si un fichero dependiente aparece en algún sitio como resultado, make actualiza o crea ese destino antes de utilizarlo como prerrequisito  en el resultado original.  Es lo que se conoce como dependencia encadenada.

  Línea de comando:  Puede ser  cualquier comando aceptado por el SO. Las líneas de comando deben ser sangradas (indentadas) al menos con un espacio o tabulación ( de lo contrario son interpretadas como líneas dependencias !!). Pueden indicarse múltiples órdenes separadas por espacios. La sintaxis geneal es:

 [prefijo] comando

Ejemplos:

 &copy $** objetos    # representa el sangrado inicial; & es el prefijo

 cd..

 bcc32 -c mysource.c

 COPY *.OBJ C:\PROJECTO

 bcc32 -c $(SOURCE)

 @bcc32 diff.obj

 -bcc32 hola.cpp


El prefijo es opcional, y depende de la implementación. comando puede ser prácticamente cualquiera aceptado por el SO, además, cada versión de Make acepta algunos comandos específicos.

§5.2  Reglas implícitas

Son reglas preconstruidas en la lógica interna de Make, que permiten construir los tipos de ficheros más usuales sin que el programador tanga que definir explícitamente los comandos necesarios.  Estas reglas suelen utilizar algunas macros también predefinidas (ver a continuación ).

Por ejemplo, el Make de GNU dispone de una regla implícita que le permite construir ficheros .o a partir de ficheros .cpp (fuentes C++):

%.o: %.cpp

    $(COMPILE.cpp) $(OUTPUT_OPTION) $<

a su vez, COMPILE.cpp y OUTPUT_OPTION son sendas macros también predefinidas:

COMPILE.cpp = $(COMPILE.cc)

COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

CXX = g++
OUTPUT_OPTION = -o $@

En consecuencia, la línea de comando de la regla resulta equivalente a

    g++ $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

Aquí g++ representa la invocación del compilador GNU C++ (g++.exe); a suv vez $@ y $< son variables automáticas de Make, cuyos valores se calculan cada vez que se ejecuta la regla en base al contenido de la línea de dependencias.  En concreto, $@ se traduce en el nombre del resultado (target) de la regla, mientras que  $< es el nombre del primer prerrequisito.  El resultado es que en esta regla implícita, pueden ser tomados como equivalentes al nombre del fichero objeto (target) y del fichero fuente (prerrequisito).  En consecuencia, la línea de comando anterior es equivalente a:

g++ $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o %.o %.cpp


La utilidad de todo esto puede entenderse si consideramos un ejemplo.  Supongamos que compilamos un proyecto que depende de dos fuentes: main.cpp y modulo.cpp.  La construcción del proyecto puede ser encomendada a un makefile adecuado a nuestro entorno:

INCLUDES = -I"C:/GNU/include" \
           -I"C:/GNU/include/c++"
 
main.exe: main.o modulo.o
   g++ main.o modulo.o -o "main.exe" -L"C:/GNU/lib"

main.o: main.cpp
   g++ -c main.cpp -o main.o $(INCLUDES)

modulo.o: modulo.cpp
   g++ -c modulo.cpp -o zstring.o $(INCLUDES)

El makefile anterior, aunque correcto, puede ser simplificado con mínimas modificaciones hasta quedar con el siguiente diseño:

CXXFLAGS = -I"C:/GNU/include" \
           -I"C:/GNU/include/c++"
 
main.exe: main.o modulo.o
   g++ main.o modulo.o -o "main.exe" -L"C:/GNU/lib"

main.o: main.cpp
modulo.o: modulo.cpp

La razón es que, ante reglas como las de las dos últimas líneas, para las que no existe ninguna línea de comando que informe sobre la forma de obtener los ficheros .o a partir de los fuente .cpp, Make hecha mano de sus reglas implícitas para ver si existe una forma de hacerlo. La regla comentada al principio del epígrafe es la adecuada, así que es aplicada con las sustituciones pertinentes en cada instancia, y el proyecto es construido sin dificultad.  Observe que ha sido necesario renombrar la macro original INCLUDES, que ahora pasa a denominarse CXXFLAGS. La razón es la ya comentada de que Make utiliza ciertos nombres en sus reglas implícitas; nombres que deben ser respetados si queremos aprovechar las posibilidades de este tipo de reglas.

Como última observación, señalar que el proyecto podría ser construido con un makefile aún más simple:

CXXFLAGS = -I"C:/GNU/include" \
           -I"C:/GNU/include/c++"
 
main.exe: main.o modulo.o
   g++ main.o modulo.o -o "main.exe" -L"C:/GNU/lib"

La razón es que, aunque en este caso no se indica que main.o o modulo.o deban ser obtenidos de la compilación de sus homónimos .cpp, Make dispone de reglas implícitas para intentar la compilación en caso de que tales ficheros existan.  La forma de hacerlo es análoga a la comentada.

§6  Macros

El lenguaje "script" de make permite el uso de macros parecidas (aunque diferentes) a las del lenguaje C++ ( 4.9.10b ). Una macro tiene dos partes: La etiqueta identificativa y el texto en que se expandirá.  El proceso de usar la macro tiene dos pasos: definir la macro y señalar el punto de expansión (donde de realiza la sustitución de la macro -la etiqueta- por el texto).

La definición se realiza colocando una etiqueta (nombre de la macro); el símbolo de asignación ( = ) y el texto. Como en el caso de C++, suelen utilizarse mayúsculas en las etiquetas. La etiqueta debe aparecer en el primer carácter de la línea (sin espacio o sangrado previo de ningún tipo). Por ejemplo:

COMPILA = bcc32 -MProyecto.map             # define la macro COMPILA

El sitio de la sustitución se señala encerrando en un paréntesis la etiqueta de la macro a expandir precedido del símbolo $.  Por ejemplo, la línea de comando:

$(COMPILA) -eProyecto.exe pa.obj pb.obj    # Expande la macro COMPILA

será expandida a:

bcc32 -MProyecto.map -eProyecto.exe pa.obj pb.obj


Recordar que, como todo en el lenguaje C++, la etiqueta de la macro es sensible a mayúscula/minúscula; COMPILA y Compila serían macros distintas. El texto en que se expandirá la macro puede contener hasta 4096 caracteres (Borland C++ make), cada macro se define en una línea diferente, aunque una macro puede contener más de una línea separadas por la barra inclinada "\". Si existen dos macros distintas con la misma etiqueta, la segunda sobreescribe a la primera definición.

§6.1  Macros predefinidas

Es digno de mención que Make tiene dispone de gran número de macros predefinidas (dependen de la implementación) que pueden ser aprovechadas para simplificar la redacción del texto y para las reglas implícitas.  Si una línea del makefile redefine alguna de estas macros predefinidas, es válida la regla del párrafo anterior (la versión del makefile sobreescribe a la definición inicial).

Por ejemplo, la versión GNU de Make dispone de la siguiente macro predefinida:

RM = rm -f

en consecuencia, en dicha versión de Make es posible utilizar la siguiente regla sin necesidad de definir previamente la macro:

clean:
   ${RM} objeto.o cabecera.h ejecutable.exe

§7  Directivas

Los scripts de make permiten incluso directivas del tipo include, undef, ifdef, if, elif etc. que recuerdan las del propio lenguaje C++ ( 4.9.10).

§8  Invocación

La sintaxis para invocar make depende de la plataforma, pero generalmente es del tipo:

make  [opciones]  [resultado [resultado]]

Las opciones son indicaciones que controlan aspectos del funcionamiento de Make.  Generalmente puede obtenerse una lista de ellas y su significado invocando make con el argumento -? o -h.  La palabra "make", las opciones y los resultados deben estar separados por espacios.

resultado es el nombre del fichero/s que se pretende construir. Este nombre se ha indicado también en makefile (el fichero que lee Make para extraer las instrucciones).


En principio Make tiene sus propias opciones de operación por defecto, pero la invocación puede imponer sus propias reglas.  Por ejemplo, en ausencia del argumento resultado en la invocación, Make establece que el goal es el primer resultado que aparece reseñado en el makefile, pero invocándolo con un argumento puede establecerse que el goal sea otro.  Por ejemplo, teniendo el makefile mk1.txt:

# Malefile mk1.txt

all:  main.exe

clean:
    erase main.o
    erase main.h

    erase main.exe

main.exe: main.o  main.h
    g++.exe main.o -o "main.exe" -L"C:/DEV-CPP/lib"

La invocación

make -f mk1.txt

obliga a Make a utilizar el fichero mk1.txt en lugar del makefile establecido por defecto (por ejemplo make.mak para Borland 5).  En este caso, el goal es all, el primer resultado que se encuentra, lo que conduce a que se ejecute la regla de las líneas 10-11, con el resultado de que se enlaza el fichero main.o para obtener el ejecutable main.exe.  En cambio, la invocación

make

utilizaría el makefile por defecto.  En caso de que utilizáramos Borland 5 y que no existiera el fichero make.mak, se obtendría un error.  Finalmente, la invocación

make -f mk1.txt clean

señala a Make que se pretende el resultado señalado por la regla clean; lo que conduce a que se ejecuten los comandos de las líneas 6 a 8, sin que tenga lugar ninguna otra acción.

§9  Secuencia de ejecución

Después de cargar el makefile, make trata de construir solamente el primer resultado explícito incluido en el makefile.  Para esto comprueba la fecha y hora de los ficheros dependientes del primer resultado ("goal"). Si son más recientes que las del fichero resultado, make ejecuta el comando asociado para actualizarlo.

Si alguno de los ficheros dependientes del primer resultado es utilizado a su vez como resultado en algún otro punto del makefile,  make comprueba dichas dependencias y construye ese resultado antes que construir el primero. Este comportamiento se denomina dependencia encadenada.

Ejemplo:

ejecutable.exe : objeto.o    # L.1: línea de dependencias

   bcc32 objeto.o            # L.2: línea de comando

...

objeto.o :  cabecera.h       # L.3: línea de dependencias

   trigraph cabecera.h       # L.4: línea de comando

En este makefile, el goal es ejecutable.exe; se supone que este fichero se construye con el comando señalado en L.2, y está relacionado con objeto.o.  Si la fecha y hora de objet.o son posteriores a las de ejecutable.exe (o sencillamente ejecutable.exe no existe), se ejecuta el comando. No obstante, como el prerrequisito objeto.o  aparece a su vez como resultado de la regla L.3,  deben satisfacerse primero las condiciones de este fichero, para lo que se invoca el comando señalado en L.4 (si objeto.o no existe o su "timestamp" es anterior que el de cabecera.h). Por último se ejecutaría L.2.

Si se produce algún fallo durante el proceso, make borra los ficheros que estuviese construyendo. Si desea que make conserve el fichero destino a pesar de un posible fallo, debe utilizarse la directiva .precious. Los comandos Ctrl+Break o Ctrl+C permiten abortar el trabajo de make una vez iniciado.

Nota: la utilidad Make es más potente y compleja de lo que se ha señalado someramente en este capítulo [4], en especial las opciones relativas a macros y las directivas de proceso. No pretendemos aquí reproducir el manual del Make de Borland, de GNU, ni de ningún otro compilador específico, solo indicar las líneas generales de lo que puede esperarse de esta utilidad y su filosofía de funcionamiento. No obstante, esta introducción le permitirá comenzar practicar con sus propios makefiles, entender mejor los capítulos que siguen dedicados a versiones específicas de Make, e ir explorando sus posibilidades con ayuda del manual de su compilador.

  Inicio.


[1]  Más adelante, al tratar de las reglas implícitas, veremos que esta especificación no siempre es necesaria, ya que Make dispone de determinadas reglas implícitas que le permiten conocer como construir los ficheros de tipo más corriente.  Por ejemplo, como obtener un fichero .o a partir de uno .cc.

[2]  La versión GNU de Make necesita una tabulación (TAB) como primer carácter.  Si se utilizan solo uno o dos espacios, interpreta que son destinos, mostrándose un mensaje de error:

makefile....:nn: *** multiple target patterns. Stop.

En cambio a la de Borland C++ 5.5 le basta con un espacio.  Esta desafortunada circunstancia me hizo perder toda una tarde con un makefile sencillísimo hasta que finalmente, cuando estaba apunto de marcharme a limpiar cochineras, por pura casualidad pude percatarme de la diferencia.

[3]  Se denominan "Scripts" un tipo de de ficheros, cuyo texto son instrucciones que son leídas por un intérprete (que utiliza un lenguaje de comandos en formato texto). Existen "scripts" para Java (Jscript);  JavaScript (incluido en los navegadores);  Visual Basic;  Access;  make, etc. En este sentido, un fichero .BAT de MS-DOS es un "script" para command.com.

[4]  Últimamente está cobrando importancia una utilidad derivada de Make que es particularmente potente y sofisticada. Es bjam ( Boost.Jam), la utilidad utilizada para construir las librerías Boost.  Es también un intérprete de scripts que utiliza un lenguaje específico y sus propios ficheros de órdenes (jamfiles).  Es propiedad intelectual (copyright) de Christopher Seiwald y Perforce Software, aunque la versión utilizada por Boost es muy generosa en cuanto a sus posibilidades: "License is hereby granted to use this software and distribute it freely, as long as this copyright notice is retained and modifications are clearly marked".

[5]  En estos casos el makefile se suele denominar fichero de proyecto ("Proyect file") o un nombre similar, y el proceso de construcción de las dependencias está más o menos velado al usuario, pero la base de funcionamiento es la que aquí se analiza.

[6]  Naturalmente, lo anterior exige que el usuario disponga del compilador adecuado, lo que es relativamente frecuente en este tipo de Sistemas. Además se tiene la comodidad de que el compilador GNU GCC permite compilar indistintamente C y C++.

[7]  Generalmente configure es un script, es bastante complejo, pero se dispone de una utilidad, autoconf, que permite generarlo a partir de un fichero de especificación (template-file) más sencillo.