CMakeは、ターゲットのソースファイルを指定するいくつかの方法を提供します。1つは、グロビング(documentation)を使用することです。次に例を示します。
FILE(GLOB MY_SRCS dir/*)
別の方法は、各ファイルを個別に指定することです。
どちらの方法が好ましいですか?グロビングは簡単に思えますが、いくつかの欠点があると聞きました。
CMakeは、ターゲットのソースファイルを指定するいくつかの方法を提供します。1つは、グロビング(documentation)を使用することです。次に例を示します。
FILE(GLOB MY_SRCS dir/*)
別の方法は、各ファイルを個別に指定することです。
どちらの方法が好ましいですか?グロビングは簡単に思えますが、いくつかの欠点があると聞きました。
回答:
完全な開示:最初は単純化のためにグロビングアプローチを好んでいましたが、長年にわたって、ファイルを明示的にリストすることは、大規模なマルチ開発者プロジェクトではエラーが発生しにくいことを認識しました。
元の答え:
グロビングの利点は次のとおりです。
新しいファイルはディスク上の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などを使用して、同じビルドディレクトリで古いバージョンのコードを試す場合です。その場合、リスト内の正しいファイルを確実に取得するために、必要以上にクリーンアップとコンパイルが必要になる場合があります。これはそのようなコーナーケースであり、あなたがすでにあなたのつま先になっている場合、それは本当に問題ではありません。
Simply "touch" the CMakeLists.txt
は、新しいファイルが見つからないcmakeの欠点を覆い隠していると思いますが、開発者であれば問題ありませんが、ソフトウェアをビルドしている他の人にとっては、更新後にビルドが失敗し、調査するのが彼らの負担であるというのは、本当に苦痛です。なぜ。
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.txt
CMakeを使用しない既存のプロジェクトのファイルを設定するため。cmake
ビルドファイルを生成する必要があります(これはCMakeの意図に反します-構成をビルドから分割する機能)。* はい、更新の前後にディスク上のファイルのツリーを比較するコードを書くこともできましたが、これはそれほど良い回避策ではなく、ビルドシステムに任せたほうがよいでしょう。
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
確実に機能する場合でも、再構築のたびにチェックを実行するにはコストがかかります。
個人的には、考えられる欠点を補うためにソースファイルリストを手動で管理する必要がないことの利点を検討します。手動でリストされたファイルに切り替える必要がある場合は、グロブソースリストを印刷して貼り付けるだけで簡単に実現できます。
依存関係を保持するために追加のファイルを犠牲にして安全にグロブすることができます(おそらくそうする必要があります)。
次のような関数をどこかに追加します。
# 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をタップします。
make
奇妙なリンカエラーを与えるという懸念を解決します。
各ファイルを個別に指定してください!
従来のCMakeLists.txtとPythonスクリプトを使用して更新します。ファイルを追加した後、Pythonスクリプトを手動で実行します。
ここで私の答えを参照してください:https : //stackoverflow.com/a/48318388/3929196