ROS Catkin 教程之 CMakeLists.txt
1. 概覽
CMakeLists.txt 是用 CMake 構建系統構建 ROS 程式包的輸入檔案。任何相容 CMake 的包都包含一個或多個 CMakeLists.txt 檔案,用以描述怎樣構建和安裝程式碼。catkin 專案採用標準的 vanilla CMakeLists.txt 檔案,並帶有一些額外的約束。
2. 總體結構和順序
你的 CMakeLists.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 Version
每個 catkin CMakeLists.txt 檔案必須以所需 CMake 最低版本開頭。 Catkin 需要 2.8.3 或更高版本。
cmake_minimum_required(VERSION 2.8.3)
4. Package name
接下來一項是程式包名,由 CMake 的 project 函式指定。比如說構建一個 robot_brain 的程式包:
project(robot_brain)
注意,在 CMake 中,如果有需要,你可以使用變數 ${PROJECT_NAME} 在指令碼的任意地方引用專案名。
5. 查詢依賴的 CMake 程式包
然後我們需要使用 find_package 函式指出在構建我們的專案前需要查詢哪些 CMake 程式包。我們總是至少有一個依賴項,那就是 catkin:
find_package(catkin REQUIRED)
如果你的專案還依賴於其他的包,他們會自動變成 catkin 的元件(就 CMake 而言)。將那些包指定為元件,而不是對其使用 find_package,會使過程變得更簡單。例如,如果你使用了 nodelet 程式包:
find_package(catkin REQUIRED COMPONENTS nodelet)
注意:你只需要找出在構建目標時依賴的元件,而不需要找出執行時依賴。
你也可以使用下面的方式:
find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)
但是,你會發現這樣頗有不便。
5.1 find_package() 做了什麼?
如果通過 find_package 查詢到某個程式包,其結果就是建立一些 CMake 環境變數,這些變數給出了有關找到的程式包的資訊。接下來可以在 CMake 指令碼中使用這些環境變數。這些環境變數描述了程式包的外部標頭檔案的位置,程式包所依賴於哪些庫,以及庫所在的路徑。變數的命名規則通常為 <PACKAGE NAME>_<PROPERTY> :
- <NAME>_FOUND - 如果找到庫則設定為真
- <NAME>_INCLUDE_DIRS 或 <NAME>_INCLUDES - 程式包匯出的包含路徑
- <NAME>_LIBRARIES 或 <NAME>_LIBS - 程式包匯出的庫路徑
- <NAME>_DEFINITIONS - ?
5.2 為什麼將程式包指定為元件?
Catkin 程式包並不真的是 catkin 的元件,相反,使用 CMake 元件功能這種設計簡化了你的輸入過程。
對於 catkin 程式包,將他們作為 catkin 的元件是有好處的,因為可以使用 catkin_ 字首統一建立一組環境變數。假如你在程式碼中用到了程式包 nodelet,查詢依賴包的建議方法如下:
find_package(catkin REQUIRED COMPONENTS nodelet)
這意味著 nodelet 匯出的包含路徑、庫路徑等也會附加到 catkin_ 變數。例如,catkin_INCLUDE_DIRS 變數不僅包含了 catkin 的相關路徑,也包含了 nodelet 的相關路徑。這將在之後派上用場。
我們也可以單獨對 nodelet 應用 find_package:
find_package(nodelet)
5.3 Boost
如果使用 C++ 和 Boost,則需要在 Boost 上呼叫 find_package(),並指出你正在使用 Boost 哪些方面的元件。例如你想使用 Boost threads,你需要這樣寫:
find_package(Boost REQUIRED COMPONENTS thread)
6. catkin_package()
catkin_package() 是一個由 catkin 提供的巨集。這是用來指定與編譯系統有關的 catkin 專用資訊的,這些資訊被用來生成 pkg-config 和 CMake 檔案。
此函式必須在用 add_library() 或 add_executable() 生成任何目標前呼叫。該函式具有 5 個可選引數:
- INCLUDE_DIRS - 包的匯出包含路徑
- LIBRARIES - 從包匯出的庫
- CATKIN_DEPENDS - 此專案依賴的其他 catkin 專案
- DEPENDS - 此專案依賴的非 catkin 的 CMake 專案。若要詳細理解,請檢視這篇解釋
- CFG_EXTRAS - 其他選項
可到此處檢視關於全部巨集的文件。
舉個例子:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
這表示標頭檔案將匯出到程式包資料夾中的“include”資料夾。其中 CMake 環境變數 ${PROJECT_NAME} 的值為你先前傳遞給 project() 函式的值。“roscpp” + “nodelet” 是構建/執行此程式包時需要存在的包,“eigen” + “opencv” 是構建/執行此程式包時需存在的系統依賴項。
7. 指定構建目標
構建目標有多種形式,但是通常是以下兩種可能中的一個:
- 可執行目標 - 可以執行的程式
- 庫目標 - 可執行目標在構建和/或執行時可以使用的庫
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 包含路徑和庫路徑
在指定目標前,你需要為目標指出可找到的資源的位置,特別是標頭檔案和庫:
- Include Paths - 要構建的程式碼(通常為 C/C++)的標頭檔案的位置
- Library Paths - 構建可執行目標所需的庫的位置
- 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_package 時新增。只需要在 target_link_libraries() 中連線這些庫就行了。
例如:
link_directories(~/my_libs)
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() 函式用於指定要構建的庫目標。預設情況下 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. Messages,Services,和 Action 目標
在被 ROS 程式包構建和使用之前,ROS 中的 Messages (.msg),services (.srv) 和 actions (.action) 檔案需要特殊的預處理構建步驟。這些巨集的關鍵作用是生成由特定程式語言編寫的檔案,以便可以在所選程式語言中使用這些 Messages,services 和 actions。構建系統將使用所有可用的生成器(例如 gencpp,genpy,genlisp 等)生成繫結。
有三個巨集來分別處理 messages,services 和 actions:
- add_message_files
- add_service_files
- add_action_files
在呼叫上述巨集之後必須呼叫下邊的巨集:
generate_messages()
8.1 重要的先決條件/限制
- 這些巨集必須位於 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 ...
...)
- 你必須為 messagese_generation 使用 find_package() ,可以單獨使用,也可以作為 catkin 的一個元件使用:
find_package(catkin REQUIRED COMPONENTS message_generation)
-
package.xml 檔案必須包含對 message_generation 的 build 依賴以及對 message_runtime 的 runtime 依賴。 如果依賴關係是從其他包傳遞的,那麼這不是必需的。
-
如果你有一個目標依賴於(甚至傳遞地依賴於)需要構建 messages/services/actions 檔案的其他目標,則需要對目標 catkin_EXPORTED_TARGETS 新增顯式依賴,以便它們以正確的順序構建。 除非你的軟體包確實不使用 ROS 的任何部分,否則這種情況幾乎總是適用。不幸的是,這種依賴關係不能自動傳播。 示例如下(some_target 是 add_executable() 設定的目標的名稱):
add_dependencies(some_target ${catkin_EXPORTED_TARGETS})
- 如果你的程式包即要構建 messages 和/或 services,又要構建使用它們的可執行檔案,則需要在自動生成的訊息目標上建立顯式依賴項,以便以正確的順序構建它們。 示例如下(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” 目錄中有兩條 messages,分別名為 “MyMessage1.msg” 和 “MyMessage2.msg”,並且這些訊息依賴於 std_msgs 和 sensor_msgs,在 “srv” 的目錄中的有一個 service 名為 “MyService.srv”, 定義使用這些 messages 和 service 的可執行檔案 message_program,以及可執行檔案 do_not_use_local_messages_program,它使用 ROS 的某些部分,但不使用此程式包中定義的messages/service,那麼你需要在 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” 的 action 規範檔案,則必須將 actionlib_msgs 新增到使用 catkin find_packaged 的元件列表中,並在呼叫 generate_messages() 前新增以下呼叫:
add_action_files(FILES
MyAction.action
)
此外,package.xml 中必須具有對 actionlib_msgs 的 build 依賴。
9. 使能 Python 支援
如果你的 ROS 程式包提供一些 Python 模組,你必須建立 setup.py 檔案,並呼叫
catkin_python_setup()
10. 單元測試
有一個 catkin 專用的巨集用於處理基於 gtest 的單元測試,稱為 catkin_add_gtest()。
if(CATKIN_ENABLE_TESTING)
catkin_add_gtest(myUnitTest test/utest.cpp)
endif()
11. 可選步驟:指定可安裝目標
構建之後,目標檔案被放在 catkin 工作空間的 devel 空間中。然而,我們通常希望將目標檔案安裝到系統中以使其他人可以使用,或者安裝到一個本地資料夾中以測試系統級的安裝。換句話說,如果你想對你的程式碼進行 “make install” 操作,你需要指定目標檔案應當被安裝到哪個位置。
這通過使用 CMake install() 函式完成,帶有以下引數:
- TARGET - 目標被安裝到哪裡
- 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”資料夾中,這通常是通過安裝整個資料夾的檔案(可按檔名過濾並排除 SVN 子資料夾)來完成的。可以通過如下所示的安裝規則來完成:
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
或者如果 include 資料夾下的子資料夾和程式包名不匹配的話:
install(DIRECTORY include/
DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
11.3 安裝 roslaunch 檔案和其他資源
其他資源,例如啟動檔案可以被安裝到 ${CATKIN_PACKAGE_SHARE_DESTINATION}:
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
PATTERN ".svn" EXCLUDE)