Prosperar
Para practicar más puntos de conocimiento sobre Makefile a través de un proyecto complejo, vamos a nombrar el proyecto playground (patio de recreo), los requisitos básicos de este proyecto para Makefile son:
Coloque todos los archivos de destino en el subdirectorio objs del directorio donde se encuentra el programa fuente;
coloque todos los programas ejecutables finales en el subdirectorio exes del directorio donde se encuentra el programa fuente;
introduzca archivos de encabezado de usuario para simular la situación de proyectos complejos .
Ejemplo 1: crear un directorio
Editar archivo MAKE
.PHONY: all
MKDIR = mkdir
DIRS = objs exes
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
Compilar y ejecutar
$ make
$ ll
$ ls
salida de resultados
Gramática Descripción
- Cabe señalar que la variable OBJS no es solo un destino dependiente, sino también un directorio, y su significado es diferente en diferentes ocasiones. Cuando el Makefile anterior se crea por primera vez, objs y exes no existen, por lo que todos los objetivos los consideran un requisito previo/objetivo de dependencia, y luego el Makefile primero crea los directorios objs y exes de acuerdo con las reglas de construcción de directorios. construyendo el directorio, el segundo se ejecutan los comandos de la regla, creando los directorios objs y exes.
Ejemplo 2: Borrar directorio
Editar archivo MAKE
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
DIRS = objs exes
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
clean:
$(RM) $(RMFLAGS) $(DIRS)
Compilar y ejecutar
$ make
$ ls
$ make clean
$ ls
salida de resultados
Gramática Descripción
- Cree un destino limpio para eliminar archivos de objetos y ejecutables generados.
Ejemplo 3: agregar archivos de encabezado
foo.h código fuente
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif
código fuente foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("This is foo()!\n");
}
código fuente main.c
#include "foo.h"
int main()
{
foo();
return 0;
}
Editar archivo MAKE
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
EXE = playground
DIRS = objs exes
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
$(EXE): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)
Compilar y ejecutar
$ make
$ ls
$ ./playground
$ make clean
salida de resultados
Gramática Descripción
- Dependencia agregada en el destino EXE después de todo. Cuando aparecen varios requisitos previos en una regla (como la regla all aquí), make construirá los objetivos uno por uno en orden de izquierda a derecha.
Ejemplo 4: poner los archivos generados en el directorio
Editar archivo MAKE
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES)
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
$(MKDIR) $@
$(EXE): $(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS) $(EXE)
Compilar y ejecutar
$ make
$ ls
$ cd exes
$ ls
$ cd ../objs/
$ ls
salida de resultados
Gramática Descripción
- Aumente el uso de la función addprefix para agregar el prefijo "objs/" a cada archivo de destino;
- También agregue el prefijo "objs/" antes del destino en la regla de patrón del archivo de destino de compilación;
- La razón para agregar el prefijo: la opción -o en el comando de regla debe usarse como la ubicación de generación final del archivo de destino, y para poder usarse para la regla de creación de destino en el Makefile, también debe adoptar un formato similar.
Ejemplo 5: Dependencias más complejas
Editar archivo MAKE
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
all: $(DIRS) $(DEPS) $(EXE)
$(DIRS):
$(MKDIR) $@
$(EXE): $(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
$(CC) -o $@ -c $^
$(DIR_DEPS)/%.dep: %.c
@echo "Making $@ ..."
@set -e; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $^ > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)
Compilar y ejecutar
$ make
$ ls exes/
$ ls objs/
$ ls deps/
salida de resultados
Gramática Descripción
- En comparación con el Ejemplo 4, este ejemplo mejora una relación de dependencia más completa;
- Problema existente en el Ejemplo 4: En el contexto de que el programa se compiló con éxito una vez, el archivo foo.h se modifica (de void foo(); a void foo(int)), y luego se vuelve a compilar, se encuentra que el programa no reporta un error, la razón es que el Makefile de la instancia 23 no depende de foo, lo que lleva a la imposibilidad de identificar con precisión los problemas existentes.
Tecnología clave:- La opción -M y la opción -MM en gcc , la diferencia entre las dos es: la opción -MM no enumera las dependencias en los archivos de encabezado del sistema, como stdio.h;
- sed : puede buscar y reemplazar cadenas;
- set -e : La función es decirle a BASH Shell que salga directamente cuando ocurra algún error durante el proceso de generación de archivos dependientes. La actuación más definitiva es que make nos dirá que algo salió mal, deteniendo así el trabajo posterior de make. Si no se realiza esta configuración, cuando se produzca un error en el archivo de dependencia de compilación, make seguirá funcionando más tarde, que no es lo que queremos.
Ejemplo 6: declaración condicional de archivo MAKE
Editar archivo MAKE
.PHONY: all
sharp = square
desk = square
table = circle
foo = defined
ifeq ($(sharp), $(desk))
result1 = "desk == sharp"
endif
ifneq "$(table)" 'square'
result2 = "table != square"
endif
ifdef foo
result3 = "foo is defined"
endif
ifdef zoo
resunlt4 = "zoo is not defined"
endif
all:
@echo $(result1)
@echo $(result2)
@echo $(result3)
@echo $(result4)
Compilar y ejecutar
$ make
salida de resultados
Gramática Descripción
- make analizará la sintaxis condicional tan pronto como la vea, que incluye las cuatro formas de declaración de ifdef, ifeq, ifndef e ifneq.
Resumen: Makefile para este proyecto
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated.exe
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
all: $(EXE)
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif
$(DIRS):
$(MKDIR) $@
$(EXE): $(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
@echo "Making $@ ..."
@set -e; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)