Makefiles avec des fichiers source dans différents répertoires

135

J'ai un projet où la structure du répertoire est comme ceci:

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

Comment dois-je écrire un makefile qui serait dans la partie / src (ou n'importe où vraiment) qui pourrait compléter / lien sur les fichiers source c / c ++ en partie? / Src?

Puis-je faire quelque chose comme -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Si cela fonctionne, y a-t-il un moyen plus simple de le faire. J'ai vu des projets où il y a un makefile dans chacune des parties correspondantes? Dossiers. [dans cet article, j'ai utilisé le point d'interrogation comme dans la syntaxe bash]

devin
la source
1
Dans le manuel original de gnu ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) sous Phony Targets, il y a un exemple sur recursive invocation, qui peut être assez élégant.
Frank Nocke
Quel outil avez-vous utilisé pour créer ce graphique de texte?
Khalid Hussain

Réponses:

114

La manière traditionnelle est d'avoir un Makefiledans chacun des sous - répertoires ( part1, part2, etc.) vous permettant de les construire de manière indépendante. De plus, ayez un Makefiledans le répertoire racine du projet qui construit tout. La "racine" Makefileressemblerait à ceci:

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

Puisque chaque ligne dans une cible de création est exécutée dans son propre shell, il n'y a pas besoin de s'inquiéter de la remontée de l'arborescence de répertoires ou vers d'autres répertoires.

Je suggère de jeter un œil à la section 5.7 du manuel de création de GNU ; c'est très utile.

Akhan
la source
26
Cela devrait être +$(MAKE) -C part1etc. Cela permet au contrôle des tâches de Make de fonctionner dans les sous-répertoires.
éphémère
26
Il s'agit d'une approche classique et largement utilisée, mais elle est sous-optimale de plusieurs manières qui s'aggravent à mesure que le projet se développe. Dave Hinton a le pointeur à suivre.
dmckee --- ex-moderator chaton
89

Si vous avez du code dans un sous-répertoire dépendant du code dans un autre sous-répertoire, vous êtes probablement mieux avec un seul makefile au niveau supérieur.

Voir Récursif Rendre considéré comme nocif pour la justification complète, mais en gros, vous voulez faire en sorte d'avoir toutes les informations dont il a besoin pour décider si un fichier doit être reconstruit ou non, et il ne l'aura pas si vous lui dites seulement environ un tiers de votre projet.

Le lien ci-dessus ne semble pas accessible. Le même document est accessible ici:

dave4420
la source
3
Merci, n'était pas au courant de cela. Il est très utile de connaître la «bonne façon» de faire les choses plutôt que les méthodes qui «fonctionnent» ou qui sont acceptées comme standard.
tjklemz
3
La création récursive était considérée comme nuisible, à l'époque où elle ne fonctionnait vraiment pas bien. Ce n'est pas considéré comme dangereux de nos jours, en fait, c'est la façon dont les autotools / automake gèrent des projets plus importants.
Edwin Buck
36

L'option VPATH peut s'avérer utile, car elle indique à make quels répertoires rechercher le code source. Cependant, vous aurez toujours besoin d'une option -I pour chaque chemin d'inclusion. Un exemple:

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

OutputExecutable: part1api.o part2api.o part3api.o

Cela trouvera automatiquement les fichiers partXapi.cpp correspondants dans l'un des répertoires spécifiés par VPATH et les compilera. Cependant, cela est plus utile lorsque votre répertoire src est divisé en sous-répertoires. Pour ce que vous décrivez, comme d'autres l'ont dit, vous êtes probablement mieux avec un makefile pour chaque partie, surtout si chaque partie peut être autonome.

CodeGoat
la source
2
Je ne peux pas croire que cette simple réponse parfaite n'ait pas obtenu plus de votes. C'est un +1 de moi.
Nicholas Hamilton
2
J'avais des fichiers sources communs dans un répertoire supérieur pour plusieurs projets disparates dans des sous-dossiers, cela a VPATH=..fonctionné pour moi!
EkriirkE
23

Vous pouvez ajouter des règles à votre Makefile racine afin de compiler les fichiers cpp nécessaires dans d'autres répertoires. L'exemple Makefile ci-dessous devrait être un bon début pour vous amener là où vous voulez être.

CC = g ++
TARGET = cppTest
OTHERDIR = .. / .. / un autre chemin / dans / projet / src

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

## Définition des sources de fin
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUDE = -I. $ (OTHERDIR) /../ inc
## end more comprend

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

## Correction de la destination de dépendance comme étant ../.dep par rapport au répertoire src
DEPENDS = $ (join $ (ajouteuffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Règle par défaut exécutée
tout: $ (TARGET)
        @vrai

## Règle propre
nettoyer:
        @ -rm -f $ (TARGET) $ (OBJ) $ (DEPEND)


## Règle de création de la cible réelle
$ (CIBLE): $ (OBJ)
        @echo "============="
        @echo "Lier la cible $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Lien terminé -

## Règle de compilation générique
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilation de $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Règles pour les fichiers objets des fichiers cpp
## Le fichier objet de chaque fichier est placé dans le répertoire obj
## un niveau au-dessus du répertoire source réel.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilation de $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Règle pour "autre répertoire" Vous en aurez besoin d'une par "autre" répertoire
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilation de $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Définir des règles de dépendance
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Construire un fichier de dépendances pour $ *. o
        @ $ (COQUE) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Règle de dépendance pour "autre" répertoire
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Construire un fichier de dépendances pour $ *. o
        @ $ (COQUE) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (AUTRE REP) /../ obj / $ *. o ^"> $ @ '

## Inclut les fichiers de dépendance
-inclut $ (DÉPEND)

RC.
la source
7
Je sais que c'est assez vieux maintenant mais, SOURCE et INCLUDE ne sont-ils pas tous les deux remplacés par une autre affectation une ligne plus tard?
skelliam
@skelliam oui, c'est le cas.
nabroyan
1
Cette approche viole le principe DRY. C'est une mauvaise idée de dupliquer le code pour chaque "autre répertoire"
Jesus H
20

Si les sources sont réparties dans de nombreux dossiers et qu'il est logique d'avoir des Makefiles individuels, comme suggéré précédemment, la création récursive est une bonne approche, mais pour les petits projets, je trouve plus facile de lister tous les fichiers source dans le Makefile avec leur chemin relatif au Makefile comme ceci:

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

Je peux ensuite définir de VPATHcette façon:

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

Ensuite, je construis les objets:

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

Maintenant, la règle est simple:

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

Et la création de la sortie est encore plus facile:

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

On peut même VPATHautomatiser la génération en:

VPATH := $(dir $(COMMON_SRC))

Ou en utilisant le fait qui sortsupprime les doublons (bien que cela ne devrait pas avoir d'importance):

VPATH := $(sort  $(dir $(COMMON_SRC)))
dashesy
la source
cela fonctionne très bien pour les petits projets où vous souhaitez simplement ajouter des bibliothèques personnalisées et les avoir dans un dossier séparé. Merci beaucoup
zitroneneis
6

Je pense qu'il est préférable de souligner que l'utilisation de Make (récursif ou non) est quelque chose que vous voudrez peut-être généralement éviter, car par rapport aux outils d'aujourd'hui, il est difficile à apprendre, à maintenir et à faire évoluer.

C'est un outil merveilleux mais son utilisation directe devrait être considérée comme obsolète en 2010+.

Sauf si, bien sûr, vous travaillez dans un environnement spécial, c'est-à-dire avec un projet hérité, etc.

Utilisez un IDE, CMake ou, si vous êtes un noyau dur, les Autotools .

(édité en raison de votes négatifs, ty Honza pour le souligner)

IlDan
la source
1
Ce serait bien que les votes négatifs soient expliqués. J'avais aussi l'habitude de faire mes Makefiles moi-même.
IlDan
2
Je vote pour la suggestion «ne faites pas ça» - KDevelop a une interface très graphique pour configurer Make et d'autres systèmes de construction, et pendant que j'écris des Makefiles moi-même, KDevelop est ce que je donne à mes collègues - mais je ne pensez pas que le lien Autotools aide. Pour comprendre Autotools, vous devez comprendre m4, libtool, automake, autoconf, shell, make et, en gros, toute la pile.
éphémère
7
Les votes négatifs sont probablement dus au fait que vous répondez à votre propre question. La question n'était pas de savoir s'il fallait ou non utiliser des Makefiles. Il s'agissait de savoir comment les écrire.
Honza le
8
ce serait bien si vous pouviez, en quelques phrases, expliquer comment les outils modernes facilitent la vie que l'écriture d'un Makefile. Fournissent-ils une abstraction de plus haut niveau? ou quoi?
Abhishek Anand
1
xterm, vi, bash, makefile == règne sur le monde, cmake est destiné aux personnes qui ne peuvent pas pirater le code.
μολὼν.λαβέ
2

Le message de RC a été SUPER utile. Je n'ai jamais pensé à utiliser la fonction $ (dir $ @), mais elle a fait exactement ce dont j'avais besoin.

Dans parentDir, ayez un tas de répertoires contenant des fichiers source: dirA, dirB, dirC. Différents fichiers dépendent des fichiers objet dans d'autres répertoires, donc je voulais pouvoir créer un fichier à partir d'un répertoire et le faire créer cette dépendance en appelant le makefile associé à cette dépendance.

Essentiellement, j'ai créé un Makefile dans parentDir qui avait (parmi beaucoup d'autres choses) une règle générique similaire à celle de RC:

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

Chaque sous-répertoire incluait ce makefile de niveau supérieur afin d'hériter de cette règle générique. Dans le Makefile de chaque sous-répertoire, j'ai écrit une règle personnalisée pour chaque fichier afin de pouvoir garder une trace de tout ce dont chaque fichier individuel dépendait.

Chaque fois que j'avais besoin de créer un fichier, j'utilisais (essentiellement) cette règle pour créer de manière récursive toutes les dépendances. Parfait!

NOTE: il y a un utilitaire appelé "makepp" qui semble faire cette tâche même de manière encore plus intuitive, mais pour des raisons de portabilité et ne dépendant pas d'un autre outil, j'ai choisi de le faire de cette façon.

J'espère que cela t'aides!

jvriesem
la source
2

Utilisation récursive de Make

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

Cela permet de makese diviser en travaux et d'utiliser plusieurs cœurs

EhevuTov
la source
2
En quoi est-ce mieux que de faire quelque chose comme ça make -j4?
devin
1
@devin comme je le vois, ce n'est pas mieux , cela permet simplement maked'utiliser le contrôle des tâches. Lors de l'exécution d'un processus de fabrication distinct, il n'est pas soumis au contrôle des travaux.
Michael Pankov
1

Je cherchais quelque chose comme ça et après quelques essais et chutes je crée mon propre makefile, je sais que ce n'est pas la "manière idiomatique" mais c'est un début pour comprendre make et ça marche pour moi, peut-être que tu pourrais essayer dans ton projet.

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!

Je sais que c'est simple et pour certaines personnes, mes indicateurs sont faux, mais comme je l'ai dit, c'est mon premier Makefile à compiler mon projet dans plusieurs répertoires et à lier le tout ensemble pour créer mon bac.

J'accepte les suggestions: D

Matheus Vinícius de Andrade
la source
-1

Je suggère d'utiliser autotools:

//## Placez les fichiers objets générés (.o) dans le même répertoire que leurs fichiers source, afin d'éviter les collisions lorsque make non récursif est utilisé.

AUTOMAKE_OPTIONS = subdir-objects

juste l'inclure dans Makefile.amles autres trucs assez simples.

Voici le tutoriel .

user2763812
la source