Make file kullanarak dizinler oluşturun


101

Makefiles konusunda çok yeniyim ve makefile kullanarak dizinler oluşturmak istiyorum. Proje dizinim şöyle

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

Tüm nesneleri ve çıktıyı ilgili çıktı klasörüne koymak istiyorum. Derledikten sonra böyle bir klasör yapısı oluşturmak istiyorum.

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

Birkaç seçeneği denedim ama başarılı olamadım. Lütfen make file kullanarak dizin yapmama yardım edin. Makefile'ımı değerlendirmeniz için gönderiyorum.

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

Şimdiden teşekkürler. Eğer hata yaptıysam lütfen bana yol göster.

Yanıtlar:


89

Bu, Unix benzeri bir ortam varsayarak yapar.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

Bunun en üst düzey dizinde çalıştırılması gerekir - veya $ {OUT_DIR} tanımının çalıştırıldığı yere göre doğru olması gerekir. Elbette, Peter Miller'ın " Özyinelemeli Zararlı Olduğunu Düşünüyor " makalesinin fermanlarını izlerseniz, o zaman yine de üst düzey dizinde make'i çalıştırıyor olacaksınız.

Şu anda bununla (RMCH) oynuyorum. Test alanı olarak kullandığım yazılım paketine biraz uyum sağlaması gerekiyordu. Süitte, bazıları paylaşılan 15 dizine yayılmış kaynakla oluşturulmuş bir düzine ayrı program vardır. Ancak biraz özenle yapılabilir. OTOH, yeni başlayanlar için uygun olmayabilir.


Yorumlarda belirtildiği gibi, 'mkdir' komutunu 'dizinler' için eylem olarak listelemek yanlıştır. Yorumlarda da belirtildiği gibi, ortaya çıkan 'çıktı / hata ayıklamayı nasıl yapacağımı bilmiyorum' hatasını düzeltmenin başka yolları da vardır. Birincisi, 'dizinler' satırındaki bağımlılığı kaldırmaktır. Bu işe yarar çünkü 'mkdir -p', yaratması istenen tüm dizinler zaten mevcutsa hata üretmez. Diğeri ise, dizini yalnızca mevcut değilse yaratmaya çalışacak olan mekanizmadır. Dün gece aklımdaki 'değiştirilmiş' versiyondu - ama her iki teknik de çalışıyor (ve çıktı / hata ayıklama varsa, ancak bir dizinden ziyade bir dosyaysa her ikisinde de sorun var).


Teşekkür ederim Jonathan. denediğimde "make: *** Hedef output/debug', needed by dizinleri yapmak için kural yok '. Durdur." hatası aldım . Ama şimdi bunun için endişelenmeyeceğim. temel kurallara bağlı kalacaktır. :). Yol gösterdiğiniz için teşekkür ederim. Ve sadece üst düzey dizinden "make" komutunu çalıştırıyorum.
Jabez

Sadece dizinlerin arkasındaki $ {OUT_DIR} 'ı silin:, o zaman çalışması gerekir.
Doc Brown

Bunu uygulamak, dizinleri komut satırından kullandığınız her olası durumu yakalamanızı gerektirir. Ayrıca, directoriesher zaman yeniden oluşturulmasına neden olmadan , derleme kuralları oluşturan herhangi bir dosyayı bağımlı hale
getiremezsiniz

@mtalexan Bu yanıtların bazılarında neyin yanlış olduğunu açıklayan birkaç yorum yaptınız, ancak alternatif bir yanıt önermediniz. Bu problem için çözümünüzü duymak endişeli.
Samuel

@Samuel Çözüm sunmadan sorunlara dikkat çektim çünkü aynı şeyi arıyordum ve bir çözüm bulamadım. Sonunda ideal çözümden daha azının düşüşüyle ​​uğraşmaya başladım.
mtalexan

135

Bana göre dizinler, teknik veya tasarım anlamında makefile'ınızın hedefi olarak değerlendirilmemelidir. Dosyaları oluşturmalısınız ve bir dosya oluşturma yeni bir dizine ihtiyaç duyuyorsa , ilgili dosya için kural içinde dizini sessizce oluşturmalısınız.

Normal veya "desenli" bir dosyayı hedefliyorsanız, sadece make'ın dahili değişkenini kullanın $(@D), bu "geçerli hedefin bulunduğu dizin" ( $@hedef için cmp. İle ) anlamına gelir. Örneğin,

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

title: $(OBJS)

Sonra, aynısını etkili bir şekilde yaparsınız: herkes için dizinler oluşturun $(OBJS), ancak bunu daha az karmaşık bir şekilde yapacaksınız.

Aynı politika (dosyalar hedeftir, dizinler asla değildir) çeşitli uygulamalarda kullanılır. Örneğin gitrevizyon kontrol sistemi dizinleri saklamaz.


Not: Kullanacaksanız, bir uygunluk değişkeni tanıtmak ve makegenişletme kurallarını kullanmak faydalı olabilir .

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
Dizin gereksinimini dosyalara bağlamak benim görüşüme göre daha iyi bir seçenek olsa da, çözümünüzün aynı zamanda mkdir işleminin yeniden oluşturulan her dosya için make dosyası tarafından çağrılması gibi önemli bir dezavantajı da var ve bunların çoğunun dizini tekrar. Windows gibi Linux dışı derleme sistemlerine uyarlandığında, aslında mkdir komutunun -p eşdeğeri olmadığından hem engellenemez bir hata çıktısına neden olur hem de daha da önemlisi, kabuk çağırma minimum düzeyde istilacı olmadığından devasa bir ek yüke neden olur.
mtalexan

1
Doğrudan mkdir'i aramak yerine, dizini zaten mevcutsa oluşturmaya çalışmaktan kaçınmak için şunları yaptım: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady

26

Veya KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Bu, Makefile çözümlendikten sonra tüm dizinleri oluşturacaktır.


3
Bu yaklaşımı seviyorum çünkü her hedefi dizinleri işlemek için komutlarla karıştırmak zorunda değilim.
Ken Williams

1
Sadece yoksa dizin oluşturmam gerekiyordu. Bu cevap benim sorunum için mükemmel.
Jeff Pal

Sadece bu değil, bu her dizinin değiştirilmiş zaman damgalarının gereksiz bir derleme adımını tetiklemesini engeller. Cevap bu olmalı
Assimilater

6
Bu daha iyidir: $(info $(shell mkdir -p $(DIRS)))olmadan $(info ...), çıkış mkdirkomutuna Makefile yapıştırılır en iyi sözdizimi hataları neden. $(info ...)Çağrı olmasını sağlar ki) hataları (varsa) kullanıcı tarafından görülebilir ve b) hiçbir şey işlev çağrısı genişletir söyledi.
cmaster -

10

makekendi içinde ve dışında, dizin hedeflerini dosya hedefleriyle aynı şekilde işler. Yani, böyle kurallar yazmak kolaydır:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Bununla ilgili tek sorun, dizinlerin zaman damgasının içindeki dosyalara ne yapıldığına bağlı olmasıdır. Yukarıdaki kurallar için bu, aşağıdaki sonuca götürür:

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

Bu kesinlikle istediğin şey değil. Dosyaya her dokunduğunuzda, dizine de dokunursunuz. Ve dosya dizine bağlı olduğundan, sonuç olarak dosya güncelliğini yitirmiş gibi görünür ve yeniden oluşturulmaya zorlanır.

Bununla birlikte, dizinin zaman damgasını yok saymak için make komutunu vererek bu döngüyü kolayca kırabilirsiniz . Bu, dizini yalnızca sipariş önkoşulu olarak bildirerek yapılır:

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

outDir:
    mkdir -p outDir

Bu doğru bir şekilde şunları verir:

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

TL; DR:

Dizini oluşturmak için bir kural yazın:

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

Ve içindeki öğeler için hedeflerin yalnızca dizin sırasına bağlı olmasını sağlayın:

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

Hangi bozuk OS / FS'de touch, ana dizindeki herhangi bir istatistik verisini değiştirdiğini gördünüz ? Bu bana mantıklı gelmiyor. Bir dizinin mtime değeri yalnızca içerdiği dosya adlarına bağlıdır. Sorununuzu yeniden oluşturamadım.
Johan Boulé

@ JohanBoulé Debian.
cmaster - reinstate monica

Ve böyle bozuk bir davranış için bir hata doldurdunuz mu?
Johan Boulé

6

Kabul edilen dahil tüm çözümlerin ilgili yorumlarında belirtildiği gibi bazı sorunları vardır. Jonathan Leffler @ tarafından kabul edilen cevabı zaten oldukça iyi ama önkoşul mutlaka (sırasında sırayla inşa edilecek olmadığını yürürlüğe almaz make -jörneğin). Ancak basitçe hareket directoriesdan ön koşul alliçin programher çalışma AFAICT üzerinde kışkırtan yeniden oluşturur. Aşağıdaki çözümde bu sorun yoktur ve AFAICS amaçlandığı gibi çalışır.

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

Oluşturulacak dosyaları tanımlamanıza ve dizinlerin otomatik olarak oluşturulmasını sağlayan oldukça makul bir çözüm buldum. İlk olarak, ALL_TARGET_FILESmakefile'ınızın oluşturulacağı her dosyanın adını tutan bir değişken tanımlayın . Ardından aşağıdaki kodu kullanın:

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

İşte nasıl çalıştığı. depend_on_dirBir dosya adı alan ve dosyayı, onu içeren dizine bağlı hale getiren bir kural oluşturan ve gerekirse bu dizini oluşturmak için bir kural tanımlayan bir işlev tanımlıyorum. Sonra her dosya adı ve sonucu foreachiçin callbu işlevi kullanıyorum eval.

Destekleyen bir GNU make sürümüne ihtiyacınız olacağını unutmayın eval, ki bence bu sürüm 3.81 ve üstü.


"Makefile'ınızın oluşturacağı her dosyanın dosya adını" tutan bir değişken oluşturmak zahmetli bir gerekliliktir - en üst düzey hedeflerimi, sonra bağlı oldukları şeyleri vb. Tanımlamayı seviyorum. Tüm hedef dosyaların düz bir listesine sahip olmak, makefile belirtiminin hiyerarşik doğasına aykırıdır ve hedef dosyalar çalışma zamanı hesaplamalarına bağlı olduğunda (kolayca) mümkün olmayabilir.
Ken Williams

3

acemi olduğunuz göz önüne alındığında, bunu henüz yapmayı denemeyin derim. kesinlikle mümkündür, ancak Makefile dosyanızı gereksiz yere karmaşıklaştıracaktır. make konusunda daha rahat olana kadar basit yollara bağlı kalın.

kaynak dizinden farklı bir dizinde derlemenin bir yolu VPATH'dir ; kalıp kurallarını tercih ederim


3

İşletim sistemi bağımsızlığı benim için kritik, bu yüzden mkdir -pbir seçenek değil. evalAna dizindeki ön koşulla dizin hedefleri oluşturmak için kullanılan bu işlev serisini yarattım . make -j 2Bağımlılıklar doğru bir şekilde belirlendiğinden , bunun sorunsuz çalışacak faydası vardır .

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

Örneğin, yukarıdakileri çalıştırmak şunları yaratır:

$ 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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.