web-dev-qa-db-fra.com

Créer des répertoires en utilisant make file

Je suis très nouveau dans les 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 sortie dans le dossier de sortie respectif. Je veux créer une structure de dossier qui serait comme 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 avec make file. Je poste 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. S'il vous plaît, guidez-moi si j'ai aussi fait des erreurs.

86
Jabez

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 devra être exécuté dans le répertoire de niveau supérieur - ou la définition de $ {OUT_DIR} devra être correcte par rapport à l'endroit où elle est exécutée. Bien sûr, si vous suivez les édits du papier " Recursed Make Considered Nocful " de Peter Miller, vous exécuterez de toute façon make dans le répertoire de niveau supérieur.

Je joue avec cela (RMCH) pour le moment. Il fallait un peu d’adaptation à la suite de logiciels que j’utilise comme terrain d’essai. La suite comprend une douzaine de programmes distincts construits avec des sources réparties sur 15 répertoires, dont certains 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, énumérer la commande "mkdir" en tant qu'action pour "répertoires" est incorrect. Comme indiqué dans les commentaires, il existe d'autres moyens de corriger l'erreur "ne sais pas comment générer/afficher le résultat". 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 invité à créer existent déjà. L'autre est le mécanisme présenté, qui tentera de créer le répertoire s'il n'existe pas. La version "telle que modifiée" correspond à ce que j'avais à l'esprit la nuit dernière - mais les deux techniques fonctionnent (et les deux ont des problèmes si la sortie/le débogage existe mais qu'il s'agit d'un fichier plutôt que d'un répertoire).

76
Jonathan Leffler

À mon avis, les répertoires ne doivent pas être considérés comme des cibles de votre makefile, ni au sens technique, ni au sens de la conception. Vous devez créer des fichiers et si une création de fichier nécessite un nouveau répertoire, alors en silence créez le répertoire dans la règle pour le fichier correspondant.

Si vous ciblez un fichier habituel ou "à motifs", utilisez simplement la variable interne make de $(@D), ce qui signifie "le répertoire dans lequel la cible actuelle réside" (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 tout $(OBJS), mais vous le ferez d'une 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, le système de contrôle de révision git ne stocke pas de répertoires.


Note: Si vous voulez l'utiliser, il peut être utile d'introduire une variable de commodité et d'utiliser les règles de développement de make.

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)
126
P Shved

Ou, KISS.

DIRS=build build/bins

... 

$(Shell mkdir -p $(DIRS))

Cela créera tous les répertoires après l’analyse du Makefile.

20
Nick Zalutskiy

make in, et de manière autonome, 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 qu'ils contiennent. 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 vraiment 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, il semble donc que le fichier soit obsolète, ce qui oblige à le reconstruire.

Cependant, vous pouvez facilement casser cette boucle par indiquant à make d'ignorer l'horodatage du répertoire. Ceci est fait en déclarant le répertoire comme un site pré-requis uniquement pour les commandes:

# 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:

Ecrivez une règle pour créer le répertoire:

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

Et les cibles pour le contenu à l'intérieur dépendent du répertoire réservé à l'ordre:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)
7
cmaster

Toutes les solutions, y compris celle acceptée, présentent certains problèmes, comme indiqué dans leurs commentaires respectifs. Le réponse acceptée par @ jonathan-leffler est déjà assez bon mais ne tient pas compte du fait que les conditions préalables ne doivent pas nécessairement être construites dans l’ordre (pendant make -j par exemple). Cependant, déplacer simplement le prérequis directories de all à program provoque des reconstructions à chaque exécution d'AFAICT. La solution suivante ne pose 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)
6
stefanct

Je viens de proposer une solution assez raisonnable qui vous permet de définir les fichiers à créer et de créer automatiquement des répertoires. Tout d’abord, définissez une variable ALL_TARGET_FILES qui contient 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 cela fonctionne. Je définis une fonction depend_on_dir qui prend un nom de fichier et génère une règle qui fait que le fichier dépend du répertoire qui le contient, puis définit une règle pour créer ce répertoire si nécessaire. Ensuite, j'utilise foreach pour call cette fonction sur chaque nom de fichier et eval le résultat.

Notez que vous aurez besoin d’une version de GNU make qui prend en charge eval, qui est à mon avis les versions 3.81 et supérieures.

4
Ryan Thompson

L’indépendance du système d’exploitation est essentielle pour moi, alors mkdir -p n'est pas une option. J'ai créé cette série de fonctions qui utilisent eval pour créer des cibles de répertoire avec les conditions préalables requises dans le répertoire parent. Cela a l'avantage que make -j 2 fonctionnera 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 va créer:

$ 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
3
SimplyKnownAsG

étant donné que vous êtes un débutant, je dirais ne pas essayer de le faire pour l'instant. c'est certainement possible, mais cela compliquera inutilement votre Makefile. tenez-vous en aux manières simples jusqu'à ce que vous soyez plus à l'aise avec make.

cela dit, une façon de créer un répertoire différent du répertoire source est VPATH ; je préfère règles de modèle

3
just somebody

Voir https://www.oreilly.com/library/view/managing-projects-with/0596006101/ch12.html

REQUIRED_DIRS = ...
_MKDIRS := $(Shell for d in $(REQUIRED_DIRS); \
             do                               \
               [[ -d $$d ]] || mkdir -p $$d;  \
             done)

$(objects) : $(sources)

Comme j'utilise Ubuntu, j'ai également besoin d'ajouter ceci au sommet de mon Makefile:

Shell := /bin/bash # Use bash syntax
0
Rudiger Wolf