catkin編譯系統


預安裝

  • Ubuntu 16.04
  • ROS kinetic (base 即可)

從最簡單的例子開始

使用終端命令行進行程序編譯

首先創建一個文件夾 hello_world_tutorial,存放我們的程序

mkdir hello_world_tutorial
cd hello_world_tutorial

 創建 C++ 源文件,名為 hello_world_node.cpp

// 為了與 ROS 交互,需要調用 ROS C++ APIs
#include <ros/ros.h>

// 標准的 C++ main 函數
int main(int argc, char** argv) { // 該命令告訴 ROS 初始化了一個 node,名為 hello_world_node 
  ros::init(argc, argv, "hello_world_node"); // 在一般的 ROS node 程序中,我們會用 ros::NodeHandle nh 來啟動 node 程序, // ros::NodeHandle nh 默認會調用 ros::start() 函數,程序關閉時也會自動調用 ros::shutdown() 函數。 // 我們也可以直接通過 ros::start() 和 ros::shutdown() 來手動控制 node 的開啟和關閉
 ros::start(); // 顯示 hello, world! 信息
  ROS_INFO_STREAM("Hello, world!"); // 用 ros::spin() 保持該程序運行,一直等待處理 subscribe 的數據 // 由於該程序並沒有 sub,所以就是簡單的保持程序不退出而已, 直到接受到終止信號 SIGINT (ctrl-c)
 ros::spin(); // 關閉 node 程序
 ros::shutdown(); // 結束主程序
  return 0; }

 下邊將 C++ 源文件編譯成可執行文件

g++ hello_world_node.cpp -o hello_world_node -I/opt/ros/kinetic/include -L/opt/ros/kinetic/lib -Wl,-rpath,/opt/ros/kinetic/lib -lroscpp -lrosconsole

各參數含義

  • -I<dir> 指定頭文件的搜索路徑
  • -L<dir> 指定靜態庫的搜索路徑
  • -Wl,-rpath,/opt/ros/kinetic/lib 指定共享庫的搜索路徑
  • -lroscpp -lrosconsole 指定需要鏈接的具體的庫文件

編譯之后,生成 hello_world_node 可執行文件。由於程序中生成了 ROS node,而 ROS node 需要與 ROS master 進行通訊注冊,否則會報錯。因此為了正常運行程序,需要先開啟 ROS master

roscore

然后運行 hello_world_node

./hello_world_node

如果一切順利,應該顯示類似如下信息:

[ INFO] [1561908777.116073864]: Hello, world!

上述編譯方式擴展性很差,對於如此簡單的 hello_world 程序,需要設置的參數已經這么多了。而且在 terminal 中書寫比較麻煩,修改也不方便。

上面是使用g++編譯器直接在終端中對源文件進行編譯,通過上面的過程了解到,當編譯的源文件需要鏈接其他庫文件、頭文件時需要額外添加命令以及文件路徑,這樣當需要鏈接的文件比較多時或者編譯文件進行修改重新編譯時,非常不方便,如果能在腳本文件中將編譯命令以及鏈接文件的路徑全部鏈接到,然后在終端直接執行這個腳本文件豈不是更加方便。因此前人進行了改進

改進:使用 Makefile 文件進行編譯

在Makefile中寫編譯規則(頭文件路徑、源文件路徑等),終端中直接make命令執行Makefile文件實現程序編譯

Makefile 編譯方式是將上述編譯命令和參數設置放入一個文件中,然后基於該文件,完成編譯過程。Makefile 文件有自己的一套語法規則,可以實現批量、相對自動化的編譯。

與前述 hello world 程序對應的 Makefile 文件內容如下:

# 聲明要使用的編譯器 CC=g++ # 聲明一些變量,實際上就是對應上述搜索路徑設置 CFLAGS=-I/opt/ros/kinetic/include LDFLAGS=-L/opt/ros/kinetic/lib -Wl,-rpath,/opt/ros/kinetic/lib -lroscpp -lrosconsole # % 作為通配符,代表對一類滿足條件的文件進行操作 # 這是由源文件 *.cpp 編譯成目標文件 *.o 的操作 %.o: %.cpp $(CC) -c -o $@ $< $(CFLAGS) # 也可以不用通配符,具體寫出要編譯的文件 # 這是由目標文件 *.o 通過鏈接 (linking) 操作生成最終的可執行文件 hello_world_node: hello_world_node.o $(CC) -o hello_world_node hello_world_node.o $(LDFLAGS)

對於 Makefile 的介紹,可以參考這里

Makefile 文件的基本格式是

target: pre-req command

即,希望生成 target 文件,依賴 pre-req 文件,通過 command 命令實現。

需要注意的是,Makefile 要求 command 那一行開頭用 TAB 鍵縮進,不能用空格,如果出現如下報錯:

makefile:...: *** missing separator. Stop

說明誤用了空格鍵。如果你跟我一樣用的是 vs code 編輯器,可以在右下角選擇 Indent Using Tabs

將上述 Makefile 文件放在與 hello_world_node.cpp 同一路徑下,然后編譯

make  # 或者指明某個 target 編譯任務,如:  make hello_world_node

Makefile 編譯方式相比於剛才的命令行編譯方式有如下優點:

  • 在設置好 Makefile 的前提下,編譯命令更簡單,只需要 make,不必每次都輸入一長串命令
  • Makefile 中將編譯和鏈接分開進行,如果項目中包含多個 c++ 源文件,改動了其中的一個,只需要重新生成改動文件的目標文件 (*.o) 即可,其他源文件不需要重新編譯,然后基於更新之后的目標文件,生成新的可執行文件。也就是說,如果源文件沒有改變,就不會浪費時間更新目標文件。

在書寫上邊的 Makefile 文件時,我們依然要明確設定頭文件和 library 的搜索路徑。為了進一步簡化這個過程,我們可以在 Makefile 中使用 pkg-config 設置搜索路徑。

改進:在 Makefile 中使用 pkg-config 設置搜索路徑

通過pkg-config變量設置搜索路徑,改寫Makefile文件,簡化了一些書寫改變不大

實際上,library 對應的搜索路徑包含在與該 library 對應的.pc 文件中,例如
roscpp library 對應的 .pc 文件為 /opt/ros/kinetic/lib/pkgconfig/roscpp.pc,里面內容如下

prefix=/opt/ros/kinetic Name: roscpp Description: Description of roscpp Version: 1.12.14 Cflags: -I/opt/ros/kinetic/include -I/usr/include Libs: -L/opt/ros/kinetic/lib -lroscpp -lpthread /usr/lib/x86_64-linux-gnu/libboost_chrono.so ... Requires: cpp_common message_runtime rosconsole roscpp_serialization ...
可以看出,這個 .pc 文件里面的 CflagsLibs 條目就是調用 roscpp 時要設置的路徑信息。我們可以通過 pkg-config 這個工具查找 roscpp.pc 文件,然后提取其中的路徑信息,放入 Makefile 中,這樣就避免了手動輸入。例如
$ pkg-config --cflags roscpp -I/opt/ros/kinetic/include $ pkg-config --libs roscpp -L/opt/ros/kinetic/lib -lroscpp -lpthread /usr/lib/x86_64-linux-gnu/libboost_chrono.so ...

 因此,我們可以改寫 Makefile 文件如下:

CC=g++ # 通過 pkg-config 設置相應的路徑信息 CFLAGS=$(shell pkg-config --cflags roscpp) LDFLAGS=$(shell pkg-config --libs roscpp) %.o:  %.cpp $(CC) -c  -o  $@  $< $(CFLAGS) hello_world_node: hello_world_node.o $(CC) -o hello_world_node hello_world_node.o $(LDFLAGS)

然后依然用 make 命令編譯文件,與前邊編譯方式相同,最終也是生成 hello_world_node 可執行文件。

在使用 pkg-config 時需要確保它能夠找到相應的 library。pkg-config 有自己的搜索 library 的路徑,存放在環境變量 PKG_CONFIG_PATH中,可以通過 echo 命令查看

echo $PKG_CONFIG_PATH

如果我們安裝完 ROS,並且運行了source /opt/ros/kinetic/setup.bash, ROS 相關的 library 對應的 .pc 文件就被加入了 pkg-config 的搜索路徑。通過 pkg-config <library> 就可以搜到相應的信息。

盡管 pkg-config 簡化了 Makefile 中設置頭文件和 library 路徑的過程,但是 Makefile 文件中后續的編譯過程依然需要手動設置。另外這里手動書寫的編譯命令是與操作系統平台相關的,Linux 中的編譯命令不能在 Windows 中使用,這就導致 Makefile 不能跨平台使用。

改進: CMake 跨平台編譯方式

Makefile不同跨平台使用,所以CMake應勢而生,解決程序跨平台編譯問題。

CMake 的一個功能是自動生成 Makefile 文件。另外,CMake 可以在 Linux 、Windows 和 Mac OS 上使用。

要使用 CMake,首先要創建一個 CMakeLists.txt 文件,包含必要的編譯設置。
與上述 hello_world_node 例子對應的 CMakeLists.txt 內容如下:

# 聲明 CMake API 版本 cmake_minimum_required(VERSION 2.8) # 聲明項目名稱 project(hello_world_tutorial) # 搜索依賴 library (即 roscpp) 的信息 # 與 pkg-config 功能類似,但可以跨平台使用 # pkg-config 查找 .pc 配置文件,而 find_package 查找 .cmake 配置文件 find_package(roscpp REQUIRED) # 搜索 roscpp 中調用的頭文件 include_directories(${roscpp_INCLUDE_DIRS}) # 設置待生成的可執行文件名字 add_executable(hello_world_node hello_world_node.cpp) # 設置編譯過程中 linking library target_link_libraries(hello_world_node ${roscpp_LIBRARIES})
其中 find_package(roscpp REQUIRED) 會自動定義幾個變量,包括 roscpp_INCLUDE_DIRSroscpp_LIBRARY_DIRSroscpp_LIBRARIES。在 CMakeLists.txt 中可以直接使用這些變量。REQUIRED 參數的作用是在找不到相應 library 時停止並報錯,提示
-- Configuring incomplete, errors occurred! See also ".../CMakeFiles/CMakeOutput.log".

 如果不加 REQUIRED,則只會提示找不到 library,整個過程並不會停止,顯示信息如下:

-- Configuring done -- Generating done -- Build files have been written to: ...

盡管顯示各種 done,由於沒有找到必要的 library ,后續的編譯肯定會不成功。

通過 CMakeLists.txt 進行編譯時會產生一些中間文件,如果都放在 .cpp 源文件目錄下,會顯得很雜亂。最好單獨建一個文件夾,存放這些編譯文件。例如在 .cpp 源文件和 CMakeLists.txt 同一路徑下新建 build 文件夾。新的路徑結構如下:

├── build
├── CMakeLists.txt
└── hello_world_node.cpp

CMakeLists.txt 中的 find_package 之所以能找到相應的 library,是因為已經設置了搜索路徑,存放在環境變量 CMAKE_PREFIX_PATH 中,通過 echo $CMAKE_PREFIX_PATH 可以顯示當前 find_package 使用的搜索路徑。在安裝完 ROS 之后,source 命令會自動將 ROS 相關的 library 加入上述搜索路徑中。

通過 cmakeCMakeLists.txt 自動生成編譯文件 Makefile:

cd build       # 進入剛才創建的 build 文件夾
cmake ..      # 運行 cmake,它會調用上一層路徑中的 CMakeLists.txt 文件 

運行完上述命令以后,產生了一些新文件,路徑結構如下:

├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
└── hello_world_node.cpp

可以看到自動產生了 Makefile ,此時就可以用 make 命令編譯文件了。

這里借張圖展示一下 CMake 編譯方式跨平台的能力
( From: https://cgold.readthedocs.io/en/latest/overview/cmake-can.html)

 
到了這里,我們就已經解決了最初的項目需求:讓 C++ 程序將內部變量以 ros topic 的形式發布出來。基本步驟:
1 . 改寫 C++ 程序,加入 ROS 元素,如 ros 頭文件,msg 頭文件等,設置 ros::init,
ros::NodeHandle ,pub msg 等,這些 ROS 元素可以使 C++ 程序在 ROS master 中以 ROS node 的形式注冊。
2. 我們原來的 C++ 程序有自己的 CMakeLists.txt 文件,在其中添加依賴的 ROS library。
3. 用基本的 cmake 方式編譯即可。

改進:針對 ROS 系統的 Catkin 編譯方式

ROS 的 Catkin 編譯系統的一個特點是將程序做成 package (稱為 catkin package 或者 ROS package) 的形式,可以理解成模塊化。典型的 ROS workspace 中包含 src, build, devel 三個文件夾,在分享時只需要分享 src 中的某個 package 即可,所有的編譯信息都在此 package 中。一個 package 在編譯時可以指定依賴於另一個 package。
另外,由於 ROS 中程序以及 library 變動比較頻繁,不太適合在整個系統層面安裝編譯之后的文件,通過 source devel 文件中的 setup.bash 文件可以告知系統去哪里查找相應的文件,避免了系統級的安裝 。

要構造 ROS package,我們首先要修改 CMakeLists.txt 文件如下:

cmake_minimum_required(VERSION 2.8) project(hello_world_tutorial) # 要用到 catkin find_package(catkin REQUIRED) # 聲明該項目為一個 catkin package catkin_package() find_package(roscpp REQUIRED) include_directories(${roscpp_INCLUDE_DIRS}) add_executable(hello_world_node hello_world_node.cpp) target_link_libraries(hello_world_node ${roscpp_LIBRARIES})

另外,還需要添加一個 package.xml 文件,指明該 package 在編譯和運行時依賴於哪些其他 package,同時也包含該 package 的一些描述信息,如作者、版本等。內容如下:

<package>
  <name>hello_world_tutorial</name>
  <maintainer email="you@example.com">Your Name</maintainer>
  <description> A ROS tutorial. </description>
  <version>0.0.0</version>
  <license>BSD</license>

  <!-- Required by Catkin -->
  <buildtool_depend>catkin</buildtool_depend>

  <!-- Package Dependencies -->
  <build_depend>roscpp</build_depend>
  <run_depend>roscpp</run_depend>
</package>

現在路徑結構如下:

├── build
├── CMakeLists.txt
├── hello_world_node.cpp
└── package.xml

跟之前一樣,進入 build 文件夾中,用 cmake + make 方式編譯

cd build
cmake ..
make

編譯結束之后會發現,並沒有在 build 根目錄下生成可執行文件。與普通的 cmake 編譯不同,catkin 編譯會生成一個 devel 文件夾,這里包含了生成的可執行文件,以及作為 library 使用的配置文件 .pc.cmake

對於我們的 hello_world_node package 來說,上述文件路徑如下:

  • 可執行文件:devel/lib/hello_world_tutorial/hello_world_node
  • .pc 配置文件:devel/lib/pkgconfig/hello_world_tutorial.pc
  • .cmake 配置文件:devel/share/hello_world_tutorial/cmake/hello_world_tutorialConfig.cmake

當作為 library 使用時,只需要將路徑 .../devel/lib/pkgconfig 添加到 PKG_CONFIG_PATH 環境變量中,或者將 .../devel 添加到 CMAKE_PREFIX_PATH 變量中。實際上,我們不需要手動設置這些環境變量,只需要通過 source devel 文件夾下的 setup.bash 文件即可,source setup.bash 不僅添加了以上兩個環境變量,還有諸如 ROS_PACKAGE_PATHPYTHONPATH等。

source 之后,由於該 package 加入了 ROS_PACKAGE_PATH,此時可以通過 ROS 相關的命令對該 package 進行操作,如 rospack find ..., rosrun <package> <exe>, roscd <package> 等。

為了更有條例地存放不同類型的文件,可以建立三個文件夾 src, build, devel,其中 src 存放源文件,源文件又以 package 為單位分別存放,build 存放編譯過程中的中間文件,devel 存放最終生成的可執行文件和配置文件。這就是所謂的 out-of-source 編譯方式。在分享、發布程序時,我們可以很清楚的知道哪些是必要的源文件,哪些是最終生成的可執行文件和 library,哪些是作為副產品存在的中間文件。

路徑結構如下:

├── build
├── devel
└── src
    └── hello_world_tutorial
        ├── CMakeLists.txt
        ├── hello_world_node.cpp
        └── package.xml

 在做了以上路徑設置之后,在編譯時,我們就需要特別指定各類文件對應的路徑:

cd build cmake ../src/hello_world_tutorial  -DCATKIN_DEVEL_PREFIX=../devel make

catkin 的特點還體現在編譯多個 package 中。

我們可以在 src 文件夾中再添加一個 catkin package,這里我們就直接從網上下載一個簡單的 package:

git clone https://github.com/ros/robot_state_publisher.git -b kinetic-devel

現在路徑結構如下:

├── build
├── devel
└── src
    ├── hello_world_tutorial
    │   ├── CMakeLists.txt
    │   ├── hello_world_node.cpp
    │   └── package.xml
    └── robot_state_publisher
        ├── CHANGELOG.rst
        ├── CMakeLists.txt
        ├── doc.dox
        ├── include
        ├── package.xml
        ├── src
        └── test
上述兩個 package 各有一個 CMakeLists.txt 文件,按照普通的 cmake 方法,我們不能同時編譯它們。catkin 為我們提供了一個更高層的 CMakeLists.txt 文件,可以從 ROS 安裝文件夾中以超鏈接的形式復制過來,放在更高層的 src 目錄下:
cd src ln -s  /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake  CMakeLists.txt

 實際上,ROS 為我們提供了專門的命令,實現上述操作:

cd src
catkin_init_workspace src

 此時,路徑結構如下:

├── build ├── devel └── src ├── CMakeLists.txt -> /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake ├── hello_world_tutorial └── robot_state_publisher

這就是典型的 ROS workspace 的結構。
此時就可以使用 cmake 同時編譯 src 中的所有 package 了,命令如下:

cd build cmake ../src  -DCATKIN_DEVEL_PREFIX=../devel make

將以上三個命令合並在一起就是 ROS 中的 catkin_make 命令。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM