1. 程式人生 > >ROS下的CMakeList.txt編寫

ROS下的CMakeList.txt編寫

目錄

一、 概述

CMake構建系統通過ROS包中的CMakeList.txt來構建軟體包。互相依賴的包都包含一個或者多個CMakeList.txt來描述如何編譯程式碼和如何安裝。在catkin

專案中,CMakeList.txt 符合標準的vanilla CMakeList.txt 格式,但稍微有點不同。

二、 整體結構和命令一覽

在編寫CMakeLists.txt時必須遵循特定的格式,否則軟體包將無法正確構建和編譯。一個完整的ROS下CMakeList.txt由下面內容組成
1. 所需CMake版本(cmake_minimum_required)
2. 軟體包名稱(project())
3. 查詢構建所需的其他CMake / Catkin軟體包(find_package())
4. 啟用Python模組支援(catkin_python_setup())
5. 訊息/服務/動作生成器(add_message_files(),add_service_files(),add_action_files())
6. 生成訊息/服務/動作等自定義訊息(generate_messages())
7. 指定包的構建資訊輸出(catkin_package())
8. 要建立的庫/可執行檔案(add_library()/ add_executable()/ target_link_libraries())
9. 測試(catkin_add_gtest())
10. 安裝規則(install())

三、 CMake版本

CMakeLists.txt檔案必須以CMake的版本開頭。下面表示的是Catkin需要2.8.3或更高的版本。

cmake_minimum_required(VERSION 2.8.3)

四、 軟體包名稱

繼而需要指定CMake工程名,一般取作包名。例如我們的專案名叫做robot_brain

project(robot_brain)

而在CMake中,可以在CMake文字中使用變數${PROJECT_NAME}來引用專案名稱。

五、 查詢相關的CMake包

接下來我們要用find_package指令來指定在構建專案過程中依賴了哪些其他的包,在ROS中,catkin必備依賴,所以雷打不打地我們寫上catkin REQUIRED

find_package(catkin REQUIRED)

在此基礎上,如果我們還需要依賴其他包(或者元件),我們就在 上述的包後面繼續新增包(元件)名即可:

find_package(catkin REQUIRED COMPONENTS nodelet)

或者可以寫成

find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)

值得注意一點的是:這裡新增的包用於構建包使用,而不是新增執行時依賴項。

你也可以做:

find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)

如何寫,看個人習慣吧,如果不考慮篇幅,其實我更喜歡把所有包(元件)列成一列,直觀。在普通的CMakeLists.txt中是沒有find_package()的,那麼,catkin中的這個語句有啥作用呢?

5.1 那find_package()作何用?

如果CMake通過find_package找到一個包,則會自動生成有關包所在路徑的CMake環境變數,環境變數描述了包中標頭檔案的位置,原始檔的位置,包所依賴的庫以及這些庫的路徑。
這些變數名稱以< PACKAGE NAME >_< PROPERTY >的形式出現:
- < NAME >_FOUND - 如果找到庫,則設定為true,否則為false
- < NAME > _INCLUDE_DIRS或 _INCLUDES - 包匯出的包含路徑
- < NAME > _LIBRARIES或 _LIBS - 由包匯出的庫
- < NAME > _DEFINITIONS -
- …

5.2為啥Catkin包是元件形式?

實際上,Catkin的包並不是catkin的真正組成部分。而catkin採用CMake的元件功能,主要是為了節省打字時間。
將Catkin的包作為一種元件看待是很有好處的,當你找到包的時候,各種以catkin為字首的新增到CMakeLists.txt中了。以前述nodelet包為例:

find_package(catkin REQUIRED COMPONENTS nodelet)

catkin執行到這個語句時,就會匯出的nodelet的include路徑,庫等也新增到catkin字首的變數中。例如,catkin_INCLUDE_DIRS不僅包含catkin的include路徑,還包含了nodelet,這在後面會用到。
當然,我們可以只選擇找nodelet的:

find_package(nodelet)

很可惜這樣的做法會令nodelet路徑,庫等不會被新增到catkin變數中,從而生成了例如nodelet_INCLUDE_DIRS,nodelet_LIBRARIES這樣的變數等。

5.3Boost庫

如果使用C ++和Boost,則需要用find_package()來找Boost庫,並指定Boost中的元件。 舉個栗子,如果想使用Boost執行緒,就可以寫成:

find_package(Boost REQUIRED COMPONENTS thread)

六、catkin_package()

catkin_package()是一個catkin提供的CMake巨集,用於將catkin特定的資訊資訊輸出到構建系統上,用於生成pkg配置檔案以及CMake檔案。

這個命令必須在add_library()或者add_executable()之前呼叫,該函式有5個可選引數:

  • INCLUDE_DIRS - 匯出包的include路徑
  • 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”是匯出標頭檔案的地方。 ${PROJECT_NAME}根據project中的內容生成,此處是rrobot_brain。“roscpp”+“nodelet”是用來構建/執行此程式包的catkin包,而“eigen”+“opencv”是用於構建/執行此程式包的非catkin包。

七、指定構建目標

構建目標可以有多種形式,但通常主要有以下兩種:

  • 執行檔案目標 - 可以執行的程式
  • 庫目標 - 可在構建和/或執行時給可執行目標使用的庫

7.1目標命名

值得重點關注的是,catkin中的構建目標的名稱必須是唯一的,但是,但構建目標命名的唯一性只能在CMake內部進行。可以使用set_target_properties()函式將目標重新命名為其他目標:
再舉個栗子:

set_target_properties(rviz_image_view
                      PROPERTIES OUTPUT_NAME image_view
                      PREFIX "")

這時就將rviz_image_view的名稱更改為image_view了。

7.2自定義輸出目錄

一般情況下,令輸出目標目錄保持預設就可以了,但有時遇到一些特定情況就得因地制宜。再再舉個栗子,一個包含了Python bindings的庫必須防止在不同的資料夾,才能夠被匯入Python中,因此可以通過下述方式進行:

set_target_properties(python_module_library
  PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})

7.3包含路徑和庫路徑

在生成目標之前,請記得先寫好包含路徑和庫路徑,不然編譯會失敗。

  • 包括路徑 - 標頭檔案
  • 庫路徑 - 構建物件需要用到的庫
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()

link_directories()函式可用於新增額外的庫路徑,但通常不推薦這樣做,因為 所有catkin和CMake軟體包在find_packaged時都會自動新增連結資訊。 只需寫target_link_libraries()中就可以了。但真要寫,就按照下面那樣來寫:

link_directories(~/my_libs)

7.4 可執行目標

要指定必須構建的可執行目標,我們必須使用add_executable()CMake函式。

add_executable(myProgram 
                src/main.cpp 
                src/some_file.cpp)

構建myProgram的目標可執行檔案,3個原始檔構建:src / main.cpp,src / some_file.cpp和src / another_file.cpp。

7.5 庫目標

add_library() 用於指定要構建的庫。

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()自動拉入。

八、訊息、服務和響應

  訊息(.msg),服務(.srv)和響應(.action)檔案在ROS包構建和使用之前需要一個特殊的前處理器構建步驟。 這些巨集的要點是生成程式語言特定的檔案,以便可以利用其選擇的程式語言中的訊息,服務和動作。 構建系統將使用生成器(例如gencpp,genpy,genlisp等)生成繫結。

提供了三個巨集來分別處理訊息,服務和響應:

  • add_message_files
  • add_service_files
  • add_action_files

而在之後必須在下面寫下面巨集,catkin時候會在devel的include資料夾中生成對應的標頭檔案:

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 ...
 ...)

並且在find_package()寫上message_generation元件:

find_package(catkin REQUIRED COMPONENTS message_generation)

package.xml檔案中加上

    <build_depend>message_generation</build_depend>
    <run_depend>message_runtime</run_depend>

  如果構建的物件依賴於其他需要構建訊息/服務/響應的構建物件,則需要向目標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})

  如果您的catkin包滿足上述兩個條件,則需要新增以下兩個依賴關係,即:

add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

8.2 例子

  現在有兩個依賴於std_msgssensor_msgs的訊息MyMessage1.msgMyMessage2.msg,還有一個自定義服務MyService.srvmessage_program是使用這些訊息和服務的指令,以及生成不使用自定義訊息、服務的程式do_not_use_local_messages_program**catkin包,那麼**CMakeLists.txt應該寫成:

# 構建時依賴項
find_package(catkin REQUIRED COMPONENTS          
             message_generation 
             std_msgs 

# 宣告要構建哪些訊息
add_message_files(FILES
                  MyMessage1.msg
                  MyMessage2.msg)

# 宣告構建哪些服務
add_service_files(FILES
                  MyService.srv)

# 宣告生成上述訊息、服務需要依賴的訊息以及服務
generate_messages(DEPENDENCIES 
                    std_msgs 
                    sensor_msgs)

# 宣告執行時依賴項
catkin_package(CATKIN_DEPENDS 
                message_runtime 
                std_msgs sensor_msgs)

# 宣告構建生成的可執行檔名稱以及依賴項
add_executable(message_program src/main.cpp)
add_dependencies(message_program ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

# 宣告構建不需要使用自定義訊息、服務的可執行檔案
add_executable(does_not_use_local_messages_program src/main.cpp)
add_dependencies(does_not_use_local_messages_program ${catkin_EXPORTED_TARGETS})

如果要構建響應,則在包中“action”目錄中建立有一個名為“MyAction.action”的檔案,則必須將actionlib_msgs新增到使用catkin進行find_package的元件列表中,然後在CMakeList.txt中加上下面兩行:

add_action_files(FILES
                 MyAction.action)

此外,該包必須具有對actionlib_msgs的構建依賴。

九、啟用Python模組支援

  如果catkin包使用一些Python模組,您應該建立一個setup.py檔案,並在呼叫catkin_python_setup()之後呼叫generate_messages()和catkin_package()。

十、單位測試

  有一個catkin-specific巨集用於處理名為catkin_add_gtest()的基於gtest的單元測試。

catkin_add_gtest(myUnitTest test / utest.cpp)

十一、可選步驟:指定可安裝的目標

  構建後,構建目標通常被放置在catkin工作區中。但有時候我們希望將目標安裝到系統其他地方(有關安裝路徑的資訊可以在REP 122中找到),以便其他人或本地資料夾可以使用它們來測試。換句話說,如果你想要做一個“make install”的程式碼,你需要指定目標應該生成到哪裡。
這是使用CMake install()函式作為引數完成的:

目標 - 目標是安裝
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/ P R O J E C T N A M E / D E S T I N A T I O N {CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN “.svn” EXCLUDE
)
或者如果包含的子資料夾與包名稱不匹配:
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)

參考文章:
1.ROS中的CMakeLists.txt
2.catkinCMakeLists.txt