ROS Package構建過程中的CMakeLists文件分析

轉眼間又是一個輪迴,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組件,設計目的就是用來節省時間的。放到catkinCOMPONENTS裏,可以使依賴包的頭文件路徑、庫等信息統一包含到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編寫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章