Makefiles mit Quelldateien in verschiedenen Verzeichnissen


135

Ich habe ein Projekt, in dem die Verzeichnisstruktur wie folgt ist:

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

Wie soll ich ein Makefile schreiben, das sich in part / src befindet (oder wo auch immer), das die c / c ++ - Quelldateien teilweise kompilieren / verknüpfen kann? / Src?

Kann ich so etwas wie -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Wenn das funktionieren würde, gibt es einen einfacheren Weg, dies zu tun. Ich habe Projekte gesehen, bei denen es in jedem entsprechenden Teil ein Makefile gibt. Ordner. [In diesem Beitrag habe ich das Fragezeichen wie in der Bash-Syntax verwendet]



1
Im Original-Gnu-Handbuch ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) unter Phony Targets finden Sie ein Beispiel recursive invocation, das sehr elegant sein kann.
Frank Nocke

Mit welchem ​​Tool haben Sie diese Textgrafik erstellt?
Khalid Hussain

Antworten:


114

Der traditionelle Weg ist haben Makefilein jedem der Unterverzeichnisse ( part1, part2usw.) so dass Sie sie unabhängig voneinander zu bauen. Haben Sie außerdem ein Makefileim Stammverzeichnis des Projekts, das alles erstellt. Die "Wurzel" Makefilewürde ungefähr so ​​aussehen:

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

Da jede Zeile in einem make-Ziel in einer eigenen Shell ausgeführt wird, müssen Sie sich keine Gedanken über das Durchlaufen des Verzeichnisbaums oder anderer Verzeichnisse machen.

Ich schlage vor, einen Blick auf das GNU-Handbuch 5.7 zu werfen . Es ist sehr hilfreich.


26
Dies sollte +$(MAKE) -C part1usw. sein . Dadurch kann die Jobsteuerung von Make in die Unterverzeichnisse eingearbeitet werden.
Ephemient

26
Dies ist ein klassischer Ansatz, der weit verbreitet ist, aber in mehrfacher Hinsicht nicht optimal ist und sich mit dem Wachstum des Projekts verschlechtert. Dave Hinton hat den Zeiger zu folgen.
dmckee --- Ex-Moderator Kätzchen

89

Wenn Sie Code in einem Unterverzeichnis haben, der von Code in einem anderen Unterverzeichnis abhängig ist, sind Sie wahrscheinlich mit einem einzelnen Makefile auf oberster Ebene besser dran.

Sehen rekursive Make schädlich für die vollen Gründe, aber im Grunde wollen Sie machen die vollständigen Informationen , um es zu entscheiden , ob benötigt oder eine Datei nicht wieder aufgebaut werden muss, und es wird nicht haben, wenn man ihm nur sagen , über ein Drittel dein Projekt.

Der obige Link scheint nicht erreichbar zu sein. Das gleiche Dokument ist hier erreichbar:


3
Danke, war mir dessen nicht bewusst. Es ist sehr nützlich, den "richtigen Weg" zu kennen, Dinge zu tun, anstatt Wege zu finden, die "nur funktionieren" oder als Standard akzeptiert werden.
Tjklemz

3
Rekursives Make wurde als schädlich angesehen, als es wirklich nicht gut funktionierte. Es wird heutzutage nicht als schädlich angesehen. Tatsächlich verwalten die Autotools / Automake größere Projekte.
Edwin Buck

36

Die VPATH-Option kann nützlich sein, um anzugeben, in welchen Verzeichnissen nach Quellcode gesucht werden soll. Sie benötigen jedoch weiterhin eine Option -I für jeden Include-Pfad. Ein Beispiel:

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

OutputExecutable: part1api.o part2api.o part3api.o

Dadurch werden die passenden partXapi.cpp-Dateien automatisch in einem der von VPATH angegebenen Verzeichnisse gefunden und kompiliert. Dies ist jedoch nützlicher, wenn Ihr src-Verzeichnis in Unterverzeichnisse unterteilt ist. Für das, was Sie beschreiben, wie andere gesagt haben, sind Sie wahrscheinlich besser dran, wenn Sie für jedes Teil ein Makefile erstellen, insbesondere wenn jedes Teil für sich allein stehen kann.


2
Ich kann nicht glauben, dass diese einfache, perfekte Antwort nicht mehr Stimmen hat. Es ist eine +1 von mir.
Nicholas Hamilton

2
Ich hatte einige gemeinsame Quelldateien in einem höheren Verzeichnis für mehrere unterschiedliche Projekte in Unterordnern, VPATH=..arbeitete für mich!
EkriirkE

23

Sie können Ihrem Root-Makefile Regeln hinzufügen, um die erforderlichen CPP-Dateien in anderen Verzeichnissen zu kompilieren. Das folgende Makefile-Beispiel sollte ein guter Anfang sein, um Sie dahin zu bringen, wo Sie sein möchten.

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

SOURCE = cppTest.cpp
SOURCE = $ (OTHERDIR) /file.cpp

## Quellendefinition beenden
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUDE = -I. $ (OTHERDIR) /../ inc
## Ende mehr enthält

VPATH = $ (OTHERDIR)
OBJ = $ (Join $ (Addsuffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## Das Abhängigkeitsziel wurde auf ../.dep relativ zum src-Verzeichnis festgelegt
DEPENDS = $ (join $ (fügt das Suffix ../.dep/ hinzu, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Standardregel ausgeführt
alle: $ (ZIEL)
        @wahr

## Regel reinigen
reinigen:
        @ -rm -f $ (ZIEL) $ (OBJ) $ (ABHÄNGIG)


## Regel zur Erstellung des eigentlichen Ziels
$ (ZIEL): $ (OBJ)
        @echo "============="
        @echo "Verknüpfen des Ziels $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Link fertig -

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


## Regeln für Objektdateien aus CPP-Dateien
## Die Objektdatei für jede Datei wird im obj-Verzeichnis abgelegt
## eine Ebene höher als das eigentliche Quellverzeichnis.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "$ <kompilieren"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Regel für "anderes Verzeichnis" Sie benötigen eine pro "anderes" Verzeichnis
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "$ <kompilieren"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Abhängigkeitsregeln erstellen
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Abhängigkeitsdatei für $ * erstellen. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Abhängigkeitsregel für "anderes" Verzeichnis
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Abhängigkeitsdatei für $ * erstellen. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Fügen Sie die Abhängigkeitsdateien hinzu
-include $ (ABHÄNGIG)


7
Ich weiß, dass dies mittlerweile ziemlich alt ist, aber werden SOURCE und INCLUDE nicht eine Zeile später durch eine andere Aufgabe überschrieben?
Skelliam

@ Skelliam ja, das tut es.
Nabroyan

1
Dieser Ansatz verstößt gegen das DRY-Prinzip. Es ist eine schlechte Idee, den Code für jedes "andere Verzeichnis" zu duplizieren
Jesus H

20

Wenn die Quellen in vielen Ordnern verteilt sind und es sinnvoll ist, einzelne Makefiles zu haben, ist rekursives Make, wie zuvor vorgeschlagen, ein guter Ansatz, aber für kleinere Projekte finde ich es einfacher, alle Quelldateien im Makefile mit ihrem relativen Pfad aufzulisten zum Makefile wie folgt :

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

Ich kann dann so einstellen VPATH:

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

Dann baue ich die Objekte:

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

Jetzt ist die Regel einfach:

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

Und das Erstellen der Ausgabe ist noch einfacher:

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

Man kann die VPATHErzeugung sogar automatisieren, indem man:

VPATH := $(dir $(COMMON_SRC))

Oder die Tatsache verwenden, dass sortDuplikate entfernt werden (obwohl dies keine Rolle spielen sollte):

VPATH := $(sort  $(dir $(COMMON_SRC)))

Dies eignet sich hervorragend für kleinere Projekte, bei denen Sie nur einige benutzerdefinierte Bibliotheken hinzufügen und in einem separaten Ordner speichern möchten. Vielen Dank
zitroneneis

6

Ich denke, es ist besser darauf hinzuweisen, dass die Verwendung von Make (rekursiv oder nicht) etwas ist, das Sie normalerweise vermeiden möchten, da es im Vergleich zu heutigen Tools schwierig ist, zu lernen, zu warten und zu skalieren.

Es ist ein wunderbares Tool, aber seine direkte Verwendung sollte ab 2010 als veraltet angesehen werden.

Es sei denn, Sie arbeiten in einer speziellen Umgebung, z. B. mit einem Legacy-Projekt usw.

Verwenden Sie eine IDE, CMake oder, wenn Sie hart im Nehmen sind, die Autotools .

(bearbeitet wegen Abstimmungen, ty Honza für den Hinweis)


1
Es wäre schön, wenn die Abstimmungen erklärt würden. Ich habe meine Makefiles auch selbst gemacht.
IlDan

2
Ich stimme für den Vorschlag "Mach das nicht" - KDevelop hat eine sehr grafische Oberfläche zum Konfigurieren von Make und anderen Build-Systemen, und während ich selbst Makefiles schreibe, gebe ich meinen Arbeitskollegen KDevelop - aber ich Ich glaube nicht, dass der Autotools-Link hilft. Um Autotools zu verstehen, müssen Sie m4, libtool, automake, autoconf, shell, make und im Grunde den gesamten Stack verstehen.
Ephemient

7
Die Abstimmungen sind wahrscheinlich darauf zurückzuführen, dass Sie Ihre eigene Frage beantworten. Die Frage war nicht, ob man Makefiles verwenden sollte oder nicht. Es ging darum, wie man sie schreibt.
Honza

8
Es wäre schön, wenn Sie in wenigen Sätzen erklären könnten, wie die modernen Tools das Leben einfacher machen als das Schreiben eines Makefiles. Bieten sie eine Abstraktion auf höherer Ebene? oder was?
Abhishek Anand

1
xterm, vi, bash, makefile == regiere die Welt, cmake ist für Leute, die keinen Code hacken können.
μολὼν.λαβέ

2

RCs Beitrag war SUPER nützlich. Ich habe nie darüber nachgedacht, die Funktion $ (dir $ @) zu verwenden, aber sie hat genau das getan, wofür ich sie brauchte.

Haben Sie in parentDir eine Reihe von Verzeichnissen mit Quelldateien: dirA, dirB, dirC. Verschiedene Dateien hängen von den Objektdateien in anderen Verzeichnissen ab, daher wollte ich in der Lage sein, eine Datei aus einem Verzeichnis heraus zu erstellen und diese Abhängigkeit durch Aufrufen des mit dieser Abhängigkeit verknüpften Makefiles herzustellen.

Im Wesentlichen habe ich ein Makefile in parentDir erstellt, das (unter anderem) eine generische Regel hatte, die der von RC ähnelt:

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

Jedes Unterverzeichnis enthielt dieses Makefile der oberen Ebene, um diese generische Regel zu erben. Im Makefile jedes Unterverzeichnisses habe ich für jede Datei eine benutzerdefinierte Regel geschrieben, damit ich alles verfolgen kann, von dem jede einzelne Datei abhängt.

Wann immer ich eine Datei erstellen musste, habe ich (im Wesentlichen) diese Regel verwendet, um rekursiv alle Abhängigkeiten zu erstellen. Perfekt!

HINWEIS: Es gibt ein Dienstprogramm namens "makepp", das diese Aufgabe noch intuitiver zu erledigen scheint. Aus Gründen der Portabilität und unabhängig von einem anderen Tool habe ich mich jedoch für diese Aufgabe entschieden.

Hoffe das hilft!


2

Rekursive Verwendung von Make

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

Dies ermöglicht makedie Aufteilung in Jobs und die Verwendung mehrerer Kerne


2
Wie ist das besser als so etwas zu tun make -j4?
Devin

1
@devin wie ich es sehe, ist es nicht besser , es ermöglicht nur diemake Jobsteuerung überhaupt. Wenn ein separater Make-Prozess ausgeführt wird, unterliegt er nicht der Jobkontrolle.
Michael Pankov

1

Ich habe nach so etwas gesucht und nach einigen Versuchen und Stürzen erstelle ich mein eigenes Makefile. Ich weiß, dass dies nicht der "idiomatische Weg" ist, aber es ist ein Anfang, Make zu verstehen, und das funktioniert für mich. Vielleicht könnten Sie es in Ihrem Projekt versuchen.

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!

Ich weiß, dass das einfach ist und für einige Leute sind meine Flags falsch, aber wie gesagt, dies ist mein erstes Makefile, das mein Projekt in mehreren Verzeichnissen kompiliert und dann alle miteinander verknüpft, um meinen Bin zu erstellen.

Ich akzeptiere Vorschläge: D.


-1

Ich schlage vor zu verwenden autotools:

//## Platzieren Sie generierte Objektdateien (.o) im selben Verzeichnis wie ihre Quelldateien, um Kollisionen zu vermeiden, wenn nicht rekursives make verwendet wird.

AUTOMAKE_OPTIONS = subdir-objects

nur Makefile.ammit den anderen ganz einfachen Sachen.

Hier ist das Tutorial .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.