CMakeは、ターゲットのソースファイルを指定するいくつかの方法を提供します。1つは、グロブ(ドキュメント)を使用することです。次に例を示します。
FILE(GLOB MY_SRCS dir/*)
別の方法は、各ファイルを個別に指定することです。
どちらの方法が好ましいですか?グロブは簡単に思えますが、いくつかの欠点があると聞きました。
完全な開示:私は当初、その単純さのためにグロブアプローチを好みましたが、長年にわたって、ファイルを明示的にリストすることは、大規模なマルチ開発者プロジェクトではエラーが発生しにくいことを認識するようになりました。
元の答え:
グロブの利点は次のとおりです。
新しいファイルはディスク上の1つの場所にしかリストされていないため、簡単に追加できます。グロブしないと重複が生じます。
CMakeLists.txtファイルは短くなります。たくさんのファイルがある場合、これは大きなプラスです。グロブしないと、ファイルの膨大なリストの中でCMakeロジックが失われます。
ハードコードされたファイルリストを使用する利点は次のとおりです。
CMakeはディスク上の新しいファイルの依存関係を正しく追跡します-globを使用すると、CMakeを実行したときに最初にglobされなかったファイルは取得されません
必要なファイルのみが追加されるようにします。Globbingは、不要な漂遊ファイルを拾う可能性があります。
最初の問題を回避するには、touchコマンドを使用するか、ファイルを変更せずに書き込むことで、globを実行するCMakeLists.txtに「タッチ」するだけです。これにより、CMakeが強制的に再実行され、新しいファイルが取得されます。
2番目の問題を修正するには、コードを慎重にディレクトリに整理します。これは、おそらくとにかく行うことです。最悪の場合、次のlist(REMOVE_ITEM)
コマンドを使用して、グロブされたファイルのリストをクリーンアップできます。
file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})
これがあなたを噛む可能性がある唯一の実際の状況は、git-bisectのようなものを使用して、同じビルドディレクトリで古いバージョンのコードを試している場合です。その場合、リストに適切なファイルを確実に取得するために、必要以上にクリーンアップしてコンパイルする必要があります。これは非常に重要なケースであり、すでに問題になっているケースであるため、実際には問題にはなりません。
CMakeでソースファイルを指定する最良の方法は、それらを明示的にリストすることです。
CMakeの作成者自身は、グロブを使用しないようにアドバイスしています。
参照:https ://cmake.org/cmake/help/v3.15/command/file.html?highlight = glob#file
(GLOBを使用してソースツリーからソースファイルのリストを収集することはお勧めしません。ソースが追加または削除されたときにCMakeLists.txtファイルが変更されない場合、生成されたビルドシステムはCMakeに再生成を要求するタイミングを認識できません。)
もちろん、あなたは欠点が何であるかを知りたいかもしれません-読んでください!
グロブの大きな欠点は、ファイルを作成/削除してもビルドシステムが自動的に更新されないことです。
あなたがファイルを追加する人であれば、これは許容できるトレードオフのように見えるかもしれませんが、これはコードをビルドする他の人に問題を引き起こします。彼らはバージョン管理からプロジェクトを更新し、ビルドを実行してから、
「ビルドは壊れた"。
さらに悪いことに、障害は通常、問題の原因を示唆しないリンクエラーを引き起こし、トラブルシューティングに時間がかかります。
私が取り組んだプロジェクトでは、グロブを開始しましたが、新しいファイルが追加されると非常に多くの苦情が寄せられたため、グロブではなくファイルを明示的にリストするのに十分な理由でした。
これにより、一般的なgitワークフロー
(git bisect
および機能ブランチ間の切り替え)も中断されます。
だから私はこれをお勧めできませんでした、それが引き起こす問題は便利さをはるかに上回ります、これのために誰かがあなたのソフトウェアを構築できないとき、彼らは問題を追跡するために多くの時間を失うか、単にあきらめるかもしれません。
また、CMakeLists.txt
グロブを使用する自動ビルドでは、最後のビルド以降にファイルが追加/削除された可能性があるため、すべてのビルドcmake
の前に実行する必要がありました*。
グロブが望ましい場合があります。
CMakeLists.txt
CMakeを使用しない既存のプロジェクトのファイルを設定します。cmake
ビルドファイルを生成するための実行を受け入れる必要があります(これはCMakeの意図に反します-ビルドから構成を分割する機能)。*はい、更新の前後でディスク上のファイルのツリーを比較するコードを書くこともできましたが、これはそれほど良い回避策ではなく、ビルドシステムに任せたほうがよいでしょう。
CMake 3.12では、file(GLOB ...)
andfile(GLOB_RECURSE ...)
コマンドにCONFIGURE_DEPENDS
、globの値が変更された場合に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をタッチします。
各ファイルを個別に指定してください!
従来のCMakeLists.txtとPythonスクリプトを使用して更新します。ファイルを追加した後、Pythonスクリプトを手動で実行します。
ここで私の答えを参照してください: https ://stackoverflow.com/a/48318388/3929196