Cómo generar un Makefile con fuente en subdirectorios usando solo un makefile

StackOverflow https://stackoverflow.com/questions/231229

  •  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?

¿Fue útil?

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/

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top