make fileを使用してディレクトリを作成する


101

私はmakefileが初めてなので、makefileを使用してディレクトリを作成します。私のプロジェクトディレクトリはこのようです

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

すべてのオブジェクトを配置して、それぞれの出力フォルダーに出力したいと思います。コンパイルするとこんな感じのフォルダ構造を作りたい。

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

いくつかのオプションを試してみましたが、成功しませんでした。make fileを使用してディレクトリを作成するのを手伝ってください。私はあなたの検討のために私のメイクファイルを投稿しています。

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

前もって感謝します。間違いもあったら教えてください。

回答:


88

これはそれを行います-Unixライクな環境を想定しています。

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

これは、トップレベルのディレクトリで実行する必要があります-または、$ {OUT_DIR}の定義は、それが実行される場所に対して相対的に正しい必要があります。もちろん、Peter Millerの「Recursive Make考慮された有害な」紙の指示に従っていれば、とにかくトップレベルのディレクトリでmakeを実行しているでしょう。

現在、これ(RMCH)で遊んでいます。テストグラウンドとして使用している一連のソフトウェアへの少しの適応が必要でした。このスイートには、15のディレクトリにまたがるソースで構築された12の個別のプログラムがあり、一部は共有されています。しかし、少し注意すればそれは可能です。OTOH、初心者には向かないかもしれません。


コメントに記載されているように、「mkdir」コマンドを「ディレクトリ」のアクションとしてリストすることは間違っています。コメントにも記載されているように、結果として発生する「出力/デバッグの方法がわからない」エラーを修正する方法は他にもあります。1つは、 'directories'行への依存関係を削除することです。これが機能するのは、作成を要求されたすべてのディレクトリがすでに存在している場合、「mkdir -p」はエラーを生成しないためです。もう1つは、示されているメカニズムで、ディレクトリが存在しない場合にのみディレクトリの作成を試みます。「修正された」バージョンは私が昨夜念頭に置いていたものですが、どちらの手法も機能します(出力/デバッグが存在するがディレクトリではなくファイルである場合、どちらも問題があります)。


ジョナサン、ありがとう。しようとすると、「make:***ターゲットoutput/debug', needed by ディレクトリを作成するルールがありません」というエラーが発生しました。しかし、私はそれについて今心配するつもりはありません。基本的なルールを守ります。:)。ご案内ありがとうございます。そして、私はトップレベルのディレクトリからのみ「make」を実行しています。
Jabez、

ディレクトリの後ろにある$ {OUT_DIR}を削除するだけで動作します。
Doc Brown、

これを実装するには、コマンドラインからディレクトリを使用する可能性のあるすべてのケースをキャッチする必要があります。さらに、あなたはに依存し、任意のファイルの作成ビルドルールを作ることができないdirectories...彼らは常に再構築させることなく、
mtalexan

@mtalexanこれらの回答のいくつかの問題点を説明するコメントをいくつか提供しましたが、別の回答を提案していません。この問題の解決策を聞きたいと思っています。
サミュエル

@Samuel同じことを探していて解決策が見つからなかったため、解決策を提供せずに問題を指摘しました。私は結局、理想的とは言えない解決策からの脱落に対処することになった。
mtalexan 2016年

135

私の意見では、ディレクトリは、技術的な意味でもデザイン上の意味でも、メイクファイルのターゲットと見なすべきではありません。あなたは、作成すべきファイルを、ファイルの作成が新しいディレクトリを必要とするならば、静かに関連するファイルのためのルール内のディレクトリを作成します。

通常のファイルまたは「パターン化された」ファイルをターゲットにしている場合は、makeの内部変数を使用するだけです$(@D)。つまり、「現在のターゲットが存在するディレクトリ」(ターゲットの場合はcmp。$@)を使用します。例えば、

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

title: $(OBJS)

次に、実質的に同じ$(OBJS)ことを実行します。すべてのディレクトリを作成しますが、これはそれほど複雑ではない方法で行います。

同じポリシー(ファイルはターゲットであり、ディレクトリはターゲットではありません)がさまざまなアプリケーションで使用されます。たとえば、gitリビジョン管理システムはディレクトリを保存しません。


注:これを使用する場合は、コンビニエンス変数を導入し、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)

12
私の意見では、ディレクトリ要件をファイルにリンクする方がより良いオプションですが、ソリューションには、再構築されるすべての単一ファイルに対してmkdirプロセスがmakeファイルによって呼び出されるという大きな欠点もあります。そのほとんどは、再びディレクトリ。Windowsのような非Linuxビルドシステムに適合させると、mkdirコマンドに相当する-pがないため、ブロックできないエラー出力が実際に発生します。さらに重要なことに、シェルの呼び出しが最小限の侵襲ではないため、莫大なオーバーヘッドが発生します。
mtalexan 2013

1
直接mkdirを呼び出す代わりに、ディレクトリがすでに存在する場合に作成を試みないように、次のようにしました。$(shell [!-d $(@ D)] && mkdir -p $(@ D))
Brady

26

または、KISS。

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

これにより、Makefileの解析後にすべてのディレクトリが作成されます。


3
ディレクトリを処理するコマンドで各ターゲットを混乱させる必要がないため、このアプローチが好きです。
ケンウィリアムズ

1
存在しない場合は、ディレクトリを作成するだけで済みます。この答えは私の問題にぴったりです。
Jeff Pal

それだけでなく、これにより、各ディレクトリの変更されたタイムスタンプが不要なビルドステップをトリガーするのを防ぎます。これが答えになるはずです
Assimilater

6
これはより良いです:$(info $(shell mkdir -p $(DIRS)))なし$(info ...)で、mkdirコマンドの出力はMakefile貼り付けられ、せいぜい構文エラーにつながります。この$(info ...)呼び出しにより、a)エラー(ある場合)がユーザーに表示され、b)関数呼び出しが何も展開されないことが保証されます。
cmaster-モニカを

10

make内外で、ファイルターゲットと同じようにディレクトリターゲットを処理します。したがって、次のようなルールを記述するのは簡単です。

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

それに関する唯一の問題は、ディレクトリのタイムスタンプが内部のファイルに対して行われる処理に依存することです。上記のルールの場合、これは次の結果になります。

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

これは間違いなくあなたが望むものではありません。ファイルに触れるたびに、ディレクトリにも触れます。また、ファイルはディレクトリに依存しているため、ファイルは古くなっているように見え、強制的に再構築されます。

ただし、ディレクトリのタイムスタンプを無視するようにmakeに指示することで、このループを簡単に解除できます。これは、ディレクトリを注文のみの前提条件として宣言することによって行われます。

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

outDir:
    mkdir -p outDir

これは正しく生成されます:

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

TL; DR:

ディレクトリを作成するルールを記述します。

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

そして、中身のターゲットはディレクトリの順序のみに依存します:

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

壊れたOS / FSでtouch親ディレクトリの統計データを変更しましたか?それは私には意味がありません。dirのmtimeは、それが含むファイル名にのみ依存します。問題を再現できませんでした。
JohanBoulé19年

@JohanBouléDebian。
cmaster-モニカを

そして、あなたはそのような壊れた振る舞いのバグを埋めましたか?
JohanBoulé19年

6

承認されたものを含むすべてのソリューションには、それぞれのコメントに記載されているようにいくつかの問題があります。@ジョナサン・レフラーで受け入れ答えはすでにかなり良いですが、前提条件は、(中に順番に構築することが必ずしもないことを有効に入れていないmake -jなど)。ただし、単純にdirectories前提条件をからallに移動すると、programAFAICTを実行するたびに再構築が引き起こされます。次のソリューションにはその問題はなく、AFAICSは意図したとおりに機能します。

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

ビルドするファイルを定義し、ディレクトリを自動的に作成させる、かなり合理的なソリューションを思いついたところです。まず、ALL_TARGET_FILESmakefileがビルドされるすべてのファイルのファイル名を保持する変数を定義します。次に、次のコードを使用します。

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

これがどのように機能するかです。depend_on_dirファイル名を取得し、そのファイルを含むディレクトリにファイルを依存させるルールを生成する関数を定義し、必要に応じてそのディレクトリを作成するルールを定義します。次に、各ファイル名と結果についてこの関数を使用foreachcallますeval

をサポートするGNU makeのバージョンが必要であることに注意してくださいeval。バージョン3.81以降です。


「makefileが作成するすべてのファイルのファイル名」を保持する変数を作成することは、やっかいな要件のようなものです。トップレベルのターゲットを定義してから、それらが依存するものを定義するなどです。すべてのターゲットファイルのフラットリストを持つことは、メイクファイル仕様の階層的な性質に反し、ターゲットファイルが実行時の計算に依存している場合は(簡単に)できない場合があります。
ケン・ウィリアムズ

3

あなたが初心者であることを考えると、まだこれをしようとしないでください。それは間違いなく可能ですが、Makefileを不必要に複雑にします。makeに慣れるまで、単純な方法を使用してください。

つまり、ソースディレクトリとは異なるディレクトリを構築する1つの方法はVPATHです。私はパターンルールを好む


3

OSの独立性は私にとって重要なのでmkdir -p、オプションではありません。eval親ディレクトリに前提条件を持つディレクトリターゲットを作成するために使用するこの一連の関数を作成しました。これには、make -j 2依存関係が正しく判断されるため、問題なく機能するという利点があります。

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

たとえば、上記を実行すると、以下が作成されます。

$ 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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.