轉眼間又是一個輪迴,2018年6月之後因爲一些事情就沒有再更新博客,重新寫的時候已是2019年的5月,然後2019年6月之後,因爲精力投向了其它方面就又停了,現在重新提筆又是5月,只不過是2020年的5月。最近寫代碼,遇到了一些編譯鏈接上的問題,想寫下來。因爲這些問題都跟ROS中的構建系統有關,所以首先就找到了官網的教程,以下內容,只是個人對其的翻譯和理解。
文章目錄
1. 概述
CMakeLists.txt文件作爲CMake軟件構建系統的切入點,任何使用CMake系統進行構建的軟件包都包含一個或多個CMakeLists.txt文件,用以描述如何進行代碼構建和部署。而在ROS的catkin工程中使用的CMakeLists.txt文件符合標準的vanilla CMakeLists.txt文件格式,只有些許針對性的限制。
2.文件結構和次序
CMakeLists.txt文件具有固定的格式,甚至有時候自己編寫時,其格式所包含的關鍵指令次序不對,也會引發各種問題。在ROS中,新創建一個包時會附帶創建一個CMakeLists.txt,先看一下帶有部分自帶信息的CMakeLists.txt文件,去掉了暫時用不到指令和說明。
cmake_minimum_required(VERSION 2.8.3) // 所需要的 CMake 版本
project(yumi_test) // 軟件包名稱
add_compile_options(-std=c++11) // 使用 C++11 標準進行編譯,支持 Kinetic 及之後的版本
find_package(catkin REQUIRED COMPONENTS // 搜索 build 需要的軟件包
geometric_shapes
moveit_core
moveit_ros_planning
moveit_ros_planning_interface
pcl_conversions
pcl_ros
pluginlib
rosbag
roscpp
tf2_eigen
tf2_geometry_msgs
tf2_ros
yumi_hw
message_generation
)
find_package(Boost REQUIRED system filesystem date_time thread)
# catkin_python_setup() // python 模塊支持
add_message_files( // 添加 msg 文件夾中自定義的 msg 消息
FILES
Message.msg
)
add_service_files( // 添加 srv 文件夾中自定義的 srv 服務消息
FILES
Service.srv
)
add_action_files( // 添加 action 文件夾中自定義的 action 消息
FILES
Action.action
)
generate_messages( // 消息生成宏命令
DEPENDENCIES
tf2_geometry_msgs // 消息生成所依賴的其它消息類型
)
# generate_dynamic_reconfigure_options(// 生成cfg文件夾定義的動態配置參數
# cfg/DynReconf.cfg
# )
catkin_package( // 爲軟件包生成 CMake 配置文件
INCLUDE_DIRS include // 當前軟件包將要導出的頭文件存放目錄
LIBRARIES ${PROJECT_NAME} // 當前軟件包將要導出的庫
CATKIN_DEPENDS Boost Eigen geometric_shapes moveit_core moveit_ros_planning
moveit_ros_planning_interface pcl_conversions pcl_ros pluginlib rosbag roscpp
tf2_eigen tf2_geometry_msgs tf2_ros message_runtime
DEPENDS system_lib
)
include_directories( // 指定額外的頭文件位置,當前軟件包的頭文件位置放在最前面
include // 當前軟件的頭文件位置
${catkin_INCLUDE_DIRS} // 生成的軟件所依賴的頭文件的路徑變量
)
add_library(${PROJECT_NAME} // 構建 C++ 庫目標
src/${PROJECT_NAME}/yumi_motion.cpp
)
add_dependencies(${PROJECT_NAME} // 爲構建的庫目標添加依賴
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)
add_executable(${PROJECT_NAME}_node src/yumi_gripper_node.cpp) // 構建可執行目標,加前綴防止命名衝突
add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) //添加依賴
target_link_libraries(${PROJECT_NAME}_node ${catkin_LIBRARIES} ) // Specify libraries to link a library or executable target against
set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") //修改名稱
add_executable(yumi_gripper_test src/yumi_gripper_test.cpp)
target_link_libraries(yumi_gripper_test ${catkin_LIBRARIES} ${Boost_LIBRARIES} )
add_dependencies(yumi_gripper_test ${catkin_EXPORTED_TARGETS})
## Mark executables for installation
# install(TARGETS ${PROJECT_NAME}_node
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
## Mark libraries for installation
# install(TARGETS ${PROJECT_NAME}
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )
## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
# FILES_MATCHING PATTERN "*.h"
# PATTERN ".svn" EXCLUDE
# )
## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
# # myfile1
# # myfile2
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )
# catkin_add_gtest(${PROJECT_NAME}-test test/test_yumi_motion.cpp) // 添加測試用可執行目標
# if(TARGET ${PROJECT_NAME}-test)
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()
3.格式說明
3.1 CMake版本
每個CMakeLists.txt文件必須首先聲明使用的CMake版本,一般是2.8.3或更高的版本。
cmake_minimum_required(VERSION 2.8.3)
3.2 軟件包名稱
指定軟件包的名稱,在CMake中使用project()
功能指定的軟件包名稱,隨後可以在文件中通過${PROJECT_NAME}
進行引用。
project(yumi_test)
3.3 搜尋依賴的CMake軟件包
搜索構建包所依賴的軟件包,首要的依賴包就是catkin,其次所依賴的包可以以組件的形式,寫在同一條件指令中。推薦的是在一條指令中添加所有依賴的包,當然也可以分開添加。
find_package(catkin REQUIRED COMPONENTS // 搜索 build 需要的軟件包
geometric_shapes
moveit_core
moveit_ros_planning
moveit_ros_planning_interface
pcl_conversions
pcl_ros
pluginlib
rosbag
roscpp
tf2_eigen
tf2_geometry_msgs
tf2_ros
yumi_hw
message_generation
)
find_package(Boost REQUIRED system filesystem date_time thread) // 分別添加的依賴項
CMake通過find_package
搜尋到的依賴包,會生成對應的環境變量供隨後的軟件包構建使用。這些環境變量描述了軟件包所依賴的頭文件、源文件和庫的路徑與位置。生成的環境變量命名上是按<PACKAGENAME>_<PROPERTY>
的形式。再詳細的就不展開了,掌握到這裏不影響使用。
而其中的COMPONENTS
組件,設計目的就是用來節省時間的。放到catkin
的COMPONENTS
裏,可以使依賴包的頭文件路徑、庫等信息統一包含到catkin
的環境變量中,而不是單獨創建環境變量。不過有些庫可能需要單獨的指令進行搜尋。
3.4 Python模塊支持
用來添加python的配置腳本,需要在generate_messages()
和catkin_package()
之前使用。
3.5 消息、服務和動作消息生成器
Messages(.msg),services(.srv) 和 actions(.action) 文件在ROS的軟件包使用之前需要特殊的預處理器進行處理。這些處理的宏會將其生成對應於編程所用語言的相關文件,供編程使用。編譯系統將使用所有可能的生成器生成對應語言的可用文件。在catkin的構建系統中,定義了三種宏命令,用來分別處理Messages(.msg),services(.srv) 和 actions(.action) 文件。
add_message_files( // 添加 msg 文件夾中自定義的 msg 消息
FILES
Message.msg
)
add_service_files( // 添加 srv 文件夾中自定義的 srv 服務消息
FILES
Service.srv
)
add_action_files( // 添加 action 文件夾中自定義的 action 消息
FILES
Action.action
)
而要生成對應的可用文件,則必須調用生成命令。如下所示:
generate_messages( // 消息生成宏命令
DEPENDENCIES
tf2_geometry_msgs // 消息生成所依賴的其它消息類型
)
3.5.1 先決條件
需要注意的是,以上宏指令的使用,必須滿足以下先決條件:
- 以上宏指令必須在
catkin_package()
宏指令之前,以確保構建的正常執行 catkin_package()
宏中的CATKIN_DEPENDS
參數必須具有message_runtime
這一依賴項find_package()
宏指令中必須包含message_generation
軟件包package.xml
文件中包含message_generation
的構建依賴和message_runtime
的執行依賴
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3.6 軟件包編譯配置文件生成
catkin_package()
是提供的catkin提供的CMake宏,用來指定編譯系統的配置信息,也被用來生成軟件包的配置和CMake文件。其必須在add_library()
和add_ececutable()
之前調用。其具有5項可選參數。
- INCLUDE_DIRS: 軟件包將導出的include路徑(即頭文件放在哪個文件夾之下)
- LIBRARIES: 軟件包將導出的庫
- CATKIN_DEPENDS:依賴的其它catkin projects
- DEPENDS:依賴的非catkin CMake Projects
- CFG_EXTRAS:其它的配置信息
例如:
catkin_package( //爲軟件包生成 CMake 配置文件
INCLUDE_DIRS include //配置軟件包中包含的頭文件導出路徑或位置
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS Boost Eigen geometric_shapes moveit_core moveit_ros_planning
moveit_ros_planning_interface pcl_conversions pcl_ros pluginlib rosbag roscpp
tf2_eigen tf2_geometry_msgs tf2_ros
DEPENDS system_lib
)
3.7 指定構建目標
構建目標可以採用多種形式,但通常是以下兩種形式之一:
- 可執行目標
- 庫目標
在設置構建目標之前,需要指定構建目標的代碼或庫的位置。用以下兩條指令:
include_directories(<dir1>,<dir2>,……,<dirN>) //頭文件所在的位置
link_directories(<dir1>,<dir2>,……,<dirN>) //庫文件所在的位置
第一條指令的參數應該是由find_package()
所生成的*_INCLUDE_DIRS
變量和其它需要包含進來的路徑。如果使用catkin和Boost,則第一條指令應該如下所示:
include_directories(include ${Boost_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS})
第一個參數include
表示當前包的include/
路徑。第二條指令用於包含額外的庫路徑,並不推薦,因爲在find_package()
調用時會自動添加。
在構建目標的設置過程中,非常重要的一點是:在CMake的內部,catkin的構建目標名稱必須是唯一的,而與構建或安裝到的文件夾無關。這也是CMake規定的。 接下來,開始目標構建.
3.7.1 構建可執行目標
這一目標是通過add_executable()
功能實現的,例如:
add_executable(yumi_gripper_test src/yumi_gripper_test.cpp)
構建名稱爲yumi_gripper_test的可執行文件,源文件爲當前軟件包中的src/yumi_gripper_test.cpp。如果具有多個源文件,可以依次在後面添加。
add_executable(yumi_gripper_test src/yumi_gripper_test.cpp src/first.cpp src/second.cpp)
3.7.2 構建庫目標
這一目標是通過add_library()
這一CMake功能來實現的,默認構建的爲共享庫。
add_library(${PROJECT_NAME}
src/${PROJECT_NAME}/yumi_test.cpp
)
第一個參數爲構建的庫名稱,第二個參數則是指定構建所用的代碼。
3.7.3 構建存在依賴的目標
》》》》如果構建的目標依賴於庫目標
則需要通過target_link_libraries()
來指定構建的目標所鏈接到的庫。通常這條指令是在目標構建或庫構建指令之後添加。
target_link_libraries(yumi_gripper_test ${catkin_LIBRARIES} ${Boost_LIBRARIES})
簡單的說就是構建當前目標依賴哪些額外的資源,比如說用C++寫了很多個類,想在一個Main文件中使用所有的類來完成一種功能,那每一個類對應的頭文件與源文件就可以編譯成對應的庫,供Main文件生成的執行目標進行鏈接。
add_library(yumi_control_node src/yumi_control_node.cpp) // 寫的類文件
add_dependencies(yumi_control_node ${catkin_EXPORTED_TARGETS}) // 添加外部依賴,使用了其它包生成的.srv文件
target_link_libraries(yumi_control_node ${catkin_LIBRARIES} ${Boost_LIBRARIES}) // 添加生成鏈接
add_executable(yumi_test src/yumi_test.cpp) // 構建使用yumi_control_node類的主文件
add_dependencies(yumi_test ${catkin_EXPORTED_TARGETS}) // 添加外部依賴,其中使用了其它包生成的.srv文件
target_link_libraries(yumi_test yumi_control_node ${catkin_LIBRARIES} ${Boost_LIBRARIES}) // 添加生成鏈接,加入由類生成的依賴庫
》》》》如果構建的目標依賴於其它目標或軟件包所生成的.msg、.srv、.action文件
則需要通過添加明確的依賴關係,以確保正確的構建順序。這種情況是普遍存在的,除非當前的程序包不使用ROS提供的任何信息格式。
add_dependencies(yumi_gripper_test ${catkin_EXPORTED_TARGETS})
》》》》如果構建的目標依賴於當前軟件包所生成的.msg、.srv、.action文件
則需要爲當前目標的構建添加明確的依賴關係,以確保需要的.msg、.srv等文件預先進行生成,保證軟件包正確的構建順序。
add_dependencies(yumi_gripper_test ${${PROJECT_NAME}_EXPORTED_TARGETS})
》》》》如果構建的目標以上兩種依賴都存在
需要將兩種依賴關係,都明確的添加到目標構建的過程中。
add_dependencies(yumi_gripper_test ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
3.8 代碼測試
構建系統中定義了一個專門用於構建測試目標的宏,基於gtest,如下所示:
if(TESTING_ENABLE)
catkin_add_gtest(gripper_test test/gripper_test.cpp)
endif()
3.9 軟件包部署配置
這一部分準備後面再研究,想把自己寫的ROS軟件包打包成binary的形式,可以在需要使用的電腦上直接部署使用,而不是把整個源代碼copy過去。
4. 參考資料
[1] catkin/CMakeLists.txt
[2] 在ROS中功能包中將類的函數定義與聲明分開文件寫用main.cpp調用如何配置CMakeLists.txt
[3] ROS下的CMakeLists.txt編寫