ソースファイルをGLOBで指定するか、CMakeで各ファイルを個別に指定する方が良いですか?


157

CMakeは、ターゲットのソースファイルを指定するいくつかの方法を提供します。1つは、グロビング(documentation)を使用することです。次に例を示します。

FILE(GLOB MY_SRCS dir/*)

別の方法は、各ファイルを個別に指定することです。

どちらの方法が好ましいですか?グロビングは簡単に思えますが、いくつかの欠点があると聞きました。

回答:


185

完全な開示:最初は単純化のためにグロビングアプローチを好んでいましたが、長年にわたって、ファイルを明示的にリストすることは、大規模なマルチ開発者プロジェクトではエラーが発生しにくいことを認識しました。

元の答え:


グロビングの利点は次のとおりです。

  • 新しいファイルはディスク上の1か所にのみリストされているため、簡単に追加できます。グロビングを行わないと、複製が作成されます。

  • CMakeLists.txtファイルは短くなります。多くのファイルがある場合、これは大きなプラスです。グロビングしないと、ファイルの巨大なリストの中でCMakeロジックが失われます。

ハードコードされたファイルリストを使用する利点は次のとおりです。

  • CMakeはディスク上の新しいファイルの依存関係を正しく追跡します。グロブを使用すると、CMakeを実行したときに最初にグロブされないファイルは取得されません。

  • 必要なファイルのみが追加されていることを確認します。Globbingは、不要な不要なファイルを取得する場合があります。

最初の問題を回避するには、タッチコマンドを使用するか、変更を加えずにファイルを書き込むことにより、グロブを実行するCMakeLists.txtを「タッチ」するだけです。これにより、CMakeが強制的に再実行され、新しいファイルを取得します。

2番目の問題を修正するには、コードを注意深くディレクトリに編成できます。これはおそらくとにかく行うことです。最悪の場合、list(REMOVE_ITEM)コマンドを使用して、ファイルのグロブリストをクリーンアップできます。

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

これが問題になる唯一の実際の状況は、git-bisectなどを使用して、同じビルドディレクトリで古いバージョンのコードを試す場合です。その場合、リスト内の正しいファイルを確実に取得するために、必要以上にクリーンアップとコンパイルが必要になる場合があります。これはそのようなコーナーケースであり、あなたがすでにあなたのつま先になっている場合、それは本当に問題ではありません。


1
また、グロビングに問題があります。gitのdifftoolファイルが$ basename。$ ext。$ type。$ pid。$ extとして保存されているため、単一のマージ解決後にコンパイルしようとすると、楽しいエラーが発生する可能性があります。
mathstuf 2013年

9
この答えSimply "touch" the CMakeLists.txtは、新しいファイルが見つからないcmakeの欠点を覆い隠していると思いますが、開発者であれば問題ありませんが、ソフトウェアをビルドしている他の人にとっては、更新後にビルドが失敗、調査するのが彼らの負担であるというのは、本当に苦痛です。なぜ。
ideasman42 2013

36
あのね?6年前にこの回答を書いて以来、私は少し気が変わって、今はファイルを明示的にリストすることを好みます。「ファイルを追加するのに少し手間がかかる」というのが本当の不利な点だけですが、それによってあらゆる種類の頭痛が軽減されます。そして多くの点で、明示的は暗黙的よりも優れています。
richq 2015年

1
@richq このgitフックはあなたの現在の位置を再考させますか?:)
アントニオ

8
アントニオが言うように、投票は「グロビング」アプローチを支持するために与えられました。回答の性質を変えることは、それらの有権者のために行う餌とスイッチのことです。妥協案として、私の意見の変化を反映するように編集を加えました。ティーカップでそのような嵐を引き起こしたことをインターネットに謝罪します:-P
richq

113

CMakeでソースファイルを指定する最良の方法は、それらを明示的にリストすることです。

CMakeの作成者自身は、グロビングを使用しないようにアドバイスしてます。

参照:https : //cmake.org/cmake/help/v3.15/command/file.html?highlight=glob#file

(GLOBを使用してソースツリーからソースファイルのリストを収集することはお勧めしません。ソースが追加または削除されたときにCMakeLists.txtファイルが変更されない場合、生成されたビルドシステムは、CMakeに再生成を要求するタイミングを認識できません。)

もちろん、あなたはマイナス面が何であるか知りたいかもしれません- 読んでください!


グラブが失敗した場合:

グロビングの大きな欠点は、ファイルを作成/削除してもビルドシステムが自動的に更新されないことです。

あなたがファイルを追加する人である場合、これは許容できるトレードオフのように見えるかもしれませんが、これはコードをビルドする他の人々に問題を引き起こします、彼らはバージョン管理からプロジェクトを更新し、ビルドを実行し、それからあなたに連絡して不平を言います
「ビルドの壊れた"。

さらに悪いことに、障害は通常、リンクエラーを発生させ、問題の原因についてのヒントを提供せず、トラブルシューティングに時間がかかります。

私が取り組んでいるプロジェクトでは、グロビングを開始しましたが、新しいファイルが追加されたときに非常に多くの不満があり、グロビングの代わりにファイルを明示的にリストするのに十分な理由でした。

これはまた、一般的なgitワークフロー
git bisectおよび機能ブランチ間の切り替え)を壊します。

だから私はこれをお勧めできませんでした、それが原因で問題が利便性をはるかに上回っています。これが原因で誰かがソフトウェアをビルドできない場合、問題を追跡するために多くの時間を失うか、単にあきらめるかもしれません。

もう1つの注意は、タッチすることを覚えているだけCMakeLists.txtでは必ずしも十分ではないことです。グロビングを使用する自動ビルドでは、最後のビルド以降にファイル追加/削除された可能性があるためすべてのビルドのcmake前に実行する必要がありました*。

ルールの例外:

グロビングが望ましい場合があります:

  • CMakeLists.txtCMakeを使用しない既存のプロジェクトのファイルを設定するため。
    すべてのソースを参照するための高速な方法(ビルドシステムの実行後-グロビングを明示的なファイルリストで置き換えます)。
  • CMakeがプライマリビルドシステムとして使用されていない場合(たとえば、CMakeを使用していないプロジェクトを使用していて、独自のビルドシステムを維持したい場合)。
  • ファイルリストが頻繁に変更されるために維持することが実用的でなくなる状況。この場合、それ便利かもしれませんが、信頼性のある正しいビルドを得るには毎回実行を受け入れてcmakeビルドファイルを生成する必要があります(これはCMakeの意図に反します-構成をビルドから分割する機能)

* はい、更新の前後にディスク上のファイルのツリーを比較するコードを書くこともできましたが、これはそれほど良い回避策ではなく、ビルドシステムに任せたほうがよいでしょう。


9
「グロビングの大きな欠点は、新しいファイルを作成してもビルドシステムが自動的に更新されないことです。」しかし、グロブを行わない場合でも、手動でCMakeLists.txtを更新する必要があります。つまり、cmakeはまだビルドシステムを自動的に更新していません。新しいファイルを構築するために、手動で何かを行うことを覚えておかなければならない方法のようです。CMakeLists.txtを開いて編集し、新しいファイルを追加するよりも、CMakeLists.txtに触れる方が簡単に思えます。
Dan

17
@Dan、あなたのシステムのために-確かに、あなたが一人で開発するだけならこれは問題ありませんが、あなたのプロジェクトを構築する他のすべての人はどうですか?それらにメールで送信し、CMakeファイルを手動でタッチしますか?ファイルが追加または削除されるたびに -CMakeにファイルリストを保存すると、vcsが認識しているのと同じファイルが常にビルドで使用されます。私を信じてください-これはほんのわずかな詳細ではありません-多くの開発者があなたのビルドが失敗したとき-彼らはメーリングリストに行き、コードが壊れていることをIRCに尋ねます。注:(あなた自身のシステムでも、たとえばgitの履歴に戻って、CMakeファイルにアクセスして触ろうとは思わないでしょう)
ideasman42

2
ああ、そんなことは考えていなかった。それが私がグロビングに対して聞いた最大の理由です。cmakeのドキュメントが、人々がグロビングを避けることを推奨する理由を拡張してほしいと思います。
Dan

1
私は最後のcmake実行のタイムスタンプをファイルに書き込むソリューションについて考えていました。唯一の問題は次のとおりです。1)クロスプラットフォームになるには、おそらくcmakeで実行する必要があるため、どうにかしてcmakeが2度目に実行されないようにする必要があります。2)おそらくより多くのマージ競合(まだファイルリストbtwで発生します)この場合、実際には後でタイムスタンプを取得することで簡単に解決できます。
Predelnik、2017

2
@ tim-mb、「しかし、CMakeが作成したfiletree_updatedファイルをチェックインできると便利です。ファイルは、ファイルのグロブが更新されるたびに自動的に変更されます。」-あなたは私の答えが何をするのか正確に説明しました。
グレンノウルズ

21

CMake 3.12では、file(GLOB ...)およびfile(GLOB_RECURSE ...)コマンドにCONFIGURE_DEPENDS、グロブの値が変更された場合にcmakeを再実行するオプションが追加されました。これがソースファイルのグロビングの主な欠点だったので、今はそうしても問題ありません。

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

ただし、一部の人々は依然としてソースのグロビングを回避することを推奨しています。実際、ドキュメントには次のように記載されています。

ソースツリーからソースファイルのリストを収集するためにGLOBを使用することはお勧めしません。... CONFIGURE_DEPENDSフラグはすべてのジェネレーターで確実に機能しない場合があります。または、フラグをサポートできない新しいジェネレーターが将来追加された場合、それを使用するプロジェクトはスタックします。CONFIGURE_DEPENDS確実に機能する場合でも、再構築のたびにチェックを実行するにはコストがかかります。

個人的には、考えられる欠点を補うためにソースファイルリストを手動で管理する必要がないことの利点を検討します。手動でリストされたファイルに切り替える必要がある場合は、グロブソースリストを印刷して貼り付けるだけで簡単に実現できます。


ビルドシステムが完全なcmakeとビルドサイクルを実行する場合(ビルドディレクトリを削除し、そこからcmakeを実行してからmakefileを呼び出す)、不要なファイルを取得しない限り、GLOBbedソースを使用しても欠点はありませんか?私の経験では、cmakeパーツはビルドよりもはるかに高速に実行されるため、とにかくそれほどオーバーヘッドではありません
Den-Jason

9

依存関係を保持するために追加のファイルを犠牲にして安全にグロブすることができます(おそらくそうする必要があります)。

次のような関数をどこかに追加します。

# Compare the new contents with the existing file, if it exists and is the 
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of 
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it's the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it's tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

そして、グロビングします:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

以前と同じように、明示的な依存関係を回避し(そしてすべての自動ビルドをトリガーします!)、1つではなく2つのファイルにのみ存在します。

手順の唯一の変更点は、新しいファイルを作成した後です。グロブを行わない場合のワークフローは、Visual Studio内からCMakeLists.txtを変更して再構築することです。グロブを行う場合は、明示的にcmakeを実行するか、CMakeLists.txtをタップします。


最初は、ソースファイルが追加されたときにMakefileを自動的に更新するツールだと思っていましたが、その値がわかりました。いいね!これは、誰かがリポジトリから更新し、make奇妙なリンカエラーを与えるという懸念を解決します。
クリスルエンゴ2017

1
これは良い方法だと思います。もちろん、ファイルを追加または削除した後もcmakeをトリガーすることを忘れないでください。また、この依存関係ファイルをコミットする必要があるため、ユーザー側の教育が必要です。主な欠点は、この依存関係ファイルが厄介なマージ競合を引き起こす可能性があり、開発者がメカニズムをある程度理解していないと解決できない場合があることです。
アントニオ

1
これは、プロジェクトに条件付きでインクルードされたファイル(たとえば、機能が有効な場合にのみ使用される、または特定のオペレーティングシステムでのみ使用されるいくつかのファイル)がある場合は機能しません。一部のファイルは特定のプラットフォームでのみ使用されるため、ポータブルソフトウェアでは十分に一般的です。
ideasman42

0

各ファイルを個別に指定してください!

従来のCMakeLists.txtとPythonスクリプトを使用して更新します。ファイルを追加した後、Pythonスクリプトを手動で実行します。

ここで私の答えを参照してください:https : //stackoverflow.com/a/48318388/3929196

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