flor e fruta
Para praticar o aprofundamento da aplicação do Makefile através de um projeto relativamente complexo, vamos nomear o projeto como enorme (big guy);
O diagrama da estrutura do diretório do projeto é o seguinte:
Exemplo 1: Makefile em source/foo/src
editar source/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 e executar
$ make
$ ls
$ ls ../../../bulild/libs/
$ ls ../../../build/exes/
$ ls deps/
$ ls objs/
$ make clean
saída de resultado
Descrição gramatical
- O propósito das duas variáveis AR e ARFLAGS é gerar uma biblioteca estática;
- IDS_EXES indica a localização real do diretório exes e agora usa um caminho relativo;
- A variável DIRS aumenta o conteúdo da variável DIR_LIBS para que o diretório build/libs seja gerado antes que o arquivo de biblioteca seja gerado;
- As variáveis RMS são usadas para representar diretórios e arquivos a serem removidos;
- O Makefile é apenas para construir a biblioteca libfoo.a, então make clean não pode deletar todos os diretórios exes e libs localizados no diretório de construção;
- A instrução condicional ifneq é usada para determinar se a variável EXE está definida, pois posteriormente ao definir a dependência de todos os destinos, é necessário determinar se a variável EXE possui um valor. Se não houver valor, não precisamos fazer todos os alvos dependem de $(EXE) Adicione um prefixo para chamar a função addprefix, caso contrário, interromperá o método de julgar se a variável EXE tem um valor posteriormente, para decidir se deve fazer com que todos os diretórios dependam dela;
- Se EXE tiver um valor, seu valor deve ser adicionado à variável RMS para que possamos limpá-lo quando chamarmos make clean;
- A variável LIB é usada para armazenar o nome da biblioteca, por exemplo, o nome da biblioteca aqui é libfoo.a;
- Adicione uma regra para construção que chame o comando ar com o argumento crs para gerar a biblioteca.
- No comando clean target, o conteúdo da variável RMS é excluído em vez do conteúdo da variável DIRS. Porque não queremos excluir os diretórios libs e exes no diretório bulid ao fazer clean no módulo foo.
Exemplo 2: Usando make.rule para melhorar a reutilização
Divida o Makefile no módulo foo do Exemplo 1 em duas partes: make.rule no diretório build e Makefile no diretório source/foo/src, e expanda o Makefile no diretório large/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. Edite source/foo/src/Makefile
EXE =
LIB = libfoo.a
include $(ROOT)/build/make.rule
Atribua um valor à variável ROOT no diretório huge/:
$ export ROOT=`pwd`
Digite enorme/fonte/foo/src para compilar e executar
$ make
$ ls
$ ls ../../../bulild/
$ ls ../../../build/libs/
$ ls deps/
$ ls objs/
$ make clean
saída de resultado
3. Crie main.c no diretório source/huge/src/
int main()
{
return 0;
}
Editar source/huge/src/Makefile
EXE = huge.exe
LIB =
include $(ROOT)/build/make.rule
Digite source/huge/src para compilar e executar
$ make
$ ls
$ ls deps/
$ ls objs/
$ ls ../../../build/exes/
$ make clean
saída de resultado
Descrição gramatical
- Por meio do arquivo make.rule no diretório build, todos os Makefiles localizados no diretório src de cada módulo de software podem ser usados para melhorar a capacidade de reutilização;
- Colocando parte do Makefile do módulo foo em make.rule;
- As definições das variáveis EXE e LIB são diferentes para cada módulo de software, por exemplo, neste projeto, é necessário definir o valor da variável LIB para libfoo.a no Makefile no diretório source/foo.src, e a variável EXE está vazio; e No Makefile no diretório source/huge/src, defina apenas o valor da variável EXE como large.exe;
- Para tornar a variável DIR_EXES e a variável DIR_LIBS iguais para todos os módulos, pode-se realizar usando o caminho absoluto; este projeto é realizado definindo a variável de ambiente ROOT (deve-se notar que ao exportar a variável ROOT necessária, em além de entrar primeiro fora do diretório raiz do projeto enorme, os caracteres antes e depois do comando pwd são "`" em vez de "'", este caractere é o que está à esquerda da tecla "!" no teclado)
Exemplo 3: adicionar arquivos de programa de origem
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. Edite source/foo/src/Makefile
EXE =
LIB = libfoo.a
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule
3. Edite source/huge/src/Makefile
EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule
4. Código fonte do arquivo fonte
// 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 o Makefile de huge/source/foo/src
6. Compile o Makefile de huge/source/huge/src
7. Execute o enorme de huge/build/exes
Descrição gramatical
- A variável INC_DIRS é adicionada no arquivo make.rule para armazenar todos os arquivos de cabeçalho usados por um módulo, e um bloco de declaração condicional é adicionado no make.rule, ou seja, quando o valor em INC_DIRS não estiver vazio, a função strip é usado primeiro Remova os espaços extras e, em seguida, use a função addprefix para prefixar todos os diretórios com "-I" (i maiúsculo). A alteração final é adicionar uma referência à variável INC_DIRS nas regras de geração de arquivo de objeto e nas regras de geração de arquivo dependente para informar ao gcc onde localizar o arquivo de cabeçalho;
- A variável LINK_LIBS é adicionada ao arquivo make.rule para armazenar todas as bibliotecas que precisam ser usadas ao vincular;
- No arquivo make.rule, adicione a variável DIR_LIBS ao diretório de pesquisa do vinculador usando a opção -L do gcc. Como colocamos todos os arquivos de biblioteca no diretório $(DIR_LIBS), esse método pode simplificar o Makefile. porque não preciso especificar vários diretórios;
- O Makefile no diretório src de cada módulo adiciona a variável LINK_LIBS e, ao mesmo tempo, no source/huge/src/Makefile, o valor negativo de LINK_LIBS é foo (o formato de um nome de biblioteca no linux é libxxxx.a ou .so, onde xxxx É o nome que precisamos dar quando usamos a opção -l (L minúsculo) do gcc)
Exemplo 4: Adicionando módulos para verificar a compatibilidade do projeto de compilação
Adicione um módulo zoo ao projeto large e use-o como uma biblioteca libzoo.a no projeto.O módulo zoo e o módulo foo estão no mesmo caminho, conforme mostrado na figura acima.
1. Código fonte do arquivo fonte
// 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. Edite source/zoo/src/Makefile
EXE =
LIB = libzoo.a
INC_DIRS = $(ROOT)/source/zoo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule
3. Edite source/huge/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 source/foo/src/Makefile
5. Compile source/zoo/src/Makefile
6. Compile source/huge/src/Makefile
7. Execute build/exes/huge
Descrição gramatical
- O Makefile sob o módulo zoo é basicamente o mesmo que o Makefile do módulo foo, exceto por algumas pequenas alterações adicionais;
- O Makefile sob o módulo enorme adiciona uma referência à biblioteca do módulo zoo.
Exemplo 5: Simplifique as operações
A partir do exemplo 4 acima, podemos ver que do arquivo de biblioteca para o arquivo executável, o processo de compilação precisa passar por: "Entre no diretório source/foo/src/ para executar a compilação - insira o source/zoo/src/ diretório para executar a compilação - entre em source/huge/ src/diretório execute a compilação", um total de 3 compilações manuais; se o número de módulos continuar aumentando, as etapas de compilação serão mais complicadas, dificultando o desenvolvimento e a manutenção . Portanto, introduzimos o arquivo Makefile no diretório build/ para simplificar a compilação do projeto.
Edite o Makefile no diretório 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 o Makefile no diretório build/
$ ganhar
$ make
descrição de sintaxe limpa
- Use a instrução for do shell para percorrer cada diretório na variável DIRS e insira o diretório para executar o make;
- Use variáveis especiais MAKE em vez de usar make diretamente para melhor portabilidade;
- O arquivo da biblioteca precisa ser construído antes do programa executável.Na variável DIRS, o diretório da biblioteca precisa ser colocado antes do diretório do programa executável, pois o Makefile é construído de acordo com a ordem dos diretórios.