Domine fácilmente Makefile con la ayuda de ejemplos: florecer y dar frutos



flor y fruto

inserte la descripción de la imagen aquí

Para practicar la profundización de la aplicación de Makefile a través de un proyecto relativamente complejo, llamaremos al proyecto enorme (big guy);

El diagrama de estructura de directorios del proyecto es el siguiente:
inserte la descripción de la imagen aquí

Ejemplo 1: Makefile bajo source/foo/src


editar fuente/foo/src/Makefile

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = ../../../build/exes/
DIR_DEPS = deps
DIR_LIBS = ../../../build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)

EXE = zoo
ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif

LIB = libfoo.a


ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        @set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(DIRS) $(RMS)


Compilar y ejecutar

$ make
$ ls
$ ls ../../../bulild/libs/
$ ls ../../../build/exes/
$ ls deps/
$ ls objs/
$ make clean


salida de resultados

inserte la descripción de la imagen aquí


Gramática Descripción

  • El propósito de las dos variables AR y ARFLAGS es generar una biblioteca estática;
  • IDS_EXES indica la ubicación real del directorio exes y ahora usa una ruta relativa;
  • La variable DIRS aumenta el contenido de la variable DIR_LIBS para que el directorio build/libs se genere antes de que se genere el archivo de la biblioteca;
  • Las variables RMS se utilizan para representar directorios y archivos que se eliminarán;
  • El Makefile es solo para compilar la biblioteca libfoo.a, por lo que make clean no puede eliminar todos los directorios exes y libs ubicados en el directorio de compilación;
  • La instrucción condicional ifneq se usa para determinar si la variable EXE está definida, porque luego, al establecer la dependencia de todos los objetivos, es necesario determinar si la variable EXE tiene un valor. Si no hay valor, no necesitamos hacer todos los objetivos dependen de $(EXE).Agregue un prefijo para llamar a la función addprefix, de lo contrario romperá el método de juzgar si la variable EXE tiene un valor más tarde, para decidir si hacer que todo el directorio dependa de él;
  • Si EXE tiene un valor, su valor debe agregarse a la variable RMS para que podamos borrarlo cuando llamemos a make clean;
  • La variable LIB se usa para almacenar el nombre de la biblioteca, por ejemplo, el nombre de la biblioteca aquí es libfoo.a;
  • Agregue una regla para construir que llame al comando ar con el argumento crs para generar la biblioteca.
  • En el comando de destino limpio, el contenido de la variable RMS se elimina en lugar del contenido de la variable DIRS. Porque no queremos eliminar los directorios libs y exes en el directorio bulid cuando se limpia en el módulo foo.


Ejemplo 2: uso de make.rule para mejorar la reutilización


Divida el Makefile en el módulo foo del Ejemplo 1 en dos partes: make.rule en el directorio de compilación y Makefile en el directorio source/foo/src, y expanda el Makefile en el directorio huge/src.


1. Edite build/make.rule

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)

ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif


ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        @set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(DIRS) $(RMS)


2. Editar fuente/foo/src/Makefile

EXE = 
LIB = libfoo.a
include $(ROOT)/build/make.rule


Asigne un valor a la variable ROOT en el directorio enorme/:

$ export ROOT=`pwd`


Ingrese enorme/fuente/foo/src para compilar y ejecutar

$ make
$ ls
$ ls ../../../bulild/
$ ls ../../../build/libs/
$ ls deps/
$ ls objs/
$ make clean


salida de resultados

inserte la descripción de la imagen aquí


3. Cree main.c en el directorio source/huge/src/

int main()
{
    
    
	return 0;
}


Editar fuente/enorme/src/Makefile

EXE = huge.exe
LIB = 
include $(ROOT)/build/make.rule


Ingrese source/huge/src para compilar y ejecutar

$ make
$ ls
$ ls deps/
$ ls objs/
$ ls ../../../build/exes/
$ make clean


salida de resultados

inserte la descripción de la imagen aquí


Gramática Descripción

  • A través del archivo make.rule en el directorio de compilación, todos los Makefiles ubicados en el directorio src de cada módulo de software se pueden usar para mejorar la reutilización;
  • Poniendo parte del Makefile del módulo foo en make.rule;
  • Las definiciones de las variables EXE y LIB son diferentes para cada módulo de software, por ejemplo, en este proyecto, es necesario establecer el valor de la variable LIB en libfoo.a en el Makefile en el directorio source/foo.src, y la variable EXE está vacío y en el Makefile en el directorio source/huge/src, solo defina el valor de la variable EXE como huge.exe;
  • Para que la variable DIR_EXES y la variable DIR_LIBS sean iguales para todos los módulos, se puede realizar utilizando la ruta absoluta; este proyecto se realiza definiendo la variable de entorno ROOT (cabe señalar que al exportar la variable ROOT requerida, en Además de ingresar primero fuera del directorio raíz del proyecto enorme, los caracteres antes y después del comando pwd son "`" en lugar de "'", este carácter es el que está a la izquierda de la tecla "!" en el teclado)


Ejemplo 3: agregar archivos de programa fuente


1. Edite build/make.rule

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)


ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif



ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif

ifneq ($(INC_DIRS), "")
INC_DIRS:=$(strip $(INC_DIRS))
INC_DIRS:=$(addprefix -I, $(INC_DIRS))
endif

ifneq ($(LINK_LIBS),"")
LINK_LIBS:=$(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC)-L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) $(INC_DIRS) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) $(INC_DIRS) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(RMS)



2. Editar fuente/foo/src/Makefile

EXE =
LIB = libfoo.a
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule


3. Editar fuente/enorme/src/Makefile

EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule


4. Código fuente del archivo fuente

// huge/source/foo/inc目录 foo.h
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif

// huge/source/foo/src 目录 foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
    
    
        printf("This is foo()!\n");
}

// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"

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


5. Compile el Makefile de enorme/fuente/foo/src

inserte la descripción de la imagen aquí

6. Compile el Makefile de enorme/fuente/enorme/src

inserte la descripción de la imagen aquí

7. Ejecute el enorme de enorme/compilación/exes

inserte la descripción de la imagen aquí


Gramática Descripción

  • La variable INC_DIRS se agrega en el archivo make.rule para almacenar todos los archivos de encabezado utilizados por un módulo, y se agrega un bloque de declaraciones condicionales en make.rule, es decir, cuando el valor en INC_DIRS no está vacío, la función strip se usa primero Elimine los espacios adicionales y luego use la función addprefix para prefijar todos los directorios con "-I" (i mayúscula). El cambio final es agregar una referencia a la variable INC_DIRS en las reglas de generación de archivos de objetos y las reglas de generación de archivos dependientes para decirle a gcc dónde encontrar el archivo de encabezado;
  • La variable LINK_LIBS se agrega al archivo make.rule para almacenar todas las bibliotecas que deben usarse al vincular;
  • En el archivo make.rule, agregue la variable DIR_LIBS al directorio de búsqueda del enlazador usando la opción -L de gcc. Dado que colocamos todos los archivos de la biblioteca en el directorio $(DIR_LIBS), este método puede simplificar el diseño Makefile. porque no necesito especificar varios directorios;
  • El Makefile en el directorio src de cada módulo agrega la variable LINK_LIBS, y al mismo tiempo, en source/huge/src/Makefile, el valor negativo de LINK_LIBS es foo (el formato del nombre de una biblioteca en Linux es libxxxx.a o .so, donde xxxx es el nombre que debemos darle cuando usamos la opción -l (L minúscula) de gcc)


Ejemplo 4: Adición de módulos para comprobar la compatibilidad del diseño de compilación

inserte la descripción de la imagen aquí

Agregue un módulo de zoológico al proyecto enorme y utilícelo como una biblioteca libzoo.a en el proyecto. El módulo de zoológico y el módulo foo están en la misma ruta, como se muestra en la figura anterior.


1. Código fuente del archivo fuente

// huge/source/zoo/inc目录 zoo.h
#ifndef __ZOO_H
#define __ZOO_H
void zoo();
#endif

// huge/source/zoo/src 目录 zoo.c
#include <stdio.h>
#include "zoo.h"
void zoo()
{
    
    
        printf("This is zoo()!\n");
}

// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"
#include "zoo.h"
int main()
{
    
    
        foo();
        zoo();
        return 0;
}


2. Editar fuente/zoo/src/Makefile

EXE =
LIB = libzoo.a
INC_DIRS = $(ROOT)/source/zoo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule


3. Editar fuente/enorme/src/Makefile

EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc \
$(ROOT)/source/zoo/inc
LINK_LIBS = foo zoo
include $(ROOT)/build/make.rule


4. Compilar fuente/foo/src/Makefile

inserte la descripción de la imagen aquí


5. Compilar código fuente/zoo/src/Makefile

inserte la descripción de la imagen aquí


6. Compilar fuente/enorme/src/Makefile

inserte la descripción de la imagen aquí


7. Ejecutar build/exes/huge

inserte la descripción de la imagen aquí


Gramática Descripción

  • El Makefile bajo el módulo zoo es básicamente el mismo que el Makefile del módulo foo, excepto por algunos cambios menores adicionales;
  • El Makefile debajo del módulo enorme agrega una referencia a la biblioteca del módulo del zoológico.

Ejemplo 5: Simplificar operaciones


Del ejemplo anterior 4, podemos ver que desde el archivo de la biblioteca hasta el archivo ejecutable, el proceso de compilación debe pasar por: "Ingrese al directorio source/foo/src/ para ejecutar make compilation - ingrese source/zoo/src/ directorio para ejecutar make compilation - ingrese source/huge/ src/directory execute make compilation", un total de 3 compilaciones manuales; si la cantidad de módulos continúa aumentando, los pasos de compilación serán más complicados, lo que dificultará su desarrollo y mantenimiento . Por lo tanto, introducimos el archivo Makefile en el directorio build/ para simplificar la compilación del proyecto.


Edite el Makefile en el directorio build/

.PHONY: all clean
DIRS = $(ROOT)/source/foo/src \
$(ROOT)/source/bar/src \
$(ROOT)/source/huge/src

RM = rm
RMFLAGS = -fr
RMS = $(ROOT)/build/exes $(ROOT)/build/libs

all:
	@set -e; \
	for dir in $(DIRS); \
		do \
		cd $$dir && $(MAKE) ; \
	done
	@echo ""
	@echo ":-) Completed"
	@echo ""
clean:
	@set -e; \
	for dir in $(DIRS); \
		do \
		cd $$dir && $(MAKE) clean;\
	done
	$(RM) $(RMFLAGS) $(RMS)
	@echo ""
	@echo ":-) Completed"
	@echo ""


Compile el Makefile en el directorio build/

$ hacer
inserte la descripción de la imagen aquí


$ hacer
inserte la descripción de la imagen aquí

una descripción de sintaxis limpia

  • Use la instrucción for del shell para recorrer cada directorio en la variable DIRS e ingrese el directorio para ejecutar make;
  • Use variables especiales MAKE en lugar de usar make directamente para una mejor portabilidad;
  • El archivo de la biblioteca debe construirse antes que el programa ejecutable En la variable DIRS, el directorio de la biblioteca debe colocarse antes del directorio del programa ejecutable, porque el Makefile se construye de acuerdo con el orden de los directorios.

Supongo que te gusta

Origin blog.csdn.net/locahuang/article/details/127015296
Recomendado
Clasificación