Créer des répertoires à l'aide de make file

101

Je suis un tout nouveau makefiles et je veux créer des répertoires en utilisant makefile. Mon répertoire de projet est comme ça

+--Project  
   +--output  
   +--source  
     +Testfile.cpp  
   +Makefile  

Je veux mettre tous les objets et la sortie dans le dossier de sortie respectif. Je veux créer une structure de dossier qui ressemblerait à ceci après la compilation.

+--Project
   +--output
     +--debug (or release)
       +--objs
         +Testfile.o
       +Testfile (my executable file)
   +--source
     +Testfile.cpp
   +Makefile

J'ai essayé avec plusieurs options, mais je n'ai pas réussi. Aidez-moi à créer des répertoires en utilisant make file. Je publie mon Makefile pour votre considération.

#---------------------------------------------------------------------
# Input dirs, names, files
#---------------------------------------------------------------------
OUTPUT_ROOT := output/

TITLE_NAME := TestProj 

ifdef DEBUG 
    TITLE_NAME += _DEBUG
else
ifdef RELEASE
    TITLE_NAME += _RELEASE
endif
endif


# Include all the source files here with the directory tree
SOURCES := \
        source/TestFile.cpp \

#---------------------------------------------------------------------
# configs
#---------------------------------------------------------------------
ifdef DEBUG
OUT_DIR     := $(OUTPUT_ROOT)debug
CC_FLAGS    := -c -Wall
else
ifdef RELEASE
OUT_DIR     := $(OUTPUT_ROOT)release
CC_FLAGS    := -c -Wall
else
$(error no build type defined)
endif
endif

# Put objects in the output directory.
OUT_O_DIR   := $(OUT_DIR)/objs

#---------------------------------------------------------------------
# settings
#---------------------------------------------------------------------
OBJS = $(SOURCES:.cpp=.o)
DIRS = $(subst /,/,$(sort $(dir $(OBJS))))
DIR_TARGET = $(OUT_DIR)

OUTPUT_TARGET = $(OUT_DIR)/$(TITLE_NAME)

CC_FLAGS +=   

LCF_FLAGS := 

LD_FLAGS := 

#---------------------------------------------------------------------
# executables
#---------------------------------------------------------------------
MD := mkdir
RM := rm
CC := g++

#---------------------------------------------------------------------
# rules
#---------------------------------------------------------------------
.PHONY: all clean title 

all: title 

clean:
    $(RM) -rf $(OUT_DIR)

$(DIR_TARGET):
    $(MD) -p $(DIRS)

.cpp.o: 
    @$(CC) -c $< -o $@

$(OBJS): $(OUT_O_DIR)/%.o: %.cpp
    @$(CC) -c $< -o $@

title: $(DIR_TARGET) $(OBJS)

Merci d'avance. Veuillez me guider si j'ai également fait des erreurs.

Jabez
la source

Réponses:

88

Cela le ferait - en supposant un environnement de type Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

${OUT_DIR}:
        ${MKDIR_P} ${OUT_DIR}

Cela devrait être exécuté dans le répertoire de niveau supérieur - ou la définition de $ {OUT_DIR} devrait être correcte par rapport à l'endroit où il est exécuté. Bien sûr, si vous suivez les édits de l'article " Recursive Make Considered Harmful " de Peter Miller , vous exécuterez de toute façon make dans le répertoire de niveau supérieur.

Je joue avec ça (RMCH) pour le moment. Il a fallu un peu d'adaptation à la suite de logiciels que j'utilise comme terrain d'essai. La suite a une douzaine de programmes distincts construits avec des sources réparties sur 15 répertoires, dont certains sont partagés. Mais avec un peu de soin, cela peut être fait. OTOH, cela pourrait ne pas convenir à un débutant.


Comme indiqué dans les commentaires, la liste de la commande 'mkdir' comme action pour les 'répertoires' est incorrecte. Comme indiqué également dans les commentaires, il existe d'autres moyens de corriger l'erreur «Je ne sais pas comment effectuer une sortie / débogage» qui en résulte. La première consiste à supprimer la dépendance sur la ligne «répertoires». Cela fonctionne car 'mkdir -p' ne génère pas d'erreurs si tous les répertoires qu'il est demandé de créer existent déjà. L'autre est le mécanisme montré, qui tentera de créer le répertoire uniquement s'il n'existe pas. La version 'telle qu'amendée' est ce que j'avais en tête la nuit dernière - mais les deux techniques fonctionnent (et les deux ont des problèmes si la sortie / débogage existe mais est un fichier plutôt qu'un répertoire).

Jonathan Leffler
la source
Merci Jonathan. quand j'ai essayé que j'ai eu une erreur "make: *** Aucune règle pour faire des output/debug', needed by répertoires cibles ". Stop. " Mais je ne vais pas m'inquiéter de ça maintenant. s'en tiendra aux règles de base. :). Merci d'avoir guidé. Et j'exécute "make" à partir du répertoire de niveau supérieur uniquement.
Jabez
Supprimez simplement le $ {OUT_DIR} derrière les répertoires :, alors cela devrait fonctionner.
Doc Brown
L'implémentation de cela nécessite que vous détectiez tous les cas possibles où vous utilisez les répertoires à partir de la ligne de commande. De plus, vous ne pouvez pas rendre dépendant de fichiers générant des règles de construction directoriessans les
forcer
@mtalexan Vous avez fourni quelques commentaires expliquant ce qui ne va pas avec certaines de ces réponses, mais vous n'avez pas proposé de réponse alternative. Soucieux d'entendre votre solution à ce problème.
Samuel du
@Samuel J'ai signalé les problèmes sans apporter de solution car je cherchais la même chose et je n'ai jamais trouvé de solution. J'ai fini par faire face à la chute d'une solution loin d'être idéale.
mtalexan
135

À mon avis, les répertoires ne devraient pas être considérés comme des cibles de votre makefile, que ce soit au sens technique ou au sens de la conception. Vous devez créer des fichiers et si une création de fichier nécessite un nouveau répertoire, créez tranquillement le répertoire dans la règle pour le fichier concerné.

Si vous ciblez un fichier habituel ou "modelé", utilisez simplement makela variable interne de $(@D), cela signifie "le répertoire dans lequel réside la cible actuelle" (cmp. Avec $@pour la cible). Par exemple,

$(OUT_O_DIR)/%.o: %.cpp
        @mkdir -p $(@D)
        @$(CC) -c $< -o $@

title: $(OBJS)

Ensuite, vous faites effectivement la même chose: créez des répertoires pour tous $(OBJS), mais vous le ferez de manière moins compliquée.

La même politique (les fichiers sont des cibles, les répertoires ne le sont jamais) est utilisée dans diverses applications. Par exemple, gitle système de contrôle des révisions ne stocke pas les répertoires.


Remarque: si vous comptez l'utiliser, il peut être utile d'introduire une variable de commodité et d'utiliser makeles règles d'expansion de.

dir_guard=@mkdir -p $(@D)

$(OUT_O_DIR)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -c $< -o $@

$(OUT_O_DIR_DEBUG)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -g -c $< -o $@

title: $(OBJS)
P Shved
la source
12
Bien que lier l'exigence de répertoire à des fichiers soit une meilleure option à mon avis, votre solution présente également l'inconvénient majeur que le processus mkdir sera appelé par le fichier make pour chaque fichier reconstruit, dont la plupart n'auront pas besoin de faire le répertoire à nouveau. Lorsqu'il est adapté à des systèmes de construction non Linux comme Windows, il provoque en fait à la fois une sortie d'erreur imblocable car il n'y a pas d'équivalent -p à la commande mkdir, et plus important encore, une énorme quantité de frais généraux puisque l'invocation du shell n'est pas minimalement invasive.
mtalexan
1
Au lieu d'appeler directement mkdir, j'ai fait ce qui suit pour éviter d'essayer de créer le répertoire s'il existe déjà: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady
26

Ou, KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Cela créera tous les répertoires une fois le Makefile analysé.

Nick Zalutskiy
la source
3
J'aime cette approche car je n'ai pas à encombrer chaque cible avec des commandes pour gérer les répertoires.
Ken Williams
1
J'avais juste besoin de créer un répertoire s'il n'existe pas. Cette réponse correspond parfaitement à mon problème.
Jeff Pal
Non seulement cela, cela empêche les horodatages modifiés de chaque répertoire de déclencher une étape de construction inutile. Cela devrait être la réponse
Assimilateur
6
C'est mieux: $(info $(shell mkdir -p $(DIRS)))sans le $(info ...), la sortie de la mkdircommande sera collée dans le Makefile , conduisant au mieux à des erreurs de syntaxe. L' $(info ...)appel garantit que a) les erreurs (le cas échéant) sont visibles pour l'utilisateur, et b) que l'appel de fonction se développe en rien.
cmaster
10

makedans et hors de lui-même, gère les cibles de répertoire de la même manière que les cibles de fichier. Donc, il est facile d'écrire des règles comme celle-ci:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Le seul problème avec cela est que l'horodatage des répertoires dépend de ce qui est fait aux fichiers à l'intérieur. Pour les règles ci-dessus, cela conduit au résultat suivant:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget

Ce n'est certainement pas ce que vous voulez. Chaque fois que vous touchez le fichier, vous touchez également le répertoire. Et comme le fichier dépend du répertoire, le fichier semble par conséquent obsolète, ce qui oblige à le reconstruire.

Cependant, vous pouvez facilement interrompre cette boucle en disant à make d'ignorer l'horodatage du répertoire . Cela se fait en déclarant le répertoire comme un pré-site de commande uniquement:

# The pipe symbol tells make that the following prerequisites are order-only
#                           |
#                           v
outDir/someTarget: Makefile | outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Cela donne correctement:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
make: 'outDir/someTarget' is up to date.

TL; DR:

Écrivez une règle pour créer le répertoire:

$(OUT_DIR):
    mkdir -p $(OUT_DIR)

Et faire en sorte que les cibles pour les choses à l'intérieur dépendent de l'ordre du répertoire uniquement:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)
cmaster - réintégrer monica
la source
Sur quel OS / FS cassé avez-vous vu touchmodifier des données statistiques sur le répertoire parent? Cela n'a pas de sens pour moi. Le mtime d'un répertoire ne dépend que des noms de fichiers qu'il contient. Je n'ai pas pu reproduire votre problème.
Johan Boulé
@ JohanBoulé Debian.
cmaster - réintégrer monica le
Et avez-vous comblé un bug pour un comportement aussi cassé?
Johan Boulé
6

Toutes les solutions, y compris celle acceptée, présentent des problèmes comme indiqué dans leurs commentaires respectifs. La réponse acceptée par @ jonathan-leffler est déjà assez bonne mais ne prend pas en compte le fait que les prérequis ne doivent pas nécessairement être construits dans l'ordre (pendant make -jpar exemple). Cependant, le simple fait de déplacer le directoriesprérequis de allà programprovoque des reconstructions à chaque exécution d'AFAICT. La solution suivante n'a pas ce problème et AFAICS fonctionne comme prévu.

MKDIR_P := mkdir -p
OUT_DIR := build

.PHONY: directories all clean

all: $(OUT_DIR)/program

directories: $(OUT_DIR)

$(OUT_DIR):
    ${MKDIR_P} $(OUT_DIR)

$(OUT_DIR)/program: | directories
    touch $(OUT_DIR)/program

clean:
    rm -rf $(OUT_DIR)
stefanct
la source
4

Je viens de proposer une solution assez raisonnable qui vous permet de définir les fichiers à construire et de créer automatiquement des répertoires. Tout d'abord, définissez une variable ALL_TARGET_FILEScontenant le nom de chaque fichier que votre makefile sera construit. Ensuite, utilisez le code suivant:

define depend_on_dir
$(1): | $(dir $(1))

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p $$@

$(dir $(1))_DIRECTORY_RULE_IS_DEFINED := 1
endif
endef

$(foreach file,$(ALL_TARGET_FILES),$(eval $(call depend_on_dir,$(file))))

Voici comment ça fonctionne. Je définis une fonction depend_on_dirqui prend un nom de fichier et génère une règle qui fait dépendre le fichier du répertoire qui le contient, puis définit une règle pour créer ce répertoire si nécessaire. Puis - je utiliser foreachà callcette fonction sur chaque nom de fichier et evalle résultat.

Notez que vous aurez besoin d'une version de GNU make qui prend en charge eval, qui, je pense, sont les versions 3.81 et supérieures.

Ryan C. Thompson
la source
Créer une variable contenant "le nom de chaque fichier que votre makefile va construire" est une sorte d'exigence onéreuse - j'aime définir mes cibles de premier niveau, puis les choses dont elles dépendent, et ainsi de suite. Avoir une liste plate de tous les fichiers cibles va à l'encontre de la nature hiérarchique de la spécification makefile, et peut ne pas être (facilement) possible lorsque les fichiers cibles dépendent des calculs d'exécution.
Ken Williams
3

étant donné que vous êtes un débutant, je dirais que n'essayez pas encore de faire ça. c'est certainement possible, mais compliquera inutilement votre Makefile. tenez-vous-en aux méthodes simples jusqu'à ce que vous soyez plus à l'aise avec make.

ceci dit, une façon de construire dans un répertoire différent du répertoire source est VPATH ; je préfère les règles de modèle

juste quelqu'un
la source
3

L'indépendance du système d'exploitation est essentielle pour moi, ce mkdir -pn'est donc pas une option. J'ai créé cette série de fonctions qui permettent evalde créer des cibles de répertoire avec le prérequis sur le répertoire parent. Cela a l'avantage de make -j 2fonctionner sans problème puisque les dépendances sont correctement déterminées.

# convenience function for getting parent directory, will eventually return ./
#     $(call get_parent_dir,somewhere/on/earth/) -> somewhere/on/
get_parent_dir=$(dir $(patsubst %/,%,$1))

# function to create directory targets.
# All directories have order-only-prerequisites on their parent directories
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
TARGET_DIRS:=
define make_dirs_recursively
TARGET_DIRS+=$1
$1: | $(if $(subst ./,,$(call get_parent_dir,$1)),$(call get_parent_dir,$1))
    mkdir $1
endef

# function to recursively get all directories 
#     $(call get_all_dirs,things/and/places/) -> things/ things/and/ things/and/places/
#     $(call get_all_dirs,things/and/places) -> things/ things/and/
get_all_dirs=$(if $(subst ./,,$(dir $1)),$(call get_all_dirs,$(call get_parent_dir,$1)) $1)

# function to turn all targets into directories
#     $(call get_all_target_dirs,obj/a.o obj/three/b.o) -> obj/ obj/three/
get_all_target_dirs=$(sort $(foreach target,$1,$(call get_all_dirs,$(dir $(target)))))

# create target dirs
create_dirs=$(foreach dirname,$(call get_all_target_dirs,$1),$(eval $(call make_dirs_recursively,$(dirname))))

TARGETS := w/h/a/t/e/v/e/r/things.dat w/h/a/t/things.dat

all: $(TARGETS)

# this must be placed after your .DEFAULT_GOAL, or you can manually state what it is
# https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
$(call create_dirs,$(TARGETS))

# $(TARGET_DIRS) needs to be an order-only-prerequisite
w/h/a/t/e/v/e/r/things.dat: w/h/a/t/things.dat | $(TARGET_DIRS)
    echo whatever happens > $@

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > $@

Par exemple, exécuter ce qui précède créera:

$ make
mkdir w/
mkdir w/h/
mkdir w/h/a/
mkdir w/h/a/t/
mkdir w/h/a/t/e/
mkdir w/h/a/t/e/v/
mkdir w/h/a/t/e/v/e/
mkdir w/h/a/t/e/v/e/r/
echo whatever happens > w/h/a/t/things.dat
echo whatever happens > w/h/a/t/e/v/e/r/things.dat
SimplyKnownAsG
la source