Makefile, zależności nagłówka

97

Powiedzmy, że mam plik makefile z regułą

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

Chcę, aby * .o było odbudowywane po każdej zmianie pliku nagłówkowego. Zamiast opracowywać listę zależności, za każdym razem, gdy /includezmieni się dowolny plik nagłówkowy , wszystkie obiekty w katalogu muszą zostać odbudowane.

Nie mogę wymyślić fajnego sposobu na zmianę reguły, aby to uwzględnić, jestem otwarty na sugestie. Dodatkowe punkty, jeśli lista nagłówków nie musi być zakodowana na stałe

Mikrofon
źródło
Po napisaniu mojej odpowiedzi poniżej przejrzałem powiązaną listę i znalazłem: stackoverflow.com/questions/297514/… który wydaje się być duplikatem. Odpowiedź Chrisa Dodda jest równoważna mojej, chociaż używa innej konwencji nazewnictwa.
dmckee --- kociak ex-moderator

Odpowiedzi:

116

Jeśli używasz kompilatora GNU, kompilator może utworzyć dla Ciebie listę zależności. Fragment pliku Makefile:

depend: .depend

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

include .depend

lub

depend: .depend

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

include .depend

gdzie SRCSjest zmienną wskazującą na całą listę plików źródłowych.

Jest też narzędzie makedepend, ale nigdy nie podobało mi się tak bardzogcc -MM

dmckee
źródło
2
Podoba mi się ta sztuczka, ale jak mogę dependuruchomić tylko wtedy, gdy zmieniły się pliki źródłowe? Wydaje się, że działa za każdym razem, niezależnie ...
ścigaj
2
@chase: Cóż, błędnie utworzyłem zależność od plików obiektowych, podczas gdy powinna ona oczywiście znajdować się w źródłach i kolejność zależności również była niewłaściwa dla dwóch celów. To właśnie otrzymuję za pisanie z pamięci. Spróbuj teraz.
dmckee --- ex-moderator kitten
4
Czy jest to sposób na dodanie przed każdym plikiem przedrostka, aby pokazać, że znajduje się on w innym katalogu, np. build/file.o?
RiaD
Zmieniłem SRCS na OBJECTS, gdzie OBJECTS to lista moich plików * .o. Wydawało się, że uniemożliwia to uruchamianie zależności za każdym razem, a także przechwytuje zmiany tylko w plikach nagłówkowych. Wydaje się to sprzeczne z poprzednimi komentarzami… czy czegoś mi brakuje?
BigBrownBear00
2
Dlaczego średnik jest potrzebny? jeśli spróbuję tego bez niego lub z -MF ./.depend nie będącym ostatnim argumentem, zapisuje tylko zależności ostatniego pliku w $ (SRCS).
humodz
72

Większość odpowiedzi jest zaskakująco skomplikowana lub błędna. Jednak proste i solidne przykłady zostały opublikowane w innym miejscu [przegląd kodowy ]. Prawdą jest, że opcje oferowane przez preprocesor gnu są nieco zagmatwane. Jednak usunięcie wszystkich katalogów z celu kompilacji -MMjest udokumentowane i nie jest błędem [ gpp ]:

Domyślnie CPP przyjmuje nazwę głównego pliku wejściowego, usuwa wszystkie składniki katalogu i wszelkie rozszerzenia plików, takie jak „.c”, oraz dołącza zwykły przyrostek obiektu platformy.

-MMDPrawdopodobnie chcesz skorzystać z (nieco nowszej) opcji. Dla kompletności przykład pliku makefile, który obsługuje wiele katalogów src i buduje katalogi z komentarzami. Prosta wersja bez katalogów kompilacji znajduje się w [ 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)

Ta metoda działa, ponieważ jeśli istnieje wiele linii zależności dla jednego celu, zależności są po prostu łączone, np .:

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

jest równa:

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

jak wspomniano w: Makefile wiele linii zależności dla jednego celu?

Zofia
źródło
1
Podoba mi się to rozwiązanie. Nie chcę wpisywać polecenia make depend. Przydatne !!
Robert
1
W wartości zmiennej OBJ jest błąd ortograficzny: CPPnależy przeczytaćCPPS
ctrucza
1
To jest moja preferowana odpowiedź; +1 dla Ciebie. To jedyne na tej stronie, które ma sens i obejmuje (z tego, co widzę) wszystkie sytuacje, w których konieczna jest ponowna kompilacja (unikanie niepotrzebnej kompilacji, ale wystarczająca)
Joost
1
Po wyjęciu z pudełka nie udało się zlokalizować dla mnie nagłówków, mimo że hpp i cpp są w tym samym katalogu.
villasv
1
jeśli masz swoje pliki źródłowe ( a.cpp, b.cpp) w ./src/, czy to podstawienie nie spowodowałoby $(OBJ)=./build/src/a.o ./build/src/b.o?
galois
26

Jak napisałem tutaj, gcc może tworzyć zależności i kompilować w tym samym czasie:

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

-include $(DEPS)

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

Parametr „-MF” określa plik, w którym mają być przechowywane zależności.

Myślnik na początku „-include” mówi Make, aby kontynuował, gdy plik .d nie istnieje (np. Przy pierwszej kompilacji).

Zauważ, że wydaje się, że w gcc jest błąd dotyczący opcji -o. Jeśli ustawisz nazwę pliku obiektu na obj / _file__c.o, to wygenerowany plik .d nadal będzie zawierał plik .o, a nie obj / _file__c.o.

Martin Fido
źródło
4
Kiedy próbuję tego, wszystkie moje pliki .o są tworzone jako puste pliki. Mam swoje obiekty w podfolderze kompilacji (więc $ OBJECTS zawiera build / main.o build / smbus.o build / etc ...) i to z pewnością tworzy pliki .d tak, jak opisałeś z widocznym błędem, ale z pewnością w ogóle nie buduje plików .o, podczas gdy robi to, jeśli usunę -MM i -MF.
bobpaul
1
Użycie -MT spowoduje rozwiązanie uwagi w ostatnich wierszach odpowiedzi, która aktualizuje cel każdej listy zależności.
Godric Seer
3
@bobpaul, ponieważ man gccmówi -MMimplikuje -E, co „zatrzymuje się po wstępnym przetworzeniu”. Zamiast -MMDtego potrzebujesz : stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
23

A może coś takiego:

includes = $(wildcard include/*.h)

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

Możesz również użyć symboli wieloznacznych bezpośrednio, ale zwykle okazuje się, że potrzebuję ich w więcej niż jednym miejscu.

Zauważ, że działa to dobrze tylko w małych projektach, ponieważ zakłada, że ​​każdy plik obiektowy zależy od każdego pliku nagłówkowego.

Michael Williamson
źródło
dzięki, okazało się, że jest to o wiele bardziej skomplikowane niż było to konieczne
Mike
15
To działa, jednak problem polega na tym, że każdy plik obiektowy jest rekompilowany za każdym razem, gdy wprowadzana jest mała zmiana, tj. Jeśli masz 100 plików źródłowych / nagłówkowych i wprowadzasz małą zmianę tylko w jednym, wszystkie 100 są rekompilowane .
Nicholas Hamilton
1
Naprawdę powinieneś zaktualizować swoją odpowiedź, aby powiedzieć, że jest to bardzo nieefektywny sposób, ponieważ odbudowuje WSZYSTKIE pliki za każdym razem, gdy zmieniany jest DOWOLNY plik nagłówkowy. Inne odpowiedzi są znacznie lepsze.
xaxxon
2
To bardzo złe rozwiązanie. Pewnie, że będzie działać na małym projekcie, ale dla zespołu o dowolnej wielkości produkcyjnej i kompilacji doprowadzi to do strasznego czasu kompilacji i stanie się odpowiednikiem uruchamiania za make clean allkażdym razem.
Julien Guertault
W moim teście to w ogóle nie działa. gccLinia nie jest wykonywany w ogóle, ale wbudowany reguły ( %o: %.creguła) jest wykonywany zamiast.
Penghe Geng
4

Powyższe rozwiązanie Martina działa świetnie, ale nie obsługuje plików .o, które znajdują się w podkatalogach. Godric zwraca uwagę, że flaga -MT rozwiązuje ten problem, ale jednocześnie zapobiega poprawnemu zapisaniu pliku .o. Następujące osoby zadbają o oba te problemy:

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

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<
Michael
źródło
3

To wykona zadanie dobrze, a nawet obsłuży określone podkatalogi:

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

przetestowałem to z gcc 4.8.3

g24l
źródło
3

Oto dwuwierszówka:

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

Działa to z domyślną recepturą na make, o ile masz listę wszystkich plików obiektów w OBJS .

tbodt
źródło
1

Wolę to rozwiązanie, zamiast zaakceptowanej odpowiedzi Michaela Williamsona, wychwytuje zmiany w źródłach + plikach wbudowanych, następnie źródłach + nagłówkach, a na końcu tylko źródłach. Zaletą jest to, że cała biblioteka nie jest ponownie kompilowana, jeśli wprowadzono tylko kilka zmian. Nie jest to wielka uwaga w przypadku projektu z kilkoma plikami, jeśli jednak masz 10 lub 100 źródeł, zauważysz różnicę.

COMMAND= gcc -Wall -Iinclude ...

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

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

%.o: %.cpp
    $(COMMAND)
Nicholas Hamilton
źródło
2
Działa to tylko wtedy, gdy w plikach nagłówkowych nie ma niczego, co wymagałoby rekompilacji jakichkolwiek plików cpp innych niż odpowiadający im plik implementacji.
matec
0

U mnie działa:

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

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<
Marcel Keller
źródło
0

Nieco zmodyfikowana wersja Sophie odpowiedź , która pozwala wysyłać * .d pliki do innego folderu (będę tylko wkleić interesującą część, która generuje pliki z zależnościami):

$(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 $@

Zwróć uwagę, że parametr

-MT $@

służy do zapewnienia, że ​​cele (tj. nazwy plików obiektów) w wygenerowanych plikach * .d zawierają pełną ścieżkę do plików * .o, a nie tylko nazwę pliku.

Nie wiem, dlaczego ten parametr NIE jest potrzebny, gdy używam -MMD w połączeniu z -c (jak w wersji Sophie ). W tej kombinacji wydaje się, że zapisuje pełną ścieżkę plików * .o do plików * .d. Bez tej kombinacji -MMD zapisuje również tylko czyste nazwy plików bez żadnych składników katalogów do plików * .d. Może ktoś wie, dlaczego -MMD zapisuje pełną ścieżkę w połączeniu z -c. Nie znalazłem żadnej wskazówki na stronie podręcznika g ++.

Maksymalna liczba klatek na sekundę
źródło