Makefile, dependencias de encabezado

97

Digamos que tengo un archivo MAKE con la regla

%.o: %.c
 gcc -Wall -Iinclude ...

Quiero que * .o se reconstruya cada vez que cambie un archivo de encabezado. En lugar de elaborar una lista de dependencias, siempre que cualquier archivo de encabezado en/include cambie , todos los objetos del directorio deben reconstruirse.

No puedo pensar en una buena manera de cambiar la regla para adaptarse a esto, estoy abierto a sugerencias. Puntos de bonificación si la lista de encabezados no tiene que estar codificada

Miguel
fuente
Habiendo escrito mi respuesta a continuación, miré en la lista relacionada y encontré: stackoverflow.com/questions/297514/… que parece ser un duplicado. La respuesta de Chris Dodd es equivalente a la mía, aunque usa una convención de nomenclatura diferente.
dmckee --- ex-moderador gatito

Respuestas:

116

Si está utilizando un compilador GNU, el compilador puede ensamblar una lista de dependencias por usted. Fragmento de Makefile:

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

o

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ > ./.depend;

include .depend

donde SRCSes una variable que apunta a su lista completa de archivos fuente.

También está la herramienta makedepend, pero nunca me gustó tanto comogcc -MM

dmckee
fuente
2
Me gusta este truco, pero ¿cómo puedo dependejecutar solo cuando los archivos de origen han cambiado? Parece que se ejecuta siempre independientemente ...
persecución el
2
@chase: Bueno, erróneamente hice la dependencia en los archivos de objeto, cuando obviamente debería estar en las fuentes y también tenía el orden de dependencia incorrecto para los dos objetivos. Eso es lo que obtengo por escribir de memoria. Pruebalo ahora.
dmckee --- ex-moderador gatito
4
¿Es una forma de agregar antes de cada archivo algún prefijo para mostrar que está en otro directorio, por ejemplo build/file.o?
RiaD
Cambié SRCS a OBJECTS, donde OBJECTS es una lista de mis archivos * .o. Eso parecía evitar que depend se ejecutara cada vez y también detectaba cambios solo en los archivos de encabezado. Esto parece contrario a los comentarios anteriores. ¿Me estoy perdiendo algo?
BigBrownBear00
2
¿Por qué es necesario el punto y coma? si lo intento sin él, o con -MF ./.depend no es el último argumento, solo guarda las dependencias del último archivo en $ (SRCS).
humodz
72

La mayoría de las respuestas son sorprendentemente complicadas o erróneas. Sin embargo, se han publicado ejemplos simples y sólidos en otros lugares [ revisión de código ]. Es cierto que las opciones proporcionadas por el preprocesador gnu son un poco confusas. Sin embargo, la eliminación de todos los directorios del destino de compilación con -MMestá documentada y no es un error [ gpp ]:

Por defecto, CPP toma el nombre del archivo de entrada principal, elimina cualquier componente de directorio y cualquier sufijo de archivo como '.c', y agrega el sufijo de objeto habitual de la plataforma.

La -MMDopción (algo más nueva) es probablemente lo que desea. Para completar, un ejemplo de un archivo MAKE que admite varios directorios src y directorios de compilación con algunos comentarios. Para una versión simple sin directorios de compilación, vea [ codereview ].

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

Este método funciona porque si hay varias líneas de dependencia para un solo objetivo, las dependencias simplemente se unen, por ejemplo:

a.o: a.h
a.o: a.c
    ./cmd

es equivalente a:

a.o: a.c a.h
    ./cmd

como se menciona en: Makefile múltiples líneas de dependencia para un solo objetivo?

Sophie
fuente
1
Me gusta esta solucion No quiero escribir el comando make depend. Útil !!
Robert
1
Hay un error de ortografía en el valor de la variable OBJ: CPPdebe leerseCPPS
ctrucza
1
Esta es mi respuesta preferida; +1 para ti. Este es el único en esta página que tiene sentido y cubre (por lo que puedo ver) todas las situaciones en las que la recopilación es necesaria (evitando la compilación innecesaria, pero suficiente)
Joost
1
Fuera de la caja, esto no pudo ubicar los encabezados por mí a pesar de que hpp y cpp están en el mismo directorio.
villasv
1
si tiene sus archivos fuente ( a.cpp, b.cpp) en ./src/, ¿no haría esa sustitución $(OBJ)=./build/src/a.o ./build/src/b.o?
Galois
26

Como publiqué aquí, gcc puede crear dependencias y compilar al mismo tiempo:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

El parámetro '-MF' especifica un archivo para almacenar las dependencias.

El guión al comienzo de '-include' le dice a Make que continúe cuando el archivo .d no existe (por ejemplo, en la primera compilación).

Tenga en cuenta que parece haber un error en gcc con respecto a la opción -o. Si configura el nombre de archivo del objeto para que diga obj / _file__c.o, entonces el archivo .d generado aún contendrá el archivo .o, no obj / _file__c.o.

Martín Fido
fuente
4
Cuando intento esto, todos mis archivos .o se crean como archivos vacíos. Tengo mis objetos en una subcarpeta de compilación (por lo que $ OBJECTS contiene build / main.o build / smbus.o build / etc ...) y eso ciertamente crea los archivos .d como describiste con el error aparente, pero ciertamente no está construyendo los archivos .o en absoluto, mientras que lo hace si elimino los archivos -MM y -MF.
bobpaul
1
El uso de -MT resolverá la nota en las últimas líneas de su respuesta que actualiza el objetivo de cada lista de dependencias.
Godric Seer
3
@bobpaul porque man gccdice -MMimplica -E, que "se detiene después del preprocesamiento". Necesita en su -MMDlugar: stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
23

¿Qué tal algo como:

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

También puede usar los comodines directamente, pero suelo encontrar que los necesito en más de un lugar.

Tenga en cuenta que esto solo funciona bien en proyectos pequeños, ya que supone que cada archivo de objeto depende de cada archivo de encabezado.

Michael Williamson
fuente
gracias, estaba haciendo que esto fuera mucho más complicado de lo necesario
Mike
15
Esto funciona, sin embargo, el problema con esto es que cada archivo objeto se vuelve a compilar, cada vez que se realiza un pequeño cambio, es decir, si tiene 100 archivos fuente / encabezado, y hace un pequeño cambio en solo uno, los 100 se vuelven a compilar .
Nicholas Hamilton
1
Realmente debería actualizar su respuesta para decir que esta es una forma muy ineficiente de hacerlo porque reconstruye TODOS los archivos cada vez que se cambia CUALQUIER archivo de encabezado. Las otras respuestas son mucho mejores.
xaxxon
2
Ésta es una muy mala solución. Seguro que funcionará en un proyecto pequeño, pero para cualquier tamaño de producción y construcción, esto dará lugar a un tiempo de compilación terrible y se convertirá en el equivalente a ejecutarlo make clean alltodo el tiempo.
Julien Guertault
En mi prueba, esto no funciona en absoluto. La gcclínea no se ejecuta en absoluto, pero en su %o: %.clugar se ejecuta la regla incorporada ( regla).
Penghe Geng
4

La solución de Martin anterior funciona muy bien, pero no maneja archivos .o que residen en subdirectorios. Godric señala que la bandera -MT se encarga de ese problema, pero simultáneamente evita que el archivo .o se escriba correctamente. Lo siguiente se encargará de ambos problemas:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<
Miguel
fuente
3

Esto hará el trabajo bien, e incluso manejará los subdirectorios que se especifican:

    $(CC) $(CFLAGS) -MD -o $@ $<

lo probé con gcc 4.8.3

g24l
fuente
3

Aquí hay dos líneas:

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

Esto funciona con la receta de creación predeterminada, siempre que tenga una lista de todos sus archivos de objeto en formato OBJS.

tbodt
fuente
1

Prefiero esta solución, sobre la respuesta aceptada por Michael Williamson, detecta cambios en las fuentes + archivos en línea, luego en las fuentes + encabezados y, finalmente, solo en las fuentes. La ventaja aquí es que no se vuelve a compilar toda la biblioteca si solo se realizan unos pocos cambios. No es una gran consideración para un proyecto con un par de archivos, pero si tiene 10 o 100 fuentes, notará la diferencia.

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)
Nicholas Hamilton
fuente
2
Esto funciona solo si no tiene nada en sus archivos de encabezado que requiera la recompilación de cualquier archivo cpp que no sea el archivo de implementación correspondiente.
matec
0

Lo siguiente funciona para mí:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<
Marcel Keller
fuente
0

Una versión ligeramente modificada de la respuesta de Sophie que permite enviar los archivos * .d a una carpeta diferente (solo pegaré la parte interesante que genera los archivos de dependencia):

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

Tenga en cuenta que el parámetro

-MT $@

se utiliza para asegurar que los destinos (es decir, los nombres de los archivos de objetos) en los archivos * .d generados contienen la ruta completa a los archivos * .o y no solo el nombre del archivo.

No sé por qué NO se necesita este parámetro cuando se usa -MMD en combinación con -c (como en la versión de Sophie ). En esta combinación, parece escribir la ruta completa de los archivos * .o en los archivos * .d. Sin esta combinación, -MMD también escribe solo los nombres de archivo puros sin ningún componente de directorio en los archivos * .d. Quizás alguien sepa por qué -MMD escribe la ruta completa cuando se combina con -c. No he encontrado ninguna pista en la página de manual de g ++.

Máximo FPS
fuente