単一のソースファイルからいくつかのターゲットを生成するGNU Makefileルール


105

私は次のことをしようとしています。foo-bin1つの入力ファイルを受け取り、2つの出力ファイルを生成するプログラムがあります。このための単純なMakefileルールは次のようになります。

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out

ただし、これはmake、両方のターゲットが同時に生成されることを決して意味しません。これはmakeシリアルで実行する場合は問題ありませんが、make -j16同じようにクレイジーにしようとすると問題が発生する可能性があります。

問題は、そのような場合に適切なMakefileルールを作成する方法があるかどうかです。明らかに、それはDAGを生成しますが、どういうわけか、GNU makeマニュアルは、このケースの処理方法を指定していません。

同じコードを2回実行し、1つの結果のみを生成することは問題外です。計算に時間がかかるためです(数時間と考えてください)。データファイルの一部のみを処理する方法を知らないGNUPLOTへの入力として頻繁に使用されるため、1つのファイルのみを出力することもかなり困難です。


1
:Automakeはこの上の文書があるgnu.org/software/automake/manual/html_node/...
フランク

回答:


12

私はそれを次のように解決します:

file-a.out: input.in
    foo-bin input.in file-a.out file-b.out   

file-b.out: file-a.out
    #do nothing
    noop

この場合、並列makeはaとbの作成を「シリアル化」しますが、bの作成は何もしないため、時間はかかりません。


16
実際、これには問題があります。場合はfile-b.out明記して、神秘的に削除済みとして作成された、makeので、それを再作成することができませんfile-a.out、まだ存在します。何かご意見は?
マケサウルス

不思議なことに削除するfile-a.outと、上記の解決策がうまく機能します。これはハックを示唆しています。aまたはbのいずれか1つのみが存在する場合、欠落しているファイルがの出力として表示されるように依存関係を順序付ける必要がありinput.inます。いくつか$(widcard...)のs、$(filter...)s、$(filter-out...)sなどがうまくいくはずです。ああ。
bobbogo 2011年

3
PS foo-binは明らかにファイルの1つを最初に(マイクロ秒の解像度を使用して)作成するので、両方のファイルが存在する場合は、正しい依存関係の順序があることを確認する必要があります。
bobbogo 2011年

2
@bobbogoのいずれか、または呼び出しtouch file-a.out後の2番目のコマンドとして追加しfoo-binます。
コナー・ハリス

これは潜在的な修正です:touch file-b.out最初のルールの2番目のコマンドとして追加し、2番目のルールのコマンドを複製します。いずれかのファイルが見つからないか、より古い場合input.in、コマンドは1回だけ実行され、file-b.outより新しいファイルで両方のファイルを再生成しますfile-a.out
chqrlie

128

トリックは、複数のターゲットを持つパターンルールを使用することです。その場合、makeは両方のターゲットがコマンドの1回の呼び出しで作成されると想定します。

すべて:file-a.out file-b.out
file-a%out file-b%out:input.in
    foo-bin input.in file-a $ * out file-b $ * out

パターンルールと通常のルールの解釈の違いは正確には意味がありませんが、このような場合に役立ちます。マニュアルに記載されています。

このトリックは、それらの名前が%一致する共通のサブストリングを持っている限り、任意の数の出力ファイルに使用できます。(この場合、共通のサブストリングは「。」です。)


3
これは実際には正しくありません。ルールは、これらのパターンに一致するファイルが、指定されたコマンドを使用してinput.inから作成されることを指定していますが、それらが同時に作成されるとはどこにも述べられていません。実際に並行してmake実行すると、同じコマンドが同時に2回実行されます。
makesaurus

47
動作しないと言う前に試してみてください;-) GNU makeマニュアルには、「パターンルールには複数のターゲットがある場合があります。通常のルールとは異なり、これは同じ前提条件とコマンドで多くの異なるルールとして機能しません。パターンルールには複数のターゲットがあります。ルールのコマンドがすべてのターゲットを作成する責任があることを確認してください。すべてのターゲットを作成するためにコマンドが1回だけ実行されます。」 gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
しかし、完全に異なる入力がある場合はどうなりますか?foo.inとbar.srcと言いますか?パターンルールは空のパターンマッチをサポートしていますか?
grwlf 2013

2
@dcow:出力ファイルは、部分文字列を共有するだけで十分です(「。」などの単一の文字で十分です)。共通の部分文字列がない場合は、richardとdeemerで説明されている中間ターゲットを使用できます。@grwlf:ねえ、それは "foo.in"と "bar.src"ができるということを意味します:foo%in bar%src: input.in:-)
slowdog

1
それぞれの出力ファイルが変数に保持されている場合、レシピは次のように記述できます$(subst .,%,$(varA) $(varB)): input.in(GNU make 4.1でテスト済み)。
stefanct 2017年

55

Makeにはこれを行うための直感的な方法はありませんが、2つの適切な回避策があります。

まず、関係するターゲットに共通の語幹がある場合は、(GNU makeで)接頭辞ルールを使用できます。つまり、次のルールを修正したい場合は、

object.out1 object.out2: object.input
    foo-bin object.input object.out1 object.out2

次のように書くことができます:

%.out1 %.out2: %.input
    foo-bin $*.input $*.out1 $*.out2

(パターンの一致した部分を表すパターンルール変数$ *を使用)

非GNU Make実装に移植可能にしたい場合、またはパターンルールに一致するようにファイルに名前を付けることができない場合は、別の方法があります。

file-a.out file-b.out: input.in.intermediate ;

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

これは、makeが実行されるまでinput.in.intermediateが存在しないことをmakeに伝えます。そのため、その不在(またはそのタイムスタンプ)によってfoo-binが誤って実行されることはありません。そして、file-a.outとfile-b.outのどちらかまたは両方が(input.inに対して)古くても、foo-binは一度だけ実行されます。.INTERMEDIATEの代わりに.SECONDARYを使用できます。これは、架空のファイル名input.in.intermediateを削除しないようmakeに指示します。この方法は、並列makeビルドにも安全です。

1行目のセミコロンは重要です。Makeがfile-a.outとfile-b.outを実際に更新することを認識できるように、そのルールの空のレシピを作成します(@siulkiulkiとこれを指摘した他の人に感謝)


7
素敵な、.INTERMEDIATEのようなルックがうまく機能します。 問題を説明するAutomakeの記事も参照してください(ただし、移植性のある方法で解決しようとします)。
grwlf 2013

3
これは私がこれに対して見た最良の答えです。他の特別なターゲットも興味深いかもしれません:gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemerこれはOSXでは動作しません。(GNU Make 3.81)
ホルヘブカラン

2
私はこれを試してみましたが、並列ビルドの微妙な問題に気づきました---依存関係が中間ターゲット全体に常に適切に伝播するとは限りません(-j1ビルドではすべて問題ありません)。また、makefileが中断されてファイルが残ったinput.in.intermediateままになると、混乱してしまいます。
デビッドギブン

3
この回答にはエラーがあります。並列ビルドを完全に機能させることができます。あなただけ変更する必要があるfile-a.out file-b.out: input.in.intermediateためにfile-a.out file-b.out: input.in.intermediate ;参照してくださいstackoverflow.com/questions/37873522/...を詳細については。
siulkilulki 2018年

10

これは、パターンルールに依存しない@deemerの2番目の回答に基づいており、回避策のネストされた使用で発生していた問題を修正します。

file-a.out file-b.out: input.in.intermediate
    @# Empty recipe to propagate "newness" from the intermediate to final targets

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

これを@deemerの回答へのコメントとして追加したはずですが、このアカウントを作成したばかりで評判がないため、追加できません。

説明:空のレシピがマークに適切な簿記を行うことを確認しできるようにするために必要とされるfile-a.outfile-b.out、再構築されたものとして。に依存するさらに別の中間ターゲットがあるfile-a.out場合、Makeは外側の中間体を構築しないことを選択し、次のように主張します。

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.

9

GNU Make 4.3(2020年1月19日)以降は、「グループ化された明示的ターゲット」を使用できます。交換してください:&:

file-a.out file-b.out &: input.in
    foo-bin input.in file-a.out file-b.out

GNU MakeのNEWSファイルはこう言っています:

新機能:グループ化された明示的ターゲット

パターンルールは常に、レシピの1回の呼び出しで複数のターゲットを生成する機能を備えています。明示的なルールが単一の呼び出しで複数のターゲットを生成することを宣言できるようになりました。これを使用するには、ルールの「:」トークンを「&:」に置き換えます。この機能を検出するには、.FEATURES特殊変数で「grouped-target」を検索します。Kaz Kylheku <kaz@kylheku.com>による実装


2

これが私のやり方です。最初に、私は常に事前要求をレシピから分離します。次に、この場合、レシピを実行する新しいターゲット。

all: file-a.out file-b.out #first rule

file-a.out file-b.out: input.in

file-a.out file-b.out: dummy-a-and-b.out

.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
    echo creating: file-a.out file-b.out
    touch file-a.out file-b.out

初回:
1. file-a.outをビルドしようとしますが、dummy-a-and-b.outを最初に作成する必要があるため、makeはdummy-a-and-b.outレシピを実行します。
2. file-b.outをビルドしようとしますが、dummy-a-and-b.outは最新です。

2回目以降:
1. file-a.outを作成しようとします。前提条件を確認します。通常の前提条件は最新であり、2番目の前提条件が欠落しているため無視されます。
2. file-b.outをビルドしようとします。前提条件を確認します。通常の前提条件は最新であり、2次的な前提条件が欠落しているため無視されます。


1
これは壊れています。file-b.outmake を削除した場合、makeを再作成する方法はありません。
bobbogo 2011年

すべて追加:最初のルールとしてfile-a.out file-b.out。file-b.outが削除され、ターゲットなしでmakeが実行されたかのように、コマンドラインでは再ビルドされません。
ctrl-alt-delor 2011年

@bobbogo修正済み:上記のコメントを参照してください。
ctrl-alt-delor 2014年

0

@deemerの回答の拡張として、私はそれを関数に一般化しました。

sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))

ATOMIC=\
    $(eval s1=$(strip $1)) \
    $(eval target=$(call inter,$(s1))) \
    $(eval $(s1): $(target) ;) \
    $(eval .INTERMEDIATE: $(target) ) \
    $(target)

$(call ATOMIC, file-a.out file-b.out): input.in
    foo-bin input.in file-a.out file-b.out

壊す:

$(eval s1=$(strip $1))

最初の引数から先頭/末尾の空白を削除します

$(eval target=$(call inter,$(s1)))

中間ターゲットとして使用する一意の値に変数ターゲットを作成します。この場合、値は.inter.file-a.out_file-b.outになります。

$(eval $(s1): $(target) ;)

依存関係として一意のターゲットを使用して、出力用に空のレシピを作成します。

$(eval .INTERMEDIATE: $(target) ) 

一意のターゲットを中間体として宣言します。

$(target)

この関数をレシピで直接使用できるように、一意のターゲットへの参照で終了します。

また、ここでevalを使用するのは、evalが何にも展開されないため、関数の完全な展開が唯一の一意のターゲットであるためです。

この関数からインスピレーションを得たGNU Makeのアトミックルールを評価する必要があります。


0

make -jN使用した複数の出力を含むルールの並列複数実行を防ぐには、を使用します.NOTPARALLEL: outputs。あなたの場合:

.NOTPARALLEL: file-a.out file-b.out

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