[C++ Avanzado] Fundamentos de Makefile (1)

Makefile es en realidad solo un archivo de comando que le indica al programa make (en lo sucesivo denominado make o, a veces, comando make) cómo trabajar para nosotros. Cuando decimos que Makefile en realidad está hablando de make, debemos tener una comprensión clara de esto. Para nuestro proyecto, Makefile se refiere al entorno de compilación del proyecto de software. El contenido de trabajo más común en la fase de codificación del desarrollo de productos de software es aproximadamente:

  • Código de los desarrolladores basado en el diseño del esquema
  • El desarrollador compila el código fuente diseñado para producir un ejecutable
  • Los desarrolladores prueban los productos de software para verificar la corrección de su funcionalidad

Los tres pasos anteriores son un proceso iterativo. Si finalmente se verifica que el diseño cumple con los requisitos, entonces se completa el desarrollo de la fase de codificación. De lo contrario, estos tres pasos deben repetirse hasta que se cumplan los requisitos de diseño.

Entre los pasos anteriores, el segundo paso es el más relacionado con el Makefile, entonces, ¿qué influencia tiene la calidad del Makefile en el desarrollo del proyecto? Con un Makefile bien diseñado, cuando recompilamos, solo necesitamos compilar aquellos archivos que han sido modificados desde la última compilación exitosa, es decir, se compila un delta en lugar de todo el proyecto. Por el contrario, si hay un mal entorno Makefile, puede ser necesario limpiar para cada compilación y luego volver a compilar todo el proyecto. La diferencia entre los dos casos es obvia, el último consumirá mucho tiempo para que los desarrolladores compilen, lo que significa una baja eficiencia. Para proyectos pequeños, la ineficiencia puede no ser obvia, pero para proyectos relativamente grandes, es muy obvia. Los desarrolladores pueden hacer diez compilaciones al día (o incluso menos) y no tener tiempo para codificar y probar (depurar). Es por eso que, por lo general, un proyecto grande tendrá un pequeño equipo dedicado a mantener el Makefile
para respaldar el desarrollo del producto.

Lo más importante es dominar dos conceptos, uno es target y el otro es dependencia. El objetivo se refiere a qué hacer, o lo que se genera después de ejecutar make, y la dependencia le dice a make cómo hacerlo para lograr el objetivo. En un Makefile, los objetivos y las dependencias se expresan a través de reglas. Con lo que estamos más familiarizados es con el uso de make para compilar el código de los productos de software, pero se puede usar para hacer muchas cosas, y daremos algunos ejemplos de cómo no usar make para compilar el código más adelante. Para controlar Makefile, lo más importante es aprender a usar metas y dependencias para pensar en los problemas a resolver.


inserte la descripción de la imagen aquí

  • Destinos: los destinos en Makefile se refieren a los archivos que deben generarse o las operaciones que deben realizarse. Un objetivo puede ser un archivo, un comando o una secuencia de operaciones.
  • Dependencias: Las dependencias en el Makefile se refieren a los archivos o comandos de los que depende el objetivo. Si se modifican los archivos dependientes, también es necesario regenerar el destino.
  • Comandos: Los comandos en el Makefile se refieren a la secuencia de operaciones que deben realizarse para generar el destino. Estas operaciones pueden ser de compilación, enlace, copia, empaquetado, etc. Los comandos deben comenzar con un tabulador o varios espacios; de lo contrario, se tratarán como comentarios.

inserte la descripción de la imagen aquí
Makefile es un archivo de texto que contiene reglas e instrucciones que describen cómo compilar y vincular uno o más archivos de código fuente para generar programas ejecutables o archivos de biblioteca.

El Makefile funciona de la siguiente manera:

  1. Los archivos MAKE definen archivos de destino, dependencias y comandos. Los archivos de objetos suelen ser programas ejecutables o archivos de biblioteca, los archivos dependientes son archivos de código fuente, archivos de encabezado u otras dependencias, y los comandos son las operaciones de compilación, vinculación y generación de archivos de objetos.

  2. Cuando se ejecuta el comando make, se analizarán las reglas en el Makefile y se generará un gráfico de dependencia de acuerdo con las dependencias para determinar qué archivos deben volver a compilarse.

  3. El programa Make realiza recursivamente las operaciones de compilación, vinculación y generación de archivos de objetos de acuerdo con el gráfico y las reglas de dependencia, lo que garantiza que todas las dependencias se compilan y vinculan para generar el archivo de objeto final.

  4. Si algunas dependencias no han cambiado, no hay necesidad de volver a compilar y vincular, lo que mejora la eficiencia de la compilación.

  5. Makefile también admite funciones avanzadas como variables, declaraciones condicionales y declaraciones de bucle, y se puede compilar y vincular de acuerdo con diferentes condiciones para generar diferentes archivos de destino.

1. Medio ambiente

Los requisitos del entorno para usar el archivo MAKE son los siguientes:

  1. Sistema operativo: los archivos MAKE se pueden usar en la mayoría de los sistemas operativos, incluidos Linux, Unix, Mac OS X, Windows, etc.
  2. Compilador: makefile requiere un compilador que admita la sintaxis de GNU make, como GNU make, BSD make, etc.
  3. Archivos de objetos: los archivos MAKE requieren archivos de código fuente compilables, como C, C ++, Java, etc.
  4. Variables de entorno: Makefile necesita algunas variables de entorno para especificar el compilador, las opciones de compilación, etc., como CC, CFLAGS, LDFLAGS, etc.
  5. Editor: Makefile necesita un editor para escribir y editar archivos MAKE, como Vim, Emacs, etc.
  6. herramienta make: makefile requiere una herramienta make para ejecutar el archivo make, como GNU make.

Pasos para el uso:

  1. Instale la herramienta GNU Make: Make es una herramienta de línea de comandos que se utiliza para automatizar el proceso de creación de software. Puede descargar e instalar la herramienta Make desde el sitio web oficial de GNU.
  2. Crear un Makefile: Un Makefile es un archivo de texto que contiene una serie de reglas e instrucciones que describen cómo crear software. Se puede crear un archivo denominado Makefile en el directorio raíz del proyecto.
  3. Escribir reglas de Makefile: las reglas de Makefile constan de destinos, dependencias y comandos. El objetivo se refiere al archivo que se generará o la operación que se realizará; la dependencia se refiere al requisito previo para generar el objetivo; el comando se refiere a la operación específica de generar el objetivo.
  4. Ejecute el comando Make: ingrese el directorio raíz del proyecto en la línea de comando, ingrese el comando make para ejecutar las reglas definidas en el Makefile, generar archivos de destino o realizar operaciones.

Ingrese la línea de comando make -v, si aparece la información de la versión similar a la figura a continuación, significa que make ya está disponible en su entorno:
inserte la descripción de la imagen aquí

Precauciones:

  1. Al escribir Makefile, las variables y funciones deben usarse tanto como sea posible para mejorar la legibilidad y la capacidad de mantenimiento del código.
  2. Los comandos en un Makefile deben comenzar con la tecla Tabulador, no con una barra espaciadora.
  3. Las dependencias en un Makefile deben ser lo más explícitas posible para determinar correctamente qué reglas deben ejecutarse.
  4. Antes de ejecutar el comando Make, debe asegurarse de que todos los archivos dependientes ya existan; de lo contrario, la compilación fallará.

2. Reglas

Usamos Hello World para comenzar a aprender las reglas de Makefile, escribimos un Makefile de la siguiente manera, y el directorio de almacenamiento del archivo puede ser arbitrario:

all:
	echo "Hello World"

inserte la descripción de la imagen aquí

Cabe señalar que solo debe haber TAB delante de echo , y debe haber al menos un TAB, y no se pueden usar espacios en su lugar.

El primer concepto muy importante en Makefile es el objetivo. Todo en el código anterior es nuestro objetivo. El objetivo se coloca delante de: y su nombre puede estar compuesto de letras y guiones bajos. echo “Hello World”Es el comando para generar el destino. Estos comandos pueden ser cualquier comando que se ejecute en su entorno y funciones definidas por make, etc. Aquí, echo es un comando en BASH Shell, y su función es imprimir cadenas en la terminal. El objetivo de todo aquí es imprimir "Hello World" en la terminal, y en ocasiones el objetivo será un concepto más abstracto. La definición de todos los objetivos en realidad define cómo generar todos los objetivos, lo que se denomina regla, es decir, el Makefile anterior define una regla para generar todos los objetivos.

El siguiente ejemplo muestra tres formas diferentes de correr y los resultados de cada forma:

  • La primera forma: siempre que ejecute makeel comando en el directorio donde se encuentra el Makefile, se mostrarán dos líneas en el terminal, la primera línea es en realidad el comando que escribimos en el Makefile, y la segunda línea es el resultado de ejecutando el comando
  • La segunda forma: ejecute make allel comando, que le dice a la herramienta de creación que quiero generar el objetivo todo, y el resultado es el mismo que la primera forma
  • La tercera forma: ejecutar make test, instruye a make para generar el objetivo de prueba para nosotros. Dado que no definimos el objetivo de prueba en absoluto, el resultado de la ejecución es predecible, make informa que no se puede encontrar el objetivo de prueba

inserte la descripción de la imagen aquí
Ahora haga un pequeño cambio en el Makefile anterior, como se muestra a continuación, agregando la regla de prueba para construir el objetivo de prueba, para imprimir "¡Solo para prueba!" en la terminal:

all:
	echo "Hello World"
test:
	echo "Just for test!"

inserte la descripción de la imagen aquí
De la salida anterior podemos encontrar:

  • Se pueden definir múltiples objetivos en un Makefile
  • Al llamar makea un comando, tenemos que decirle cuál es nuestro objetivo, es decir, qué queremos que haga. Cuando no se especifica un objetivo específico, make usa el primer objetivo definido en el Makefile como el objetivo para esta ejecución. Este primer objetivo también se denomina objetivo predeterminado (no tiene nada que ver con si es todo)
  • Cuando make obtiene el objetivo, primero encuentra las reglas que definen el objetivo y luego ejecuta los comandos en las reglas para lograr el propósito de construir el objetivo. En el Makefile que se muestra ahora, solo hay un comando en cada regla, pero en el Makefile real, cada regla puede contener muchos comandos.

Al igual que en el ejemplo anterior, cuando se ejecuta make, los comandos del Makefile también se imprimen en el terminal. A veces, esto no es deseable, ya que puede hacer que la salida parezca un poco confusa. Para hacer que makeel comando no se imprima, solo haga una pequeña modificación, el Makefile modificado es el siguiente, es decir, agregue un antes del comando @. Este símbolo le indica makeque no muestre esta línea de comando en tiempo de ejecución:

all:
	@echo "Hello World"
test:
	@echo "Just for test!

inserte la descripción de la imagen aquí
Realice un pequeño cambio en el código anterior, :agregue el objetivo de prueba después de todos los objetivos, como se muestra a continuación

all: test
        @echo "Hello World"
test:
        @echo "Just for test!"

inserte la descripción de la imagen aquí


Expliquemos las dependencias en el Makefile

En el código anterior, la prueba después de todos los objetivos le dice a make que todos los objetivos dependen del objetivo de prueba, y este objetivo dependiente también se denomina requisito previo en el Makefile. Cuando se produce una dependencia de destino de este tipo, la herramienta de creación primero creará cada destino del que depende la regla en orden de izquierda a derecha. Si desea construir el objetivo completo, entonces make tendrá que construir el objetivo de prueba antes de construirlo, razón por la cual se llama un requisito previo. El siguiente diagrama de clases expresa las dependencias de todos los destinos:

inserte la descripción de la imagen aquí

Hasta ahora, entendemos las reglas en el Makefile, el siguiente es el texto y UML de las reglas. Una regla se compone de objetivos, requisitos previos y comandos. Cabe señalar que la expresión entre el objetivo y el prerrequisito es la dependencia (dependency), la cual indica que el prerrequisito debe ser satisfecho (o construido) antes de que se construya el objetivo, el prerrequisito pueden ser otros objetivos, cuando un prerrequisito es un objetivo, primero debe construirse.

targets : prerequisites
	command

inserte la descripción de la imagen aquí
Puede haber varios objetivos en una regla. Cuando hay varios objetivos y esta regla es la primera regla en el Makefile, si ejecutamos el comando make sin ningún objetivo, el primer objetivo de la regla se considerará como el objetivo predeterminado, como sigue:

all test:
	@echo "Hello World"

inserte la descripción de la imagen aquí
El diagrama de actividad de hacer que el procesamiento sea una regla se muestra en la figura a continuación. La actividad de crear objetivos dependientes (tenga en cuenta que es una actividad, no una acción) es repetir la misma actividad como se muestra en la figura a continuación. Se puede ver como una llamada recursiva al diagrama de actividad a continuación. El comando ejecutar para construir el destino (ejecutar comando para construir el destino) es una acción, que es una acción compuesta de comandos. La diferencia entre una actividad y una acción es que una acción solo hace una cosa (pero puede tener varios comandos), mientras que una actividad puede incluir varias acciones.
inserte la descripción de la imagen aquí

3. Principio

A continuación, tratamos de aplicar las reglas a la compilación del programa. A continuación, asumimos que hay dos archivos de programa fuente que se usan para crear un archivo ejecutable simple. Necesitamos escribir un archivo Makefile para crear un programa ejecutable simple. ¿Cómo se debe compilar este archivo Makefile? ¿Escribir?
foo.c

#include <stdio.h>
void foo ()
{
    
    
	printf ("This is foo()\n");
}

C Principal

extern void foo();
int main ()
{
    
    
	foo();
	
	return 0;
}

El primer paso para escribir un Makefile no es saltar e intentar escribir una regla, sino usar un método orientado a las dependencias para descubrir qué tipo de dependencias necesita expresar el Makefile que se va a escribir.Esto es muy importante. A través de la práctica continua, finalmente podemos lograr un uso natural de las dependencias para pensar en los problemas. En ese momento, cuando vuelvas a escribir Makefile, tu mente estará muy clara sobre lo que estás escribiendo y lo que escribirás después. Ahora deje a un lado el Makefile, echemos un vistazo a cuáles son las dependencias del programa simple.

El primer gráfico de dependencia que me viene a la mente, donde el ejecutable simple obviamente se produce al compilar y vincular main.c y foo.c al final. A través de este gráfico de dependencia, se puede escribir un Makefile. El Makefile escrito por tales dependencias no es muy factible en la realidad, es decir, hay que poner todos los programas fuente en una sola línea y dejar que GCC los compile para nosotros: la siguiente figura es una expresión más precisa de las dependencias del programa simple
inserte la descripción de la imagen aquí
.Que agregó el archivo de objeto. Para un programa ejecutable simple, la siguiente figura muestra su "árbol de dependencia". Lo siguiente que hay que hacer es expresar cada una de las dependencias, es decir, cada una de las líneas punteadas con flechas, con las reglas en el Makefile:
inserte la descripción de la imagen aquí

all: main.o foo.o
        gcc main.o foo.o -o simple
main.o: main.c
        gcc main.c -c
foo.o: foo.c
        gcc foo.c -c

.PHONY:clean
clean:
        rm -f main.o foo.o simple

En este Makefile, también agregué un pseudoobjetivo para eliminar archivos generados, incluidos archivos de objetos y programas ejecutables simples, que son muy comunes en proyectos reales.
inserte la descripción de la imagen aquí
¿Qué pasa si recompilamos sin cambiar el código? La siguiente figura muestra los resultados. Observe que la segunda compilación no tiene la acción de construir el archivo de destino, pero ¿por qué hay una acción de construir el programa ejecutable simple?
inserte la descripción de la imagen aquí
Makefile juzgará si el archivo debe reconstruirse de acuerdo con la marca de tiempo del archivo (es decir, la hora de la última modificación). Si un archivo tiene una marca de tiempo más antigua que los archivos que dependen de él, es necesario reconstruir el archivo. Por lo tanto, si ejecuta el comando make varias veces, incluso si los archivos de origen y encabezado no han cambiado, la marca de tiempo del ejecutable se actualizará, lo que provocará una reconstrucción. Si desea evitar esto, puede usar la función de compilación incremental de make, que solo reconstruye los archivos necesarios.

Verifiquemos que si hacemos un cambio en foo.c, se reconstruirá. Para la herramienta de creación, si un archivo se cambia no se basa en el tamaño del archivo, sino en su marca de tiempo. En Linux, solo necesita usar el comando táctil para cambiar la marca de tiempo del archivo, lo que equivale a simular una edición del archivo sin editarlo realmente.Como se muestra en la figura, make encuentra el cambio de foo.c, y lo recopilé:
inserte la descripción de la imagen aquí

4. Objetivos falsos

En Makefile, un pseudoobjetivo es un objetivo especial que no representa un archivo real, pero se usa para completar una tarea específica u organizar la secuencia de ejecución de otros objetivos.

Supongamos que tenemos un proyecto en lenguaje C, que incluye los siguientes archivos: main.c, foo.c, bar.c, foo.h, bar.h. Necesitamos compilar este proyecto para producir un ejecutable my_program. Un Makefile simple podría verse así:

my_program: main.o foo.o bar.o
	gcc -Wall -g -o my_program main.o foo.o bar.o

main.o: main.c foo.h bar.h
	gcc -Wall -g -c main.c

foo.o: foo.c foo.h
	gcc -Wall -g -c foo.c

bar.o: bar.c bar.h
	gcc -Wall -g -c bar.c

clean:
	rm -f *.o my_program

En este Makefile, tenemos un cleanobjetivo llamado . No depende de otros objetivos ni representa un archivo real. Su función es eliminar todos los archivos intermedios ( .oarchivos) y los archivos ejecutables generados ( my_program), que es un pseudo-objetivo típico.

Principales características y usos de los blancos falsos:

  • No representa un archivo real: el pseudoobjetivo no corresponde a ningún archivo real, existe solo para completar una tarea específica
  • Evite las colisiones de nombres: dado que los pseudo-objetivos no representan archivos reales, podemos evitar errores causados ​​por nombres de archivo y de destino que son iguales.
  • Organizar mejor los Makefiles: con pseudo objetivos, podemos separar diferentes tareas y operaciones, haciendo que los Makefiles sean más claros y fáciles de leer.
  • Cumplimiento: mediante el uso de pseudoobjetivos, podemos aplicar una determinada tarea independientemente de si el archivo existe o se ha actualizado.

En el Makefile, podemos usar `.PHONY`` para declarar un pseudoobjetivo para decirle explícitamente a make que el objetivo no es un archivo real. Por ejemplo, podríamos agregar la siguiente declaración al ejemplo anterior:

.PHONY: clean

La ventaja de esto es que incluso si hay un archivo llamado make en el directorio actual clean, make sabrá que cleanes un pseudoobjetivo y no un archivo real.

Por supuesto, además de los pseudo-objetivos limpios mencionados anteriormente, existen otros pseudo-objetivos comunes. Los siguientes son algunos pseudo-objetivos que se usan a menudo en Makefiles:

  1. all: este pseudoobjetivo se suele utilizar para compilar todo el proyecto. Cuando el usuario ejecuta make o make all, compilará y generará automáticamente todos los objetivos requeridos.
    .PHONY: all
    all: my_program
    
  2. install: este pseudodestino se utiliza para instalar el programa compilado en el directorio especificado por el sistema. Por lo general, esto requiere privilegios de administrador, ya que implica crear o modificar archivos en los directorios del sistema.
    .PHONY: install
    install: my_program
    	cp my_program /usr/local/bin
    
  3. uninstall: este pseudo destino se utiliza para eliminar los programas instalados del sistema. Al igual que la instalación, generalmente requiere privilegios de administrador.
    .PHONY: uninstall
    uninstall:
    	rm -f /usr/local/bin/my_program
    
  4. test: este pseudoobjetivo se utiliza para ejecutar los casos de prueba del proyecto, lo que garantiza que varias partes del proyecto funcionen correctamente.
    .PHONY: test
    test: my_program
    	./test_script.sh
    
  5. help: este pseudoobjetivo se utiliza para mostrar las instrucciones del Makefile para ayudar a los usuarios a comprender cómo utilizar el Makefile.
    .PHONY: help
    help:
    	@echo "Usage:"
    	@echo "  make all      - Compile the project"
    	@echo "  make clean    - Remove compiled files and binaries"
    	@echo "  make install  - Install the program"
    	@echo "  make test     - Run tests"
    

Supongo que te gusta

Origin blog.csdn.net/weixin_52665939/article/details/130131272
Recomendado
Clasificación