Crea directory usando make file


101

Sono un principiante nei makefile e voglio creare directory usando makefile. La directory del mio progetto è così

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

Voglio mettere tutti gli oggetti e l'output nella rispettiva cartella di output. Voglio creare una struttura di cartelle che sarebbe come questa dopo la compilazione.

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

Ho provato con diverse opzioni, ma non sono riuscito. Per favore aiutami a creare directory usando make file. Sto postando il mio Makefile per la tua considerazione.

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

Grazie in anticipo. Per favore guidami se ho fatto anche degli errori.

Risposte:


88

Questo lo farebbe - assumendo un ambiente simile a Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

Questo dovrebbe essere eseguito nella directory di primo livello o la definizione di $ {OUT_DIR} dovrebbe essere corretta rispetto a dove viene eseguita. Ovviamente, se segui gli editti dell'articolo " Recursive Make Considered Harmful " di Peter Miller , allora eseguirai comunque make nella directory di primo livello.

Sto giocando con questo (RMCH) al momento. Aveva bisogno di un po 'di adattamento alla suite di software che sto usando come terreno di prova. La suite ha una dozzina di programmi separati costruiti con il sorgente distribuito in 15 directory, alcune delle quali condivise. Ma con un po 'di attenzione si può fare. OTOH, potrebbe non essere appropriato per un principiante.


Come notato nei commenti, elencare il comando "mkdir" come azione per "directory" è sbagliato. Come notato anche nei commenti, ci sono altri modi per correggere l'errore "non so come fare output / debug" che ne risulta. Uno è rimuovere la dipendenza dalla riga "directory". Questo funziona perché 'mkdir -p' non genera errori se tutte le directory che viene chiesto di creare esistono già. L'altro è il meccanismo mostrato, che tenterà di creare la directory solo se non esiste. La versione "modificata" è quella che avevo in mente ieri sera, ma entrambe le tecniche funzionano (ed entrambe hanno problemi se l'output / debug esiste ma è un file piuttosto che una directory).


Grazie Jonathan. quando ho provato ho ricevuto un errore "make: *** No rule to make target output/debug', needed by directories '. Stop." Ma ora non me ne preoccuperò. si atterrà alle regole di base. :). Grazie per la guida. E sto eseguendo "make" solo dalla directory di primo livello.
Jabez

Elimina semplicemente $ {OUT_DIR} dietro le directory :, quindi dovrebbe funzionare.
Doc Brown,

L'implementazione di ciò richiede tuttavia di catturare ogni possibile caso in cui si utilizzano le directory dalla riga di comando. Inoltre, non è possibile rendere dipendente alcun file che genera regole di compilazione directoriessenza che venga sempre ricostruito ..
mtalexan

@mtalexan Hai fornito un paio di commenti per spiegare cosa c'è di sbagliato in alcune di queste risposte ma non hai proposto una risposta alternativa. Ansioso di sentire la tua soluzione per questo problema.
Samuel,

@Samuele ho indicato i problemi senza fornire una soluzione perché cercavo la stessa cosa e non ho mai trovato una soluzione. Ho finito per affrontare la caduta di una soluzione tutt'altro che ideale.
mtalexan

135

A mio parere, le directory non dovrebbero essere considerate target del tuo makefile, né in senso tecnico né in senso progettuale. È necessario creare file e se la creazione di un file richiede una nuova directory, creare in silenzio la directory all'interno della regola per il file pertinente.

Se stai mirando a un file normale o "modellato", usa semplicemente makela variabile interna di $(@D), che significa "la directory in cui risiede il target corrente" (cmp. Con $@per il target). Per esempio,

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

title: $(OBJS)

Quindi, stai effettivamente facendo lo stesso: crea directory per tutti $(OBJS), ma lo farai in un modo meno complicato.

La stessa politica (i file sono obiettivi, le directory non lo sono mai) viene utilizzata in varie applicazioni. Ad esempio, il gitsistema di controllo delle revisioni non memorizza le directory.


Nota: se intendi usarlo, potrebbe essere utile introdurre una variabile di convenienza e utilizzare makele regole di espansione di.

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
Sebbene collegare il requisito della directory ai file sia un'opzione migliore a mio parere, la tua soluzione ha anche il significativo svantaggio che il processo mkdir verrà chiamato dal file make per ogni singolo file che viene ricostruito, la maggior parte del quale non avrà bisogno di fare il nuovamente la directory. Quando adattato a sistemi di compilazione non Linux come Windows, in realtà causa sia un output di errore non bloccabile poiché non esiste un -p equivalente al comando mkdir, sia, cosa più importante, una quantità enorme di overhead poiché l'invocazione della shell non è minimamente invasiva.
mtalexan

1
Invece di chiamare direttamente mkdir, ho fatto quanto segue per evitare di provare a creare la directory se esiste già: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady

26

Oppure, KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Questo creerà tutte le directory dopo che il Makefile è stato analizzato.


3
Mi piace questo approccio perché non devo ingombrare ogni destinazione con i comandi per gestire le directory.
Ken Williams,

1
Avevo solo bisogno di creare una directory se non esiste. Questa risposta si adatta perfettamente al mio problema.
Jeff Pal

Non solo, questo impedisce ai timestamp modificati di ciascuna directory di attivare un passaggio di build non necessario. Questa dovrebbe essere la risposta
Assimilater

6
È meglio: $(info $(shell mkdir -p $(DIRS)))senza il $(info ...), l'output del mkdircomando verrà incollato nel Makefile , portando a errori di sintassi nella migliore delle ipotesi. La $(info ...)chiamata garantisce che a) gli errori (se presenti) siano visibili all'utente eb) che la chiamata di funzione non si espanda a nulla.
cmaster - ripristina monica il

10

makedentro e fuori da sé, gestisce le destinazioni di directory esattamente come le destinazioni di file. Quindi, è facile scrivere regole come questa:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

L'unico problema è che il timestamp delle directory dipende da cosa viene fatto ai file all'interno. Per le regole di cui sopra, questo porta al seguente risultato:

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

Questo sicuramente non è quello che vuoi. Ogni volta che tocchi il file, tocchi anche la directory. E poiché il file dipende dalla directory, il file di conseguenza sembra non essere aggiornato, costringendolo a essere ricostruito.

Tuttavia, puoi facilmente interrompere questo ciclo dicendo a make di ignorare il timestamp della directory . Questo viene fatto dichiarando la directory come un prerequisito di solo ordine:

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

outDir:
    mkdir -p outDir

Ciò produce correttamente:

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

TL; DR:

Scrivi una regola per creare la directory:

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

E gli obiettivi per le cose all'interno dipendono solo dall'ordine della directory:

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

Su quale sistema operativo / FS danneggiato hai visto touchmodificare i dati statistici nella directory principale? Non ha senso per me. L'mtime di una directory dipende solo dai nomi dei file che contiene. Non sono riuscito a riprodurre il tuo problema.
Johan Boulé

@ JohanBoulé Debian.
cmaster - ripristina monica il

E hai riempito un bug per un comportamento così rotto?
Johan Boulé

6

Tutte le soluzioni, inclusa quella accettata, presentano alcuni problemi come indicato nei rispettivi commenti. La risposta accettata da @ jonathan-leffler è già abbastanza buona ma non ha effetto sul fatto che i prerequisiti non devono essere necessariamente costruiti in ordine (durante, make -jad esempio). Tuttavia, il semplice spostamento del directoriesprerequisito da alla programprovoca le ricostruzioni ad ogni esecuzione AFAICT. La seguente soluzione non presenta questo problema e AFAICS funziona come previsto.

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

Ho appena trovato una soluzione abbastanza ragionevole che ti consente di definire i file da compilare e creare automaticamente le directory. Per prima cosa, definisci una variabile ALL_TARGET_FILESche contenga il nome di ogni file che il tuo makefile verrà creato. Quindi utilizzare il codice seguente:

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

Ecco come funziona. Definisco una funzione depend_on_dirche prende un nome di file e genera una regola che fa dipendere il file dalla directory che lo contiene e quindi definisce una regola per creare quella directory se necessario. Poi io uso foreachdi callquesta funzione su ogni nome del file e evalil risultato.

Nota che avrai bisogno di una versione di GNU make che supporti eval, che penso sia la versione 3.81 e successive.


Creare una variabile che contenga "il nome di ogni file che il tuo makefile creerà" è una specie di requisito oneroso: mi piace definire i miei obiettivi di primo livello, poi le cose da cui dipendono e così via. Avere un elenco semplice di tutti i file di destinazione va contro la natura gerarchica della specifica del makefile e potrebbe non essere (facilmente) possibile quando i file di destinazione dipendono dai calcoli di runtime.
Ken Williams

3

dato che sei un principiante, direi di non provare ancora a farlo. è sicuramente possibile, ma complicherà inutilmente il tuo Makefile. attenersi ai metodi semplici finché non si è più a proprio agio con make.

detto questo, un modo per compilare in una directory diversa dalla directory dei sorgenti è VPATH ; preferisco le regole del modello


3

L'indipendenza dal sistema operativo è fondamentale per me, quindi mkdir -pnon è un'opzione. Ho creato questa serie di funzioni che utilizzano evalper creare destinazioni di directory con il prerequisito sulla directory padre. Questo ha il vantaggio che make -j 2funzionerà senza problemi poiché le dipendenze sono determinate correttamente.

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

Ad esempio, l'esecuzione di quanto sopra creerà:

$ 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

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.