web-dev-qa-db-ja.com

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

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

FILE (GLOB dir/*)

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

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

139
Marenz

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

元の答え:


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

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

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

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

  • CMakeはディスク上の新しいファイルの依存関係を正しく追跡します-globを使用すると、CMakeを最初に実行したときにglobされなかったファイルはピックアップされません

  • 必要なファイルのみが追加されるようにします。グロビングは、不要なファイルを拾う可能性があります。

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

2番目の問題を修正するには、コードをディレクトリに慎重に整理します。最悪の場合、list(REMOVE_ITEM)コマンドを使用して、グロブ化されたファイルのリストをクリーンアップできます。

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

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

171
richq

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

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

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

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

もちろん、マイナス面を知りたいかもしれません-続きを読む!


グロビングが失敗した場合:

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

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

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

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

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

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

そして別のメモ、ただCMakeLists.txtだけでは十分ではありません。グロビングを使用する自動ビルドでは、ファイルmightが実行されるので、すべてのビルドの前にcmakeを実行する必要がありました最後の建物以降に追加/削除*。

ルールの例外:

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

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

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

105
ideasman42

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

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

# 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をタッチします。

8
Glen Knowles

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は確実に機能しますが、再構築のたびにチェックを実行するためのコストがまだあります。

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

5
Justin

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

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

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

0
palfi