Makefile con file sorgente in diverse directory


135

Ho un progetto in cui la struttura delle directory è così:

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

Come dovrei scrivere un makefile che sarebbe in parte / src (o dovunque davvero) che potrebbe compilare / linkare in parte i file sorgente c / c ++? / Src?

Posso fare qualcosa come -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Se funzionasse, c'è un modo più semplice per farlo. Ho visto progetti in cui è presente un makefile in ciascuna delle parti corrispondenti? cartelle. [in questo post ho usato il punto interrogativo come nella sintassi bash]



1
Nel manuale originale di gnu ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) sotto Phony Targets c'è un esempio su recursive invocation, che è abbastanza elegante.
Frank Nocke,

Quale strumento hai usato per creare quella grafica di testo?
Khalid Hussain,

Risposte:


114

Il modo tradizionale è quello di avere un Makefilein ciascuna delle sottodirectory ( part1, part2, ecc) che consente di costruire in modo indipendente. Inoltre, avere un Makefilenella directory principale del progetto che costruisce tutto. Il "root" Makefilesarebbe simile al seguente:

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

Poiché ogni riga in un target di make viene eseguita nella propria shell, non è necessario preoccuparsi di attraversare il backup dell'albero delle directory o verso altre directory.

Suggerisco di dare un'occhiata al manuale GNU make sezione 5.7 ; è molto utile


26
Questo dovrebbe essere +$(MAKE) -C part1ecc. Ciò consente al controllo del lavoro di Make di operare nelle sottodirectory.
effimero

26
Questo è un approccio classico ed è ampiamente usato, ma è subottimale in diversi modi che peggiorano man mano che il progetto cresce. Dave Hinton ha il puntatore da seguire.
dmckee --- ex-moderatore gattino

89

Se hai un codice in una sottodirectory dipendente dal codice in un'altra sottodirectory, probabilmente stai meglio con un singolo makefile al livello superiore.

Vedi Ricorsivo Rendi considerato dannoso per la logica completa, ma fondamentalmente vuoi fare in modo di avere tutte le informazioni necessarie per decidere se un file deve essere ricostruito o meno e non lo avrà se lo dici solo su un terzo di il tuo progetto.

Il link sopra sembra non essere raggiungibile. Lo stesso documento è raggiungibile qui:


3
Grazie, non ne ero a conoscenza. È molto utile conoscere il "modo giusto" di fare le cose invece di modi che "funzionano" o sono accettati come standard.
tjklemz,

3
La marca ricorsiva era considerata dannosa, quando in realtà non funzionava bene. Oggigiorno non è considerato dannoso, infatti, è il modo in cui gli autotools / automake gestiscono progetti più grandi.
Edwin Buck,

36

L'opzione VPATH potrebbe rivelarsi utile, che indica quali directory cercare il codice sorgente. Tuttavia, avresti comunque bisogno di un'opzione -I per ogni percorso di inclusione. Un esempio:

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

OutputExecutable: part1api.o part2api.o part3api.o

Questo troverà automaticamente i file partXapi.cpp corrispondenti in una qualsiasi delle directory specificate da VPATH e li compilerà. Tuttavia, questo è più utile quando la directory src è suddivisa in sottodirectory. Per quello che descrivi, come altri hanno già detto, probabilmente stai meglio con un makefile per ogni parte, specialmente se ogni parte può essere isolata.


2
Non posso credere che questa semplice risposta perfetta non abbia ottenuto più voti. È un +1 da parte mia.
Nicholas Hamilton,

2
Avevo alcuni file sorgente comuni in una directory superiore per diversi progetti disparati in sottocartelle, VPATH=..ho lavorato per me!
EkriirkE,

23

Puoi aggiungere regole al tuo Makefile di root per compilare i file cpp necessari in altre directory. L'esempio Makefile di seguito dovrebbe essere un buon inizio per portarti dove vuoi essere.

CC = g ++
TARGET = cppTest
Otherdir = .. / .. / someotherpath / in / progetto / src

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

## Termina la definizione delle fonti
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUDE = -I. $ (OTHERDIR) /../ inc
## fine include più

VPATH = $ (otherdir)
OBJ = $ (join $ (aggiungiuffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## Correggi la destinazione della dipendenza in modo che sia ../.dep relativa alla directory src
DEPENDS = $ (join $ (aggiungiuffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Regola predefinita eseguita
tutto: $ (TARGET)
        @vero

## Regola pulita
pulito:
        @ -rm -f $ (TARGET) $ (OBJ) $ (DIPENDE)


## Regola per fare il bersaglio reale
$ (TARGET): $ (OBJ)
        @echo "============="
        @echo "Collegamento dell'obiettivo $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Link terminato -

## Regola di compilazione generica
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilazione $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Regole per i file oggetto da file cpp
## Il file oggetto per ogni file viene inserito nella directory obj
## a un livello superiore dalla directory di origine effettiva.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilazione $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Regola per "altra directory" Ne occorrerà una per "altra" dir
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilazione $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Crea regole di dipendenza
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Creazione del file delle dipendenze per $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Regola di dipendenza per la directory "altro"
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Creazione del file delle dipendenze per $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Includi i file delle dipendenze
-include $ (DIPENDE)


7
So che ormai è piuttosto vecchio, ma SOURCE e INCLUDE non vengono sovrascritti da un altro compito una riga dopo?
skelliam,

@skelliam, sì.
nabroyan,

1
Questo approccio viola il principio DRY. È una cattiva idea duplicare il codice per ogni "altra directory"
Gesù H

20

Se i sorgenti sono distribuiti in molte cartelle e ha senso avere singoli Makefile, come suggerito prima, make ricorsivo è un buon approccio, ma per i progetti più piccoli trovo più semplice elencare tutti i file sorgente nel Makefile con il loro percorso relativo al Makefile in questo modo:

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

Posso quindi impostare in VPATHquesto modo:

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

Quindi costruisco gli oggetti:

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

Ora la regola è semplice:

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

E costruire l'output è ancora più semplice:

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

Si può anche rendere VPATHautomatizzata la generazione:

VPATH := $(dir $(COMMON_SRC))

O usando il fatto che sortrimuove i duplicati (anche se non dovrebbe importare):

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

funziona benissimo per i progetti più piccoli in cui desideri solo aggiungere alcune librerie personalizzate e inserirle in una cartella separata. Grazie mille
zitroneneis

6

Penso che sia meglio sottolineare che l'uso di Make (ricorsivo o no) è qualcosa che di solito potresti voler evitare, perché rispetto agli strumenti di oggi, è difficile da imparare, mantenere e ridimensionare.

È uno strumento meraviglioso ma il suo uso diretto dovrebbe essere considerato obsoleto nel 2010+.

A meno che, ovviamente, non lavori in un ambiente speciale, ad esempio con un progetto legacy, ecc.

Utilizzare un IDE, CMake o, se si è fortemente animati , gli Autotools .

(modificato a causa di downvotes, ty Honza per la precisazione)


1
Sarebbe bello avere la spiegazione dei voti negativi. Anche io creavo i miei Makefile.
IlDan,

2
Sto votando per il suggerimento "non farlo" - KDevelop ha un'interfaccia molto grafica per configurare Make e altri sistemi di compilazione, e mentre scrivo da solo Makefile, KDevelop è ciò che do ai miei compagni di lavoro - ma io non pensare che il link Autotools sia d'aiuto. Per comprendere gli autotools, è necessario comprendere m4, libtool, automake, autoconf, shell, make e sostanzialmente l'intero stack.
effimero il

7
I voti negativi sono probabilmente perché stai rispondendo alla tua domanda. La domanda non era se si dovessero usare i Makefile. Si trattava di come scriverli.
Honza,

8
sarebbe bello se potessi, in poche frasi, spiegare come gli strumenti moderni semplificano la vita che scrivere un Makefile. Forniscono un'astrazione di livello superiore? o cosa?
Abhishek Anand,

1
xterm, vi, bash, makefile == governano il mondo, cmake è per le persone che non possono hackerare il codice.
μολὼν.λαβέ

2

Il post di RC è stato SUPER utile. Non ho mai pensato di usare la funzione $ (dir $ @), ma ha fatto esattamente quello di cui avevo bisogno.

In parentDir, avere un sacco di directory con i file sorgente in esse: dirA, dirB, dirC. Vari file dipendono dai file oggetto in altre directory, quindi volevo essere in grado di creare un file all'interno di una directory e fare in modo che dipendesse chiamando il makefile associato a quella dipendenza.

In sostanza, ho creato un Makefile in parentDir che aveva (tra le altre cose) una regola generica simile a quella di RC:

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

Ogni sottodirectory includeva questo makefile di livello superiore per ereditare questa regola generica. Nel Makefile di ogni sottodirectory, ho scritto una regola personalizzata per ogni file in modo da poter tenere traccia di tutto ciò da cui dipendeva ogni singolo file.

Ogni volta che dovevo creare un file, ho usato (essenzialmente) questa regola per fare in modo ricorsivo tutte le dipendenze. Perfetto!

NOTA: esiste un'utilità chiamata "makepp" che sembra svolgere questo compito in modo ancora più intuitivo, ma per motivi di portabilità e non a seconda di un altro strumento, ho scelto di farlo in questo modo.

Spero che questo ti aiuti!


2

Uso ricorsivo di Make

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

Ciò consente makedi suddividere in lavori e utilizzare più core


2
In che modo è meglio che fare qualcosa del genere make -j4?
Devin,

1
@devin come la vedo io, non è meglio , consente solo makedi utilizzare il controllo del lavoro. Quando si esegue un processo separato di make, non è soggetto al controllo del lavoro.
Michael Pankov

1

Stavo cercando qualcosa del genere e dopo alcuni tentativi ho creato il mio makefile, so che non è il "modo idiomatico" ma è un inizio per capire il make e questo funziona per me, forse potresti provare nel tuo progetto.

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!

So che è semplice e per alcune persone le mie bandiere sono sbagliate, ma come ho già detto questo è il mio primo Makefile per compilare il mio progetto in più dir e collegarli tutti insieme per creare il mio cestino.

Accetto le sugestions: D


-1

Suggerisco di usare autotools:

//## Posizionare i file degli oggetti generati (.o) nella stessa directory dei loro file di origine, al fine di evitare collisioni quando si utilizza make non ricorsivo.

AUTOMAKE_OPTIONS = subdir-objects

includendolo semplicemente Makefile.amcon le altre cose abbastanza semplici.

Ecco il tutorial .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.