1. 程式人生 > >ROS Catkin 教程之 CMakeLists.txt

ROS Catkin 教程之 CMakeLists.txt

1. 概覽

CMakeLists.txt 是用 CMake 構建系統構建 ROS 程式包的輸入檔案。任何相容 CMake 的包都包含一個或多個 CMakeLists.txt 檔案,用以描述怎樣構建和安裝程式碼。catkin 專案採用標準的 vanilla CMakeLists.txt 檔案,並帶有一些額外的約束。

2. 總體結構和順序

你的 CMakeLists.txt 檔案必須遵從以下格式,否則你的程式包將無法正確編譯。這些配置命令是有順序的。

  1. Required CMake Version (cmake_minimum_required)
  2. Package Name (project())
  3. Find other CMake/Catkin packages needed for build (find_package())
  4. Enable Python module support (catkin_python_setup())
  5. Message/Service/Action Generators (add_message_files(), add_service_files(), add_action_files())
  6. Invoke message/service/action generation (generate_messages())
  7. Specify package build info export
    (catkin_package())
  8. Libraries/Executables to build (add_library()/add_executable()/target_link_libraries())
  9. Tests to build (catkin_add_gtest())
  10. 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)