Erstellen Sie Verzeichnisse mit make file


101

Ich bin sehr neu in Makefiles und ich möchte Verzeichnisse mit Makefile erstellen. Mein Projektverzeichnis ist so

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

Ich möchte alle Objekte und Ausgaben in den jeweiligen Ausgabeordner legen. Ich möchte eine Ordnerstruktur erstellen, die nach dem Kompilieren so aussehen würde.

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

Ich habe es mit mehreren Optionen versucht, konnte aber nicht erfolgreich sein. Bitte helfen Sie mir, Verzeichnisse mit make file zu erstellen. Ich poste mein Makefile für Ihre Überlegung.

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

Danke im Voraus. Bitte führe mich, wenn ich auch Fehler gemacht habe.

Antworten:


88

Dies würde es tun - unter der Annahme einer Unix-ähnlichen Umgebung.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

Dies müsste im Verzeichnis der obersten Ebene ausgeführt werden - oder die Definition von $ {OUT_DIR} müsste in Bezug auf den Ausführungsort korrekt sein. Wenn Sie den Edikten von Peter Millers Artikel " Recursive Make Considered Harmful " folgen, wird make natürlich trotzdem im Verzeichnis der obersten Ebene ausgeführt.

Ich spiele gerade damit (RMCH). Es musste ein wenig an die Software angepasst werden, die ich als Testgelände verwende. Die Suite verfügt über ein Dutzend separate Programme, deren Quellcode auf 15 Verzeichnisse verteilt ist, von denen einige gemeinsam genutzt werden. Aber mit ein bisschen Sorgfalt kann es gemacht werden. OTOH, es könnte für einen Neuling nicht geeignet sein.


Wie in den Kommentaren erwähnt, ist es falsch, den Befehl 'mkdir' als Aktion für 'Verzeichnisse' aufzulisten. Wie auch in den Kommentaren erwähnt, gibt es andere Möglichkeiten, um den daraus resultierenden Fehler "Weiß nicht, wie Ausgabe / Debugging ausgeführt werden soll" zu beheben. Eine besteht darin, die Abhängigkeit von der Zeile 'Verzeichnisse' zu entfernen. Dies funktioniert, weil 'mkdir -p' keine Fehler generiert, wenn alle Verzeichnisse, die erstellt werden sollen, bereits vorhanden sind. Der andere ist der gezeigte Mechanismus, der nur dann versucht, das Verzeichnis zu erstellen, wenn es nicht vorhanden ist. Die 'wie geändert' Version ist das, was ich letzte Nacht im Sinn hatte - aber beide Techniken funktionieren (und beide haben Probleme, wenn Ausgabe / Debug vorhanden ist, aber eher eine Datei als ein Verzeichnis ist).


Danke Jonathan. Als ich versuchte, dass ich einen Fehler bekam "make: *** Keine Regel, um Zielverzeichnisse zu output/debug', needed by erstellen. Stop." Aber darüber werde ich mir jetzt keine Sorgen machen. wird sich an die Grundregeln halten. :). Vielen Dank für die Anleitung. Und ich führe "make" nur aus dem Toplevel-Verzeichnis aus.
Jabez

Löschen Sie einfach das $ {OUT_DIR} hinter den Verzeichnissen:, dann sollte es funktionieren.
Doc Brown

Um dies zu implementieren, müssen Sie jeden möglichen Fall abfangen, in dem Sie die Verzeichnisse über die Befehlszeile verwenden. Darüber hinaus können Sie keine Regeln zum Erstellen von Dateien abhängig machen, directoriesohne dass diese immer neu erstellt werden.
mtalexan

@mtalexan Sie haben einige Kommentare abgegeben, in denen erläutert wird, was mit einigen dieser Antworten nicht stimmt, aber Sie haben keine alternative Antwort vorgeschlagen. Ich bin gespannt auf Ihre Lösung für dieses Problem.
Samuel

@Samuel Ich habe auf die Probleme hingewiesen, ohne eine Lösung anzubieten, weil ich nach der gleichen Sache gesucht und nie eine Lösung gefunden habe. Am Ende habe ich mich nur mit dem Fall einer weniger als idealen Lösung befasst.
Mtalexan

135

Meiner Meinung nach sollten Verzeichnisse weder im technischen noch im gestalterischen Sinne als Ziele Ihres Makefiles betrachtet werden. Sie sollten erstellen Dateien und wenn eine Datei erstellen ein neues Verzeichnis muss dann ruhig das Verzeichnis in der Regel für die entsprechende Datei erstellen.

Wenn Sie auf eine normale oder "gemusterte" Datei abzielen, verwenden Sie einfach die makeinterne Variable $(@D), dh "das Verzeichnis, in dem sich das aktuelle Ziel befindet" (cmp. Mit $@für das Ziel). Beispielsweise,

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

title: $(OBJS)

Dann machen Sie effektiv dasselbe: Erstellen Sie Verzeichnisse für alle $(OBJS), aber Sie tun es auf weniger komplizierte Weise.

Dieselbe Richtlinie (Dateien sind Ziele, Verzeichnisse niemals) wird in verschiedenen Anwendungen verwendet. Beispielsweise gitspeichert das Revisionskontrollsystem keine Verzeichnisse.


Hinweis: Wenn Sie es verwenden möchten, kann es hilfreich sein, eine Convenience-Variable einzuführen und die makeErweiterungsregeln zu verwenden.

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)

12
Obwohl das Verknüpfen der Verzeichnisanforderungen mit Dateien meiner Meinung nach eine bessere Option ist, hat Ihre Lösung auch den erheblichen Nachteil, dass der mkdir-Prozess von der make-Datei für jede einzelne Datei aufgerufen wird, die neu erstellt wird, von denen die meisten nicht erstellt werden müssen wieder Verzeichnis. Wenn es an Nicht-Linux-Build-Systeme wie Windows angepasst wird, verursacht es tatsächlich sowohl eine nicht blockierbare Fehlerausgabe, da es kein -p gibt, das dem Befehl mkdir entspricht, als auch, was noch wichtiger ist, einen gigantischen Overhead, da der Shell-Aufruf nicht minimal invasiv ist.
Mtalexan

1
Anstatt mkdir direkt aufzurufen, habe ich Folgendes getan, um zu vermeiden, dass versucht wird, das Verzeichnis zu erstellen, falls es bereits vorhanden ist: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady

26

Oder KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Dadurch werden alle Verzeichnisse erstellt, nachdem das Makefile analysiert wurde.


3
Ich mag diesen Ansatz, weil ich nicht jedes Ziel mit Befehlen überladen muss, um mit den Verzeichnissen umzugehen.
Ken Williams

1
Ich musste nur ein Verzeichnis erstellen, wenn es nicht existiert. Diese Antwort passt perfekt zu meinem Problem.
Jeff Pal

Darüber hinaus wird verhindert, dass die geänderten Zeitstempel jedes Verzeichnisses einen unnötigen Erstellungsschritt auslösen. Dies sollte die Antwort sein
Assimilater

6
Dies ist besser: $(info $(shell mkdir -p $(DIRS)))Ohne das $(info ...)wird die Ausgabe des mkdirBefehls in das Makefile eingefügt , was bestenfalls zu Syntaxfehlern führt. Der $(info ...)Aufruf stellt sicher, dass a) die Fehler (falls vorhanden) für den Benutzer sichtbar sind und b) der Funktionsaufruf zu nichts erweitert wird.
cmaster - wieder herstellen Monica

10

makeIn und Off selbst werden Verzeichnisziele genauso behandelt wie Dateiziele. Es ist also einfach, Regeln wie diese zu schreiben:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Das einzige Problem dabei ist, dass der Zeitstempel des Verzeichnisses davon abhängt, was mit den darin enthaltenen Dateien gemacht wird. Für die obigen Regeln führt dies zu folgendem Ergebnis:

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

Dies ist definitiv nicht das, was Sie wollen. Wenn Sie die Datei berühren, berühren Sie auch das Verzeichnis. Und da die Datei vom Verzeichnis abhängt, scheint die Datei veraltet zu sein, sodass sie neu erstellt werden muss.

Sie können diese Schleife jedoch leicht unterbrechen, indem Sie make anweisen, den Zeitstempel des Verzeichnisses zu ignorieren . Dies erfolgt durch Deklarieren des Verzeichnisses als Voraussetzung nur für Bestellungen:

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

outDir:
    mkdir -p outDir

Dies ergibt richtig:

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

TL; DR:

Schreiben Sie eine Regel, um das Verzeichnis zu erstellen:

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

Und hängen die Ziele für das darin enthaltene Material nur von der Reihenfolge des Verzeichnisses ab:

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

Auf welchem ​​defekten OS / FS haben Sie touchStat-Daten im übergeordneten Verzeichnis geändert? Das macht für mich keinen Sinn. Die Zeit eines Verzeichnisses hängt nur von den darin enthaltenen Dateinamen ab. Ich konnte Ihr Problem nicht reproduzieren.
Johan Boulé

@ JohanBoulé Debian.
cmaster - wieder herstellen Monica

Und hast du einen Fehler für solch ein kaputtes Verhalten gemacht?
Johan Boulé

6

Alle Lösungen, einschließlich der akzeptierten, weisen einige Probleme auf, wie in den jeweiligen Kommentaren angegeben. Die akzeptierte Antwort von @ jonathan-leffler ist bereits recht gut, berücksichtigt jedoch nicht, dass die Voraussetzungen nicht unbedingt in der richtigen Reihenfolge (während) erstellt werden müssenmake -j zum Beispiel ) erstellt werden müssen. Wenn Sie jedoch einfach die directoriesVoraussetzung von verschieben, allum programbei jedem AFAICT-Lauf Neuerstellungen zu provozieren. Die folgende Lösung hat dieses Problem nicht und AFAICS funktioniert wie beabsichtigt.

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)

4

Ich habe gerade eine ziemlich vernünftige Lösung gefunden, mit der Sie die zu erstellenden Dateien definieren und Verzeichnisse automatisch erstellen lassen können. Definieren Sie zunächst eine Variable ALL_TARGET_FILES, die den Dateinamen jeder Datei enthält, die Ihr Makefile erstellen soll. Verwenden Sie dann den folgenden Code:

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))))

So funktioniert das. Ich definiere eine Funktiondepend_on_dir die einen Dateinamen annimmt und eine Regel generiert, die die Datei von dem Verzeichnis abhängig macht, in dem sie enthalten ist, und definiere dann eine Regel, um dieses Verzeichnis bei Bedarf zu erstellen. Dann benutze ich , foreachum calldiese Funktion auf jeden Dateinamen und evaldas Ergebnis.

Beachten Sie, dass Sie eine Version von GNU make benötigen, die unterstützt eval. Ich denke, es handelt sich um Versionen 3.81 und höher.


Das Erstellen einer Variablen, die "den Dateinamen jeder Datei enthält, die von Ihrem Makefile erstellt wird", ist eine schwierige Anforderung. Ich definiere gerne meine Ziele auf oberster Ebene, dann die Dinge, von denen sie abhängen, und so weiter. Eine flache Liste aller Zieldateien widerspricht der hierarchischen Natur der Makefile-Spezifikation und ist möglicherweise nicht (leicht) möglich, wenn Zieldateien von Laufzeitberechnungen abhängen.
Ken Williams

3

Da Sie ein Neuling sind, würde ich sagen, versuchen Sie dies noch nicht. Es ist definitiv möglich, wird aber Ihr Makefile unnötig komplizieren. Halten Sie sich an die einfachen Methoden, bis Sie sich mit make besser auskennen.

Eine Möglichkeit, ein anderes Verzeichnis als das Quellverzeichnis zu erstellen , ist VPATH . Ich bevorzuge Musterregeln


3

Die Unabhängigkeit des Betriebssystems ist für mich von entscheidender Bedeutung und daher mkdir -pkeine Option. Ich habe diese Reihe von Funktionen erstellt, evalmit denen Verzeichnisziele mit der Voraussetzung für das übergeordnete Verzeichnis erstellt werden. Dies hat den Vorteil, dass dies make -j 2problemlos funktioniert, da die Abhängigkeiten korrekt ermittelt werden.

# 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 > $@

Wenn Sie beispielsweise Folgendes ausführen, wird Folgendes erstellt:

$ 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

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.