ROS2における共有ライブラリ:--library-nodeオプションを使用しない方法

【[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)】

【次:[ROS2の共有ライブラリを使用したプログラムの作成]】

[ROS2の共有ライブラリを使用したプログラムの作成]:https://qiita.com/NeK/items/dfeb32b7762864641e42

Eloquentまでは[こちら](https://qiita.com/NeK/items/fb5dac0158497c1ff77f)
更新履歴:
2020/07/24:ament_export_dependenciesについて必要ないかのように書いていたけど，やっぱり必要だったので修正

---

ROS2の共有ライブラリを作成する．

共有ライブラリはクラスで作成するので，これまでのようにROS1風のものはない．
また，共有ライブラリは色々なところで使用されるのでnamespaceを用いている．
本当はこれまででもnamespaceを使用した方がよかったのだが，自分が使用するターゲットのみの場合必須でないので省略してきた．
namespaceに関しては[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)の「知っといてほしい知識」などを参考に．

# 共通ライブラリ概要
ここで作成する共有ライブラリの概要は以下のとおり．
ちなみに作成したライブラリを使用したターゲットはここで作成せず共有ライブラリのみの作成とする．
また作成するクラスは一つでも良かったが，後々のために二つ作成する．基本的には一つのライブラリに複数の機能が収められる．

- 共有ライブラリのパッケージ名
    - minimal_comp
- 共有ライブラリ名
    - minimal_comp_node
- 共有ライブラリで使用できる機能・クラスを収めたソースファイル
    - minimal_comp_node1.hppと.cpp
        - クラス名:MinimalCompNode1
    - minimal_comp_node2.hppと.cpp
        - クラス名:MinimalCompNode2
- パッケージ共通のnamespace
    - minimal_comp

# 準備

```bash:terminal
$ cd ~/ros2_studies_ws/
$ ros2 pkg create minimal_comp --dependencies rclcpp
```

# 共有ライブラリ

作成物:[github.com](https://github.com/nek009/minimal_comp)の以下のファイル

- include/visibility.h
- include/minimal_comp_node1.hpp
- include/minimal_comp_node2.hpp
- src/minimal_comp_node1.cpp
- src/minimal_comp_node2.cpp

## 共有ライブラリ独特の手順

1. visibility.hを作成する
    - 公式からDLして自分のパッケージ用にアレンジするのが楽
2. 普通にクラスを作成する
    - 外部に公開するのでsrc/以下にヘッダファイルを置くのではなくinclude/[パッケージ名]/以下にヘッダファイルを置く．
3. 作成したクラスを共有ライブラリ用に少し追記する
    - visibility.hで書いたPUBLICに相当する記述(ここではMINIMAL_COMP_PUBLICになる)をpublicな関数(外部に公開する機能)の前に書く

## visibility.hの用意

自分で以下のファイルを用意するのでもよいが，[公式のexamplesにあるminimal_composition](https://github.com/ros2/examples/tree/master/rclcpp/composition/minimal_composition/include/minimal_composition)からvisibility.hをDLして自分用に改変することを推奨．

```bash:terminal
$ curl https://raw.githubusercontent.com/ros2/examples/master/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h > visibility.h
$ sed s/MINIMAL_COMPOSITION/MINIMAL_COMP/g visibility.h > my_visibility.h
my_visibility.hの内容を確認
$ mv my_visibility.h visibility.h
```

一度にやるなら以下のとおり．

```bash:terminal
$ curl https://raw.githubusercontent.com/ros2/examples/master/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h | sed s/MINIMAL_COMPOSITION/MINIMAL_COMP/g > visibility.h
```

今回はパッケージ名がminimal_compであったので上記のようにsedを使用した．ここは自分のパッケージ名に合わせて変更すること．

```bash
$ sed s/MINIMAL_COMPOSITION/[自分のパッケージ名]/g visibility.h > my_visibility.h
```

自分で作成する場合は以下の通り．
ここでもパッケージ名minimal_compで作成しているので，自分のパッケージ名に合わせて適宜変更

```c++:visibility.h
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef MINIMAL_COMP__VISIBILITY_H_
#define MINIMAL_COMP__VISIBILITY_H_

#ifdef __cplusplus
extern "C"
{
#endif

// This logic was borrowed (then namespaced) from the examples on the gcc wiki:
//     https://gcc.gnu.org/wiki/Visibility

#if defined _WIN32 || defined __CYGWIN__

  #ifdef __GNUC__
    #define MINIMAL_COMP_EXPORT __attribute__ ((dllexport))
    #define MINIMAL_COMP_IMPORT __attribute__ ((dllimport))
  #else
    #define MINIMAL_COMP_EXPORT __declspec(dllexport)
    #define MINIMAL_COMP_IMPORT __declspec(dllimport)
  #endif

  #ifdef MINIMAL_COMP_DLL
    #define MINIMAL_COMP_PUBLIC MINIMAL_COMP_EXPORT
  #else
    #define MINIMAL_COMP_PUBLIC MINIMAL_COMP_IMPORT
  #endif

  #define MINIMAL_COMP_PUBLIC_TYPE MINIMAL_COMP_PUBLIC

  #define MINIMAL_COMP_LOCAL

#else

  #define MINIMAL_COMP_EXPORT __attribute__ ((visibility("default")))
  #define MINIMAL_COMP_IMPORT

  #if __GNUC__ >= 4
    #define MINIMAL_COMP_PUBLIC __attribute__ ((visibility("default")))
    #define MINIMAL_COMP_LOCAL  __attribute__ ((visibility("hidden")))
  #else
    #define MINIMAL_COMP_PUBLIC
    #define MINIMAL_COMP_LOCAL
  #endif

  #define MINIMAL_COMP_PUBLIC_TYPE
#endif

#ifdef __cplusplus
}
#endif

#endif  // MINIMAL_COMP__VISIBILITY_H_
```

## 共有ライブラリ化する二つのクラス
注目点はpublicとしているコンストラクタの前．visibility.hにあるMINIMAL_COMP_PUBLICが付いていることを確認．

```c++:minimal_comp_node1.hpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/visibility.h" // 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode1 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1(
    const std::string &node_name="",
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

```c++:minimal_comp_node1.cpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/minimal_comp_node1.hpp"

namespace minimal_comp {

MinimalCompNode1::MinimalCompNode1(
  const std::string &node_name,
  const rclcpp::NodeOptions& options
): Node("minimal_comp1", node_name, options)
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 1 test");
}

}
```

```c++:minimal_comp_node2.hpp
#include <rclcpp/rclcpp.hpp>
// 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode2 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode2(
    const std::string &node_name="",
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

```c++:minimal_comp_node2.cpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/minimal_comp_node2.hpp"

namespace minimal_comp {

MinimalCompNode2::MinimalCompNode2(
  const std::string &node_name,
  const rclcpp::NodeOptions& options
): Node("minimal_comp2", node_name, options)
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 2 test");
}

}
```

### 概要
簡単なメッセージを出力するだけのもの

# package.xmlとCMakeLists.txt
以下は追加した・重要な部分のみ抜粋．
詳しくは[ament_cmake User Documentation](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)を参照．

## package.xml

```xml:package.xml
<package format="3">
  <depend>rclcpp</depend>
```

build_dependタグからdependタグへの変更については[ROS2 package.xmlとCMakeLists.txtのチートシート](https://qiita.com/NeK/items/583f1492a3faf85aa7db)を参照のこと．

### CMakeLists.txt

```CMakeLists.txt
find_package(rclcpp REQUIRED)

add_library(minimal_comp_node SHARED
  src/minimal_comp_node1.cpp
  src/minimal_comp_node2.cpp
)
target_compile_definitions(minimal_comp_node
  PRIVATE "MY_LIBRARY_BUILDING_LIBRARY"
)
target_compile_options(minimal_comp_node PUBLIC -Wall)
ament_target_dependencies(minimal_comp_node
  rclcpp
)

target_include_directories(minimal_comp_node
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

if(NOT WIN32)
  ament_environment_hooks(
    "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}"
  )
endif()

ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
ament_export_dependencies(
  rclcpp
)

install(
  DIRECTORY include/
  DESTINATION include
)
install(TARGETS
  minimal_comp_node
  EXPORT export_${PROJECT_NAME}
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include
)
```

<details><summary>ament_export_*に関するFoxy Fitzroyからの変更について</summary><div>
Eloquent Elusorまでは共有ライブラリのエクスポートに関するコマンドは以下の~~4つ~~3つであった．

* ament_export_interfaces
* ament_export_libraries
* ament_export_inclujde_directories
* ~~ament_export_dependencies~~
    * これは必要だった！

これがament_export_targetsの一つになった．使い方としては，ament_export_interfacesの代わりにament_export_targetsを使うような感じらしい．

詳しくは公式のDistributionのニュースのうち，[Foxy Fitzroyでの変更:Classic CMake vs. modern CMake](https://index.ros.org/doc/ros2/Releases/Release-Foxy-Fitzroy/#id10)を参照のこと．
</div></details>

### 概要
通常のfind_package，ament_target_dependencies以外に以下の要素がある．

- add_library
- target_compile_definitions
- target_compile_options
- target_include_directories
- ament_environment_hooks
- ament_export_interfaces
- ament_export_dependencies
- ament_export_libraries
- ament_export_include_directories
- install 2種類

### 説明:add_library
add_executableと同じように他で使用するライブラリ名を付けつつコンパイルに必要なソースファイルを記述．
ライブラリ名を使用するのは以下の要素．

- target_compile_definitions
- target_compile_options
- ament_target_dependencies
- target_include_directories
- ament_export_libraries
- install

### 説明:target_compile_*
コンパイル時の設定．他でもそのまま使用．

### 説明:target_include_directories
include/[パッケージ名]/以下のヘッダファイルは外部に公開するものである．他のパッケージはtarget_include_directoriesによって外部公開用のヘッダファイルにアクセスできる．共有ライブラリ自身にとってもinclude/[パッケージ名]/以下にヘッダファイルがあるので(src/以下にはないので)，target_include_directoriesを指定する必要がある．

### 説明:ament_environment_hooks
OS依存の処理っぽい．他でもそのまま使用．

### 説明:install
最初のinstallは共有ライブラリを使用するパッケージ用にヘッダファイルをインストールするためのコマンド．

```
install(
  DIRECTORY include/
  DESTINATION include
)
```

二つ目のinstallは作成したライブラリをインストールするためのコマンド．
特にEXPORTはament_export_interfacesで使うためのもの．
名前はなんでもよいがライブラリ名にexport_を付けたものが用いられる．
理由は[ament_cmake User Documentation](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)のBuilding a Libraryを参照のこと．

```
install(TARGETS
  minimal_comp_node
  EXPORT export_minimal_comp_node
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include
)
```

### 説明:ament_export_targets
ament_export_targetsは，installのEXPORTで指定したものにHAS_LIBRARY_TARGETを．

### 説明:ament_export_dependencies
共有ライブラリを使う側に依存関係を知らせ，使う側で改めて依存関係処理しなくて済むようにするもの．


# ビルド
```
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-up-to minimal_comp
$ . install/setup.bash
```

# 複数のライブラリを作成する場合
一つのライブラリの中に複数の機能(クラス)を詰め込むのではなく，複数のライブラリ自体を一つのパッケージで作成する場合．

一つのライブラリのやり方の中で，以下に注目・変更

- ament_export_targets
- install

## 概要
作成したライブラリをインストールするためのinstallにて．

- TARGETS
    - add_libraryのライブラリ名を複数記述
- EXPORT
    - この項は全体で一つ．
    - よって，例えばexport_[package名]などに．
- ament_export_targets
    - EXPORTで名づけしたものを記述
    - よって上記の例ではexport_[package名]

## 例
抜粋

```CMakeLists.txt
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)

install(TARGETS
  model1
  model2
  EXPORT export_${PROJECT_NAME}
...
)
```




# 参考

- [teratail](https://teratail.com/questions/38812)
    - curlで特定のファイルをDL
- [curlコマンド 使い方メモ](https://qiita.com/yasuhiroki/items/a569d3371a66e365316f)
- [sed, awk, grepの使い分け](https://qiita.com/lca367/items/e174ce6985a1e7f3c441)