Pliki Makefile z plikami źródłowymi w różnych katalogach

139

Mam projekt, w którym struktura katalogów wygląda następująco:

                         $projectroot
                              |
              +---------------+----------------+
              |               |                |
            part1/          part2/           part3/
              |               |                |
       +------+-----+     +---+----+       +---+-----+
       |      |     |     |        |       |         |
     data/   src/  inc/  src/     inc/   src/       inc/

Jak napisać plik makefile, który byłby w części / src (lub gdziekolwiek naprawdę), który mógłby w części zawierać / linkować pliki źródłowe c / c ++? / Src?

Czy mogę zrobić coś takiego jak -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Jeśli to zadziała, czy jest na to łatwiejszy sposób. Widziałem projekty, w których w każdej odpowiadającej części znajduje się plik makefile? lornetka składana. [w tym poście użyłem znaku zapytania jak w składni basha]

devin
źródło
1
W oryginalnym podręczniku GNU ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) pod Phony Targets znajduje się próbka recursive invocation, która jest dość elegancka.
Frank Nocke,
Jakiego narzędzia użyłeś do stworzenia tej grafiki tekstowej?
Khalid Hussain

Odpowiedzi:

116

Tradycyjnym sposobem jest mieć Makefilew każdym z podkatalogów ( part1, part2etc.) pozwalających budować je niezależnie. Co więcej, Makefilew katalogu głównym projektu, który wszystko buduje. „Katalog główny” Makefilewyglądałby mniej więcej tak:

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Ponieważ każda linia w make target jest uruchamiana we własnej powłoce, nie ma potrzeby martwić się o przechodzenie z kopii zapasowej drzewa katalogów lub do innych katalogów.

Proponuję zajrzeć do sekcji 5.7 podręcznika GNU make ; to bardzo pomocne.

akhan
źródło
26
Powinno być +$(MAKE) -C part1itd. Dzięki temu kontrola zadań programu Make może działać w podkatalogach.
ephemient
26
Jest to klasyczne podejście i jest szeroko stosowane, ale pod kilkoma względami jest nieoptymalne, które pogarsza się wraz z rozwojem projektu. Dave Hinton ma wskazówkę do naśladowania.
dmckee --- kociak byłego moderatora
90

Jeśli masz kod w jednym podkatalogu zależny od kodu w innym podkatalogu, prawdopodobnie lepiej będzie, jeśli masz pojedynczy plik makefile na najwyższym poziomie.

Pełne uzasadnienie można znaleźć w artykule Rekursywne uznawane za szkodliwe , ale zasadniczo chcesz mieć pełne informacje potrzebne do podjęcia decyzji, czy plik musi zostać odbudowany, a nie będzie, jeśli podasz mu tylko jedną trzecią Twój projekt.

Wydaje się, że powyższy link jest nieosiągalny. Ten sam dokument jest dostępny tutaj:

dave4420
źródło
3
Dziękuję, nie byłeś tego świadomy. Bardzo przydatna jest znajomość „właściwego sposobu” robienia rzeczy zamiast sposobów, które „po prostu działają” lub są akceptowane jako standardowe.
tjklemz
3
Marka rekurencyjna była uważana za szkodliwą, kiedy naprawdę nie działała dobrze. W dzisiejszych czasach nie jest to uważane za szkodliwe, w rzeczywistości jest to sposób, w jaki narzędzia automatyczne / automake zarządzają większymi projektami.
Edwin Buck
Od września 2020 r. Wszystkie te linki wydają się nieosiągalne. Można go znaleźć w Google
mizkichan
37

Przydać się może opcja VPATH, która mówi make, w jakich katalogach szukać kodu źródłowego. Nadal potrzebujesz opcji -I dla każdej ścieżki dołączania. Przykład:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc
VPATH=part1/src:part2/src:part3/src

OutputExecutable: part1api.o part2api.o part3api.o

Spowoduje to automatyczne wyszukanie pasujących plików partXapi.cpp w dowolnym z katalogów określonych przez VPATH i skompilowanie ich. Jest to jednak bardziej przydatne, gdy katalog src jest podzielony na podkatalogi. Z tego, co opisujesz, jak powiedzieli inni, prawdopodobnie lepiej będzie, jeśli masz plik makefile dla każdej części, zwłaszcza jeśli każda część może być samodzielna.

CodeGoat
źródło
2
Nie mogę uwierzyć, że ta prosta, idealna odpowiedź nie ma więcej głosów. To +1 ode mnie.
Nicholas Hamilton
2
Miałem kilka wspólnych plików źródłowych w wyższym katalogu dla kilku różnych projektów w podfolderach, VPATH=..pracowało dla mnie!
EkriirkE
24

Możesz dodać reguły do ​​głównego pliku Makefile, aby skompilować niezbędne pliki cpp w innych katalogach. Poniższy przykład pliku Makefile powinien być dobrym początkiem do przeniesienia Cię tam, gdzie chcesz.

CC = g ++
TARGET = cppTest
OTHERDIR = .. / .. / someotherpath / in / project / src

ŹRÓDŁO = cppTest.cpp
ŹRÓDŁO = $ (INNY KATALOG) /file.cpp

## Definicja źródeł końcowych
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUDE = -I. $ (OTHERDIR) /../ inc
## koniec więcej zawiera

VPATH = $ (OTHERDIR)
OBJ = $ (dołącz $ (addsuffix ../obj/, $ (dir $ (ŹRÓDŁO))), $ (notdir $ (ŹRÓDŁO: .cpp = .o))) 

## Napraw miejsce docelowe zależności na ../.dep względem katalogu src
DEPENDS = $ (dołącz $ (addsuffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (ŹRÓDŁO: .cpp = .d)))

## Domyślna reguła wykonana
all: $ (TARGET)
        @prawdziwe

## Czysta zasada
czysty:
        @ -rm -f $ (CEL) $ (OBJ) $ (ZALEŻY)


## Zasada określania rzeczywistego celu
$ (TARGET): $ (OBJ)
        @echo "============="
        @echo "Łączenie celu $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Link zakończony -

## Ogólna reguła kompilacji
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo „Kompilowanie $ <”
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Reguły dla plików obiektowych z plików cpp
## Plik obiektu dla każdego pliku jest umieszczany w katalogu obj
## jeden poziom wyżej od rzeczywistego katalogu źródłowego.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo „Kompilowanie $ <”
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Reguła dla „innego katalogu” Będziesz potrzebować jednej dla każdego „innego” katalogu
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo „Kompilowanie $ <”
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Stwórz reguły zależności
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Tworzenie pliku zależności dla $ *. o
        @ $ (POWŁOKA) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Reguła zależności dla „innego” katalogu
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Tworzenie pliku zależności dla $ *. o
        @ $ (POWŁOKA) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Uwzględnij pliki zależności
-włącz $ (ZALEŻY)

RC.
źródło
7
Wiem, że jest to już dość stare, ale czy zarówno ŹRÓDŁO, jak i INCLUDE nie zostały nadpisane przez inne przypisanie jeden wiersz później?
skelliam
@skelliam yes, to robi.
nabroyan
1
Takie podejście narusza zasadę DRY. Duplikowanie kodu dla każdego „innego katalogu” jest złym pomysłem
Jesus H
21

Jeśli źródła są rozrzucone w wielu folderach i ma sens posiadanie indywidualnych plików Makefile to jak wcześniej zasugerowano, rekurencyjne make jest dobrym podejściem, ale w przypadku mniejszych projektów łatwiej jest wypisać wszystkie pliki źródłowe w Makefile wraz z ich względną ścieżką do pliku Makefile w ten sposób:

# common sources
COMMON_SRC := ./main.cpp \
              ../src1/somefile.cpp \
              ../src1/somefile2.cpp \
              ../src2/somefile3.cpp \

Mogę wtedy ustawić w VPATHten sposób:

VPATH := ../src1:../src2

Następnie buduję obiekty:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC)))

Teraz zasada jest prosta:

# the "common" object files
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile
    @echo creating $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

Tworzenie wyniku jest jeszcze łatwiejsze:

# This will make the cbsdk shared library
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS)
    @echo building output ...
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS)

Można nawet VPATHzautomatyzować generowanie poprzez:

VPATH := $(dir $(COMMON_SRC))

Lub korzystając z faktu, że sortusuwa duplikaty (chociaż nie powinno to mieć znaczenia):

VPATH := $(sort  $(dir $(COMMON_SRC)))
kreskowaty
źródło
działa to świetnie w przypadku mniejszych projektów, w których chcesz po prostu dodać kilka niestandardowych bibliotek i umieścić je w oddzielnym folderze. Dziękuję bardzo
zitroneneis
6

Myślę, że lepiej jest zauważyć, że używanie Make (rekurencyjnego lub nie) jest czymś, czego zwykle warto unikać, ponieważ w porównaniu z dzisiejszymi narzędziami trudno jest się go nauczyć, utrzymać i skalować.

To wspaniałe narzędzie, ale jego bezpośrednie użycie należy uznać za przestarzałe w 2010+.

Chyba że, oczywiście, pracujesz w specjalnym środowisku, np. Z projektem starszego typu itp.

Użyj IDE, CMake lub, jeśli masz twardy rdzeń, Autotools .

(zredagowane z powodu głosów przeciw, ty Honza za wskazanie)

IlDan
źródło
1
Byłoby miło, gdyby wyjaśniono głosy przeciw. Sam też tworzyłem swoje pliki Makefile.
IlDan
2
Głosuję za sugestią „nie rób tego” - KDevelop ma bardzo graficzny interfejs do konfigurowania Make i innych systemów kompilacji, i chociaż sam piszę Makefiles, KDevelop jest tym, co daję moim współpracownikom - ale ja nie myśl, że link Autotools pomaga. Aby zrozumieć Autotools, musisz zrozumieć m4, libtool, automake, autoconf, shell, make iw zasadzie cały stos.
ephemient
7
Głosy negatywne są prawdopodobnie spowodowane tym, że odpowiadasz na własne pytanie. Pytanie nie dotyczyło tego, czy należy używać Makefiles. Chodziło o to, jak je napisać.
Honza,
8
byłoby miło, gdybyś mógł w kilku zdaniach wyjaśnić, w jaki sposób nowoczesne narzędzia ułatwiają życie niż pisanie pliku Makefile. Czy zapewniają wyższą abstrakcję? albo co?
Abhishek Anand
1
xterm, vi, bash, makefile == rządzą światem, cmake jest dla ludzi, którzy nie potrafią hakować kodu.
μολὼν.λαβέ
2

Post RC był BARDZO przydatny. Nigdy nie myślałem o użyciu funkcji $ (dir $ @), ale zrobiła dokładnie to, czego potrzebowałem.

W parentDir umieść kilka katalogów z plikami źródłowymi: dirA, dirB, dirC. Różne pliki zależą od plików obiektowych w innych katalogach, więc chciałem móc utworzyć jeden plik z jednego katalogu i sprawić, by zależało od niego, wywołując plik makefile powiązany z tą zależnością.

Zasadniczo utworzyłem jeden plik Makefile w parentDir, który miał (między innymi) ogólną regułę podobną do RC:

%.o : %.cpp
        @mkdir -p $(dir $@)
        @echo "============="
        @echo "Compiling $<"
        @$(CC) $(CFLAGS) -c $< -o $@

Każdy podkatalog zawierał plik makefile wyższego poziomu w celu dziedziczenia tej ogólnej reguły. W pliku Makefile każdego podkatalogu napisałem niestandardową regułę dla każdego pliku, dzięki czemu mogłem śledzić wszystko, od czego zależał każdy plik.

Ilekroć potrzebowałem utworzyć plik, użyłem (zasadniczo) tej reguły, aby rekurencyjnie utworzyć jakiekolwiek / wszystkie zależności. Idealny!

UWAGA: istnieje narzędzie o nazwie „makepp”, które wydaje się wykonywać to zadanie jeszcze bardziej intuicyjnie, ale ze względu na przenośność i niezależność od innego narzędzia, zdecydowałem się to zrobić w ten sposób.

Mam nadzieję że to pomoże!

jvriesem
źródło
2

Rekurencyjne użycie Make

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Pozwala to na makepodział na zadania i wykorzystanie wielu rdzeni

EhevuTov
źródło
2
Jak to jest lepsze niż robienie czegoś takiego make -j4?
devin
1
@devin jak ja to widzę, nie jest lepiej , po prostu pozwala make w ogóle sterować pracą. W przypadku wykonywania oddzielnego procesu make nie podlega on kontroli zadań.
Michael Pankov
2

Szukałem czegoś takiego i po kilku próbach i upadkach tworzę własny plik makefile, wiem, że to nie jest „idiomatyczny sposób”, ale to początek zrozumienia make i to działa dla mnie, może mógłbyś spróbować w swoim projekcie.

PROJ_NAME=mono

CPP_FILES=$(shell find . -name "*.cpp")

S_OBJ=$(patsubst %.cpp, %.o, $(CPP_FILES))

CXXFLAGS=-c \
         -g \
        -Wall

all: $(PROJ_NAME)
    @echo Running application
    @echo
    @./$(PROJ_NAME)

$(PROJ_NAME): $(S_OBJ)
    @echo Linking objects...
    @g++ -o $@ $^

%.o: %.cpp %.h
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS) -o $@

main.o: main.cpp
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS)

clean:
    @echo Removing secondary things
    @rm -r -f objects $(S_OBJ) $(PROJ_NAME)
    @echo Done!

Wiem, że to proste i dla niektórych ludzi moje flagi są błędne, ale jak powiedziałem, jest to mój pierwszy plik Makefile, w którym można skompilować mój projekt w wielu katalogach i połączyć je wszystkie razem, aby utworzyć mój bin.

Przyjmuję sugestie: D

Matheus Vinícius de Andrade
źródło
-1

Proponuję użyć autotools:

//## Umieść wygenerowane pliki obiektów (.o) w tym samym katalogu, co ich pliki źródłowe, aby uniknąć kolizji, gdy używany jest nierekurencyjny make.

AUTOMAKE_OPTIONS = subdir-objects

po prostu dołączając go Makefile.amdo innych dość prostych rzeczy.

Oto samouczek .

user2763812
źródło