ラズパイ用のC/C++開発環境をお手軽に構築する

# 本記事について
Raspberry Pi用のC/C++開発環境の*お手軽な*作り方についてまとめます。C/C++開発にはクロスコンパイル環境を構築するのが一般的ですが、小規模なプロジェクトではかける手間に対してメリットが少ないと思っています。本記事では、できるだけお手軽に環境を整えることを目標としました。 
もちろん、大規模なプロジェクトになったり、仕事で長期間1つのプロジェクトに取り組む場合はクロス環境の方がいいと思います。クロス開発環境の構築は[こちら](https://qiita.com/take-iwiw/items/5b20558f8ab3f27ca4a4)をご参考にしてみてください。

|           | 全てPi上でやる | 一般的なクロス開発環境 | 本記事の手法 |
|:----------|--------------:|:--------------------:|:------------:|
| Coding    | Pi            | ホストPC              | ホストPC |
| Build     | Pi            | ホストPC              | Pi      |
| Debug     | Pi            | ホストPC   | ホストPC/Pi |
| メリット   | 環境構築が不要 | 開発効率がいい         | 環境構築が簡単。デバッグも少しやりやすい       |
| デメリット | 開発効率が悪い | 環境構築に時間がかかる<br>ARM Toolchainのインストールが必要 | 規模が大きくなるとビルドに時間がかかる<br>デバッグにGUIを使えない |

## 本記事でできるようにすること
とにかくお手軽に、ラズパイ用にC/C++の開発環境を整えます

- ホストPC上でコーディング
- ラズパイとのソースファイルの同期(ftp/rsync)
- ホストPCからデバッグ

## 環境
- Raspberry Pi (IPアドレスなどは適宜読み替えてください)
	- IPアドレスは192.168.1.88
	- SSHは有効済み
	- ユーザー名 = pi
	- パスワード = rasp
- Host PC
	- どんな環境/OSでも大丈夫なはず。Windows10で確認
	- 本記事ではエディタとしてVisual Studio Code (VSCode)を使用
		- 他の類似エディタ(SublimeTextやAtom)でも同様のことはできるはずです
	- ssh用のターミナルソフト (TeraTermとかmsysとか。Mac/Linuxなら標準ターミナルでOK)

## 対象とするソースコード
ホスト側で下記のようなシンプルなプロジェクトを用意する

```
MyCProject/
  - main.cpp
```

```cpp:main.cpp
#include <stdio.h>
int main()
{
	printf("Hello World\n");
	for(int i = 0; i < 10; i++) {
		printf("i = %d\n", i);
	}
	printf("Done\n");
	return 0;
}
```

# コーディングとファイルの同期 (on Host PC)
## 出来ればいいこと
以下2つのことが出来ればどういうやり方でも良いです。

- ホストPCでコーディング
- ホストPC -> Raspberry Piへファイルのアップロード

## 方法1
- 好きなエディタでコーディングする
- SFTPソフト(File Zilla)などでRaspberry Piにアップロードする
- または、sftpコマンドかrsyncコマンドでアップロードする

## 方法2: Visual Studio Codeなどのエディタを使う
エディタとアドインソフト(エクステンション機能)を使用する。

- Visual Studio Codeにftp用のエクステンション(例えば、sftp by liximomo)をインストールする
- プロジェクト用のフォルダ(MyCProjectフォルダ)を開く
- Ctrl-Shift-pでコマンドパレットを開いてから、`SFTP: Config`を開く
- sftp.jsonを以下のように編集する

```json:sftp.json
{
    "host": "192.168.1.88",
    "port": 22,
    "username": "pi",
    "password": "rasp",
    "protocol": "sftp",
    "agent": null,
    "privateKeyPath": null,
    "passphrase": null,
    "passive": false,
    "interactiveAuth": false,
    "remotePath": "/home/pi/MyCProject",
    "uploadOnSave": true,
    "syncMode": "update",
    "watcher": {
        "files": false,
        "autoUpload": false,
        "autoDelete": false
    },
    "ignore": [
        "**/.vscode/**",
        "**/.git/**",
        "**/.DS_Store"
    ]
}
```

- VSCodeの左側のエクスプローラーエリアの適当なところで右クリック -> `SFTP: Upload`をクリック
- Raspberry Pi側にコードがアップロードされる
- 設定で`uploadOnSave`をtrueにしているため、この後は保存するたびに自動的にアップロードされる。(がっつり実装するときは一時的にfalseにしておいたほうがいいかもしれません)
- 新規ファイル作成時は手動で`SFTP: Upload`が必要

Visual Studio Codeには他にもftp用エクステンションが複数あるので、好きなのをご使用ください。自前のコマンドでftpアップロードしてもいいです。

# ビルドと実行 (on Raspberry Pi)
ホストPCからsshでラズパイにログインして、コンパイル、実行してみる。gccの`-g`オプションはデバッグするときには必ずつけてください。

```bash
HOST> ssh pi@192.168.1.88
PI> cd MyCProject
PI> gcc -g3 main.cpp
PI> ./a.out
```

# デバッグ (on Host PC)
ビルドしたものが一発で期待通りに動けばいいのですが、通常はそうはいきません。printfデバッグをする場合は、ひたすらコーディングと実行を繰り返せばいいです。デバッグを効率的にやるにはデバッガを使用すべきです。ステップ実行や実行中の変数の状態を見ることが出来ます。ちなみに、小見出しを「on Host PC」としていますが実行自体はラズパイ上で行われます。デバッグ操作をホストPC側からできるという意味です。

## gdbでデバッグする (1)
ホストPCからsshでラズパイにログインして、デバッグする。とりあえず、main関数の先頭にブレークポイントを張ってからステップ実行する例

```bash
HOST> ssh pi@192.168.1.88
PI> cd MyCProject
PI> gdb ./a.out
(gdb) b main
(gdb) run
(gdb) n
(gdb) n
```

## gdbでデバッグする (2)
上記方法で最低限のデバッグが出来るのですが、表示される情報が貧相でやりづらいです。見た目も格好良くありません。gdb用の設定ファイルを変えることで格好いい見た目にすることが出来ます。GDB dashboardというものを使います。方法は設定ファイルをホームディレクトリにコピーするだけです。gdb起動直後はあまり通常の場合と変わりませんが、ブレークするといろいろな情報を表示してくれる画面に代わります。ファイルの配置方法と、ブレーク後に変数iを見てみる例

```bash
PI> cd
PI> wget -P ~ git.io/.gdbinit
PI> cd MyCProject
PI> gdb ./a.out
>>> b main
>>> run
>>> p i
>>> p i = 9
```
![Clipboard01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/663db093-e471-0b18-d08a-d2ac71891514.jpeg)

## gdbでデバッグする (3)
上記方法だとデバッガの詳細出力もコンソール入力も全て同じターミナルになります。複数ターミナルでやると便利です。例えば、ターミナル1でgdbコマンドを入力して、ターミナル2でデバッガの出力をすることが出来ます。まずはターミナルを2つ立ち上げてどちらもラズパイにssh接続します。そしてttyコマンドでターミナル名を取得して出力先を切り替えます。具体的には以下のようにします(2つのターミナルのコマンドをまとめて書いています)

```bash
PI_1> tty	# ターミナル1でターミナル名を確認
/dev/pts/1
PI_0> gdb ./a.out	# ターミナル0でgdb起動
>>> dashboard -output /dev/pts/1	# 出力先を↑で確認したターミナルに切り替える
>>> b main
>>> run
```
これで/dev/pts/1のターミナルにデバッガ出力が表示されるはずです。

![Clipboard02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/a57452e8-d7cd-8214-4cec-60c5d2552b68.jpeg)


## Visual Studio Codeからリモートデバッグする (未)
GDBを使用してホストPC側からデバッグできるようになりました。GDB dashboardのおかげでだいぶ使いやすくはなったのですが、基本的にはコマンドベースです。GUIからできるようにします。

が、、、 お手軽には無理でした。  
ラズパイ側にgdbserverをインストールして実行。ホストPC側ではVSCodeにNative Debug(by webfreak)エクステンションを使用して、ホスト側から実行するところまでは行けたのですが、ブレークポイントを張ったりするのが出来ませんでした。VSCodeをgdbのフロントエンドとして使うことで行けると思ったのですが、ARM用のツールチェインが必要そうです。(バイナリが違うから当然といえば当然なのですが、Cの関数と行数レベルでブレークさせたりはできると思ったんですけどね) 
お手軽にやるにはこれくらいが限界のようです。

⇒ できた (https://qiita.com/take-iwiw/items/5b20558f8ab3f27ca4a4 )

# おまけ
## ビルド用のmakefileとディレクトリ構成一式
これまでの例ではコンパイルはコマンドラインから直接gccで行いましたが、ソースコード数が増えたりした場合にはmakeを使用した方が便利です。以下のような構成をmakeするためのmakefile例です。全部入りでまとめて書きたかったので、サブモジュールとして静的ライブラリ(*.a)と動的ライブラリ(.so)を両方使用しています。使わない場合は適宜サブモジュールのディレクトリを削除してください。中規模なプロジェクトならこれで対応できると思います。もう少しスマートな書き方があるとは思います。
実行するときはライブラリパスを指定してあげる必要があるので、下記コマンドになります。
```
LD_LIBRARY_PATH=. ./a.exe
```


```
/ComplexCProject
 - main.cpp, aaa.cpp
 - makefile
 - makefile_config
 - subStaticModuleA/
   - subA1.cpp, subA2.cpp
   - makefile
 - subDynamicModuleB/
   - subB1.cpp, subB2.cpp
   - makefile
```

```makefile:makefile_config(in_the_top_directory)
## environment
#PLATFORM = WIN_MINGW
PLATFORM = LINUX
BINARY = DEBUG
#BINARY = RELEASE


ifeq ($(PLATFORM), WIN_MINGW)
# for win
MAKE = mingw32-make
#DEL = del
DEL = rm -f
CC = g++
AR = ar
else
# for linux
MAKE = make
DEL = rm -f
CC = g++
AR = ar
endif

ifeq ($(BINARY), DEBUG)
CFLAGS = -g3 -O0 -Wall
else
CFLAGS = -O3 -Wall
endif

LDFLAGS =
LIBS = -lm

OBJS = $(SRCS:.cpp=.o)
#OBJS = *.o

.c.o:
	$(CC) $(CFLAGS) $(INCLUDES) -c $<
.cpp.o:
	$(CC) $(CFLAGS) $(INCLUDES) -c $<
```

```makefile:makefile(in_the_top_directory)
include makefile_config

TARGET = a.out
SRCS = main.cpp aaa.cpp
SUBDIRS = subStaticModuleA subDynamicModuleB
INCLUDES += -I./ -IsubStaticModuleA -IsubDynamicModuleB
SUBS = subStaticModuleA/subModuleA.a
LIBS += -L./ -lsubModuleB

all: sub_target $(TARGET)

$(TARGET): $(OBJS) $(SUBS)
	$(CC) $(LDFLAGS) -o $@ $(OBJS) $(SUBS) $(LIBS)

.PHONY: sub_target
sub_target:
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif

.PHONY: clean
clean:
	$(DEL) *.o *.a *.exe *.dll *.so *.out
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif
```

```makefile:makefile(in_the_subStaticModuleA_directory)
include ../makefile_config

TARGET = subModuleA.a
SRCS = subA1.cpp subA2.cpp
INCLUDES = -I./
SUBS =
SUBDIRS =

all: sub_target $(TARGET)

$(TARGET): $(OBJS) $(SUBS)
	$(AR) r $@ $(OBJS) $(SUBS)

.PHONY: sub_target
sub_target:
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif

.PHONY: clean
clean:
	$(DEL) *.o *.a *.exe *.dll
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif
```

```makefile:makefile(in_the_subDynamicModuleB_directory)
include ../makefile_config

TARGET = ../libsubModuleB.so
SRCS = subB1.cpp subB2.cpp
INCLUDES = -I./
SUBS =
SUBDIRS =

all: sub_target $(TARGET)

$(TARGET): $(OBJS) $(SUBS)
	$(CC) $(LDFLAGS) -shared -fPIC -o $@ $(OBJS) $(SUBS) 

.PHONY: sub_target
sub_target:
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif

.PHONY: clean
clean:
	$(DEL) *.o *.a *.exe *.dll
ifdef SUBDIRS
	$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif
```