Utwórz katalogi za pomocą make file


101

Jestem bardzo nowy w makefile i chcę tworzyć katalogi za pomocą makefile. Mój katalog projektu wygląda tak

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

Chcę umieścić wszystkie obiekty i dane wyjściowe w odpowiednim folderze wyjściowym. Chcę stworzyć strukturę folderów, która będzie taka po kompilacji.

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

Próbowałem z kilkoma opcjami, ale nie udało mi się. Proszę, pomóż mi tworzyć katalogi za pomocą make file. Przesyłam do rozważenia mój plik Makefile.

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

Z góry dziękuję. Proszę, prowadź mnie, jeśli popełniłem jakieś błędy.

Odpowiedzi:


88

To by to zrobiło - zakładając środowisko podobne do Uniksa.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

Musiałoby to zostać uruchomione w katalogu najwyższego poziomu - albo definicja $ {OUT_DIR} musiałaby być poprawna w stosunku do miejsca, w którym jest uruchamiana. Oczywiście, jeśli będziesz postępować zgodnie z edyktami artykułu Petera Millera „ Recursive Make Consented Harmful ”, to i tak będziesz uruchamiać make w katalogu najwyższego poziomu.

W tej chwili gram z tym (RMCH). Wymagało to trochę dostosowania do pakietu oprogramowania, którego używam jako poligonu testowego. Pakiet zawiera kilkanaście oddzielnych programów zbudowanych ze źródłami rozproszonymi w 15 katalogach, z których niektóre są współdzielone. Ale przy odrobinie ostrożności można to zrobić. OTOH, to może nie być odpowiednie dla nowicjusza.


Jak zauważono w komentarzach, umieszczanie polecenia „mkdir” jako akcji dla „katalogów” jest błędne. Jak również zauważono w komentarzach, istnieją inne sposoby naprawienia powstałego błędu „nie wiem, jak zrobić wyjście / debugowanie”. Jednym z nich jest usunięcie zależności od wiersza „katalogi”. Działa to, ponieważ 'mkdir -p' nie generuje błędów, jeśli wszystkie katalogi, o których utworzenie jest poproszony, już istnieją. Drugi to pokazany mechanizm, który podejmie próbę utworzenia katalogu tylko wtedy, gdy nie istnieje. Wersja „ze zmianami” jest tym, co miałem na myśli zeszłej nocy - ale obie techniki działają (i obie mają problemy, jeśli wyjście / debugowanie istnieje, ale jest plikiem, a nie katalogiem).


Dziękuję Jonathan. kiedy próbowałem, pojawił się błąd „make: *** Brak reguły tworzenia output/debug', needed by katalogów docelowych . Stop.” Ale teraz nie mam zamiaru się tym martwić. będzie przestrzegać podstawowych zasad. :). Dziękuję za przewodnictwo. Uruchamiam "make" tylko z katalogu najwyższego poziomu.
Jabez

Po prostu usuń $ {OUT_DIR} za katalogami :, to powinno działać.
Doc Brown

Implementacja tego wymaga jednak wychwycenia wszystkich możliwych przypadków, w których używasz katalogów z wiersza poleceń. Co więcej, nie możesz uzależnić żadnych reguł kompilacji generujących pliki directoriesbez powodowania, że ​​będą zawsze przebudowywać ..
mtalexan

@mtalexan Podałeś kilka komentarzy wyjaśniających, co jest nie tak z niektórymi z tych odpowiedzi, ale nie zaproponowałeś alternatywnej odpowiedzi. Z niecierpliwością czekam na rozwiązanie tego problemu.
Samuel

@Samuel Wskazałem na problemy bez podania rozwiązania, ponieważ szukałem tego samego i nigdy nie znalazłem rozwiązania. Skończyło się na tym, że miałem do czynienia z wypadkiem mniej niż idealnego rozwiązania.
mtalexan

135

Moim zdaniem katalogi nie powinny być traktowane jako cele twojego pliku makefile, ani w sensie technicznym, ani projektowym. Powinieneś utworzyć pliki i jeśli tworzenie pliku wymaga nowego katalogu, po cichu utwórz katalog w ramach reguły dla odpowiedniego pliku.

Jeśli celujesz w zwykły lub „wzorzysty” plik, po prostu użyj makewewnętrznej zmiennej $(@D), co oznacza „katalog, w którym znajduje się bieżący cel” (patrz z $@określeniem celu). Na przykład,

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

title: $(OBJS)

Następnie skutecznie robisz to samo: twórz katalogi dla wszystkich $(OBJS), ale zrobisz to w mniej skomplikowany sposób.

Ta sama polityka (pliki są celami, katalogi nigdy nie są) jest używana w różnych aplikacjach. Na przykład gitsystem kontroli wersji nie przechowuje katalogów.


Uwaga: jeśli zamierzasz go używać, może być przydatne wprowadzenie wygodnej zmiennej i wykorzystanie makereguł rozszerzania.

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
Chociaż łączenie wymagań dotyczących katalogu z plikami jest według mnie lepszą opcją, to rozwiązanie ma również istotną wadę polegającą na tym, że proces mkdir będzie wywoływany przez plik make dla każdego odbudowywanego pliku, z których większość nie będzie musiała tworzyć katalogu ponownie. Po dostosowaniu do systemów kompilacji innych niż Linux, takich jak Windows, w rzeczywistości powoduje to nieblokowalne wyjście błędu, ponieważ nie ma odpowiednika -p dla polecenia mkdir, a co ważniejsze, gigantyczne obciążenie, ponieważ wywołanie powłoki nie jest minimalnie inwazyjne.
mtalexan

1
Zamiast bezpośrednio wywoływać mkdir, wykonałem następujące czynności, aby uniknąć próby utworzenia katalogu, jeśli już istnieje: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady

26

Albo KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Spowoduje to utworzenie wszystkich katalogów po przeanalizowaniu pliku Makefile.


3
Podoba mi się to podejście, ponieważ nie muszę zaśmiecać każdego celu poleceniami do obsługi katalogów.
Ken Williams

1
Musiałem tylko utworzyć katalog, jeśli nie istnieje. Ta odpowiedź idealnie pasuje do mojego problemu.
Jeff Pal

Co więcej, zapobiega to wyzwalaniu przez zmodyfikowane sygnatury czasowe każdego katalogu niepotrzebnego kroku kompilacji. To powinna być odpowiedź
Assimilater

6
To jest lepsze: $(info $(shell mkdir -p $(DIRS)))bez polecenia $(info ...)wynik mkdirpolecenia zostanie wklejony do pliku Makefile , co w najlepszym przypadku prowadzi do błędów składniowych. Do $(info ...)połączenia zapewnia, że) błędy (jeśli w ogóle) są widoczne dla użytkownika, oraz b) że rozszerza wywołanie funkcji do niczego.
cmaster

10

makein i poza sobą, obsługuje katalogi docelowe tak samo, jak cele plików. Tak więc łatwo jest napisać takie zasady:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Jedynym problemem jest to, że sygnatura czasowa katalogów zależy od tego, co zostanie zrobione z plikami wewnątrz. W przypadku powyższych reguł prowadzi to do następującego wyniku:

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

To zdecydowanie nie jest to, czego chcesz. Za każdym razem, gdy dotykasz pliku, dotykasz również katalogu. A ponieważ plik zależy od katalogu, w konsekwencji plik wydaje się być nieaktualny, co wymusza jego odbudowę.

Możesz jednak łatwo przerwać tę pętlę, nakazując make ignorować sygnaturę czasową katalogu . Odbywa się to poprzez zadeklarowanie katalogu jako wymaganie wstępne tylko do zamówienia:

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

outDir:
    mkdir -p outDir

To poprawnie daje:

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

TL; DR:

Napisz regułę, aby utworzyć katalog:

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

A cele dla zawartości zależą tylko od kolejności katalogu:

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

Na jakim zepsutym systemie operacyjnym / FS widziałeś touchmodyfikację jakichkolwiek danych statystycznych w katalogu nadrzędnym? To nie ma dla mnie sensu. Czas m katalogu zależy tylko od nazw plików, które zawiera. Nie udało mi się odtworzyć Twojego problemu.
Johan Boulé,

@ JohanBoulé Debian.
cmaster

Czy podałeś błąd dla tak zepsutego zachowania?
Johan Boulé,

6

Wszystkie rozwiązania, w tym zaakceptowane, mają pewne problemy, zgodnie z ich komentarzami. Akceptowane odpowiedź przez @ jonathan-Leffler jest już dość dobre, ale nie bierze pod wynika, że wymagania są niekoniecznie muszą być zbudowany w kolejności (w czasie make -jna przykład). Jednak po prostu przeniesienie directorieswarunku wstępnego z allna programprowokuje odbudowę przy każdym przebiegu AFAICT. Poniższe rozwiązanie nie ma tego problemu, a AFAICS działa zgodnie z przeznaczeniem.

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

Właśnie wymyśliłem dość rozsądne rozwiązanie, które pozwala zdefiniować pliki do zbudowania i automatycznie tworzyć katalogi. Najpierw zdefiniuj zmienną, ALL_TARGET_FILESktóra przechowuje nazwę każdego pliku, który będzie tworzony przez makefile. Następnie użyj następującego kodu:

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

Oto jak to działa. Definiuję funkcję, depend_on_dirktóra przyjmuje nazwę pliku i generuje regułę, która uzależnia plik od katalogu, który go zawiera, a następnie definiuje regułę tworzenia tego katalogu, jeśli to konieczne. Następnie używam foreachdo calltej funkcji na każdej nazwy pliku i evalwynik.

Zauważ, że będziesz potrzebować wersji GNU make, która obsługuje eval, która moim zdaniem jest wersją 3.81 i wyższą.


Tworzenie zmiennej, która przechowuje "nazwę każdego pliku, który zbuduje twój plik makefile" jest rodzajem uciążliwego wymagania - lubię definiować cele na najwyższym poziomie, potem rzeczy, od których zależą, i tak dalej. Posiadanie płaskiej listy wszystkich plików docelowych jest sprzeczne z hierarchiczną naturą specyfikacji Makefile i może nie być (łatwo) możliwe, gdy pliki docelowe zależą od obliczeń w czasie wykonywania.
Ken Williams

3

biorąc pod uwagę, że jesteś nowicjuszem, powiedziałbym, że nie próbuj jeszcze tego robić. jest to zdecydowanie możliwe, ale niepotrzebnie skomplikuje twój plik Makefile. trzymaj się prostych sposobów, dopóki nie poczujesz się bardziej komfortowo z marką.

to powiedziawszy, jednym ze sposobów budowania w katalogu innym niż katalog źródłowy jest VPATH ; wolę zasady wzorców


3

Niezależność systemu operacyjnego jest dla mnie krytyczna, więc mkdir -pnie wchodzi w grę. Stworzyłem tę serię funkcji, które służą evaldo tworzenia docelowych katalogów z wymaganiem wstępnym w katalogu nadrzędnym. Ma to tę zaletę, że make -j 2będzie działać bez problemu, ponieważ zależności są poprawnie określone.

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

Na przykład uruchomienie powyższego spowoduje:

$ 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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.