Cómo generar un Makefile con fuente en subdirectorios usando solo un makefile
-
04-07-2019 - |
Pregunta
Tengo fuente en un montón de subdirectorios como:
src/widgets/apple.cpp
src/widgets/knob.cpp
src/tests/blend.cpp
src/ui/flash.cpp
En la raíz del proyecto quiero generar un solo Makefile usando una regla como:
%.o: %.cpp
$(CC) -c $<
build/test.exe: build/widgets/apple.o build/widgets/knob.o build/tests/blend.o src/ui/flash.o
$(LD) build/widgets/apple.o .... build/ui/flash.o -o build/test.exe
Cuando intento esto, no encuentra una regla para build / widgets / apple.o. ¿Puedo cambiar algo para que se use% .o:% .cpp cuando se necesita hacer build / widgets / apple.o?
Solución
La razón es que tu regla
%.o: %.cpp
...
espera que el archivo .cpp resida en el mismo directorio que .o su edificio. Dado que test.exe en su caso depende de build / widgets / apple.o (etc.), make espera que apple.cpp sea build / widgets / apple.cpp.
Puede usar VPATH para resolver esto:
VPATH = src/widgets
BUILDDIR = build/widgets
$(BUILDDIR)/%.o: %.cpp
...
Al intentar construir " build / widgets / apple.o " ;, make buscará apple.cpp en VPATH . Tenga en cuenta que la regla de compilación tiene que usar variables especiales para acceder al nombre de archivo real make find:
$(BUILDDIR)/%.o: %.cpp
$(CC) $< -o $@
Donde " $ < " se expande a la ruta donde make encuentra la primera dependencia.
También tenga en cuenta que esto generará todos los archivos .o en build / widgets. Si desea compilar los binarios en diferentes directorios, puede hacer algo como
build/widgets/%.o: %.cpp
....
build/ui/%.o: %.cpp
....
build/tests/%.o: %.cpp
....
Recomiendo que use " enlatado secuencias de comandos " para evitar repetir la regla de compilación real del compilador:
define cc-command
$(CC) $(CFLAGS) $< -o $@
endef
Puede tener múltiples reglas como esta:
build1/foo.o build1/bar.o: %.o: %.cpp
$(cc-command)
build2/frotz.o build2/fie.o: %.o: %.cpp
$(cc-command)
Otros consejos
Esto hace el truco:
CC := g++
LD := g++
MODULES := widgets test ui
SRC_DIR := $(addprefix src/,$(MODULES))
BUILD_DIR := $(addprefix build/,$(MODULES))
SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp))
OBJ := $(patsubst src/%.cpp,build/%.o,$(SRC))
INCLUDES := $(addprefix -I,$(SRC_DIR))
vpath %.cpp $(SRC_DIR)
define make-goal
$1/%.o: %.cpp
$(CC) $(INCLUDES) -c $$< -o $$@
endef
.PHONY: all checkdirs clean
all: checkdirs build/test.exe
build/test.exe: $(OBJ)
$(LD) $^ -o $@
checkdirs: $(BUILD_DIR)
$(BUILD_DIR):
@mkdir -p $@
clean:
@rm -rf $(BUILD_DIR)
$(foreach bdir,$(BUILD_DIR),$(eval $(call make-goal,$(bdir))))
Este Makefile asume que tiene sus archivos de inclusión en los directorios de origen. También comprueba si existen los directorios de compilación y los crea si no existen.
La última línea es la más importante. Crea las reglas implícitas para cada compilación utilizando la función make-goal
, y no es necesario escribirlas una por una
También puede agregar la generación automática de dependencias, utilizando el camino de Tromey
La cosa es $@
incluirá la ruta completa (relativa) al archivo fuente que a su vez se usa para construir el nombre del objeto (y, por lo tanto, su ruta relativa)
Usamos:
#####################
# rules to build the object files
$(OBJDIR_1)/%.o: %.c
@$(ECHO) "$< -> $@"
@test -d $(OBJDIR_1) || mkdir -pm 775 $(OBJDIR_1)
@test -d $(@D) || mkdir -pm 775 $(@D)
@-$(RM) $@
$(CC) $(CFLAGS) $(CFLAGS_1) $(ALL_FLAGS) $(ALL_DEFINES) $(ALL_INCLUDEDIRS:%=-I%) -c $< -o $@
Esto crea un directorio de objetos con el nombre especificado en $(OBJDIR_1)
y subdirectorios de acuerdo con los subdirectorios en la fuente.
Por ejemplo (suponga objs como directorio de objetos de nivel superior), en Makefile:
widget/apple.cpp
tests/blend.cpp
da como resultado el siguiente directorio de objetos:
objs/widget/apple.o
objs/tests/blend.o
Esto lo hará sin manipulación dolorosa o múltiples secuencias de comandos:
build/%.o: src/%.cpp src/%.o: src/%.cpp %.o: $(CC) -c $< -o $@ build/test.exe: build/widgets/apple.o build/widgets/knob.o build/tests/blend.o src/ui/flash.o $(LD) $^ -o $@
JasperE ha explicado por qué "%. o:% .cpp " no funcionará esta versión tiene una regla de patrón (% .o :) con comandos y sin requisitos previos, y dos reglas de patrón (build /%. o: y src /%. o :) con requisitos previos y sin comandos. (Tenga en cuenta que puse la regla src /%. O para tratar con src / ui / flash.o, suponiendo que no fuera un error tipográfico para build / ui / flash.o, así que si no lo necesita puede déjalo afuera.)
build / test.exe necesita build / widgets / apple.o,
build / widgets / apple.o se parece a build /%. o, por lo que necesita src /%. cpp (en este caso src / widgets / apple.cpp),
build / widgets / apple.o también se parece a% .o, por lo que ejecuta el comando CC y usa los requisitos previos que acaba de encontrar (es decir, src / widgets / apple.cpp) para construir el objetivo (build / widgets / apple.o)
Este es otro truco.
En el 'Makefile' principal, defina SRCDIR para cada directorio fuente e incluya 'makef.mk' para cada valor de SRCDIR. En cada directorio fuente, coloque el archivo 'files.mk' con la lista de archivos fuente y compile las opciones para algunos de ellos. En el 'Makefile' principal se pueden definir opciones de compilación y excluir archivos para cada valor de SRCDIR.
Makefile:
PRG := prog-name
OPTIMIZE := -O2 -fomit-frame-pointer
CFLAGS += -finline-functions-called-once
LDFLAGS += -Wl,--gc-section,--reduce-memory-overheads,--relax
.DEFAULT_GOAL := hex
OBJDIR := obj
MK_DIRS := $(OBJDIR)
SRCDIR := .
include makef.mk
SRCDIR := crc
CFLAGS_crc := -DCRC8_BY_TABLE -DMODBUS_CRC_BY_TABLE
ASFLAGS_crc := -DCRC8_BY_TABLE -DMODBUS_CRC_BY_TABLE
include makef.mk
################################################################
CC := avr-gcc -mmcu=$(MCU_TARGET) -I.
OBJCOPY := avr-objcopy
OBJDUMP := avr-objdump
C_FLAGS := $(CFLAGS) $(REGS) $(OPTIMIZE)
CPP_FLAGS := $(CPPFLAGS) $(REGS) $(OPTIMIZE)
AS_FLAGS := $(ASFLAGS)
LD_FLAGS := $(LDFLAGS) -Wl,-Map,$(OBJDIR)/$(PRG).map
C_OBJS := $(C_SRC:%.c=$(OBJDIR)/%.o)
CPP_OBJS := $(CPP_SRC:%.cpp=$(OBJDIR)/%.o)
AS_OBJS := $(AS_SRC:%.S=$(OBJDIR)/%.o)
C_DEPS := $(C_OBJS:%=%.d)
CPP_DEPS := $(CPP_OBJS:%=%.d)
AS_DEPS := $(AS_OBJS:%=%.d)
OBJS := $(C_OBJS) $(CPP_OBJS) $(AS_OBJS)
DEPS := $(C_DEPS) $(CPP_DEPS) $(AS_DEPS)
hex: $(PRG).hex
lst: $(PRG).lst
$(OBJDIR)/$(PRG).elf : $(OBJS)
$(CC) $(C_FLAGS) $(LD_FLAGS) $^ -o $@
%.lst: $(OBJDIR)/%.elf
-@rm $@ 2> /dev/nul
$(OBJDUMP) -h -s -S $< > $@
%.hex: $(OBJDIR)/%.elf
-@rm $@ 2> /dev/nul
$(OBJCOPY) -j .text -j .data -O ihex $< $@
$(C_OBJS) : $(OBJDIR)/%.o : %.c Makefile
$(CC) -MMD -MF $@.p.d -c $(C_FLAGS) $(C_FLAGS_$(call clear_name,$<)) $< -o $@
@sed -e 's,.*:,SRC_FILES += ,g' < $@.p.d > $@.d
@sed -e "\$$s/$$/ $(subst /,\/,$(dir $<))files.mk\n/" < $@.p.d >> $@.d
@sed -e 's,^[^:]*: *,,' -e 's,^[ \t]*,,' -e 's, \\$$,,' -e 's,$$, :,' < $@.p.d >> $@.d
-@rm -f $@.p.d
$(CPP_OBJS) : $(OBJDIR)/%.o : %.cpp Makefile
$(CC) -MMD -MF $@.p.d -c $(CPP_FLAGS) $(CPP_FLAGS_$(call clear_name,$<)) $< -o $@
@sed -e 's,.*:,SRC_FILES += ,g' < $@.p.d > $@.d
@sed -e "\$$s/$$/ $(subst /,\/,$(dir $<))files.mk\n/" < $@.p.d >> $@.d
@sed -e 's,^[^:]*: *,,' -e 's,^[ \t]*,,' -e 's, \\$$,,' -e 's,$$, :,' < $@.p.d >> $@.d
-@rm -f $@.p.d
$(AS_OBJS) : $(OBJDIR)/%.o : %.S Makefile
$(CC) -MMD -MF $@.p.d -c $(AS_FLAGS) $(AS_FLAGS_$(call clear_name,$<)) $< -o $@
@sed -e 's,.*:,SRC_FILES += ,g' < $@.p.d > $@.d
@sed -e "\$$s/$$/ $(subst /,\/,$(dir $<))files.mk\n/" < $@.p.d >> $@.d
@sed -e 's,^[^:]*: *,,' -e 's,^[ \t]*,,' -e 's, \\$$,,' -e 's,$$, :,' < $@.p.d >> $@.d
-@rm -f $@.p.d
clean:
-@rm -rf $(OBJDIR)/$(PRG).elf
-@rm -rf $(PRG).lst $(OBJDIR)/$(PRG).map
-@rm -rf $(PRG).hex $(PRG).bin $(PRG).srec
-@rm -rf $(PRG)_eeprom.hex $(PRG)_eeprom.bin $(PRG)_eeprom.srec
-@rm -rf $(MK_DIRS:%=%/*.o) $(MK_DIRS:%=%/*.o.d)
-@rm -f tags cscope.out
# -rm -rf $(OBJDIR)/*
# -rm -rf $(OBJDIR)
# -rm $(PRG)
tag: tags
tags: $(SRC_FILES)
if [ -e tags ] ; then ctags -u $? ; else ctags $^ ; fi
cscope -U -b $^
# include dep. files
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEPS)
endif
# Create directory
$(shell mkdir $(MK_DIRS) 2>/dev/null)
makef.mk
SAVE_C_SRC := $(C_SRC)
SAVE_CPP_SRC := $(CPP_SRC)
SAVE_AS_SRC := $(AS_SRC)
C_SRC :=
CPP_SRC :=
AS_SRC :=
include $(SRCDIR)/files.mk
MK_DIRS += $(OBJDIR)/$(SRCDIR)
clear_name = $(subst /,_,$(1))
define rename_var
$(2)_$(call clear_name,$(SRCDIR))_$(call clear_name,$(1)) := \
$($(subst _,,$(2))_$(call clear_name,$(SRCDIR))) $($(call clear_name,$(1)))
$(call clear_name,$(1)) :=
endef
define proc_lang
ORIGIN_SRC_FILES := $($(1)_SRC)
ifneq ($(strip $($(1)_ONLY_FILES)),)
$(1)_SRC := $(filter $($(1)_ONLY_FILES),$($(1)_SRC))
else
ifneq ($(strip $(ONLY_FILES)),)
$(1)_SRC := $(filter $(ONLY_FILES),$($(1)_SRC))
else
$(1)_SRC := $(filter-out $(EXCLUDE_FILES),$($(1)_SRC))
endif
endif
$(1)_ONLY_FILES :=
$(foreach name,$($(1)_SRC),$(eval $(call rename_var,$(name),$(1)_FLAGS)))
$(foreach name,$(ORIGIN_SRC_FILES),$(eval $(call clear_name,$(name)) :=))
endef
$(foreach lang,C CPP AS, $(eval $(call proc_lang,$(lang))))
EXCLUDE_FILES :=
ONLY_FILES :=
SAVE_C_SRC += $(C_SRC:%=$(SRCDIR)/%)
SAVE_CPP_SRC += $(CPP_SRC:%=$(SRCDIR)/%)
SAVE_AS_SRC += $(AS_SRC:%=$(SRCDIR)/%)
C_SRC := $(SAVE_C_SRC)
CPP_SRC := $(SAVE_CPP_SRC)
AS_SRC := $(SAVE_AS_SRC)
./files.mk
C_SRC := main.c
CPP_SRC :=
AS_SRC := timer.S
main.c += -DDEBUG
./crc/files.mk
C_SRC := byte-modbus-crc.c byte-crc8.c
AS_SRC := modbus-crc.S crc8.S modbus-crc-table.S crc8-table.S
byte-modbus-crc.c += --std=gnu99
byte-crc8.c += --std=gnu99
Aquí está mi solución, inspirada en la respuesta de Beta. Es más simple que las otras soluciones propuestas
Tengo un proyecto con varios archivos C, almacenados en muchos subdirectorios. Por ejemplo:
src/lib.c
src/aa/a1.c
src/aa/a2.c
src/bb/b1.c
src/cc/c1.c
Aquí está mi Makefile (en el directorio src/
):
# make -> compile the shared library "libfoo.so"
# make clean -> remove the library file and all object files (.o)
# make all -> clean and compile
SONAME = libfoo.so
SRC = lib.c \
aa/a1.c \
aa/a2.c \
bb/b1.c \
cc/c1.c
# compilation options
CFLAGS = -O2 -g -W -Wall -Wno-unused-parameter -Wbad-function-cast -fPIC
# linking options
LDFLAGS = -shared -Wl,-soname,$(SONAME)
# how to compile individual object files
OBJS = $(SRC:.c=.o)
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: all clean
# library compilation
$(SONAME): $(OBJS) $(SRC)
$(CC) $(OBJS) $(LDFLAGS) -o $(SONAME)
# cleaning rule
clean:
rm -f $(OBJS) $(SONAME) *~
# additional rule
all: clean lib
Este ejemplo funciona bien para una biblioteca compartida, y debería ser muy fácil de adaptar para cualquier proceso de compilación.
Por lo general, crea un Makefile en cada subdirectorio y escribe en el Makefile de nivel superior para llamar a make en los subdirectorios.
Esta página puede ayudar: http://www.gnu.org/software/make/