ROS學習筆記15(ROS/CMakeLists.txt檔案)
1 概述
CMakeLists.txt檔案是構建軟體包所必備的檔案 ,其描述瞭如何構建程式以及在哪裡安裝程式包。任何一個檔案包通常都會包含一個或者多個CMakeLists.txt檔案。CMakeLists.txt檔案遵守了vanilla 標準,用於一個catkin專案,含有一定的約束條件。
2 整體結構和結構
CMakeListx.txt檔案必須準守下面的這些守則,否則的話,程式包將不能被正確的構建。在配置檔案中,下面的順序很重要。
-
Required CMake Version (cmake_minimum_required)
-
Package Name (project())
-
Find other CMake/Catkin packages needed for build (find_package())
-
Enable Python module support (catkin_python_setup())
-
Message/Service/Action Generators (add_message_files(), add_service_files(), add_action_files())
-
Invoke message/service/action generation (generate_messages())
-
Specify package build info export
-
Libraries/Executables to build (add_library()/add_executable()/target_link_libraries())
-
Tests to build (catkin_add_gtest())
-
Install rules (install())
3 CMake的版本
每一個CMakeLists.txt檔案都是以所需要的CMake的版本開始。Catkin需要的CMake版本至少2.8.3。
cmake_minimum_required(VERSION 2.8.3)
4 軟體包的名字
然後的條款是由CMake 專案函式指定包的名稱。比如,我們構建了一個叫做robot_brain的安裝包。
project(robot_brain)
需要注意的是,在之後的CMake指令碼中,你可以使用${PROJECT_NAME}引用專案名稱。
5 找到依賴的CMake包
我們使用CMake find_package函式來指定一些必備的用來構建我們專案的CMake軟體包。通常存在有至少一個被依賴的軟體包:catkin。
find_package(catkin REQUIRED)
如果你的專案也依賴於其他的一些軟體包,可以將他們自動的被轉化為catkin的元件;並不是直接使用find_package於這些軟體包,而是將它們轉化為元件,這會令任務更簡單。例如,假如你想使用nodelet軟體包。
find_package(catkin REQUIRED COMPONENTS nodelet)
注意:find_package 的元件功能僅用於當你想要找到構建專案的軟體包,不要新增 專案runtime時的依賴。
你也可以這樣做:
find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)
當然,這是一種很不方便的做法。
5.1 find_package()做了什麼
如果一個軟體包被CMake通過find_package函式找到,那麼系統會自動的生成一系列描述軟體包的CMake 環境變數。這些環境變數會稍後被CMake指令碼使用。與此同時,這些環境變數描述了軟體包會把標頭檔案匯出到了那裡,以及哪些被軟體包依賴的庫和這些庫的路徑。環境變數的命名通常是:<PACKAGE NAME>_<PROPERTY>:
<NAME>_FOUND 當庫被找到的時候,被設定為true;否則,設定為false;
<NAME>_INCLUDE_DIRS 或者 <NAME>_INCLUDES 包含了軟體包匯出的路徑
<NAME>_LIBRARIES 或者 <NAME>_LIBS 被軟體包匯出的庫
<NAME>_DEFINITIONS ?
5.2 Catkin Packages 為什麼要被指定為元件?
Catkin軟體包並不是catkin真正的元件。不過設計出來的CMake的元件功能卻可以明顯的節約你的輸入時間。
對於catkin 軟體包,當使用find_package找到它們並把它們當做catkin元件,則這種方法的優勢很明顯,而且其建立了一系列有著catkin_字首的環境變數。舉個例子,當想要在程式中使用nodelet軟體包的時候,一種比較建議的方法如下:
find_package(catkin REQUIRED COMPONENTS nodelet)
這意味著包含路徑,庫,等等被nodelet軟體包匯出的變數也被新增到了catkin_variables。比如,catkin_INCLUDE_DIRS不僅僅包含了catkin的路徑同時也包含了nodelet的路徑。這在之後用很有用。
當然我們也可以使用下面的這種方法:
find_package(nodelet)
然而這意味著nodelet的路徑,庫等等並不會新增到catkin_variables中。
這會產生了nodelet_INCLUDE_DIRS,nodelet_LIBRARIES等等一系列的變數。
同樣的變數也可以使用下面方法建立:
find_package(catkin REQUIRED COMPONENTS nodelet)
5.3 Boost
如果使用C++和Boost,你需要援引find_package()於Boost並且需要特別指定Boost的那些方面。例如,當你想要使用Boost threads時,你要如下這樣做:
find_package(Boost REQUIRED COMPONENTS thread)
6 catkin_package()
catkin_package()是一個catkin_provided 的CMake macro檔案。構建系統必須要指定catkin特定的資訊,而系統又用於生成類pkg-config和CMake檔案。
在使用add_library()和add_executable()函式宣告目標之前,必須的呼叫這個函式。這個函式有5個可選的引數。
INCLUDE_DIRS - 包含了匯出軟體包標頭檔案的路徑
LIBRARIES - 專案匯出的庫
CATKIN_DEPENDS - 這個專案依賴的其他catkin專案
DEPENDS - 這個專案依賴的其他non-catkin專案。更詳細的講解,參考this explanation.
CFG_EXTRAS - 其他額外的一些配置選擇
比較詳細的macros 說明檔案可以參考 here.
舉個例子:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
其中inlcude 中是軟體包匯出的標頭檔案路徑。CMake環境變數${PROJECT_NAME}就是之前傳遞給project()函式的引數,在這個例子中,它就是“robot_brain”。"roscpp" + "nodelet"是需要構建這個軟體包所必須的catkin軟體包,而"eigen" + "opencv"是構建這個軟體包的系統依賴,也就是非non-catkin軟體包。
7 指定生成目標
有很多的方式可以生成目標檔案。但通常具有代表性的是下面。
1.可執行目標。我們可以執行的程式。
2.庫目標。被可執行目標在構建或者執行時間時使用的庫。
7.1 目標命名
非常重要的是需要注意,catkin中構建目標的名稱必須是唯一的,不管它們是構建/安裝到哪個資料夾。這是CMake的要求。但是,目標的名稱僅在CMake內部是必需的。可以使用set_target_properties()函式將目標重新命名為其他目標:
例:
set_target_properties(rviz_image_view
PROPERTIES OUTPUT_NAME image_view
PREFIX "")
這將在構建和安裝輸出中將目標rviz_image_view的名稱更改為image_view。
7.2 自定義輸出目錄
雖然可執行檔案和庫的預設輸出目錄通常被設定為合理的值,但在某些情況下必須自定義。比如,包含Python繫結的庫必須放在不同的資料夾中,以便可以在Python中匯入,如下:
例:
set_target_properties(python_module_library
PROPERTIES LIBRARY_OUTPUT_DIRECTORY $ {CATKIN_DEVEL_PREFIX} / $ {CATKIN_PACKAGE_PYTHON_DESTINATION})
7.3 包含路徑和庫路徑
在指定目標之前,需要指定可以構建目標的資源位置,特別是標頭檔案和庫:
- 包含路徑 - 在哪裡可以找到正在構建的程式碼(在C / C ++中最常見)的標頭檔案
- 庫路徑 - 可執行目標構建的庫位於何處?
-
include_directories(<dir1>,<dir2>,...,<dirN>)
-
link_directories(<dir1>,<dir2>,...,<dirN>)
7.3.1 include_directories()
include_directories的引數應該是find_package呼叫二生成的* _INCLUDE_DIRS變數以及其他需要包含的任何其他目錄。如果您使用catkin和Boost,則include_directories()呼叫應如下所示:
include_directories(include $ {Boost_INCLUDE_DIRS} $ {catkin_INCLUDE_DIRS})
第一個引數“include”表示包中的include 目錄也是路徑的一部分(其中包含了標頭檔案的路徑)。
7.3.2 link_directories()
CMake link_directories()函式可用於新增其他庫路徑,但並不建議這樣做。所有catkin和CMake軟體包在find_packaged時自動新增其連結資訊。只需連結到target_link_libraries()中的庫即可。
例:
link_directories(〜/ my_libs)
請參閱本cmake的教程可以看到使用的詳細例子使用target_link_libraries()以及link_directories() 。
7.4 可執行目標
要指定必須構建的可執行目標,我們使用add_executable()這個CMake函式。
例:
add_executable(myProgram src / main.cpp src / some_file.cpp src / another_file.cpp)
這將構建一個名為myProgram的目標可執行檔案,它由3個原始檔構成:src/main.cpp,src/some_file.cpp和src/ another_file.cpp。
7.5 庫目標
add_library()這個 CMake函式用來指定庫來構建。預設情況下,catkin會構建共享庫。
例:
add_library($ {PROJECT_NAME} $ {$ {PROJECT_NAME} _SRCS})
7.6 target_link_libraries
使用target_link_libraries() 函式可以指定可執行目標連結的庫。
這通常在add_executable()呼叫之後完成。如果ROS 找不到的話,則應該新增$ {catkin_LIBRARIES}。
句法:
target_link_libraries(<executableTargetName>,<lib1>,<lib2>,... <libN>)
例:
add_executable(foo src / foo.cpp)
add_library(moo src / moo.cpp)
target_link_libraries(foo moo) - This links foo against libmoo.so
注意,在大多數用例中不需要使用link_directories(),因為該資訊是通過find_package()自動引入的。
8 訊息,服務和操作目標
ROS中的messages(.msg),services(.srv)和action(.action)檔案在構建和使用ROS包之前,需要進行特殊的前處理器構建步驟。這些巨集是生成指定程式語言的檔案的關鍵,以便可以使用所選程式語言中的訊息,服務和操作。構建系統可以使用所有可用的生成(例如gencpp,genpy,genlisp等)。
提供了三個巨集來分別處理訊息,服務和操作:
-
add_message_files
-
add_service_files
-
add_action_files
必須在呼叫生成的巨集之後呼叫這些巨集:
generate_messages()
8.1 重要的先決條件和限制
-
這些macros必須在catkin_package()函式使用之前呼叫,以便生成正常工作。
find_package(catkin REQUIRED COMPONENTS ...)
add_message_files(...)
add_service_files(...)
add_action_files(...)
generate_messages(...)
catkin_package(...)
...
-
catkin_package()函式必須包含這種CATKIN_DEPENDS對message_runtime依賴關係。
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
-
find_package()必須包含有包messagese_generation,可以單獨使用,也可以作為catkin的一個元件使用:
find_package(catkin REQUIRED COMPONENTS message_generation)
-
package.xml檔案構建時的依賴必須包含:message_generation;執行時的依賴必須包含:message_runtime。如果依賴性是從其他包傳遞的,那麼這不是必需的。
-
如果您有一個目標(甚至傳遞)依賴一些其他需要構建訊息/服務/操作的目標,則需要在目標catkin_EXPORTED_TARGETS上新增顯式依賴項,以便它們以正確的順序構建。除非您的軟體包確實不使用ROS的任何部分,否則這種情況幾乎總是適用。不幸的是,這種依賴關係不能自動傳播。( some_target 是被add_executable()構建的目標名稱):
add_dependencies(some_target $ {catkin_EXPORTED_TARGETS})
-
如果您有一個構建訊息和/或服務的包以及使用這個包的可執行檔案,則需要在自動生成的訊息目標上建立顯式依賴項,以便以正確的順序構建它們。( some_target 是被add_executable()構建的目標名稱):
add_dependencies(some_target $ {$ {PROJECT_NAME} _EXPORTED_TARGETS})
-
如果您的包同時滿足上述兩個條件,則需要新增兩個依賴項,即:
add_dependencies(some_target $ {$ {PROJECT_NAME} _EXPORTED_TARGETS} $ {catkin_EXPORTED_TARGETS})
8.2 例子
軟體包在名為“msg”的目錄下分別有兩個訊息檔案:“ MyMessage1.msg ”和“ MyMessage2.msg ” ,這些訊息依賴於std_msgs和sensor_msgs。軟體包在名為“srv”的目錄中有一個服務檔案:“ MyService.srv ”。使用這些訊息和服務的可執行檔案為message_program。只使用ROS部分的功能和不使用這個軟體包中定義的訊息/服務一個可執行檔案do_not_use_local_messages_program,那麼您需要在CMakeLists.txt中使用以下內容:
# Get the information about this package's buildtime dependencies
find_package(catkin REQUIRED
COMPONENTS message_generation std_msgs sensor_msgs)
# Declare the message files to be built
add_message_files(FILES
MyMessage1.msg
MyMessage2.msg
)
# Declare the service files to be built
add_service_files(FILES
MyService.srv
)
# Actually generate the language-specific message and service files
generate_messages(DEPENDENCIES std_msgs sensor_msgs)
# Declare that this catkin package's runtime dependencies
catkin_package(
CATKIN_DEPENDS message_runtime std_msgs sensor_msgs
)
# define executable using MyMessage1 etc.
add_executable(message_program src/main.cpp)
add_dependencies(message_program ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# define executable not using any messages/services provided by this package
add_executable(does_not_use_local_messages_program src/main.cpp)
add_dependencies(does_not_use_local_messages_program ${catkin_EXPORTED_TARGETS})
另外,如果要構建actionlib操作,且在“action”目錄中有一個名為“ MyAction.action ” 的操作規範檔案,則必須將actionlib_msgs新增到使用catkin find_packaged的元件列表中,並在之前新增以下呼叫對generate_messages(...)的呼叫:
add_action_files(FILES
MyAction.action
)
包必須具有對actionlib_msgs的構建(build)依賴性。
9 啟用Python模組支援
如果你的ROS包提供了一些Python模組,你應該建立一個setup.py檔案並呼叫
catkin_python_setup()
使用上述函式需要在呼叫generate_messages()和catkin_package()之前。
10 單元測試
有一個特定於catkin的巨集用於處理名為catkin_add_gtest()的基於gtest的單元測試。
if(CATKIN_ENABLE_TESTING)
catkin_add_gtest(myUnitTest test / utest.cpp)
endif()
11 可選步驟:指定可安裝目標
經過構建之後,需要將目標放置到catkin工作空間的開發空間中。但是,我們通常希望在系統中安裝目標(有關安裝路徑的資訊可以在REP 122中找到 ),以便其他人或本地資料夾可以使用它們來測試系統級安裝。換句話說,如果您希望能夠對程式碼進行“make install”,則需要指定目標應該結束的位置。
這是由CMake install()函式完成的,該函式有下面的幾個引數:
-
TARGETS - 那個被安裝的目標
-
ARCHIVE DESTINATION - 靜態庫和DLL(Windows).lib存根
-
LIBRARY DESTINATION - 非DLL共享庫和模組
-
RUNTIME DESTINATION - 可執行目標和DLL(Windows)樣式共享庫
舉個例子:
install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
除了這些標準目標,有時一些檔案必須安裝到特殊資料夾。即,必須將包含Python繫結的庫安裝到可在Python中匯入的其他資料夾中:
install(TARGETS python_module_library
ARCHIVE DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
)
11.1 安裝Python的可執行指令碼
對於Python程式碼,安裝規則看起來不同,這是因為沒有使用add_library()和add_executable()函式,以便CMake確定哪些檔案是目標以及它們是什麼型別的目標。
在CMakeLists.txt檔案中使用以下內容:
catkin_install_python(PROGRAMS scripts/myscript
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
有關安裝python指令碼和模組的詳細資訊,以及資料夾佈局的最佳實踐,請參閱catkin手冊。
如果只安裝Python指令碼並且不提供任何模組,則既不需要建立上面提到的setup.py檔案(需要使用的時候,再進行查閱),也不需要呼叫catkin_python_setup()。
11.2 安裝標頭檔案
標頭檔案也必須安裝到“include”資料夾中,這通常通過安裝整個檔案完成(可以選擇使用檔名模式排除無需安裝的子檔案,比如排除SVM字尾的子檔案)。可以通過如下的安裝規則完成:
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
或者當子資料夾的名稱與包的名稱相沖突的時候
install(DIRECTORY include/
DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
11.3 安裝roslaunch檔案和其他的一些資源
其他的一些資源,比如launchfiles可以被安裝這個檔案下:${CATKIN_PACKAGE_SHARE_DESTINATION}:
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
PATTERN ".svn" EXCLUDE)